Add test coverage (#9928)

This commit is contained in:
Michael Weimann 2023-01-18 15:49:34 +01:00 committed by GitHub
parent baa120fff3
commit 6d354e3e10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 301 additions and 29 deletions

View file

@ -414,7 +414,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
widgets.forEach((w, i) => {
localLayout[w.id] = {
container: container,
width: widths[i],
width: widths?.[i],
index: i,
height: height,
};
@ -437,7 +437,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
widgets.forEach((w, i) => {
localLayout[w.id] = {
container: container,
width: widths[i],
width: widths?.[i],
index: i,
height: height,
};

View file

@ -15,21 +15,49 @@ limitations under the License.
*/
import RoomDeviceSettingsHandler from "../../../src/settings/handlers/RoomDeviceSettingsHandler";
import { WatchManager } from "../../../src/settings/WatchManager";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { CallbackFn, WatchManager } from "../../../src/settings/WatchManager";
describe("RoomDeviceSettingsHandler", () => {
it("should correctly read cached values", () => {
const watchers = new WatchManager();
const handler = new RoomDeviceSettingsHandler(watchers);
const roomId = "!room:example.com";
const value = "test value";
const testSettings = [
"RightPanel.phases",
// special case in RoomDeviceSettingsHandler
"blacklistUnverifiedDevices",
];
let watchers: WatchManager;
let handler: RoomDeviceSettingsHandler;
let settingListener: CallbackFn;
const settingName = "RightPanel.phases";
const roomId = "!room:server";
const value = {
isOpen: true,
history: [{}],
};
beforeEach(() => {
watchers = new WatchManager();
handler = new RoomDeviceSettingsHandler(watchers);
settingListener = jest.fn();
});
handler.setValue(settingName, roomId, value);
expect(handler.getValue(settingName, roomId)).toEqual(value);
afterEach(() => {
watchers.unwatchSetting(settingListener);
});
it.each(testSettings)("should write/read/clear the value for »%s«", (setting: string): void => {
// initial value should be null
watchers.watchSetting(setting, roomId, settingListener);
expect(handler.getValue(setting, roomId)).toBeNull();
// set and read value
handler.setValue(setting, roomId, value);
expect(settingListener).toHaveBeenCalledWith(roomId, SettingLevel.ROOM_DEVICE, value);
expect(handler.getValue(setting, roomId)).toEqual(value);
// clear value
handler.setValue(setting, roomId, null);
expect(settingListener).toHaveBeenCalledWith(roomId, SettingLevel.ROOM_DEVICE, null);
expect(handler.getValue(setting, roomId)).toBeNull();
});
it("canSetValue should return true", () => {
expect(handler.canSetValue("test setting", roomId)).toBe(true);
});
});

View file

@ -0,0 +1,100 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from "jest-mock";
import { ClientEvent, EventType, MatrixClient, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
import { SyncState } from "matrix-js-sdk/src/sync";
import SettingsStore from "../../src/settings/SettingsStore";
import AutoRageshakeStore from "../../src/stores/AutoRageshakeStore";
import { mkEvent, stubClient } from "../test-utils";
jest.mock("../../src/rageshake/submit-rageshake");
describe("AutoRageshakeStore", () => {
const roomId = "!room:example.com";
let client: MatrixClient;
let utdEvent: MatrixEvent;
let autoRageshakeStore: AutoRageshakeStore;
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
client = stubClient();
// @ts-ignore bypass private ctor for tests
autoRageshakeStore = new AutoRageshakeStore();
autoRageshakeStore.start();
utdEvent = mkEvent({
event: true,
content: {},
room: roomId,
user: client.getSafeUserId(),
type: EventType.RoomMessage,
});
jest.spyOn(utdEvent, "isDecryptionFailure").mockReturnValue(true);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("when the initial sync completed", () => {
beforeEach(() => {
client.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Stopped, { nextSyncToken: "abc123" });
});
describe("and an undecryptable event occurs", () => {
beforeEach(() => {
client.emit(MatrixEventEvent.Decrypted, utdEvent);
// simulate event grace period
jest.advanceTimersByTime(5500);
});
it("should send a rageshake", () => {
expect(mocked(client).sendToDevice.mock.calls).toMatchInlineSnapshot(`
[
[
"im.vector.auto_rs_request",
{
"@userId:matrix.org": {
"undefined": {
"device_id": undefined,
"event_id": "${utdEvent.getId()}",
"recipient_rageshake": undefined,
"room_id": "!room:example.com",
"sender_key": undefined,
"session_id": undefined,
"user_id": "@userId:matrix.org",
},
},
},
],
]
`);
});
});
});
});

View file

@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/matrix";
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import WidgetStore, { IApp } from "../../src/stores/WidgetStore";
import { Container, WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
import { stubClient } from "../test-utils";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
// setup test env values
const roomId = "!room:server";
@ -34,31 +36,54 @@ const mockRoom = <Room>{
},
};
const mockApps = [
<IApp>{ roomId: roomId, id: "1" },
<IApp>{ roomId: roomId, id: "2" },
<IApp>{ roomId: roomId, id: "3" },
<IApp>{ roomId: roomId, id: "4" },
];
// fake the WidgetStore.instance to just return an object with `getApps`
jest.spyOn(WidgetStore, "instance", "get").mockReturnValue(<WidgetStore>{ getApps: (_room) => mockApps });
describe("WidgetLayoutStore", () => {
// we need to init a client so it does not error, when asking for DeviceStorage handlers (SettingsStore.setValue("Widgets.layout"))
stubClient();
let client: MatrixClient;
let store: WidgetLayoutStore;
let roomUpdateListener: (event: string) => void;
let mockApps: IApp[];
const store = WidgetLayoutStore.instance;
beforeEach(() => {
mockApps = [
<IApp>{ roomId: roomId, id: "1" },
<IApp>{ roomId: roomId, id: "2" },
<IApp>{ roomId: roomId, id: "3" },
<IApp>{ roomId: roomId, id: "4" },
];
it("all widgets should be in the right container by default", async () => {
// fake the WidgetStore.instance to just return an object with `getApps`
jest.spyOn(WidgetStore, "instance", "get").mockReturnValue({
on: jest.fn(),
off: jest.fn(),
getApps: () => mockApps,
} as unknown as WidgetStore);
});
beforeAll(() => {
// we need to init a client so it does not error, when asking for DeviceStorage handlers (SettingsStore.setValue("Widgets.layout"))
client = stubClient();
roomUpdateListener = jest.fn();
// @ts-ignore bypass private ctor for tests
store = new WidgetLayoutStore();
store.addListener(`update_${roomId}`, roomUpdateListener);
});
afterAll(() => {
store.removeListener(`update_${roomId}`, roomUpdateListener);
});
it("all widgets should be in the right container by default", () => {
store.recalculateRoom(mockRoom);
expect(store.getContainerWidgets(mockRoom, Container.Right).length).toStrictEqual(mockApps.length);
});
it("add widget to top container", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toStrictEqual([mockApps[0]]);
expect(store.getContainerHeight(mockRoom, Container.Top)).toBeNull();
});
it("add three widgets to top container", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
@ -68,6 +93,7 @@ describe("WidgetLayoutStore", () => {
new Set([mockApps[0], mockApps[1], mockApps[2]]),
);
});
it("cannot add more than three widgets to top container", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
@ -75,6 +101,7 @@ describe("WidgetLayoutStore", () => {
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
expect(store.canAddToContainer(mockRoom, Container.Top)).toEqual(false);
});
it("remove pins when maximising (other widget)", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
@ -87,6 +114,7 @@ describe("WidgetLayoutStore", () => {
);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([mockApps[3]]);
});
it("remove pins when maximising (one of the pinned widgets)", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
@ -99,6 +127,7 @@ describe("WidgetLayoutStore", () => {
new Set([mockApps[1], mockApps[2], mockApps[3]]),
);
});
it("remove maximised when pinning (other widget)", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Center);
@ -109,6 +138,7 @@ describe("WidgetLayoutStore", () => {
new Set([mockApps[2], mockApps[3], mockApps[0]]),
);
});
it("remove maximised when pinning (same widget)", async () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Center);
@ -119,4 +149,118 @@ describe("WidgetLayoutStore", () => {
new Set([mockApps[2], mockApps[3], mockApps[1]]),
);
});
it("should recalculate all rooms when the client is ready", async () => {
mocked(client.getVisibleRooms).mockReturnValue([mockRoom]);
await store.start();
expect(roomUpdateListener).toHaveBeenCalled();
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([mockApps[0]]);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Right)).toEqual([mockApps[1], mockApps[2], mockApps[3]]);
});
it("should clear the layout and emit an update if there are no longer apps in the room", () => {
store.recalculateRoom(mockRoom);
mocked(roomUpdateListener).mockClear();
jest.spyOn(WidgetStore, "instance", "get").mockReturnValue(<WidgetStore>(
({ getApps: (): IApp[] => [] } as unknown as WidgetStore)
));
store.recalculateRoom(mockRoom);
expect(roomUpdateListener).toHaveBeenCalled();
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Right)).toEqual([]);
});
it("should clear the layout if the client is not viable", () => {
store.recalculateRoom(mockRoom);
defaultDispatcher.dispatch(
{
action: "on_client_not_viable",
},
true,
);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Right)).toEqual([]);
});
it("should return the expected resizer distributions", () => {
// this only works for top widgets
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
expect(store.getResizerDistributions(mockRoom, Container.Top)).toEqual(["50.0%"]);
});
it("should set and return container height", () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
store.setContainerHeight(mockRoom, Container.Top, 23);
expect(store.getContainerHeight(mockRoom, Container.Top)).toBe(23);
});
it("should move a widget within a container", () => {
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toStrictEqual([
mockApps[0],
mockApps[1],
mockApps[2],
]);
store.moveWithinContainer(mockRoom, Container.Top, mockApps[0], 1);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toStrictEqual([
mockApps[1],
mockApps[0],
mockApps[2],
]);
});
it("should copy the layout to the room", async () => {
await store.start();
store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
store.copyLayoutToRoom(mockRoom);
expect(mocked(client.sendStateEvent).mock.calls).toMatchInlineSnapshot(`
[
[
"!room:server",
"io.element.widgets.layout",
{
"widgets": {
"1": {
"container": "top",
"height": 23,
"index": 2,
"width": 64,
},
"2": {
"container": "top",
"height": 23,
"index": 0,
"width": 10,
},
"3": {
"container": "top",
"height": 23,
"index": 1,
"width": 26,
},
"4": {
"container": "right",
},
},
},
"",
],
]
`);
});
});