@checkstack/ui 0.5.2 → 1.0.0

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/package.json +8 -15
  3. package/src/components/CodeEditor/CodeEditor.tsx +14 -420
  4. package/src/components/CodeEditor/MonacoEditor.tsx +530 -0
  5. package/src/components/CodeEditor/generateTypeDefinitions.ts +169 -0
  6. package/src/components/CodeEditor/index.ts +4 -3
  7. package/src/components/CodeEditor/templateUtils.test.ts +87 -0
  8. package/src/components/CodeEditor/templateUtils.ts +81 -0
  9. package/src/components/DynamicForm/FormField.tsx +13 -7
  10. package/src/components/DynamicForm/MultiTypeEditorField.tsx +33 -0
  11. package/src/components/DynamicForm/utils.ts +3 -0
  12. package/src/components/StatusUpdateTimeline.tsx +6 -6
  13. package/src/components/Tabs.tsx +1 -0
  14. package/src/components/CodeEditor/languageSupport/enterBehavior.test.ts +0 -173
  15. package/src/components/CodeEditor/languageSupport/enterBehavior.ts +0 -35
  16. package/src/components/CodeEditor/languageSupport/index.ts +0 -22
  17. package/src/components/CodeEditor/languageSupport/json-utils.ts +0 -117
  18. package/src/components/CodeEditor/languageSupport/json.test.ts +0 -274
  19. package/src/components/CodeEditor/languageSupport/json.ts +0 -139
  20. package/src/components/CodeEditor/languageSupport/markdown-utils.ts +0 -65
  21. package/src/components/CodeEditor/languageSupport/markdown.test.ts +0 -245
  22. package/src/components/CodeEditor/languageSupport/markdown.ts +0 -134
  23. package/src/components/CodeEditor/languageSupport/types.ts +0 -48
  24. package/src/components/CodeEditor/languageSupport/xml-utils.ts +0 -94
  25. package/src/components/CodeEditor/languageSupport/xml.test.ts +0 -239
  26. package/src/components/CodeEditor/languageSupport/xml.ts +0 -116
  27. package/src/components/CodeEditor/languageSupport/yaml-utils.ts +0 -101
  28. package/src/components/CodeEditor/languageSupport/yaml.test.ts +0 -203
  29. package/src/components/CodeEditor/languageSupport/yaml.ts +0 -120
@@ -1,239 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- isValidXmlTemplatePosition,
5
- calculateXmlIndentation,
6
- } from "./xml-utils";
7
-
8
- describe("isValidXmlTemplatePosition", () => {
9
- describe("text content positions", () => {
10
- it("returns true in text between tags", () => {
11
- expect(isValidXmlTemplatePosition("<tag>hello")).toBe(true);
12
- });
13
-
14
- it("returns true after opening tag on new line", () => {
15
- expect(isValidXmlTemplatePosition("<tag>\n ")).toBe(true);
16
- });
17
-
18
- it("returns true in text with existing template", () => {
19
- expect(isValidXmlTemplatePosition("<tag>hello {{name}}")).toBe(true);
20
- });
21
-
22
- it("returns true in empty text position", () => {
23
- expect(isValidXmlTemplatePosition("<tag>")).toBe(true);
24
- });
25
- });
26
-
27
- describe("attribute value positions", () => {
28
- it("returns true inside double-quoted attribute value", () => {
29
- expect(isValidXmlTemplatePosition('<tag attr="')).toBe(true);
30
- });
31
-
32
- it("returns true inside single-quoted attribute value", () => {
33
- expect(isValidXmlTemplatePosition("<tag attr='")).toBe(true);
34
- });
35
-
36
- it("returns true in middle of attribute value", () => {
37
- expect(isValidXmlTemplatePosition('<tag attr="hello')).toBe(true);
38
- });
39
-
40
- it("returns true in attribute with template", () => {
41
- expect(isValidXmlTemplatePosition('<tag attr="{{name}}')).toBe(true);
42
- });
43
- });
44
-
45
- describe("invalid positions", () => {
46
- it("returns false inside tag name", () => {
47
- expect(isValidXmlTemplatePosition("<ta")).toBe(false);
48
- });
49
-
50
- it("returns false for empty string", () => {
51
- expect(isValidXmlTemplatePosition("")).toBe(false);
52
- });
53
-
54
- it("returns false in attribute name position", () => {
55
- expect(isValidXmlTemplatePosition("<tag at")).toBe(false);
56
- });
57
-
58
- it("returns false right after opening bracket", () => {
59
- expect(isValidXmlTemplatePosition("<")).toBe(false);
60
- });
61
- });
62
-
63
- describe("complex nested structures", () => {
64
- it("returns true in nested tag text content", () => {
65
- expect(isValidXmlTemplatePosition("<root><child>text")).toBe(true);
66
- });
67
-
68
- it("returns true in deeply nested text", () => {
69
- expect(isValidXmlTemplatePosition("<a><b><c>")).toBe(true);
70
- });
71
-
72
- it("returns true after self-closing tag", () => {
73
- expect(isValidXmlTemplatePosition("<root><self/>text")).toBe(true);
74
- });
75
- });
76
- });
77
-
78
- describe("calculateXmlIndentation", () => {
79
- const INDENT = 2;
80
-
81
- // ============================================================================
82
- // Basic tag indentation
83
- // ============================================================================
84
- describe("basic tag indentation", () => {
85
- it("returns 0 for empty document", () => {
86
- expect(calculateXmlIndentation("", INDENT)).toBe(0);
87
- });
88
-
89
- it("indents after opening tag (new line)", () => {
90
- // User typed "<root>" and pressed Enter
91
- expect(calculateXmlIndentation("<root>\n", INDENT)).toBe(INDENT);
92
- });
93
-
94
- it("indents when inside an unclosed opening tag", () => {
95
- // After an opening tag, depth is 1 even on same line
96
- expect(calculateXmlIndentation("<root>", INDENT)).toBe(INDENT);
97
- });
98
-
99
- it("returns 0 after self-closing tag", () => {
100
- expect(calculateXmlIndentation("<self/>\n", INDENT)).toBe(0);
101
- });
102
-
103
- it("returns 0 after closing tag", () => {
104
- expect(calculateXmlIndentation("</root>\n", INDENT)).toBe(0);
105
- });
106
-
107
- it("returns 0 after complete open-close on same line", () => {
108
- expect(calculateXmlIndentation("<tag></tag>\n", INDENT)).toBe(0);
109
- });
110
- });
111
-
112
- // ============================================================================
113
- // Nested structure indentation
114
- // ============================================================================
115
- describe("nested structure indentation", () => {
116
- it("indents to level 2 after nested opening tag", () => {
117
- const text = `<root>
118
- <child>\n`;
119
- expect(calculateXmlIndentation(text, INDENT)).toBe(2 * INDENT);
120
- });
121
-
122
- it("maintains level 1 after nested closing tag", () => {
123
- const text = `<root>
124
- <child></child>\n`;
125
- expect(calculateXmlIndentation(text, INDENT)).toBe(INDENT);
126
- });
127
-
128
- it("indents to level 3 for deep nesting", () => {
129
- const text = `<root>
130
- <a>
131
- <b>\n`;
132
- expect(calculateXmlIndentation(text, INDENT)).toBe(3 * INDENT);
133
- });
134
-
135
- it("handles mixed self-closing and regular tags", () => {
136
- const text = `<root>
137
- <self/>
138
- <child>\n`;
139
- expect(calculateXmlIndentation(text, INDENT)).toBe(2 * INDENT);
140
- });
141
- });
142
-
143
- // ============================================================================
144
- // Template handling (no interference with structure)
145
- // ============================================================================
146
- describe("template handling", () => {
147
- it("counts depth correctly with template in text content", () => {
148
- // Template doesn't affect tag depth counting
149
- expect(calculateXmlIndentation("<tag>{{value}}", INDENT)).toBe(INDENT);
150
- });
151
-
152
- it("indents after tag even with template content", () => {
153
- expect(calculateXmlIndentation("<tag>\n {{value}}\n", INDENT)).toBe(
154
- INDENT,
155
- );
156
- });
157
-
158
- it("handles template in attribute value", () => {
159
- expect(calculateXmlIndentation('<tag attr="{{value}}">\n', INDENT)).toBe(
160
- INDENT,
161
- );
162
- });
163
-
164
- it("handles multiple templates", () => {
165
- const text = `<root>
166
- <a>{{val1}}</a>
167
- <b>{{val2}}</b>\n`;
168
- expect(calculateXmlIndentation(text, INDENT)).toBe(INDENT);
169
- });
170
- });
171
-
172
- // ============================================================================
173
- // Edge cases
174
- // ============================================================================
175
- describe("edge cases", () => {
176
- it("handles only newline", () => {
177
- expect(calculateXmlIndentation("\n", INDENT)).toBe(0);
178
- });
179
-
180
- it("handles multiple newlines", () => {
181
- expect(calculateXmlIndentation("\n\n\n", INDENT)).toBe(0);
182
- });
183
-
184
- it("handles XML declaration", () => {
185
- expect(calculateXmlIndentation('<?xml version="1.0"?>\n', INDENT)).toBe(
186
- 0,
187
- );
188
- });
189
-
190
- it("handles tag with attributes", () => {
191
- expect(calculateXmlIndentation('<tag attr="value">\n', INDENT)).toBe(
192
- INDENT,
193
- );
194
- });
195
-
196
- it("handles tag with multiple attributes", () => {
197
- expect(calculateXmlIndentation('<tag a="1" b="2" c="3">\n', INDENT)).toBe(
198
- INDENT,
199
- );
200
- });
201
-
202
- it("handles CDATA section (counts parent tag)", () => {
203
- // CDATA is inside an unclosed tag
204
- expect(calculateXmlIndentation("<tag><![CDATA[", INDENT)).toBe(INDENT);
205
- });
206
-
207
- it("handles comments", () => {
208
- expect(calculateXmlIndentation("<!-- comment -->\n", INDENT)).toBe(0);
209
- });
210
- });
211
-
212
- // ============================================================================
213
- // Real-world document structures
214
- // ============================================================================
215
- describe("real-world document structures", () => {
216
- it("indents HTML-like structure", () => {
217
- const text = `<html>
218
- <head>
219
- <title>Test</title>
220
- </head>
221
- <body>\n`;
222
- expect(calculateXmlIndentation(text, INDENT)).toBe(2 * INDENT);
223
- });
224
-
225
- it("handles configuration-style XML", () => {
226
- const text = `<config>
227
- <database>
228
- <host>\n`;
229
- expect(calculateXmlIndentation(text, INDENT)).toBe(3 * INDENT);
230
- });
231
-
232
- it("handles SOAP-style envelope", () => {
233
- const text = `<soap:Envelope>
234
- <soap:Body>
235
- <Request>\n`;
236
- expect(calculateXmlIndentation(text, INDENT)).toBe(3 * INDENT);
237
- });
238
- });
239
- });
@@ -1,116 +0,0 @@
1
- import { xml } from "@codemirror/lang-xml";
2
- import { Decoration } from "@codemirror/view";
3
- import type { LanguageSupport, DecorationRange } from "./types";
4
-
5
- // Re-export pure utils for backwards compatibility
6
- export {
7
- isValidXmlTemplatePosition,
8
- calculateXmlIndentation,
9
- } from "./xml-utils";
10
-
11
- // Decoration marks for XML syntax highlighting using inline styles
12
- const xmlTagMark = Decoration.mark({
13
- attributes: { style: "color: hsl(280, 65%, 60%)" },
14
- });
15
- const xmlAttrNameMark = Decoration.mark({
16
- attributes: { style: "color: hsl(190, 70%, 50%)" },
17
- });
18
- const xmlAttrValueMark = Decoration.mark({
19
- attributes: { style: "color: hsl(142.1, 76.2%, 36.3%)" },
20
- });
21
- const templateMark = Decoration.mark({
22
- attributes: { style: "color: hsl(190, 70%, 50%); font-weight: 500" },
23
- });
24
-
25
- /**
26
- * Build XML + template decorations.
27
- */
28
- function buildXmlDecorations(doc: string): DecorationRange[] {
29
- const ranges: DecorationRange[] = [];
30
-
31
- // Match templates first (highest priority)
32
- const templateRegex = /\{\{[\w.[\]]*\}\}/g;
33
- let match;
34
- while ((match = templateRegex.exec(doc)) !== null) {
35
- ranges.push({
36
- from: match.index,
37
- to: match.index + match[0].length,
38
- decoration: templateMark,
39
- });
40
- }
41
-
42
- // Match XML tags (opening and closing)
43
- const tagRegex = /<\/?[\w:-]+/g;
44
- while ((match = tagRegex.exec(doc)) !== null) {
45
- ranges.push({
46
- from: match.index,
47
- to: match.index + match[0].length,
48
- decoration: xmlTagMark,
49
- });
50
- }
51
-
52
- // Match closing > and />
53
- const closeTagRegex = /\/?>/g;
54
- while ((match = closeTagRegex.exec(doc)) !== null) {
55
- ranges.push({
56
- from: match.index,
57
- to: match.index + match[0].length,
58
- decoration: xmlTagMark,
59
- });
60
- }
61
-
62
- // Match attribute names
63
- const attrNameRegex = /\s([\w:-]+)=/g;
64
- while ((match = attrNameRegex.exec(doc)) !== null) {
65
- ranges.push({
66
- from: match.index + 1, // Skip leading space
67
- to: match.index + 1 + match[1].length,
68
- decoration: xmlAttrNameMark,
69
- });
70
- }
71
-
72
- // Match attribute values (quoted strings after =)
73
- const attrValueRegex = /=(["'])(?:(?!\1)[^\\]|\\.)*\1/g;
74
- while ((match = attrValueRegex.exec(doc)) !== null) {
75
- // Skip the = but include the quotes
76
- ranges.push({
77
- from: match.index + 1,
78
- to: match.index + match[0].length,
79
- decoration: xmlAttrValueMark,
80
- });
81
- }
82
-
83
- // Sort by position
84
- ranges.sort((a, b) => a.from - b.from || a.to - b.to);
85
-
86
- // Remove overlaps (templates take priority)
87
- const filtered: DecorationRange[] = [];
88
- for (const range of ranges) {
89
- const overlaps = filtered.some(
90
- (existing) =>
91
- (range.from >= existing.from && range.from < existing.to) ||
92
- (range.to > existing.from && range.to <= existing.to),
93
- );
94
- if (!overlaps) {
95
- filtered.push(range);
96
- }
97
- }
98
-
99
- return filtered;
100
- }
101
-
102
- // Import the pure utils for use in the language support object
103
- import {
104
- isValidXmlTemplatePosition,
105
- calculateXmlIndentation,
106
- } from "./xml-utils";
107
-
108
- /**
109
- * XML language support for CodeEditor with template expression handling.
110
- */
111
- export const xmlLanguageSupport: LanguageSupport = {
112
- extension: xml(),
113
- buildDecorations: buildXmlDecorations,
114
- isValidTemplatePosition: isValidXmlTemplatePosition,
115
- calculateIndentation: calculateXmlIndentation,
116
- };
@@ -1,101 +0,0 @@
1
- /**
2
- * Pure utility functions for YAML parsing that don't depend on CodeMirror.
3
- * These are extracted to allow testing without triggering CodeMirror's module loading.
4
- */
5
-
6
- /**
7
- * Check if cursor is in a valid YAML template position.
8
- * In YAML, templates can appear as values after "key: ".
9
- * @internal Exported for testing
10
- */
11
- export function isValidYamlTemplatePosition(text: string): boolean {
12
- // Find the current line
13
- const lastNewline = text.lastIndexOf("\n");
14
- const currentLine = text.slice(lastNewline + 1);
15
-
16
- // Valid positions in YAML:
17
- // 1. After a colon and space (value position): "key: |"
18
- // 2. Inside a quoted string: 'key: "hello|'
19
- // 3. In a list item position: "- |"
20
-
21
- // Check if we're inside a quoted string
22
- const singleQuotes = (currentLine.match(/'/g) || []).length;
23
- const doubleQuotes = (currentLine.match(/"/g) || []).length;
24
- if (singleQuotes % 2 === 1 || doubleQuotes % 2 === 1) {
25
- return true;
26
- }
27
-
28
- // Check if we're after "key: " (value position)
29
- const colonMatch = currentLine.match(/^\s*[\w-]+:\s+/);
30
- if (colonMatch) {
31
- return true;
32
- }
33
-
34
- // Check if we're in a list item position "- "
35
- const listMatch = currentLine.match(/^\s*-\s+/);
36
- if (listMatch) {
37
- return true;
38
- }
39
-
40
- // Check if we're after ": " anywhere in the line
41
- if (currentLine.includes(": ")) {
42
- const afterColon = currentLine.split(": ").slice(1).join(": ");
43
- // If we're in the value part (after colon)
44
- if (afterColon.length > 0 || currentLine.endsWith(": ")) {
45
- return true;
46
- }
47
- }
48
-
49
- return false;
50
- }
51
-
52
- /**
53
- * Calculate indentation for YAML, properly handling template expressions.
54
- * YAML uses indentation-based structure.
55
- * @internal Exported for testing
56
- */
57
- export function calculateYamlIndentation(
58
- textBefore: string,
59
- indentUnit: number,
60
- ): number {
61
- const lines = textBefore.split("\n");
62
- if (lines.length === 0) return 0;
63
-
64
- // Get the last line (the one we're currently on after Enter)
65
- const currentLine = lines.at(-1);
66
-
67
- // If current line is empty or whitespace, look at previous line
68
- if (currentLine?.trim().length === 0 && lines.length > 1) {
69
- // Find the last non-empty line
70
- for (let i = lines.length - 2; i >= 0; i--) {
71
- const line = lines[i];
72
- if (line.trim().length === 0) continue;
73
-
74
- // Count leading spaces
75
- const leadingSpaces = line.match(/^(\s*)/)?.[1].length ?? 0;
76
-
77
- // If line ends with ":" it's a key that starts a new block
78
- if (line.trimEnd().endsWith(":")) {
79
- return leadingSpaces + indentUnit;
80
- }
81
-
82
- // If line starts with "- " it's a list item
83
- if (line.trim().startsWith("-")) {
84
- return leadingSpaces + indentUnit;
85
- }
86
-
87
- // Otherwise, maintain current indentation
88
- return leadingSpaces;
89
- }
90
- }
91
-
92
- // For non-empty current line, find previous non-empty for context
93
- for (let i = lines.length - 1; i >= 0; i--) {
94
- const line = lines[i];
95
- if (line.trim().length === 0) continue;
96
- const leadingSpaces = line.match(/^(\s*)/)?.[1].length ?? 0;
97
- return leadingSpaces;
98
- }
99
-
100
- return 0;
101
- }
@@ -1,203 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- isValidYamlTemplatePosition,
5
- calculateYamlIndentation,
6
- } from "./yaml-utils";
7
-
8
- describe("isValidYamlTemplatePosition", () => {
9
- describe("value positions", () => {
10
- it("returns true after 'key: '", () => {
11
- expect(isValidYamlTemplatePosition("name: ")).toBe(true);
12
- });
13
-
14
- it("returns true after 'key: ' with existing value", () => {
15
- expect(isValidYamlTemplatePosition("name: hello")).toBe(true);
16
- });
17
-
18
- it("returns true in list item position", () => {
19
- expect(isValidYamlTemplatePosition("- ")).toBe(true);
20
- });
21
-
22
- it("returns true inside double-quoted string", () => {
23
- expect(isValidYamlTemplatePosition('name: "hello')).toBe(true);
24
- });
25
-
26
- it("returns true inside single-quoted string", () => {
27
- expect(isValidYamlTemplatePosition("name: 'hello")).toBe(true);
28
- });
29
- });
30
-
31
- describe("invalid positions", () => {
32
- it("returns false for key position", () => {
33
- expect(isValidYamlTemplatePosition("name")).toBe(false);
34
- });
35
-
36
- it("returns false for empty string", () => {
37
- expect(isValidYamlTemplatePosition("")).toBe(false);
38
- });
39
- });
40
- });
41
-
42
- describe("calculateYamlIndentation", () => {
43
- const INDENT = 2;
44
-
45
- // ============================================================================
46
- // Basic YAML key:value patterns
47
- // ============================================================================
48
- describe("basic key:value indentation", () => {
49
- it("returns 0 for empty document", () => {
50
- expect(calculateYamlIndentation("", INDENT)).toBe(0);
51
- });
52
-
53
- it("indents after key ending with colon (new line)", () => {
54
- // User typed "test:" and pressed Enter
55
- // textBefore = "test:\n" (cursor at start of empty new line)
56
- expect(calculateYamlIndentation("test:\n", INDENT)).toBe(INDENT);
57
- });
58
-
59
- it("indents after key with trailing whitespace", () => {
60
- expect(calculateYamlIndentation("test: \n", INDENT)).toBe(INDENT);
61
- });
62
-
63
- it("maintains 0 indent for same-line content after key", () => {
64
- // User is typing on the line "test:" without pressing Enter
65
- expect(calculateYamlIndentation("test:", INDENT)).toBe(0);
66
- });
67
-
68
- it("maintains 0 indent for key:value on same line", () => {
69
- expect(calculateYamlIndentation("test: value", INDENT)).toBe(0);
70
- });
71
- });
72
-
73
- // ============================================================================
74
- // Nested object indentation
75
- // ============================================================================
76
- describe("nested object indentation", () => {
77
- it("indents to level 2 after nested key", () => {
78
- const text = `parent:
79
- child:\n`;
80
- expect(calculateYamlIndentation(text, INDENT)).toBe(2 * INDENT);
81
- });
82
-
83
- it("maintains level 1 after nested key:value", () => {
84
- const text = `parent:
85
- child: value\n`;
86
- expect(calculateYamlIndentation(text, INDENT)).toBe(INDENT);
87
- });
88
-
89
- it("indents to level 3 for deep nesting", () => {
90
- const text = `level1:
91
- level2:
92
- level3:\n`;
93
- expect(calculateYamlIndentation(text, INDENT)).toBe(3 * INDENT);
94
- });
95
-
96
- it("returns to level 1 after deep nesting with value", () => {
97
- const text = `level1:
98
- level2:
99
- level3: value\n`;
100
- expect(calculateYamlIndentation(text, INDENT)).toBe(2 * INDENT);
101
- });
102
- });
103
-
104
- // ============================================================================
105
- // List item indentation
106
- // ============================================================================
107
- describe("list item indentation", () => {
108
- it("indents after list item", () => {
109
- expect(calculateYamlIndentation("- item\n", INDENT)).toBe(INDENT);
110
- });
111
-
112
- it("indents after list item with nested key", () => {
113
- const text = `items:
114
- - name: test\n`;
115
- expect(calculateYamlIndentation(text, INDENT)).toBe(2 * INDENT);
116
- });
117
-
118
- it("indents after empty list marker", () => {
119
- expect(calculateYamlIndentation("-\n", INDENT)).toBe(INDENT);
120
- });
121
-
122
- it("handles list with object items", () => {
123
- const text = `items:
124
- - name: test
125
- value: 123\n`;
126
- expect(calculateYamlIndentation(text, INDENT)).toBe(2 * INDENT);
127
- });
128
- });
129
-
130
- // ============================================================================
131
- // Template handling (no interference with structure)
132
- // ============================================================================
133
- describe("template handling", () => {
134
- it("maintains indentation with template value on same line", () => {
135
- expect(calculateYamlIndentation("name: {{value}}", INDENT)).toBe(0);
136
- });
137
-
138
- it("indents after key even when value contains template", () => {
139
- expect(calculateYamlIndentation("name: {{value}}\n", INDENT)).toBe(0);
140
- });
141
-
142
- it("maintains nesting with template in nested value", () => {
143
- const text = `parent:
144
- child: {{value}}\n`;
145
- expect(calculateYamlIndentation(text, INDENT)).toBe(INDENT);
146
- });
147
-
148
- it("indents after key with template but ending with colon", () => {
149
- const text = `parent:
150
- child:\n`;
151
- expect(calculateYamlIndentation(text, INDENT)).toBe(2 * INDENT);
152
- });
153
- });
154
-
155
- // ============================================================================
156
- // Multi-line string handling
157
- // ============================================================================
158
- describe("multi-line scenarios", () => {
159
- it("maintains current indent after blank line", () => {
160
- const text = `parent:
161
- child: value
162
-
163
- `;
164
- expect(calculateYamlIndentation(text, INDENT)).toBe(INDENT);
165
- });
166
-
167
- it("handles multiple blank lines", () => {
168
- const text = `parent:
169
- child: value
170
-
171
-
172
- `;
173
- expect(calculateYamlIndentation(text, INDENT)).toBe(INDENT);
174
- });
175
- });
176
-
177
- // ============================================================================
178
- // Edge cases
179
- // ============================================================================
180
- describe("edge cases", () => {
181
- it("handles only newline", () => {
182
- expect(calculateYamlIndentation("\n", INDENT)).toBe(0);
183
- });
184
-
185
- it("handles multiple newlines only", () => {
186
- expect(calculateYamlIndentation("\n\n\n", INDENT)).toBe(0);
187
- });
188
-
189
- it("handles colon in string value", () => {
190
- expect(
191
- calculateYamlIndentation('url: "http://example.com"\n', INDENT),
192
- ).toBe(0);
193
- });
194
-
195
- it("handles key with numbers", () => {
196
- expect(calculateYamlIndentation("key123:\n", INDENT)).toBe(INDENT);
197
- });
198
-
199
- it("handles key with hyphens", () => {
200
- expect(calculateYamlIndentation("my-key:\n", INDENT)).toBe(INDENT);
201
- });
202
- });
203
- });