import { describe, expect, expectTypeOf, it } from "vitest"; import { customBlock, type CustomBlockOptions } from "./custom-block.js"; import { notion, type DatabaseHandle } from "./notion-as-code/index.js"; import { finishMetadataRecording, startMetadataRecording, withMetadataCapabilityKey, } from "./notion-as-code/recorder.js"; import { toCustomBlockPropertyType } from "./translate.js"; describe("customBlock", () => { it("stamps the current capability key only during metadata recording", async () => { expect(customBlock({ path: "blocks/board" }).key).toBeUndefined(); const recorder = startMetadataRecording(); const block = await withMetadataCapabilityKey("board", async () => customBlock({ path: "blocks/board" }), ); expect(block.key).toBe("board"); finishMetadataRecording(recorder); }); it("supports static assets without a build command", () => { expect(customBlock({ path: "assets", static: true }).config.source).toEqual({ type: "static", path: "assets", }); }); it.each(["//hello", "Hello", "-hello", "", "a".repeat(65)])( "rejects invalid slash command %s", (slashCommand) => { expect(() => customBlock({ path: "blocks/hello", slashCommand })).toThrow( /slashCommand/, ); }, ); it("generates mappings and default bindings from a single-source database", () => { const tasks = notion.database("tasks-db", { dataSourceResourceId: "tasks-source", name: "Tasks", description: "Work items shown on the board", schema: { Name: { resourceId: "task-name", type: "title" }, Notes: { resourceId: "task-notes", type: "text", description: "Extra context", }, Files: { resourceId: "task-files", type: "file" }, Due: { resourceId: "task-due", type: "date" }, }, }); expect( customBlock({ path: "./blocks/board", name: "Task board", description: "Shows tasks by status", dataSources: { tasks }, }).config, ).toEqual({ source: { type: "project", path: "./blocks/board", command: "npx vite build", output: "dist", }, manifest: { version: 1, name: "Task board", description: "Shows tasks by status", dataSources: { tasks: { name: "Tasks", description: "Work items shown on the board", properties: { Name: { name: "Name", type: "title" }, Notes: { name: "Notes", description: "Extra context", type: "rich_text", }, Files: { name: "Files", type: "files" }, Due: { name: "Due", type: "date" }, }, }, }, }, defaultBindings: { tasks: { dataSourceResourceId: "tasks-source", propertyResourceIdsByKey: { Name: "task-name", Notes: "task-notes", Files: "task-files", Due: "task-due", }, }, }, }); }); it("accepts a data-source handle alongside an inline mapping", () => { const tasks = notion.database("tasks-db", { name: "Project", datasources: { Tasks: { resourceId: "tasks-source", description: "Current tasks", schema: { Name: { resourceId: "task-name", type: "title" } }, }, }, }); const block = customBlock({ path: "./blocks/board", dataSources: { tasks: tasks.datasources.Tasks, people: { name: "People", properties: {} }, }, }); expect(block.config.manifest.dataSources).toEqual({ tasks: { name: "Tasks", description: "Current tasks", properties: { Name: { name: "Name", type: "title" } }, }, people: { name: "People", properties: {} }, }); expect(block.config.defaultBindings).toEqual({ tasks: { dataSourceResourceId: "tasks-source", propertyResourceIdsByKey: { Name: "task-name" }, }, }); }); it("rejects multi-source database handles in the dataSources type", () => { type DataSourceOption = NonNullable[string]; expectTypeOf().not.toExtend(); }); it.each([ ["title", "title"], ["text", "rich_text"], ["number", "number"], ["select", "select"], ["multi_select", "multi_select"], ["status", "status"], ["date", "date"], ["checkbox", "checkbox"], ["url", "url"], ["email", "email"], ["phone_number", "phone_number"], ["file", "files"], ["person", "people"], ["auto_increment_id", "unique_id"], ["relation", "relation"], ["formula", "formula"], ["rollup", "rollup"], ["created_time", "created_time"], ["last_edited_time", "last_edited_time"], ["created_by", "created_by"], ["last_edited_by", "last_edited_by"], ["verification", "verification"], ] as const)("maps Notion-as-Code %s properties to manifest %s properties", (input, output) => { expect(toCustomBlockPropertyType(input)).toBe(output); }); });