Expose reusable builder kit surfaces and email preset pack
Quality / quality (ubuntu-latest) (push) Failing after 10m2s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
Clintchiz committed 2026-09-05 19:51:57 +05:30
1 parent e019ac34c8
commit eb1875f0bc
20 files changed
+635 -4

No files matched your search

@@ -0,0 +1,17 @@
import { expect, test } from "bun:test";
import { builderDragAnnouncement } from "../src/announcements.ts";
test("drag messages cover every lifecycle event in supported locales", () => {
for (const event of ["pickedUp", "movedOver", "invalid", "dropped", "cancelled"]) {
for (const locale of ["en", "hi", "ar"]) {
const message = builderDragAnnouncement(event, locale);
expect(message.length).toBeGreaterThan(10);
expect(message).not.toBe(event);
expect(builderDragAnnouncement(event, `${locale}-IN`)).toBe(message);
}
}
expect(builderDragAnnouncement("dropped", "unknown")).toBe(
builderDragAnnouncement("dropped", "en"),
);
expect(builderDragAnnouncement("toString")).toBe("");
});
@@ -0,0 +1,23 @@
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
const source = new Bun.Transpiler({ loader: "ts" }).transformSync(readFileSync(new URL("../src/download.ts", import.meta.url), "utf8")).replace("export function", "function");
test("download keeps direct link after blocked automatic click and releases replaced/detached blobs", () => {
const revoked: string[] = [], observers: Array<{ callback(): void; disconnect(): void }> = [];
let sequence = 0, clicks = 0, disconnected = 0;
const links: any[] = [];
const outlet = { replaceChildren: (link: unknown) => { links.splice(0, links.length, link); } };
const host = { isConnected: true, querySelector: () => outlet, ownerDocument: { documentElement: {}, createElement: () => ({ href: "", download: "", textContent: "", click() { clicks++; throw new Error("blocked"); }, remove() { const index = links.indexOf(this); if (index >= 0) links.splice(index, 1); } }) } };
const offer = new Function("URL", "Blob", "MutationObserver", source + ";return offerBuilderDownload;")(
{ createObjectURL: () => `blob:${++sequence}`, revokeObjectURL: (url: string) => revoked.push(url) }, Blob,
class { constructor(public callback: () => void) { observers.push(this); } observe() {} disconnect() { disconnected++; } },
);
offer(host, "html", "text/html", "draft.html");
expect(links[0].href).toBe("blob:1"); expect(links[0].download).toBe("draft.html");
expect(revoked).toEqual([]); expect(clicks).toBe(1);
offer(host, "text", "text/plain", "draft.txt");
expect(revoked).toEqual(["blob:1"]); expect(links[0].download).toBe("draft.txt");
host.isConnected = false; observers[1]!.callback(); observers[1]!.callback();
expect(revoked).toEqual(["blob:1", "blob:2"]); expect(links).toEqual([]); expect(disconnected).toBe(2);
});
@@ -0,0 +1,26 @@
import { expect, test } from "bun:test";
import { showImageFallbacks } from "../src/image-preview.ts";
test("image-disabled mode exposes safe alt text, proportions and reversible placeholders", () => {
const displayed: any[] = [];
const images = [{ alt: '<b>Logo & name</b>', width: '320', height: '180' }, { alt: '', width: 'not a size', height: '99999' }].map(attributes => ({
getAttribute: (key: string) => (attributes as Record<string, string>)[key] ?? null,
after: (element: unknown) => displayed.push(element),
}));
const canvas = {
querySelectorAll: () => images,
createElement: () => ({ dataset: {}, style: {} as Record<string, string>, textContent: "", attributes: {} as Record<string, string>, setAttribute(key: string, value: string) { this.attributes[key] = value; }, remove() { const index = displayed.indexOf(this); if (index >= 0) displayed.splice(index, 1); } }),
} as unknown as Document;
showImageFallbacks(canvas, true);
expect(displayed).toHaveLength(2);
expect(displayed[0].textContent).toBe('<b>Logo & name</b>');
expect(displayed[0].attributes['aria-label']).toBe('<b>Logo & name</b>');
expect(displayed[0].style.aspectRatio).toBe('320/180');
expect(displayed[1].attributes['aria-hidden']).toBe('true');
expect(displayed[1].style.maxWidth).toBeUndefined();
showImageFallbacks(canvas, true);
expect(displayed).toHaveLength(2);
showImageFallbacks(canvas, false);
expect(displayed).toHaveLength(0);
expect(images[0]!.getAttribute('alt')).toBe('<b>Logo & name</b>');
});
@@ -0,0 +1,62 @@
import { expect, test } from "bun:test";
import { InspectorDrafts, type InspectorValues } from "../src/inspector-drafts.ts";
const node = { id: "text", props: { text: "Saved" }, styles: {} };
const baseline: InspectorValues = {
props: { text: "Saved", decorative: false },
styles: { fontSize: { value: "", empty: "true" } },
};
const edited: InspectorValues = {
props: { text: "Typing", decorative: true },
styles: { fontSize: { value: "20px", empty: "" } },
};
test("selection and preview remount preserve independent props, checkbox, and style drafts", () => {
const store = new InspectorDrafts();
store.capture(node, baseline, edited);
expect(store.restore({ ...node, id: "other" }, baseline)).toEqual(baseline);
expect(store.restore(node, baseline)).toEqual(edited);
expect(store.restore(node, baseline)).toEqual(edited);
expect(node.props.text).toBe("Saved");
expect(store.size).toBe(1);
});
test("applying content does not discard pending style changes or resurrect applied content", () => {
const store = new InspectorDrafts();
store.capture(node, baseline, edited);
const applied = { ...node, props: { text: "Typing" } };
const next = { ...baseline, props: { ...baseline.props, text: "Typing" } };
expect(store.restore(applied, next)).toEqual({ ...next, styles: edited.styles });
expect(store.restore(node, baseline).props).toEqual(baseline.props);
});
test("applying styles does not erase content drafts", () => {
const store = new InspectorDrafts();
store.capture(node, baseline, edited);
const applied = { ...node, styles: { fontSize: { base: "20px" } } };
const next = { ...baseline, styles: edited.styles };
expect(store.restore(applied, next)).toEqual({ props: edited.props, styles: edited.styles });
});
test("reverting fields, discarding, and deleting blocks release their drafts", () => {
const store = new InspectorDrafts();
store.capture(node, baseline, edited);
store.capture(node, baseline, baseline);
expect(store.size).toBe(0);
store.capture(node, baseline, edited);
store.discard(node.id);
expect(store.restore(node, baseline)).toEqual(baseline);
store.capture(node, baseline, edited);
store.retain(new Set(["other"]));
expect(store.size).toBe(0);
});
test("capture and restore do not share mutable control snapshots", () => {
const store = new InspectorDrafts(),
draft = structuredClone(edited);
store.capture(node, baseline, draft);
draft.styles.fontSize!.value = "99px";
const restored = store.restore(node, baseline);
restored.styles.fontSize!.value = "88px";
expect(store.restore(node, baseline).styles.fontSize!.value).toBe("20px");
});
+31
View File
@@ -0,0 +1,31 @@
import { expect, test } from "bun:test";
import { defineBlock, object, string } from "@wrnexus/blocks";
import { createBuilderKit } from "../src/kit.ts";
const block = defineBlock({
manifest: { type: "custom.note", version: 1, name: { key: "note", fallback: "Note" }, icon: "note", category: "content",
props: object({ text: string({ min: 1 }) }), styles: object({}), slots: {} },
create: ({ id }: { id(): string }) => ({ id: id(), type: "custom.note", version: 1, props: { text: "Hello" }, styles: {}, slots: {} }),
});
test("trusted custom blocks and presets expose independent instances and explicit metadata", () => {
let next = 0; const context = { id: () => String(++next) };
const kit = createBuilderKit({ blocks: [block], presets: [{ id: "note", name: { key: "preset.note", fallback: "Note" }, icon: "note", create: block.create }] });
expect(kit.blocks()[0].manifest.name.fallback).toBe("Note");
expect(kit.presets()[0].id).toBe("note");
const first = kit.createBlock("custom.note", context), second = kit.createPreset("note", context);
first.props.text = "Changed";
expect(second.props.text).toBe("Hello");
expect(first.id).not.toBe(second.id);
expect(Object.isFrozen(kit)).toBe(true);
expect(() => kit.validateProps("custom.note", { text: "" })).toThrow();
expect(() => kit.createBlock("missing.block", context)).toThrow();
});
test("duplicate registration and invalid factories fail before insertion", () => {
expect(() => createBuilderKit({ blocks: [block, block] })).toThrow();
const preset = { id: "note", name: { key: "note", fallback: "Note" }, icon: "note", create: block.create };
expect(() => createBuilderKit({ blocks: [block], presets: [preset, preset] })).toThrow();
const invalid = createBuilderKit({ blocks: [{ ...block, create: () => ({ ...block.create({ id: () => "id" }), props: { text: "" } }) }] });
expect(() => invalid.createBlock("custom.note", { id: () => "id" })).toThrow();
const wrong = createBuilderKit({ blocks: [{ ...block, create: () => ({ ...block.create({ id: () => "id" }), type: "custom.other" }) }] });
expect(() => wrong.createBlock("custom.note", { id: () => "id" })).toThrow();
});
@@ -0,0 +1,20 @@
import { expect, test } from "bun:test";
import type { BlockNode } from "@wrnexus/blocks";
import { builderOutlineLabel } from "../src/outline.ts";
const node = (props: BlockNode["props"], type = "content.button"): BlockNode => ({ id: "n", type, version: 1, props, styles: {}, slots: {} });
test("outline uses registered names and distinguishes content without mutating it", () => {
const block = node({ label: " Buy\n now " });
expect(builderOutlineLabel(block, { name: { fallback: "Button" } })).toBe("Button — Buy now");
expect(block.props.label).toBe(" Buy\n now ");
expect(builderOutlineLabel(node({ width: "35%" }, "layout.column"), { name: { fallback: "Column" } })).toBe("Column — 35%");
expect(builderOutlineLabel(node({}, "custom.unknown"))).toBe("custom.unknown");
});
test("outline bounds Unicode snippets and treats content as plain text", () => {
const label = builderOutlineLabel(node({ label: "😀".repeat(65) }), { name: { fallback: "Button" } });
expect(label).toBe("Button — " + "😀".repeat(64) + "…");
expect(builderOutlineLabel(node({ label: '<img onerror="bad">' }))).toContain('<img onerror="bad">');
expect(builderOutlineLabel(node({ text: {}, label: "", title: "Product title" }))).toBe("content.button — Product title");
});