@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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" }),
|
||||
);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user