feat(builder): add dynamic blocks email and dnd packages
Quality / quality (ubuntu-latest) (push) Failing after 11m26s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
Clintchiz committed 2026-09-03 14:00:41 +05:30
1 parent 06433e4ac4
commit 0bd2a6860d
37 files changed
+3060 -1

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
import { describe, expect, test } from "bun:test";
import {
applyCommand,
createRegistry,
decodeClipboard,
defineBlock,
defineDocumentKind,
duplicateNodes,
encodeClipboard,
json,
object,
record,
string,
validateDocument,
type BlockDocument,
} from "../src/index.ts";
let serial = 0;
const id = () => `id-${++serial}`;
const styles = record(json) as never;
const text = defineBlock({
manifest: {
type: "content.text",
version: 1,
name: { key: "text", fallback: "Text" },
icon: "text",
category: "content",
capabilities: ["content"],
props: object({ text: string({ max: 20 }) }) as never,
styles,
slots: {},
},
create: ({ id }) => ({
id: id(),
type: "content.text",
version: 1,
props: { text: "" },
styles: {},
slots: {},
}),
renderers: { html: ({ node }) => String(node.props.text) },
textRenderer: ({ node }) => String(node.props.text),
});
const root = defineBlock({
manifest: {
type: "layout.root",
version: 1,
name: { key: "root", fallback: "Root" },
icon: "root",
category: "layout",
props: object({}) as never,
styles,
slots: { content: { accepts: { capabilities: ["content"] }, maxChildren: 5 } },
},
create: ({ id }) => ({
id: id(),
type: "layout.root",
version: 1,
props: {},
styles: {},
slots: { content: [] },
}),
renderers: { html: ({ slots }) => slots.content },
textRenderer: ({ slots }) => slots.content,
});
const kind = defineDocumentKind({
kind: "test",
version: 1,
metadata: object({ title: string() }) as never,
rootTypes: ["layout.root"],
blocks: [root, text],
variables: {},
breakpoints: {},
capabilities: [],
targets: ["html"],
limits: {
maxBytes: 10_000,
maxNodes: 20,
maxDepth: 5,
maxChildrenPerSlot: 5,
maxStringLength: 100,
maxOutputBytes: 1_000,
},
urlSchemes: ["https"],
});
const registry = createRegistry(kind.blocks).freeze();
const doc = (): BlockDocument => ({
schemaVersion: 1,
kind: "test",
meta: { title: "x" },
theme: { tokens: {} },
root: {
id: "root",
type: "layout.root",
version: 1,
props: {},
styles: {},
slots: { content: [] },
},
});
describe("dynamic blocks engine", () => {
test("rejects duplicate registrations and freezes registries", () => {
expect(() => createRegistry([text, text])).toThrow(/Duplicate block/);
const frozen = createRegistry([text]).freeze();
expect(() => (frozen as never as { register(value: unknown): void }).register(text)).toThrow();
});
test("validates named slots and applies invertible commands", () => {
const node = text.create({ id });
node.props.text = "Hello";
const inserted = applyCommand(
doc(),
{ type: "insert", location: { parentId: "root", slot: "content", index: 0 }, nodes: [node] },
kind,
registry,
);
expect(inserted.ok).toBe(true);
if (!inserted.ok) return;
expect(inserted.value.document.root.slots.content[0]?.props.text).toBe("Hello");
const undone = applyCommand(inserted.value.document, inserted.value.inverse, kind, registry);
expect(undone.ok && undone.value.document.root.slots.content).toEqual([]);
});
test("rejects cycles and invalid slots", () => {
const bad = doc();
bad.root.slots.missing = [];
expect(validateDocument(bad, kind, registry).ok).toBe(false);
});
test("clipboard is deterministic and remaps ids", () => {
const node = text.create({ id });
node.props.text = "Hello";
const encoded = encodeClipboard("test", [node]);
expect(encoded).toBe(encodeClipboard("test", [node]));
const decoded = decodeClipboard(encoded, id);
expect(decoded.ok && decoded.value.roots[0]?.id).not.toBe(node.id);
expect(duplicateNodes([node], id)[0]?.props).toEqual(node.props);
});
});