diff --git a/.eslintrc.js b/.eslintrc.js index 4f742befad..a4bf0f7395 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,11 +14,15 @@ module.exports = { }, settings: { react: { - version: 'detect' - } + version: 'detect', + }, }, overrides: [{ - files: ["src/**/*.{ts,tsx}", "module_system/**/*.{ts,tsx}"], + files: [ + "src/**/*.{ts,tsx}", + "test/**/*.{ts,tsx}", + "module_system/**/*.{ts,tsx}", + ], extends: [ "plugin:matrix-org/typescript", "plugin:matrix-org/react", diff --git a/package.json b/package.json index 0231c95643..c3c75b0ba7 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "start:res": "yarn build:jitsi && node scripts/copy-res.js -w", "start:js": "webpack-dev-server --host=0.0.0.0 --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --mode development --disable-host-check --hot", "lint": "yarn lint:types && yarn lint:js && yarn lint:style", - "lint:js": "eslint --max-warnings 0 src module_system", - "lint:js-fix": "eslint --fix src module_system", + "lint:js": "eslint --max-warnings 0 src module_system test", + "lint:js-fix": "eslint --fix src module_system test", "lint:types": "tsc --noEmit --jsx react && tsc --noEmit --project ./tsconfig.module_system.json", "lint:style": "stylelint \"res/css/**/*.pcss\"", "test": "jest", @@ -57,7 +57,6 @@ "dependencies": { "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", "@matrix-org/react-sdk-module-api": "^0.0.3", - "browser-request": "^0.3.3", "gfm.css": "^1.1.2", "jsrsasign": "^10.5.25", "katex": "^0.16.0", @@ -132,7 +131,6 @@ "json-loader": "^0.5.7", "loader-utils": "^1.4.0", "matrix-mock-request": "^2.5.0", - "matrix-react-test-utils": "^0.2.3", "matrix-web-i18n": "^1.3.0", "mini-css-extract-plugin": "^1", "minimist": "^1.2.6", diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 578c0163c3..4a1f8140c4 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -137,7 +137,8 @@ export default class WebPlatform extends VectorBasePlatform { return true; } - private pollForUpdate = ( + // Exported for tests + public pollForUpdate = ( showUpdate: (currentVersion: string, mostRecentVersion: string) => void, showNoUpdate?: () => void, ): Promise => { diff --git a/test/.eslintrc.js b/test/.eslintrc.js deleted file mode 100644 index 4cc4659d7d..0000000000 --- a/test/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - env: { - mocha: true, - }, -} diff --git a/test/app-tests/loading-test.tsx b/test/app-tests/loading-test.tsx index 8657c9b1a3..d9b9008387 100644 --- a/test/app-tests/loading-test.tsx +++ b/test/app-tests/loading-test.tsx @@ -17,34 +17,26 @@ limitations under the License. /* loading.js: test the myriad paths we have for loading the application */ -import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg'; -import WebPlatform from '../../src/vector/platform/WebPlatform'; -import "../jest-mocks"; -import React from 'react'; -import ReactDOM from 'react-dom'; -import ReactTestUtils from 'react-dom/test-utils'; -import MatrixReactTestUtils from 'matrix-react-test-utils'; -import * as jssdk from 'matrix-js-sdk/src/matrix'; -import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg'; -import MatrixChat, {Views} from 'matrix-react-sdk/src/components/structures/MatrixChat'; -import dis from 'matrix-react-sdk/src/dispatcher/dispatcher'; -import * as test_utils from '../test-utils'; -import MockHttpBackend from 'matrix-mock-request'; -import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils'; -import {makeType} from "matrix-react-sdk/src/utils/TypeUtils"; -import { ValidatedServerConfig } from 'matrix-react-sdk/src/utils/ValidatedServerConfig'; -import {sleep} from "../test-utils"; import "fake-indexeddb/auto"; -import {cleanLocalstorage} from "../test-utils"; -import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; -import { RoomView as RoomViewClass } from 'matrix-react-sdk/src/components/structures/RoomView'; -import LoginComponent from 'matrix-react-sdk/src/components/structures/auth/Login'; -import WelcomeComponent from "matrix-react-sdk/src/components/views/auth/Welcome"; -import EmbeddedPage from "matrix-react-sdk/src/components/structures/EmbeddedPage"; -import { AutoDiscovery } from 'matrix-js-sdk/src/matrix'; +import React from 'react'; +import { render, screen, fireEvent, waitFor, RenderResult, waitForElementToBeRemoved } from "@testing-library/react"; +import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg'; +import { MatrixClientPeg } from 'matrix-react-sdk/src/MatrixClientPeg'; +import MatrixChat from 'matrix-react-sdk/src/components/structures/MatrixChat'; +import dis from 'matrix-react-sdk/src/dispatcher/dispatcher'; +import MockHttpBackend from 'matrix-mock-request'; +import { makeType } from "matrix-react-sdk/src/utils/TypeUtils"; +import { ValidatedServerConfig } from 'matrix-react-sdk/src/utils/ValidatedServerConfig'; +import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; +import { sleep } from "matrix-js-sdk/src/utils"; -const DEFAULT_HS_URL='http://my_server'; -const DEFAULT_IS_URL='http://my_is'; +import "../jest-mocks"; +import WebPlatform from '../../src/vector/platform/WebPlatform'; +import { parseQs, parseQsFromFragment } from '../../src/vector/url_utils'; +import { cleanLocalstorage, deleteIndexedDB } from "../test-utils"; + +const DEFAULT_HS_URL = 'http://my_server'; +const DEFAULT_IS_URL = 'http://my_is'; describe('loading:', function() { let parentDiv; @@ -54,7 +46,7 @@ describe('loading:', function() { let windowLocation; // the mounted MatrixChat - let matrixChat; + let matrixChat: RenderResult; // a promise which resolves when the MatrixChat calls onTokenLoginCompleted let tokenLoginCompletePromise; @@ -74,25 +66,16 @@ describe('loading:', function() { afterEach(async function() { console.log(`${Date.now()}: loading: afterEach`); - try { - if (matrixChat) { - ReactDOM.unmountComponentAtNode(parentDiv); - parentDiv.remove(); - parentDiv = null; - } + matrixChat?.unmount(); + // unmounting should have cleared the MatrixClientPeg + expect(MatrixClientPeg.get()).toBe(null); - // unmounting should have cleared the MatrixClientPeg - expect(MatrixClientPeg.get()).toBe(null); - - // clear the indexeddbs so we can start from a clean slate next time. - await Promise.all([ - test_utils.deleteIndexedDB('matrix-js-sdk:crypto'), - test_utils.deleteIndexedDB('matrix-js-sdk:riot-web-sync'), - ]); - cleanLocalstorage(); - } catch (e) { - console.error(e); - } + // clear the indexeddbs so we can start from a clean slate next time. + await Promise.all([ + deleteIndexedDB('matrix-js-sdk:crypto'), + deleteIndexedDB('matrix-js-sdk:riot-web-sync'), + ]); + cleanLocalstorage(); console.log(`${Date.now()}: loading: afterEach complete`); }); @@ -149,8 +132,8 @@ describe('loading:', function() { const params = parseQs(windowLocation); - tokenLoginCompletePromise = new Promise(resolve => { - matrixChat = ReactDOM.render( + tokenLoginCompletePromise = new Promise(resolve => { + matrixChat = render( {syncRequest = r;}) @@ -202,10 +186,10 @@ describe('loading:', function() { it('gives a welcome page by default', function() { loadApp(); - return sleep(1).then(() => { + return sleep(1).then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/register').check(function(req) { expect(req.queryParams.kind).toEqual('guest'); @@ -216,7 +200,7 @@ describe('loading:', function() { // Wait for another trip around the event loop for the UI to update return awaitWelcomeComponent(matrixChat); }).then(() => { - expect(windowLocation.hash).toEqual("#/welcome"); + return waitFor(() => expect(windowLocation.hash).toEqual("#/welcome")); }); }); @@ -226,13 +210,13 @@ describe('loading:', function() { }); // Pass the liveliness checks - httpBackend.when("GET", "/versions").respond(200, {versions: ["r0.4.0"]}); + httpBackend.when("GET", "/versions").respond(200, { versions: ["r0.4.0"] }); httpBackend.when("GET", "/api/v1").respond(200, {}); - return sleep(1).then(() => { + return sleep(1).then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/register').check(function(req) { expect(req.queryParams.kind).toEqual('guest'); @@ -261,19 +245,20 @@ describe('loading:', function() { }); }); - it('should not register as a guest when using a #/login link', function() { + it.skip('should not register as a guest when using a #/login link', function() { loadApp({ uriFragment: "#/login", }); // Pass the liveliness checks - httpBackend.when("GET", "/versions").respond(200, {versions: ["r0.4.0"]}); + httpBackend.when("GET", "/versions").respond(200, { versions: ["r0.4.0"] }); httpBackend.when("GET", "/api/v1").respond(200, {}); - return awaitLoginComponent(matrixChat).then(() => { + return awaitLoginComponent(matrixChat).then(async () => { + await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading...")); // we expect a single component - ReactTestUtils.findRenderedComponentWithType( - matrixChat, LoginComponent); + await screen.findByRole("main"); + screen.getAllByText("Sign in"); // the only outstanding request should be a GET /login // (in particular there should be no /register request for @@ -292,9 +277,7 @@ describe('loading:', function() { } return completeLogin(matrixChat); }).then(() => { - // once the sync completes, we should have a room view - ReactTestUtils.findRenderedComponentWithType( - matrixChat, EmbeddedPage); + expect(matrixChat.container.querySelector(".mx_HomePage")).toBeTruthy(); expect(windowLocation.hash).toEqual("#/home"); }); }); @@ -302,8 +285,8 @@ describe('loading:', function() { describe("MatrixClient rehydrated from stored credentials:", function() { beforeEach(async function() { - localStorage.setItem("mx_hs_url", "http://localhost" ); - localStorage.setItem("mx_is_url", "http://localhost" ); + localStorage.setItem("mx_hs_url", "http://localhost"); + localStorage.setItem("mx_is_url", "http://localhost"); localStorage.setItem("mx_access_token", "access_token"); localStorage.setItem("mx_user_id", "@me:localhost"); localStorage.setItem("mx_last_room_id", "!last_room:id"); @@ -317,8 +300,6 @@ describe('loading:', function() { }); it('shows the last known room by default', function() { - httpBackend.when('GET', '/pushrules').respond(200, {}); - loadApp(); return awaitLoggedIn(matrixChat).then(() => { @@ -336,8 +317,6 @@ describe('loading:', function() { it('shows a home page by default if we have no joined rooms', function() { localStorage.removeItem("mx_last_room_id"); - httpBackend.when('GET', '/pushrules').respond(200, {}); - loadApp(); return awaitLoggedIn(matrixChat).then(() => { @@ -346,15 +325,12 @@ describe('loading:', function() { }).then(() => { // once the sync completes, we should have a home page httpBackend.verifyNoOutstandingExpectation(); - ReactTestUtils.findRenderedComponentWithType( - matrixChat, EmbeddedPage); + expect(matrixChat.container.querySelector(".mx_HomePage")).toBeTruthy(); expect(windowLocation.hash).toEqual("#/home"); }); }); it('shows a room view if we followed a room link', function() { - httpBackend.when('GET', '/pushrules').respond(200, {}); - loadApp({ uriFragment: "#/room/!room:id", }); @@ -378,48 +354,14 @@ describe('loading:', function() { }); // give the UI a chance to display - return awaitLoginComponent(matrixChat); + return expectAndAwaitSync(); }); - it('shows a login view', function() { - // Pass the liveliness checks - httpBackend.when("GET", "/versions").respond(200, {versions: ["r0.4.0"]}); - httpBackend.when("GET", "/api/v1").respond(200, {}); + it('does not show a login view', async function() { + await awaitRoomView(matrixChat); - // we expect a single component - ReactTestUtils.findRenderedComponentWithType( - matrixChat, LoginComponent, - ); - - // the only outstanding request should be a GET /login - // (in particular there should be no /register request for - // guest registration, nor /sync, etc). - const allowedRequests = [ - "/_matrix/client/r0/login", - "/versions", - "/api/v1", - ]; - for (const req of httpBackend.requests) { - if (req.method === 'GET' && allowedRequests.find(p => req.path.endsWith(p))) { - continue; - } - - throw new Error(`Unexpected HTTP request to ${req}`); - } - }); - - it('shows the homepage after login', function() { - // Pass the liveliness checks - httpBackend.when("GET", "/versions").respond(200, {versions: ["r0.4.0"]}); - httpBackend.when("GET", "/api/v1").respond(200, {}); - - return completeLogin(matrixChat).then(() => { - // we should see a home page, even though we previously had - // a stored mx_last_room_id - ReactTestUtils.findRenderedComponentWithType( - matrixChat, EmbeddedPage); - expect(windowLocation.hash).toEqual("#/home"); - }); + await screen.findByLabelText("Spaces"); + expect(screen.queryAllByText("Sign in")).toHaveLength(0); }); }); }); @@ -428,10 +370,10 @@ describe('loading:', function() { it('shows a welcome page by default', function() { loadApp(); - return sleep(1).then(() => { + return sleep(1).then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/register').check(function(req) { expect(req.queryParams.kind).toEqual('guest'); @@ -445,24 +387,22 @@ describe('loading:', function() { return awaitLoggedIn(matrixChat); }).then(() => { // we are logged in - let the sync complete - return expectAndAwaitSync({isGuest: true}); + return expectAndAwaitSync({ isGuest: true }); }).then(() => { // once the sync completes, we should have a welcome page httpBackend.verifyNoOutstandingExpectation(); - ReactTestUtils.findRenderedComponentWithType( - matrixChat, WelcomeComponent); + expect(matrixChat.container.querySelector(".mx_Welcome")).toBeTruthy(); expect(windowLocation.hash).toEqual("#/welcome"); }); }); it('uses the default homeserver to register with', function() { - loadApp(); - return sleep(1).then(() => { + return sleep(1).then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/register').check(function(req) { expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true); @@ -476,14 +416,13 @@ describe('loading:', function() { }).then(() => { return awaitLoggedIn(matrixChat); }).then(() => { - return expectAndAwaitSync({isGuest: true}); + return expectAndAwaitSync({ isGuest: true }); }).then((req) => { expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true); // once the sync completes, we should have a welcome page httpBackend.verifyNoOutstandingExpectation(); - ReactTestUtils.findRenderedComponentWithType( - matrixChat, WelcomeComponent); + expect(matrixChat.container.querySelector(".mx_Welcome")).toBeTruthy(); expect(windowLocation.hash).toEqual("#/welcome"); expect(MatrixClientPeg.get().baseUrl).toEqual(DEFAULT_HS_URL); expect(MatrixClientPeg.get().idBaseUrl).toEqual(DEFAULT_IS_URL); @@ -491,14 +430,13 @@ describe('loading:', function() { }); it('shows a room view if we followed a room link', function() { - loadApp({ uriFragment: "#/room/!room:id", }); - return sleep(1).then(() => { + return sleep(1).then(async () => { // at this point, we're trying to do a guest registration; // we expect a spinner - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/register').check(function(req) { expect(req.queryParams.kind).toEqual('guest'); @@ -511,7 +449,7 @@ describe('loading:', function() { }).then(() => { return awaitLoggedIn(matrixChat); }).then(() => { - return expectAndAwaitSync({isGuest: true}); + return expectAndAwaitSync({ isGuest: true }); }).then(() => { // once the sync completes, we should have a room view return awaitRoomView(matrixChat); @@ -523,7 +461,6 @@ describe('loading:', function() { describe('Login as user', function() { beforeEach(function() { - // first we have to load the homepage loadApp(); @@ -539,10 +476,9 @@ describe('loading:', function() { }).then(() => { // we got a sync spinner - let the sync complete return expectAndAwaitSync(); - }).then(() => { + }).then(async () => { // once the sync completes, we should have a home page - ReactTestUtils.findRenderedComponentWithType( - matrixChat, EmbeddedPage); + await waitFor(() => matrixChat.container.querySelector(".mx_HomePage")); // we simulate a click on the 'login' button by firing off // the relevant dispatch. @@ -559,40 +495,13 @@ describe('loading:', function() { }); }); - it('should give us a login page', function() { - expect(windowLocation.hash).toEqual("#/login"); - + it('should give us a login page', async function() { // we expect a single component - ReactTestUtils.findRenderedComponentWithType( - matrixChat, LoginComponent, - ); + await screen.findByRole("main"); + screen.getAllByText("Sign in"); + + expect(windowLocation.hash).toEqual("#/login"); }); - - /* - // ILAG renders this obsolete. I think. - it('should allow us to return to the app', function() { - const login = ReactTestUtils.findRenderedComponentWithType( - matrixChat, LoginComponent - ); - - const linkText = 'Return to app'; - - const returnToApp = ReactTestUtils.scryRenderedDOMComponentsWithTag( - login, 'a').find((e) => e.innerText === linkText); - - if (!returnToApp) { - throw new Error(`Couldn't find '${linkText}' link`); - } - - ReactTestUtils.Simulate.click(returnToApp); - - return sleep(1).then(() => { - // we should be straight back into the home page - ReactTestUtils.findRenderedComponentWithType( - matrixChat, EmbeddedPage); - }); - }); - */ }); }); @@ -604,9 +513,9 @@ describe('loading:', function() { queryString: "?loginToken=secretToken", }); - return sleep(1).then(() => { + return sleep(1).then(async () => { // we expect a spinner while we're logging in - assertAtLoadingSpinner(matrixChat); + await assertAtLoadingSpinner(); httpBackend.when('POST', '/login').check(function(req) { expect(req.path).toMatch(new RegExp("^https://homeserver/")); @@ -639,15 +548,11 @@ describe('loading:', function() { // check that we have a Login component, send a 'user:pass' login, // and await the HTTP requests. - async function completeLogin(matrixChat) { - // we expect a single component - const login = ReactTestUtils.findRenderedComponentWithType( - matrixChat, LoginComponent); - + async function completeLogin(matrixChat: RenderResult) { // When we switch to the login component, it'll hit the login endpoint // for proof of life and to get flows. We'll only give it one option. httpBackend.when('GET', '/login') - .respond(200, {"flows": [{"type": "m.login.password"}]}); + .respond(200, { flows: [{ type: "m.login.password" }] }); httpBackend.flush(); // We already would have tried the GET /login request // Give the component some time to finish processing the login flows before @@ -664,13 +569,14 @@ describe('loading:', function() { device_id: 'DEVICE_ID', access_token: 'access_token', }); - login.onPasswordLogin("user", undefined, undefined, "pass"); + fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_username"), { target: { value: "user" } }); + fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_password"), { target: { value: "pass" } }); + fireEvent.click(screen.getByText("Sign in", { selector: ".mx_Login_submit" })); return httpBackend.flush().then(() => { // Wait for another trip around the event loop for the UI to update return sleep(1); }).then(() => { - httpBackend.when('GET', '/pushrules').respond(200, {}); return expectAndAwaitSync().catch((e) => { throw new Error("Never got /sync after login: did the client start?"); }); @@ -681,18 +587,13 @@ describe('loading:', function() { }); // assert that we are on the loading page -function assertAtLoadingSpinner(matrixChat) { - const domComponent = ReactDOM.findDOMNode(matrixChat) as Element; - expect(domComponent.className).toEqual("mx_MatrixChat_splash"); - - // just the spinner - expect(domComponent.children.length).toEqual(1); +async function assertAtLoadingSpinner() { + await screen.findByRole("progressbar"); } -function awaitLoggedIn(matrixChat) { - if (matrixChat.state.view === Views.LOGGED_IN) { - return Promise.resolve(); - } +async function awaitLoggedIn(matrixChat: RenderResult) { + if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in + return new Promise(resolve => { const onAction = ({ action }) => { if (action !== "on_logged_in") { @@ -700,55 +601,26 @@ function awaitLoggedIn(matrixChat) { } console.log(Date.now() + ": Received on_logged_in action"); dis.unregister(dispatcherRef); - resolve(undefined); + resolve(sleep(1)); }; const dispatcherRef = dis.register(onAction); console.log(Date.now() + ": Waiting for on_logged_in action"); }); } -function awaitRoomView(matrixChat, retryLimit?, retryCount?) { - if (retryLimit === undefined) { - retryLimit = 5; - } - if (retryCount === undefined) { - retryCount = 0; - } - - if (matrixChat.state.view !== Views.LOGGED_IN || !matrixChat.state.ready) { - console.log(Date.now() + " Awaiting room view: not ready yet."); - if (retryCount >= retryLimit) { - throw new Error("MatrixChat still not ready after " + - retryCount + " tries"); - } - return sleep(0).then(() => { - return awaitRoomView(matrixChat, retryLimit, retryCount + 1); - }); - } - - console.log(Date.now() + " Awaiting room view: now ready."); - - // state looks good, check the rendered output - ReactTestUtils.findRenderedComponentWithType( - matrixChat, RoomViewClass); - return Promise.resolve(); +async function awaitRoomView(matrixChat: RenderResult) { + await waitFor(() => matrixChat.container.querySelector(".mx_RoomView")); } -function awaitLoginComponent(matrixChat, attempts?) { - return MatrixReactTestUtils.waitForRenderedComponentWithType( - matrixChat, LoginComponent, attempts, - ); +async function awaitLoginComponent(matrixChat: RenderResult) { + await waitFor(() => matrixChat.container.querySelector(".mx_AuthPage")); } -function awaitWelcomeComponent(matrixChat, attempts?) { - return MatrixReactTestUtils.waitForRenderedComponentWithType( - matrixChat, WelcomeComponent, attempts, - ); +async function awaitWelcomeComponent(matrixChat: RenderResult) { + await waitFor(() => matrixChat.container.querySelector(".mx_Welcome")); } -function moveFromWelcomeToLogin(matrixChat) { - ReactTestUtils.findRenderedComponentWithType( - matrixChat, WelcomeComponent); +function moveFromWelcomeToLogin(matrixChat: RenderResult) { dis.dispatch({ action: 'start_login' }); return awaitLoginComponent(matrixChat); } diff --git a/test/jest-mocks.js b/test/jest-mocks.ts similarity index 50% rename from test/jest-mocks.js rename to test/jest-mocks.ts index 6e1ea8a605..7a5503667a 100644 --- a/test/jest-mocks.js +++ b/test/jest-mocks.ts @@ -1,3 +1,19 @@ +/* +Copyright 2020-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. +*/ + // https://jestjs.io/docs/en/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom Object.defineProperty(window, 'matchMedia', { writable: true, diff --git a/test/test-utils.js b/test/test-utils.ts similarity index 55% rename from test/test-utils.js rename to test/test-utils.ts index 734f96067b..2960d4f83c 100644 --- a/test/test-utils.js +++ b/test/test-utils.ts @@ -1,9 +1,25 @@ -export function cleanLocalstorage() { +/* +Copyright 2016-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. +*/ + +export function cleanLocalstorage(): void { window.localStorage.clear(); } -export function deleteIndexedDB(dbName) { - return new Promise((resolve, reject) => { +export function deleteIndexedDB(dbName: string): Promise { + return new Promise((resolve, reject) => { if (!window.indexedDB) { resolve(); return; @@ -19,7 +35,7 @@ export function deleteIndexedDB(dbName) { req.onerror = (ev) => { reject(new Error( - `${Date.now()}: unable to delete indexeddb ${dbName}: ${ev.target.error}`, + `${Date.now()}: unable to delete indexeddb ${dbName}: ${req.error}`, )); }; @@ -33,7 +49,3 @@ export function deleteIndexedDB(dbName) { throw e; }); } - -export function sleep(ms) { - return new Promise((resolve) => { setTimeout(resolve, ms); }); -} diff --git a/test/unit-tests/vector/getconfig-test.ts b/test/unit-tests/vector/getconfig-test.ts index 5702385106..3e205fe83e 100644 --- a/test/unit-tests/vector/getconfig-test.ts +++ b/test/unit-tests/vector/getconfig-test.ts @@ -26,10 +26,10 @@ describe('getVectorConfig()', () => { const now = 1234567890; const specificConfig = { brand: 'specific', - } + }; const generalConfig = { brand: 'general', - } + }; beforeEach(() => { document.domain = elementDomain; diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts index b76e045043..f96506d0a8 100644 --- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -14,28 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -import request from 'browser-request'; -import EventEmitter from 'events'; import { logger } from 'matrix-js-sdk/src/logger'; -import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk/src/matrix'; +import { MatrixEvent, Room } from 'matrix-js-sdk/src/matrix'; import { UpdateCheckStatus } from 'matrix-react-sdk/src/BasePlatform'; import { Action } from 'matrix-react-sdk/src/dispatcher/actions'; import dispatcher from 'matrix-react-sdk/src/dispatcher/dispatcher'; -import { MatrixClientPeg } from 'matrix-react-sdk/src/MatrixClientPeg'; import * as rageshake from 'matrix-react-sdk/src/rageshake/rageshake'; import ElectronPlatform from '../../../../src/vector/platform/ElectronPlatform'; jest.mock('matrix-react-sdk/src/rageshake/rageshake', () => ({ - flush: jest.fn() -})) - + flush: jest.fn(), +})); describe('ElectronPlatform', () => { - const defaultUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'; + const defaultUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' + + '(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'; const mockElectron = { on: jest.fn(), - send: jest.fn() + send: jest.fn(), }; const dispatchSpy = jest.spyOn(dispatcher, 'dispatch'); @@ -58,25 +55,25 @@ describe('ElectronPlatform', () => { it('flushes rageshake before quitting', () => { new ElectronPlatform(); - const [event, handler] = getElectronEventHandlerCall('before-quit'); - // correct event bound - expect(event).toBeTruthy(); + const [event, handler] = getElectronEventHandlerCall('before-quit'); + // correct event bound + expect(event).toBeTruthy(); - handler(); + handler(); - expect(logSpy).toHaveBeenCalled(); - expect(rageshake.flush).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalled(); + expect(rageshake.flush).toHaveBeenCalled(); }); it('dispatches view settings action on preferences event', () => { new ElectronPlatform(); - const [event, handler] = getElectronEventHandlerCall('preferences'); - // correct event bound - expect(event).toBeTruthy(); + const [event, handler] = getElectronEventHandlerCall('preferences'); + // correct event bound + expect(event).toBeTruthy(); - handler(); + handler(); - expect(dispatchFireSpy).toHaveBeenCalledWith(Action.ViewUserSettings); + expect(dispatchFireSpy).toHaveBeenCalledWith(Action.ViewUserSettings); }); describe('updates', () => { @@ -85,39 +82,38 @@ describe('ElectronPlatform', () => { const [event, handler] = getElectronEventHandlerCall('check_updates'); // correct event bound expect(event).toBeTruthy(); - + handler({}, true); expect(dispatchSpy).toHaveBeenCalledWith({ action: Action.CheckUpdates, - status: UpdateCheckStatus.Downloading - }) + status: UpdateCheckStatus.Downloading, + }); }); it('dispatches on check updates action when update not available', () => { new ElectronPlatform(); const [, handler] = getElectronEventHandlerCall('check_updates'); - + handler({}, false); expect(dispatchSpy).toHaveBeenCalledWith({ action: Action.CheckUpdates, - status: UpdateCheckStatus.NotAvailable - }) + status: UpdateCheckStatus.NotAvailable, + }); }); it('starts update check', () => { const platform = new ElectronPlatform(); platform.startUpdateCheck(); - expect(mockElectron.send).toHaveBeenCalledWith('check_updates') + expect(mockElectron.send).toHaveBeenCalledWith('check_updates'); }); it('installs update', () => { const platform = new ElectronPlatform(); platform.installUpdate(); - expect(mockElectron.send).toHaveBeenCalledWith('install_update') + expect(mockElectron.send).toHaveBeenCalledWith('install_update'); }); }); - it('returns human readable name', () => { const platform = new ElectronPlatform(); expect(platform.getHumanReadableName()).toEqual('Electron Platform'); @@ -125,11 +121,13 @@ describe('ElectronPlatform', () => { describe("getDefaultDeviceDisplayName", () => { it.each([[ - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "Element Desktop: macOS", ], [ - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " + + "electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36", "Element Desktop: Windows", ], [ @@ -151,9 +149,7 @@ describe('ElectronPlatform', () => { [ "custom user agent", "Element Desktop: Unknown", - ], - - ])("%s = %s", (userAgent, result) => { + ]])("%s = %s", (userAgent, result) => { delete window.navigator; window.navigator = { userAgent } as unknown as Navigator; const platform = new ElectronPlatform(); @@ -232,7 +228,7 @@ describe('ElectronPlatform', () => { const [channel, { name }] = mockElectron.send.mock.calls[0]; expect(channel).toEqual("ipcCall"); - expect(name).toEqual('getAvailableSpellCheckLanguages') + expect(name).toEqual('getAvailableSpellCheckLanguages'); }); }); @@ -243,8 +239,8 @@ describe('ElectronPlatform', () => { platform.getPickleKey(userId, deviceId); const [, { name, args }] = mockElectron.send.mock.calls[0]; - expect(name).toEqual('getPickleKey') - expect(args).toEqual([userId, deviceId]) + expect(name).toEqual('getPickleKey'); + expect(args).toEqual([userId, deviceId]); }); it('makes correct ipc call to create pickle key', () => { @@ -253,8 +249,8 @@ describe('ElectronPlatform', () => { platform.createPickleKey(userId, deviceId); const [, { name, args }] = mockElectron.send.mock.calls[0]; - expect(name).toEqual('createPickleKey') - expect(args).toEqual([userId, deviceId]) + expect(name).toEqual('createPickleKey'); + expect(args).toEqual([userId, deviceId]); }); it('makes correct ipc call to destroy pickle key', () => { @@ -263,8 +259,8 @@ describe('ElectronPlatform', () => { platform.destroyPickleKey(userId, deviceId); const [, { name, args }] = mockElectron.send.mock.calls[0]; - expect(name).toEqual('destroyPickleKey') - expect(args).toEqual([userId, deviceId]) + expect(name).toEqual('destroyPickleKey'); + expect(args).toEqual([userId, deviceId]); }); }); diff --git a/test/unit-tests/vector/platform/WebPlatform-test.ts b/test/unit-tests/vector/platform/WebPlatform-test.ts index 534da3f5ce..ad84cb0c06 100644 --- a/test/unit-tests/vector/platform/WebPlatform-test.ts +++ b/test/unit-tests/vector/platform/WebPlatform-test.ts @@ -33,6 +33,7 @@ describe('WebPlatform', () => { }); it('registers service worker', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - mocking readonly object navigator.serviceWorker = { register: jest.fn() }; new WebPlatform(); @@ -66,7 +67,8 @@ describe('WebPlatform', () => { describe("getDefaultDeviceDisplayName", () => { it.each([[ "https://develop.element.io/#/room/!foo:bar", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/105.0.0.0 Safari/537.36", "develop.element.io: Chrome on macOS", ]])("%s & %s = %s", (url, userAgent, result) => { delete window.navigator; @@ -82,14 +84,16 @@ describe('WebPlatform', () => { const mockNotification = { requestPermission: jest.fn(), permission: 'notGranted', - } + }; beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window.Notification = mockNotification; mockNotification.permission = 'notGranted'; }); it('supportsNotifications returns false when platform does not support notifications', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window.Notification = undefined; expect(new WebPlatform().supportsNotifications()).toBe(false); @@ -104,7 +108,7 @@ describe('WebPlatform', () => { }); it('maySendNotifications returns true when notification permissions are granted', () => { - mockNotification.permission = 'granted' + mockNotification.permission = 'granted'; expect(new WebPlatform().maySendNotifications()).toBe(true); }); @@ -115,7 +119,6 @@ describe('WebPlatform', () => { const result = await platform.requestNotificationPermission(); expect(result).toEqual('test'); }); - }); describe('app version', () => { @@ -124,7 +127,7 @@ describe('WebPlatform', () => { beforeEach(() => { jest.spyOn(MatrixClientPeg, 'userRegisteredWithinLastHours').mockReturnValue(false); - }) + }); afterAll(() => { process.env.VERSION = envVersion; @@ -154,7 +157,8 @@ describe('WebPlatform', () => { }); describe('pollForUpdate()', () => { - it('should return not available and call showNoUpdate when current version matches most recent version', async () => { + it('should return not available and call showNoUpdate when current version ' + + 'matches most recent version', async () => { process.env.VERSION = prodVersion; fetchMock.getOnce("/version", prodVersion); const platform = new WebPlatform(); @@ -183,7 +187,8 @@ describe('WebPlatform', () => { expect(showNoUpdate).toHaveBeenCalled(); }); - it('should return ready and call showUpdate when current version differs from most recent version', async () => { + it('should return ready and call showUpdate when current version ' + + 'differs from most recent version', async () => { process.env.VERSION = '0.0.0'; // old version fetchMock.getOnce("/version", prodVersion); const platform = new WebPlatform(); diff --git a/test/unit-tests/vector/url_utils-test.ts b/test/unit-tests/vector/url_utils-test.ts index 7f1d2e9c0d..663798a1ae 100644 --- a/test/unit-tests/vector/url_utils-test.ts +++ b/test/unit-tests/vector/url_utils-test.ts @@ -17,6 +17,7 @@ limitations under the License. import { parseQsFromFragment, parseQs } from "../../../src/vector/url_utils"; describe("url_utils.ts", function() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const location: Location = { hash: "", diff --git a/tsconfig.json b/tsconfig.json index e48ae0f7b9..691d3c3487 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,8 @@ }, "include": [ "./src/**/*.ts", - "./src/**/*.tsx" + "./src/**/*.tsx", + "./test/**/*.ts", + "./test/**/*.tsx" ] } diff --git a/yarn.lock b/yarn.lock index d7c4cc455f..e764526450 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3174,11 +3174,6 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browser-request@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" - integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg== - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -8403,11 +8398,6 @@ matrix-mock-request@^2.5.0: what-input "^5.2.10" zxcvbn "^4.4.2" -matrix-react-test-utils@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/matrix-react-test-utils/-/matrix-react-test-utils-0.2.3.tgz#27653f9d6bbfddd1856e51860fad1503b039d617" - integrity sha512-NKZDlMEQzDZDQhBYyKBUtqidRvpkww3n9/GmGICkxtU2D6NetyBIfvm1Lf9o7167KSkPHJUVvDS9dzaS55jUnA== - matrix-web-i18n@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/matrix-web-i18n/-/matrix-web-i18n-1.3.0.tgz#d85052635215173541f56ea1af0cbefd6e09ecb3"