mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-23 13:02:58 +00:00
feat: restore opt-in review harness
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { ItemView, Notice, Setting, type WorkspaceLeaf } from "@/deps";
|
||||
import {
|
||||
REVIEW_HARNESS_SCENARIOS,
|
||||
type ReviewHarnessScenarioId,
|
||||
type ReviewHarnessScenarioStatus,
|
||||
} from "./reviewHarnessContract";
|
||||
import type { ReviewHarnessController } from "./reviewHarnessController";
|
||||
|
||||
export const VIEW_TYPE_REVIEW_HARNESS = "self-hosted-livesync-review-harness";
|
||||
|
||||
const STATUS_LABELS: Record<ReviewHarnessScenarioStatus, string> = {
|
||||
idle: "Not run",
|
||||
queued: "Queued",
|
||||
running: "Running",
|
||||
"waiting-for-user": "Waiting for review",
|
||||
passed: "Passed",
|
||||
failed: "Failed",
|
||||
cancelled: "Cancelled",
|
||||
};
|
||||
|
||||
export class ReviewHarnessView extends ItemView {
|
||||
override icon = "test-tube-2";
|
||||
override navigation = true;
|
||||
private unsubscribe: (() => void) | undefined;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
private readonly controller: ReviewHarnessController
|
||||
) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return VIEW_TYPE_REVIEW_HARNESS;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "Self-hosted LiveSync review harness";
|
||||
}
|
||||
|
||||
override async onOpen(): Promise<void> {
|
||||
this.unsubscribe = this.controller.subscribe(() => this.render());
|
||||
this.render();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
override async onClose(): Promise<void> {
|
||||
this.unsubscribe?.();
|
||||
this.unsubscribe = undefined;
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
private addActionButton(
|
||||
setting: Setting,
|
||||
label: string,
|
||||
testId: string,
|
||||
action: () => void | Promise<void>,
|
||||
cta = false
|
||||
): void {
|
||||
setting.addButton((button) => {
|
||||
button.setButtonText(label);
|
||||
if (cta) button.setCta();
|
||||
button.buttonEl.dataset.testid = testId;
|
||||
button.onClick(() => void action());
|
||||
});
|
||||
}
|
||||
|
||||
private renderScenario(id: ReviewHarnessScenarioId): void {
|
||||
const scenario = REVIEW_HARNESS_SCENARIOS.find((candidate) => candidate.id === id)!;
|
||||
const snapshot = this.controller.snapshot();
|
||||
const result = snapshot.results[id];
|
||||
const setting = new Setting(this.contentEl)
|
||||
.setName(scenario.title)
|
||||
.setDesc(`${scenario.description} Mode: ${scenario.mode}. Access: ${scenario.access}.`)
|
||||
.setClass("sls-review-harness__scenario");
|
||||
setting.settingEl.dataset.testid = `review-harness-scenario-${id}`;
|
||||
|
||||
this.addActionButton(
|
||||
setting,
|
||||
id === "compatibility-review" ? "Start review" : "Run",
|
||||
`review-harness-run-${id}`,
|
||||
() => this.controller.runScenario(id)
|
||||
);
|
||||
|
||||
const resultEl = this.contentEl.createDiv({ cls: "sls-review-harness__result" });
|
||||
resultEl.dataset.testid = `review-harness-result-${id}`;
|
||||
resultEl.createEl("strong", { text: `${STATUS_LABELS[result.status]}: ` });
|
||||
resultEl.appendText(result.detail);
|
||||
if (result.observations.length > 0) {
|
||||
const observations = resultEl.createEl("ul");
|
||||
for (const observation of result.observations) observations.createEl("li", { text: observation });
|
||||
}
|
||||
|
||||
if (id === "compatibility-review" && result.status === "waiting-for-user") {
|
||||
const actions = new Setting(this.contentEl).setClass("sls-review-harness__actions");
|
||||
this.addActionButton(
|
||||
actions,
|
||||
"Open compatibility review",
|
||||
"review-harness-open-compatibility-review",
|
||||
() => this.controller.openCompatibilityReview(),
|
||||
true
|
||||
);
|
||||
this.addActionButton(actions, "Restart and return", "review-harness-restart", () =>
|
||||
this.controller.prepareCompatibilityReviewRestart()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
this.contentEl.empty();
|
||||
this.contentEl.addClass("sls-review-harness");
|
||||
this.contentEl.dataset.testid = "review-harness";
|
||||
this.contentEl.createEl("h2", { text: "Self-hosted LiveSync review harness" });
|
||||
this.contentEl.createEl("p", {
|
||||
text: "Use a dedicated test Vault. Read-only scenarios are labelled. The Vault round-trip scenario writes only after confirmation, owns one fixed fixture tree, and removes it in a finally block. The Harness never accepts arbitrary commands, paths, code, or remote credentials.",
|
||||
cls: "sls-review-harness__warning",
|
||||
});
|
||||
this.contentEl.createEl("p", {
|
||||
text: "Automatic scenarios inspect local contracts. The guided compatibility review uses the same device-local pause and explicit action as normal start-up. Real P2P transport remains covered by the Compose E2E suite.",
|
||||
});
|
||||
|
||||
const snapshot = this.controller.snapshot();
|
||||
if (snapshot.continuationError) {
|
||||
this.contentEl.createEl("p", {
|
||||
text: `Continuation error: ${snapshot.continuationError}`,
|
||||
cls: "sls-review-harness__error",
|
||||
});
|
||||
} else if (snapshot.resumedRequestId) {
|
||||
const resumed = this.contentEl.createEl("p", {
|
||||
text: "The one-shot restart continuation was consumed. Complete the guided review below.",
|
||||
cls: "sls-review-harness__resumed",
|
||||
});
|
||||
resumed.dataset.testid = "review-harness-resumed";
|
||||
}
|
||||
|
||||
const suiteActions = new Setting(this.contentEl)
|
||||
.setName("Review suite")
|
||||
.setDesc(snapshot.running ? `Running ${snapshot.current ?? "scenario"}.` : "Choose the scope to run.")
|
||||
.setClass("sls-review-harness__actions");
|
||||
this.addActionButton(suiteActions, "Automatic", "review-harness-run-automatic", () =>
|
||||
this.controller.runAutomaticScenarios()
|
||||
);
|
||||
this.addActionButton(suiteActions, "Full review", "review-harness-run-full", () =>
|
||||
this.controller.runAllScenarios()
|
||||
);
|
||||
this.addActionButton(suiteActions, "Copy Markdown report", "review-harness-copy-report", async () => {
|
||||
await this.controller.copyReport();
|
||||
new Notice("Review Harness Markdown report copied.");
|
||||
});
|
||||
|
||||
this.contentEl.createEl("h3", { text: "Scenarios" });
|
||||
for (const { id } of REVIEW_HARNESS_SCENARIOS) this.renderScenario(id);
|
||||
|
||||
this.contentEl.createEl("p", {
|
||||
text: "Reports are copied locally and are not transmitted. They omit Vault identifiers, paths, contents, remote configuration, and secrets.",
|
||||
cls: "sls-review-harness__privacy",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const REVIEW_HARNESS_SCENARIOS = [
|
||||
id: "settings-lifecycle",
|
||||
title: "Settings lifecycle",
|
||||
description:
|
||||
"Checks whether loaded settings retain the seven synchronisation choices and apply new-Vault recommendations only to a genuinely new Vault.",
|
||||
"Checks whether loaded settings expose the seven synchronisation choices and apply new-Vault recommendations only to a genuinely new Vault.",
|
||||
mode: "automatic",
|
||||
access: "read-only",
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@ describe("Review Harness contract", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("checks that an existing Vault keeps the seven synchronisation choices typed and intact", () => {
|
||||
it("checks that an existing Vault exposes the seven synchronisation choices as booleans", () => {
|
||||
const result = inspectSettingsLifecycle({
|
||||
migration: migration(),
|
||||
settings: preservedSyncSettings,
|
||||
|
||||
@@ -227,13 +227,16 @@ export class ReviewHarnessController {
|
||||
this.current = "compatibility-review";
|
||||
this.notify();
|
||||
try {
|
||||
const pauseWasPending = this.runtime.getCompatibilityPause() !== undefined;
|
||||
await this.runtime.openCompatibilityReview();
|
||||
const inspection = this.inspectCompatibilityReview();
|
||||
this.results["compatibility-review"] =
|
||||
inspection.status === "passed" && !this.runtime.getCompatibilityPause()
|
||||
? {
|
||||
status: "passed",
|
||||
detail: "The device-local compatibility pause was reviewed and cleared.",
|
||||
detail: pauseWasPending
|
||||
? "The device-local compatibility pause was reviewed and cleared."
|
||||
: inspection.detail,
|
||||
observations: inspection.observations,
|
||||
}
|
||||
: inspection.status === "failed"
|
||||
|
||||
@@ -253,6 +253,19 @@ describe("ReviewHarnessController", () => {
|
||||
expect(controller.snapshot().results["compatibility-review"].status).toBe("waiting-for-user");
|
||||
});
|
||||
|
||||
it("does not claim that a compatibility pause was cleared when none was pending", async () => {
|
||||
const runtime = createRuntime();
|
||||
runtime.compatibilityPause = undefined;
|
||||
const controller = new ReviewHarnessController(runtime);
|
||||
|
||||
await controller.openCompatibilityReview();
|
||||
|
||||
expect(controller.snapshot().results["compatibility-review"]).toMatchObject({
|
||||
status: "passed",
|
||||
detail: "No compatibility review is pending on this device.",
|
||||
});
|
||||
});
|
||||
|
||||
it("copies a Markdown report after compatibility evidence is recorded", async () => {
|
||||
const runtime = createRuntime();
|
||||
const controller = new ReviewHarnessController(runtime);
|
||||
|
||||
+6
-1
@@ -42,6 +42,7 @@ import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/
|
||||
import { useP2PReplicatorFeature } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/useP2PReplicatorFeature";
|
||||
import { useP2PReplicatorCommands } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/useP2PReplicatorCommands";
|
||||
import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts";
|
||||
import { useReviewHarness } from "./serviceFeatures/useReviewHarness.ts";
|
||||
import { createOpenReplicationUI, createOpenRebuildUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts";
|
||||
import { useCompatibilityReview } from "./serviceFeatures/compatibilityReview.ts";
|
||||
import { createObsidianCompatibilityReviewUi } from "./serviceFeatures/compatibilityReviewObsidian.ts";
|
||||
@@ -194,7 +195,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||
useOfflineScanner(core);
|
||||
useRedFlagFeatures(core);
|
||||
useCheckRemoteSize(core);
|
||||
useCompatibilityReview(core, createObsidianCompatibilityReviewUi(core.confirm));
|
||||
const compatibilityReview = useCompatibilityReview(
|
||||
core,
|
||||
createObsidianCompatibilityReviewUi(core.confirm)
|
||||
);
|
||||
useReviewHarness(core, this, replicator, compatibilityReview);
|
||||
// p2pReplicatorResult = useP2PReplicator(core, [
|
||||
// VIEW_TYPE_P2P,
|
||||
// (leaf: any) => new P2PReplicatorPaneView(leaf, core, p2pReplicatorResult!),
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { NEW_VAULT_SETTINGS } from "@vrtmrz/livesync-commonlib/settings";
|
||||
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||
import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
||||
import type ObsidianLiveSyncPlugin from "@/main";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import type { WorkspaceLeaf } from "@/deps";
|
||||
import {
|
||||
ReviewHarnessController,
|
||||
REVIEW_HARNESS_STATE_KEY,
|
||||
type ReviewHarnessRuntime,
|
||||
} from "@/features/ReviewHarness/reviewHarnessController";
|
||||
import { ReviewHarnessView, VIEW_TYPE_REVIEW_HARNESS } from "@/features/ReviewHarness/ReviewHarnessView";
|
||||
import type { ReviewHarnessScenarioResult } from "@/features/ReviewHarness/reviewHarnessContract";
|
||||
import {
|
||||
REVIEW_HARNESS_FIXTURE_ROOT,
|
||||
runReviewHarnessVaultRoundTrip,
|
||||
} from "@/features/ReviewHarness/reviewHarnessVaultFixture";
|
||||
import type { CompatibilityReviewController } from "./compatibilityReview";
|
||||
|
||||
async function runVaultRoundTrip(plugin: ObsidianLiveSyncPlugin): Promise<ReviewHarnessScenarioResult> {
|
||||
const vault = plugin.app.vault;
|
||||
return runReviewHarnessVaultRoundTrip({
|
||||
confirmFixtureAccess: async () =>
|
||||
(await plugin.core.services.UI.confirm.askYesNoDialog(
|
||||
"This scenario creates, reads, modifies, renames, and removes one owned fixture tree. Use a dedicated test Vault. Continue?",
|
||||
{
|
||||
title: "Review Harness: Vault fixture access",
|
||||
defaultOption: "No",
|
||||
}
|
||||
)) === "yes",
|
||||
fixtureRootExists: () => vault.getAbstractFileByPath(REVIEW_HARNESS_FIXTURE_ROOT) !== null,
|
||||
createFixtureRoot: async () => {
|
||||
await vault.createFolder(REVIEW_HARNESS_FIXTURE_ROOT);
|
||||
},
|
||||
createFile: (path, content) => vault.create(path, content),
|
||||
readFile: (file) => vault.read(file),
|
||||
modifyFile: (file, content) => vault.modify(file, content),
|
||||
renameFile: (file, path) => vault.rename(file, path),
|
||||
filePath: (file) => file.path,
|
||||
removeFixtureRoot: async () => {
|
||||
const fixtureRoot = vault.getAbstractFileByPath(REVIEW_HARNESS_FIXTURE_ROOT);
|
||||
if (fixtureRoot) await plugin.app.fileManager.trashFile(fixtureRoot);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReviewHarness(
|
||||
core: LiveSyncCore,
|
||||
plugin: ObsidianLiveSyncPlugin,
|
||||
p2p: UseP2PReplicatorResult,
|
||||
compatibilityReview: CompatibilityReviewController
|
||||
): ReviewHarnessController {
|
||||
const services = core.services;
|
||||
const runtime: ReviewHarnessRuntime = {
|
||||
now: () => new Date(),
|
||||
getSettings: () => services.setting.currentSettings(),
|
||||
getNewVaultSettings: () => NEW_VAULT_SETTINGS,
|
||||
getSettingsMigrationState: () => services.setting.getSettingsMigrationState(),
|
||||
isCompatibilityReviewInitialised: () => compatibilityReview.initialised,
|
||||
getCompatibilityPause: () => compatibilityReview.pendingPause,
|
||||
openCompatibilityReview: () => compatibilityReview.openReview(),
|
||||
getP2PComposition: () => ({
|
||||
first: p2p.replicator,
|
||||
second: p2p.replicator,
|
||||
expectedServices: services,
|
||||
}),
|
||||
runVaultRoundTrip: () => runVaultRoundTrip(plugin),
|
||||
readContinuation: () => services.setting.getSmallConfig(REVIEW_HARNESS_STATE_KEY),
|
||||
writeContinuation: (value) => services.setting.setSmallConfig(REVIEW_HARNESS_STATE_KEY, value),
|
||||
deleteContinuation: () => services.setting.deleteSmallConfig(REVIEW_HARNESS_STATE_KEY),
|
||||
restart: () => services.appLifecycle.performRestart(),
|
||||
reportError: (error) => services.API.addLog(error, LOG_LEVEL_NOTICE),
|
||||
copyText: async (value) => {
|
||||
if (!activeWindow.navigator.clipboard) throw new Error("Clipboard access is unavailable on this device.");
|
||||
await activeWindow.navigator.clipboard.writeText(value);
|
||||
},
|
||||
getEnvironment: () => ({
|
||||
pluginVersion: services.API.getPluginVersion(),
|
||||
obsidianVersion: services.API.getAppVersion(),
|
||||
platform: services.API.getPlatform(),
|
||||
userAgent: activeWindow.navigator.userAgent || "unavailable",
|
||||
viewport:
|
||||
typeof activeWindow.innerWidth === "number" && typeof activeWindow.innerHeight === "number"
|
||||
? `${activeWindow.innerWidth}x${activeWindow.innerHeight}`
|
||||
: "unavailable",
|
||||
}),
|
||||
};
|
||||
const controller = new ReviewHarnessController(runtime);
|
||||
let continuationConsumed = false;
|
||||
let registered = false;
|
||||
let openAfterLayout = false;
|
||||
|
||||
services.appLifecycle.onSettingLoaded.addHandler(() => {
|
||||
if (!continuationConsumed) {
|
||||
continuationConsumed = true;
|
||||
controller.consumeContinuation();
|
||||
}
|
||||
const snapshot = controller.snapshot();
|
||||
openAfterLayout = snapshot.resumedRequestId !== null || snapshot.continuationError !== null;
|
||||
if (!services.setting.currentSettings().enableDebugTools || registered) return Promise.resolve(true);
|
||||
|
||||
registered = true;
|
||||
services.API.registerWindow(
|
||||
VIEW_TYPE_REVIEW_HARNESS,
|
||||
(leaf: WorkspaceLeaf) => new ReviewHarnessView(leaf, controller)
|
||||
);
|
||||
services.API.addCommand({
|
||||
id: "open-review-harness",
|
||||
name: "Open review harness",
|
||||
callback: () => {
|
||||
void services.API.showWindow(VIEW_TYPE_REVIEW_HARNESS);
|
||||
},
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
|
||||
services.appLifecycle.onLayoutReady.addHandler(() => {
|
||||
if (openAfterLayout && services.setting.currentSettings().enableDebugTools) {
|
||||
void services.API.showWindow(VIEW_TYPE_REVIEW_HARNESS);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
|
||||
return controller;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { CompatibilityPause } from "@/common/databaseCompatibility.ts";
|
||||
import { REVIEW_HARNESS_STATE_KEY } from "@/features/ReviewHarness/reviewHarnessController.ts";
|
||||
import { useReviewHarness } from "./useReviewHarness.ts";
|
||||
|
||||
const VIEW_TYPE_REVIEW_HARNESS = "self-hosted-livesync-review-harness";
|
||||
|
||||
vi.mock("@/features/ReviewHarness/ReviewHarnessView", () => ({
|
||||
VIEW_TYPE_REVIEW_HARNESS: "self-hosted-livesync-review-harness",
|
||||
ReviewHarnessView: class {},
|
||||
}));
|
||||
|
||||
function compatibilityPause(): CompatibilityPause {
|
||||
return {
|
||||
resumable: true,
|
||||
reasons: [
|
||||
{
|
||||
source: "settings-schema",
|
||||
sourceVersion: 9,
|
||||
currentVersion: 10,
|
||||
isFromFutureSchema: false,
|
||||
resumable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function createFixture(options: { enableDebugTools?: boolean; continuation?: string } = {}) {
|
||||
const settingLoadedHandlers: Array<() => Promise<boolean>> = [];
|
||||
const layoutReadyHandlers: Array<() => Promise<boolean>> = [];
|
||||
const local = new Map<string, string>();
|
||||
if (options.continuation) local.set(REVIEW_HARNESS_STATE_KEY, options.continuation);
|
||||
const settings = {
|
||||
enableDebugTools: options.enableDebugTools ?? true,
|
||||
liveSync: false,
|
||||
syncOnSave: false,
|
||||
syncOnEditorSave: true,
|
||||
syncOnStart: false,
|
||||
syncOnFileOpen: true,
|
||||
syncAfterMerge: false,
|
||||
periodicReplication: true,
|
||||
};
|
||||
const services = {} as Record<string, unknown>;
|
||||
const replicator = { env: { services } };
|
||||
const api = {
|
||||
registerWindow: vi.fn(),
|
||||
addCommand: vi.fn(),
|
||||
showWindow: vi.fn().mockResolvedValue(undefined),
|
||||
addLog: vi.fn(),
|
||||
getPluginVersion: () => "1.0.0-rc.0",
|
||||
getAppVersion: () => "1.12.7",
|
||||
getPlatform: () => "desktop",
|
||||
};
|
||||
Object.assign(services, {
|
||||
setting: {
|
||||
currentSettings: () => settings,
|
||||
getSettingsMigrationState: () => ({
|
||||
sourceVersion: 9,
|
||||
targetVersion: 10,
|
||||
isNewVault: false,
|
||||
isFromFutureSchema: false,
|
||||
changed: true,
|
||||
requiresSyncReview: true,
|
||||
reviewReasons: [],
|
||||
}),
|
||||
getSmallConfig: (key: string) => local.get(key) ?? "",
|
||||
setSmallConfig: (key: string, value: string) => local.set(key, value),
|
||||
deleteSmallConfig: (key: string) => local.delete(key),
|
||||
},
|
||||
appLifecycle: {
|
||||
onSettingLoaded: {
|
||||
addHandler: vi.fn((handler: () => Promise<boolean>) => settingLoadedHandlers.push(handler)),
|
||||
},
|
||||
onLayoutReady: {
|
||||
addHandler: vi.fn((handler: () => Promise<boolean>) => layoutReadyHandlers.push(handler)),
|
||||
},
|
||||
performRestart: vi.fn(),
|
||||
},
|
||||
API: api,
|
||||
UI: {
|
||||
confirm: { askYesNoDialog: vi.fn().mockResolvedValue("no") },
|
||||
},
|
||||
});
|
||||
let pause: CompatibilityPause | undefined = compatibilityPause();
|
||||
const compatibilityReview = {
|
||||
initialised: true,
|
||||
get pendingPause() {
|
||||
return pause;
|
||||
},
|
||||
openReview: vi.fn(async () => {
|
||||
pause = undefined;
|
||||
}),
|
||||
};
|
||||
const core = { services };
|
||||
const plugin = {
|
||||
core,
|
||||
app: {
|
||||
vault: {},
|
||||
fileManager: {},
|
||||
},
|
||||
};
|
||||
|
||||
const controller = useReviewHarness(core as never, plugin as never, { replicator } as never, compatibilityReview as never);
|
||||
return {
|
||||
controller,
|
||||
api,
|
||||
settings,
|
||||
local,
|
||||
settingLoadedHandlers,
|
||||
layoutReadyHandlers,
|
||||
compatibilityReview,
|
||||
};
|
||||
}
|
||||
|
||||
describe("Review Harness composition", () => {
|
||||
it("does not register the command or view when developer debug tools are disabled", async () => {
|
||||
const fixture = createFixture({ enableDebugTools: false });
|
||||
|
||||
await fixture.settingLoadedHandlers[0]();
|
||||
|
||||
expect(fixture.api.registerWindow).not.toHaveBeenCalled();
|
||||
expect(fixture.api.addCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes a one-shot continuation before reopening the Harness after layout", async () => {
|
||||
const requestedAt = "2026-07-18T11:59:00.000Z";
|
||||
const continuation = JSON.stringify({
|
||||
formatVersion: 1,
|
||||
requestId: `compatibility-review-${requestedAt}`,
|
||||
scenarioId: "compatibility-review",
|
||||
stage: "awaiting-restart",
|
||||
requestedAt,
|
||||
});
|
||||
const fixture = createFixture({ continuation });
|
||||
|
||||
await fixture.settingLoadedHandlers[0]();
|
||||
|
||||
expect(fixture.local.has(REVIEW_HARNESS_STATE_KEY)).toBe(false);
|
||||
expect(fixture.controller.snapshot().resumedRequestId).toBe(`compatibility-review-${requestedAt}`);
|
||||
expect(fixture.api.registerWindow).toHaveBeenCalledWith(VIEW_TYPE_REVIEW_HARNESS, expect.any(Function));
|
||||
|
||||
await fixture.layoutReadyHandlers[0]();
|
||||
|
||||
expect(fixture.api.showWindow).toHaveBeenCalledWith(VIEW_TYPE_REVIEW_HARNESS);
|
||||
});
|
||||
|
||||
it("uses the actual compatibility controller as the guided review boundary", async () => {
|
||||
const fixture = createFixture();
|
||||
|
||||
await fixture.controller.runScenario("compatibility-review");
|
||||
expect(fixture.controller.snapshot().results["compatibility-review"].status).toBe("waiting-for-user");
|
||||
|
||||
await fixture.controller.openCompatibilityReview();
|
||||
|
||||
expect(fixture.compatibilityReview.openReview).toHaveBeenCalledOnce();
|
||||
expect(fixture.controller.snapshot().results["compatibility-review"].status).toBe("passed");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user