203 lines
5.0 KiB
TypeScript
203 lines
5.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
DEFAULT_CODE_BLOCK_SHOW_BACKGROUND,
|
|
EMPTY_CODE_BLOCK_SELECTION_STATE,
|
|
createCodeBlockCustomData,
|
|
getCodeBlockSelectionState,
|
|
getCodeBlockCustomData,
|
|
isCodeBlockEmbeddable,
|
|
sanitizeCodeBlockLanguage,
|
|
updateCodeBlockElements,
|
|
} from "./codeblock";
|
|
|
|
const baseEmbeddable = {
|
|
id: "codeblock-1",
|
|
type: "embeddable" as const,
|
|
x: 10,
|
|
y: 20,
|
|
strokeColor: "transparent",
|
|
backgroundColor: "transparent",
|
|
fillStyle: "solid" as const,
|
|
strokeWidth: 1,
|
|
strokeStyle: "solid" as const,
|
|
roundness: null,
|
|
roughness: 0,
|
|
opacity: 100,
|
|
width: 420,
|
|
height: 260,
|
|
angle: 0,
|
|
seed: 1,
|
|
version: 1,
|
|
versionNonce: 2,
|
|
index: null,
|
|
isDeleted: false,
|
|
groupIds: [],
|
|
frameId: null,
|
|
boundElements: null,
|
|
updated: 1,
|
|
link: null,
|
|
locked: false,
|
|
};
|
|
|
|
describe("codeblock helpers", () => {
|
|
test("type guard accepts only repo-owned codeblock embeddables", () => {
|
|
expect(
|
|
isCodeBlockEmbeddable({
|
|
...baseEmbeddable,
|
|
customData: createCodeBlockCustomData(),
|
|
}),
|
|
).toBe(true);
|
|
|
|
expect(
|
|
isCodeBlockEmbeddable({
|
|
...baseEmbeddable,
|
|
customData: {
|
|
excaliType: "other",
|
|
version: 2,
|
|
code: "",
|
|
language: "tsx",
|
|
showBackground: true,
|
|
},
|
|
}),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
isCodeBlockEmbeddable({
|
|
...baseEmbeddable,
|
|
link: "https://example.com",
|
|
customData: createCodeBlockCustomData(),
|
|
}),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
isCodeBlockEmbeddable({
|
|
...baseEmbeddable,
|
|
type: "iframe",
|
|
customData: createCodeBlockCustomData(),
|
|
}),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
isCodeBlockEmbeddable({
|
|
...baseEmbeddable,
|
|
customData: {
|
|
excaliType: "codeblock",
|
|
version: 1,
|
|
code: "",
|
|
language: "tsx",
|
|
showBackground: true,
|
|
},
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
test("create helper defaults background to enabled", () => {
|
|
expect(createCodeBlockCustomData().showBackground).toBe(
|
|
DEFAULT_CODE_BLOCK_SHOW_BACKGROUND,
|
|
);
|
|
});
|
|
|
|
test("update helper preserves unrelated fields and non-codeblock embeddables", () => {
|
|
const codeBlock = {
|
|
...baseEmbeddable,
|
|
customData: createCodeBlockCustomData({ code: "const a = 1;" }),
|
|
};
|
|
const foreignEmbeddable = {
|
|
...baseEmbeddable,
|
|
id: "embed-2",
|
|
customData: { source: "foreign" },
|
|
};
|
|
const elements = [codeBlock, foreignEmbeddable];
|
|
|
|
const nextElements = updateCodeBlockElements(elements, "codeblock-1", {
|
|
code: "const a = 2;",
|
|
language: "ts",
|
|
showBackground: false,
|
|
});
|
|
|
|
expect(nextElements).not.toBe(elements);
|
|
expect(nextElements[0]).not.toBe(codeBlock);
|
|
expect(nextElements[0]).toMatchObject({
|
|
id: "codeblock-1",
|
|
x: 10,
|
|
y: 20,
|
|
width: 420,
|
|
customData: {
|
|
excaliType: "codeblock",
|
|
version: 2,
|
|
code: "const a = 2;",
|
|
language: "typescript",
|
|
showBackground: false,
|
|
},
|
|
});
|
|
expect(nextElements[1]).toBe(foreignEmbeddable);
|
|
expect(
|
|
updateCodeBlockElements(elements, "embed-2", { code: "ignored" }),
|
|
).toBe(elements);
|
|
});
|
|
|
|
test("selection helper opens only for a single selected codeblock", () => {
|
|
const codeBlock = {
|
|
...baseEmbeddable,
|
|
customData: createCodeBlockCustomData(),
|
|
};
|
|
const other = {
|
|
...baseEmbeddable,
|
|
id: "shape-2",
|
|
type: "rectangle" as const,
|
|
};
|
|
|
|
expect(
|
|
getCodeBlockSelectionState([codeBlock, other], {
|
|
selectedElementIds: { "codeblock-1": true },
|
|
}),
|
|
).toMatchObject({
|
|
isPanelOpen: true,
|
|
selectedCodeBlockId: "codeblock-1",
|
|
selectedCodeBlock: {
|
|
customData: {
|
|
showBackground: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(
|
|
getCodeBlockSelectionState([codeBlock, other], {
|
|
selectedElementIds: { "codeblock-1": true, "shape-2": true },
|
|
}),
|
|
).toEqual(EMPTY_CODE_BLOCK_SELECTION_STATE);
|
|
|
|
expect(
|
|
getCodeBlockSelectionState([codeBlock, other], {
|
|
selectedElementIds: { "shape-2": true },
|
|
}),
|
|
).toEqual(EMPTY_CODE_BLOCK_SELECTION_STATE);
|
|
});
|
|
|
|
test("language sanitization accepts shiki aliases and normalizes them", () => {
|
|
expect(sanitizeCodeBlockLanguage("ts")).toBe("typescript");
|
|
expect(sanitizeCodeBlockLanguage("js")).toBe("javascript");
|
|
expect(sanitizeCodeBlockLanguage("md")).toBe("markdown");
|
|
expect(sanitizeCodeBlockLanguage("bash")).toBe("shellscript");
|
|
expect(sanitizeCodeBlockLanguage("plaintext")).toBe("plaintext");
|
|
});
|
|
|
|
test("custom data reads normalize persisted shiki aliases", () => {
|
|
const codeBlock = {
|
|
...baseEmbeddable,
|
|
customData: {
|
|
excaliType: "codeblock" as const,
|
|
version: 2 as const,
|
|
code: "echo hi",
|
|
language: "bash" as const,
|
|
showBackground: true,
|
|
},
|
|
};
|
|
|
|
expect(getCodeBlockCustomData(codeBlock)).toMatchObject({
|
|
code: "echo hi",
|
|
language: "shellscript",
|
|
});
|
|
});
|
|
});
|