@runflow-ai/sdk 1.1.11 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Snapshot tests for `normalizeCardConfig`. Each pair (input → canonical
3
+ * output) is one historical legacy shape we've seen in the wild plus an
4
+ * already-canonical case to confirm idempotency.
5
+ */
6
+ export {};
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Snapshot tests for `normalizeCardConfig`. Each pair (input → canonical
4
+ * output) is one historical legacy shape we've seen in the wild plus an
5
+ * already-canonical case to confirm idempotency.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const dashboard_cards_normalize_1 = require("../dashboard-cards.normalize");
9
+ describe("normalizeCardConfig", () => {
10
+ describe("table", () => {
11
+ test("tableMode → mode", () => {
12
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("table", {
13
+ eventName: "sale",
14
+ tableMode: "raw",
15
+ columns: ["a", "b"],
16
+ });
17
+ expect(r.config).toMatchObject({ mode: "raw", columns: ["a", "b"] });
18
+ expect(r.config.tableMode).toBeUndefined();
19
+ expect(r.aliasesApplied).toContain("table.tableMode->mode");
20
+ });
21
+ test("already-canonical config is untouched", () => {
22
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("table", {
23
+ mode: "aggregate",
24
+ groupByKey: "status",
25
+ });
26
+ expect(r.config).toEqual({ mode: "aggregate", groupByKey: "status" });
27
+ expect(r.aliasesApplied).toEqual([]);
28
+ });
29
+ });
30
+ describe("funnel", () => {
31
+ test("funnelSteps → steps + defaults funnelMode to multi_event", () => {
32
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("funnel", {
33
+ funnelSteps: [
34
+ { eventName: "a", label: "A" },
35
+ { eventName: "b", label: "B" },
36
+ ],
37
+ });
38
+ expect(r.config).toMatchObject({
39
+ funnelMode: "multi_event",
40
+ steps: [
41
+ { eventName: "a", label: "A" },
42
+ { eventName: "b", label: "B" },
43
+ ],
44
+ });
45
+ expect(r.config.funnelSteps).toBeUndefined();
46
+ expect(r.aliasesApplied).toContain("funnel.funnelSteps->steps");
47
+ expect(r.aliasesApplied).toContain("funnel.missingFunnelMode->multi_event");
48
+ });
49
+ test("stepEvents → steps (older builds)", () => {
50
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("funnel", {
51
+ funnelMode: "multi_event",
52
+ stepEvents: [{ eventName: "x" }],
53
+ });
54
+ expect(r.config.steps).toEqual([{ eventName: "x" }]);
55
+ expect(r.aliasesApplied).toContain("funnel.stepEvents->steps");
56
+ });
57
+ test("single_event funnel passes through", () => {
58
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("funnel", {
59
+ funnelMode: "single_event",
60
+ eventName: "ticket",
61
+ propertyKey: "status",
62
+ stepValues: [{ value: "open" }, { value: "closed" }],
63
+ });
64
+ expect(r.config).toMatchObject({
65
+ funnelMode: "single_event",
66
+ eventName: "ticket",
67
+ propertyKey: "status",
68
+ });
69
+ expect(r.aliasesApplied).toEqual([]);
70
+ });
71
+ });
72
+ describe("gauge", () => {
73
+ test("thresholds [v1,v2] array → object", () => {
74
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("gauge", {
75
+ min: 0,
76
+ max: 100,
77
+ thresholds: [30, 70],
78
+ });
79
+ expect(r.config.thresholds).toEqual({ v1: 30, v2: 70 });
80
+ expect(r.aliasesApplied).toContain("gauge.thresholdsArray->object");
81
+ });
82
+ test("zoneColors [low,mid,high] array → object", () => {
83
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("gauge", {
84
+ min: 0,
85
+ max: 10,
86
+ thresholds: { v1: 3, v2: 7 },
87
+ zoneColors: ["#f00", "#ff0", "#0f0"],
88
+ });
89
+ expect(r.config.zoneColors).toEqual({
90
+ low: "#f00",
91
+ mid: "#ff0",
92
+ high: "#0f0",
93
+ });
94
+ expect(r.aliasesApplied).toContain("gauge.zoneColorsArray->object");
95
+ });
96
+ test("legacy `colors` field → zoneColors", () => {
97
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("gauge", {
98
+ min: 0,
99
+ max: 10,
100
+ thresholds: { v1: 3, v2: 7 },
101
+ colors: ["red", "yellow", "green"],
102
+ });
103
+ expect(r.config.zoneColors).toEqual({
104
+ low: "red",
105
+ mid: "yellow",
106
+ high: "green",
107
+ });
108
+ expect(r.config.colors).toBeUndefined();
109
+ expect(r.aliasesApplied).toContain("gauge.colorsArray->zoneColors");
110
+ });
111
+ test("range {from,to} → min/max", () => {
112
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("gauge", {
113
+ thresholds: { v1: 1, v2: 2 },
114
+ range: { from: 0, to: 5 },
115
+ });
116
+ expect(r.config.min).toBe(0);
117
+ expect(r.config.max).toBe(5);
118
+ expect(r.config.range).toBeUndefined();
119
+ expect(r.aliasesApplied).toContain("gauge.range->minMax");
120
+ });
121
+ });
122
+ describe("idempotency", () => {
123
+ test.each(["number", "rate", "line", "bar", "pie", "table", "funnel", "gauge"])("normalize is idempotent for %s", (cardType) => {
124
+ // Start with a legacy-ish input
125
+ const input = cardType === "table"
126
+ ? { tableMode: "raw" }
127
+ : cardType === "funnel"
128
+ ? { funnelSteps: [{ eventName: "a" }, { eventName: "b" }] }
129
+ : cardType === "gauge"
130
+ ? { min: 0, max: 10, thresholds: [3, 7] }
131
+ : { eventName: "x", aggregation: "count" };
132
+ const r1 = (0, dashboard_cards_normalize_1.normalizeCardConfig)(cardType, input);
133
+ const r2 = (0, dashboard_cards_normalize_1.normalizeCardConfig)(cardType, r1.config);
134
+ expect(r2.config).toEqual(r1.config);
135
+ // Second pass detects 0 aliases (already canonical)
136
+ expect(r2.aliasesApplied).toEqual([]);
137
+ });
138
+ });
139
+ describe("non-objects", () => {
140
+ test("null input → empty config", () => {
141
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("number", null);
142
+ expect(r.config).toEqual({});
143
+ });
144
+ test("undefined input → empty config", () => {
145
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("number", undefined);
146
+ expect(r.config).toEqual({});
147
+ });
148
+ test("primitive input → empty config", () => {
149
+ const r = (0, dashboard_cards_normalize_1.normalizeCardConfig)("number", "garbage");
150
+ expect(r.config).toEqual({});
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for the grid-layout constraint helper and the zod schemas
3
+ * (discriminated union acceptance + per-card-type field validation).
4
+ */
5
+ export {};
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for the grid-layout constraint helper and the zod schemas
4
+ * (discriminated union acceptance + per-card-type field validation).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const dashboard_cards_1 = require("../dashboard-cards");
8
+ const dashboard_cards_zod_1 = require("../dashboard-cards.zod");
9
+ describe("GRID_CONSTRAINTS + isValidGridLayout", () => {
10
+ test("every card type has constraints", () => {
11
+ for (const t of dashboard_cards_1.ALL_CARD_TYPES) {
12
+ expect(dashboard_cards_1.GRID_CONSTRAINTS[t]).toBeDefined();
13
+ expect(dashboard_cards_1.GRID_CONSTRAINTS[t].minW).toBeGreaterThan(0);
14
+ expect(dashboard_cards_1.GRID_CONSTRAINTS[t].maxW).toBeGreaterThanOrEqual(dashboard_cards_1.GRID_CONSTRAINTS[t].minW);
15
+ }
16
+ });
17
+ test("minimum-sized layout passes for every type", () => {
18
+ for (const t of dashboard_cards_1.ALL_CARD_TYPES) {
19
+ const c = dashboard_cards_1.GRID_CONSTRAINTS[t];
20
+ const res = (0, dashboard_cards_1.isValidGridLayout)(t, { x: 0, y: 0, w: c.minW, h: c.minH });
21
+ expect(res.ok).toBe(true);
22
+ }
23
+ });
24
+ test("layout below minW is rejected with a useful reason", () => {
25
+ const res = (0, dashboard_cards_1.isValidGridLayout)("table", { x: 0, y: 0, w: 1, h: 5 });
26
+ expect(res.ok).toBe(false);
27
+ if (res.ok === false) {
28
+ expect(res.reason).toMatch(/w must be >= 4 for table/);
29
+ }
30
+ });
31
+ test("layout above maxW is rejected", () => {
32
+ const res = (0, dashboard_cards_1.isValidGridLayout)("number", { x: 0, y: 0, w: 99, h: 3 });
33
+ expect(res.ok).toBe(false);
34
+ if (res.ok === false) {
35
+ expect(res.reason).toMatch(/w must be <= 6 for number/);
36
+ }
37
+ });
38
+ test("negative x/y is rejected", () => {
39
+ const res = (0, dashboard_cards_1.isValidGridLayout)("number", { x: -1, y: 0, w: 3, h: 3 });
40
+ expect(res.ok).toBe(false);
41
+ });
42
+ test("unknown card type is rejected", () => {
43
+ const res = (0, dashboard_cards_1.isValidGridLayout)("bogus", {
44
+ x: 0,
45
+ y: 0,
46
+ w: 3,
47
+ h: 3,
48
+ });
49
+ expect(res.ok).toBe(false);
50
+ });
51
+ });
52
+ describe("GridLayoutItemSchema", () => {
53
+ test("accepts a valid 4-field object", () => {
54
+ expect(dashboard_cards_zod_1.GridLayoutItemSchema.safeParse({ x: 0, y: 0, w: 4, h: 3 }).success).toBe(true);
55
+ });
56
+ test("rejects missing fields", () => {
57
+ expect(dashboard_cards_zod_1.GridLayoutItemSchema.safeParse({ x: 0, y: 0, w: 4 }).success).toBe(false);
58
+ });
59
+ test("rejects extra fields (.strict())", () => {
60
+ expect(dashboard_cards_zod_1.GridLayoutItemSchema.safeParse({ x: 0, y: 0, w: 4, h: 3, span: 6 })
61
+ .success).toBe(false);
62
+ });
63
+ test("rejects non-integer values", () => {
64
+ expect(dashboard_cards_zod_1.GridLayoutItemSchema.safeParse({ x: 0, y: 0, w: 4.5, h: 3 }).success).toBe(false);
65
+ });
66
+ });
67
+ describe("DashboardCardInputSchema — happy paths per card type", () => {
68
+ test("number card with minimal config", () => {
69
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
70
+ cardType: "number",
71
+ title: "Vendas",
72
+ config: { eventName: "sale", aggregation: "count" },
73
+ });
74
+ expect(r.success).toBe(true);
75
+ });
76
+ test("table card aggregate mode", () => {
77
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
78
+ cardType: "table",
79
+ title: "Status",
80
+ config: {
81
+ mode: "aggregate",
82
+ groupByKey: "status",
83
+ metrics: [{ aggregation: "count" }],
84
+ },
85
+ });
86
+ expect(r.success).toBe(true);
87
+ });
88
+ test("funnel multi_event with 3 steps", () => {
89
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
90
+ cardType: "funnel",
91
+ title: "Checkout",
92
+ config: {
93
+ funnelMode: "multi_event",
94
+ steps: [
95
+ { eventName: "open" },
96
+ { eventName: "started" },
97
+ { eventName: "paid" },
98
+ ],
99
+ },
100
+ });
101
+ expect(r.success).toBe(true);
102
+ });
103
+ test("gauge with thresholds object", () => {
104
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
105
+ cardType: "gauge",
106
+ title: "CSAT",
107
+ config: {
108
+ eventName: "csat",
109
+ aggregation: "avg",
110
+ propertyKey: "score",
111
+ min: 0,
112
+ max: 10,
113
+ thresholds: { v1: 3, v2: 7 },
114
+ },
115
+ });
116
+ expect(r.success).toBe(true);
117
+ });
118
+ });
119
+ describe("DashboardCardInputSchema — rejections", () => {
120
+ test("funnel multi_event requires >= 2 steps", () => {
121
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
122
+ cardType: "funnel",
123
+ title: "x",
124
+ config: {
125
+ funnelMode: "multi_event",
126
+ steps: [{ eventName: "only-one" }],
127
+ },
128
+ });
129
+ expect(r.success).toBe(false);
130
+ if (!r.success) {
131
+ expect(r.error.issues.some((i) => i.message.includes("requires at least 2 steps"))).toBe(true);
132
+ }
133
+ });
134
+ test("gauge.min >= gauge.max is rejected", () => {
135
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
136
+ cardType: "gauge",
137
+ title: "x",
138
+ config: {
139
+ min: 10,
140
+ max: 0,
141
+ thresholds: { v1: 1, v2: 5 },
142
+ },
143
+ });
144
+ expect(r.success).toBe(false);
145
+ if (!r.success) {
146
+ expect(r.error.issues.some((i) => i.message.includes("must be strictly less than gauge.max"))).toBe(true);
147
+ }
148
+ });
149
+ test("gauge.thresholds.v1 > v2 is rejected", () => {
150
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
151
+ cardType: "gauge",
152
+ title: "x",
153
+ config: {
154
+ min: 0,
155
+ max: 100,
156
+ thresholds: { v1: 70, v2: 30 },
157
+ },
158
+ });
159
+ expect(r.success).toBe(false);
160
+ });
161
+ test("funnel single_event requires eventName + propertyKey + stepValues", () => {
162
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
163
+ cardType: "funnel",
164
+ title: "x",
165
+ config: { funnelMode: "single_event" },
166
+ });
167
+ expect(r.success).toBe(false);
168
+ if (!r.success) {
169
+ const messages = r.error.issues.map((i) => i.message);
170
+ expect(messages.some((m) => m.includes("requires eventName"))).toBe(true);
171
+ expect(messages.some((m) => m.includes("requires propertyKey"))).toBe(true);
172
+ }
173
+ });
174
+ test("unknown cardType is rejected by the discriminator", () => {
175
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
176
+ cardType: "bogus",
177
+ title: "x",
178
+ config: {},
179
+ });
180
+ expect(r.success).toBe(false);
181
+ });
182
+ test("gridLayout that violates cardType constraints is rejected", () => {
183
+ const r = dashboard_cards_zod_1.DashboardCardInputSchema.safeParse({
184
+ cardType: "number",
185
+ title: "small",
186
+ config: { eventName: "x", aggregation: "count" },
187
+ // number requires minW=2 minH=2, maxW=6 maxH=4 — this one is 8 wide
188
+ gridLayout: { x: 0, y: 0, w: 8, h: 3 },
189
+ });
190
+ expect(r.success).toBe(false);
191
+ if (!r.success) {
192
+ expect(r.error.issues.some((i) => i.message.includes("w must be <= 6"))).toBe(true);
193
+ }
194
+ });
195
+ });