test(service-worker): better align mock global scope implementation with actual implementation (#42736)

This commit better aligns the mock `ServiceWorkerGlobalScope`
implementation used in ServiceWorker tests (and the associated typings)
with the actual implementation (and the official TypeScript typings).
This allows verifying the ServiceWorker behavior in a slightly more
realistic environment.

This is in preparation of switching from our custom typings to the
official TypeScript typings (`lib.webworker.d.ts`).

PR Close #42736
This commit is contained in:
George Kalpakas 2021-07-08 16:25:16 +03:00 committed by atscott
parent 7c2f80067a
commit a47aaabf70
3 changed files with 71 additions and 23 deletions

View File

@ -18,7 +18,7 @@ import {NamedCacheStorage} from './named-cache-storage';
*/
export class Adapter<T extends CacheStorage = CacheStorage> {
readonly caches: NamedCacheStorage<T>;
private readonly origin: string;
readonly origin: string;
constructor(protected readonly scopeUrl: string, caches: T) {
const parsedScopeUrl = this.parseUrl(this.scopeUrl);

View File

@ -106,23 +106,54 @@ interface ExtendableMessageEvent extends ExtendableEvent {
readonly source: Client|ServiceWorker|MessagePort|null;
}
// WorkerGlobalScope
// Explicitly omit the `caches` property to disallow accessing `CacheStorage` APIs directly. All
// interactions with `CacheStorage` should go through a `NamedCacheStorage` instance (exposed by the
// `Adapter`).
interface WorkerGlobalScope extends EventTarget, Omit<WindowOrWorkerGlobalScope, 'caches'> {
readonly location: WorkerLocation;
readonly navigator: WorkerNavigator;
readonly self: WorkerGlobalScope & typeof globalThis;
importScripts(...urls: string[]): void;
addEventListener<K extends keyof WorkerGlobalScopeEventMap>(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof WorkerGlobalScopeEventMap>(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
interface WorkerGlobalScopeEventMap {
error: ErrorEvent;
languagechange: Event;
offline: Event;
online: Event;
rejectionhandled: PromiseRejectionEvent;
unhandledrejection: PromiseRejectionEvent;
}
// ServiceWorkerGlobalScope
interface ServiceWorkerGlobalScope {
// Intentionally does not include a `caches` property to disallow accessing `CacheStorage` APIs
// directly. All interactions with `CacheStorage` should go through a `NamedCacheStorage` instance
// (exposed by the `Adapter`).
clients: Clients;
registration: ServiceWorkerRegistration;
interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
readonly clients: Clients;
readonly registration: ServiceWorkerRegistration;
readonly serviceWorker: ServiceWorker;
addEventListener(event: 'activate', fn: (event?: ExtendableEvent) => any): void;
addEventListener(event: 'message', fn: (event?: ExtendableMessageEvent) => any): void;
addEventListener(event: 'fetch', fn: (event?: FetchEvent) => any): void;
addEventListener(event: 'install', fn: (event?: ExtendableEvent) => any): void;
addEventListener(event: 'push', fn: (event?: PushEvent) => any): void;
addEventListener(event: 'notificationclick', fn: (event?: NotificationEvent) => any): void;
addEventListener(event: 'sync', fn: (event?: SyncEvent) => any): void;
fetch(request: Request|string): Promise<Response>;
skipWaiting(): Promise<void>;
addEventListener<K extends keyof ServiceWorkerGlobalScopeEventMap>(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof ServiceWorkerGlobalScopeEventMap>(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
interface ServiceWorkerGlobalScopeEventMap extends WorkerGlobalScopeEventMap {
activate: ExtendableEvent;
fetch: FetchEvent;
install: ExtendableEvent;
message: ExtendableMessageEvent;
messageerror: MessageEvent;
notificationclick: NotificationEvent;
notificationclose: NotificationEvent;
push: PushEvent;
sync: SyncEvent;
}

View File

@ -37,13 +37,16 @@ export class SwTestHarnessBuilder {
}
build(): SwTestHarness {
return new SwTestHarness(this.server, this.caches, this.scopeUrl);
return new SwTestHarnessImpl(this.server, this.caches, this.scopeUrl) as SwTestHarness;
}
}
export class SwTestHarness extends Adapter<MockCacheStorage> implements ServiceWorkerGlobalScope {
export type SwTestHarness = SwTestHarnessImpl&ServiceWorkerGlobalScope;
export class SwTestHarnessImpl extends Adapter<MockCacheStorage> implements
Partial<ServiceWorkerGlobalScope> {
readonly clients = new MockClients();
private eventHandlers = new Map<string, Function>();
private eventHandlers = new Map<string, EventListener>();
private skippedWaiting = false;
private selfMessageQueue: any[] = [];
@ -131,12 +134,26 @@ export class SwTestHarness extends Adapter<MockCacheStorage> implements ServiceW
}
}
addEventListener(event: string, handler: Function): void {
this.eventHandlers.set(event, handler);
addEventListener(
type: string, listener: EventListenerOrEventListenerObject,
options?: boolean|AddEventListenerOptions): void {
if (options !== undefined) {
throw new Error('Mock `addEventListener()` does not support `options`.');
}
const handler: EventListener =
(typeof listener === 'function') ? listener : evt => listener.handleEvent(evt);
this.eventHandlers.set(type, handler);
}
removeEventListener(event: string, handler?: Function): void {
this.eventHandlers.delete(event);
removeEventListener(
type: string, listener: EventListenerOrEventListenerObject,
options?: boolean|AddEventListenerOptions): void {
if (options !== undefined) {
throw new Error('Mock `removeEventListener()` does not support `options`.');
}
this.eventHandlers.delete(type);
}
newRequest(url: string, init: Object = {}): Request {