web: repair config source editor
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
declare const Deno: {
|
||||
test(name: string, fn: () => Promise<void> | void): void;
|
||||
readTextFile(path: URL): Promise<string>;
|
||||
};
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
Deno.test("config editor snapshots Svelte proxies before cloning baselines", async () => {
|
||||
const source = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../src/lib/workspace/config-source/ConfigSourceEditor.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
assert(
|
||||
source.includes("$state.raw<WorkspaceConfigTreeResponse"),
|
||||
"the immutable config baseline should not be wrapped in another deep proxy",
|
||||
);
|
||||
assert(
|
||||
source.includes("$state.snapshot(treeState.snapshot)"),
|
||||
"reactive tree snapshots should be converted to plain values before reuse",
|
||||
);
|
||||
assert(
|
||||
!source.includes("structuredClone(treeState.snapshot)"),
|
||||
"structuredClone must never receive a Svelte state proxy",
|
||||
);
|
||||
assert(
|
||||
source.includes("toolchain?.setSnapshot(") &&
|
||||
source.includes("treeState.contract.schema_bundle") &&
|
||||
source.includes("remote.contract.schema_bundle"),
|
||||
"the authoritative schema bundle should be installed with every snapshot",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Decodal editor follows readonly prop changes after mount", async () => {
|
||||
const source = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../src/lib/workspace/settings/DecodalSourceEditor.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
|
||||
assert(
|
||||
source.includes("let view = $state.raw<EditorView | null>(null)") &&
|
||||
source.includes("if (!host || untrack(() => view)) return"),
|
||||
"imperative EditorView should avoid deep proxying and remain outside the mount effect dependency graph",
|
||||
);
|
||||
assert(
|
||||
source.includes("new Compartment()") &&
|
||||
source.includes("readonlyCompartment.reconfigure") &&
|
||||
source.includes("EditorView.editable.of(!nextReadonly)"),
|
||||
"readonly and editable facets should be reconfigured when the prop changes",
|
||||
);
|
||||
assert(
|
||||
source.includes("syntaxHighlighting(syntaxTheme)") &&
|
||||
source.includes("tags.keyword") &&
|
||||
source.includes("tags.string"),
|
||||
"Decodal tokens should use an explicit syntax theme",
|
||||
);
|
||||
assert(
|
||||
source.includes(".cm-cursor, .cm-dropCursor") &&
|
||||
source.includes(".cm-selectionBackground") &&
|
||||
source.includes("var(--interactive-selected)"),
|
||||
"cursor and selection should be visible against the workspace theme",
|
||||
);
|
||||
assert(
|
||||
!source.includes("--surface-2") &&
|
||||
!source.includes("--text-primary") &&
|
||||
!source.includes("--border-subtle"),
|
||||
"CodeMirror theme must use workspace tokens that actually exist",
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
declare const Deno: {
|
||||
test(name: string, fn: () => Promise<void> | void): void;
|
||||
readTextFile(path: URL): Promise<string>;
|
||||
};
|
||||
|
||||
import { toCodeMirrorCompletion } from "../../src/lib/workspace/config-source/completion.ts";
|
||||
import { jsonWorkerMessage } from "../../src/lib/workspace/config-source/toolchain-message.ts";
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
Deno.test("toolchain converts reactive-like proxies to plain Worker messages", async () => {
|
||||
const snapshot = new Proxy(
|
||||
{
|
||||
revision: 1,
|
||||
digest: "sha256:test",
|
||||
entries: {},
|
||||
},
|
||||
{},
|
||||
);
|
||||
const request = {
|
||||
id: 1,
|
||||
kind: "set_snapshot" as const,
|
||||
snapshot,
|
||||
};
|
||||
|
||||
let cloneRejected = false;
|
||||
try {
|
||||
structuredClone(request);
|
||||
} catch (error) {
|
||||
cloneRejected = error instanceof DOMException &&
|
||||
error.name === "DataCloneError";
|
||||
}
|
||||
assert(
|
||||
cloneRejected,
|
||||
"fixture should reproduce the Worker postMessage clone failure",
|
||||
);
|
||||
|
||||
const message = jsonWorkerMessage(request);
|
||||
assert(
|
||||
message.snapshot !== snapshot,
|
||||
"snapshot should be detached from the Proxy",
|
||||
);
|
||||
structuredClone(message);
|
||||
|
||||
const source = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../src/lib/workspace/config-source/toolchain.ts",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
assert(
|
||||
source.includes("const message = jsonWorkerMessage({ ...request, id })") &&
|
||||
source.includes("this.#worker.postMessage(message)"),
|
||||
"ConfigSourceToolchain should normalize every command at the Worker boundary",
|
||||
);
|
||||
const workerSource = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../src/lib/workspace/config-source/toolchain.worker.ts",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
assert(
|
||||
workerSource.includes("set_schema_bundle(request.schemaBundle)"),
|
||||
"Config Source worker should install the WorkspaceConfigSchema global contract",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("toolchain adapts WASM completion items and byte offsets for CodeMirror", () => {
|
||||
const source = "let 名 = tru";
|
||||
const result = toCodeMirrorCompletion(source, {
|
||||
from: new TextEncoder().encode("let 名 = ").length,
|
||||
items: [
|
||||
{
|
||||
label: "true",
|
||||
kind: "constant",
|
||||
detail: "Bool",
|
||||
priority: 20,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert(result !== null, "WASM completion should produce a CodeMirror result");
|
||||
assert(
|
||||
result.from === "let 名 = ".length,
|
||||
"byte offsets should become UTF-16 offsets",
|
||||
);
|
||||
assert(
|
||||
result.options.length === 1,
|
||||
"WASM items should become CodeMirror options",
|
||||
);
|
||||
assert(
|
||||
result.options[0].label === "true",
|
||||
"completion label should be preserved",
|
||||
);
|
||||
assert(
|
||||
result.options[0].type === "constant",
|
||||
"completion kind should become the icon type",
|
||||
);
|
||||
assert(
|
||||
result.options[0].detail === "Bool",
|
||||
"completion detail should be preserved",
|
||||
);
|
||||
assert(
|
||||
result.options[0].boost === 20,
|
||||
"completion priority should become its boost",
|
||||
);
|
||||
});
|
||||
@@ -3,8 +3,11 @@
|
||||
import { assertEquals } from "jsr:@std/assert";
|
||||
import init, {
|
||||
analyze_snapshot,
|
||||
complete_current,
|
||||
compose_schema_bundle,
|
||||
evaluate_snapshot,
|
||||
set_schema_bundle,
|
||||
set_snapshot,
|
||||
} from "../../src/lib/workspace/config-source/generated/config_source_wasm.js";
|
||||
import type {
|
||||
ConfigTreeSnapshot,
|
||||
@@ -241,3 +244,74 @@ Deno.test("generated WASM preserves native Decodal 0.4 diagnostic semantics", ()
|
||||
assertEquals(diagnostic.span.end_byte > diagnostic.span.start_byte, true);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("generated WASM returns completion items for the editor adapter", () => {
|
||||
const source = 'import "./"';
|
||||
set_snapshot({
|
||||
...snapshot,
|
||||
entries: {
|
||||
...snapshot.entries,
|
||||
"workspace.dcdl": {
|
||||
...snapshot.entries["workspace.dcdl"],
|
||||
content: source,
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = complete_current(
|
||||
"workspace.dcdl",
|
||||
source,
|
||||
source.length - 1,
|
||||
true,
|
||||
) as {
|
||||
from: number;
|
||||
items: Array<{ label: string; kind: string }>;
|
||||
};
|
||||
|
||||
assertEquals(result.from, 8);
|
||||
assertEquals(result.items[0].label, "./lib/value.dcdl");
|
||||
assertEquals(result.items[0].kind, "file");
|
||||
});
|
||||
|
||||
Deno.test("generated WASM completes asserted WorkspaceConfigSchema keys", () => {
|
||||
const bareSource = "{ pro }";
|
||||
const source = "{ pro } as WorkspaceConfigSchema";
|
||||
const cursor = source.indexOf("pro") + 3;
|
||||
set_snapshot({
|
||||
...snapshot,
|
||||
entries: {
|
||||
...snapshot.entries,
|
||||
"workspace.dcdl": {
|
||||
...snapshot.entries["workspace.dcdl"],
|
||||
content: source,
|
||||
},
|
||||
},
|
||||
});
|
||||
set_schema_bundle({
|
||||
contributions: [],
|
||||
source: "{ profile = { default_profile = String; }; prompts = {}; }",
|
||||
fingerprint: "sha256:test-schema",
|
||||
});
|
||||
const bare = complete_current(
|
||||
"workspace.dcdl",
|
||||
bareSource,
|
||||
bareSource.indexOf("pro") + 3,
|
||||
true,
|
||||
) as { items: Array<{ label: string }> } | null;
|
||||
assertEquals(
|
||||
bare?.items.some((item) => item.label === "profile") ?? false,
|
||||
false,
|
||||
);
|
||||
|
||||
const result = complete_current(
|
||||
"workspace.dcdl",
|
||||
source,
|
||||
cursor,
|
||||
true,
|
||||
) as {
|
||||
from: number;
|
||||
items: Array<{ label: string; kind: string }>;
|
||||
};
|
||||
|
||||
assertEquals(result.from, 2);
|
||||
assertEquals(result.items.some((item) => item.label === "profile"), true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user