Archived
refactor(test): route SettingsDialog panel test through a public seam
Replace the SettingsDialog.general.test.ts markup-scraping assertion (which scraped the rendered TemplateResult for '<settings-general-panel', 'scope-note', 'This tab edits:') with an exported pure routing seam activeSettingsPanelTag() and assert the section->panel contract directly. Delete the now-unused collectTemplateStrings helper and its private template-reflection cluster from SettingsDialog.testSupport.ts. The .packages/.plugins/.sessiond siblings inspect no TemplateResult internals (orchestration reflection only), so no migration was needed for them.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { configApi, type PiWebConfigResponse } from "../api";
|
||||
import { SettingsDialog } from "./SettingsDialog";
|
||||
import { callDialogPromise, callDialogUpdated, collectTemplateStrings, configResponse, deferred, getDialogProperty, remoteMachine, secondRemoteMachine, setDialogProperty, stubWindowTimers } from "./SettingsDialog.testSupport";
|
||||
import { activeSettingsPanelTag, SettingsDialog } from "./SettingsDialog";
|
||||
import { callDialogPromise, callDialogUpdated, configResponse, deferred, getDialogProperty, remoteMachine, secondRemoteMachine, setDialogProperty, stubWindowTimers } from "./SettingsDialog.testSupport";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -9,16 +9,15 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("settings-dialog general settings machine targeting", () => {
|
||||
it("renders the active settings panel without the old global scope note", () => {
|
||||
const dialog = new SettingsDialog();
|
||||
dialog.section = "general";
|
||||
dialog.machine = remoteMachine;
|
||||
|
||||
const strings = collectTemplateStrings(dialog.render()).join("");
|
||||
|
||||
expect(strings).toContain("<settings-general-panel");
|
||||
expect(strings).not.toContain("scope-note");
|
||||
expect(strings).not.toContain("This tab edits:");
|
||||
it("routes each section to a single settings panel with no per-tab scope-note wrapper", () => {
|
||||
// The old global "scope-note"/"This tab edits:" wrapper is gone: each section
|
||||
// now maps to exactly one panel element. Assert that public routing contract
|
||||
// (`activeSettingsPanelTag`) instead of scraping the rendered template markup.
|
||||
expect(activeSettingsPanelTag("general")).toBe("settings-general-panel");
|
||||
expect(activeSettingsPanelTag("sessiond")).toBe("settings-sessiond-panel");
|
||||
expect(activeSettingsPanelTag("packages")).toBe("settings-packages-panel");
|
||||
expect(activeSettingsPanelTag("plugins")).toBe("settings-plugins-panel");
|
||||
expect(activeSettingsPanelTag("shortcuts")).toBe("settings-shortcuts-panel");
|
||||
});
|
||||
|
||||
it("keeps gateway server config saves on the gateway config endpoint", async () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { vi } from "vitest";
|
||||
import { PI_WEB_CAPABILITIES } from "../../../shared/capabilities";
|
||||
import type { Machine, MachineRuntime, PiPackageInfo, PiPackageMutationResponse, PiWebConfigResponse, PiWebConfigValues, PiWebPluginInfo, PiWebPluginsResponse } from "../api";
|
||||
@@ -58,43 +57,6 @@ function isDialogMethod(value: unknown): value is (this: SettingsDialog, ...args
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
export function collectTemplateStrings(template: TemplateResult): string[] {
|
||||
const strings: string[] = [];
|
||||
visitTemplate(template);
|
||||
return strings;
|
||||
|
||||
function visitTemplate(current: TemplateResult): void {
|
||||
strings.push(...templateStrings(current));
|
||||
for (const value of templateValues(current)) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) if (isTemplateResult(item)) visitTemplate(item);
|
||||
} else if (isTemplateResult(value)) {
|
||||
visitTemplate(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function templateStrings(template: TemplateResult): readonly string[] {
|
||||
const strings = Reflect.get(template, "strings");
|
||||
if (!isStringArray(strings)) throw new Error("TemplateResult strings were unavailable");
|
||||
return strings;
|
||||
}
|
||||
|
||||
function templateValues(template: TemplateResult): readonly unknown[] {
|
||||
const values = Reflect.get(template, "values");
|
||||
if (!Array.isArray(values)) throw new Error("TemplateResult values were unavailable");
|
||||
return values.map((value: unknown) => value);
|
||||
}
|
||||
|
||||
function isTemplateResult(value: unknown): value is TemplateResult {
|
||||
return typeof value === "object" && value !== null && isStringArray(Reflect.get(value, "strings")) && Array.isArray(Reflect.get(value, "values"));
|
||||
}
|
||||
|
||||
function isStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && value.every((item: unknown) => typeof item === "string");
|
||||
}
|
||||
|
||||
export function configResponse(config: PiWebConfigValues): PiWebConfigResponse {
|
||||
return {
|
||||
path: "/tmp/pi-web/config.json",
|
||||
|
||||
@@ -129,6 +129,9 @@ export class SettingsDialog extends LitElement {
|
||||
}
|
||||
|
||||
private renderActiveSection(): TemplateResult {
|
||||
// Keep the section -> panel routing in sync with the public
|
||||
// `activeSettingsPanelTag` seam below, which tests assert against instead of
|
||||
// scraping this template's markup.
|
||||
if (this.section === "sessiond") {
|
||||
return html`
|
||||
<settings-sessiond-panel
|
||||
@@ -677,3 +680,33 @@ export class SettingsDialog extends LitElement {
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export type SettingsPanelTag =
|
||||
| "settings-general-panel"
|
||||
| "settings-sessiond-panel"
|
||||
| "settings-packages-panel"
|
||||
| "settings-plugins-panel"
|
||||
| "settings-shortcuts-panel";
|
||||
|
||||
/**
|
||||
* The single custom-element panel the settings dialog renders for a section.
|
||||
*
|
||||
* This is the public routing contract behind `renderActiveSection`: each section
|
||||
* maps to exactly one panel element and nothing else (no per-tab "scope note"
|
||||
* wrapper). Tests assert this mapping instead of inspecting the rendered
|
||||
* `TemplateResult`'s markup.
|
||||
*/
|
||||
export function activeSettingsPanelTag(section: SettingsSection): SettingsPanelTag {
|
||||
switch (section) {
|
||||
case "sessiond":
|
||||
return "settings-sessiond-panel";
|
||||
case "packages":
|
||||
return "settings-packages-panel";
|
||||
case "plugins":
|
||||
return "settings-plugins-panel";
|
||||
case "shortcuts":
|
||||
return "settings-shortcuts-panel";
|
||||
case "general":
|
||||
return "settings-general-panel";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user