mirror of
https://github.com/block/buzz.git
synced 2026-10-30 14:01:37 +00:00
265 lines
8.4 KiB
JavaScript
265 lines
8.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import React from "react";
|
|
import { JSDOM } from "jsdom";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import {
|
|
DesktopLifecycleControl,
|
|
DesktopLifecycleReceiver,
|
|
} from "./DesktopLifecycleControl.tsx";
|
|
import { toast } from "sonner";
|
|
import { relayClient } from "../../../shared/api/relayClient.ts";
|
|
|
|
test("mounted Start exposes unavailable provisioning and exact retry; Restart resolves source", async () => {
|
|
const dom = new JSDOM("<div id='root'></div>", {
|
|
url: "https://desktop.test",
|
|
});
|
|
Object.assign(globalThis, {
|
|
window: dom.window,
|
|
document: dom.window.document,
|
|
localStorage: dom.window.localStorage,
|
|
IS_REACT_ACT_ENVIRONMENT: true,
|
|
});
|
|
const { createRoot } = await import("react-dom/client");
|
|
const client = new QueryClient({
|
|
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
|
|
});
|
|
client.setQueryData(
|
|
["relay-agents"],
|
|
[
|
|
{ pubkey: "agent", name: "Owned agent", ownerPubkey: "owner" },
|
|
{ pubkey: "foreign", name: "Foreign agent", ownerPubkey: "other" },
|
|
],
|
|
);
|
|
const scope = { owner: "owner", community: "wss://one.example" };
|
|
const originals = {
|
|
fetch: relayClient.fetchEvents,
|
|
publish: relayClient.publishEvent,
|
|
};
|
|
const prepared = [],
|
|
sent = [];
|
|
let stop = "failed";
|
|
window.__TAURI_INTERNALS__ = {
|
|
invoke: async (command, args) => {
|
|
assert.equal(args.owner, scope.owner);
|
|
assert.equal(args.community, scope.community);
|
|
if (command === "observe_desktop_placement") return;
|
|
if (command === "read_desktop_placement") return ["source", "selection"];
|
|
if (
|
|
command === "prepare_desktop_lifecycle" ||
|
|
command === "prepare_desktop_stop"
|
|
) {
|
|
const request = {
|
|
id: `request-${prepared.length}`,
|
|
kind: command.endsWith("_stop") ? 50180 : 50182,
|
|
...args,
|
|
};
|
|
prepared.push(request);
|
|
return request;
|
|
}
|
|
if (command === "read_desktop_lifecycle_results")
|
|
return args.request.action === "status"
|
|
? "running"
|
|
: "provisioning_unavailable";
|
|
if (command === "read_desktop_stop_results") return stop;
|
|
throw Error(command);
|
|
},
|
|
};
|
|
relayClient.fetchEvents = async () => [];
|
|
relayClient.publishEvent = async (event, _timeout, _failure, check) => {
|
|
check();
|
|
sent.push(event);
|
|
};
|
|
const root = createRoot(document.getElementById("root"));
|
|
const click = (text) =>
|
|
React.act(async () =>
|
|
[...document.querySelectorAll("button")]
|
|
.find((b) => b.textContent === text)
|
|
.click(),
|
|
);
|
|
const select = (label, value) =>
|
|
React.act(async () => {
|
|
const element = document.querySelector(`select[aria-label="${label}"]`);
|
|
element.value = value;
|
|
element.dispatchEvent(new dom.window.Event("change", { bubbles: true }));
|
|
});
|
|
try {
|
|
await React.act(async () =>
|
|
root.render(
|
|
React.createElement(
|
|
QueryClientProvider,
|
|
{ client },
|
|
React.createElement(DesktopLifecycleControl, {
|
|
scope,
|
|
desktops: [
|
|
{ id: "source", name: "Source" },
|
|
{ id: "destination", name: "Destination" },
|
|
],
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
assert.doesNotMatch(document.body.textContent, /Foreign agent/);
|
|
await select("Agent to place", "agent");
|
|
await select("Destination Desktop", "destination");
|
|
await click("Start on destination");
|
|
assert.match(
|
|
document.body.textContent,
|
|
/keyless launch provisioning is unavailable/,
|
|
);
|
|
assert.equal(prepared[0].desktop, "destination");
|
|
await click("Retry same request");
|
|
assert.equal(prepared.length, 1);
|
|
assert.equal(sent[0], sent[1]);
|
|
await click("Restart on current Desktop");
|
|
const restart = prepared.at(-1);
|
|
assert.equal(
|
|
restart.desktop,
|
|
"source",
|
|
"destination picker must not redirect Restart",
|
|
);
|
|
assert.equal(restart.action, "restart");
|
|
assert.equal(restart.observed, prepared.at(-2).id);
|
|
await click("Move to destination");
|
|
assert.match(document.body.textContent, /destination was not started/);
|
|
const count = prepared.length;
|
|
stop = "stopped";
|
|
await React.act(async () => {});
|
|
assert.equal(prepared.length, count, "late Stop cannot resume failed Move");
|
|
assert.doesNotMatch(document.body.textContent, /Retry same request/);
|
|
} finally {
|
|
await React.act(async () => root.unmount());
|
|
client.clear();
|
|
relayClient.fetchEvents = originals.fetch;
|
|
relayClient.publishEvent = originals.publish;
|
|
dom.window.close();
|
|
}
|
|
});
|
|
|
|
test("receiver failure is a scope-owned notification, not pre-shell layout", async () => {
|
|
const originalRaf = globalThis.requestAnimationFrame;
|
|
globalThis.requestAnimationFrame = (fn) => setTimeout(fn, 0);
|
|
const dom = new JSDOM("<div id='root'></div>", {
|
|
url: "https://desktop.test",
|
|
});
|
|
Object.assign(globalThis, {
|
|
window: dom.window,
|
|
document: dom.window.document,
|
|
localStorage: dom.window.localStorage,
|
|
IS_REACT_ACT_ENVIRONMENT: true,
|
|
});
|
|
const { createRoot } = await import("react-dom/client");
|
|
const originals = {
|
|
fetch: relayClient.fetchEvents,
|
|
subscribe: relayClient.subscribeLive,
|
|
};
|
|
let readiness;
|
|
let closed = 0;
|
|
let rejectLate;
|
|
let delayed = false;
|
|
let storageAvailable = false;
|
|
let subscribed = 0;
|
|
relayClient.fetchEvents = async () => {
|
|
if (delayed)
|
|
return new Promise((_, reject) => {
|
|
rejectLate = reject;
|
|
});
|
|
return [];
|
|
};
|
|
relayClient.subscribeLive = async (
|
|
_filter,
|
|
_event,
|
|
_onReadiness,
|
|
_timeout,
|
|
options,
|
|
) => {
|
|
subscribed++;
|
|
readiness = options.onState;
|
|
readiness("eose");
|
|
return () => {
|
|
closed++;
|
|
};
|
|
};
|
|
window.__TAURI_INTERNALS__ = {
|
|
invoke: async () => {
|
|
if (storageAvailable) return;
|
|
throw new Error("fixture: storage unavailable");
|
|
},
|
|
};
|
|
const root = createRoot(document.getElementById("root"));
|
|
const scope = { owner: "owner", community: "wss://one.example" };
|
|
const warnings = () =>
|
|
toast
|
|
.getToasts()
|
|
.filter((t) => String(t.title).startsWith("Desktop lifecycle"));
|
|
try {
|
|
await React.act(async () =>
|
|
root.render(React.createElement(DesktopLifecycleReceiver, { scope })),
|
|
);
|
|
assert.equal(
|
|
document.getElementById("root").childElementCount,
|
|
0,
|
|
"startup must not render in-flow failure UI",
|
|
);
|
|
assert.equal(warnings().length, 1);
|
|
assert.equal(
|
|
warnings()[0].title,
|
|
"Desktop lifecycle receiver is unavailable (projection: request failed).",
|
|
);
|
|
assert.equal(warnings()[0].duration, Infinity);
|
|
assert.equal(warnings()[0].closeButton, true);
|
|
readiness("closed");
|
|
assert.equal(
|
|
warnings().length,
|
|
1,
|
|
"repeated failures update one notification",
|
|
);
|
|
const retryAction = warnings()[0].action;
|
|
assert.equal(retryAction.label, "Retry receiver");
|
|
storageAvailable = true;
|
|
await React.act(async () => retryAction.onClick());
|
|
assert.equal(subscribed, 2, "explicit recovery starts a new live receiver");
|
|
assert.equal(
|
|
warnings().length,
|
|
0,
|
|
"successful recovery removes its warning",
|
|
);
|
|
await React.act(async () =>
|
|
root.render(
|
|
React.createElement(DesktopLifecycleReceiver, { scope: null }),
|
|
),
|
|
);
|
|
assert.equal(warnings().length, 0, "leaving the scope removes its warning");
|
|
readiness("closed");
|
|
assert.equal(
|
|
warnings().length,
|
|
0,
|
|
"retired receiver cannot notify another scope",
|
|
);
|
|
delayed = true;
|
|
await React.act(async () =>
|
|
root.render(React.createElement(DesktopLifecycleReceiver, { scope })),
|
|
);
|
|
assert.equal(typeof rejectLate, "function");
|
|
await React.act(async () => root.unmount());
|
|
await React.act(async () => rejectLate(new Error("late failure")));
|
|
assert.equal(
|
|
warnings().length,
|
|
0,
|
|
"late startup rejection must not recreate the warning",
|
|
);
|
|
assert.equal(
|
|
closed,
|
|
3,
|
|
"failed, recovered and retired subscriptions are released",
|
|
);
|
|
} finally {
|
|
await React.act(async () => root.unmount());
|
|
relayClient.fetchEvents = originals.fetch;
|
|
relayClient.subscribeLive = originals.subscribe;
|
|
for (const warning of warnings()) toast.dismiss(warning.id);
|
|
globalThis.requestAnimationFrame = originalRaf;
|
|
dom.window.close();
|
|
}
|
|
});
|