Files
webstudio/apps/builder/app/shared/content-block-source-utils.test.ts
Oleg Isonen 0d448313ab fix: harden MCP mutations and validation (#6402)
## Summary

Fixes the reproducible MCP regressions reported today:

- validate font upload metadata locally instead of surfacing an HTTP 500
- replace only the addressed child when `insertIndex` is supplied
- support cross-parent duplication
- bind data-variable assignments in actions
- reject string-valued `style` bindings
- include descriptions for every advertised MCP tool
- preserve resource data-source exposure on full updates
- render mixed expression/element children in the Builder canvas
- preserve explicit outer Collection references in nested scopes
- reject unsupported `update-page-settings` fields instead of dropping
them
- reject unavailable asset-query variables and log server error details

Closes #6390
Closes #6391
Closes #6392
Closes #6393
Closes #6394
Closes #6395
Closes #6398
Closes #6399
Closes #6401
Closes #6404
Closes #6405

## Verification

- 484 project-build tests
- 62 CLI tests
- 35 Builder browser tests
- 25 SDK tests
- project-build, Builder, CLI, and SDK type checks
- root lint and generated runtime contracts
2026-09-17 10:16:11 +02:00

79 lines
2.4 KiB
TypeScript

import { describe, expect, test } from "vitest";
import { ROOT_INSTANCE_ID, type Instances } from "@webstudio-is/sdk";
import {
isRepeatedContentBlockOccurrence,
parseContentBlockRenderScope,
resolveContentBlockOccurrenceAssetId,
} from "./content-block-source-utils";
const selector = ["block", "collection[item]", "collection", "body"];
describe("Content Block occurrence source", () => {
test("parses only instance selectors", () => {
expect(parseContentBlockRenderScope(JSON.stringify(selector))).toEqual(
selector
);
expect(parseContentBlockRenderScope("{}")).toBeUndefined();
expect(parseContentBlockRenderScope('["block",1]')).toBeUndefined();
});
test("resolves a root-qualified variable scope", () => {
const values = new Map([["asset", "article"]]);
const rootQualified = new Map([
[JSON.stringify([...selector, ROOT_INSTANCE_ID]), values],
]);
expect(
resolveContentBlockOccurrenceAssetId({
source: { type: "expression", value: "asset" },
instanceSelector: selector,
variableValuesByRenderScope: rootQualified,
})
).toBe("article");
});
test("resolves each bound Collection occurrence independently", () => {
const first = ["block", "collection[first]", "collection", "body"];
const second = ["block", "collection[second]", "collection", "body"];
const values = new Map([
[JSON.stringify(first), new Map([["asset", "a.mdx"]])],
[JSON.stringify(second), new Map([["asset", "b.mdx"]])],
]);
expect(
[first, second].map((instanceSelector) =>
resolveContentBlockOccurrenceAssetId({
source: { type: "expression", value: "asset" },
instanceSelector,
variableValuesByRenderScope: values,
})
)
).toEqual(["a.mdx", "b.mdx"]);
});
test("detects repeated selectors consistently", () => {
const instances = new Map(
["block", "collection", "body"].map((id) => [
id,
{
type: "instance" as const,
id,
component: "Box",
children: [],
},
])
) as Instances;
expect(
isRepeatedContentBlockOccurrence({
instanceSelector: selector,
instances,
})
).toBe(true);
expect(
isRepeatedContentBlockOccurrence({
instanceSelector: ["block", "collection", "body"],
instances,
})
).toBe(false);
});
});