@invarn/cibuild 2.2.0 → 2.2.2
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 +238 -24
- package/dist/src/yaml/steps/structural-facts.d.ts +83 -0
- 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 +320 -2
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +33 -1
- package/package.json +1 -1
|
@@ -224,9 +224,25 @@ export type AppearanceFact = {
|
|
|
224
224
|
kind: 'element_extra';
|
|
225
225
|
elementId: string;
|
|
226
226
|
};
|
|
227
|
+
/**
|
|
228
|
+
* A reference-shaped snapshot of the render's OWN appearance — the element-id
|
|
229
|
+
* set plus per-element text/colors it produced. Persisted in the structural
|
|
230
|
+
* block so a later no-Figma render can use it as the `previous_render` baseline
|
|
231
|
+
* (the colors/text/ids to diff against), mirroring how Signal B's geometry
|
|
232
|
+
* baseline reuses the previous render's persisted `geometry`. text/color are
|
|
233
|
+
* present only for the elements that exposed them.
|
|
234
|
+
*/
|
|
235
|
+
export interface AppearanceRendered {
|
|
236
|
+
elementIds: string[];
|
|
237
|
+
text?: TextSource;
|
|
238
|
+
color?: ColorSource;
|
|
239
|
+
}
|
|
227
240
|
export interface StructuralAppearance {
|
|
228
241
|
baseline: AppearanceBaseline;
|
|
229
242
|
facts: AppearanceFact[];
|
|
243
|
+
/** The render's own appearance, persisted as the baseline source for the next
|
|
244
|
+
* no-Figma render. Present whenever Signal C ran. */
|
|
245
|
+
rendered?: AppearanceRendered;
|
|
230
246
|
}
|
|
231
247
|
/**
|
|
232
248
|
* The reference side of the appearance diff, passed into `buildStructuralBlock`.
|
|
@@ -238,6 +254,14 @@ export interface StructuralAppearance {
|
|
|
238
254
|
export interface AppearanceReference {
|
|
239
255
|
baseline: AppearanceBaseline;
|
|
240
256
|
elementIds: string[];
|
|
257
|
+
/** Reference text per element id (Figma `characters` on the figma baseline; the
|
|
258
|
+
* previous render's text on `previous_render`). When supplied, copy facts are
|
|
259
|
+
* derived against the render's per-node text. Absent → presence facts only. */
|
|
260
|
+
text?: TextSource;
|
|
261
|
+
/** Reference fill/text color per element id (Figma `fills` on the figma
|
|
262
|
+
* baseline; the previous render's colors on `previous_render`). When supplied,
|
|
263
|
+
* color facts are derived against the render's per-node colors. */
|
|
264
|
+
color?: ColorSource;
|
|
241
265
|
}
|
|
242
266
|
/**
|
|
243
267
|
* Element-presence facts from the set difference of the rendered element-id set
|
|
@@ -252,6 +276,61 @@ export interface AppearanceReference {
|
|
|
252
276
|
* separately-wired concern.
|
|
253
277
|
*/
|
|
254
278
|
export declare function derivePresenceFacts(renderedIds: Iterable<string>, referenceIds: Iterable<string>): AppearanceFact[];
|
|
279
|
+
/** Per-element text content, keyed on the stable element id (rendered text from
|
|
280
|
+
* the layout dump's per-node `text`; reference text from the Figma node tree's
|
|
281
|
+
* `characters`). A missing or empty string means "no text signal for that
|
|
282
|
+
* element," which the deriver treats as absence, never a divergence. */
|
|
283
|
+
export type TextSource = Record<string, string | undefined>;
|
|
284
|
+
/**
|
|
285
|
+
* Copy/text facts from the per-element comparison of rendered text vs reference
|
|
286
|
+
* text, keyed on the stable element id shared with Signal B. For each element the
|
|
287
|
+
* REFERENCE declares text for: identical text → no fact; a different rendered
|
|
288
|
+
* string → `text_mismatch` (both strings attached); empty/absent rendered text →
|
|
289
|
+
* `text_missing`. An element with no reference text signal produces no fact —
|
|
290
|
+
* absence of a signal is never reported as a divergence, so a rendered string the
|
|
291
|
+
* reference never declared is silent.
|
|
292
|
+
*
|
|
293
|
+
* Pure and platform-agnostic; order-independent and deterministic (output sorted
|
|
294
|
+
* by element id). The deriver reports facts only; significance is a later,
|
|
295
|
+
* separately-wired concern.
|
|
296
|
+
*/
|
|
297
|
+
export declare function deriveCopyFacts(rendered: TextSource, reference: TextSource): AppearanceFact[];
|
|
298
|
+
/** CIELAB triple [L, a, b]. */
|
|
299
|
+
export type Lab = [number, number, number];
|
|
300
|
+
/** One element's rendered/reference colors, by channel. A missing channel is
|
|
301
|
+
* "no color signal" for that element/channel — never a divergence. */
|
|
302
|
+
export interface ElementColors {
|
|
303
|
+
fill?: string;
|
|
304
|
+
text?: string;
|
|
305
|
+
}
|
|
306
|
+
export type ColorSource = Record<string, ElementColors>;
|
|
307
|
+
/**
|
|
308
|
+
* Convert an sRGB hex string to CIELAB (D65). White → ~[100,0,0], black → L 0.
|
|
309
|
+
* Returns [0,0,0] for an unparseable string so a malformed color never throws
|
|
310
|
+
* (the deriver guards on signal presence separately).
|
|
311
|
+
*/
|
|
312
|
+
export declare function hexToLab(hex: string): Lab;
|
|
313
|
+
/**
|
|
314
|
+
* CIEDE2000 colour-difference ΔE00 between two CIELAB colours (kL=kC=kH=1).
|
|
315
|
+
* Implements Sharma, Wu & Dalal (2005); validated against their supplementary
|
|
316
|
+
* test data. Pure and symmetric; identical colours give 0.
|
|
317
|
+
*/
|
|
318
|
+
export declare function ciede2000(lab1: Lab, lab2: Lab): number;
|
|
319
|
+
/**
|
|
320
|
+
* Color facts from the per-element, per-channel comparison of rendered vs
|
|
321
|
+
* reference color, keyed on the stable element id shared with Signal B. For each
|
|
322
|
+
* element/channel the REFERENCE declares a color for and the render also exposes
|
|
323
|
+
* one: identical color → no fact; any divergence → a `color` fact carrying the
|
|
324
|
+
* channel (fill vs text), both hex values, and the CIEDE2000 ΔE as the metric.
|
|
325
|
+
* Fill and text are distinct channels, so a correct background with a wrong text
|
|
326
|
+
* color and the reverse are separate facts, never collapsed. A missing rendered
|
|
327
|
+
* or reference color for a channel is no signal → no fact.
|
|
328
|
+
*
|
|
329
|
+
* Pure and platform-agnostic; order-independent and deterministic (output sorted
|
|
330
|
+
* by element id then channel, fill before text). Measurement only — significance
|
|
331
|
+
* is a later, separately-wired concern.
|
|
332
|
+
*/
|
|
333
|
+
export declare function deriveColorFacts(rendered: ColorSource, reference: ColorSource): AppearanceFact[];
|
|
255
334
|
/**
|
|
256
335
|
* Assemble the inline `structural` block (ADR-0004): Signal-A facts always, plus
|
|
257
336
|
* the Signal-B geometry block when `referenceGeometry` is supplied. Size (w/h)
|
|
@@ -337,5 +416,9 @@ export declare function getGeometryStageInternals(): {
|
|
|
337
416
|
geometry?: StructuralGeometry | null;
|
|
338
417
|
}) => ReviewScaffold;
|
|
339
418
|
derivePresenceFacts: (renderedIds: Iterable<string>, referenceIds: Iterable<string>) => AppearanceFact[];
|
|
419
|
+
deriveCopyFacts: (rendered: TextSource, reference: TextSource) => AppearanceFact[];
|
|
420
|
+
deriveColorFacts: (rendered: ColorSource, reference: ColorSource) => AppearanceFact[];
|
|
421
|
+
ciede2000: (lab1: Lab, lab2: Lab) => number;
|
|
422
|
+
hexToLab: (hex: string) => Lab;
|
|
340
423
|
};
|
|
341
424
|
//# 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":"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
|
|
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, derivePresenceFacts, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
|
|
11
|
+
import { deriveStructuralFacts, derivePresenceFacts, deriveCopyFacts, deriveColorFacts, ciede2000, hexToLab, 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', () => {
|
|
@@ -518,6 +518,125 @@ describe('derivePresenceFacts — set difference over element ids', () => {
|
|
|
518
518
|
]);
|
|
519
519
|
});
|
|
520
520
|
});
|
|
521
|
+
describe('ciede2000 — CIEDE2000 ΔE against the Sharma supplementary test data', () => {
|
|
522
|
+
// Canonical test vectors from Sharma, Wu & Dalal, "The CIEDE2000 Color-
|
|
523
|
+
// Difference Formula: Implementation Notes, Supplementary Test Data, and
|
|
524
|
+
// Mathematical Observations" (2005). Each row: Lab1, Lab2, expected ΔE00.
|
|
525
|
+
// These exercise the hue-discontinuity and high-chroma edge cases an
|
|
526
|
+
// incorrect implementation gets wrong.
|
|
527
|
+
const SHARMA = [
|
|
528
|
+
[[50, 2.6772, -79.7751], [50, 0, -82.7485], 2.0425],
|
|
529
|
+
[[50, 3.1571, -77.2803], [50, 0, -82.7485], 2.8615],
|
|
530
|
+
[[50, 2.8361, -74.02], [50, 0, -82.7485], 3.4412],
|
|
531
|
+
[[50, -1.3802, -84.2814], [50, 0, -82.7485], 1.0],
|
|
532
|
+
[[50, -1.1848, -84.8006], [50, 0, -82.7485], 1.0],
|
|
533
|
+
[[50, 0, 0], [50, -1, 2], 2.3669],
|
|
534
|
+
[[50, 2.49, -0.001], [50, -2.49, 0.0009], 7.1792],
|
|
535
|
+
[[50, 2.49, -0.001], [50, -2.49, 0.0011], 7.2195],
|
|
536
|
+
[[50, -0.001, 2.49], [50, 0.0009, -2.49], 4.8045],
|
|
537
|
+
[[50, 2.5, 0], [50, 0, -2.5], 4.3065],
|
|
538
|
+
[[50, 2.5, 0], [73, 25, -18], 27.1492],
|
|
539
|
+
[[50, 2.5, 0], [50, 3.1736, 0.5854], 1.0],
|
|
540
|
+
[[60.2574, -34.0099, 36.2677], [60.4626, -34.1751, 39.4387], 1.2644],
|
|
541
|
+
[[63.0109, -31.0961, -5.8663], [62.8187, -29.7946, -4.0864], 1.263],
|
|
542
|
+
[[22.7233, 20.0904, -46.694], [23.0331, 14.973, -42.5619], 2.0373],
|
|
543
|
+
[[90.8027, -2.0831, 1.441], [91.1528, -1.6435, 0.0447], 1.4441],
|
|
544
|
+
[[2.0776, 0.0795, -1.135], [0.9033, -0.0636, -0.5514], 0.9082],
|
|
545
|
+
];
|
|
546
|
+
for (const [lab1, lab2, expected] of SHARMA) {
|
|
547
|
+
test(`ΔE(${lab1.join(',')} ; ${lab2.join(',')}) ≈ ${expected}`, () => {
|
|
548
|
+
expect(ciede2000(lab1, lab2)).toBeCloseTo(expected, 4);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
test('identical colors have ΔE 0 and the metric is symmetric', () => {
|
|
552
|
+
expect(ciede2000([42, 7, -13], [42, 7, -13])).toBe(0);
|
|
553
|
+
expect(ciede2000([50, 2.5, 0], [73, 25, -18])).toBeCloseTo(ciede2000([73, 25, -18], [50, 2.5, 0]), 10);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
describe('hexToLab — sRGB hex → CIELAB (D65)', () => {
|
|
557
|
+
test('white maps to L≈100, a≈0, b≈0', () => {
|
|
558
|
+
const [L, a, b] = hexToLab('#ffffff');
|
|
559
|
+
expect(L).toBeCloseTo(100, 3);
|
|
560
|
+
expect(a).toBeCloseTo(0, 3);
|
|
561
|
+
expect(b).toBeCloseTo(0, 3);
|
|
562
|
+
});
|
|
563
|
+
test('black maps to L≈0', () => {
|
|
564
|
+
const [L] = hexToLab('#000000');
|
|
565
|
+
expect(L).toBeCloseTo(0, 3);
|
|
566
|
+
});
|
|
567
|
+
test('accepts hex with or without a leading # and is case-insensitive', () => {
|
|
568
|
+
expect(hexToLab('FFFFFF')).toEqual(hexToLab('#ffffff'));
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
describe('deriveColorFacts — rendered vs reference color per element/channel', () => {
|
|
572
|
+
test('matching colors produce no fact', () => {
|
|
573
|
+
expect(deriveColorFacts({ card: { fill: '#ff0000', text: '#000000' } }, { card: { fill: '#ff0000', text: '#000000' } })).toEqual([]);
|
|
574
|
+
// case-insensitive match
|
|
575
|
+
expect(deriveColorFacts({ card: { fill: '#FF0000' } }, { card: { fill: '#ff0000' } })).toEqual([]);
|
|
576
|
+
});
|
|
577
|
+
test('a fill divergence yields one fill-channel fact with the correct ΔE', () => {
|
|
578
|
+
const facts = deriveColorFacts({ card: { fill: '#ff0000' } }, { card: { fill: '#00ff00' } });
|
|
579
|
+
expect(facts).toHaveLength(1);
|
|
580
|
+
expect(facts[0]).toMatchObject({ kind: 'color', elementId: 'card', channel: 'fill', rendered: '#ff0000', reference: '#00ff00' });
|
|
581
|
+
expect(facts[0].deltaE).toBeCloseTo(ciede2000(hexToLab('#ff0000'), hexToLab('#00ff00')), 10);
|
|
582
|
+
});
|
|
583
|
+
test('fill and text are distinct channels — a wrong text color does not collapse with a correct fill', () => {
|
|
584
|
+
const facts = deriveColorFacts({ card: { fill: '#ffffff', text: '#111111' } }, { card: { fill: '#ffffff', text: '#000000' } });
|
|
585
|
+
expect(facts).toHaveLength(1);
|
|
586
|
+
expect(facts[0]).toMatchObject({ kind: 'color', elementId: 'card', channel: 'text', rendered: '#111111', reference: '#000000' });
|
|
587
|
+
});
|
|
588
|
+
test('both channels diverging yield two facts, fill before text', () => {
|
|
589
|
+
const facts = deriveColorFacts({ card: { fill: '#ff0000', text: '#111111' } }, { card: { fill: '#00ff00', text: '#000000' } });
|
|
590
|
+
expect(facts.map((f) => f.channel)).toEqual(['fill', 'text']);
|
|
591
|
+
});
|
|
592
|
+
test('an element/channel with no color signal produces no fact', () => {
|
|
593
|
+
// reference declares a fill the render never exposes → no signal → no fact
|
|
594
|
+
expect(deriveColorFacts({ card: {} }, { card: { fill: '#ff0000' } })).toEqual([]);
|
|
595
|
+
// render has a color the reference never declared → no reference signal → no fact
|
|
596
|
+
expect(deriveColorFacts({ card: { text: '#abcdef' } }, { card: { fill: '#ff0000' } })).toEqual([]);
|
|
597
|
+
// element absent from the reference entirely → no fact
|
|
598
|
+
expect(deriveColorFacts({ extra: { fill: '#ff0000' } }, {})).toEqual([]);
|
|
599
|
+
});
|
|
600
|
+
test('result is order-independent and deterministic (sorted by element id)', () => {
|
|
601
|
+
const a = deriveColorFacts({ b: { fill: '#ff0000' }, a: { fill: '#000000' } }, { a: { fill: '#ffffff' }, b: { fill: '#ff0000' } });
|
|
602
|
+
const b = deriveColorFacts({ a: { fill: '#000000' }, b: { fill: '#ff0000' } }, { b: { fill: '#ff0000' }, a: { fill: '#ffffff' } });
|
|
603
|
+
expect(a).toEqual(b);
|
|
604
|
+
expect(a.map((f) => f.elementId)).toEqual(['a']);
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
describe('deriveCopyFacts — rendered vs reference text per element', () => {
|
|
608
|
+
test('identical text produces no fact', () => {
|
|
609
|
+
expect(deriveCopyFacts({ title: '5 EUR off' }, { title: '5 EUR off' })).toEqual([]);
|
|
610
|
+
});
|
|
611
|
+
test('differing text yields text_mismatch with both strings', () => {
|
|
612
|
+
expect(deriveCopyFacts({ title: '5 EUR' }, { title: '5 EUR off' })).toEqual([{ kind: 'text_mismatch', elementId: 'title', rendered: '5 EUR', reference: '5 EUR off' }]);
|
|
613
|
+
});
|
|
614
|
+
test('reference text with empty/absent render yields text_missing', () => {
|
|
615
|
+
// absent render entry
|
|
616
|
+
expect(deriveCopyFacts({}, { cta: 'Shop now' })).toEqual([
|
|
617
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
618
|
+
]);
|
|
619
|
+
// empty-string render entry counts as no rendered text
|
|
620
|
+
expect(deriveCopyFacts({ cta: '' }, { cta: 'Shop now' })).toEqual([
|
|
621
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
622
|
+
]);
|
|
623
|
+
});
|
|
624
|
+
test('an element with no reference text produces no fact (absence of signal is not a divergence)', () => {
|
|
625
|
+
// render has text the reference never declared → no fact (no reference signal)
|
|
626
|
+
expect(deriveCopyFacts({ debugLabel: 'x' }, {})).toEqual([]);
|
|
627
|
+
// reference declares an empty string → no signal → no fact regardless of render
|
|
628
|
+
expect(deriveCopyFacts({ note: 'hi' }, { note: '' })).toEqual([]);
|
|
629
|
+
});
|
|
630
|
+
test('result is order-independent and deterministic (sorted by element id)', () => {
|
|
631
|
+
const a = deriveCopyFacts({ b: 'r', a: 'wrong' }, { a: 'right', b: 'r', c: 'gone' });
|
|
632
|
+
const b = deriveCopyFacts({ a: 'wrong', b: 'r' }, { c: 'gone', b: 'r', a: 'right' });
|
|
633
|
+
expect(a).toEqual(b);
|
|
634
|
+
expect(a).toEqual([
|
|
635
|
+
{ kind: 'text_mismatch', elementId: 'a', rendered: 'wrong', reference: 'right' },
|
|
636
|
+
{ kind: 'text_missing', elementId: 'c', reference: 'gone' },
|
|
637
|
+
]);
|
|
638
|
+
});
|
|
639
|
+
});
|
|
521
640
|
describe('buildStructuralBlock — appearance container (Signal C, additive)', () => {
|
|
522
641
|
test('no appearanceReference → no appearance container; facts + geometry unchanged', () => {
|
|
523
642
|
const withC = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
@@ -539,6 +658,7 @@ describe('buildStructuralBlock — appearance container (Signal C, additive)', (
|
|
|
539
658
|
{ kind: 'element_extra', elementId: 'copy' },
|
|
540
659
|
{ kind: 'element_extra', elementId: 'node-1' },
|
|
541
660
|
],
|
|
661
|
+
rendered: { elementIds: ['banner', 'copy', 'node-1', 'voucher'] },
|
|
542
662
|
});
|
|
543
663
|
// Additive: the existing Signal A facts and Signal B geometry are untouched.
|
|
544
664
|
expect(block.facts).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2).facts);
|
|
@@ -549,7 +669,137 @@ describe('buildStructuralBlock — appearance container (Signal C, additive)', (
|
|
|
549
669
|
baseline: 'figma',
|
|
550
670
|
elementIds: ['node-1', 'banner', 'voucher', 'copy'],
|
|
551
671
|
});
|
|
552
|
-
expect(block.appearance).toEqual({
|
|
672
|
+
expect(block.appearance).toEqual({
|
|
673
|
+
baseline: 'figma',
|
|
674
|
+
facts: [],
|
|
675
|
+
rendered: { elementIds: ['banner', 'copy', 'node-1', 'voucher'] },
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
// A dump whose per-node `text` (Signal C render-side enrichment) drives the
|
|
679
|
+
// copy-fact comparison: `title` renders the wrong string, `cta` renders no
|
|
680
|
+
// text, and `debugOverlay` is an extra element absent from the reference.
|
|
681
|
+
const COPY_DUMP = {
|
|
682
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
683
|
+
nodes: [
|
|
684
|
+
{ id: 'title', bounds: { left: 0, top: 0, right: 100, bottom: 20 }, parentId: null, clipsChildren: false, text: '5 EUR' },
|
|
685
|
+
{ id: 'cta', bounds: { left: 0, top: 30, right: 100, bottom: 50 }, parentId: null, clipsChildren: false },
|
|
686
|
+
{ id: 'debugOverlay', bounds: { left: 0, top: 60, right: 100, bottom: 80 }, parentId: null, clipsChildren: false },
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
test('with reference text → copy facts are appended after presence facts', () => {
|
|
690
|
+
const block = buildStructuralBlock(COPY_DUMP, null, 2, undefined, {
|
|
691
|
+
baseline: 'figma',
|
|
692
|
+
elementIds: ['title', 'cta'],
|
|
693
|
+
text: { title: '5 EUR off', cta: 'Shop now' },
|
|
694
|
+
});
|
|
695
|
+
expect(block.appearance).toEqual({
|
|
696
|
+
baseline: 'figma',
|
|
697
|
+
facts: [
|
|
698
|
+
// presence first (debugOverlay is rendered but not in the reference)
|
|
699
|
+
{ kind: 'element_extra', elementId: 'debugOverlay' },
|
|
700
|
+
// then copy (sorted by id: cta missing, title mismatched)
|
|
701
|
+
{ kind: 'text_missing', elementId: 'cta', reference: 'Shop now' },
|
|
702
|
+
{ kind: 'text_mismatch', elementId: 'title', rendered: '5 EUR', reference: '5 EUR off' },
|
|
703
|
+
],
|
|
704
|
+
rendered: { elementIds: ['cta', 'debugOverlay', 'title'], text: { title: '5 EUR' } },
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
test('an appearanceReference without text → presence only (issue-01 behavior unchanged)', () => {
|
|
708
|
+
const block = buildStructuralBlock(COPY_DUMP, null, 2, undefined, {
|
|
709
|
+
baseline: 'figma',
|
|
710
|
+
elementIds: ['title', 'cta', 'debugOverlay'],
|
|
711
|
+
});
|
|
712
|
+
expect(block.appearance).toEqual({
|
|
713
|
+
baseline: 'figma',
|
|
714
|
+
facts: [],
|
|
715
|
+
rendered: { elementIds: ['cta', 'debugOverlay', 'title'], text: { title: '5 EUR' } },
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
// A dump whose per-node fill/text colors (Signal C render-side enrichment)
|
|
719
|
+
// drive the color-fact comparison: `card` renders the wrong fill, correct text.
|
|
720
|
+
const COLOR_DUMP = {
|
|
721
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
722
|
+
nodes: [
|
|
723
|
+
{ id: 'card', bounds: { left: 0, top: 0, right: 100, bottom: 40 }, parentId: null, clipsChildren: false, fillColor: '#ff0000', textColor: '#000000' },
|
|
724
|
+
],
|
|
725
|
+
};
|
|
726
|
+
test('with reference color → color facts are appended after presence/copy facts', () => {
|
|
727
|
+
const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
|
|
728
|
+
baseline: 'figma',
|
|
729
|
+
elementIds: ['card'],
|
|
730
|
+
color: { card: { fill: '#00ff00', text: '#000000' } },
|
|
731
|
+
});
|
|
732
|
+
expect(block.appearance?.baseline).toBe('figma');
|
|
733
|
+
expect(block.appearance?.facts).toEqual([
|
|
734
|
+
{
|
|
735
|
+
kind: 'color',
|
|
736
|
+
elementId: 'card',
|
|
737
|
+
channel: 'fill',
|
|
738
|
+
rendered: '#ff0000',
|
|
739
|
+
reference: '#00ff00',
|
|
740
|
+
deltaE: ciede2000(hexToLab('#ff0000'), hexToLab('#00ff00')),
|
|
741
|
+
},
|
|
742
|
+
]);
|
|
743
|
+
});
|
|
744
|
+
test('an appearanceReference without color → no color facts (presence only)', () => {
|
|
745
|
+
const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
|
|
746
|
+
baseline: 'figma',
|
|
747
|
+
elementIds: ['card'],
|
|
748
|
+
});
|
|
749
|
+
expect(block.appearance).toEqual({
|
|
750
|
+
baseline: 'figma',
|
|
751
|
+
facts: [],
|
|
752
|
+
rendered: { elementIds: ['card'], color: { card: { fill: '#ff0000', text: '#000000' } } },
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
// ---- Signal C no-Figma baseline (issue 04) ----
|
|
756
|
+
test('the appearance container persists a rendered snapshot (the baseline source for the next render)', () => {
|
|
757
|
+
// The snapshot captures THIS render's own element ids + text + colors so a
|
|
758
|
+
// later no-Figma render can diff against it. Text/color are present only for
|
|
759
|
+
// the nodes that expose them.
|
|
760
|
+
const block = buildStructuralBlock(COPY_DUMP, null, 2, undefined, {
|
|
761
|
+
baseline: 'figma',
|
|
762
|
+
elementIds: ['title', 'cta'],
|
|
763
|
+
});
|
|
764
|
+
expect(block.appearance?.rendered).toEqual({
|
|
765
|
+
elementIds: ['cta', 'debugOverlay', 'title'],
|
|
766
|
+
text: { title: '5 EUR' },
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
test('a previous_render baseline marks the facts as regressions (drift), derivers unchanged', () => {
|
|
770
|
+
// The same derivers run; only the reference source + baseline marking differ.
|
|
771
|
+
// Reference here is the *previous render*'s ids/text/color.
|
|
772
|
+
const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
|
|
773
|
+
baseline: 'previous_render',
|
|
774
|
+
elementIds: ['card'],
|
|
775
|
+
color: { card: { fill: '#00ff00' } },
|
|
776
|
+
});
|
|
777
|
+
expect(block.appearance?.baseline).toBe('previous_render');
|
|
778
|
+
expect(block.appearance?.facts).toEqual([
|
|
779
|
+
{
|
|
780
|
+
kind: 'color',
|
|
781
|
+
elementId: 'card',
|
|
782
|
+
channel: 'fill',
|
|
783
|
+
rendered: '#ff0000',
|
|
784
|
+
reference: '#00ff00',
|
|
785
|
+
deltaE: ciede2000(hexToLab('#ff0000'), hexToLab('#00ff00')),
|
|
786
|
+
},
|
|
787
|
+
]);
|
|
788
|
+
});
|
|
789
|
+
test('a previous_render baseline diffs presence against the previous element-id set', () => {
|
|
790
|
+
// COLOR_DUMP renders only `card`; the previous render had `card` + `legacyBadge`.
|
|
791
|
+
const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
|
|
792
|
+
baseline: 'previous_render',
|
|
793
|
+
elementIds: ['card', 'legacyBadge'],
|
|
794
|
+
});
|
|
795
|
+
expect(block.appearance).toMatchObject({
|
|
796
|
+
baseline: 'previous_render',
|
|
797
|
+
facts: [{ kind: 'element_missing', elementId: 'legacyBadge' }],
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
test('no appearanceReference (neither Figma nor previous render) → no container, no rendered snapshot', () => {
|
|
801
|
+
const block = buildStructuralBlock(COLOR_DUMP, null, 2);
|
|
802
|
+
expect(block.appearance).toBeUndefined();
|
|
553
803
|
});
|
|
554
804
|
});
|
|
555
805
|
describe('embedded geometry stage parity', () => {
|
|
@@ -579,6 +829,74 @@ describe('embedded geometry stage parity', () => {
|
|
|
579
829
|
const appearanceReference = { baseline: 'figma', elementIds: ['banner', 'voucher'] };
|
|
580
830
|
expect(embedded.buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2, undefined, appearanceReference));
|
|
581
831
|
});
|
|
832
|
+
test('embedded deriveCopyFacts matches the module (Signal C)', () => {
|
|
833
|
+
const cases = [
|
|
834
|
+
[{ title: 'a' }, { title: 'a' }],
|
|
835
|
+
[{ title: 'a' }, { title: 'b' }],
|
|
836
|
+
[{}, { cta: 'Shop now' }],
|
|
837
|
+
[{ cta: '' }, { cta: 'Shop now' }],
|
|
838
|
+
[{ debugLabel: 'x' }, {}],
|
|
839
|
+
[{ note: 'hi' }, { note: '' }],
|
|
840
|
+
[{ b: 'r', a: 'wrong' }, { a: 'right', b: 'r', c: 'gone' }],
|
|
841
|
+
];
|
|
842
|
+
for (const [rendered, reference] of cases) {
|
|
843
|
+
expect(embedded.deriveCopyFacts(rendered, reference)).toEqual(deriveCopyFacts(rendered, reference));
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
test('embedded buildStructuralBlock matches the module with an appearanceReference carrying text', () => {
|
|
847
|
+
const dumpWithText = {
|
|
848
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
849
|
+
nodes: [
|
|
850
|
+
{ id: 'title', bounds: { left: 0, top: 0, right: 100, bottom: 20 }, parentId: null, clipsChildren: false, text: '5 EUR' },
|
|
851
|
+
{ id: 'cta', bounds: { left: 0, top: 30, right: 100, bottom: 50 }, parentId: null, clipsChildren: false },
|
|
852
|
+
],
|
|
853
|
+
};
|
|
854
|
+
const appearanceReference = {
|
|
855
|
+
baseline: 'figma',
|
|
856
|
+
elementIds: ['title', 'cta'],
|
|
857
|
+
text: { title: '5 EUR off', cta: 'Shop now' },
|
|
858
|
+
};
|
|
859
|
+
expect(embedded.buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference));
|
|
860
|
+
});
|
|
861
|
+
test('embedded ciede2000 + hexToLab match the module (Signal C)', () => {
|
|
862
|
+
const labCases = [
|
|
863
|
+
[[50, 2.6772, -79.7751], [50, 0, -82.7485]],
|
|
864
|
+
[[50, 2.5, 0], [73, 25, -18]],
|
|
865
|
+
[[60.2574, -34.0099, 36.2677], [60.4626, -34.1751, 39.4387]],
|
|
866
|
+
];
|
|
867
|
+
for (const [a, b] of labCases) {
|
|
868
|
+
expect(embedded.ciede2000(a, b)).toBeCloseTo(ciede2000(a, b), 12);
|
|
869
|
+
}
|
|
870
|
+
for (const hex of ['#ffffff', '#000000', '#ff0000', '#3a7bd5', 'abcdef']) {
|
|
871
|
+
expect(embedded.hexToLab(hex)).toEqual(hexToLab(hex));
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
test('embedded deriveColorFacts matches the module (Signal C)', () => {
|
|
875
|
+
const cases = [
|
|
876
|
+
[{ card: { fill: '#ff0000' } }, { card: { fill: '#ff0000' } }],
|
|
877
|
+
[{ card: { fill: '#ff0000' } }, { card: { fill: '#00ff00' } }],
|
|
878
|
+
[{ card: { fill: '#ffffff', text: '#111111' } }, { card: { fill: '#ffffff', text: '#000000' } }],
|
|
879
|
+
[{ card: {} }, { card: { fill: '#ff0000' } }],
|
|
880
|
+
[{ extra: { fill: '#ff0000' } }, {}],
|
|
881
|
+
];
|
|
882
|
+
for (const [rendered, reference] of cases) {
|
|
883
|
+
expect(embedded.deriveColorFacts(rendered, reference)).toEqual(deriveColorFacts(rendered, reference));
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
test('embedded buildStructuralBlock matches the module with an appearanceReference carrying color', () => {
|
|
887
|
+
const dumpWithColor = {
|
|
888
|
+
canvas: { left: 0, top: 0, right: 200, bottom: 400 },
|
|
889
|
+
nodes: [
|
|
890
|
+
{ id: 'card', bounds: { left: 0, top: 0, right: 100, bottom: 40 }, parentId: null, clipsChildren: false, fillColor: '#ff0000', textColor: '#000000' },
|
|
891
|
+
],
|
|
892
|
+
};
|
|
893
|
+
const appearanceReference = {
|
|
894
|
+
baseline: 'figma',
|
|
895
|
+
elementIds: ['card'],
|
|
896
|
+
color: { card: { fill: '#00ff00', text: '#000000' } },
|
|
897
|
+
};
|
|
898
|
+
expect(embedded.buildStructuralBlock(dumpWithColor, null, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(dumpWithColor, null, 2, undefined, appearanceReference));
|
|
899
|
+
});
|
|
582
900
|
const parityCases = [
|
|
583
901
|
{ name: 'factsOnly', dump: HUB_PROMO_DUMP, ref: null },
|
|
584
902
|
{ 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;AA8xBD;;;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,18 @@ 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;
|
|
102
|
+
// params.reference_color (Signal C: Figma fills per element id per screen, as
|
|
103
|
+
// { fill, text } hex), set in main() and read by the structural assembler. Null
|
|
104
|
+
// when none declared → no color facts.
|
|
105
|
+
var referenceColor = null;
|
|
106
|
+
// params.reference_baseline (Signal C: 'figma' (default) or 'previous_render').
|
|
107
|
+
// Marks whether the appearance reference is the design (divergences) or the
|
|
108
|
+
// previous catalog render (regressions/drift, the no-Figma path).
|
|
109
|
+
var referenceBaseline = null;
|
|
98
110
|
|
|
99
111
|
function log(message) {
|
|
100
112
|
console.log('[ui-fidelity-render-android] ' + message);
|
|
@@ -607,7 +619,7 @@ function renderScreens(renderable, projectRoot) {
|
|
|
607
619
|
// geometry deltas vs reference_geometry) onto the screen entry, so the
|
|
608
620
|
// judge reads the signals from protocol-result.json without fetching
|
|
609
621
|
// artifacts. CONFIG.scale is the render density (dp = px / scale).
|
|
610
|
-
assembleStructural(entry, layoutSource, referenceGeometry, CONFIG.scale);
|
|
622
|
+
assembleStructural(entry, layoutSource, referenceGeometry, CONFIG.scale, referenceText, referenceColor, referenceBaseline);
|
|
611
623
|
}
|
|
612
624
|
});
|
|
613
625
|
} finally {
|
|
@@ -673,6 +685,26 @@ function main() {
|
|
|
673
685
|
!Array.isArray(params.reference_geometry)
|
|
674
686
|
? params.reference_geometry
|
|
675
687
|
: null;
|
|
688
|
+
// Signal-C copy reference: { "<Screen>": { "<elementId>": "<text>" } } — the
|
|
689
|
+
// Figma characters mapped to element ids. Absent → presence facts only.
|
|
690
|
+
referenceText =
|
|
691
|
+
params.reference_text &&
|
|
692
|
+
typeof params.reference_text === 'object' &&
|
|
693
|
+
!Array.isArray(params.reference_text)
|
|
694
|
+
? params.reference_text
|
|
695
|
+
: null;
|
|
696
|
+
// Signal-C color reference: { "<Screen>": { "<elementId>": { fill, text } } } —
|
|
697
|
+
// the Figma fills mapped to element ids. Absent → no color facts.
|
|
698
|
+
referenceColor =
|
|
699
|
+
params.reference_color &&
|
|
700
|
+
typeof params.reference_color === 'object' &&
|
|
701
|
+
!Array.isArray(params.reference_color)
|
|
702
|
+
? params.reference_color
|
|
703
|
+
: null;
|
|
704
|
+
// Signal-C baseline marker: 'previous_render' when the appearance reference was
|
|
705
|
+
// sourced from the previous catalog render (no-Figma drift path), else 'figma'.
|
|
706
|
+
referenceBaseline =
|
|
707
|
+
params.reference_baseline === 'previous_render' ? 'previous_render' : 'figma';
|
|
676
708
|
|
|
677
709
|
var screens = Object.keys(params.screens);
|
|
678
710
|
currentEntries = screens.map(function (screen) {
|