@jay-framework/jay-stack-cli 0.12.0 → 0.14.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/agent-kit-template/INSTRUCTIONS.md +10 -6
- package/agent-kit-template/cli-commands.md +2 -3
- package/agent-kit-template/contracts-and-plugins.md +69 -18
- package/dist/index.d.ts +75 -3
- package/dist/index.js +2215 -556
- package/lib/vendors/README.md +510 -0
- package/lib/vendors/figma/README.md +396 -0
- package/package.json +10 -10
- package/test/vendors/figma/fixtures/README.md +164 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Figma Vendor
|
|
2
|
+
|
|
3
|
+
Converts Figma documents to Jay HTML with full contract binding support.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Figma vendor implements the complete conversion pipeline from Figma design documents to Jay-HTML, handling:
|
|
8
|
+
|
|
9
|
+
- **Data bindings** - Contract tags bound to UI elements
|
|
10
|
+
- **Interactive refs** - Button clicks, input fields, etc.
|
|
11
|
+
- **Attribute bindings** - src, value, href, etc.
|
|
12
|
+
- **Variant permutations** - Figma component variants to Jay if conditions
|
|
13
|
+
- **Repeater contexts** - forEach loops with proper path scoping
|
|
14
|
+
- **Plugin contracts** - Multi-component plugin support
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
### Conversion Pipeline
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
FigmaVendorDocument
|
|
22
|
+
↓
|
|
23
|
+
1. Extract bindings from pluginData
|
|
24
|
+
↓
|
|
25
|
+
2. Analyze bindings (type, paths, context)
|
|
26
|
+
↓
|
|
27
|
+
3. Validate binding rules
|
|
28
|
+
↓
|
|
29
|
+
4. Route to appropriate converter
|
|
30
|
+
↓
|
|
31
|
+
- Repeater → convertRepeaterNode()
|
|
32
|
+
- Variant → convertVariantNode()
|
|
33
|
+
- Regular → convertRegularNode()
|
|
34
|
+
↓
|
|
35
|
+
Jay-HTML output
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Key Files
|
|
39
|
+
|
|
40
|
+
- **`index.ts`** - Main vendor implementation, conversion pipeline
|
|
41
|
+
- **`binding-analysis.ts`** - Binding analysis, path resolution, validation
|
|
42
|
+
- **`types.ts`** - Type definitions for bindings and context
|
|
43
|
+
- **`utils.ts`** - CSS conversion utilities
|
|
44
|
+
- **`converters/`** - Node-specific converters (text, rectangle, etc.)
|
|
45
|
+
- **`pageContractPath.ts`** - Contract path utilities
|
|
46
|
+
|
|
47
|
+
## Binding Types
|
|
48
|
+
|
|
49
|
+
### 1. Dynamic Content (Data)
|
|
50
|
+
|
|
51
|
+
**Figma Binding:**
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"tagPath": ["productPage", "name"],
|
|
56
|
+
"pageContractPath": { ... }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Jay-HTML:**
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<div>{productPage.name}</div>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Interactive Ref
|
|
67
|
+
|
|
68
|
+
**Figma Binding:**
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"tagPath": ["submitButton"],
|
|
73
|
+
"pageContractPath": { ... }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Jay-HTML:**
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<button ref="submitButton">Submit</button>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Dual Binding (Data + Interactive)
|
|
84
|
+
|
|
85
|
+
**Figma Binding:**
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"tagPath": ["email"],
|
|
90
|
+
"pageContractPath": { ... }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Jay-HTML:**
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<input ref="email" value="{email}" />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Attribute Binding
|
|
101
|
+
|
|
102
|
+
**Figma Binding:**
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"tagPath": ["product", "imageUrl"],
|
|
107
|
+
"attribute": "src",
|
|
108
|
+
"pageContractPath": { ... }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Jay-HTML:**
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<img src="{product.imageUrl}" alt="..." />
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Supported Attributes:**
|
|
119
|
+
|
|
120
|
+
- `src` - Image sources (requires `semanticHtml: 'img'`)
|
|
121
|
+
- `href` - Link destinations (requires `semanticHtml: 'a'`)
|
|
122
|
+
- `value` - Input field values (requires `semanticHtml: 'input'`)
|
|
123
|
+
- `alt` - Image alt text
|
|
124
|
+
- `placeholder` - Input placeholders
|
|
125
|
+
|
|
126
|
+
#### Image Conversion (Semantic HTML: `img`)
|
|
127
|
+
|
|
128
|
+
When a node has `semanticHtml: 'img'` in its plugin data:
|
|
129
|
+
|
|
130
|
+
**Bound Image** (with `src` attribute binding):
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<img src="{productPage.imageUrl}" alt="{productPage.imageAlt}" data-figma-id="..." />
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Static Image** (no bindings, uses Figma fills):
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<img src="/assets/images/product-hero.png" alt="Product Hero" data-figma-id="..." />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For static images, the plugin must export and save the image:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// In plugin serialization for IMAGE fills:
|
|
146
|
+
if (fill.type === 'IMAGE' && fill.imageHash) {
|
|
147
|
+
const imageBytes = await figma.getImageByHash(fill.imageHash)?.getBytesAsync();
|
|
148
|
+
if (imageBytes) {
|
|
149
|
+
const imageUrl = await saveImageToAssets(imageBytes, node.id);
|
|
150
|
+
serializedFill.imageUrl = imageUrl; // ← Add this!
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 5. Property Binding (Variants)
|
|
156
|
+
|
|
157
|
+
**Figma Bindings:**
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
[
|
|
161
|
+
{ "property": "mediaType", "tagPath": ["productPage", "mediaType"] },
|
|
162
|
+
{ "property": "selected", "tagPath": ["productPage", "isSelected"] }
|
|
163
|
+
]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Jay-HTML:**
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<div if="productPage.mediaType == IMAGE && productPage.isSelected == true">
|
|
170
|
+
<!-- IMAGE + selected variant -->
|
|
171
|
+
</div>
|
|
172
|
+
<div if="productPage.mediaType == VIDEO && productPage.isSelected == true">
|
|
173
|
+
<!-- VIDEO + selected variant -->
|
|
174
|
+
</div>
|
|
175
|
+
<!-- ... other permutations -->
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Pseudo-CSS Variants:**
|
|
179
|
+
|
|
180
|
+
Variant values containing `:` (like `image:hover`, `:active`, `:disabled`) are automatically filtered out from Jay-HTML `if` conditions. These are pseudo-CSS class variants that should be handled via CSS display toggling instead:
|
|
181
|
+
|
|
182
|
+
```css
|
|
183
|
+
/* Pseudo-variant CSS (handled separately, not in conversion) */
|
|
184
|
+
.mediaType_hover {
|
|
185
|
+
display: none;
|
|
186
|
+
}
|
|
187
|
+
.mediaType:hover .mediaType_hover {
|
|
188
|
+
display: block;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This filtering prevents invalid expressions like `if="media == image:hover"` from being generated.
|
|
193
|
+
|
|
194
|
+
### 6. Repeater
|
|
195
|
+
|
|
196
|
+
**Figma Binding:**
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"tagPath": ["productPage", "items"],
|
|
201
|
+
"pageContractPath": { ... }
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Contract:**
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
- tag: items
|
|
209
|
+
type: subContract
|
|
210
|
+
repeated: true
|
|
211
|
+
trackBy: id
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Jay-HTML:**
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<div forEach="productPage.items" trackBy="id">
|
|
218
|
+
<div>{title}</div>
|
|
219
|
+
<!-- Context-relative path -->
|
|
220
|
+
</div>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Important:** Only the **first child** of a repeater node is converted. This first child serves as the template that gets repeated for each item in the array. Other sibling nodes are ignored as they are not part of the repeater pattern.
|
|
224
|
+
|
|
225
|
+
## Repeater Context
|
|
226
|
+
|
|
227
|
+
Repeaters change the path context for their children:
|
|
228
|
+
|
|
229
|
+
**Full Paths:**
|
|
230
|
+
|
|
231
|
+
- Repeater: `productPage.products`
|
|
232
|
+
- Child: `productPage.products.title`
|
|
233
|
+
|
|
234
|
+
**In Jay-HTML:**
|
|
235
|
+
|
|
236
|
+
```html
|
|
237
|
+
<div forEach="productPage.products" trackBy="id">
|
|
238
|
+
<div>{title}</div>
|
|
239
|
+
<!-- Not productPage.products.title -->
|
|
240
|
+
</div>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Nested Repeaters:**
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<div forEach="compKey.items" trackBy="id">
|
|
247
|
+
<div forEach="subItems" trackBy="id">
|
|
248
|
+
<!-- No prefix -->
|
|
249
|
+
<div>{name}</div>
|
|
250
|
+
<!-- Relative to subItems -->
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Validation Rules
|
|
256
|
+
|
|
257
|
+
1. **Property bindings are exclusive** - If ANY binding has a property, ALL must have properties
|
|
258
|
+
2. **Attributes + dynamic content allowed** - Multiple attribute bindings + dynamic content is valid
|
|
259
|
+
3. **Interactive must be pure** - Interactive bindings cannot have attribute or property
|
|
260
|
+
4. **One ref per node** - Only one interactive binding per node
|
|
261
|
+
|
|
262
|
+
## Vendor Document Type
|
|
263
|
+
|
|
264
|
+
The Figma vendor uses the `FigmaVendorDocument` type defined in `@jay-framework/editor-protocol`. This is the **single source of truth** for the document structure.
|
|
265
|
+
|
|
266
|
+
### For Figma Plugin Developers
|
|
267
|
+
|
|
268
|
+
Import the type from the editor protocol:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { FigmaVendorDocument } from '@jay-framework/editor-protocol';
|
|
272
|
+
|
|
273
|
+
// Your Figma plugin code
|
|
274
|
+
const vendorDoc: FigmaVendorDocument = {
|
|
275
|
+
type: selectedNode.type,
|
|
276
|
+
name: selectedNode.name,
|
|
277
|
+
children: selectedNode.children,
|
|
278
|
+
pluginData: {
|
|
279
|
+
'jpage': 'true',
|
|
280
|
+
'jay-layer-bindings': JSON.stringify([
|
|
281
|
+
{
|
|
282
|
+
pageContractPath: { ... },
|
|
283
|
+
tagPath: ['productPage', 'name'],
|
|
284
|
+
jayPageSectionId: '...'
|
|
285
|
+
}
|
|
286
|
+
])
|
|
287
|
+
},
|
|
288
|
+
// ... other Figma node properties
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
await editorProtocol.export({
|
|
292
|
+
vendorId: 'figma',
|
|
293
|
+
pageUrl: '/products/:slug',
|
|
294
|
+
vendorDoc,
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Plugin Data Format
|
|
299
|
+
|
|
300
|
+
**`jay-layer-bindings`** - Array of LayerBinding:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
type LayerBinding = {
|
|
304
|
+
pageContractPath: PageContractPath;
|
|
305
|
+
jayPageSectionId: string;
|
|
306
|
+
tagPath: string[];
|
|
307
|
+
attribute?: string;
|
|
308
|
+
property?: string;
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**`jpage`** - Marks top-level section as a Jay Page:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
pluginData: {
|
|
316
|
+
'jpage': 'true',
|
|
317
|
+
'urlRoute': '/products/:slug'
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**`semanticHtml`** - Specifies HTML tag:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
pluginData: {
|
|
325
|
+
'semanticHtml': 'img' // or 'button', 'input', etc.
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Usage Example
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { figmaVendor } from './vendors/figma';
|
|
333
|
+
|
|
334
|
+
const result = await figmaVendor.convertToBodyHtml(
|
|
335
|
+
vendorDoc,
|
|
336
|
+
'/products/:slug',
|
|
337
|
+
projectPage,
|
|
338
|
+
plugins,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
console.log(result.bodyHtml);
|
|
342
|
+
// <div data-figma-id="...">
|
|
343
|
+
// <div>{productPage.name}</div>
|
|
344
|
+
// <img src="{productPage.image}" />
|
|
345
|
+
// <div forEach="productPage.items" trackBy="id">
|
|
346
|
+
// <div>{title}</div>
|
|
347
|
+
// </div>
|
|
348
|
+
// </div>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Extending the Vendor
|
|
352
|
+
|
|
353
|
+
### Adding a New Binding Type
|
|
354
|
+
|
|
355
|
+
1. Update `BindingAnalysis` type in `types.ts`
|
|
356
|
+
2. Add analysis logic in `analyzeBindings()` in `binding-analysis.ts`
|
|
357
|
+
3. Add validation rules in `validateBindings()`
|
|
358
|
+
4. Handle new type in `convertRegularNode()` or create new converter
|
|
359
|
+
|
|
360
|
+
### Adding Node Type Support
|
|
361
|
+
|
|
362
|
+
1. Create converter in `converters/` (e.g., `my-node.ts`)
|
|
363
|
+
2. Import and call from `convertRegularNode()` in `index.ts`
|
|
364
|
+
3. Handle node-specific styling in converter
|
|
365
|
+
|
|
366
|
+
### Extending Path Resolution
|
|
367
|
+
|
|
368
|
+
Modify `resolveBinding()` in `binding-analysis.ts` to handle:
|
|
369
|
+
|
|
370
|
+
- New contract sources
|
|
371
|
+
- Custom path transformations
|
|
372
|
+
- Additional context types
|
|
373
|
+
|
|
374
|
+
## Known Limitations
|
|
375
|
+
|
|
376
|
+
1. **Variant Components:** Requires `componentPropertyDefinitions` in `FigmaVendorDocument`
|
|
377
|
+
2. **Page Contracts:** Page contract resolution not fully implemented
|
|
378
|
+
3. **Mixed Fonts:** Only single font per text node supported
|
|
379
|
+
4. **Error Recovery:** Logs warnings but doesn't have graceful fallbacks
|
|
380
|
+
|
|
381
|
+
## Testing
|
|
382
|
+
|
|
383
|
+
_To be added - See design-log/67 for test plan_
|
|
384
|
+
|
|
385
|
+
## Design Documentation
|
|
386
|
+
|
|
387
|
+
See `design-log/67 - Figma Vendor Conversion Algorithm` for:
|
|
388
|
+
|
|
389
|
+
- Complete design rationale
|
|
390
|
+
- Implementation details
|
|
391
|
+
- Examples and edge cases
|
|
392
|
+
- Trade-off decisions
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
**Last Updated:** January 12, 2026
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/jay-stack-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-jay-html": "^0.
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.
|
|
29
|
-
"@jay-framework/dev-server": "^0.
|
|
30
|
-
"@jay-framework/editor-server": "^0.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.
|
|
32
|
-
"@jay-framework/logger": "^0.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.
|
|
34
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
27
|
+
"@jay-framework/compiler-jay-html": "^0.14.0",
|
|
28
|
+
"@jay-framework/compiler-shared": "^0.14.0",
|
|
29
|
+
"@jay-framework/dev-server": "^0.14.0",
|
|
30
|
+
"@jay-framework/editor-server": "^0.14.0",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.14.0",
|
|
32
|
+
"@jay-framework/logger": "^0.14.0",
|
|
33
|
+
"@jay-framework/plugin-validator": "^0.14.0",
|
|
34
|
+
"@jay-framework/stack-server-runtime": "^0.14.0",
|
|
35
35
|
"chalk": "^4.1.2",
|
|
36
36
|
"commander": "^14.0.0",
|
|
37
37
|
"express": "^5.0.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"yaml": "^2.3.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@jay-framework/dev-environment": "^0.
|
|
43
|
+
"@jay-framework/dev-environment": "^0.14.0",
|
|
44
44
|
"@types/express": "^5.0.2",
|
|
45
45
|
"@types/node": "^22.15.21",
|
|
46
46
|
"nodemon": "^3.0.3",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Figma Vendor Test Fixtures
|
|
2
|
+
|
|
3
|
+
This directory contains fixture-based integration tests for the Figma vendor converter.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
Each fixture is a directory containing:
|
|
8
|
+
|
|
9
|
+
- **`page.figma.json`** - Input Figma document (serialized from Figma plugin)
|
|
10
|
+
- **`page.jay-contract`** - Contract defining data bindings and structure
|
|
11
|
+
- **`expected.jay-html`** - Expected HTML output after conversion
|
|
12
|
+
|
|
13
|
+
## Current Fixtures
|
|
14
|
+
|
|
15
|
+
### 1. `basic-text/` ✅ **Working**
|
|
16
|
+
|
|
17
|
+
Tests basic text rendering with multiple text nodes and font families.
|
|
18
|
+
|
|
19
|
+
**Features tested:**
|
|
20
|
+
|
|
21
|
+
- Static text conversion
|
|
22
|
+
- Font family collection (Inter)
|
|
23
|
+
- Font weight and size
|
|
24
|
+
- Text alignment
|
|
25
|
+
- Color conversion
|
|
26
|
+
- Basic FRAME container with children
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### 2. `button-with-variants/` ✅ **Working**
|
|
31
|
+
|
|
32
|
+
Tests variant components with multiple properties.
|
|
33
|
+
|
|
34
|
+
**Features tested:**
|
|
35
|
+
|
|
36
|
+
- COMPONENT_SET with variants
|
|
37
|
+
- Boolean variants (disabled: true/false)
|
|
38
|
+
- Enum variants (variant: primary/secondary)
|
|
39
|
+
- Property bindings
|
|
40
|
+
- Variant permutation generation
|
|
41
|
+
- Conditional rendering with `if` attributes
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### 3. `repeater-list/` ✅ **Working**
|
|
46
|
+
|
|
47
|
+
Tests repeater (forEach) functionality with nested bindings.
|
|
48
|
+
|
|
49
|
+
**Features tested:**
|
|
50
|
+
|
|
51
|
+
- Frame with repeater binding
|
|
52
|
+
- `forEach` attribute generation
|
|
53
|
+
- `trackBy` attribute
|
|
54
|
+
- Nested data bindings (items.title, items.description)
|
|
55
|
+
- Repeater context management
|
|
56
|
+
- Sub-contract with repeated items
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### 4. `complex-page/` ✅ **Working**
|
|
61
|
+
|
|
62
|
+
Tests a realistic product page with multiple features combined.
|
|
63
|
+
|
|
64
|
+
**Features tested:**
|
|
65
|
+
|
|
66
|
+
- Dynamic text content bindings (product.name, product.price)
|
|
67
|
+
- Nested repeater (product.reviews)
|
|
68
|
+
- Parameterized route (/products/:id)
|
|
69
|
+
- Multiple font families (Roboto)
|
|
70
|
+
- Complex contract structure with nested sub-contracts
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Adding New Fixtures
|
|
75
|
+
|
|
76
|
+
1. **Create a directory** under `fixtures/`:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
mkdir test/vendors/figma/fixtures/my-new-test
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
2. **Add required files**:
|
|
83
|
+
|
|
84
|
+
- `page.figma.json` - Figma document structure
|
|
85
|
+
- `expected.jay-html` - Expected HTML output
|
|
86
|
+
- `page.jay-contract` OR `page.conf.yaml` - Contract or plugin config
|
|
87
|
+
|
|
88
|
+
3. **Add fixture content** - You can:
|
|
89
|
+
|
|
90
|
+
- Export a real Figma document using the plugin
|
|
91
|
+
- Hand-craft a test case
|
|
92
|
+
- Copy and modify an existing fixture
|
|
93
|
+
|
|
94
|
+
4. **Run the test** to generate `actual-output.jay-html`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm test -- test/vendors/figma/fixtures.test.ts -t "my-new-test"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
5. **Review and copy** actual output to `expected.jay-html` if correct
|
|
101
|
+
|
|
102
|
+
6. **That's it!** The test automatically discovers all fixture directories - no code changes needed!
|
|
103
|
+
|
|
104
|
+
## Running Tests
|
|
105
|
+
|
|
106
|
+
Run all fixture tests (auto-discovers all fixture directories):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm test -- test/vendors/figma/fixtures.test.ts
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Run a specific fixture (via filtering):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm test -- test/vendors/figma/fixtures.test.ts -t "basic-text"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The test runner **automatically discovers** all directories under `fixtures/` - you don't need to manually register new fixtures!
|
|
119
|
+
|
|
120
|
+
## Tips
|
|
121
|
+
|
|
122
|
+
### Debugging Failed Tests
|
|
123
|
+
|
|
124
|
+
When a test fails, check the `actual-output.jay-html` file saved in the fixture directory.
|
|
125
|
+
|
|
126
|
+
### Contract Requirements
|
|
127
|
+
|
|
128
|
+
- Use `type: sub-contract` (not `subContract`)
|
|
129
|
+
- Include all fields referenced by `trackBy` attributes
|
|
130
|
+
- Check console warnings for validation errors
|
|
131
|
+
|
|
132
|
+
### Normalizing HTML
|
|
133
|
+
|
|
134
|
+
The test normalizes HTML before comparison to handle:
|
|
135
|
+
|
|
136
|
+
- Whitespace differences
|
|
137
|
+
- Comment removal
|
|
138
|
+
- Line break variations
|
|
139
|
+
|
|
140
|
+
## Test Coverage
|
|
141
|
+
|
|
142
|
+
Current fixtures cover:
|
|
143
|
+
|
|
144
|
+
- ✅ Static text rendering (basic-text)
|
|
145
|
+
- ✅ Font collection (basic-text)
|
|
146
|
+
- ✅ FRAME containers with layout (basic-text)
|
|
147
|
+
- ✅ Dynamic content bindings (repeater-list, complex-page, plugin-product-card)
|
|
148
|
+
- ✅ Variant components (button-with-variants)
|
|
149
|
+
- ✅ Repeaters with trackBy (repeater-list, complex-page)
|
|
150
|
+
- ✅ Nested sub-contracts (complex-page)
|
|
151
|
+
- ✅ Conditional rendering (button-with-variants)
|
|
152
|
+
- ✅ Plugin-based pages (plugin-product-card)
|
|
153
|
+
- ✅ Headless components (plugin-product-card)
|
|
154
|
+
|
|
155
|
+
**Current Status:** 5 fixtures fully working (22 passing tests total in vendor suite)
|
|
156
|
+
|
|
157
|
+
Missing coverage (potential future fixtures):
|
|
158
|
+
|
|
159
|
+
- ⬜ Interactive bindings (ref)
|
|
160
|
+
- ⬜ Dual bindings (data + interactive)
|
|
161
|
+
- ⬜ Error cases (invalid bindings, missing contracts)
|
|
162
|
+
- ⬜ Images with dynamic src bindings
|
|
163
|
+
- ⬜ Attribute bindings beyond variants
|
|
164
|
+
- ⬜ NPM-installed plugins (currently only local plugins tested)
|