Release Schema Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 08:04:49 +02:00
parent 754608e52b
commit 0c47ec5105
30 changed files with 587 additions and 94 deletions
+3 -3
View File
@@ -36,10 +36,10 @@ test("validates and traces a supplied local JSON Schema reference", async ({
}) => {
const external = await localOnly(page);
await page.goto("/deep/nested/schema/");
await page.getByRole("tab", { name: "Validate" }).click();
await page.getByRole("button", { name: "Validate" }).click();
await page.getByRole("button", { name: "Validate instance" }).click();
await expect(page.getByText(/Instance is valid/u)).toBeVisible();
await page.getByRole("tab", { name: "References" }).click();
await page.getByRole("button", { name: "References" }).click();
await expect(
page.getByText("address.schema.json#/$defs/address", { exact: true }),
).toBeVisible();
@@ -70,7 +70,7 @@ test("serves release identity and hardened headers", async ({ request }) => {
const manifest = await request.get("/deep/nested/schema/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.schema-tools",
version: "0.1.2",
version: "0.2.0",
entry: "./",
privacy: { processing: "local", telemetry: false },
});
+18
View File
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";
test("keeps the primary workspace inside a narrow viewport", async ({
page,
}) => {
await page.goto("/deep/nested/schema/");
await expect(page.locator("main").first()).toBeVisible();
await expect(
page.locator("main .loading, main .workbench-loading"),
).toHaveCount(0);
const widths = await page.evaluate(() => ({
content: document.documentElement.scrollWidth,
viewport: document.documentElement.clientWidth,
}));
expect(widths.viewport).toBeLessThanOrEqual(430);
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
});
+1 -1
View File
@@ -20,7 +20,7 @@ describe("Schema Tools", () => {
);
expect(screen.getByText("focused validation")).toBeVisible();
expect(screen.getAllByText("structural only")).toHaveLength(2);
await userEvent.click(screen.getByRole("tab", { name: "Validate" }));
await userEvent.click(screen.getByRole("button", { name: "Validate" }));
await userEvent.click(
screen.getByRole("button", { name: "Validate instance" }),
);
+75 -2
View File
@@ -6,6 +6,7 @@ import {
inspectWorkspace,
parseSchemaDocument,
validateJsonInstance,
validateJsonInstanceSafe,
} from "../../src/schema/model";
const entry = {
@@ -109,6 +110,76 @@ function captureError(run: () => unknown): Error {
}
describe("schema workspace", () => {
it("validates pattern and patternProperties through a bounded batch runner", async () => {
const workspace = inspectWorkspace([
{
name: "pattern.schema.json",
source: JSON.stringify({
type: "object",
propertyNames: { pattern: "^[a-z-]+$" },
properties: {
label: { type: "string", pattern: "^ok(?:-|$)" },
},
patternProperties: {
"^x-": { type: "integer", minimum: 0 },
},
additionalProperties: false,
}),
},
]);
const runner = async (
patterns: readonly string[],
candidates: readonly string[],
) =>
new Map(
patterns.map((source) => {
const expression = new RegExp(source, "u");
return [
source,
new Set(
candidates.filter((candidate) => expression.test(candidate)),
),
] as const;
}),
);
expect(
(
await validateJsonInstanceSafe(
workspace,
JSON.stringify({ label: "ok-value", "x-count": 2 }),
workspace.entry,
runner,
)
).valid,
).toBe(true);
const invalid = await validateJsonInstanceSafe(
workspace,
JSON.stringify({ label: "bad", "x-count": "two", Other: true }),
workspace.entry,
runner,
);
expect(invalid.valid).toBe(false);
expect(invalid.diagnostics.map((item) => item.message).join("\n")).toMatch(
/pattern.*does not match|type.*integer|additionalProperties|false schema/iu,
);
});
it("refuses synchronous regex validation instead of executing on the caller thread", () => {
const workspace = inspectWorkspace([
{
name: "pattern.schema.json",
source: JSON.stringify({ type: "string", pattern: "^(a+)+$" }),
},
]);
const result = validateJsonInstance(workspace, JSON.stringify("aaaa"));
expect(result.valid).toBe(false);
expect(result.diagnostics[0]?.message).toMatch(
/bounded asynchronous validation|isolated worker/iu,
);
});
it("resolves local references, generates a sample, and validates instances", () => {
const workspace = inspectWorkspace([entry, support], entry.name);
expect(workspace.references).toEqual([
@@ -451,7 +522,7 @@ describe("schema workspace", () => {
);
});
it("refuses executable regex keywords in the bounded validator", () => {
it("routes executable regex keywords away from the synchronous validator", () => {
const workspace = inspectWorkspace([
{
name: "pattern.json",
@@ -460,7 +531,9 @@ describe("schema workspace", () => {
]);
const result = validateJsonInstance(workspace, JSON.stringify("aaaa"));
expect(result.valid).toBe(false);
expect(result.diagnostics[0]?.message).toMatch(/pattern.*not executed/iu);
expect(result.diagnostics[0]?.message).toMatch(
/regular expressions.*bounded asynchronous validation/iu,
);
});
it("also refuses regex and dynamic-reference keywords in supplied reference documents", () => {