1import { describe, expect, it } from "vitest";
2
3import {
4 defaultViewConfig,
5 viewData,
6 validateButtonConfig,
7 resolveTemplate,
8 describeAction,
9 VIEW_TYPES,
10} from "./index.js";
11import type { RecordRow, PropertyInfo, PropertyValue } from "../values/types.js";
12
13const props: PropertyInfo[] = [
14 { id: "p-title", name: "Title", type: "title", config: {} },
15 {
16 id: "p-status",
17 name: "Status",
18 type: "select",
19 config: {
20 options: [
21 { id: "done", name: "Done", color: "green" },
22 { id: "todo", name: "To Do", color: "gray" },
23 ],
24 },
25 },
26 { id: "p-date", name: "Due", type: "date", config: {} },
27 { id: "p-num", name: "N", type: "number", config: {} },
28];
29
30const row = (id: string, values: Record<string, PropertyValue>): RecordRow => ({
31 id,
32 pageId: id,
33 position: `a${id}`,
34 createdTime: 1,
35 lastEditedTime: 1,
36 values,
37});
38
39describe("defaultViewConfig (TASK-0069 R5)", () => {
40 it("all 9 types produce stable defaults", () => {
41 for (const t of VIEW_TYPES) {
42 const c = defaultViewConfig(t, { properties: props });
43 expect(c["visiblePropertyIds"]).toEqual(["p-title", "p-status", "p-date", "p-num"]);
44 expect(typeof c["pageSize"]).toBe("number");
45 }
46 });
47
48 it("board defaults group by the first STATUS/SELECT", () => {
49 const c = defaultViewConfig("board", { properties: props }) as {
50 group: Array<{ propertyId: string }>;
51 };
52 expect(c.group).toEqual([{ propertyId: "p-status", direction: "asc" }]);
53 });
54
55 it("calendar defaults to the first DATE/DATE_RANGE; empty group when absent", () => {
56 const c = defaultViewConfig("calendar", { properties: props }) as {
57 group: Array<{ propertyId: string }>;
58 };
59 expect(c.group[0]!.propertyId).toBe("p-date");
60 const none = defaultViewConfig("calendar", { properties: [props[0]!, props[3]!] }) as {
61 group: unknown[];
62 };
63 expect(none.group).toEqual([]);
64 });
65});
66
67describe("viewData — the 9 render resolvers", () => {
68 const rows = [
69 row("r1", { "p-status": "todo", "p-date": "2025-06-01" }),
70 row("r2", { "p-status": "done", "p-date": "2025-06-02" }),
71 ];
72 it("board groups by option order with (Empty) last", () => {
73
74 const v = viewData("board", rows, { group: [{ propertyId: "p-status" }] }, props);
75 expect(v.board!.groups.map((g) => g.key)).toEqual(["done", "todo"]);
76 });
77
78 it("calendar extracts events with dates", () => {
79 const v = viewData("calendar", rows, { group: [{ propertyId: "p-date" }] }, props);
80 expect(v.calendar!.events.map((e) => e.date)).toEqual(["2025-06-01", "2025-06-02"]);
81 });
82
83 it("chart/dashboard pass through their ids", () => {
84 expect(viewData("chart", [], { chartId: "ch1" }, props).chart!.chartId).toBe("ch1");
85 expect(viewData("dashboard", [], { dashboardId: "dash1" }, props).dashboard!.dashboardId).toBe(
86 "dash1",
87 );
88 });
89
90 it("form is disabled (Phase 10)", () => {
91 const v = viewData("form", [], {}, props);
92 expect(v.form!.disabled).toBe(true);
93 expect(v.form!.reason).toBe("form_view_phase_10");
94 });
95});
96
97describe("buttons (TASK-0087 pure half)", () => {
98 it("validates config: name 1–50, 1–5 actions, per-action fields", () => {
99 expect(
100 validateButtonConfig({
101 name: "Run",
102 icon: "▶️",
103 actions: [{ type: "set_property", propertyId: "p", value: 1 }],
104 }),
105 ).toEqual([]);
106 expect(validateButtonConfig({ name: "", actions: [] })).toHaveLength(2);
107 expect(
108 validateButtonConfig({
109 name: "x",
110 actions: Array.from({ length: 6 }, () => ({ type: "set_property", propertyId: "p" })),
111 }),
112 ).toHaveLength(1);
113 const badType = validateButtonConfig({ name: "x", actions: [{ type: "explode" }] });
114 expect(badType[0]!.field).toBe("actions[0].type");
115 });
116
117 it("resolveTemplate expands {tokens} case-insensitively; unknown → ''", () => {
118 const out = resolveTemplate("Task: {title} due {Due}", "Ship it", {
119 due: { display: "Jun 15" },
120 });
121 expect(out).toBe("Task: Ship it due Jun 15");
122 expect(resolveTemplate("{Nope}", "t", {})).toBe("");
123 });
124
125 it("describeAction renders the confirm-popover lines", () => {
126 const names = new Map([["p", "Status"]]);
127 expect(describeAction({ type: "set_property", propertyId: "p", value: "x" }, names)).toBe(
128 "Set Status",
129 );
130 expect(describeAction({ type: "set_property", propertyId: "p", setTo: "empty" }, names)).toBe(
131 "Clear Status",
132 );
133 expect(describeAction({ type: "create_page", databaseId: "db" }, names)).toBe(
134 "Create a record",
135 );
136 expect(describeAction({ type: "archive_page", target: "self" }, names)).toBe(
137 "Archive this record",
138 );
139 expect(describeAction({ type: "send_webhook", webhookId: "w" }, names)).toBe("Send webhook");
140 });
141});
142