Don't assume that widget IDs are unique (#8052)

* Don't assume that widget IDs are unique

Signed-off-by: Robin Townsend <robin@robin.town>

* Don't remove live tiles that don't exist

Signed-off-by: Robin Townsend <robin@robin.town>

* Add unit test for AppTile's live tile tracking

Signed-off-by: Robin Townsend <robin@robin.town>
This commit is contained in:
Robin 2022-03-15 08:15:26 -04:00 committed by GitHub
parent bc8fdac491
commit 744eeb53fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 276 additions and 159 deletions

View file

@ -44,7 +44,20 @@ describe("AppTile", () => {
let r1;
let r2;
const resizeNotifier = new ResizeNotifier();
let app: IApp;
let app1: IApp;
let app2: IApp;
const waitForRps = (roomId: string) => new Promise<void>(resolve => {
const update = () => {
if (
RightPanelStore.instance.currentCardForRoom(roomId).phase !==
RightPanelPhases.Widget
) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
beforeAll(async () => {
stubClient();
@ -66,18 +79,31 @@ describe("AppTile", () => {
return [r1, r2];
});
// Adjust various widget stores to add a mock app
app = {
// Adjust various widget stores to add mock apps
app1 = {
id: "1",
eventId: "1",
roomId: "r1",
type: MatrixWidgetType.Custom,
url: "https://example.com",
name: "Example",
name: "Example 1",
creatorUserId: cli.getUserId(),
avatar_url: null,
};
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([app]);
app2 = {
id: "1",
eventId: "2",
roomId: "r2",
type: MatrixWidgetType.Custom,
url: "https://example.com",
name: "Example 2",
creatorUserId: cli.getUserId(),
avatar_url: null,
};
jest.spyOn(WidgetStore.instance, "getApps").mockImplementation(roomId => {
if (roomId === "r1") return [app1];
if (roomId === "r2") return [app2];
});
// Wake up various stores we rely on
WidgetLayoutStore.instance.useUnitTestClient(cli);
@ -88,6 +114,26 @@ describe("AppTile", () => {
await RightPanelStore.instance.onReady();
});
it("tracks live tiles correctly", () => {
expect(AppTile.isLive("1", "r1")).toEqual(false);
// Try removing the tile before it gets added
AppTile.removeLiveTile("1", "r1");
expect(AppTile.isLive("1", "r1")).toEqual(false);
AppTile.addLiveTile("1", "r1");
expect(AppTile.isLive("1", "r1")).toEqual(true);
AppTile.addLiveTile("1", "r1");
expect(AppTile.isLive("1", "r1")).toEqual(true);
AppTile.removeLiveTile("1", "r1");
expect(AppTile.isLive("1", "r1")).toEqual(true);
AppTile.removeLiveTile("1", "r1");
expect(AppTile.isLive("1", "r1")).toEqual(false);
});
it("destroys non-persisted right panel widget on room change", async () => {
// Set up right panel state
const realGetValue = SettingsStore.getValue;
@ -115,24 +161,14 @@ describe("AppTile", () => {
/>
</MatrixClientContext.Provider>);
// Wait for RPS room 1 updates to fire
const rpsUpdated = new Promise<void>(resolve => {
const update = () => {
if (
RightPanelStore.instance.currentCardForRoom("r1").phase !==
RightPanelPhases.Widget
) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
const rpsUpdated = waitForRps("r1");
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
});
await rpsUpdated;
expect(AppTile.isLive("1")).toBe(true);
expect(AppTile.isLive("1", "r1")).toBe(true);
// We want to verify that as we change to room 2, we should close the
// right panel and destroy the widget.
@ -152,11 +188,88 @@ describe("AppTile", () => {
</MatrixClientContext.Provider>);
expect(endWidgetActions.mock.calls.length).toBe(1);
expect(AppTile.isLive("1")).toBe(false);
expect(AppTile.isLive("1", "r1")).toBe(false);
mockSettings.mockRestore();
});
it("distinguishes widgets with the same ID in different rooms", async () => {
// Set up right panel state
const realGetValue = SettingsStore.getValue;
SettingsStore.getValue = (name, roomId) => {
if (name === "RightPanel.phases") {
if (roomId === "r1") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
}],
isOpen: true,
};
}
return null;
}
return realGetValue(name, roomId);
};
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
// Wait for RPS room 1 updates to fire
const rpsUpdated1 = waitForRps("r1");
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
});
await rpsUpdated1;
expect(AppTile.isLive("1", "r1")).toBe(true);
expect(AppTile.isLive("1", "r2")).toBe(false);
SettingsStore.getValue = (name, roomId) => {
if (name === "RightPanel.phases") {
if (roomId === "r2") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
}],
isOpen: true,
};
}
return null;
}
return realGetValue(name, roomId);
};
// Wait for RPS room 2 updates to fire
const rpsUpdated2 = waitForRps("r2");
// Switch to room 2
dis.dispatch({
action: Action.ViewRoom,
room_id: "r2",
});
renderer.update(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
await rpsUpdated2;
expect(AppTile.isLive("1", "r1")).toBe(false);
expect(AppTile.isLive("1", "r2")).toBe(true);
SettingsStore.getValue = realGetValue;
});
it("preserves non-persisted widget on container move", async () => {
// Set up widget in top container
const realGetValue = SettingsStore.getValue;
@ -187,7 +300,7 @@ describe("AppTile", () => {
/>
</MatrixClientContext.Provider>);
expect(AppTile.isLive("1")).toBe(true);
expect(AppTile.isLive("1", "r1")).toBe(true);
// We want to verify that as we move the widget to the center container,
// the widget frame remains running.
@ -199,11 +312,11 @@ describe("AppTile", () => {
// Stop mocking settings so that the widget move can take effect
mockSettings.mockRestore();
TestRenderer.act(() => {
WidgetLayoutStore.instance.moveToContainer(r1, app, Container.Center);
WidgetLayoutStore.instance.moveToContainer(r1, app1, Container.Center);
});
expect(endWidgetActions.mock.calls.length).toBe(0);
expect(AppTile.isLive("1")).toBe(true);
expect(AppTile.isLive("1", "r1")).toBe(true);
});
afterAll(async () => {