| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. 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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 17:06:06 -05:00
										 |  |  | import {Subject} from 'rxjs'; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {Adapter, Context} from '../src/adapter'; | 
					
						
							|  |  |  | import {AssetGroupConfig, Manifest} from '../src/manifest'; | 
					
						
							|  |  |  | import {sha1} from '../src/sha1'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {MockCacheStorage} from './cache'; | 
					
						
							|  |  |  | import {MockHeaders, MockRequest, MockResponse} from './fetch'; | 
					
						
							|  |  |  | import {MockServerState, MockServerStateBuilder} from './mock'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const EMPTY_SERVER_STATE = new MockServerStateBuilder().build(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class MockClient { | 
					
						
							|  |  |  |   queue = new Subject<Object>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(readonly id: string) {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   readonly messages: Object[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   postMessage(message: Object): void { | 
					
						
							|  |  |  |     this.messages.push(message); | 
					
						
							|  |  |  |     this.queue.next(message); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class SwTestHarnessBuilder { | 
					
						
							|  |  |  |   private server = EMPTY_SERVER_STATE; | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |   private caches = new MockCacheStorage(this.origin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(private origin = 'http://localhost/') {} | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   withCacheState(cache: string): SwTestHarnessBuilder { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |     this.caches = new MockCacheStorage(this.origin, cache); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     return this; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   withServerState(state: MockServerState): SwTestHarnessBuilder { | 
					
						
							|  |  |  |     this.server = state; | 
					
						
							|  |  |  |     return this; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |   build(): SwTestHarness { return new SwTestHarness(this.server, this.caches, this.origin); } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class MockClients implements Clients { | 
					
						
							|  |  |  |   private clients = new Map<string, MockClient>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   add(clientId: string): void { | 
					
						
							|  |  |  |     if (this.clients.has(clientId)) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.clients.set(clientId, new MockClient(clientId)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   remove(clientId: string): void { this.clients.delete(clientId); } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-01 01:12:31 +03:00
										 |  |  |   async get(id: string): Promise<Client> { return this.clients.get(id) !as any as Client; } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   getMock(id: string): MockClient|undefined { return this.clients.get(id); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async matchAll(): Promise<Client[]> { | 
					
						
							|  |  |  |     return Array.from(this.clients.values()) as any[] as Client[]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async claim(): Promise<any> {} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:15 +02:00
										 |  |  |   readonly cacheNamePrefix: string; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   readonly clients = new MockClients(); | 
					
						
							|  |  |  |   private eventHandlers = new Map<string, Function>(); | 
					
						
							|  |  |  |   private skippedWaiting = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private selfMessageQueue: any[] = []; | 
					
						
							| 
									
										
										
										
											2018-10-09 14:54:15 +03:00
										 |  |  |   autoAdvanceTime = false; | 
					
						
							| 
									
										
										
										
											2018-06-18 16:38:33 -07:00
										 |  |  |   // TODO(issue/24571): remove '!'.
 | 
					
						
							|  |  |  |   unregistered !: boolean; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   readonly notifications: {title: string, options: Object}[] = []; | 
					
						
							|  |  |  |   readonly registration: ServiceWorkerRegistration = { | 
					
						
							|  |  |  |     active: { | 
					
						
							|  |  |  |       postMessage: (msg: any) => { this.selfMessageQueue.push(msg); }, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |     scope: this.origin, | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     showNotification: (title: string, options: Object) => { | 
					
						
							|  |  |  |       this.notifications.push({title, options}); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     unregister: () => { this.unregistered = true; }, | 
					
						
							|  |  |  |   } as any; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static envIsSupported(): boolean { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:14 +02:00
										 |  |  |     if (typeof URL === 'function') { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // In older Node.js versions, the `URL` global does not exist. We can use `url` instead.
 | 
					
						
							|  |  |  |     const url = (typeof require === 'function') && require('url'); | 
					
						
							|  |  |  |     return url && (typeof url.parse === 'function') && (typeof url.resolve === 'function'); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   time: number; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private timers: { | 
					
						
							|  |  |  |     at: number, | 
					
						
							|  |  |  |     duration: number, | 
					
						
							|  |  |  |     fn: Function, | 
					
						
							|  |  |  |     fired: boolean, | 
					
						
							|  |  |  |   }[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |       private server: MockServerState, readonly caches: MockCacheStorage, private origin: string) { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |     const baseHref = this.parseUrl(origin).path; | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:15 +02:00
										 |  |  |     this.cacheNamePrefix = 'ngsw:' + baseHref; | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |     this.time = Date.now(); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async resolveSelfMessages(): Promise<void> { | 
					
						
							|  |  |  |     while (this.selfMessageQueue.length > 0) { | 
					
						
							|  |  |  |       const queue = this.selfMessageQueue; | 
					
						
							|  |  |  |       this.selfMessageQueue = []; | 
					
						
							|  |  |  |       await queue.reduce(async(previous, msg) => { | 
					
						
							|  |  |  |         await previous; | 
					
						
							|  |  |  |         await this.handleMessage(msg, null); | 
					
						
							|  |  |  |       }, Promise.resolve()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async startup(firstTime: boolean = false): Promise<boolean|null> { | 
					
						
							|  |  |  |     if (!firstTime) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     let skippedWaiting: boolean = false; | 
					
						
							|  |  |  |     if (this.eventHandlers.has('install')) { | 
					
						
							|  |  |  |       const installEvent = new MockInstallEvent(); | 
					
						
							|  |  |  |       this.eventHandlers.get('install') !(installEvent); | 
					
						
							|  |  |  |       await installEvent.ready; | 
					
						
							|  |  |  |       skippedWaiting = this.skippedWaiting; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (this.eventHandlers.has('activate')) { | 
					
						
							|  |  |  |       const activateEvent = new MockActivateEvent(); | 
					
						
							|  |  |  |       this.eventHandlers.get('activate') !(activateEvent); | 
					
						
							|  |  |  |       await activateEvent.ready; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return skippedWaiting; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   updateServerState(server?: MockServerState): void { this.server = server || EMPTY_SERVER_STATE; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fetch(req: string|Request): Promise<Response> { | 
					
						
							|  |  |  |     if (typeof req === 'string') { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |       if (req.startsWith(this.origin)) { | 
					
						
							|  |  |  |         req = '/' + req.substr(this.origin.length); | 
					
						
							| 
									
										
										
										
											2017-10-02 15:59:57 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |       return this.server.fetch(new MockRequest(req)); | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2017-10-02 15:59:57 -07:00
										 |  |  |       const mockReq = req.clone() as MockRequest; | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:16 +02:00
										 |  |  |       if (mockReq.url.startsWith(this.origin)) { | 
					
						
							|  |  |  |         mockReq.url = '/' + mockReq.url.substr(this.origin.length); | 
					
						
							| 
									
										
										
										
											2017-10-02 15:59:57 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       return this.server.fetch(mockReq); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addEventListener(event: string, handler: Function): void { | 
					
						
							|  |  |  |     this.eventHandlers.set(event, handler); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   removeEventListener(event: string, handler?: Function): void { this.eventHandlers.delete(event); } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-02 15:59:57 -07:00
										 |  |  |   newRequest(url: string, init: Object = {}): Request { return new MockRequest(url, init); } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-02 15:59:57 -07:00
										 |  |  |   newResponse(body: string, init: Object = {}): Response { return new MockResponse(body, init); } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   newHeaders(headers: {[name: string]: string}): Headers { | 
					
						
							|  |  |  |     return Object.keys(headers).reduce((mock, name) => { | 
					
						
							|  |  |  |       mock.set(name, headers[name]); | 
					
						
							|  |  |  |       return mock; | 
					
						
							|  |  |  |     }, new MockHeaders()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-25 22:51:10 +03:00
										 |  |  |   parseUrl(url: string, relativeTo?: string): {origin: string, path: string, search: string} { | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:14 +02:00
										 |  |  |     const parsedUrl: URL = (typeof URL === 'function') ? | 
					
						
							| 
									
										
										
										
											2019-06-19 18:56:40 +02:00
										 |  |  |         (!relativeTo ? new URL(url) : new URL(url, relativeTo)) : | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:14 +02:00
										 |  |  |         require('url').parse(require('url').resolve(relativeTo || '', url)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       origin: parsedUrl.origin || `${parsedUrl.protocol}//${parsedUrl.host}`, | 
					
						
							|  |  |  |       path: parsedUrl.pathname, | 
					
						
							| 
									
										
										
										
											2019-04-25 22:51:10 +03:00
										 |  |  |       search: parsedUrl.search || '', | 
					
						
							| 
									
										
										
										
											2019-03-20 23:29:14 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async skipWaiting(): Promise<void> { this.skippedWaiting = true; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   waitUntil(promise: Promise<void>): void {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-01 01:19:31 +03:00
										 |  |  |   handleFetch(req: Request, clientId: string|null = null): | 
					
						
							|  |  |  |       [Promise<Response|undefined>, Promise<void>] { | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     if (!this.eventHandlers.has('fetch')) { | 
					
						
							|  |  |  |       throw new Error('No fetch handler registered'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-05-01 01:19:31 +03:00
										 |  |  |     const event = new MockFetchEvent(req, clientId); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     this.eventHandlers.get('fetch') !.call(this, event); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-01 01:12:31 +03:00
										 |  |  |     if (clientId) { | 
					
						
							|  |  |  |       this.clients.add(clientId); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     return [event.response, event.ready]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   handleMessage(data: Object, clientId: string|null): Promise<void> { | 
					
						
							|  |  |  |     if (!this.eventHandlers.has('message')) { | 
					
						
							|  |  |  |       throw new Error('No message handler registered'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     let event: MockMessageEvent; | 
					
						
							|  |  |  |     if (clientId === null) { | 
					
						
							|  |  |  |       event = new MockMessageEvent(data, null); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.clients.add(clientId); | 
					
						
							| 
									
										
										
										
											2018-05-01 01:19:31 +03:00
										 |  |  |       event = new MockMessageEvent(data, this.clients.getMock(clientId) || null); | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     this.eventHandlers.get('message') !.call(this, event); | 
					
						
							|  |  |  |     return event.ready; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   handlePush(data: Object): Promise<void> { | 
					
						
							|  |  |  |     if (!this.eventHandlers.has('push')) { | 
					
						
							|  |  |  |       throw new Error('No push handler registered'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const event = new MockPushEvent(data); | 
					
						
							|  |  |  |     this.eventHandlers.get('push') !.call(this, event); | 
					
						
							|  |  |  |     return event.ready; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-07 14:56:40 +02:00
										 |  |  |   handleClick(notification: Object, action?: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.eventHandlers.has('notificationclick')) { | 
					
						
							|  |  |  |       throw new Error('No notificationclick handler registered'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-21 11:11:06 +02:00
										 |  |  |     const event = new MockNotificationEvent(notification, action); | 
					
						
							| 
									
										
										
										
											2018-09-07 14:56:40 +02:00
										 |  |  |     this.eventHandlers.get('notificationclick') !.call(this, event); | 
					
						
							|  |  |  |     return event.ready; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   timeout(ms: number): Promise<void> { | 
					
						
							| 
									
										
										
										
											2018-10-09 14:54:15 +03:00
										 |  |  |     const promise = new Promise<void>(resolve => { | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |       this.timers.push({ | 
					
						
							|  |  |  |         at: this.time + ms, | 
					
						
							|  |  |  |         duration: ms, | 
					
						
							|  |  |  |         fn: resolve, | 
					
						
							|  |  |  |         fired: false, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-10-09 14:54:15 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (this.autoAdvanceTime) { | 
					
						
							|  |  |  |       this.advance(ms); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return promise; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   advance(by: number): void { | 
					
						
							|  |  |  |     this.time += by; | 
					
						
							|  |  |  |     this.timers.filter(timer => !timer.fired) | 
					
						
							|  |  |  |         .filter(timer => timer.at <= this.time) | 
					
						
							|  |  |  |         .forEach(timer => { | 
					
						
							|  |  |  |           timer.fired = true; | 
					
						
							|  |  |  |           timer.fn(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   isClient(obj: any): obj is Client { return obj instanceof MockClient; } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface StaticFile { | 
					
						
							|  |  |  |   url: string; | 
					
						
							|  |  |  |   contents: string; | 
					
						
							|  |  |  |   hash?: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class AssetGroupBuilder { | 
					
						
							|  |  |  |   constructor(private up: ConfigBuilder, readonly name: string) {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private files: StaticFile[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addFile(url: string, contents: string, hashed: boolean = true): AssetGroupBuilder { | 
					
						
							|  |  |  |     const file: StaticFile = {url, contents, hash: undefined}; | 
					
						
							|  |  |  |     if (hashed) { | 
					
						
							|  |  |  |       file.hash = sha1(contents); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.files.push(file); | 
					
						
							|  |  |  |     return this; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   finish(): ConfigBuilder { return this.up; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   toManifestGroup(): AssetGroupConfig { return null !; } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class ConfigBuilder { | 
					
						
							|  |  |  |   assetGroups = new Map<string, AssetGroupBuilder>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addAssetGroup(name: string): ConfigBuilder { | 
					
						
							|  |  |  |     const builder = new AssetGroupBuilder(this, name); | 
					
						
							|  |  |  |     this.assetGroups.set(name, builder); | 
					
						
							|  |  |  |     return this; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   finish(): Manifest { | 
					
						
							|  |  |  |     const assetGroups = Array.from(this.assetGroups.values()).map(group => group.toManifestGroup()); | 
					
						
							|  |  |  |     const hashTable = {}; | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       configVersion: 1, | 
					
						
							| 
									
										
										
										
											2019-03-05 15:24:07 +02:00
										 |  |  |       timestamp: 1234567890123, | 
					
						
							| 
									
										
										
										
											2018-04-12 18:04:11 +03:00
										 |  |  |       index: '/index.html', assetGroups, | 
					
						
							|  |  |  |       navigationUrls: [], hashTable, | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class OneTimeContext implements Context { | 
					
						
							|  |  |  |   private queue: Promise<void>[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   waitUntil(promise: Promise<void>): void { this.queue.push(promise); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   get ready(): Promise<void> { | 
					
						
							|  |  |  |     return (async() => { | 
					
						
							|  |  |  |       while (this.queue.length > 0) { | 
					
						
							|  |  |  |         await this.queue.shift(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockExtendableEvent extends OneTimeContext {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockFetchEvent extends MockExtendableEvent { | 
					
						
							|  |  |  |   response: Promise<Response|undefined> = Promise.resolve(undefined); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(readonly request: Request, readonly clientId: string|null) { super(); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   respondWith(promise: Promise<Response>): Promise<Response> { | 
					
						
							|  |  |  |     this.response = promise; | 
					
						
							|  |  |  |     return promise; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockMessageEvent extends MockExtendableEvent { | 
					
						
							|  |  |  |   constructor(readonly data: Object, readonly source: MockClient|null) { super(); } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockPushEvent extends MockExtendableEvent { | 
					
						
							| 
									
										
										
										
											2017-10-10 12:57:50 -07:00
										 |  |  |   constructor(private _data: Object) { super(); } | 
					
						
							|  |  |  |   data = { | 
					
						
							|  |  |  |     json: () => this._data, | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-09-21 11:11:06 +02:00
										 |  |  | class MockNotificationEvent extends MockExtendableEvent { | 
					
						
							|  |  |  |   constructor(private _notification: any, readonly action?: string) { super(); } | 
					
						
							| 
									
										
										
										
											2018-09-28 09:43:24 +02:00
										 |  |  |   readonly notification = {...this._notification, close: () => undefined}; | 
					
						
							| 
									
										
										
										
											2018-09-07 14:56:40 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-09-28 16:18:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | class MockInstallEvent extends MockExtendableEvent {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MockActivateEvent extends MockExtendableEvent {} |