mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-03 14:58:18 +00:00
Own browser UI sessions and shared navigation
This commit is contained in:
1 parent
cfdc698775
commit
273079e827
28 files changed
+1948
-121
No files matched your search
@@ -0,0 +1,202 @@
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { resolve } from "@std/path";
|
||||
import { chromium } from "playwright";
|
||||
|
||||
import { observePageFailures, startStaticServer, waitFor } from "../helpers/browser.ts";
|
||||
|
||||
type OwnerName = "owner-a" | "owner-b" | "owner-c" | "owner-d";
|
||||
type DialogOutcome =
|
||||
| { readonly kind: "resolved"; readonly value: unknown }
|
||||
| { readonly kind: "rejected"; readonly message: string };
|
||||
|
||||
interface BrowserUiLifecycleFixture {
|
||||
readonly ready: boolean;
|
||||
startDialog(name: OwnerName, initialData?: string): void;
|
||||
startMenu(name: OwnerName, title: string): void;
|
||||
clickSavedBackdrop(name: OwnerName): void;
|
||||
hideMenu(name: OwnerName): void;
|
||||
unload(name: OwnerName): void;
|
||||
abortParent(name: OwnerName): void;
|
||||
state(name: OwnerName): {
|
||||
readonly dialogs: readonly DialogOutcome[];
|
||||
readonly menus: readonly string[];
|
||||
readonly hasSavedBackdrop: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
browserUiLifecycle?: BrowserUiLifecycleFixture;
|
||||
}
|
||||
}
|
||||
|
||||
const fixtureDist = resolve(import.meta.dirname!, "../../../_testdata/browser-ui-lifecycle");
|
||||
|
||||
Deno.test({
|
||||
name: "Browser UI: dialogue and menu lifecycles remain owned by their document and plug-in scope",
|
||||
sanitizeOps: false,
|
||||
sanitizeResources: false,
|
||||
async fn() {
|
||||
const server = await startStaticServer(fixtureDist);
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
const assertNoPageFailures = observePageFailures(page);
|
||||
await page.goto(server.baseUrl);
|
||||
await page.waitForFunction(() => window.browserUiLifecycle?.ready === true);
|
||||
|
||||
const startDialog = async (name: OwnerName, initialData?: string) =>
|
||||
await page.evaluate(
|
||||
({ name, initialData }) => window.browserUiLifecycle!.startDialog(name, initialData),
|
||||
{ name, initialData }
|
||||
);
|
||||
const startMenu = async (name: OwnerName, title: string) =>
|
||||
await page.evaluate(({ name, title }) => window.browserUiLifecycle!.startMenu(name, title), {
|
||||
name,
|
||||
title,
|
||||
});
|
||||
const state = async (name: OwnerName) =>
|
||||
await page.evaluate((ownerName) => window.browserUiLifecycle!.state(ownerName), name);
|
||||
|
||||
const ownerA = page.frameLocator("#owner-a");
|
||||
const ownerB = page.frameLocator("#owner-b");
|
||||
const ownerC = page.frameLocator("#owner-c");
|
||||
const ownerD = page.frameLocator("#owner-d");
|
||||
|
||||
await startDialog("owner-a", "A-seed");
|
||||
await startDialog("owner-b", "B-seed");
|
||||
const initial = await page.evaluate(() => ({
|
||||
first: window.browserUiLifecycle!.state("owner-a").dialogs,
|
||||
second: window.browserUiLifecycle!.state("owner-b").dialogs,
|
||||
}));
|
||||
assertEquals(initial, { first: [], second: [] });
|
||||
assertNoPageFailures();
|
||||
await ownerA.getByTestId("lifecycle-dialog").waitFor();
|
||||
await ownerB.getByTestId("lifecycle-dialog").waitFor();
|
||||
assertEquals(await page.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerA.locator(".dialog-host").count(), 1);
|
||||
assertEquals(await ownerB.locator(".dialog-host").count(), 1);
|
||||
|
||||
await ownerA.getByTestId("submit-dialog").click();
|
||||
await waitFor(async () => (await state("owner-a")).dialogs.length === 1, "Owner A dialogue did not finish");
|
||||
assertEquals((await state("owner-a")).dialogs, [{ kind: "resolved", value: "result:A-seed" }]);
|
||||
assertEquals(await ownerA.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerB.locator(".dialog-host").count(), 1);
|
||||
|
||||
await ownerB.getByTestId("close-dialog").click();
|
||||
await waitFor(async () => (await state("owner-b")).dialogs.length === 1, "Owner B dialogue did not finish");
|
||||
assertEquals((await state("owner-b")).dialogs, [{ kind: "resolved", value: "undefined" }]);
|
||||
|
||||
await startMenu("owner-a", "first menu");
|
||||
await ownerA.getByText("first menu", { exact: true }).waitFor();
|
||||
assertEquals(await page.locator(".background").count(), 0);
|
||||
assertEquals(await ownerA.locator(".background").count(), 1);
|
||||
assertEquals(await ownerB.locator(".background").count(), 0);
|
||||
|
||||
await startMenu("owner-a", "replacement menu");
|
||||
await ownerA.getByText("replacement menu", { exact: true }).waitFor();
|
||||
await waitFor(
|
||||
async () => (await state("owner-a")).menus.length === 1,
|
||||
"Replacing the menu did not close its first promise"
|
||||
);
|
||||
assertEquals((await state("owner-a")).menus, ["resolved"]);
|
||||
assertEquals((await state("owner-a")).hasSavedBackdrop, true);
|
||||
|
||||
await page.evaluate(() => window.browserUiLifecycle!.clickSavedBackdrop("owner-a"));
|
||||
assertEquals(await ownerA.locator(".background").count(), 1);
|
||||
assertEquals((await state("owner-a")).menus, ["resolved"]);
|
||||
await page.evaluate(() => window.browserUiLifecycle!.hideMenu("owner-a"));
|
||||
await waitFor(
|
||||
async () => (await state("owner-a")).menus.length === 2,
|
||||
"Hiding the replacement menu did not finish"
|
||||
);
|
||||
assertEquals((await state("owner-a")).menus, ["resolved", "resolved"]);
|
||||
assertEquals(await ownerA.locator(".background").count(), 0);
|
||||
|
||||
await startDialog("owner-a", "unload-A");
|
||||
await startMenu("owner-a", "A unload menu");
|
||||
await startDialog("owner-b", "still-B");
|
||||
await startMenu("owner-b", "B remains open");
|
||||
await ownerA.getByTestId("lifecycle-dialog").waitFor();
|
||||
await ownerA.getByText("A unload menu", { exact: true }).waitFor();
|
||||
await ownerB.getByTestId("lifecycle-dialog").waitFor();
|
||||
await ownerB.getByText("B remains open", { exact: true }).waitFor();
|
||||
|
||||
await page.evaluate(() => window.browserUiLifecycle!.unload("owner-a"));
|
||||
await waitFor(
|
||||
async () =>
|
||||
(await state("owner-a")).dialogs.length === 2 && (await state("owner-a")).menus.length === 3,
|
||||
"Owner A unload did not settle its UI operations"
|
||||
);
|
||||
assertEquals((await state("owner-a")).dialogs[1], { kind: "rejected", message: "Plug-in unloaded" });
|
||||
assertEquals(await ownerA.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerA.locator(".background").count(), 0);
|
||||
assertEquals(await ownerB.locator(".dialog-host").count(), 1);
|
||||
assertEquals(await ownerB.locator(".background").count(), 1);
|
||||
|
||||
await startDialog("owner-a", "after-unload");
|
||||
await startMenu("owner-a", "after unload menu");
|
||||
await waitFor(
|
||||
async () =>
|
||||
(await state("owner-a")).dialogs.length === 3 && (await state("owner-a")).menus.length === 4,
|
||||
"Post-unload UI operations did not settle"
|
||||
);
|
||||
assertEquals((await state("owner-a")).dialogs[2], { kind: "rejected", message: "Plug-in unloaded" });
|
||||
assertEquals((await state("owner-a")).menus[3], "resolved");
|
||||
assertEquals(await ownerA.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerA.locator(".background").count(), 0);
|
||||
|
||||
await startDialog("owner-c", "parent-abort");
|
||||
await startMenu("owner-c", "parent abort menu");
|
||||
await ownerC.getByTestId("lifecycle-dialog").waitFor();
|
||||
await ownerC.getByText("parent abort menu", { exact: true }).waitFor();
|
||||
await page.evaluate(() => window.browserUiLifecycle!.abortParent("owner-c"));
|
||||
await waitFor(
|
||||
async () =>
|
||||
(await state("owner-c")).dialogs.length === 1 && (await state("owner-c")).menus.length === 1,
|
||||
"Parent abort did not settle its UI operations"
|
||||
);
|
||||
assertEquals((await state("owner-c")).dialogs[0], { kind: "rejected", message: "Fixture UI owner ended" });
|
||||
assertEquals((await state("owner-c")).menus[0], "resolved");
|
||||
assertEquals(await ownerC.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerC.locator(".background").count(), 0);
|
||||
assertEquals(await ownerB.locator(".dialog-host").count(), 1);
|
||||
assertEquals(await ownerB.locator(".background").count(), 1);
|
||||
|
||||
await startDialog("owner-c", "after-parent-abort");
|
||||
await startMenu("owner-c", "after parent abort menu");
|
||||
await waitFor(
|
||||
async () =>
|
||||
(await state("owner-c")).dialogs.length === 2 && (await state("owner-c")).menus.length === 2,
|
||||
"Post-abort UI operations did not settle"
|
||||
);
|
||||
assertEquals((await state("owner-c")).dialogs[1], { kind: "rejected", message: "Fixture UI owner ended" });
|
||||
assertEquals((await state("owner-c")).menus[1], "resolved");
|
||||
assertEquals(await ownerC.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerC.locator(".background").count(), 0);
|
||||
|
||||
await startDialog("owner-d", "pre-aborted");
|
||||
await startMenu("owner-d", "pre-aborted menu");
|
||||
await waitFor(
|
||||
async () =>
|
||||
(await state("owner-d")).dialogs.length === 1 && (await state("owner-d")).menus.length === 1,
|
||||
"Pre-aborted UI operations did not settle"
|
||||
);
|
||||
assertEquals((await state("owner-d")).dialogs[0], { kind: "rejected", message: "Fixture UI owner ended" });
|
||||
assertEquals((await state("owner-d")).menus[0], "resolved");
|
||||
assertEquals(await ownerD.locator(".dialog-host").count(), 0);
|
||||
assertEquals(await ownerD.locator(".background").count(), 0);
|
||||
|
||||
await page.evaluate(() => window.browserUiLifecycle!.hideMenu("owner-b"));
|
||||
await waitFor(async () => (await state("owner-b")).menus.length === 1, "Owner B menu did not close");
|
||||
await ownerB.getByTestId("close-dialog").click();
|
||||
await waitFor(async () => (await state("owner-b")).dialogs.length === 2, "Owner B dialogue did not close");
|
||||
assertEquals(await ownerB.locator(".background").count(), 0);
|
||||
assertEquals(await ownerB.locator(".dialog-host").count(), 0);
|
||||
assertNoPageFailures();
|
||||
} finally {
|
||||
await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user