@invarn/cibuild 2.5.2 → 2.5.4
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.
|
@@ -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, deriveCopyFacts, deriveSpanFacts, deriveLineFacts, deriveColorFacts, deriveBorderFacts, deriveTypefaceFacts, ciede2000, hexToLab, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, appearanceReferenceFromPreviousRender, buildAppearanceReferenceFromParams, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
|
|
11
|
+
import { deriveStructuralFacts, derivePresenceFacts, deriveCopyFacts, deriveSpanFacts, deriveLineFacts, deriveColorFacts, deriveBorderFacts, deriveTypefaceFacts, deriveTypeMetricFacts, ciede2000, hexToLab, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, appearanceReferenceFromPreviousRender, buildAppearanceReferenceFromParams, 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', () => {
|
|
@@ -419,7 +419,9 @@ describe('buildStructuralBlock — facts only (no reference geometry)', () => {
|
|
|
419
419
|
elements: [
|
|
420
420
|
{ id: 'banner', rendered: { w: 360, h: 96, x: 0, y: 0 } },
|
|
421
421
|
{ id: 'voucher', rendered: { w: 96, h: 96, x: 248, y: 0 } },
|
|
422
|
-
|
|
422
|
+
// copy's honest height is 93px ÷ 2 = 46.5 — the box persists at
|
|
423
|
+
// 2-decimal precision; 47 was the whole-dp era's quantization.
|
|
424
|
+
{ id: 'copy', rendered: { w: 296, h: 46.5, x: 32, y: 12 } },
|
|
423
425
|
],
|
|
424
426
|
});
|
|
425
427
|
});
|
|
@@ -532,7 +534,8 @@ describe('buildStructuralBlock — with reference geometry (Signal B)', () => {
|
|
|
532
534
|
// appended after the compared elements.
|
|
533
535
|
expect(block.geometry?.elements.map((e) => e.id)).toEqual(['voucher', 'banner', 'copy']);
|
|
534
536
|
const copy = block.geometry?.elements.find((e) => e.id === 'copy');
|
|
535
|
-
|
|
537
|
+
// 93px ÷ 2 = 46.5 — 2-decimal persistence; 47 was the quantization.
|
|
538
|
+
expect(copy).toEqual({ id: 'copy', rendered: { w: 296, h: 46.5, x: 32, y: 12 } });
|
|
536
539
|
// The summary still reflects the comparison only.
|
|
537
540
|
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 24, maxClipDeltaDp: 0 });
|
|
538
541
|
});
|
|
@@ -1029,6 +1032,69 @@ describe('deriveLineFacts — declared line split vs rendered laid-out lines', (
|
|
|
1029
1032
|
]);
|
|
1030
1033
|
});
|
|
1031
1034
|
});
|
|
1035
|
+
describe('deriveTypeMetricFacts — laid-out size/tracking/leading vs design', () => {
|
|
1036
|
+
// The blind spot: with family and weight matching, the typeface comparison
|
|
1037
|
+
// finds nothing, so a wrong font size can only surface as the text box
|
|
1038
|
+
// measuring small — filed as structure, with a hint naming a fix site that
|
|
1039
|
+
// does not exist. The live case survived two autofix passes and was
|
|
1040
|
+
// recommended for dismissal as unfixable.
|
|
1041
|
+
test('the motivating case: 18sp design against a 16sp wrapper yields a font-size fact', () => {
|
|
1042
|
+
const facts = deriveTypeMetricFacts({ 'button-16': { fontSize: 16, lineHeight: 24 } }, { 'button-16': { family: 'Poppins', weight: 600, fontSize: 18, lineHeight: 24 } });
|
|
1043
|
+
expect(facts).toEqual([
|
|
1044
|
+
{
|
|
1045
|
+
kind: 'type_metric',
|
|
1046
|
+
elementId: 'button-16',
|
|
1047
|
+
channel: 'text',
|
|
1048
|
+
metric: 'fontSize',
|
|
1049
|
+
rendered: 16,
|
|
1050
|
+
reference: 18,
|
|
1051
|
+
},
|
|
1052
|
+
]);
|
|
1053
|
+
});
|
|
1054
|
+
test('two metrics diverging on one element yield TWO facts, size first', () => {
|
|
1055
|
+
// Folding them into one fact would collapse downstream into a single
|
|
1056
|
+
// finding (identity is screen+element+category) and silently drop the
|
|
1057
|
+
// other — and the motivating element diverged in BOTH at once.
|
|
1058
|
+
const facts = deriveTypeMetricFacts({ 'button-16': { fontSize: 16, letterSpacing: 0 } }, { 'button-16': { family: 'Poppins', fontSize: 18, letterSpacing: -0.6 } });
|
|
1059
|
+
expect(facts.map((f) => f.metric)).toEqual(['fontSize', 'letterSpacing']);
|
|
1060
|
+
});
|
|
1061
|
+
test('a metric declared on only ONE side is no signal, never a divergence', () => {
|
|
1062
|
+
// A design that declares no tracking is not asserting 0, and a style that
|
|
1063
|
+
// leaves leading Unspecified has not chosen a value to be wrong about.
|
|
1064
|
+
expect(deriveTypeMetricFacts({ label: { fontSize: 14, letterSpacing: 0.5 } }, { label: { family: 'Poppins', fontSize: 14 } })).toEqual([]);
|
|
1065
|
+
expect(deriveTypeMetricFacts({ label: { fontSize: 14 } }, { label: { family: 'Poppins', lineHeight: 20 } })).toEqual([]);
|
|
1066
|
+
});
|
|
1067
|
+
test('a sub-unit difference does not flap', () => {
|
|
1068
|
+
// 15.999 against 16 is the same type; a fact for it would recur on every
|
|
1069
|
+
// render forever. No design system steps type by a tenth of a point.
|
|
1070
|
+
expect(deriveTypeMetricFacts({ label: { fontSize: 15.999, lineHeight: 20.2 } }, { label: { family: 'Poppins', fontSize: 16, lineHeight: 20 } })).toEqual([]);
|
|
1071
|
+
});
|
|
1072
|
+
test('a real one-point difference is reported', () => {
|
|
1073
|
+
const facts = deriveTypeMetricFacts({ label: { fontSize: 15 } }, { label: { family: 'Poppins', fontSize: 16 } });
|
|
1074
|
+
expect(facts).toHaveLength(1);
|
|
1075
|
+
});
|
|
1076
|
+
test('an element the render measured no metrics for yields nothing', () => {
|
|
1077
|
+
expect(deriveTypeMetricFacts({}, { label: { family: 'Poppins', fontSize: 16 } })).toEqual([]);
|
|
1078
|
+
});
|
|
1079
|
+
test('a non-finite measurement is no measurement, not a zero', () => {
|
|
1080
|
+
expect(deriveTypeMetricFacts({ label: { fontSize: Number.NaN } }, { label: { family: 'Poppins', fontSize: 16 } })).toEqual([]);
|
|
1081
|
+
});
|
|
1082
|
+
test('deterministic: output sorted by element id', () => {
|
|
1083
|
+
const facts = deriveTypeMetricFacts({ b: { fontSize: 10 }, a: { fontSize: 10 } }, { b: { family: 'P', fontSize: 16 }, a: { family: 'P', fontSize: 16 } });
|
|
1084
|
+
expect(facts.map((f) => f.elementId)).toEqual(['a', 'b']);
|
|
1085
|
+
});
|
|
1086
|
+
test('the embedded render-side port agrees with the module', () => {
|
|
1087
|
+
// The runner executes the JS twin, not this module. A twin that drifted
|
|
1088
|
+
// would emit different facts from the ones these tests guarantee.
|
|
1089
|
+
const { deriveTypeMetricFacts: embedded } = getGeometryStageInternals();
|
|
1090
|
+
const rendered = { 'button-16': { fontSize: 16, letterSpacing: 0, lineHeight: 24 } };
|
|
1091
|
+
const reference = {
|
|
1092
|
+
'button-16': { family: 'Poppins', weight: 600, fontSize: 18, letterSpacing: -0.6, lineHeight: 24 },
|
|
1093
|
+
};
|
|
1094
|
+
expect(embedded(rendered, reference)).toEqual(deriveTypeMetricFacts(rendered, reference));
|
|
1095
|
+
expect(embedded(rendered, reference)).toHaveLength(2);
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
1032
1098
|
describe('deriveTypefaceFacts — resolved vs design typeface per element', () => {
|
|
1033
1099
|
test('a family mismatch yields one typeface fact carrying both fonts', () => {
|
|
1034
1100
|
const facts = deriveTypefaceFacts({ code: { family: 'Roboto', weight: 400 } }, { code: { family: 'Poppins', weight: 600 } });
|
|
@@ -2909,4 +2975,65 @@ describe('deriveBorderFacts — the design stroke vs the rendered border', () =>
|
|
|
2909
2975
|
]);
|
|
2910
2976
|
});
|
|
2911
2977
|
});
|
|
2978
|
+
describe('buildStructuralBlock — sub-dp rendered boxes', () => {
|
|
2979
|
+
// The rendered box persists at 2-decimal precision instead of whole dp: the
|
|
2980
|
+
// paint is sub-dp, and quantizing the box moved it up to half a dp off the
|
|
2981
|
+
// pixels it describes — visibly mis-wrapped once evidence is magnified. The
|
|
2982
|
+
// COMPARISON layer does not move: deltas were always computed from raw
|
|
2983
|
+
// values and persisted integer-rounded, and the flag bands read raw deltas.
|
|
2984
|
+
const FRACTIONAL_DUMP = {
|
|
2985
|
+
canvas: { left: 0, top: 0, right: 1125, bottom: 2436 },
|
|
2986
|
+
nodes: [
|
|
2987
|
+
{
|
|
2988
|
+
id: 'node-1',
|
|
2989
|
+
bounds: { left: 0, top: 0, right: 1125, bottom: 2436 },
|
|
2990
|
+
unclippedBounds: { left: 0, top: 0, right: 1125, bottom: 2436 },
|
|
2991
|
+
parentId: null,
|
|
2992
|
+
clipsChildren: false,
|
|
2993
|
+
},
|
|
2994
|
+
// ÷3 density: repeating decimals, so the 2-decimal clamp is load-bearing
|
|
2995
|
+
// (133.666… must persist as 133.67, never 133.66666666666666).
|
|
2996
|
+
{
|
|
2997
|
+
id: 'dot',
|
|
2998
|
+
bounds: { left: 401, top: 641, right: 426, bottom: 666 },
|
|
2999
|
+
unclippedBounds: { left: 401, top: 641, right: 426, bottom: 666 },
|
|
3000
|
+
parentId: 'node-1',
|
|
3001
|
+
clipsChildren: false,
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
id: 'halo',
|
|
3005
|
+
bounds: { left: 300, top: 500, right: 400, bottom: 600 },
|
|
3006
|
+
unclippedBounds: { left: 300, top: 500, right: 400, bottom: 600 },
|
|
3007
|
+
parentId: 'node-1',
|
|
3008
|
+
clipsChildren: false,
|
|
3009
|
+
},
|
|
3010
|
+
],
|
|
3011
|
+
};
|
|
3012
|
+
const FRACTIONAL_REFERENCE = { dot: { w: 8, h: 8 } };
|
|
3013
|
+
test('persists the compared box at 2 decimals while deltas and flags stay whole-dp', () => {
|
|
3014
|
+
const block = buildStructuralBlock(FRACTIONAL_DUMP, FRACTIONAL_REFERENCE, 3);
|
|
3015
|
+
expect(block.geometry?.elements.find((e) => e.id === 'dot')).toEqual({
|
|
3016
|
+
id: 'dot',
|
|
3017
|
+
rendered: { w: 8.33, h: 8.33, x: 133.67, y: 213.67 },
|
|
3018
|
+
design: { w: 8, h: 8 },
|
|
3019
|
+
// The raw 0.33dp delta rounds to 0 — no delta emitted, no flag, exactly
|
|
3020
|
+
// as the whole-dp era persisted it.
|
|
3021
|
+
deltas: [],
|
|
3022
|
+
sizeFlag: false,
|
|
3023
|
+
ancestors: ['node-1'],
|
|
3024
|
+
});
|
|
3025
|
+
expect(block.summary.clean).toBe(true);
|
|
3026
|
+
});
|
|
3027
|
+
test('persists described-only (rendered-only) boxes at the same precision', () => {
|
|
3028
|
+
const block = buildStructuralBlock(FRACTIONAL_DUMP, FRACTIONAL_REFERENCE, 3);
|
|
3029
|
+
expect(block.geometry?.elements.find((e) => e.id === 'halo')).toEqual({
|
|
3030
|
+
id: 'halo',
|
|
3031
|
+
rendered: { w: 33.33, h: 33.33, x: 100, y: 166.67 },
|
|
3032
|
+
});
|
|
3033
|
+
});
|
|
3034
|
+
test('embedded stage matches the module for sub-dp rendered boxes (parity)', () => {
|
|
3035
|
+
const embedded = getGeometryStageInternals();
|
|
3036
|
+
expect(embedded.buildStructuralBlock(FRACTIONAL_DUMP, FRACTIONAL_REFERENCE, 3)).toEqual(buildStructuralBlock(FRACTIONAL_DUMP, FRACTIONAL_REFERENCE, 3));
|
|
3037
|
+
});
|
|
3038
|
+
});
|
|
2912
3039
|
//# sourceMappingURL=structural-facts.test.js.map
|