quodolores
Version:
Monorepo for the Firebase JavaScript SDK
220 lines (177 loc) • 5.46 kB
text/typescript
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Client,
Clients,
ExtendableEvent,
ServiceWorkerGlobalScope,
WindowClient
} from '../../util/sw-types';
import { Writable } from 'ts-essentials';
// Add fake SW types.
declare const self: Window & Writable<ServiceWorkerGlobalScope>;
// When trying to stub self.clients self.registration, Sinon complains that these properties do not
// exist. This is because we're not actually running these tests in a service worker context.
// Here we're adding placeholders for Sinon to overwrite, which prevents the "Cannot stub
// non-existent own property" errors.
// Casting to any is needed because TS also thinks that we're in a SW context and considers these
// properties readonly.
// Missing function types are implemented from interfaces, so types are actually defined.
/* eslint-disable @typescript-eslint/explicit-function-return-type */
// const originalSwRegistration = ServiceWorkerRegistration;
export function mockServiceWorker(): void {
self.clients = new FakeClients();
self.registration = new FakeServiceWorkerRegistration();
}
export function restoreServiceWorker(): void {
self.clients = new FakeClients();
self.registration = new FakeServiceWorkerRegistration();
}
class FakeClients implements Clients {
private readonly clients: Client[] = [];
async get(id: string) {
return this.clients.find(c => id === c.id) ?? null;
}
async matchAll({ type = 'all' } = {}) {
if (type === 'all') {
return this.clients;
}
return this.clients.filter(c => c.type === type);
}
async openWindow(url: string) {
const windowClient = new FakeWindowClient();
windowClient.url = url;
this.clients.push(windowClient);
return windowClient;
}
async claim() {}
}
let currentId = 0;
class FakeWindowClient implements WindowClient {
readonly id: string;
readonly type = 'window';
focused = false;
visibilityState: VisibilityState = 'hidden';
url = 'https://example.org';
constructor() {
this.id = (currentId++).toString();
}
async focus() {
this.focused = true;
return this;
}
async navigate(url: string) {
this.url = url;
return this;
}
postMessage() {}
}
export class FakeServiceWorkerRegistration
implements ServiceWorkerRegistration {
active = null;
installing = null;
waiting = null;
onupdatefound = null;
pushManager = new FakePushManager();
scope = '/scope-value';
// Unused in FCM Web SDK, no need to mock these.
navigationPreload = (null as unknown) as NavigationPreloadManager;
sync = (null as unknown) as SyncManager;
updateViaCache = (null as unknown) as ServiceWorkerUpdateViaCache;
async getNotifications() {
return [];
}
async showNotification() {}
async update() {}
async unregister() {
return true;
}
addEventListener() {}
removeEventListener() {}
dispatchEvent() {
return true;
}
}
class FakePushManager implements PushManager {
private subscription: FakePushSubscription | null = null;
async permissionState() {
return 'granted' as const;
}
async getSubscription() {
return this.subscription;
}
async subscribe() {
if (!this.subscription) {
this.subscription = new FakePushSubscription();
}
return this.subscription!;
}
}
export class FakePushSubscription implements PushSubscription {
endpoint = 'https://example.org';
expirationTime = 1234567890;
auth = 'auth-value'; // Encoded: 'YXV0aC12YWx1ZQ'
p256 = 'p256-value'; // Encoded: 'cDI1Ni12YWx1ZQ'
getKey(name: PushEncryptionKeyName) {
const encoder = new TextEncoder();
return encoder.encode(name === 'auth' ? this.auth : this.p256);
}
async unsubscribe() {
return true;
}
// Unused in FCM
toJSON = (null as unknown) as () => PushSubscriptionJSON;
options = (null as unknown) as PushSubscriptionOptions;
}
/**
* Most of the fields in here are unused / deprecated. They are only added here to match the TS
* Event interface.
*/
export class FakeEvent implements ExtendableEvent {
NONE = Event.NONE;
AT_TARGET = Event.AT_TARGET;
BUBBLING_PHASE = Event.BUBBLING_PHASE;
CAPTURING_PHASE = Event.CAPTURING_PHASE;
bubbles: boolean;
cancelable: boolean;
composed: boolean;
timeStamp = 123456;
isTrusted = true;
eventPhase = Event.NONE;
target = null;
currentTarget = null;
srcElement = null;
cancelBubble = false;
defaultPrevented = false;
returnValue = false;
preventDefault() {
this.defaultPrevented = true;
this.returnValue = true;
}
stopPropagation() {}
stopImmediatePropagation() {}
initEvent() {}
waitUntil() {}
composedPath() {
return [];
}
constructor(public type: string, options: EventInit = {}) {
this.bubbles = options.bubbles ?? false;
this.cancelable = options.cancelable ?? false;
this.composed = options.composed ?? false;
}
}