test(service-worker): make mock implementations more similar to actual ones (#37922)
This commit makes the mock implementations used is ServiceWorker tests behave more similar to the actual ones. PR Close #37922
This commit is contained in:
parent
d380e93b82
commit
667aba7508
|
@ -13,7 +13,7 @@ import {AssetGroupConfig, DataGroupConfig, Manifest} from '../src/manifest';
|
|||
import {sha1} from '../src/sha1';
|
||||
import {clearAllCaches, MockCache} from '../testing/cache';
|
||||
import {MockRequest, MockResponse} from '../testing/fetch';
|
||||
import {MockFileSystemBuilder, MockServerStateBuilder, tmpHashTableForFs} from '../testing/mock';
|
||||
import {MockFileSystem, MockFileSystemBuilder, MockServerStateBuilder, tmpHashTableForFs} from '../testing/mock';
|
||||
import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
|
||||
|
||||
(function() {
|
||||
|
@ -352,7 +352,7 @@ describe('Driver', () => {
|
|||
await scope.resolveSelfMessages();
|
||||
scope.autoAdvanceTime = false;
|
||||
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertSawRequestFor('/redirected.txt');
|
||||
|
@ -364,7 +364,7 @@ describe('Driver', () => {
|
|||
it('initializes prefetched content correctly, after a request kicks it off', async () => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertSawRequestFor('/redirected.txt');
|
||||
|
@ -381,7 +381,7 @@ describe('Driver', () => {
|
|||
// Making a request initializes the driver (fetches assets).
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
expect(driver['latestHash']).toEqual(jasmine.any(String));
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertSawRequestFor('/redirected.txt');
|
||||
|
@ -400,7 +400,7 @@ describe('Driver', () => {
|
|||
// Pushing a message initializes the driver (fetches assets).
|
||||
await scope.handleMessage({action: 'foo'}, 'someClient');
|
||||
expect(driver['latestHash']).toEqual(jasmine.any(String));
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertSawRequestFor('/redirected.txt');
|
||||
|
@ -466,7 +466,7 @@ describe('Driver', () => {
|
|||
|
||||
scope.updateServerState(serverUpdate);
|
||||
expect(await driver.checkForUpdate()).toEqual(true);
|
||||
serverUpdate.assertSawRequestFor('ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/foo.txt');
|
||||
serverUpdate.assertSawRequestFor('/redirected.txt');
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
|
@ -562,7 +562,7 @@ describe('Driver', () => {
|
|||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
serverUpdate.assertSawRequestFor('ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/foo.txt');
|
||||
serverUpdate.assertSawRequestFor('/redirected.txt');
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
|
@ -578,7 +578,7 @@ describe('Driver', () => {
|
|||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
});
|
||||
|
||||
it('does not make concurrent checks for updates on navigation', async () => {
|
||||
|
@ -593,7 +593,7 @@ describe('Driver', () => {
|
|||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
|
@ -791,70 +791,75 @@ describe('Driver', () => {
|
|||
});
|
||||
|
||||
it('should bypass serviceworker on ngsw-bypass parameter', async () => {
|
||||
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'true'}});
|
||||
server.assertNoRequestFor('/foo.txt');
|
||||
// NOTE:
|
||||
// Requests that bypass the SW are not handled at all in the mock implementation of `scope`,
|
||||
// therefore no requests reach the server.
|
||||
|
||||
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': 'anything'}});
|
||||
server.assertNoRequestFor('/foo.txt');
|
||||
await makeRequest(scope, '/some/url', undefined, {headers: {'ngsw-bypass': 'true'}});
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypass': null!}});
|
||||
server.assertNoRequestFor('/foo.txt');
|
||||
await makeRequest(scope, '/some/url', undefined, {headers: {'ngsw-bypass': 'anything'}});
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/foo.txt', undefined, {headers: {'NGSW-bypass': 'upperCASE'}});
|
||||
server.assertNoRequestFor('/foo.txt');
|
||||
await makeRequest(scope, '/some/url', undefined, {headers: {'ngsw-bypass': null!}});
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/foo.txt', undefined, {headers: {'ngsw-bypasss': 'anything'}});
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
await makeRequest(scope, '/some/url', undefined, {headers: {'NGSW-bypass': 'upperCASE'}});
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/some/url', undefined, {headers: {'ngsw-bypasss': 'anything'}});
|
||||
server.assertSawRequestFor('/some/url');
|
||||
|
||||
server.clearRequests();
|
||||
|
||||
await makeRequest(scope, '/bar.txt?ngsw-bypass=true');
|
||||
server.assertNoRequestFor('/bar.txt');
|
||||
await makeRequest(scope, '/some/url?ngsw-bypass=true');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar.txt?ngsw-bypasss=true');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
await makeRequest(scope, '/some/url?ngsw-bypasss=true');
|
||||
server.assertSawRequestFor('/some/url');
|
||||
|
||||
server.clearRequests();
|
||||
|
||||
await makeRequest(scope, '/bar.txt?ngsw-bypaSS=something');
|
||||
server.assertNoRequestFor('/bar.txt');
|
||||
await makeRequest(scope, '/some/url?ngsw-bypaSS=something');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar.txt?testparam=test&ngsw-byPASS=anything');
|
||||
server.assertNoRequestFor('/bar.txt');
|
||||
await makeRequest(scope, '/some/url?testparam=test&ngsw-byPASS=anything');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar.txt?testparam=test&angsw-byPASS=anything');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
await makeRequest(scope, '/some/url?testparam=test&angsw-byPASS=anything');
|
||||
server.assertSawRequestFor('/some/url');
|
||||
|
||||
server.clearRequests();
|
||||
|
||||
await makeRequest(scope, '/bar&ngsw-bypass=true.txt?testparam=test&angsw-byPASS=anything');
|
||||
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
|
||||
await makeRequest(scope, '/some/url&ngsw-bypass=true.txt?testparam=test&angsw-byPASS=anything');
|
||||
server.assertSawRequestFor('/some/url&ngsw-bypass=true.txt');
|
||||
|
||||
server.clearRequests();
|
||||
|
||||
await makeRequest(scope, '/bar&ngsw-bypass=true.txt');
|
||||
server.assertSawRequestFor('/bar&ngsw-bypass=true.txt');
|
||||
await makeRequest(scope, '/some/url&ngsw-bypass=true.txt');
|
||||
server.assertSawRequestFor('/some/url&ngsw-bypass=true.txt');
|
||||
|
||||
server.clearRequests();
|
||||
|
||||
await makeRequest(
|
||||
scope, '/bar&ngsw-bypass=true.txt?testparam=test&ngSW-BYPASS=SOMETHING&testparam2=test');
|
||||
server.assertNoRequestFor('/bar&ngsw-bypass=true.txt');
|
||||
scope,
|
||||
'/some/url&ngsw-bypass=true.txt?testparam=test&ngSW-BYPASS=SOMETHING&testparam2=test');
|
||||
server.assertNoRequestFor('/some/url&ngsw-bypass=true.txt');
|
||||
|
||||
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass');
|
||||
server.assertNoRequestFor('/bar');
|
||||
await makeRequest(scope, '/some/url?testparam=test&ngsw-bypass');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar?testparam=test&ngsw-bypass&testparam2');
|
||||
server.assertNoRequestFor('/bar');
|
||||
await makeRequest(scope, '/some/url?testparam=test&ngsw-bypass&testparam2');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar?ngsw-bypass&testparam2');
|
||||
server.assertNoRequestFor('/bar');
|
||||
await makeRequest(scope, '/some/url?ngsw-bypass&testparam2');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar?ngsw-bypass=&foo=ngsw-bypass');
|
||||
server.assertNoRequestFor('/bar');
|
||||
await makeRequest(scope, '/some/url?ngsw-bypass=&foo=ngsw-bypass');
|
||||
server.assertNoRequestFor('/some/url');
|
||||
|
||||
await makeRequest(scope, '/bar?ngsw-byapass&testparam2');
|
||||
server.assertSawRequestFor('/bar');
|
||||
await makeRequest(scope, '/some/url?ngsw-byapass&testparam2');
|
||||
server.assertSawRequestFor('/some/url');
|
||||
});
|
||||
|
||||
it('unregisters when manifest 404s', async () => {
|
||||
|
@ -922,115 +927,172 @@ describe('Driver', () => {
|
|||
});
|
||||
|
||||
describe('cache naming', () => {
|
||||
let uid: number;
|
||||
|
||||
// Helpers
|
||||
const cacheKeysFor = (baseHref: string) =>
|
||||
const cacheKeysFor = (baseHref: string, manifestHash: string) =>
|
||||
[`ngsw:${baseHref}:db:control`,
|
||||
`ngsw:${baseHref}:${manifestHash}:assets:assets:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:${manifestHash}:assets:assets:meta`,
|
||||
`ngsw:${baseHref}:${manifestHash}:assets:other:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:${manifestHash}:assets:other:meta`,
|
||||
`ngsw:${baseHref}:${manifestHash}:assets:lazy_prefetch:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:${manifestHash}:assets:lazy_prefetch:meta`,
|
||||
`ngsw:${baseHref}:${manifestHash}:assets:eager:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:${manifestHash}:assets:eager:meta`,
|
||||
`ngsw:${baseHref}:${manifestHash}:assets:lazy:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:${manifestHash}:assets:lazy:meta`,
|
||||
`ngsw:${baseHref}:42:data:dynamic:api:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:42:data:dynamic:api:lru`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:42:data:dynamic:api:age`,
|
||||
`ngsw:${baseHref}:43:data:dynamic:api-static:cache`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:43:data:dynamic:api-static:lru`,
|
||||
`ngsw:${baseHref}:db:ngsw:${baseHref}:43:data:dynamic:api-static:age`,
|
||||
];
|
||||
|
||||
const createManifestWithBaseHref = (baseHref: string, distDir: MockFileSystem): Manifest => ({
|
||||
configVersion: 1,
|
||||
timestamp: 1234567890123,
|
||||
index: `${baseHref}foo.txt`,
|
||||
assetGroups: [
|
||||
{
|
||||
name: 'eager',
|
||||
installMode: 'prefetch',
|
||||
updateMode: 'prefetch',
|
||||
urls: [
|
||||
`${baseHref}foo.txt`,
|
||||
`${baseHref}bar.txt`,
|
||||
],
|
||||
patterns: [],
|
||||
cacheQueryOptions: {ignoreVary: true},
|
||||
},
|
||||
{
|
||||
name: 'lazy',
|
||||
installMode: 'lazy',
|
||||
updateMode: 'lazy',
|
||||
urls: [
|
||||
`${baseHref}baz.txt`,
|
||||
`${baseHref}qux.txt`,
|
||||
],
|
||||
patterns: [],
|
||||
cacheQueryOptions: {ignoreVary: true},
|
||||
},
|
||||
],
|
||||
dataGroups: [
|
||||
{
|
||||
name: 'api',
|
||||
version: 42,
|
||||
maxAge: 3600000,
|
||||
maxSize: 100,
|
||||
strategy: 'freshness',
|
||||
patterns: [
|
||||
'/api/.*',
|
||||
],
|
||||
cacheQueryOptions: {ignoreVary: true},
|
||||
},
|
||||
],
|
||||
navigationUrls: processNavigationUrls(baseHref),
|
||||
hashTable: tmpHashTableForFs(distDir, {}, baseHref),
|
||||
});
|
||||
|
||||
const getClientAssignments = async (sw: SwTestHarness, baseHref: string) => {
|
||||
const cache = await sw.caches.open(`ngsw:${baseHref}:db:control`) as unknown as MockCache;
|
||||
const dehydrated = cache.dehydrate();
|
||||
return JSON.parse(dehydrated['/assignments'].body!);
|
||||
};
|
||||
|
||||
const initializeSwFor =
|
||||
async (baseHref: string, initialCacheState = '{}', serverState = server) => {
|
||||
const initializeSwFor = async (baseHref: string, initialCacheState = '{}') => {
|
||||
const newDistDir = dist.extend().addFile('/foo.txt', `this is foo v${++uid}`).build();
|
||||
const newManifest = createManifestWithBaseHref(baseHref, newDistDir);
|
||||
const newManifestHash = sha1(JSON.stringify(newManifest));
|
||||
|
||||
const serverState = new MockServerStateBuilder()
|
||||
.withRootDirectory(baseHref)
|
||||
.withStaticFiles(newDistDir)
|
||||
.withManifest(newManifest)
|
||||
.build();
|
||||
|
||||
const newScope = new SwTestHarnessBuilder(`http://localhost${baseHref}`)
|
||||
.withCacheState(initialCacheState)
|
||||
.withServerState(serverState)
|
||||
.build();
|
||||
const newDriver = new Driver(newScope, newScope, new CacheDatabase(newScope, newScope));
|
||||
|
||||
await makeRequest(newScope, '/foo.txt', baseHref.replace(/\//g, '_'));
|
||||
await makeRequest(newScope, newManifest.index, baseHref.replace(/\//g, '_'));
|
||||
await newDriver.initialized;
|
||||
|
||||
return newScope;
|
||||
return [newScope, newManifestHash] as [SwTestHarness, string];
|
||||
};
|
||||
|
||||
it('includes the SW scope in all cache names', async () => {
|
||||
// Default SW with scope `/`.
|
||||
await makeRequest(scope, '/foo.txt');
|
||||
await driver.initialized;
|
||||
const cacheNames = await scope.caches.keys();
|
||||
beforeEach(() => {
|
||||
uid = 0;
|
||||
});
|
||||
|
||||
expect(cacheNames).toEqual(cacheKeysFor('/'));
|
||||
it('includes the SW scope in all cache names', async () => {
|
||||
// SW with scope `/`.
|
||||
const [rootScope, rootManifestHash] = await initializeSwFor('/');
|
||||
const cacheNames = await rootScope.caches.keys();
|
||||
|
||||
expect(cacheNames).toEqual(cacheKeysFor('/', rootManifestHash));
|
||||
expect(cacheNames.every(name => name.includes('/'))).toBe(true);
|
||||
|
||||
// SW with scope `/foo/`.
|
||||
const fooScope = await initializeSwFor('/foo/');
|
||||
const [fooScope, fooManifestHash] = await initializeSwFor('/foo/');
|
||||
const fooCacheNames = await fooScope.caches.keys();
|
||||
|
||||
expect(fooCacheNames).toEqual(cacheKeysFor('/foo/'));
|
||||
expect(fooCacheNames).toEqual(cacheKeysFor('/foo/', fooManifestHash));
|
||||
expect(fooCacheNames.every(name => name.includes('/foo/'))).toBe(true);
|
||||
});
|
||||
|
||||
it('does not affect caches from other scopes', async () => {
|
||||
// Create SW with scope `/foo/`.
|
||||
const fooScope = await initializeSwFor('/foo/');
|
||||
const [fooScope, fooManifestHash] = await initializeSwFor('/foo/');
|
||||
const fooAssignments = await getClientAssignments(fooScope, '/foo/');
|
||||
|
||||
expect(fooAssignments).toEqual({_foo_: manifestHash});
|
||||
expect(fooAssignments).toEqual({_foo_: fooManifestHash});
|
||||
|
||||
// Add new SW with different scope.
|
||||
const barScope = await initializeSwFor('/bar/', await fooScope.caches.dehydrate());
|
||||
const [barScope, barManifestHash] =
|
||||
await initializeSwFor('/bar/', await fooScope.caches.dehydrate());
|
||||
const barCacheNames = await barScope.caches.keys();
|
||||
const barAssignments = await getClientAssignments(barScope, '/bar/');
|
||||
|
||||
expect(barAssignments).toEqual({_bar_: manifestHash});
|
||||
expect(barAssignments).toEqual({_bar_: barManifestHash});
|
||||
expect(barCacheNames).toEqual([
|
||||
...cacheKeysFor('/foo/'),
|
||||
...cacheKeysFor('/bar/'),
|
||||
...cacheKeysFor('/foo/', fooManifestHash),
|
||||
...cacheKeysFor('/bar/', barManifestHash),
|
||||
]);
|
||||
|
||||
// The caches for `/foo/` should be intact.
|
||||
const fooAssignments2 = await getClientAssignments(barScope, '/foo/');
|
||||
expect(fooAssignments2).toEqual({_foo_: manifestHash});
|
||||
expect(fooAssignments2).toEqual({_foo_: fooManifestHash});
|
||||
});
|
||||
|
||||
it('updates existing caches for same scope', async () => {
|
||||
// Create SW with scope `/foo/`.
|
||||
const fooScope = await initializeSwFor('/foo/');
|
||||
await makeRequest(fooScope, '/foo.txt', '_bar_');
|
||||
const [fooScope, fooManifestHash] = await initializeSwFor('/foo/');
|
||||
await makeRequest(fooScope, '/foo/foo.txt', '_bar_');
|
||||
const fooAssignments = await getClientAssignments(fooScope, '/foo/');
|
||||
|
||||
expect(fooAssignments).toEqual({
|
||||
_foo_: manifestHash,
|
||||
_bar_: manifestHash,
|
||||
_foo_: fooManifestHash,
|
||||
_bar_: fooManifestHash,
|
||||
});
|
||||
|
||||
expect(await makeRequest(fooScope, '/baz.txt', '_foo_')).toBe('this is baz');
|
||||
expect(await makeRequest(fooScope, '/baz.txt', '_bar_')).toBe('this is baz');
|
||||
expect(await makeRequest(fooScope, '/foo/baz.txt', '_foo_')).toBe('this is baz');
|
||||
expect(await makeRequest(fooScope, '/foo/baz.txt', '_bar_')).toBe('this is baz');
|
||||
|
||||
// Add new SW with same scope.
|
||||
const fooScope2 =
|
||||
await initializeSwFor('/foo/', await fooScope.caches.dehydrate(), serverUpdate);
|
||||
const [fooScope2, fooManifestHash2] =
|
||||
await initializeSwFor('/foo/', await fooScope.caches.dehydrate());
|
||||
|
||||
// Update client `_foo_` but not client `_bar_`.
|
||||
await fooScope2.handleMessage({action: 'CHECK_FOR_UPDATES'}, '_foo_');
|
||||
await fooScope2.handleMessage({action: 'ACTIVATE_UPDATE'}, '_foo_');
|
||||
const fooAssignments2 = await getClientAssignments(fooScope2, '/foo/');
|
||||
|
||||
expect(fooAssignments2).toEqual({
|
||||
_foo_: manifestUpdateHash,
|
||||
_bar_: manifestHash,
|
||||
_foo_: fooManifestHash2,
|
||||
_bar_: fooManifestHash,
|
||||
});
|
||||
|
||||
// Everything should still work as expected.
|
||||
expect(await makeRequest(fooScope2, '/foo.txt', '_foo_')).toBe('this is foo v2');
|
||||
expect(await makeRequest(fooScope2, '/foo.txt', '_bar_')).toBe('this is foo');
|
||||
expect(await makeRequest(fooScope2, '/foo/foo.txt', '_foo_')).toBe('this is foo v2');
|
||||
expect(await makeRequest(fooScope2, '/foo/foo.txt', '_bar_')).toBe('this is foo v1');
|
||||
|
||||
expect(await makeRequest(fooScope2, '/baz.txt', '_foo_')).toBe('this is baz v2');
|
||||
expect(await makeRequest(fooScope2, '/baz.txt', '_bar_')).toBe('this is baz');
|
||||
expect(await makeRequest(fooScope2, '/foo/baz.txt', '_foo_')).toBe('this is baz');
|
||||
expect(await makeRequest(fooScope2, '/foo/baz.txt', '_bar_')).toBe('this is baz');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1154,7 +1216,7 @@ describe('Driver', () => {
|
|||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
it('redirects to index on a request to the origin URL request', async () => {
|
||||
it('redirects to index on a request to the scope URL', async () => {
|
||||
expect(await navRequest('http://localhost/')).toEqual('this is foo');
|
||||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockRequest, MockResponse} from './fetch';
|
||||
import {MockResponse} from './fetch';
|
||||
import {normalizeUrl} from './utils';
|
||||
|
||||
export interface DehydratedResponse {
|
||||
body: string|null;
|
||||
|
@ -104,7 +105,7 @@ export class MockCache {
|
|||
}
|
||||
|
||||
async 'delete'(request: RequestInfo, options?: CacheQueryOptions): Promise<boolean> {
|
||||
let url = (typeof request === 'string' ? request : request.url);
|
||||
let url = this.getRequestUrl(request);
|
||||
if (this.cache.has(url)) {
|
||||
this.cache.delete(url);
|
||||
return true;
|
||||
|
@ -127,10 +128,7 @@ export class MockCache {
|
|||
}
|
||||
|
||||
async match(request: RequestInfo, options?: CacheQueryOptions): Promise<Response> {
|
||||
let url = (typeof request === 'string' ? request : request.url);
|
||||
if (url.startsWith(this.origin)) {
|
||||
url = '/' + url.substr(this.origin.length);
|
||||
}
|
||||
let url = this.getRequestUrl(request);
|
||||
// TODO: cleanup typings. Typescript doesn't know this can resolve to undefined.
|
||||
let res = this.cache.get(url);
|
||||
if (!res && options?.ignoreSearch) {
|
||||
|
@ -150,8 +148,7 @@ export class MockCache {
|
|||
if (request === undefined) {
|
||||
return Array.from(this.cache.values());
|
||||
}
|
||||
const url = (typeof request === 'string' ? request : request.url);
|
||||
const res = await this.match(url, options);
|
||||
const res = await this.match(request, options);
|
||||
if (res) {
|
||||
return [res];
|
||||
} else {
|
||||
|
@ -160,7 +157,7 @@ export class MockCache {
|
|||
}
|
||||
|
||||
async put(request: RequestInfo, response: Response): Promise<void> {
|
||||
const url = (typeof request === 'string' ? request : request.url);
|
||||
const url = this.getRequestUrl(request);
|
||||
this.cache.set(url, response.clone());
|
||||
|
||||
// Even though the body above is cloned, consume it here because the
|
||||
|
@ -190,6 +187,12 @@ export class MockCache {
|
|||
return dehydrated;
|
||||
}
|
||||
|
||||
/** Get the normalized URL from a `RequestInfo` value. */
|
||||
private getRequestUrl(request: RequestInfo): string {
|
||||
const url = typeof request === 'string' ? request : request.url;
|
||||
return normalizeUrl(url, this.origin);
|
||||
}
|
||||
|
||||
/** remove the query/hash part from a url*/
|
||||
private stripQueryAndHash(url: string): string {
|
||||
return url.replace(/[?#].*/, '');
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AssetGroupConfig, Manifest} from '../src/manifest';
|
||||
import {Manifest} from '../src/manifest';
|
||||
import {sha1} from '../src/sha1';
|
||||
|
||||
import {MockResponse} from './fetch';
|
||||
|
@ -69,19 +69,38 @@ export class MockFileSystem {
|
|||
}
|
||||
|
||||
export class MockServerStateBuilder {
|
||||
private rootDir = '/';
|
||||
private resources = new Map<string, Response>();
|
||||
private errors = new Set<string>();
|
||||
|
||||
withStaticFiles(fs: MockFileSystem): MockServerStateBuilder {
|
||||
fs.list().forEach(path => {
|
||||
const file = fs.lookup(path)!;
|
||||
this.resources.set(path, new MockResponse(file.contents, {headers: file.headers}));
|
||||
withRootDirectory(newRootDir: string): MockServerStateBuilder {
|
||||
// Update existing resources/errors.
|
||||
const oldRootDir = this.rootDir;
|
||||
const updateRootDir = (path: string) =>
|
||||
path.startsWith(oldRootDir) ? joinPaths(newRootDir, path.slice(oldRootDir.length)) : path;
|
||||
|
||||
this.resources = new Map(
|
||||
[...this.resources].map(([path, contents]) => [updateRootDir(path), contents.clone()]));
|
||||
this.errors = new Set([...this.errors].map(url => updateRootDir(url)));
|
||||
|
||||
// Set `rootDir` for future resource/error additions.
|
||||
this.rootDir = newRootDir;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
withStaticFiles(dir: MockFileSystem): MockServerStateBuilder {
|
||||
dir.list().forEach(path => {
|
||||
const file = dir.lookup(path)!;
|
||||
this.resources.set(
|
||||
joinPaths(this.rootDir, path), new MockResponse(file.contents, {headers: file.headers}));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
withManifest(manifest: Manifest): MockServerStateBuilder {
|
||||
this.resources.set('ngsw.json', new MockResponse(JSON.stringify(manifest)));
|
||||
const manifestPath = joinPaths(this.rootDir, 'ngsw.json');
|
||||
this.resources.set(manifestPath, new MockResponse(JSON.stringify(manifest)));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -234,14 +253,16 @@ export function tmpManifestSingleAssetGroup(fs: MockFileSystem): Manifest {
|
|||
}
|
||||
|
||||
export function tmpHashTableForFs(
|
||||
fs: MockFileSystem, breakHashes: {[url: string]: boolean} = {}): {[url: string]: string} {
|
||||
fs: MockFileSystem, breakHashes: {[url: string]: boolean} = {},
|
||||
baseHref = '/'): {[url: string]: string} {
|
||||
const table: {[url: string]: string} = {};
|
||||
fs.list().forEach(path => {
|
||||
const file = fs.lookup(path)!;
|
||||
fs.list().forEach(filePath => {
|
||||
const urlPath = joinPaths(baseHref, filePath);
|
||||
const file = fs.lookup(filePath)!;
|
||||
if (file.hashThisFile) {
|
||||
table[path] = file.hash;
|
||||
if (breakHashes[path]) {
|
||||
table[path] = table[path].split('').reverse().join('');
|
||||
table[urlPath] = file.hash;
|
||||
if (breakHashes[filePath]) {
|
||||
table[urlPath] = table[urlPath].split('').reverse().join('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -256,3 +277,11 @@ export function tmpHashTable(manifest: Manifest): Map<string, string> {
|
|||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
/**
|
||||
* Join two path segments, ensuring that there is exactly one slash (`/`) between them.
|
||||
*/
|
||||
function joinPaths(path1: string, path2: string): string {
|
||||
return `${path1.replace(/\/$/, '')}/${path2.replace(/^\//, '')}`;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import {sha1} from '../src/sha1';
|
|||
import {MockCacheStorage} from './cache';
|
||||
import {MockHeaders, MockRequest, MockResponse} from './fetch';
|
||||
import {MockServerState, MockServerStateBuilder} from './mock';
|
||||
import {normalizeUrl, parseUrl} from './utils';
|
||||
|
||||
const EMPTY_SERVER_STATE = new MockServerStateBuilder().build();
|
||||
|
||||
|
@ -32,10 +33,11 @@ export class MockClient {
|
|||
}
|
||||
|
||||
export class SwTestHarnessBuilder {
|
||||
private origin = parseUrl(this.scopeUrl).origin;
|
||||
private server = EMPTY_SERVER_STATE;
|
||||
private caches = new MockCacheStorage(this.origin);
|
||||
|
||||
constructor(private origin = 'http://localhost/') {}
|
||||
constructor(private scopeUrl = 'http://localhost/') {}
|
||||
|
||||
withCacheState(cache: string): SwTestHarnessBuilder {
|
||||
this.caches = new MockCacheStorage(this.origin, cache);
|
||||
|
@ -48,7 +50,7 @@ export class SwTestHarnessBuilder {
|
|||
}
|
||||
|
||||
build(): SwTestHarness {
|
||||
return new SwTestHarness(this.server, this.caches, this.origin);
|
||||
return new SwTestHarness(this.server, this.caches, this.scopeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +139,8 @@ export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope,
|
|||
fired: boolean,
|
||||
}[] = [];
|
||||
|
||||
parseUrl = parseUrl;
|
||||
|
||||
constructor(
|
||||
private server: MockServerState, readonly caches: MockCacheStorage, scopeUrl: string) {
|
||||
super(scopeUrl);
|
||||
|
@ -176,17 +180,12 @@ export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope,
|
|||
this.server = server || EMPTY_SERVER_STATE;
|
||||
}
|
||||
|
||||
fetch(req: string|Request): Promise<Response> {
|
||||
fetch(req: RequestInfo): Promise<Response> {
|
||||
if (typeof req === 'string') {
|
||||
if (req.startsWith(this.origin)) {
|
||||
req = '/' + req.substr(this.origin.length);
|
||||
}
|
||||
return this.server.fetch(new MockRequest(req));
|
||||
return this.server.fetch(new MockRequest(normalizeUrl(req, this.scopeUrl)));
|
||||
} else {
|
||||
const mockReq = req.clone() as MockRequest;
|
||||
if (mockReq.url.startsWith(this.origin)) {
|
||||
mockReq.url = '/' + mockReq.url.substr(this.origin.length);
|
||||
}
|
||||
mockReq.url = normalizeUrl(mockReq.url, this.scopeUrl);
|
||||
return this.server.fetch(mockReq);
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +199,7 @@ export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope,
|
|||
}
|
||||
|
||||
newRequest(url: string, init: Object = {}): Request {
|
||||
return new MockRequest(url, init);
|
||||
return new MockRequest(normalizeUrl(url, this.scopeUrl), init);
|
||||
}
|
||||
|
||||
newResponse(body: string, init: Object = {}): Response {
|
||||
|
@ -214,18 +213,6 @@ export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope,
|
|||
}, new MockHeaders());
|
||||
}
|
||||
|
||||
parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} {
|
||||
const parsedUrl: URL = (typeof URL === 'function') ?
|
||||
(!relativeTo ? new URL(url) : new URL(url, relativeTo)) :
|
||||
require('url').parse(require('url').resolve(relativeTo || '', url));
|
||||
|
||||
return {
|
||||
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
|
||||
path: parsedUrl.pathname,
|
||||
search: parsedUrl.search || '',
|
||||
};
|
||||
}
|
||||
|
||||
async skipWaiting(): Promise<void> {
|
||||
this.skippedWaiting = true;
|
||||
}
|
||||
|
@ -409,6 +396,7 @@ class MockPushEvent extends MockExtendableEvent {
|
|||
json: () => this._data,
|
||||
};
|
||||
}
|
||||
|
||||
class MockNotificationEvent extends MockExtendableEvent {
|
||||
constructor(private _notification: any, readonly action?: string) {
|
||||
super();
|
||||
|
@ -418,5 +406,4 @@ class MockNotificationEvent extends MockExtendableEvent {
|
|||
|
||||
class MockInstallEvent extends MockExtendableEvent {}
|
||||
|
||||
|
||||
class MockActivateEvent extends MockExtendableEvent {}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a normalized representation of a URL relative to a provided base URL.
|
||||
*
|
||||
* More specifically:
|
||||
* 1. Resolve the URL relative to the provided base URL.
|
||||
* 2. If the URL is relative to the base URL, then strip the origin (and only return the path and
|
||||
* search parts). Otherwise, return the full URL.
|
||||
*
|
||||
* @param url The raw URL.
|
||||
* @param relativeTo The base URL to resolve `url` relative to.
|
||||
* (This is usually the ServiceWorker's origin or registration scope).
|
||||
* @return A normalized representation of the URL.
|
||||
*/
|
||||
export function normalizeUrl(url: string, relativeTo: string): string {
|
||||
const {origin, path, search} = parseUrl(url, relativeTo);
|
||||
const {origin: relativeToOrigin} = parseUrl(relativeTo);
|
||||
|
||||
return (origin === relativeToOrigin) ? path + search : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a URL into its different parts, such as `origin`, `path` and `search`.
|
||||
*/
|
||||
export function parseUrl(
|
||||
url: string, relativeTo?: string): {origin: string, path: string, search: string} {
|
||||
const parsedUrl: URL = (typeof URL === 'function') ?
|
||||
(!relativeTo ? new URL(url) : new URL(url, relativeTo)) :
|
||||
require('url').parse(require('url').resolve(relativeTo || '', url));
|
||||
|
||||
return {
|
||||
origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`,
|
||||
path: parsedUrl.pathname,
|
||||
search: parsedUrl.search || '',
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue