similarbuild 0.1.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.
- package/CHANGELOG.md +110 -0
- package/LICENSE +21 -0
- package/README.md +301 -0
- package/bin/install.js +256 -0
- package/lib/copy-templates.mjs +52 -0
- package/lib/install-deps.mjs +62 -0
- package/lib/prompt-config.mjs +83 -0
- package/lib/verify-env.mjs +19 -0
- package/package.json +63 -0
- package/scripts/sync-templates.mjs +71 -0
- package/templates/commands/build-page.md +490 -0
- package/templates/commands/build-site.md +548 -0
- package/templates/commands/clip-section.md +519 -0
- package/templates/memory/anti-patterns.md +212 -0
- package/templates/memory/design-knowledge.md +225 -0
- package/templates/memory/fixes.md +163 -0
- package/templates/memory/patterns.md +681 -0
- package/templates/presets/shopify-section.yaml +51 -0
- package/templates/presets/wp-elementor.yaml +49 -0
- package/templates/reports/fixtures/mock-run-1.json +115 -0
- package/templates/reports/fixtures/mock-run-2.json +72 -0
- package/templates/reports/report-renderer.mjs +218 -0
- package/templates/reports/report-template.html +571 -0
- package/templates/skills/sb-build-shopify/SKILL.md +104 -0
- package/templates/skills/sb-build-shopify/references/shopify-build-rules.md +563 -0
- package/templates/skills/sb-build-shopify/scripts/build-shopify.mjs +637 -0
- package/templates/skills/sb-build-shopify/scripts/tests/test-build-shopify.mjs +424 -0
- package/templates/skills/sb-build-wp/SKILL.md +83 -0
- package/templates/skills/sb-build-wp/references/wp-build-rules.md +376 -0
- package/templates/skills/sb-build-wp/scripts/build-wp.mjs +327 -0
- package/templates/skills/sb-build-wp/scripts/tests/test-build-wp.mjs +224 -0
- package/templates/skills/sb-compare-visual/SKILL.md +121 -0
- package/templates/skills/sb-compare-visual/scripts/compare-visual.mjs +387 -0
- package/templates/skills/sb-compare-visual/scripts/lib/compare-tokens.mjs +273 -0
- package/templates/skills/sb-compare-visual/scripts/tests/test-compare-tokens.mjs +350 -0
- package/templates/skills/sb-compare-visual/scripts/tests/test-compare-visual.mjs +626 -0
- package/templates/skills/sb-crawl-and-list/SKILL.md +99 -0
- package/templates/skills/sb-crawl-and-list/scripts/crawl-and-list.mjs +437 -0
- package/templates/skills/sb-crawl-and-list/scripts/lib/blocklist-filter.mjs +176 -0
- package/templates/skills/sb-crawl-and-list/scripts/lib/fallback-crawler.mjs +107 -0
- package/templates/skills/sb-crawl-and-list/scripts/lib/page-classifier.mjs +89 -0
- package/templates/skills/sb-crawl-and-list/scripts/lib/sitemap-parser.mjs +118 -0
- package/templates/skills/sb-crawl-and-list/scripts/tests/test-blocklist-filter.mjs +204 -0
- package/templates/skills/sb-crawl-and-list/scripts/tests/test-crawl-and-list.mjs +276 -0
- package/templates/skills/sb-crawl-and-list/scripts/tests/test-fallback-crawler.mjs +243 -0
- package/templates/skills/sb-crawl-and-list/scripts/tests/test-page-classifier.mjs +120 -0
- package/templates/skills/sb-crawl-and-list/scripts/tests/test-sitemap-parser.mjs +157 -0
- package/templates/skills/sb-extract-assets/SKILL.md +112 -0
- package/templates/skills/sb-extract-assets/scripts/extract-assets.mjs +484 -0
- package/templates/skills/sb-extract-assets/scripts/tests/test-extract-assets.mjs +112 -0
- package/templates/skills/sb-inspect-live/SKILL.md +105 -0
- package/templates/skills/sb-inspect-live/scripts/inspect-live.mjs +693 -0
- package/templates/skills/sb-inspect-live/scripts/tests/test-inspect-live.mjs +181 -0
- package/templates/skills/sb-review-checks/SKILL.md +113 -0
- package/templates/skills/sb-review-checks/references/review-rules.md +195 -0
- package/templates/skills/sb-review-checks/scripts/lib/anti-patterns.mjs +379 -0
- package/templates/skills/sb-review-checks/scripts/lib/cross-reference.mjs +115 -0
- package/templates/skills/sb-review-checks/scripts/lib/design-quality.mjs +541 -0
- package/templates/skills/sb-review-checks/scripts/review-checks.mjs +250 -0
- package/templates/skills/sb-review-checks/scripts/tests/test-anti-patterns.mjs +343 -0
- package/templates/skills/sb-review-checks/scripts/tests/test-cross-reference.mjs +170 -0
- package/templates/skills/sb-review-checks/scripts/tests/test-design-quality.mjs +493 -0
- package/templates/skills/sb-review-checks/scripts/tests/test-review-checks.mjs +267 -0
- package/templates/skills/sb-tweak/SKILL.md +130 -0
- package/templates/skills/sb-tweak/references/tweak-patterns.md +157 -0
- package/templates/skills/sb-tweak/scripts/lib/diff-summarizer.mjs +140 -0
- package/templates/skills/sb-tweak/scripts/lib/element-locator.mjs +507 -0
- package/templates/skills/sb-tweak/scripts/lib/intent-parser.mjs +324 -0
- package/templates/skills/sb-tweak/scripts/tests/test-diff-summarizer.mjs +248 -0
- package/templates/skills/sb-tweak/scripts/tests/test-element-locator.mjs +418 -0
- package/templates/skills/sb-tweak/scripts/tests/test-intent-parser.mjs +496 -0
- package/templates/skills/sb-tweak/scripts/tests/test-tweak.mjs +407 -0
- package/templates/skills/sb-tweak/scripts/tweak.mjs +656 -0
- package/templates/skills/sb-validate-render/SKILL.md +120 -0
- package/templates/skills/sb-validate-render/scripts/tests/test-validate-render.mjs +304 -0
- package/templates/skills/sb-validate-render/scripts/validate-render.mjs +645 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// test-intent-parser.mjs — Unit tests for lib/intent-parser.mjs.
|
|
3
|
+
// Covers: action verbs (PT/EN), target types, qualifiers, property phrases,
|
|
4
|
+
// value extraction, language detection, confidence scoring, and edge cases.
|
|
5
|
+
|
|
6
|
+
import { strict as assert } from 'node:assert'
|
|
7
|
+
import { parseIntent, extractValue, defaultProperty } from '../lib/intent-parser.mjs'
|
|
8
|
+
|
|
9
|
+
let passed = 0
|
|
10
|
+
let failed = 0
|
|
11
|
+
|
|
12
|
+
function test(name, fn) {
|
|
13
|
+
try {
|
|
14
|
+
fn()
|
|
15
|
+
process.stdout.write(`ok - ${name}\n`)
|
|
16
|
+
passed++
|
|
17
|
+
} catch (err) {
|
|
18
|
+
process.stdout.write(`not ok - ${name}\n ${err.message}\n`)
|
|
19
|
+
failed++
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── Action verbs ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
test('PT: "aumenta" → increase', () => {
|
|
26
|
+
const i = parseIntent('aumenta o título do hero pra 32px')
|
|
27
|
+
assert.equal(i.action, 'increase')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('PT: "diminui" → decrease', () => {
|
|
31
|
+
const i = parseIntent('diminui o subtítulo para 14px')
|
|
32
|
+
assert.equal(i.action, 'decrease')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('PT: "muda" → set', () => {
|
|
36
|
+
const i = parseIntent('muda a cor do botão pra #fff')
|
|
37
|
+
assert.equal(i.action, 'set')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('PT: "troca" → set', () => {
|
|
41
|
+
const i = parseIntent('troca a imagem do hero')
|
|
42
|
+
assert.equal(i.action, 'set')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('PT: "adiciona" → add', () => {
|
|
46
|
+
const i = parseIntent('adiciona alt="Logo" na primeira imagem')
|
|
47
|
+
assert.equal(i.action, 'add')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('PT: "remove" → remove', () => {
|
|
51
|
+
const i = parseIntent('remove o segundo botão')
|
|
52
|
+
assert.equal(i.action, 'remove')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('EN: "increase" → increase', () => {
|
|
56
|
+
const i = parseIntent('increase the hero title to 32px')
|
|
57
|
+
assert.equal(i.action, 'increase')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('EN: "shrink" → decrease', () => {
|
|
61
|
+
const i = parseIntent('shrink the button text to 16px')
|
|
62
|
+
assert.equal(i.action, 'decrease')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('EN: "change" → set', () => {
|
|
66
|
+
const i = parseIntent('change the hero title color to #fff')
|
|
67
|
+
assert.equal(i.action, 'set')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('EN: "add" → add', () => {
|
|
71
|
+
const i = parseIntent('add alt "Logo" to the first image')
|
|
72
|
+
assert.equal(i.action, 'add')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('EN: "remove" → remove', () => {
|
|
76
|
+
const i = parseIntent('remove the last button')
|
|
77
|
+
assert.equal(i.action, 'remove')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// ─── Target types ─────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
test('target: título → heading', () => {
|
|
83
|
+
const i = parseIntent('aumenta o título pra 32px')
|
|
84
|
+
assert.equal(i.target.type, 'heading')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('target: subtítulo → subheading', () => {
|
|
88
|
+
const i = parseIntent('diminui o subtítulo para 14px')
|
|
89
|
+
assert.equal(i.target.type, 'subheading')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('target: imagem → image', () => {
|
|
93
|
+
const i = parseIntent('troca a imagem do hero')
|
|
94
|
+
assert.equal(i.target.type, 'image')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('target: botão → button', () => {
|
|
98
|
+
const i = parseIntent('muda a cor do botão pra #fff')
|
|
99
|
+
assert.equal(i.target.type, 'button')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('target: button (EN) → button', () => {
|
|
103
|
+
const i = parseIntent('change the button color to red')
|
|
104
|
+
assert.equal(i.target.type, 'button')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('target: image (EN) → image', () => {
|
|
108
|
+
const i = parseIntent('change the hero image alt to "Hero shot"')
|
|
109
|
+
assert.equal(i.target.type, 'image')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('target: link → link', () => {
|
|
113
|
+
const i = parseIntent('change the link to https://example.com')
|
|
114
|
+
assert.equal(i.target.type, 'link')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('target: ícone → icon', () => {
|
|
118
|
+
const i = parseIntent('aumenta o ícone para 24px')
|
|
119
|
+
assert.equal(i.target.type, 'icon')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// ─── Qualifiers ───────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
test('qualifier: hero', () => {
|
|
125
|
+
const i = parseIntent('aumenta o título do hero pra 32px')
|
|
126
|
+
assert.equal(i.target.qualifier, 'hero')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('qualifier: footer', () => {
|
|
130
|
+
const i = parseIntent('muda o texto do footer')
|
|
131
|
+
assert.equal(i.target.qualifier, 'footer')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('qualifier: header', () => {
|
|
135
|
+
const i = parseIntent('aumenta o link do header pra 18px')
|
|
136
|
+
assert.equal(i.target.qualifier, 'header')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('qualifier: primeira (PT) → first', () => {
|
|
140
|
+
const i = parseIntent('adiciona alt na primeira imagem')
|
|
141
|
+
assert.equal(i.target.qualifier, 'first')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('qualifier: segundo (PT) → second', () => {
|
|
145
|
+
const i = parseIntent('remove o segundo botão')
|
|
146
|
+
assert.equal(i.target.qualifier, 'second')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('qualifier: third (EN)', () => {
|
|
150
|
+
const i = parseIntent('change the third button text to "Buy"')
|
|
151
|
+
assert.equal(i.target.qualifier, 'third')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('qualifier: last', () => {
|
|
155
|
+
const i = parseIntent('remove the last button')
|
|
156
|
+
assert.equal(i.target.qualifier, 'last')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// ─── Property detection ───────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
test('property: explicit "tamanho" → font-size', () => {
|
|
162
|
+
const i = parseIntent('aumenta o tamanho do título para 32px')
|
|
163
|
+
assert.equal(i.property, 'font-size')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('property: explicit "cor de fundo" → background-color', () => {
|
|
167
|
+
const i = parseIntent('muda a cor de fundo do hero pra #1a1a1a')
|
|
168
|
+
assert.equal(i.property, 'background-color')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('property: explicit "cor do texto" → color', () => {
|
|
172
|
+
const i = parseIntent('muda a cor do texto do botão pra #fff')
|
|
173
|
+
assert.equal(i.property, 'color')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('property: bare "cor" defaults to color (text)', () => {
|
|
177
|
+
const i = parseIntent('muda a cor do título pra red')
|
|
178
|
+
// The dictionary returns property='color' from "cor"; default would also be color.
|
|
179
|
+
assert.equal(i.property, 'color')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('property: alt → alt', () => {
|
|
183
|
+
const i = parseIntent('change the image alt to "Hero shot"')
|
|
184
|
+
assert.equal(i.property, 'alt')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('property: src → src', () => {
|
|
188
|
+
const i = parseIntent('change the image src to https://example.com/x.png')
|
|
189
|
+
assert.equal(i.property, 'src')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('property: default for "increase title" with px → font-size', () => {
|
|
193
|
+
const i = parseIntent('increase the hero title to 32px')
|
|
194
|
+
assert.equal(i.property, 'font-size')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('property: default for "set image alt" via quoted text', () => {
|
|
198
|
+
const i = parseIntent('change the hero image to "AlphaInfuse hero"')
|
|
199
|
+
// No "alt" word → defaults from value.kind=text + target.type=image → alt
|
|
200
|
+
assert.equal(i.property, 'alt')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// ─── Value extraction ─────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
test('value: pixels', () => {
|
|
206
|
+
const v = extractValue('aumenta pra 32px')
|
|
207
|
+
assert.equal(v.kind, 'pixels')
|
|
208
|
+
assert.equal(v.parsed, 32)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('value: pixels with decimal', () => {
|
|
212
|
+
const v = extractValue('change to 1.5px')
|
|
213
|
+
assert.equal(v.kind, 'pixels')
|
|
214
|
+
assert.equal(v.parsed, 1.5)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('value: rem', () => {
|
|
218
|
+
const v = extractValue('change to 2rem')
|
|
219
|
+
assert.equal(v.kind, 'length')
|
|
220
|
+
assert.equal(v.parsed.value, 2)
|
|
221
|
+
assert.equal(v.parsed.unit, 'rem')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('value: percent', () => {
|
|
225
|
+
const v = extractValue('change to 100%')
|
|
226
|
+
assert.equal(v.kind, 'length')
|
|
227
|
+
assert.equal(v.parsed.unit, '%')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('value: hex color', () => {
|
|
231
|
+
const v = extractValue('cor pra #fff')
|
|
232
|
+
assert.equal(v.kind, 'color')
|
|
233
|
+
assert.equal(v.parsed, '#fff')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('value: long hex color', () => {
|
|
237
|
+
const v = extractValue('cor pra #1a2b3c')
|
|
238
|
+
assert.equal(v.kind, 'color')
|
|
239
|
+
assert.equal(v.parsed, '#1a2b3c')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('value: rgb()', () => {
|
|
243
|
+
const v = extractValue('change color to rgb(255, 0, 0)')
|
|
244
|
+
assert.equal(v.kind, 'color')
|
|
245
|
+
assert.match(v.parsed, /rgb\(/)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('value: rgba()', () => {
|
|
249
|
+
const v = extractValue('change to rgba(0, 0, 0, 0.5)')
|
|
250
|
+
assert.equal(v.kind, 'color')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('value: color name (red)', () => {
|
|
254
|
+
const v = extractValue('change color to red')
|
|
255
|
+
assert.equal(v.kind, 'color')
|
|
256
|
+
assert.equal(v.parsed, 'red')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('value: color name PT (vermelho)', () => {
|
|
260
|
+
const v = extractValue('muda a cor pra vermelho')
|
|
261
|
+
assert.equal(v.kind, 'color')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('value: quoted text (double)', () => {
|
|
265
|
+
const v = extractValue('change alt to "Hero shot"')
|
|
266
|
+
assert.equal(v.kind, 'text')
|
|
267
|
+
assert.equal(v.parsed, 'Hero shot')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('value: quoted text (single)', () => {
|
|
271
|
+
const v = extractValue("change alt to 'Hero shot'")
|
|
272
|
+
assert.equal(v.kind, 'text')
|
|
273
|
+
assert.equal(v.parsed, 'Hero shot')
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('value: URL', () => {
|
|
277
|
+
const v = extractValue('change src to https://cdn.example.com/img.png')
|
|
278
|
+
assert.equal(v.kind, 'url')
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('value: bare number → number', () => {
|
|
282
|
+
const v = extractValue('aumenta para 32')
|
|
283
|
+
assert.equal(v.kind, 'number')
|
|
284
|
+
assert.equal(v.parsed, 32)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
test('value: empty → null', () => {
|
|
288
|
+
const v = extractValue('aumenta o título')
|
|
289
|
+
assert.equal(v, null)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('value: URL beats hex (URLs come first)', () => {
|
|
293
|
+
const v = extractValue('https://example.com/path?c=#fff')
|
|
294
|
+
assert.equal(v.kind, 'url')
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test('value: quoted hex stays text not color', () => {
|
|
298
|
+
const v = extractValue('write "#fff" in the title')
|
|
299
|
+
assert.equal(v.kind, 'text')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// ─── Confidence scoring ───────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
test('confidence: complete request scores ≥ 0.7', () => {
|
|
305
|
+
const i = parseIntent('aumenta o título do hero pra 32px')
|
|
306
|
+
assert.ok(i.confidence >= 0.7, `confidence was ${i.confidence}`)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('confidence: missing action drops below 0.7', () => {
|
|
310
|
+
const i = parseIntent('o título do hero pra 32px')
|
|
311
|
+
assert.ok(i.confidence < 0.7, `confidence was ${i.confidence}`)
|
|
312
|
+
assert.equal(i.reason, 'no-action-verb')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('confidence: missing target drops', () => {
|
|
316
|
+
const i = parseIntent('aumenta para 32px')
|
|
317
|
+
assert.ok(i.confidence < 0.7, `confidence was ${i.confidence}`)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
test('confidence: missing value for "set" drops', () => {
|
|
321
|
+
const i = parseIntent('troca o título do hero')
|
|
322
|
+
assert.ok(i.confidence < 0.7, `confidence was ${i.confidence}`)
|
|
323
|
+
assert.equal(i.reason, 'no-value')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('confidence: remove without value is fine', () => {
|
|
327
|
+
const i = parseIntent('remove o segundo botão')
|
|
328
|
+
assert.ok(i.confidence >= 0.7, `confidence was ${i.confidence}`)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// ─── Language detection ──────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
test('language: PT', () => {
|
|
334
|
+
const i = parseIntent('aumenta o título do hero')
|
|
335
|
+
assert.equal(i.language, 'pt')
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('language: EN', () => {
|
|
339
|
+
const i = parseIntent('increase the hero title')
|
|
340
|
+
assert.equal(i.language, 'en')
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test('language: unknown returns confidence ≤ 0.1', () => {
|
|
344
|
+
const i = parseIntent('zzz xxx yyy 123')
|
|
345
|
+
assert.equal(i.language, 'unknown')
|
|
346
|
+
assert.ok(i.confidence <= 0.2)
|
|
347
|
+
assert.equal(i.reason, 'language-unsupported')
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test('language: empty request', () => {
|
|
351
|
+
const i = parseIntent('')
|
|
352
|
+
assert.equal(i.confidence, 0)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('language: whitespace-only request', () => {
|
|
356
|
+
const i = parseIntent(' \t \n ')
|
|
357
|
+
assert.equal(i.confidence, 0)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// ─── defaultProperty ──────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
test('defaultProperty: increase + heading + pixels → font-size', () => {
|
|
363
|
+
const p = defaultProperty({
|
|
364
|
+
action: 'increase',
|
|
365
|
+
target: { type: 'heading' },
|
|
366
|
+
value: { kind: 'pixels', parsed: 32 },
|
|
367
|
+
})
|
|
368
|
+
assert.equal(p, 'font-size')
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test('defaultProperty: set + button + color → background-color', () => {
|
|
372
|
+
const p = defaultProperty({
|
|
373
|
+
action: 'set',
|
|
374
|
+
target: { type: 'button' },
|
|
375
|
+
value: { kind: 'color', parsed: '#fff' },
|
|
376
|
+
})
|
|
377
|
+
assert.equal(p, 'background-color')
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
test('defaultProperty: set + heading + color → color (text)', () => {
|
|
381
|
+
const p = defaultProperty({
|
|
382
|
+
action: 'set',
|
|
383
|
+
target: { type: 'heading' },
|
|
384
|
+
value: { kind: 'color', parsed: '#fff' },
|
|
385
|
+
})
|
|
386
|
+
assert.equal(p, 'color')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
test('defaultProperty: set + image + url → src', () => {
|
|
390
|
+
const p = defaultProperty({
|
|
391
|
+
action: 'set',
|
|
392
|
+
target: { type: 'image' },
|
|
393
|
+
value: { kind: 'url', parsed: 'https://x' },
|
|
394
|
+
})
|
|
395
|
+
assert.equal(p, 'src')
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test('defaultProperty: set + image + text → alt', () => {
|
|
399
|
+
const p = defaultProperty({
|
|
400
|
+
action: 'set',
|
|
401
|
+
target: { type: 'image' },
|
|
402
|
+
value: { kind: 'text', parsed: 'hero' },
|
|
403
|
+
})
|
|
404
|
+
assert.equal(p, 'alt')
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('defaultProperty: set + heading + text → text', () => {
|
|
408
|
+
const p = defaultProperty({
|
|
409
|
+
action: 'set',
|
|
410
|
+
target: { type: 'heading' },
|
|
411
|
+
value: { kind: 'text', parsed: 'New title' },
|
|
412
|
+
})
|
|
413
|
+
assert.equal(p, 'text')
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
test('defaultProperty: increase + image → width', () => {
|
|
417
|
+
const p = defaultProperty({
|
|
418
|
+
action: 'increase',
|
|
419
|
+
target: { type: 'image' },
|
|
420
|
+
value: { kind: 'pixels', parsed: 200 },
|
|
421
|
+
})
|
|
422
|
+
assert.equal(p, 'width')
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('defaultProperty: remove → null', () => {
|
|
426
|
+
const p = defaultProperty({
|
|
427
|
+
action: 'remove',
|
|
428
|
+
target: { type: 'button' },
|
|
429
|
+
value: null,
|
|
430
|
+
})
|
|
431
|
+
assert.equal(p, null)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test('defaultProperty: no target → null', () => {
|
|
435
|
+
const p = defaultProperty({ action: 'set', target: null, value: null })
|
|
436
|
+
assert.equal(p, null)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// ─── Edge cases ───────────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
test('edge: typing "trocar" infinitive form', () => {
|
|
442
|
+
const i = parseIntent('quero trocar a imagem do hero por https://x.com/y.png')
|
|
443
|
+
assert.equal(i.action, 'set')
|
|
444
|
+
assert.equal(i.target.type, 'image')
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
test('edge: capitalization', () => {
|
|
448
|
+
const i = parseIntent('AUMENTA o TÍTULO do HERO PARA 32PX')
|
|
449
|
+
assert.equal(i.action, 'increase')
|
|
450
|
+
assert.equal(i.target.type, 'heading')
|
|
451
|
+
assert.equal(i.target.qualifier, 'hero')
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('edge: extra whitespace', () => {
|
|
455
|
+
const i = parseIntent(' aumenta o título pra 32px ')
|
|
456
|
+
assert.equal(i.action, 'increase')
|
|
457
|
+
assert.equal(i.value.parsed, 32)
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
test('edge: ordinal "1º" → first', () => {
|
|
461
|
+
const i = parseIntent('remove o 1º botão')
|
|
462
|
+
assert.equal(i.target.qualifier, 'first')
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
test('edge: longest target match wins (subtítulo not título)', () => {
|
|
466
|
+
const i = parseIntent('aumenta o subtítulo pra 16px')
|
|
467
|
+
assert.equal(i.target.type, 'subheading')
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
test('edge: increase + size verb gets px from bare number? No — needs explicit unit', () => {
|
|
471
|
+
// We treat bare number as "number" kind; CLI converts to px when target/property implies it.
|
|
472
|
+
const i = parseIntent('aumenta o título pra 32')
|
|
473
|
+
assert.equal(i.action, 'increase')
|
|
474
|
+
assert.equal(i.value.kind, 'number')
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
test('edge: link with quoted text targets href if URL given', () => {
|
|
478
|
+
const i = parseIntent('change the link to https://example.com')
|
|
479
|
+
assert.equal(i.target.type, 'link')
|
|
480
|
+
assert.equal(i.property, 'href')
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
test('edge: "fundo" alone implies background-color', () => {
|
|
484
|
+
const i = parseIntent('muda o fundo do hero pra #1a1a1a')
|
|
485
|
+
assert.equal(i.property, 'background-color')
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
test('edge: returns raw request for debugging', () => {
|
|
489
|
+
const i = parseIntent('aumenta o título')
|
|
490
|
+
assert.equal(i.raw, 'aumenta o título')
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// ─── Done ─────────────────────────────────────────────────────────────────────
|
|
494
|
+
|
|
495
|
+
process.stdout.write(`\n1..${passed + failed}\n# passed ${passed}, failed ${failed}\n`)
|
|
496
|
+
process.exit(failed === 0 ? 0 : 1)
|