diff --git a/src/favicon.ts b/src/favicon.ts index bec13c7866..2212d2aad8 100644 --- a/src/favicon.ts +++ b/src/favicon.ts @@ -54,7 +54,7 @@ export default class Favicon { private isReady = false; // callback to run once isReady is asserted, allows for a badge to be queued for when it can be shown - private readyCb = () => {}; + private readyCb?: () => void; constructor(params: Partial = {}) { this.params = { ...defaults, ...params }; @@ -180,7 +180,7 @@ export default class Favicon { private ready() { if (this.isReady) return; this.isReady = true; - this.readyCb(); + this.readyCb?.(); } private setIcon(canvas) { @@ -230,9 +230,9 @@ export default class Favicon { private static getLinks() { const icons: HTMLLinkElement[] = []; const links = window.document.getElementsByTagName("head")[0].getElementsByTagName("link"); - for (let i = 0; i < links.length; i++) { - if ((/(^|\s)icon(\s|$)/i).test(links[i].getAttribute("rel"))) { - icons.push(links[i]); + for (const link of links) { + if ((/(^|\s)icon(\s|$)/i).test(link.getAttribute("rel"))) { + icons.push(link); } } return icons; diff --git a/src/vector/init.tsx b/src/vector/init.tsx index 72dcd6b073..d8e79dde20 100644 --- a/src/vector/init.tsx +++ b/src/vector/init.tsx @@ -92,8 +92,8 @@ export function loadOlm(): Promise { locateFile: () => olmWasmPath, }).then(() => { logger.log("Using WebAssembly Olm"); - }).catch((e) => { - logger.log("Failed to load Olm: trying legacy version", e); + }).catch((wasmLoadError) => { + logger.log("Failed to load Olm: trying legacy version", wasmLoadError); return new Promise((resolve, reject) => { const s = document.createElement('script'); s.src = 'olm_legacy.js'; // XXX: This should be cache-busted too @@ -106,8 +106,8 @@ export function loadOlm(): Promise { return window.Olm.init(); }).then(() => { logger.log("Using legacy Olm"); - }).catch((e) => { - logger.log("Both WebAssembly and asm.js Olm failed!", e); + }).catch((legacyLoadError) => { + logger.log("Both WebAssembly and asm.js Olm failed!", legacyLoadError); }); }); } diff --git a/src/vector/platform/IPCManager.ts b/src/vector/platform/IPCManager.ts index c0ceda64ea..d25fe0af63 100644 --- a/src/vector/platform/IPCManager.ts +++ b/src/vector/platform/IPCManager.ts @@ -48,7 +48,7 @@ export class IPCManager { return deferred.promise; } - private onIpcReply = (ev: {}, payload: IPCPayload): void => { + private onIpcReply = (_ev: {}, payload: IPCPayload): void => { if (payload.id === undefined) { logger.warn("Ignoring IPC reply with no ID"); return; diff --git a/src/vector/platform/VectorBasePlatform.ts b/src/vector/platform/VectorBasePlatform.ts index b6e78629eb..cca39ea45e 100644 --- a/src/vector/platform/VectorBasePlatform.ts +++ b/src/vector/platform/VectorBasePlatform.ts @@ -47,7 +47,8 @@ export default abstract class VectorBasePlatform extends BasePlatform { if (this._favicon) { return this._favicon; } - return this._favicon = new Favicon(); + this._favicon = new Favicon(); + return this._favicon; } private updateFavicon() { diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index bef9c51d30..77be97ce49 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -80,7 +80,7 @@ export default class WebPlatform extends VectorBasePlatform { // annoyingly, the latest spec says this returns a // promise, but this is only supported in Chrome 46 // and Firefox 47, so adapt the callback API. - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { window.Notification.requestPermission((result) => { resolve(result); }); diff --git a/src/vector/rageshakesetup.ts b/src/vector/rageshakesetup.ts index d7dc3d05fa..cddd7adc98 100644 --- a/src/vector/rageshakesetup.ts +++ b/src/vector/rageshakesetup.ts @@ -39,7 +39,7 @@ export function initRageshake() { logger.log("To fix line numbers in Chrome: " + "Meatball menu → Settings → Ignore list → Add /rageshake\\.js$"); - window.addEventListener('beforeunload', (e) => { + window.addEventListener('beforeunload', () => { logger.log('element-web closing'); // try to flush the logs to indexeddb rageshake.flush(); diff --git a/src/vector/routing.ts b/src/vector/routing.ts index 73d5794179..d2633cb8e3 100644 --- a/src/vector/routing.ts +++ b/src/vector/routing.ts @@ -41,7 +41,7 @@ function routeUrl(location: Location) { (window.matrixChat as MatrixChatType).showScreen(s.screen, s.params); } -function onHashChange(ev: HashChangeEvent) { +function onHashChange() { if (decodeURIComponent(window.location.hash) === lastLocationHashSet) { // we just set this: no need to route it! return; diff --git a/test/unit-tests/vector/getconfig-test.ts b/test/unit-tests/vector/getconfig-test.ts index 87de15a7d6..360b888d86 100644 --- a/test/unit-tests/vector/getconfig-test.ts +++ b/test/unit-tests/vector/getconfig-test.ts @@ -48,16 +48,16 @@ describe('getVectorConfig()', () => { it('requests specific config for document domain', async () => { setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(specificConfig)) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + await getVectorConfig(); expect(request.mock.calls[0][0]).toEqual({ method: "GET", url: 'config.app.element.io.json', qs: { cachebuster: now } }) }); - + it('adds trailing slash to relativeLocation when not an empty string', async () => { setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(specificConfig)) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + await getVectorConfig('..'); expect(request.mock.calls[0][0]).toEqual(expect.objectContaining({ url: '../config.app.element.io.json' })) @@ -67,7 +67,7 @@ describe('getVectorConfig()', () => { it('returns parsed specific config when it is non-empty', async () => { setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(specificConfig)) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(specificConfig); }); @@ -75,7 +75,7 @@ describe('getVectorConfig()', () => { it('returns general config when specific config succeeds but is empty', async () => { setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify({})) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(generalConfig); }); @@ -83,7 +83,7 @@ describe('getVectorConfig()', () => { it('returns general config when specific config 404s', async () => { setRequestMockImplementationOnce(undefined, { status: 404 }) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(generalConfig); }); @@ -91,7 +91,7 @@ describe('getVectorConfig()', () => { it('returns general config when specific config is fetched from a file and is empty', async () => { setRequestMockImplementationOnce(undefined, { status: 0 }, '') setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(generalConfig); }); @@ -99,7 +99,7 @@ describe('getVectorConfig()', () => { it('returns general config when specific config returns a non-200 status', async () => { setRequestMockImplementationOnce(undefined, { status: 401 }) setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(generalConfig); }); @@ -107,7 +107,7 @@ describe('getVectorConfig()', () => { it('returns general config when specific config returns an error', async () => { setRequestMockImplementationOnce('err1') setRequestMockImplementationOnce(undefined, { status: 200 }, JSON.stringify(generalConfig)) - + const result = await getVectorConfig(); expect(result).toEqual(generalConfig); }); @@ -119,4 +119,12 @@ describe('getVectorConfig()', () => { await expect(() => getVectorConfig()).rejects.toEqual({"err": "err-general", "response": undefined}); }); + it('rejects with an error when config is invalid JSON', async () => { + setRequestMockImplementationOnce('err-specific'); + setRequestMockImplementationOnce(undefined, { status: 200 }, '{"invalid": "json",}'); + + await expect(() => getVectorConfig()).rejects.toEqual({ + err: new SyntaxError("Unexpected token } in JSON at position 19"), + }); + }); }); diff --git a/test/unit-tests/vector/platform/PWAPlatform-test.ts b/test/unit-tests/vector/platform/PWAPlatform-test.ts new file mode 100644 index 0000000000..23c41399ec --- /dev/null +++ b/test/unit-tests/vector/platform/PWAPlatform-test.ts @@ -0,0 +1,33 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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 PWAPlatform from "../../../../src/vector/platform/PWAPlatform"; + +describe('PWAPlatform', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("setNotificationCount", () => { + it("should call Navigator::setAppBadge", () => { + navigator.setAppBadge = jest.fn().mockResolvedValue(undefined); + const platform = new PWAPlatform(); + expect(navigator.setAppBadge).not.toHaveBeenCalled(); + platform.setNotificationCount(123); + expect(navigator.setAppBadge).toHaveBeenCalledWith(123); + }); + }); +}); diff --git a/test/unit-tests/vector/platform/WebPlatform-test.ts b/test/unit-tests/vector/platform/WebPlatform-test.ts index fa5c3e7d8b..39d4af2b5f 100644 --- a/test/unit-tests/vector/platform/WebPlatform-test.ts +++ b/test/unit-tests/vector/platform/WebPlatform-test.ts @@ -30,6 +30,13 @@ describe('WebPlatform', () => { expect(platform.getHumanReadableName()).toEqual('Web Platform'); }); + it('registers service worker', () => { + // @ts-ignore - mocking readonly object + navigator.serviceWorker = { register: jest.fn() }; + new WebPlatform(); + expect(navigator.serviceWorker.register).toHaveBeenCalled(); + }); + describe('notification support', () => { const mockNotification = { requestPermission: jest.fn(), @@ -50,7 +57,7 @@ describe('WebPlatform', () => { it('supportsNotifications returns true when platform supports notifications', () => { expect(new WebPlatform().supportsNotifications()).toBe(true); }); - + it('maySendNotifications returns true when notification permissions are not granted', () => { expect(new WebPlatform().maySendNotifications()).toBe(false); }); @@ -109,78 +116,76 @@ describe('WebPlatform', () => { }); describe('pollForUpdate()', () => { - it('should return not available and call showNoUpdate when current version matches most recent version', async () => { process.env.VERSION = prodVersion; setRequestMockImplementation(undefined, { status: 200}, prodVersion); const platform = new WebPlatform(); - + const showUpdate = jest.fn(); const showNoUpdate = jest.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); - + expect(result).toEqual({ status: UpdateCheckStatus.NotAvailable }); expect(showUpdate).not.toHaveBeenCalled(); expect(showNoUpdate).toHaveBeenCalled(); }); - + it('should strip v prefix from versions before comparing', async () => { process.env.VERSION = prodVersion; setRequestMockImplementation(undefined, { status: 200}, `v${prodVersion}`); const platform = new WebPlatform(); - + const showUpdate = jest.fn(); const showNoUpdate = jest.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); - + // versions only differ by v prefix, no update expect(result).toEqual({ status: UpdateCheckStatus.NotAvailable }); expect(showUpdate).not.toHaveBeenCalled(); expect(showNoUpdate).toHaveBeenCalled(); }); - + it('should return ready and call showUpdate when current version differs from most recent version', async () => { process.env.VERSION = '0.0.0'; // old version setRequestMockImplementation(undefined, { status: 200}, prodVersion); const platform = new WebPlatform(); - + const showUpdate = jest.fn(); const showNoUpdate = jest.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); - + expect(result).toEqual({ status: UpdateCheckStatus.Ready }); expect(showUpdate).toHaveBeenCalledWith('0.0.0', prodVersion); expect(showNoUpdate).not.toHaveBeenCalled(); }); - + it('should return ready without showing update when user registered in last 24', async () => { process.env.VERSION = '0.0.0'; // old version jest.spyOn(MatrixClientPeg, 'userRegisteredWithinLastHours').mockReturnValue(true); setRequestMockImplementation(undefined, { status: 200}, prodVersion); const platform = new WebPlatform(); - + const showUpdate = jest.fn(); const showNoUpdate = jest.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); - + expect(result).toEqual({ status: UpdateCheckStatus.Ready }); expect(showUpdate).not.toHaveBeenCalled(); expect(showNoUpdate).not.toHaveBeenCalled(); }); - + it('should return error when version check fails', async () => { setRequestMockImplementation('oups'); const platform = new WebPlatform(); - + const showUpdate = jest.fn(); const showNoUpdate = jest.fn(); const result = await platform.pollForUpdate(showUpdate, showNoUpdate); - + expect(result).toEqual({ status: UpdateCheckStatus.Error, detail: 'Unknown Error' }); expect(showUpdate).not.toHaveBeenCalled(); expect(showNoUpdate).not.toHaveBeenCalled(); }); }); - }); });