Merge branch 'refs/heads/develop' into florianduros/tooltip-update
This commit is contained in:
commit
79cbfafce3
23 changed files with 628 additions and 77 deletions
|
@ -17,6 +17,10 @@ limitations under the License.
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import fetchMockJest from "fetch-mock-jest";
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
ProvideCryptoSetupExtensions,
|
||||
SecretStorageKeyDescription,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
|
||||
import { advanceDateAndTime, stubClient } from "./test-utils";
|
||||
import { IMatrixClientPeg, MatrixClientPeg as peg } from "../src/MatrixClientPeg";
|
||||
|
@ -25,6 +29,7 @@ import Modal from "../src/Modal";
|
|||
import PlatformPeg from "../src/PlatformPeg";
|
||||
import { SettingLevel } from "../src/settings/SettingLevel";
|
||||
import { Features } from "../src/settings/Settings";
|
||||
import { ModuleRunner } from "../src/modules/ModuleRunner";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
|
@ -77,6 +82,78 @@ describe("MatrixClientPeg", () => {
|
|||
expect(peg.userRegisteredWithinLastHours(24)).toBe(false);
|
||||
});
|
||||
|
||||
describe(".start extensions", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
beforeEach(() => {
|
||||
// instantiate a MatrixClientPegClass instance, with a new MatrixClient
|
||||
testPeg = new PegClass();
|
||||
fetchMockJest.get("http://example.com/_matrix/client/versions", {});
|
||||
});
|
||||
|
||||
describe("cryptoSetup extension", () => {
|
||||
it("should call default cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(null),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call overridden cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockDehydrationKeyCallback = () => Uint8Array.from([0x11, 0x22, 0x33]);
|
||||
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(mockDehydrationKeyCallback),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const client = testPeg.get();
|
||||
const dehydrationKey = await client?.cryptoCallbacks.getDehydrationKey!(
|
||||
{} as SecretStorageKeyDescription,
|
||||
(key: Uint8Array) => true,
|
||||
);
|
||||
expect(dehydrationKey).toEqual(Uint8Array.from([0x11, 0x22, 0x33]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".start", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
|
|
|
@ -35,5 +35,61 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_testid_1"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_testid_1"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_testid_2"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_testid_2"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -641,6 +641,60 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
|
|||
role="list"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1472,6 +1526,60 @@ exports[`<Notifications /> matches the snapshot 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Show all activity in the room list (dots or number of unread messages)"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_QRlYy75nfv5b"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Only show notifications in the thread activity centre"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_OEPN1su1JYVt"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1523,11 +1631,11 @@ exports[`<Notifications /> matches the snapshot 1`] = `
|
|||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
id="checkbox_QRlYy75nfv"
|
||||
id="checkbox_OyR5kbu3pE"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="checkbox_QRlYy75nfv"
|
||||
for="checkbox_OyR5kbu3pE"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
|
|
|
@ -30,6 +30,7 @@ import { stubClient } from "../../../test-utils";
|
|||
import { populateThread } from "../../../test-utils/threads";
|
||||
import { NotificationLevel } from "../../../../src/stores/notifications/NotificationLevel";
|
||||
import { useUnreadThreadRooms } from "../../../../src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("useUnreadThreadRooms", () => {
|
||||
let client: MatrixClient;
|
||||
|
@ -43,6 +44,10 @@ describe("useUnreadThreadRooms", () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("has no notifications with no rooms", async () => {
|
||||
const { result } = renderHook(() => useUnreadThreadRooms(false));
|
||||
const { greatestNotificationLevel, rooms } = result.current;
|
||||
|
@ -51,7 +56,7 @@ describe("useUnreadThreadRooms", () => {
|
|||
expect(rooms.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("an activity notification is ignored", async () => {
|
||||
it("an activity notification is ignored by default", async () => {
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
client: client,
|
||||
|
@ -73,6 +78,30 @@ describe("useUnreadThreadRooms", () => {
|
|||
expect(rooms.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("an activity notification is displayed with the setting enabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
client: client,
|
||||
authorId: "@foo:bar",
|
||||
participantUserIds: ["@fee:bar"],
|
||||
});
|
||||
room.setThreadUnreadNotificationCount(notifThreadInfo.thread.id, NotificationCountType.Total, 0);
|
||||
|
||||
client.getVisibleRooms = jest.fn().mockReturnValue([room]);
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MatrixClientContext.Provider value={client}>{children}</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUnreadThreadRooms(true), { wrapper });
|
||||
const { greatestNotificationLevel, rooms } = result.current;
|
||||
|
||||
expect(greatestNotificationLevel).toBe(NotificationLevel.Activity);
|
||||
expect(rooms.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("a notification and a highlight summarise to a highlight", async () => {
|
||||
const notifThreadInfo = await populateThread({
|
||||
room: room,
|
||||
|
|
|
@ -16,6 +16,9 @@ limitations under the License.
|
|||
|
||||
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
|
||||
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
|
||||
import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
|
||||
import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
|
||||
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
|
@ -29,6 +32,11 @@ export class MockModule extends RuntimeModule {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModule(): MockModule {
|
||||
let module: MockModule | undefined;
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
|
@ -43,3 +51,88 @@ export function registerMockModule(): MockModule {
|
|||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
class MockModuleWithCryptoSetupExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithCryptoSetupExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
cryptoSetup: {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn().mockReturnValue(Uint8Array.from([0x11, 0x22, 0x99])),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn(),
|
||||
} as ProvideCryptoSetupExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
class MockModuleWithExperimentalExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithExperimentalExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
experimental: {
|
||||
experimentalMethod: jest.fn().mockReturnValue(Uint8Array.from([0x22, 0x44, 0x88])),
|
||||
} as ProvideExperimentalExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the cryptoSetup extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithCryptoSetupExtension(): MockModuleWithCryptoSetupExtension {
|
||||
let module: MockModuleWithCryptoSetupExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithCryptoSetupExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the experimental extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithExperimentalExtension(): MockModuleWithExperimentalExtension {
|
||||
let module: MockModuleWithExperimentalExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithExperimentalExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@ limitations under the License.
|
|||
|
||||
import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
|
||||
import { MockModule, registerMockModule } from "./MockModule";
|
||||
import {
|
||||
MockModule,
|
||||
registerMockModule,
|
||||
registerMockModuleWithCryptoSetupExtension,
|
||||
registerMockModuleWithExperimentalExtension,
|
||||
} from "./MockModule";
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
describe("ModuleRunner", () => {
|
||||
|
@ -49,4 +54,48 @@ describe("ModuleRunner", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extensions", () => {
|
||||
it("should return default values when no crypto-setup extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return default values when no experimental extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions?.experimental.experimentalMethod();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return value from crypto-setup-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toEqual(Uint8Array.from([0x11, 0x22, 0x99]));
|
||||
});
|
||||
|
||||
it("should return value from experimental-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const result = ModuleRunner.instance.extensions.experimental.experimentalMethod();
|
||||
expect(result).toEqual(Uint8Array.from([0x22, 0x44, 0x88]));
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide cryptoSetup extension", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const t = () => registerMockModuleWithCryptoSetupExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding cryptoSetup extension implementation from module MockModuleWithCryptoSetupExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide experimental extension", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const t = () => registerMockModuleWithExperimentalExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding experimental extension implementation from module MockModuleWithExperimentalExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue