Migrate batch of tests to vitest (#34111)
* Migrate batch of tests tro vitest * Iterate * Migrate another test * Fix lockfile
This commit is contained in:
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { vi } from "vitest";
|
||||
import { vi, expect as viExpect } from "vitest";
|
||||
import { mocked as jestMocked } from "jest-mock";
|
||||
|
||||
export const isJest = typeof jest !== "undefined";
|
||||
@@ -22,4 +22,7 @@ const adapter = {
|
||||
const mocked = adapter.mocked;
|
||||
export { adapter as vi, mocked };
|
||||
|
||||
const _expect = isJest ? (expect as unknown as typeof viExpect) : viExpect;
|
||||
export { _expect as expect };
|
||||
|
||||
export { type Mocked, type MockedObject } from "vitest";
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { mkMessage, type MessageEventProps } from "./test-utils";
|
||||
import { expect } from "../setup/adapter.ts";
|
||||
|
||||
export const makeThreadEvent = ({
|
||||
rootEventId,
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { type Room, type RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { avatarUrlForRoom } from "../../src/Avatar";
|
||||
import { type Media, mediaFromMxc } from "../../src/customisations/Media";
|
||||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||
|
||||
jest.mock("../../src/customisations/Media", () => ({
|
||||
mediaFromMxc: jest.fn(),
|
||||
}));
|
||||
|
||||
const roomId = "!room:example.com";
|
||||
const avatarUrl1 = "https://example.com/avatar1";
|
||||
const avatarUrl2 = "https://example.com/avatar2";
|
||||
|
||||
describe("avatarUrlForRoom", () => {
|
||||
let getThumbnailOfSourceHttp: jest.Mock;
|
||||
let room: Room;
|
||||
let roomMember: RoomMember;
|
||||
let dmRoomMap: DMRoomMap;
|
||||
|
||||
beforeEach(() => {
|
||||
getThumbnailOfSourceHttp = jest.fn();
|
||||
mocked(mediaFromMxc).mockImplementation((): Media => {
|
||||
return {
|
||||
getThumbnailOfSourceHttp,
|
||||
} as unknown as Media;
|
||||
});
|
||||
room = {
|
||||
roomId,
|
||||
getMxcAvatarUrl: jest.fn(),
|
||||
isSpaceRoom: jest.fn(),
|
||||
getType: jest.fn(),
|
||||
getAvatarFallbackMember: jest.fn(),
|
||||
} as unknown as Room;
|
||||
dmRoomMap = {
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
} as unknown as DMRoomMap;
|
||||
DMRoomMap.setShared(dmRoomMap);
|
||||
roomMember = {
|
||||
getMxcAvatarUrl: jest.fn(),
|
||||
} as unknown as RoomMember;
|
||||
});
|
||||
|
||||
it("should return null for a null room", () => {
|
||||
expect(avatarUrlForRoom(null, 128, 128)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the HTTP source if the room provides a MXC url", () => {
|
||||
mocked(room.getMxcAvatarUrl).mockReturnValue(avatarUrl1);
|
||||
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
|
||||
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
|
||||
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
|
||||
});
|
||||
|
||||
it("should return null for a space room", () => {
|
||||
mocked(room.isSpaceRoom).mockReturnValue(true);
|
||||
mocked(room.getType).mockReturnValue(RoomType.Space);
|
||||
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if the room is not a DM", () => {
|
||||
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue(undefined);
|
||||
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
|
||||
expect(dmRoomMap.getUserIdForRoomId).toHaveBeenCalledWith(roomId);
|
||||
});
|
||||
|
||||
it("should return null if there is no other member in the room", () => {
|
||||
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
|
||||
mocked(room.getAvatarFallbackMember).mockReturnValue(undefined);
|
||||
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if the other member has no avatar URL", () => {
|
||||
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
|
||||
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
|
||||
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the other member's avatar URL", () => {
|
||||
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
|
||||
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
|
||||
mocked(roomMember.getMxcAvatarUrl).mockReturnValue(avatarUrl2);
|
||||
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
|
||||
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
|
||||
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
|
||||
});
|
||||
});
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 Clemens Zeidler
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { isKeyComboMatch, type KeyCombo } from "../../src/KeyBindingsManager";
|
||||
|
||||
function mockKeyEvent(
|
||||
key: string,
|
||||
modifiers?: {
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
},
|
||||
): KeyboardEvent {
|
||||
return {
|
||||
key,
|
||||
ctrlKey: modifiers?.ctrlKey ?? false,
|
||||
altKey: modifiers?.altKey ?? false,
|
||||
shiftKey: modifiers?.shiftKey ?? false,
|
||||
metaKey: modifiers?.metaKey ?? false,
|
||||
} as KeyboardEvent;
|
||||
}
|
||||
|
||||
describe("KeyBindingsManager", () => {
|
||||
it("should match basic key combo", () => {
|
||||
const combo1: KeyCombo = {
|
||||
key: "k",
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k"), combo1, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n"), combo1, false)).toBe(false);
|
||||
});
|
||||
|
||||
it("should match key + modifier key combo", () => {
|
||||
const combo: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k"), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true, metaKey: true }), combo, false)).toBe(false);
|
||||
|
||||
const combo2: KeyCombo = {
|
||||
key: "k",
|
||||
metaKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo2, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { metaKey: true }), combo2, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k"), combo2, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { altKey: true, metaKey: true }), combo2, false)).toBe(false);
|
||||
|
||||
const combo3: KeyCombo = {
|
||||
key: "k",
|
||||
altKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { altKey: true }), combo3, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { altKey: true }), combo3, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k"), combo3, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo3, false)).toBe(false);
|
||||
|
||||
const combo4: KeyCombo = {
|
||||
key: "k",
|
||||
shiftKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true }), combo4, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { shiftKey: true }), combo4, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k"), combo4, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true, ctrlKey: true }), combo4, false)).toBe(false);
|
||||
});
|
||||
|
||||
it("should match key + multiple modifiers key combo", () => {
|
||||
const combo: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlKey: true,
|
||||
altKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true, altKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true, shiftKey: true }), combo, false)).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
const combo2: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
altKey: true,
|
||||
};
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true }), combo2, false)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true, shiftKey: true, altKey: true }), combo2, false)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo2, false)).toBe(false);
|
||||
expect(
|
||||
isKeyComboMatch(
|
||||
mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
|
||||
combo2,
|
||||
false,
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
const combo3: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
altKey: true,
|
||||
metaKey: true,
|
||||
};
|
||||
expect(
|
||||
isKeyComboMatch(
|
||||
mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
|
||||
combo3,
|
||||
false,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isKeyComboMatch(
|
||||
mockKeyEvent("n", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
|
||||
combo3,
|
||||
false,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true }), combo3, false)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("should match ctrlOrMeta key combo", () => {
|
||||
const combo: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlOrCmdKey: true,
|
||||
};
|
||||
// PC:
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, false)).toBe(false);
|
||||
// MAC:
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo, true)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, true)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, true)).toBe(false);
|
||||
});
|
||||
|
||||
it("should match advanced ctrlOrMeta key combo", () => {
|
||||
const combo: KeyCombo = {
|
||||
key: "k",
|
||||
ctrlOrCmdKey: true,
|
||||
altKey: true,
|
||||
};
|
||||
// PC:
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true, altKey: true }), combo, false)).toBe(false);
|
||||
// MAC:
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true, altKey: true }), combo, true)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, true)).toBe(false);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import Markdown from "../../src/Markdown";
|
||||
|
||||
describe("Markdown parser test", () => {
|
||||
describe("fixing HTML links", () => {
|
||||
const testString = [
|
||||
"Test1:",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"http://google.com/_thing_",
|
||||
"https://matrix.org/_matrix/client/foo/123_",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"",
|
||||
"Test1A:",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"http://google.com/_thing_",
|
||||
"https://matrix.org/_matrix/client/foo/123_",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"",
|
||||
"Test2:",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"",
|
||||
"Test3:",
|
||||
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org",
|
||||
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org",
|
||||
].join("\n");
|
||||
|
||||
it("tests that links with markdown empasis in them are getting properly HTML formatted", () => {
|
||||
/* eslint-disable max-len */
|
||||
const expectedResult = [
|
||||
"<p>Test1:<br />#_foonetic_xkcd:matrix.org<br />http://google.com/_thing_<br />https://matrix.org/_matrix/client/foo/123_<br />#_foonetic_xkcd:matrix.org</p>",
|
||||
"<p>Test1A:<br />#_foonetic_xkcd:matrix.org<br />http://google.com/_thing_<br />https://matrix.org/_matrix/client/foo/123_<br />#_foonetic_xkcd:matrix.org</p>",
|
||||
"<p>Test2:<br />http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg<br />http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</p>",
|
||||
"<p>Test3:<br />https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org<br />https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</p>",
|
||||
"",
|
||||
].join("\n");
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
it("tests that links with autolinks are not touched at all and are still properly formatted", () => {
|
||||
const test = [
|
||||
"Test1:",
|
||||
"<#_foonetic_xkcd:matrix.org>",
|
||||
"<http://google.com/_thing_>",
|
||||
"<https://matrix.org/_matrix/client/foo/123_>",
|
||||
"<#_foonetic_xkcd:matrix.org>",
|
||||
"",
|
||||
"Test1A:",
|
||||
"<#_foonetic_xkcd:matrix.org>",
|
||||
"<http://google.com/_thing_>",
|
||||
"<https://matrix.org/_matrix/client/foo/123_>",
|
||||
"<#_foonetic_xkcd:matrix.org>",
|
||||
"",
|
||||
"Test2:",
|
||||
"<http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg>",
|
||||
"<http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg>",
|
||||
"",
|
||||
"Test3:",
|
||||
"<https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org>",
|
||||
"<https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org>",
|
||||
].join("\n");
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* NOTE: I'm not entirely sure if those "<"" and ">" should be visible in here for #_foonetic_xkcd:matrix.org
|
||||
* but it seems to be actually working properly
|
||||
*/
|
||||
const expectedResult = [
|
||||
'<p>Test1:<br /><#_foonetic_xkcd:matrix.org><br /><a href="http://google.com/_thing_">http://google.com/_thing_</a><br /><a href="https://matrix.org/_matrix/client/foo/123_">https://matrix.org/_matrix/client/foo/123_</a><br /><#_foonetic_xkcd:matrix.org></p>',
|
||||
'<p>Test1A:<br /><#_foonetic_xkcd:matrix.org><br /><a href="http://google.com/_thing_">http://google.com/_thing_</a><br /><a href="https://matrix.org/_matrix/client/foo/123_">https://matrix.org/_matrix/client/foo/123_</a><br /><#_foonetic_xkcd:matrix.org></p>',
|
||||
'<p>Test2:<br /><a href="http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a><br /><a href="http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a></p>',
|
||||
'<p>Test3:<br /><a href="https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a><br /><a href="https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a></p>',
|
||||
"",
|
||||
].join("\n");
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(test);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it("expects that links in codeblock are not modified", () => {
|
||||
const expectedResult = [
|
||||
'<pre><code class="language-Test1:">#_foonetic_xkcd:matrix.org',
|
||||
"http://google.com/_thing_",
|
||||
"https://matrix.org/_matrix/client/foo/123_",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"",
|
||||
"Test1A:",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"http://google.com/_thing_",
|
||||
"https://matrix.org/_matrix/client/foo/123_",
|
||||
"#_foonetic_xkcd:matrix.org",
|
||||
"",
|
||||
"Test2:",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"",
|
||||
"Test3:",
|
||||
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org",
|
||||
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org```",
|
||||
"</code></pre>",
|
||||
"",
|
||||
].join("\n");
|
||||
const md = new Markdown("```" + testString + "```");
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('expects that links with emphasis are "escaped" correctly', () => {
|
||||
/* eslint-disable max-len */
|
||||
const testString = [
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg" +
|
||||
" " +
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg" +
|
||||
" " +
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"https://example.com/_test_test2_-test3",
|
||||
"https://example.com/_test_test2_test3_",
|
||||
"https://example.com/_test__test2_test3_",
|
||||
"https://example.com/_test__test2__test3_",
|
||||
"https://example.com/_test__test2_test3__",
|
||||
"https://example.com/_test__test2",
|
||||
].join("\n");
|
||||
const expectedResult = [
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
|
||||
"https://example.com/_test_test2_-test3",
|
||||
"https://example.com/_test_test2_test3_",
|
||||
"https://example.com/_test__test2_test3_",
|
||||
"https://example.com/_test__test2__test3_",
|
||||
"https://example.com/_test__test2_test3__",
|
||||
"https://example.com/_test__test2",
|
||||
].join("<br />");
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it("expects that the link part will not be accidentally added to <strong>", () => {
|
||||
/* eslint-disable max-len */
|
||||
const testString = `https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py`;
|
||||
const expectedResult = "https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py";
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it("expects that the link part will not be accidentally added to <strong> for multiline links", () => {
|
||||
/* eslint-disable max-len */
|
||||
const testString = [
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
|
||||
" " +
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
|
||||
" " +
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
|
||||
].join("\n");
|
||||
const expectedResult = [
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
|
||||
" " +
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
|
||||
" " +
|
||||
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
|
||||
].join("<br />");
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it("resumes applying formatting to the rest of a message after a link", () => {
|
||||
const testString = "http://google.com/_thing_ *does* __not__ exist";
|
||||
const expectedResult = "http://google.com/_thing_ <em>does</em> <strong>not</strong> exist";
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,395 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { type PostHog } from "posthog-js";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import {
|
||||
Anonymity,
|
||||
getRedactedCurrentLocation,
|
||||
type IPosthogEvent,
|
||||
PosthogAnalytics,
|
||||
} from "../../src/PosthogAnalytics";
|
||||
import SdkConfig from "../../src/SdkConfig";
|
||||
import { getMockClientWithEventEmitter } from "../test-utils";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { Layout } from "../../src/settings/enums/Layout";
|
||||
import defaultDispatcher from "../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../src/dispatcher/actions";
|
||||
import { SettingLevel } from "../../src/settings/SettingLevel";
|
||||
|
||||
const getFakePosthog = (): PostHog =>
|
||||
({
|
||||
capture: jest.fn(),
|
||||
init: jest.fn(),
|
||||
identify: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
register: jest.fn(),
|
||||
get_distinct_id: jest.fn(),
|
||||
persistence: {
|
||||
get_property: jest.fn(),
|
||||
},
|
||||
identifyUser: jest.fn(),
|
||||
}) as unknown as PostHog;
|
||||
|
||||
interface ITestEvent extends IPosthogEvent {
|
||||
eventName: "JestTestEvents";
|
||||
foo?: string;
|
||||
}
|
||||
|
||||
describe("PosthogAnalytics", () => {
|
||||
let fakePosthog: PostHog;
|
||||
const shaHashes: Record<string, string> = {
|
||||
"42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049",
|
||||
"some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b",
|
||||
"pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4",
|
||||
"foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
fakePosthog = getFakePosthog();
|
||||
|
||||
Object.defineProperty(window, "crypto", {
|
||||
value: {
|
||||
subtle: {
|
||||
digest: async (_: AlgorithmIdentifier, encodedMessage: BufferSource) => {
|
||||
const message = new TextDecoder().decode(encodedMessage);
|
||||
const hexHash = shaHashes[message];
|
||||
const bytes: number[] = [];
|
||||
for (let c = 0; c < hexHash.length; c += 2) {
|
||||
bytes.push(parseInt(hexHash.slice(c, c + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(window, "crypto", {
|
||||
value: null,
|
||||
});
|
||||
SdkConfig.reset(); // we touch the config, so clean up
|
||||
});
|
||||
|
||||
describe("Initialisation", () => {
|
||||
it("Should not be enabled without config being set", () => {
|
||||
// force empty/invalid state for posthog options
|
||||
SdkConfig.put({ brand: "Testing" });
|
||||
const analytics = new PosthogAnalytics(fakePosthog);
|
||||
expect(analytics.isEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("Should be enabled if config is set", () => {
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
const analytics = new PosthogAnalytics(fakePosthog);
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
expect(analytics.isEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tracking", () => {
|
||||
let analytics: PosthogAnalytics;
|
||||
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
|
||||
analytics = new PosthogAnalytics(fakePosthog);
|
||||
});
|
||||
|
||||
it("Should pass event to posthog", () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][0]).toBe("JestTestEvents");
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["foo"]).toEqual("bar");
|
||||
});
|
||||
|
||||
it("Should not track events if anonymous", async () => {
|
||||
analytics.setAnonymity(Anonymity.Anonymous);
|
||||
await analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
expect(fakePosthog.capture).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should not track any events if disabled", async () => {
|
||||
analytics.setAnonymity(Anonymity.Disabled);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
expect(fakePosthog.capture).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should anonymise location of a known screen", async () => {
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "#/register/some/pii", "/");
|
||||
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
||||
});
|
||||
|
||||
it("Should anonymise location of an unknown screen", async () => {
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "#/not_a_screen_name/some/pii", "/");
|
||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
||||
});
|
||||
|
||||
it("Should handle an empty hash", async () => {
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "", "/");
|
||||
expect(location).toBe("https://foo.bar/");
|
||||
});
|
||||
|
||||
it("Should identify the user to posthog if pseudonymous", async () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
const client = getMockClientWithEventEmitter({
|
||||
getAccountDataFromServer: jest.fn().mockResolvedValue(null),
|
||||
setAccountData: jest.fn().mockResolvedValue({}),
|
||||
});
|
||||
await analytics.identifyUser(client, () => "analytics_id");
|
||||
expect(mocked(fakePosthog).identify.mock.calls[0][0]).toBe("analytics_id");
|
||||
});
|
||||
|
||||
it("Should not identify the user to posthog if anonymous", async () => {
|
||||
analytics.setAnonymity(Anonymity.Anonymous);
|
||||
const client = getMockClientWithEventEmitter({});
|
||||
await analytics.identifyUser(client, () => "analytics_id");
|
||||
expect(mocked(fakePosthog).identify.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it("Should identify using the server's analytics id if present", async () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
|
||||
const client = getMockClientWithEventEmitter({
|
||||
getAccountDataFromServer: jest.fn().mockResolvedValue({ id: "existing_analytics_id" }),
|
||||
setAccountData: jest.fn().mockResolvedValue({}),
|
||||
});
|
||||
await analytics.identifyUser(client, () => "new_analytics_id");
|
||||
expect(mocked(fakePosthog).identify.mock.calls[0][0]).toBe("existing_analytics_id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("WebLayout", () => {
|
||||
let analytics: PosthogAnalytics;
|
||||
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
|
||||
analytics = new PosthogAnalytics(fakePosthog);
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
it("should send layout IRC correctly", async () => {
|
||||
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||
defaultDispatcher.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: "layout",
|
||||
},
|
||||
true,
|
||||
);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
});
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
|
||||
WebLayout: "IRC",
|
||||
});
|
||||
});
|
||||
|
||||
it("should send layout Bubble correctly", async () => {
|
||||
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||
defaultDispatcher.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: "layout",
|
||||
},
|
||||
true,
|
||||
);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
});
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
|
||||
WebLayout: "Bubble",
|
||||
});
|
||||
});
|
||||
|
||||
it("should send layout Group correctly", async () => {
|
||||
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
defaultDispatcher.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: "layout",
|
||||
},
|
||||
true,
|
||||
);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
});
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
|
||||
WebLayout: "Group",
|
||||
});
|
||||
});
|
||||
|
||||
it("should send layout Compact correctly", async () => {
|
||||
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true);
|
||||
defaultDispatcher.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: "useCompactLayout",
|
||||
},
|
||||
true,
|
||||
);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
});
|
||||
console.log(mocked(fakePosthog).capture.mock.calls[0]);
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
|
||||
WebLayout: "Compact",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("UrlPreviews", () => {
|
||||
let analytics: PosthogAnalytics;
|
||||
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
|
||||
analytics = new PosthogAnalytics(fakePosthog);
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
SdkConfig.reset();
|
||||
});
|
||||
|
||||
it("should set UrlPreviewsEnabled on change", async () => {
|
||||
defaultDispatcher.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: "urlPreviewsEnabled",
|
||||
newValue: true,
|
||||
},
|
||||
true,
|
||||
);
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
});
|
||||
expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toMatchObject({
|
||||
URLPreviewsEnabled: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("CryptoSdk", () => {
|
||||
let analytics: PosthogAnalytics;
|
||||
const getFakeClient = (): MatrixClient =>
|
||||
({
|
||||
getCrypto: jest.fn(),
|
||||
setAccountData: jest.fn(),
|
||||
// just fake return an `im.vector.analytics` content
|
||||
getAccountDataFromServer: jest.fn().mockReturnValue({
|
||||
id: "0000000",
|
||||
pseudonymousAnalyticsOptIn: true,
|
||||
}),
|
||||
}) as unknown as MatrixClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
|
||||
analytics = new PosthogAnalytics(fakePosthog);
|
||||
});
|
||||
|
||||
// `updateAnonymityFromSettings` is called On page load / login / account data change.
|
||||
// We manually call it so we can test the behaviour.
|
||||
async function simulateLogin(rustBackend: boolean, pseudonymous = true) {
|
||||
// To simulate a switch we call updateAnonymityFromSettings.
|
||||
// As per documentation this function is called On login.
|
||||
const mockClient = getFakeClient();
|
||||
mocked(mockClient.getCrypto).mockReturnValue({
|
||||
getVersion: () => {
|
||||
return rustBackend ? "Rust SDK 0.6.0 (9c6b550), Vodozemac 0.5.0" : "Olm 3.2.0";
|
||||
},
|
||||
} as unknown as CryptoApi);
|
||||
await analytics.updateAnonymityFromSettings(mockClient, pseudonymous);
|
||||
}
|
||||
|
||||
it("should send rust cryptoSDK superProperty correctly", async () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
|
||||
await simulateLogin(false);
|
||||
|
||||
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy");
|
||||
});
|
||||
|
||||
it("should send Legacy cryptoSDK superProperty correctly", async () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
|
||||
await simulateLogin(false);
|
||||
|
||||
// Super Properties are properties associated with events that are set once and then sent with every capture call.
|
||||
// They are set using posthog.register
|
||||
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Legacy");
|
||||
});
|
||||
|
||||
it("should send cryptoSDK superProperty when enabling analytics", async () => {
|
||||
analytics.setAnonymity(Anonymity.Disabled);
|
||||
|
||||
await simulateLogin(true, false);
|
||||
|
||||
// This initial call is due to the call to register platformSuperProperties
|
||||
// The important thing is that the cryptoSDK superProperty is not set.
|
||||
expect(mocked(fakePosthog).register.mock.lastCall![0]).toStrictEqual({});
|
||||
|
||||
// switching to pseudonymous should ensure that the cryptoSDK superProperty is set correctly
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
// Super Properties are properties associated with events that are set once and then sent with every capture call.
|
||||
// They are set using posthog.register
|
||||
expect(mocked(fakePosthog).register.mock.lastCall![0]["cryptoSDK"]).toStrictEqual("Rust");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { PosthogAnalytics } from "../../src/PosthogAnalytics";
|
||||
import PosthogTrackers from "../../src/PosthogTrackers";
|
||||
|
||||
describe("PosthogTrackers", () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("tracks URL Previews", () => {
|
||||
jest.spyOn(PosthogAnalytics.instance, "trackEvent");
|
||||
const tracker = new PosthogTrackers();
|
||||
tracker.trackUrlPreview("$123456", false, [
|
||||
{
|
||||
image: {},
|
||||
},
|
||||
]);
|
||||
tracker.trackUrlPreview("$123456", false, [{}]);
|
||||
// Ignores subsequent calls.
|
||||
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledWith({
|
||||
eventName: "UrlPreviewRendered",
|
||||
previewKind: "LegacyCard",
|
||||
hasThumbnail: true,
|
||||
previewCount: 1,
|
||||
encryptedRoom: false,
|
||||
});
|
||||
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,395 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import {
|
||||
PushRuleActionName,
|
||||
TweakName,
|
||||
NotificationCountType,
|
||||
Room,
|
||||
EventStatus,
|
||||
EventType,
|
||||
type MatrixEvent,
|
||||
PendingEventOrdering,
|
||||
type MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { mkEvent, mkRoom, mkRoomMember, muteRoom, stubClient, upsertRoomStateEvents } from "../test-utils";
|
||||
import {
|
||||
getRoomNotifsState,
|
||||
RoomNotifState,
|
||||
getUnreadNotificationCount,
|
||||
determineUnreadState,
|
||||
getUnsentMessages,
|
||||
} from "../../src/RoomNotifs";
|
||||
import { NotificationLevel } from "../../src/stores/notifications/NotificationLevel";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
import { mkThread } from "../test-utils/threads";
|
||||
|
||||
describe("getUnsentMessages", () => {
|
||||
const ROOM_ID = "!roomId";
|
||||
let room: Room;
|
||||
let event: MatrixEvent;
|
||||
let client: MatrixClient;
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room(ROOM_ID, client, client.getUserId()!, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
user: "@user1:server",
|
||||
room: "!room1:server",
|
||||
content: {},
|
||||
});
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
});
|
||||
|
||||
it("returns no unsent messages", () => {
|
||||
expect(getUnsentMessages(room)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("checks the event status", () => {
|
||||
room.addPendingEvent(event, "123");
|
||||
|
||||
expect(getUnsentMessages(room)).toHaveLength(1);
|
||||
event.status = EventStatus.SENT;
|
||||
|
||||
expect(getUnsentMessages(room)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("only returns events related to a thread", () => {
|
||||
room.addPendingEvent(event, "123");
|
||||
|
||||
const { rootEvent, events } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: "@alice:example.org",
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 2,
|
||||
});
|
||||
rootEvent.status = EventStatus.NOT_SENT;
|
||||
room.addPendingEvent(rootEvent, rootEvent.getId()!);
|
||||
for (const event of events) {
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
room.addPendingEvent(event, Date.now() + Math.random() + "");
|
||||
}
|
||||
|
||||
const pendingEvents = getUnsentMessages(room, rootEvent.getId());
|
||||
|
||||
expect(pendingEvents[0].threadRootId).toBe(rootEvent.getId());
|
||||
expect(pendingEvents[1].threadRootId).toBe(rootEvent.getId());
|
||||
expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId());
|
||||
|
||||
// Filters out the non thread events
|
||||
expect(pendingEvents.every((ev) => ev.getId() !== event.getId())).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RoomNotifs test", () => {
|
||||
let client: jest.Mocked<MatrixClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient() as jest.Mocked<MatrixClient>;
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles rules with no conditions", () => {
|
||||
mocked(client).pushRules = {
|
||||
global: {
|
||||
override: [
|
||||
{
|
||||
rule_id: "!roomId:server",
|
||||
enabled: true,
|
||||
default: false,
|
||||
actions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(getRoomNotifsState(client, "!roomId:server")).toBe(null);
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles guest users", () => {
|
||||
mocked(client).isGuest.mockReturnValue(true);
|
||||
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessages);
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles mute state", () => {
|
||||
const room = mkRoom(client, "!roomId:server");
|
||||
muteRoom(room);
|
||||
expect(getRoomNotifsState(client, room.roomId)).toBe(RoomNotifState.Mute);
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles mute state for legacy DontNotify action", () => {
|
||||
const room = mkRoom(client, "!roomId:server");
|
||||
muteRoom(room);
|
||||
client.pushRules!.global.override![0]!.actions = [PushRuleActionName.DontNotify];
|
||||
expect(getRoomNotifsState(client, room.roomId)).toBe(RoomNotifState.Mute);
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles mentions only", () => {
|
||||
(client as any).getRoomPushRule = () => ({
|
||||
rule_id: "!roomId:server",
|
||||
enabled: true,
|
||||
default: false,
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
});
|
||||
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.MentionsOnly);
|
||||
});
|
||||
|
||||
it("getRoomNotifsState handles noisy", () => {
|
||||
(client as any).getRoomPushRule = () => ({
|
||||
rule_id: "!roomId:server",
|
||||
enabled: true,
|
||||
default: false,
|
||||
actions: [{ set_tweak: TweakName.Sound, value: "default" }],
|
||||
});
|
||||
expect(getRoomNotifsState(client, "!roomId:server")).toBe(RoomNotifState.AllMessagesLoud);
|
||||
});
|
||||
|
||||
describe("getUnreadNotificationCount", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
const THREAD_ID = "$threadId";
|
||||
|
||||
let room: Room;
|
||||
beforeEach(() => {
|
||||
room = new Room(ROOM_ID, client, client.getUserId()!);
|
||||
});
|
||||
|
||||
it("counts room notification type", () => {
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(0);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(0);
|
||||
});
|
||||
|
||||
it("counts notifications type", () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 2);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1);
|
||||
});
|
||||
|
||||
describe("when there is a room predecessor", () => {
|
||||
const OLD_ROOM_ID = "!oldRoomId:example.org";
|
||||
const mkCreateEvent = (predecessorId?: string): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: "m.room.create",
|
||||
room: ROOM_ID,
|
||||
user: "@zoe:localhost",
|
||||
content: {
|
||||
...(predecessorId ? { predecessor: { room_id: predecessorId, event_id: "$someevent" } } : {}),
|
||||
creator: "@zoe:localhost",
|
||||
room_version: "5",
|
||||
},
|
||||
ts: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
const mkPredecessorEvent = (predecessorId: string): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomPredecessor,
|
||||
room: ROOM_ID,
|
||||
user: "@zoe:localhost",
|
||||
skey: "",
|
||||
content: {
|
||||
predecessor_room_id: predecessorId,
|
||||
},
|
||||
ts: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorInTheCreateEvent = (): void => {
|
||||
it("and there is a predecessor in the create event, it should count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent = (): void => {
|
||||
it("and there is a predecessor event, it should count predecessor highlight", () => {
|
||||
client.getVisibleRooms();
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 2);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
|
||||
|
||||
const oldRoom = new Room(OLD_ROOM_ID, client, client.getUserId()!);
|
||||
oldRoom.setUnreadNotificationCount(NotificationCountType.Total, 10);
|
||||
oldRoom.setUnreadNotificationCount(NotificationCountType.Highlight, 6);
|
||||
|
||||
client.getRoom.mockImplementation((roomId: string | undefined): Room | null => {
|
||||
if (roomId === room.roomId) return room;
|
||||
if (roomId === OLD_ROOM_ID) return oldRoom;
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
describe("and dynamic room predecessors are disabled", () => {
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorInTheCreateEvent();
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
|
||||
|
||||
it("and there is only a predecessor event, it should not count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and dynamic room predecessors are enabled", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_dynamic_room_predecessors",
|
||||
);
|
||||
});
|
||||
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorInTheCreateEvent();
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
|
||||
|
||||
it("and there is only a predecessor event, it should count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7);
|
||||
});
|
||||
|
||||
it("and there is an unknown room in the predecessor event, it should not count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent("!unknon:example.com")]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("counts thread notification type", () => {
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false, THREAD_ID)).toBe(0);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false, THREAD_ID)).toBe(0);
|
||||
});
|
||||
|
||||
it("counts thread notifications type", () => {
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 2);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 1);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false, THREAD_ID)).toBe(2);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false, THREAD_ID)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("determineUnreadState", () => {
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
room = new Room("!room-id:example.com", client, "@user:example.com", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
});
|
||||
|
||||
it("shows nothing by default", async () => {
|
||||
const { level, symbol, count } = determineUnreadState(room);
|
||||
|
||||
expect(symbol).toBe(null);
|
||||
expect(level).toBe(NotificationLevel.None);
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it("indicates if there are unsent messages", async () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: "m.message",
|
||||
user: "@user:example.org",
|
||||
content: {},
|
||||
});
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
room.addPendingEvent(event, "txn");
|
||||
|
||||
const { level, symbol, count } = determineUnreadState(room);
|
||||
|
||||
expect(symbol).toBe("!");
|
||||
expect(level).toBe(NotificationLevel.Unsent);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("indicates the user has been invited to a channel", async () => {
|
||||
room.updateMyMembership(KnownMembership.Invite);
|
||||
|
||||
const { level, symbol, count } = determineUnreadState(room);
|
||||
|
||||
expect(symbol).toBe("!");
|
||||
expect(level).toBe(NotificationLevel.Highlight);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("indicates the user knock has been denied", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
|
||||
return name === "feature_ask_to_join";
|
||||
});
|
||||
const roomMember = mkRoomMember(
|
||||
room.roomId,
|
||||
MatrixClientPeg.get()!.getSafeUserId(),
|
||||
KnownMembership.Leave,
|
||||
true,
|
||||
{
|
||||
membership: KnownMembership.Knock,
|
||||
},
|
||||
);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(roomMember);
|
||||
const { level, symbol, count } = determineUnreadState(room);
|
||||
|
||||
expect(symbol).toBe("!");
|
||||
expect(level).toBe(NotificationLevel.Highlight);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("shows nothing for muted channels", async () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 99);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 99);
|
||||
muteRoom(room);
|
||||
|
||||
const { level, count } = determineUnreadState(room);
|
||||
|
||||
expect(level).toBe(NotificationLevel.None);
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it("uses the correct number of unreads", async () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 999);
|
||||
|
||||
const { level, count } = determineUnreadState(room);
|
||||
|
||||
expect(level).toBe(NotificationLevel.Notification);
|
||||
expect(count).toBe(999);
|
||||
});
|
||||
|
||||
it("uses the correct number of highlights", async () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 888);
|
||||
|
||||
const { level, count } = determineUnreadState(room);
|
||||
|
||||
expect(level).toBe(NotificationLevel.Highlight);
|
||||
expect(count).toBe(888);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { setDMRoom } from "../../src/Rooms";
|
||||
import { mkEvent, stubClient } from "../test-utils";
|
||||
|
||||
describe("setDMRoom", () => {
|
||||
const userId1 = "@user1:example.com";
|
||||
const userId2 = "@user2:example.com";
|
||||
const userId3 = "@user3:example.com";
|
||||
const roomId1 = "!room1:example.com";
|
||||
const roomId2 = "!room2:example.com";
|
||||
const roomId3 = "!room3:example.com";
|
||||
const roomId4 = "!room4:example.com";
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = mocked(stubClient());
|
||||
client.getAccountData = jest.fn().mockImplementation((eventType: string): MatrixEvent | undefined => {
|
||||
if (eventType === EventType.Direct) {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
[userId1]: [roomId1, roomId2],
|
||||
[userId2]: [roomId3],
|
||||
},
|
||||
type: EventType.Direct,
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("when logged in as a guest and marking a room as DM", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.isGuest).mockReturnValue(true);
|
||||
setDMRoom(client, roomId1, userId1);
|
||||
});
|
||||
|
||||
it("should not update the account data", () => {
|
||||
expect(client.setAccountData).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding a new room to an existing DM relation", () => {
|
||||
beforeEach(() => {
|
||||
setDMRoom(client, roomId4, userId1);
|
||||
});
|
||||
|
||||
it("should update the account data accordingly", () => {
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(EventType.Direct, {
|
||||
[userId1]: [roomId1, roomId2, roomId4],
|
||||
[userId2]: [roomId3],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding a new DM room", () => {
|
||||
beforeEach(() => {
|
||||
setDMRoom(client, roomId4, userId3);
|
||||
});
|
||||
|
||||
it("should update the account data accordingly", () => {
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(EventType.Direct, {
|
||||
[userId1]: [roomId1, roomId2],
|
||||
[userId2]: [roomId3],
|
||||
[userId3]: [roomId4],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when removing an existing DM", () => {
|
||||
beforeEach(() => {
|
||||
setDMRoom(client, roomId1, null);
|
||||
});
|
||||
|
||||
it("should update the account data accordingly", () => {
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(EventType.Direct, {
|
||||
[userId1]: [roomId2],
|
||||
[userId2]: [roomId3],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the direct event is undefined", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.getAccountData).mockReturnValue(undefined);
|
||||
setDMRoom(client, roomId1, userId1);
|
||||
});
|
||||
|
||||
it("should update the account data accordingly", () => {
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(EventType.Direct, {
|
||||
[userId1]: [roomId1],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the current content is undefined", () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
mocked(client.getAccountData).mockReturnValue({
|
||||
getContent: jest.fn(),
|
||||
});
|
||||
setDMRoom(client, roomId1, userId1);
|
||||
});
|
||||
|
||||
it("should update the account data accordingly", () => {
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(EventType.Direct, {
|
||||
[userId1]: [roomId1],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import fetchMock from "@fetch-mock/jest";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ScalarAuthClient from "../../src/ScalarAuthClient";
|
||||
import { stubClient } from "../test-utils";
|
||||
import SdkConfig from "../../src/SdkConfig";
|
||||
import { WidgetType } from "../../src/widgets/WidgetType";
|
||||
|
||||
describe("ScalarAuthClient", function () {
|
||||
const apiUrl = "https://test.com/api";
|
||||
const uiUrl = "https:/test.com/app";
|
||||
const tokenObject = {
|
||||
access_token: "token",
|
||||
token_type: "Bearer",
|
||||
matrix_server_name: "localhost",
|
||||
expires_in: 999,
|
||||
};
|
||||
|
||||
let client: MatrixClient;
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
it("should request a new token if the old one fails", async function () {
|
||||
const sac = new ScalarAuthClient(apiUrl + 0, uiUrl);
|
||||
|
||||
fetchMock.get("https://test.com/api0/account?scalar_token=brokentoken&v=1.1", {
|
||||
body: { message: "Invalid token" },
|
||||
});
|
||||
|
||||
fetchMock.get("https://test.com/api0/account?scalar_token=wokentoken&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject);
|
||||
|
||||
sac.exchangeForScalarToken = jest.fn((arg) => {
|
||||
return Promise.resolve(arg === tokenObject ? "wokentoken" : "othertoken");
|
||||
});
|
||||
|
||||
await sac.connect();
|
||||
|
||||
expect(sac.exchangeForScalarToken).toHaveBeenCalledWith(tokenObject);
|
||||
expect(sac.hasCredentials).toBeTruthy();
|
||||
// @ts-ignore private property
|
||||
expect(sac.scalarToken).toEqual("wokentoken");
|
||||
});
|
||||
|
||||
describe("exchangeForScalarToken", () => {
|
||||
it("should return `scalar_token` from API /register", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 1, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api1/register?v=1.1", {
|
||||
body: { scalar_token: "stoken" },
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).resolves.toBe("stoken");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 2, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api2/register?v=1.1", {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Scalar request failed: 500");
|
||||
});
|
||||
|
||||
it("should throw if scalar_token is missing in response", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 3, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api3/register?v=1.1", {
|
||||
body: {},
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Missing scalar_token in response");
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerForToken", () => {
|
||||
it("should call `termsInteractionCallback` upon M_TERMS_NOT_SIGNED error", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 4, uiUrl);
|
||||
const termsInteractionCallback = jest.fn();
|
||||
sac.setTermsInteractionCallback(termsInteractionCallback);
|
||||
fetchMock.get("https://test.com/api4/account?scalar_token=testtoken1&v=1.1", {
|
||||
body: { errcode: "M_TERMS_NOT_SIGNED" },
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken1"));
|
||||
mocked(client.getTerms).mockResolvedValue({ policies: {} });
|
||||
|
||||
await expect(sac.registerForToken()).resolves.toBe("testtoken1");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 5, uiUrl);
|
||||
fetchMock.get("https://test.com/api5/account?scalar_token=testtoken2&v=1.1", {
|
||||
body: { errcode: "SERVER_IS_SAD" },
|
||||
status: 500,
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken2"));
|
||||
|
||||
await expect(sac.registerForToken()).rejects.toBeTruthy();
|
||||
});
|
||||
|
||||
it("should throw if user_id is missing from response", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 6, uiUrl);
|
||||
fetchMock.get("https://test.com/api6/account?scalar_token=testtoken3&v=1.1", {
|
||||
body: {},
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken3"));
|
||||
|
||||
await expect(sac.registerForToken()).rejects.toThrow("Missing user_id in response");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getScalarPageTitle", () => {
|
||||
let sac: ScalarAuthClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
SdkConfig.put({
|
||||
integrations_rest_url: apiUrl + 7,
|
||||
integrations_ui_url: uiUrl,
|
||||
});
|
||||
|
||||
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api7", "wokentoken1");
|
||||
fetchMock.get("https://test.com/api7/account?scalar_token=wokentoken1&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
sac = new ScalarAuthClient(apiUrl + 7, uiUrl);
|
||||
await sac.connect();
|
||||
});
|
||||
|
||||
it("should return `cached_title` from API /widgets/title_lookup", async () => {
|
||||
const url = "google.com";
|
||||
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
|
||||
body: {
|
||||
page_title_cache_item: {
|
||||
cached_title: "Google",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(sac.getScalarPageTitle(url)).resolves.toBe("Google");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const url = "yahoo.com";
|
||||
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(sac.getScalarPageTitle(url)).rejects.toThrow("Scalar request failed: 500");
|
||||
});
|
||||
});
|
||||
|
||||
describe("disableWidgetAssets", () => {
|
||||
let sac: ScalarAuthClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
SdkConfig.put({
|
||||
integrations_rest_url: apiUrl + 8,
|
||||
integrations_ui_url: uiUrl,
|
||||
});
|
||||
|
||||
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api8", "wokentoken1");
|
||||
fetchMock.get("https://test.com/api8/account?scalar_token=wokentoken1&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
sac = new ScalarAuthClient(apiUrl + 8, uiUrl);
|
||||
await sac.connect();
|
||||
});
|
||||
|
||||
it("should send state=disable to API /widgets/set_assets_state", async () => {
|
||||
fetchMock.get(
|
||||
"https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
|
||||
"&widget_type=m.custom&widget_id=id1&state=disable",
|
||||
{
|
||||
body: "OK",
|
||||
},
|
||||
);
|
||||
|
||||
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id1")).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
fetchMock.get(
|
||||
"https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
|
||||
"&widget_type=m.custom&widget_id=id2&state=disable",
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id2")).rejects.toThrow(
|
||||
"Scalar request failed: 500",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import SdkConfig, { DEFAULTS } from "../../src/SdkConfig";
|
||||
|
||||
describe("SdkConfig", () => {
|
||||
describe("with default values", () => {
|
||||
it("should return the default config", () => {
|
||||
expect(SdkConfig.get()).toEqual(DEFAULTS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with custom values", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
feedback: {
|
||||
existing_issues_url: "https://existing",
|
||||
} as any,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return the custom config", () => {
|
||||
const customConfig = JSON.parse(JSON.stringify(DEFAULTS));
|
||||
customConfig.feedback.existing_issues_url = "https://existing";
|
||||
expect(SdkConfig.get()).toEqual(customConfig);
|
||||
});
|
||||
|
||||
it("should allow overriding individual fields of sub-objects", () => {
|
||||
const feedback = SdkConfig.getObject("feedback");
|
||||
expect(feedback.get("existing_issues_url")).toMatchInlineSnapshot(`"https://existing"`);
|
||||
expect(feedback.get("new_issue_url")).toMatchInlineSnapshot(
|
||||
`"https://github.com/vector-im/element-web/issues/new/choose"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { getBrowserSupport, checkBrowserSupport, LOCAL_STORAGE_KEY } from "../../src/SupportedBrowser";
|
||||
import ToastStore from "../../src/stores/ToastStore";
|
||||
import GenericToast from "../../src/components/views/toasts/GenericToast";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/logger");
|
||||
|
||||
describe("SupportedBrowser", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
localStorage.clear();
|
||||
getBrowserSupport.clear();
|
||||
});
|
||||
|
||||
const testUserAgentFactory =
|
||||
(expectedWarning?: string) =>
|
||||
async (userAgent: string): Promise<void> => {
|
||||
const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
|
||||
const warnLogSpy = jest.spyOn(logger, "warn");
|
||||
Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true });
|
||||
checkBrowserSupport();
|
||||
if (expectedWarning) {
|
||||
expect(warnLogSpy).toHaveBeenCalledWith(expectedWarning, expect.any(String));
|
||||
expect(toastSpy).toHaveBeenCalled();
|
||||
} else {
|
||||
expect(warnLogSpy).not.toHaveBeenCalled();
|
||||
expect(toastSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
};
|
||||
|
||||
it.each([
|
||||
// Safari on iOS
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
|
||||
// Firefox on iOS
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/128.0 Mobile/15E148 Safari/605.1.15",
|
||||
// Opera on Samsung
|
||||
"Mozilla/5.0 (Linux; Android 10; SM-G970F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.64 Mobile Safari/537.36 OPR/76.2.4027.73374",
|
||||
])("should warn for mobile browsers", testUserAgentFactory("Browser unsupported, unsupported device type"));
|
||||
|
||||
it.each([
|
||||
// Chrome on Chrome OS
|
||||
"Mozilla/5.0 (X11; CrOS x86_64 15633.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.212 Safari/537.36",
|
||||
// Opera on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 OPR/113.0.0.0",
|
||||
// Vivaldi on Linux
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Vivaldi/6.8.3381.48",
|
||||
// IE11 on Windows 10
|
||||
"Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko",
|
||||
// Firefox 115 on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_4_5; rv:115.0) Gecko/20000101 Firefox/115.0",
|
||||
])(
|
||||
"should warn for unsupported desktop browsers",
|
||||
testUserAgentFactory("Browser unsupported, unsupported user agent"),
|
||||
);
|
||||
|
||||
// Grabbed from https://www.whatismybrowser.com/guides/the-latest-user-agent/
|
||||
it.each([
|
||||
// Safari 26.0 on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",
|
||||
// Latest Firefox on macOS Sonoma
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:150.0) Gecko/20100101 Firefox/150.0",
|
||||
// Latest Edge on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.3856.84",
|
||||
// Latest Edge on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.3856.84",
|
||||
// Latest Firefox on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0",
|
||||
// Latest Firefox on Linux
|
||||
"Mozilla/5.0 (X11; Linux i686; rv:150.0) Gecko/20100101 Firefox/150.0",
|
||||
// Latest Chrome on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
|
||||
])("should not warn for supported browsers", testUserAgentFactory());
|
||||
|
||||
it.each([
|
||||
// Element Nightly on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2024072501 Chrome/126.0.6478.127 Electron/31.2.1 Safari/537.36",
|
||||
])("should not warn for Element Desktop", testUserAgentFactory());
|
||||
|
||||
it.each(["AppleTV11,1/11.1"])(
|
||||
"should handle unknown user agent sanely",
|
||||
testUserAgentFactory("Browser unsupported, unknown client"),
|
||||
);
|
||||
|
||||
it("should not warn for unsupported browser if user accepted already", async () => {
|
||||
const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
|
||||
const warnLogSpy = jest.spyOn(logger, "warn");
|
||||
const userAgent =
|
||||
"Mozilla/5.0 (X11; CrOS x86_64 15633.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.212 Safari/537.36";
|
||||
Object.defineProperty(window, "navigator", { value: { userAgent: userAgent }, writable: true });
|
||||
|
||||
checkBrowserSupport();
|
||||
expect(warnLogSpy).toHaveBeenCalledWith("Browser unsupported, unsupported user agent", expect.any(String));
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
component: GenericToast,
|
||||
title: "Element does not support this browser",
|
||||
}),
|
||||
);
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, String(true));
|
||||
toastSpy.mockClear();
|
||||
warnLogSpy.mockClear();
|
||||
|
||||
getBrowserSupport.clear();
|
||||
checkBrowserSupport();
|
||||
expect(warnLogSpy).toHaveBeenCalledWith("Browser unsupported, but user has previously accepted");
|
||||
expect(toastSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as tzh from "../../src/TimezoneHandler";
|
||||
|
||||
describe("TimezoneHandler", () => {
|
||||
it("should support setting a user timezone", async () => {
|
||||
const tz = "Europe/Paris";
|
||||
await tzh.setUserTimezone(tz);
|
||||
expect(tzh.getUserTimezone()).toEqual(tz);
|
||||
});
|
||||
it("Return undefined with an empty TZ", async () => {
|
||||
await tzh.setUserTimezone("");
|
||||
expect(tzh.getUserTimezone()).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,431 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Favicon should clear a badge if called with a zero value 1`] = `
|
||||
[
|
||||
{
|
||||
"props": {
|
||||
"height": 144,
|
||||
"width": 144,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "clearRect",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"dHeight": 144,
|
||||
"dWidth": 144,
|
||||
"dx": 0,
|
||||
"dy": 0,
|
||||
"img": <img
|
||||
height="144"
|
||||
width="144"
|
||||
/>,
|
||||
"sHeight": 144,
|
||||
"sWidth": 144,
|
||||
"sx": 0,
|
||||
"sy": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "drawImage",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"fillRule": "nonzero",
|
||||
"path": [
|
||||
{
|
||||
"props": {},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "beginPath",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 72.72,
|
||||
"y": 57.6,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "moveTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 100.79999999999998,
|
||||
"y": 57.6,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 143.99999999999997,
|
||||
"y": 100.80000000000001,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 44.64,
|
||||
"y": 144,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 1.4400000000000048,
|
||||
"y": 100.8,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
],
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "fill",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"path": [
|
||||
{
|
||||
"props": {},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "beginPath",
|
||||
},
|
||||
],
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "stroke",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"maxWidth": null,
|
||||
"text": "123",
|
||||
"x": 72,
|
||||
"y": 131,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "fillText",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"height": 144,
|
||||
"width": 144,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "clearRect",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"dHeight": 144,
|
||||
"dWidth": 144,
|
||||
"dx": 0,
|
||||
"dy": 0,
|
||||
"img": <img
|
||||
height="144"
|
||||
width="144"
|
||||
/>,
|
||||
"sHeight": 144,
|
||||
"sWidth": 144,
|
||||
"sx": 0,
|
||||
"sy": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "drawImage",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
|
||||
[
|
||||
{
|
||||
"props": {
|
||||
"height": 144,
|
||||
"width": 144,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "clearRect",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"dHeight": 144,
|
||||
"dWidth": 144,
|
||||
"dx": 0,
|
||||
"dy": 0,
|
||||
"img": <img
|
||||
height="144"
|
||||
width="144"
|
||||
/>,
|
||||
"sHeight": 144,
|
||||
"sWidth": 144,
|
||||
"sx": 0,
|
||||
"sy": 0,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "drawImage",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"fillRule": "nonzero",
|
||||
"path": [
|
||||
{
|
||||
"props": {},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "beginPath",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 72.72,
|
||||
"y": 57.6,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "moveTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 100.79999999999998,
|
||||
"y": 57.6,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 143.99999999999997,
|
||||
"y": 100.80000000000001,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 44.64,
|
||||
"y": 144,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"x": 1.4400000000000048,
|
||||
"y": 100.8,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "lineTo",
|
||||
},
|
||||
],
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "fill",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"path": [
|
||||
{
|
||||
"props": {},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "beginPath",
|
||||
},
|
||||
],
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "stroke",
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"maxWidth": null,
|
||||
"text": "123",
|
||||
"x": 72,
|
||||
"y": 131,
|
||||
},
|
||||
"transform": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "fillText",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import "jest-canvas-mock";
|
||||
|
||||
import Favicon from "../../src/favicon";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe("Favicon", () => {
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.getElementsByTagName("head")[0]?.remove();
|
||||
const head = document.createElement("head");
|
||||
window.document.documentElement.prepend(head);
|
||||
});
|
||||
|
||||
it("should create a link element if one doesn't yet exist", () => {
|
||||
const favicon = new Favicon();
|
||||
expect(favicon).toBeTruthy();
|
||||
const link = window.document.querySelector("link")!;
|
||||
expect(link.rel).toContain("icon");
|
||||
});
|
||||
|
||||
it("should draw a badge if called with a non-zero value", () => {
|
||||
const favicon = new Favicon();
|
||||
favicon.badge(123);
|
||||
jest.runAllTimers();
|
||||
expect(favicon["context"].__getDrawCalls()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should clear a badge if called with a zero value", () => {
|
||||
const favicon = new Favicon();
|
||||
favicon.badge(123);
|
||||
jest.runAllTimers();
|
||||
favicon.badge(0);
|
||||
expect(favicon["context"].__getDrawCalls()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should recreate link element for firefox and opera", () => {
|
||||
window["InstallTrigger"] = {};
|
||||
window["opera"] = {};
|
||||
const favicon = new Favicon();
|
||||
const originalLink = window.document.querySelector("link");
|
||||
favicon.badge(123);
|
||||
jest.runAllTimers();
|
||||
const newLink = window.document.querySelector("link");
|
||||
expect(originalLink).not.toStrictEqual(newLink);
|
||||
});
|
||||
|
||||
it("should default to 144px", () => {
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
const link = document.createElement("link");
|
||||
link.rel = "icon";
|
||||
link.href = "favicon.png";
|
||||
head.appendChild(link);
|
||||
|
||||
const spy = jest.spyOn(document, "createElement");
|
||||
const favicon = new Favicon();
|
||||
jest.runAllTimers();
|
||||
|
||||
const img = spy.mock.results[0].value;
|
||||
img.onload();
|
||||
|
||||
expect(favicon["canvas"].height).toBe(144);
|
||||
});
|
||||
|
||||
it("should use the size of the last favicon", () => {
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
const link = document.createElement("link");
|
||||
link.rel = "icon";
|
||||
link.sizes = "512x512";
|
||||
link.href = "favicon.png";
|
||||
head.appendChild(link);
|
||||
|
||||
const spy = jest.spyOn(document, "createElement");
|
||||
const favicon = new Favicon();
|
||||
jest.runAllTimers();
|
||||
|
||||
const img = spy.mock.results[0].value;
|
||||
img.height = 512;
|
||||
img.onload();
|
||||
|
||||
expect(favicon["canvas"].height).toBe(512);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user