mirror of https://github.com/vector-im/riot-web
				
				
				
			
		
			
				
	
	
		
			309 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2021 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 { 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";
 | 
						|
import SettingsStore from "../../src/settings/SettingsStore";
 | 
						|
 | 
						|
// setup test env values
 | 
						|
const roomId = "!room:server";
 | 
						|
const mockRoom = <Room>{
 | 
						|
    roomId: roomId,
 | 
						|
    currentState: {
 | 
						|
        getStateEvents: (_l, _x) => {
 | 
						|
            return {
 | 
						|
                getId: () => "$layoutEventId",
 | 
						|
                getContent: () => null,
 | 
						|
            };
 | 
						|
        },
 | 
						|
    },
 | 
						|
};
 | 
						|
 | 
						|
describe("WidgetLayoutStore", () => {
 | 
						|
    let client: MatrixClient;
 | 
						|
    let store: WidgetLayoutStore;
 | 
						|
    let roomUpdateListener: (event: string) => void;
 | 
						|
    let mockApps: IApp[];
 | 
						|
 | 
						|
    beforeEach(() => {
 | 
						|
        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({
 | 
						|
            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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[1], Container.Top);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[2], Container.Top);
 | 
						|
        expect(new Set(store.getContainerWidgets(mockRoom, Container.Top))).toEqual(
 | 
						|
            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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[1], Container.Top);
 | 
						|
        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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[1], Container.Top);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[2], Container.Top);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[3], Container.Center);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
 | 
						|
        expect(new Set(store.getContainerWidgets(mockRoom, Container.Right))).toEqual(
 | 
						|
            new Set([mockApps[0], mockApps[1], mockApps[2]]),
 | 
						|
        );
 | 
						|
        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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[1], Container.Top);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[2], Container.Top);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[0], Container.Center);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([mockApps[0]]);
 | 
						|
        expect(new Set(store.getContainerWidgets(mockRoom, Container.Right))).toEqual(
 | 
						|
            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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[1], Container.Top);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([mockApps[1]]);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);
 | 
						|
        expect(new Set(store.getContainerWidgets(mockRoom, Container.Right))).toEqual(
 | 
						|
            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);
 | 
						|
        store.moveToContainer(mockRoom, mockApps[0], Container.Top);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([mockApps[0]]);
 | 
						|
        expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);
 | 
						|
        expect(new Set(store.getContainerWidgets(mockRoom, Container.Right))).toEqual(
 | 
						|
            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",
 | 
						|
                    },
 | 
						|
                  },
 | 
						|
                },
 | 
						|
                "",
 | 
						|
              ],
 | 
						|
            ]
 | 
						|
        `);
 | 
						|
    });
 | 
						|
 | 
						|
    it("Can call onNotReady before onReady has been called", () => {
 | 
						|
        // Just to quieten SonarCloud :-(
 | 
						|
 | 
						|
        // @ts-ignore bypass private ctor for tests
 | 
						|
        const store = new WidgetLayoutStore();
 | 
						|
        // @ts-ignore calling private method
 | 
						|
        store.onNotReady();
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when feature_dynamic_room_predecessors is not enabled", () => {
 | 
						|
        beforeAll(() => {
 | 
						|
            jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
 | 
						|
        });
 | 
						|
 | 
						|
        it("passes the flag in to getVisibleRooms", async () => {
 | 
						|
            mocked(client.getVisibleRooms).mockRestore();
 | 
						|
            mocked(client.getVisibleRooms).mockReturnValue([]);
 | 
						|
            // @ts-ignore bypass private ctor for tests
 | 
						|
            const store = new WidgetLayoutStore();
 | 
						|
            await store.start();
 | 
						|
            expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when feature_dynamic_room_predecessors is enabled", () => {
 | 
						|
        beforeAll(() => {
 | 
						|
            jest.spyOn(SettingsStore, "getValue").mockImplementation(
 | 
						|
                (settingName) => settingName === "feature_dynamic_room_predecessors",
 | 
						|
            );
 | 
						|
        });
 | 
						|
 | 
						|
        it("passes the flag in to getVisibleRooms", async () => {
 | 
						|
            mocked(client.getVisibleRooms).mockRestore();
 | 
						|
            mocked(client.getVisibleRooms).mockReturnValue([]);
 | 
						|
            // @ts-ignore bypass private ctor for tests
 | 
						|
            const store = new WidgetLayoutStore();
 | 
						|
            await store.start();
 | 
						|
            expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |