@invarn/cibuild 2.1.9 → 2.2.1
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/dist/cli.cjs +108 -13
- package/dist/src/yaml/steps/structural-facts.d.ts +93 -2
- package/dist/src/yaml/steps/structural-facts.d.ts.map +1 -1
- package/dist/src/yaml/steps/structural-facts.js +0 -0
- package/dist/src/yaml/steps/structural-facts.test.js +183 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +13 -1
- package/package.json +1 -1
|
@@ -39,6 +39,12 @@ export interface LayoutNode {
|
|
|
39
39
|
/** Legacy clip hint (parent clips its children). The current emitter writes a
|
|
40
40
|
* constant false; used only for the legacy (no-`unclippedBounds`) fallback. */
|
|
41
41
|
clipsChildren: boolean;
|
|
42
|
+
/** Accessible text content. */
|
|
43
|
+
text?: string;
|
|
44
|
+
/** Fill (background) color, hex. */
|
|
45
|
+
fillColor?: string;
|
|
46
|
+
/** Text (foreground) color, hex. */
|
|
47
|
+
textColor?: string;
|
|
42
48
|
}
|
|
43
49
|
export interface LayoutDump {
|
|
44
50
|
/** The captured canvas / root bounds in px. */
|
|
@@ -185,7 +191,90 @@ export interface StructuralBlock {
|
|
|
185
191
|
summary: StructuralSummary;
|
|
186
192
|
facts: StructuralFact[];
|
|
187
193
|
geometry?: StructuralGeometry;
|
|
194
|
+
appearance?: StructuralAppearance;
|
|
188
195
|
}
|
|
196
|
+
/** Which reference the appearance facts were diffed against. `previous_render`
|
|
197
|
+
* facts are regressions/drift, NOT design divergences (the no-Figma path). */
|
|
198
|
+
export type AppearanceBaseline = 'figma' | 'previous_render';
|
|
199
|
+
export type AppearanceFact = {
|
|
200
|
+
kind: 'color';
|
|
201
|
+
elementId: string;
|
|
202
|
+
channel: 'fill' | 'text';
|
|
203
|
+
rendered: string;
|
|
204
|
+
reference: string;
|
|
205
|
+
/** CIEDE2000 perceptual distance — measurement only, no significance. */
|
|
206
|
+
deltaE: number;
|
|
207
|
+
} | {
|
|
208
|
+
kind: 'text_mismatch';
|
|
209
|
+
elementId: string;
|
|
210
|
+
rendered: string;
|
|
211
|
+
reference: string;
|
|
212
|
+
} | {
|
|
213
|
+
kind: 'text_missing';
|
|
214
|
+
elementId: string;
|
|
215
|
+
reference: string;
|
|
216
|
+
}
|
|
217
|
+
/** In the reference, not rendered. */
|
|
218
|
+
| {
|
|
219
|
+
kind: 'element_missing';
|
|
220
|
+
elementId: string;
|
|
221
|
+
}
|
|
222
|
+
/** Rendered, not in the reference. */
|
|
223
|
+
| {
|
|
224
|
+
kind: 'element_extra';
|
|
225
|
+
elementId: string;
|
|
226
|
+
};
|
|
227
|
+
export interface StructuralAppearance {
|
|
228
|
+
baseline: AppearanceBaseline;
|
|
229
|
+
facts: AppearanceFact[];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The reference side of the appearance diff, passed into `buildStructuralBlock`.
|
|
233
|
+
* `elementIds` is the reference element-id set (Figma node ids on the `figma`
|
|
234
|
+
* baseline; the previous render's element ids on `previous_render`). This slice
|
|
235
|
+
* derives presence only; the copy/color slices extend this with reference text /
|
|
236
|
+
* colors keyed by element id.
|
|
237
|
+
*/
|
|
238
|
+
export interface AppearanceReference {
|
|
239
|
+
baseline: AppearanceBaseline;
|
|
240
|
+
elementIds: string[];
|
|
241
|
+
/** Reference text per element id (Figma `characters` on the figma baseline; the
|
|
242
|
+
* previous render's text on `previous_render`). When supplied, copy facts are
|
|
243
|
+
* derived against the render's per-node text. Absent → presence facts only. */
|
|
244
|
+
text?: TextSource;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Element-presence facts from the set difference of the rendered element-id set
|
|
248
|
+
* and the reference element-id set, keyed on the stable element id shared with
|
|
249
|
+
* Signal B. An id present in the reference but not rendered is `element_missing`;
|
|
250
|
+
* an id rendered but not in the reference is `element_extra`.
|
|
251
|
+
*
|
|
252
|
+
* Pure and platform-agnostic: it takes two id collections and is order-
|
|
253
|
+
* independent — the result depends only on the two SETS, not on input order or
|
|
254
|
+
* duplicates. Output is sorted (missing before extra, each by id) so callers get
|
|
255
|
+
* a deterministic list. The deriver reports facts only; significance is a later,
|
|
256
|
+
* separately-wired concern.
|
|
257
|
+
*/
|
|
258
|
+
export declare function derivePresenceFacts(renderedIds: Iterable<string>, referenceIds: Iterable<string>): AppearanceFact[];
|
|
259
|
+
/** Per-element text content, keyed on the stable element id (rendered text from
|
|
260
|
+
* the layout dump's per-node `text`; reference text from the Figma node tree's
|
|
261
|
+
* `characters`). A missing or empty string means "no text signal for that
|
|
262
|
+
* element," which the deriver treats as absence, never a divergence. */
|
|
263
|
+
export type TextSource = Record<string, string | undefined>;
|
|
264
|
+
/**
|
|
265
|
+
* Copy/text facts from the per-element comparison of rendered text vs reference
|
|
266
|
+
* text, keyed on the stable element id shared with Signal B. For each element the
|
|
267
|
+
* REFERENCE declares text for: identical text → no fact; a different rendered
|
|
268
|
+
* string → `text_mismatch` (both strings attached); empty/absent rendered text →
|
|
269
|
+
* `text_missing`. An element with no reference text signal produces no fact —
|
|
270
|
+
* absence of a signal is never reported as a divergence, so a rendered string the
|
|
271
|
+
* reference never declared is silent.
|
|
272
|
+
*
|
|
273
|
+
* Pure and platform-agnostic; order-independent and deterministic (output sorted
|
|
274
|
+
* by element id). The deriver reports facts only; significance is a later,
|
|
275
|
+
* separately-wired concern.
|
|
276
|
+
*/
|
|
277
|
+
export declare function deriveCopyFacts(rendered: TextSource, reference: TextSource): AppearanceFact[];
|
|
189
278
|
/**
|
|
190
279
|
* Assemble the inline `structural` block (ADR-0004): Signal-A facts always, plus
|
|
191
280
|
* the Signal-B geometry block when `referenceGeometry` is supplied. Size (w/h)
|
|
@@ -195,7 +284,7 @@ export interface StructuralBlock {
|
|
|
195
284
|
export declare function buildStructuralBlock(dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
196
285
|
sizeTolerancePx?: number;
|
|
197
286
|
overhangTolerancePx?: number;
|
|
198
|
-
}): StructuralBlock;
|
|
287
|
+
}, appearanceReference?: AppearanceReference | null): StructuralBlock;
|
|
199
288
|
export interface ReviewRow {
|
|
200
289
|
element: string;
|
|
201
290
|
rendered: string | null;
|
|
@@ -265,10 +354,12 @@ export declare function getGeometryStageInternals(): {
|
|
|
265
354
|
buildStructuralBlock: (dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
266
355
|
sizeTolerancePx?: number;
|
|
267
356
|
overhangTolerancePx?: number;
|
|
268
|
-
}) => StructuralBlock;
|
|
357
|
+
}, appearanceReference?: AppearanceReference | null) => StructuralBlock;
|
|
269
358
|
buildReviewScaffold: (structural: {
|
|
270
359
|
facts?: StructuralFact[];
|
|
271
360
|
geometry?: StructuralGeometry | null;
|
|
272
361
|
}) => ReviewScaffold;
|
|
362
|
+
derivePresenceFacts: (renderedIds: Iterable<string>, referenceIds: Iterable<string>) => AppearanceFact[];
|
|
363
|
+
deriveCopyFacts: (rendered: TextSource, reference: TextSource) => AppearanceFact[];
|
|
273
364
|
};
|
|
274
365
|
//# sourceMappingURL=structural-facts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX;6CACyC;IACzC,MAAM,EAAE,YAAY,CAAC;IACrB;;;+EAG2E;IAC3E,eAAe,CAAC,EAAE,YAAY,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;oFACgF;IAChF,aAAa,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* overflowing voucher tag; the deriver must surface the bottom clip as a
|
|
9
9
|
* fact so it is never again skipped by eye.
|
|
10
10
|
*/
|
|
11
|
-
import { deriveStructuralFacts, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
|
|
11
|
+
import { deriveStructuralFacts, derivePresenceFacts, deriveCopyFacts, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
|
|
12
12
|
// 200x400 canvas in px for all fixtures unless noted.
|
|
13
13
|
const CANVAS = { left: 0, top: 0, right: 200, bottom: 400 };
|
|
14
14
|
describe('deriveStructuralFacts — containment', () => {
|
|
@@ -484,6 +484,142 @@ describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
|
484
484
|
});
|
|
485
485
|
});
|
|
486
486
|
});
|
|
487
|
+
// ---- Signal C: element-presence facts (appearance container) ----
|
|
488
|
+
describe('derivePresenceFacts — set difference over element ids', () => {
|
|
489
|
+
test('equal element sets produce no facts', () => {
|
|
490
|
+
expect(derivePresenceFacts(['a', 'b', 'c'], ['a', 'b', 'c'])).toEqual([]);
|
|
491
|
+
});
|
|
492
|
+
test('an id only in the reference yields element_missing', () => {
|
|
493
|
+
// rendered lacks `voucher`, which the reference declares.
|
|
494
|
+
expect(derivePresenceFacts(['banner'], ['banner', 'voucher'])).toEqual([
|
|
495
|
+
{ kind: 'element_missing', elementId: 'voucher' },
|
|
496
|
+
]);
|
|
497
|
+
});
|
|
498
|
+
test('an id only in the render yields element_extra', () => {
|
|
499
|
+
// rendered has an extra `debugOverlay` the reference does not declare.
|
|
500
|
+
expect(derivePresenceFacts(['banner', 'debugOverlay'], ['banner'])).toEqual([
|
|
501
|
+
{ kind: 'element_extra', elementId: 'debugOverlay' },
|
|
502
|
+
]);
|
|
503
|
+
});
|
|
504
|
+
test('reports both a missing and an extra id, missing before extra', () => {
|
|
505
|
+
expect(derivePresenceFacts(['banner', 'extra'], ['banner', 'gone'])).toEqual([
|
|
506
|
+
{ kind: 'element_missing', elementId: 'gone' },
|
|
507
|
+
{ kind: 'element_extra', elementId: 'extra' },
|
|
508
|
+
]);
|
|
509
|
+
});
|
|
510
|
+
test('result is order-independent — same sets in any input order give the same facts', () => {
|
|
511
|
+
const a = derivePresenceFacts(['a', 'b', 'c'], ['b', 'd', 'a']);
|
|
512
|
+
const b = derivePresenceFacts(['c', 'b', 'a'], ['d', 'a', 'b']);
|
|
513
|
+
expect(a).toEqual(b);
|
|
514
|
+
// d missing, c extra — independent of how the ids were ordered or duplicated.
|
|
515
|
+
expect(derivePresenceFacts(['c', 'c', 'b', 'a'], ['a', 'b', 'd', 'd'])).toEqual([
|
|
516
|
+
{ kind: 'element_missing', elementId: 'd' },
|
|
517
|
+
{ kind: 'element_extra', elementId: 'c' },
|
|
518
|
+
]);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
describe('deriveCopyFacts — rendered vs reference text per element', () => {
|
|
522
|
+
test('identical text produces no fact', () => {
|
|
523
|
+
expect(deriveCopyFacts({ title: '5 EUR off' }, { title: '5 EUR off' })).toEqual([]);
|
|
524
|
+
});
|
|
525
|
+
test('differing text yields text_mismatch with both strings', () => {
|
|
526
|
+
expect(deriveCopyFacts({ title: '5 EUR' }, { title: '5 EUR off' })).toEqual([{ kind: 'text_mismatch', elementId: 'title', rendered: '5 EUR', reference: '5 EUR off' }]);
|
|
527
|
+
});
|
|
528
|
+
test('reference text with empty/absent render yields text_missing', () => {
|
|
529
|
+
// absent render entry
|
|
530
|
+
expect(deriveCopyFacts({}, { cta: 'Shop now' })).toEqual([
|
|
531
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
532
|
+
]);
|
|
533
|
+
// empty-string render entry counts as no rendered text
|
|
534
|
+
expect(deriveCopyFacts({ cta: '' }, { cta: 'Shop now' })).toEqual([
|
|
535
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
536
|
+
]);
|
|
537
|
+
});
|
|
538
|
+
test('an element with no reference text produces no fact (absence of signal is not a divergence)', () => {
|
|
539
|
+
// render has text the reference never declared → no fact (no reference signal)
|
|
540
|
+
expect(deriveCopyFacts({ debugLabel: 'x' }, {})).toEqual([]);
|
|
541
|
+
// reference declares an empty string → no signal → no fact regardless of render
|
|
542
|
+
expect(deriveCopyFacts({ note: 'hi' }, { note: '' })).toEqual([]);
|
|
543
|
+
});
|
|
544
|
+
test('result is order-independent and deterministic (sorted by element id)', () => {
|
|
545
|
+
const a = deriveCopyFacts({ b: 'r', a: 'wrong' }, { a: 'right', b: 'r', c: 'gone' });
|
|
546
|
+
const b = deriveCopyFacts({ a: 'wrong', b: 'r' }, { c: 'gone', b: 'r', a: 'right' });
|
|
547
|
+
expect(a).toEqual(b);
|
|
548
|
+
expect(a).toEqual([
|
|
549
|
+
{ kind: 'text_mismatch', elementId: 'a', rendered: 'wrong', reference: 'right' },
|
|
550
|
+
{ kind: 'text_missing', elementId: 'c', reference: 'gone' },
|
|
551
|
+
]);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
describe('buildStructuralBlock — appearance container (Signal C, additive)', () => {
|
|
555
|
+
test('no appearanceReference → no appearance container; facts + geometry unchanged', () => {
|
|
556
|
+
const withC = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
557
|
+
const without = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, null);
|
|
558
|
+
expect(withC.appearance).toBeUndefined();
|
|
559
|
+
// Signal A facts and Signal B geometry are byte-identical whether or not the
|
|
560
|
+
// appearance arg is threaded.
|
|
561
|
+
expect(without).toEqual(withC);
|
|
562
|
+
});
|
|
563
|
+
test('with a figma appearanceReference → presence facts from rendered vs reference ids', () => {
|
|
564
|
+
// dump node ids: node-1, banner, voucher, copy; reference declares banner+voucher.
|
|
565
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, {
|
|
566
|
+
baseline: 'figma',
|
|
567
|
+
elementIds: ['banner', 'voucher'],
|
|
568
|
+
});
|
|
569
|
+
expect(block.appearance).toEqual({
|
|
570
|
+
baseline: 'figma',
|
|
571
|
+
facts: [
|
|
572
|
+
{ kind: 'element_extra', elementId: 'copy' },
|
|
573
|
+
{ kind: 'element_extra', elementId: 'node-1' },
|
|
574
|
+
],
|
|
575
|
+
});
|
|
576
|
+
// Additive: the existing Signal A facts and Signal B geometry are untouched.
|
|
577
|
+
expect(block.facts).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2).facts);
|
|
578
|
+
expect(block.geometry).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2).geometry);
|
|
579
|
+
});
|
|
580
|
+
test('an appearanceReference matching the render → ran-and-clean (baseline, zero facts)', () => {
|
|
581
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, null, 2, undefined, {
|
|
582
|
+
baseline: 'figma',
|
|
583
|
+
elementIds: ['node-1', 'banner', 'voucher', 'copy'],
|
|
584
|
+
});
|
|
585
|
+
expect(block.appearance).toEqual({ baseline: 'figma', facts: [] });
|
|
586
|
+
});
|
|
587
|
+
// A dump whose per-node `text` (Signal C render-side enrichment) drives the
|
|
588
|
+
// copy-fact comparison: `title` renders the wrong string, `cta` renders no
|
|
589
|
+
// text, and `debugOverlay` is an extra element absent from the reference.
|
|
590
|
+
const COPY_DUMP = {
|
|
591
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
592
|
+
nodes: [
|
|
593
|
+
{ id: 'title', bounds: { left: 0, top: 0, right: 100, bottom: 20 }, parentId: null, clipsChildren: false, text: '5 EUR' },
|
|
594
|
+
{ id: 'cta', bounds: { left: 0, top: 30, right: 100, bottom: 50 }, parentId: null, clipsChildren: false },
|
|
595
|
+
{ id: 'debugOverlay', bounds: { left: 0, top: 60, right: 100, bottom: 80 }, parentId: null, clipsChildren: false },
|
|
596
|
+
],
|
|
597
|
+
};
|
|
598
|
+
test('with reference text → copy facts are appended after presence facts', () => {
|
|
599
|
+
const block = buildStructuralBlock(COPY_DUMP, null, 2, undefined, {
|
|
600
|
+
baseline: 'figma',
|
|
601
|
+
elementIds: ['title', 'cta'],
|
|
602
|
+
text: { title: '5 EUR off', cta: 'Shop now' },
|
|
603
|
+
});
|
|
604
|
+
expect(block.appearance).toEqual({
|
|
605
|
+
baseline: 'figma',
|
|
606
|
+
facts: [
|
|
607
|
+
// presence first (debugOverlay is rendered but not in the reference)
|
|
608
|
+
{ kind: 'element_extra', elementId: 'debugOverlay' },
|
|
609
|
+
// then copy (sorted by id: cta missing, title mismatched)
|
|
610
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
611
|
+
{ kind: 'text_mismatch', elementId: 'title', rendered: '5 EUR', reference: '5 EUR off' },
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
test('an appearanceReference without text → presence only (issue-01 behavior unchanged)', () => {
|
|
616
|
+
const block = buildStructuralBlock(COPY_DUMP, null, 2, undefined, {
|
|
617
|
+
baseline: 'figma',
|
|
618
|
+
elementIds: ['title', 'cta', 'debugOverlay'],
|
|
619
|
+
});
|
|
620
|
+
expect(block.appearance).toEqual({ baseline: 'figma', facts: [] });
|
|
621
|
+
});
|
|
622
|
+
});
|
|
487
623
|
describe('embedded geometry stage parity', () => {
|
|
488
624
|
// The render ships a JS-source port of the differ + assembler. It must produce
|
|
489
625
|
// byte-identical blocks to the canonical typed module, or run-side structural
|
|
@@ -494,6 +630,52 @@ describe('embedded geometry stage parity', () => {
|
|
|
494
630
|
const reference = { card: { width: 328, height: 72 } };
|
|
495
631
|
expect(embedded.diffKeyGeometry(generated, reference)).toEqual(diffKeyGeometry(generated, reference));
|
|
496
632
|
});
|
|
633
|
+
test('embedded derivePresenceFacts matches the module (Signal C)', () => {
|
|
634
|
+
const cases = [
|
|
635
|
+
[['a', 'b', 'c'], ['a', 'b', 'c']],
|
|
636
|
+
[['banner'], ['banner', 'voucher']],
|
|
637
|
+
[['banner', 'debugOverlay'], ['banner']],
|
|
638
|
+
[['c', 'b', 'a'], ['d', 'a', 'b']],
|
|
639
|
+
[[], ['x', 'y']],
|
|
640
|
+
[['x', 'y'], []],
|
|
641
|
+
];
|
|
642
|
+
for (const [rendered, reference] of cases) {
|
|
643
|
+
expect(embedded.derivePresenceFacts(rendered, reference)).toEqual(derivePresenceFacts(rendered, reference));
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
test('embedded buildStructuralBlock matches the module with an appearanceReference', () => {
|
|
647
|
+
const appearanceReference = { baseline: 'figma', elementIds: ['banner', 'voucher'] };
|
|
648
|
+
expect(embedded.buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, appearanceReference));
|
|
649
|
+
});
|
|
650
|
+
test('embedded deriveCopyFacts matches the module (Signal C)', () => {
|
|
651
|
+
const cases = [
|
|
652
|
+
[{ title: 'a' }, { title: 'a' }],
|
|
653
|
+
[{ title: 'a' }, { title: 'b' }],
|
|
654
|
+
[{}, { cta: 'Shop now' }],
|
|
655
|
+
[{ cta: '' }, { cta: 'Shop now' }],
|
|
656
|
+
[{ debugLabel: 'x' }, {}],
|
|
657
|
+
[{ note: 'hi' }, { note: '' }],
|
|
658
|
+
[{ b: 'r', a: 'wrong' }, { a: 'right', b: 'r', c: 'gone' }],
|
|
659
|
+
];
|
|
660
|
+
for (const [rendered, reference] of cases) {
|
|
661
|
+
expect(embedded.deriveCopyFacts(rendered, reference)).toEqual(deriveCopyFacts(rendered, reference));
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
test('embedded buildStructuralBlock matches the module with an appearanceReference carrying text', () => {
|
|
665
|
+
const dumpWithText = {
|
|
666
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
667
|
+
nodes: [
|
|
668
|
+
{ id: 'title', bounds: { left: 0, top: 0, right: 100, bottom: 20 }, parentId: null, clipsChildren: false, text: '5 EUR' },
|
|
669
|
+
{ id: 'cta', bounds: { left: 0, top: 30, right: 100, bottom: 50 }, parentId: null, clipsChildren: false },
|
|
670
|
+
],
|
|
671
|
+
};
|
|
672
|
+
const appearanceReference = {
|
|
673
|
+
baseline: 'figma',
|
|
674
|
+
elementIds: ['title', 'cta'],
|
|
675
|
+
text: { title: '5 EUR off', cta: 'Shop now' },
|
|
676
|
+
};
|
|
677
|
+
expect(embedded.buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference));
|
|
678
|
+
});
|
|
497
679
|
const parityCases = [
|
|
498
680
|
{ name: 'factsOnly', dump: HUB_PROMO_DUMP, ref: null },
|
|
499
681
|
{ name: 'withGeometry', dump: HUB_PROMO_DUMP, ref: HUB_PROMO_REFERENCE },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-fidelity-render-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAKpE,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"ui-fidelity-render-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAKpE,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AA0wBD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,CAYrF;AAED;;;GAGG;AACH,qBAAa,mCAAoC,SAAQ,gBAAgB;IACvE,yBAAyB,CACvB,OAAO,EAAE,6BAA6B,EACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAepB,OAAO,CACX,MAAM,EAAE,6BAA6B,EACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAgBpB"}
|
|
@@ -95,6 +95,10 @@ var buildError = null;
|
|
|
95
95
|
// read by the structural assembler. Null when the run declared none → Signal B
|
|
96
96
|
// is skipped and the structural block carries facts only.
|
|
97
97
|
var referenceGeometry = null;
|
|
98
|
+
// params.reference_text (Signal C: Figma characters per element id per screen),
|
|
99
|
+
// set in main() and read by the structural assembler. Null when none declared →
|
|
100
|
+
// the appearance container carries presence facts only (no copy facts).
|
|
101
|
+
var referenceText = null;
|
|
98
102
|
|
|
99
103
|
function log(message) {
|
|
100
104
|
console.log('[ui-fidelity-render-android] ' + message);
|
|
@@ -607,7 +611,7 @@ function renderScreens(renderable, projectRoot) {
|
|
|
607
611
|
// geometry deltas vs reference_geometry) onto the screen entry, so the
|
|
608
612
|
// judge reads the signals from protocol-result.json without fetching
|
|
609
613
|
// artifacts. CONFIG.scale is the render density (dp = px / scale).
|
|
610
|
-
assembleStructural(entry, layoutSource, referenceGeometry, CONFIG.scale);
|
|
614
|
+
assembleStructural(entry, layoutSource, referenceGeometry, CONFIG.scale, referenceText);
|
|
611
615
|
}
|
|
612
616
|
});
|
|
613
617
|
} finally {
|
|
@@ -673,6 +677,14 @@ function main() {
|
|
|
673
677
|
!Array.isArray(params.reference_geometry)
|
|
674
678
|
? params.reference_geometry
|
|
675
679
|
: null;
|
|
680
|
+
// Signal-C copy reference: { "<Screen>": { "<elementId>": "<text>" } } — the
|
|
681
|
+
// Figma characters mapped to element ids. Absent → presence facts only.
|
|
682
|
+
referenceText =
|
|
683
|
+
params.reference_text &&
|
|
684
|
+
typeof params.reference_text === 'object' &&
|
|
685
|
+
!Array.isArray(params.reference_text)
|
|
686
|
+
? params.reference_text
|
|
687
|
+
: null;
|
|
676
688
|
|
|
677
689
|
var screens = Object.keys(params.screens);
|
|
678
690
|
currentEntries = screens.map(function (screen) {
|