@riddledc/riddle-proof 0.7.20 → 0.7.22
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/basic-gameplay.cjs +157 -12
- package/dist/basic-gameplay.d.cts +35 -5
- package/dist/basic-gameplay.d.ts +35 -5
- package/dist/basic-gameplay.js +1 -1
- package/dist/{chunk-6H742VFJ.js → chunk-ELZSPOWV.js} +157 -12
- package/dist/{chunk-TPUR67H5.js → chunk-WTHSVHG5.js} +206 -7
- package/dist/cli.cjs +206 -7
- package/dist/cli.js +1 -1
- package/dist/index.cjs +363 -19
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/profile.cjs +206 -7
- package/dist/profile.d.cts +19 -1
- package/dist/profile.d.ts +19 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
|
@@ -79,11 +79,13 @@ function assessBasicGameplayEvidence(evidence, options = {}) {
|
|
|
79
79
|
}
|
|
80
80
|
const routeResults = (run.results || []).map((route) => augmentRouteAssessmentWithProgressionChecks(
|
|
81
81
|
assessBasicGameplayRoute(route, options),
|
|
82
|
-
route
|
|
82
|
+
route,
|
|
83
|
+
options
|
|
83
84
|
));
|
|
84
85
|
const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
|
|
85
86
|
name: result.name,
|
|
86
87
|
path: result.path,
|
|
88
|
+
assessment_mode: result.assessment_mode,
|
|
87
89
|
failures: result.failures,
|
|
88
90
|
warnings: result.warnings,
|
|
89
91
|
suite_failures: result.suite_failures
|
|
@@ -110,6 +112,8 @@ function assessBasicGameplayRoute(route, options = {}) {
|
|
|
110
112
|
const failOnConsoleError = options.failOnConsoleError ?? false;
|
|
111
113
|
const failures = [];
|
|
112
114
|
const warnings = [];
|
|
115
|
+
const assessmentMode = routeAssessmentMode(route);
|
|
116
|
+
const auditAssessment = isAuditAssessmentRoute(route);
|
|
113
117
|
const initial = route.initial || {};
|
|
114
118
|
const timed = route.timed || {};
|
|
115
119
|
const afterAction = route.after_action || route.afterAction || {};
|
|
@@ -121,7 +125,9 @@ function assessBasicGameplayRoute(route, options = {}) {
|
|
|
121
125
|
const continuedActionChange = changed(afterAction, afterContinue);
|
|
122
126
|
const cleanupBase = route.after_continue || route.afterContinue ? afterContinue : afterAction;
|
|
123
127
|
const cleanupActionChange = changed(cleanupBase, afterCleanup);
|
|
124
|
-
const
|
|
128
|
+
const interactiveSurfaceVisible = numberValue(initial.visible_canvas_count) > 0 || numberValue(initial.enabled_clickable_count) > 0 || numberValue(initial.visible_large_node_count) >= minSurfaceLargeNodes;
|
|
129
|
+
const auditSurfaceVisible = numberValue(initial.body_text_length) >= minBodyTextLength || numberValue(initial.visible_large_node_count) >= minVisibleLargeNodes || numberValue(initial.enabled_clickable_count) > 0 || numberValue(initial.visible_canvas_count) > 0;
|
|
130
|
+
const surfaceVisible = auditAssessment ? auditSurfaceVisible : interactiveSurfaceVisible;
|
|
125
131
|
const actionResults = listValue(route.action_results || route.actionResults);
|
|
126
132
|
const continuedActionResults = listValue(route.continued_action_results || route.continuedActionResults);
|
|
127
133
|
const continuedCleanupActionResults = listValue(route.continued_cleanup_action_results || route.continuedCleanupActionResults);
|
|
@@ -135,7 +141,7 @@ function assessBasicGameplayRoute(route, options = {}) {
|
|
|
135
141
|
const responseStatus = firstNumber(route.http_status, route.response_status, route.status);
|
|
136
142
|
const pageErrorCount = numberValue(route.page_error_count);
|
|
137
143
|
const consoleErrorCount = numberValue(route.console_error_count);
|
|
138
|
-
const mobileOverflowPx =
|
|
144
|
+
const mobileOverflowPx = horizontalBoundsOverflowPx(mobile);
|
|
139
145
|
if (responseStatus !== null && responseStatus >= 400) failures.push("route_http_error");
|
|
140
146
|
if (pageErrorCount > 0) failures.push("fatal_page_error");
|
|
141
147
|
if (numberValue(initial.body_text_length) < minBodyTextLength && numberValue(initial.visible_large_node_count) < minVisibleLargeNodes) {
|
|
@@ -143,20 +149,21 @@ function assessBasicGameplayRoute(route, options = {}) {
|
|
|
143
149
|
}
|
|
144
150
|
if (!surfaceVisible) failures.push("no_game_surface");
|
|
145
151
|
if (mobileOverflowPx > maxMobileOverflowPx) failures.push("mobile_horizontal_overflow");
|
|
146
|
-
if (!actionAttempted && !timedChange.changed) failures.push("primary_control_missing");
|
|
147
|
-
if (actionAttempted && !stateChangeObserved) failures.push("primary_control_inert");
|
|
152
|
+
if (!auditAssessment && !actionAttempted && !timedChange.changed) failures.push("primary_control_missing");
|
|
153
|
+
if (!auditAssessment && actionAttempted && !stateChangeObserved) failures.push("primary_control_inert");
|
|
148
154
|
if (failOnConsoleError && consoleErrorCount > 0) failures.push("fatal_page_error");
|
|
149
155
|
if (numberValue(initial.visible_canvas_count) > 0 && actionAttempted && !timedChange.canvas_changed && !actionChange.canvas_changed && !actionChange.screenshot_changed) {
|
|
150
156
|
warnings.push("canvas_inert");
|
|
151
157
|
}
|
|
152
158
|
if (actionFailed) warnings.push("some_actions_failed");
|
|
153
|
-
if (warnOnMissingResetPath && !resetPathPresent && route.requires_reset !== false && actionAttempted && stateChangeObserved) {
|
|
159
|
+
if (!auditAssessment && warnOnMissingResetPath && !resetPathPresent && route.requires_reset !== false && actionAttempted && stateChangeObserved) {
|
|
154
160
|
warnings.push("missing_reset_path");
|
|
155
161
|
}
|
|
156
162
|
if (warnOnConsoleError && consoleErrorCount > 0) warnings.push("critical_console_error");
|
|
157
163
|
return {
|
|
158
164
|
name: route.name,
|
|
159
165
|
path: route.path,
|
|
166
|
+
assessment_mode: assessmentMode,
|
|
160
167
|
ok: failures.length === 0,
|
|
161
168
|
failures,
|
|
162
169
|
warnings,
|
|
@@ -277,15 +284,16 @@ function assessBasicGameplayProgressionCheck(check) {
|
|
|
277
284
|
function assessBasicGameplayProgressionChecks(route) {
|
|
278
285
|
return progressionChecksForRoute(route).map((check) => assessBasicGameplayProgressionCheck(check));
|
|
279
286
|
}
|
|
280
|
-
function augmentBasicGameplayAssessmentWithProgressionChecks(assessment, evidence) {
|
|
287
|
+
function augmentBasicGameplayAssessmentWithProgressionChecks(assessment, evidence, options = {}) {
|
|
281
288
|
const run = extractBasicGameplayEvidence(evidence);
|
|
282
289
|
if (!run?.results?.length) return assessment;
|
|
283
290
|
const routeResults = assessment.route_results.map(
|
|
284
|
-
(result, index) => augmentRouteAssessmentWithProgressionChecks(result, run.results?.[index] || {})
|
|
291
|
+
(result, index) => augmentRouteAssessmentWithProgressionChecks(result, run.results?.[index] || {}, options)
|
|
285
292
|
);
|
|
286
293
|
const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
|
|
287
294
|
name: result.name,
|
|
288
295
|
path: result.path,
|
|
296
|
+
assessment_mode: result.assessment_mode,
|
|
289
297
|
failures: result.failures,
|
|
290
298
|
warnings: result.warnings,
|
|
291
299
|
suite_failures: result.suite_failures
|
|
@@ -342,7 +350,7 @@ function attachBasicGameplayArtifactScreenshotHashes(evidence, routesOrOptions =
|
|
|
342
350
|
}
|
|
343
351
|
return evidence;
|
|
344
352
|
}
|
|
345
|
-
function createBasicGameplayCatchRecords(assessment, evidence) {
|
|
353
|
+
function createBasicGameplayCatchRecords(assessment, evidence, options = {}) {
|
|
346
354
|
const run = extractBasicGameplayEvidence(evidence);
|
|
347
355
|
if (!run?.results?.length) return [];
|
|
348
356
|
const catches = [];
|
|
@@ -416,6 +424,24 @@ function createBasicGameplayCatchRecords(assessment, evidence) {
|
|
|
416
424
|
summary: `${route.name || route.path || "Route"}: responsive setup failed on ${failure.viewport?.label || "viewport"} (${failure.reason || "responsive_setup_failed"})`
|
|
417
425
|
});
|
|
418
426
|
}
|
|
427
|
+
for (const failure of responsiveBoundsFailures(route, options.maxMobileOverflowPx ?? 4)) {
|
|
428
|
+
catches.push({
|
|
429
|
+
site: run.site || null,
|
|
430
|
+
route: route.name,
|
|
431
|
+
path: route.path,
|
|
432
|
+
code: failure.code,
|
|
433
|
+
label: failure.label,
|
|
434
|
+
type: "responsive_bounds",
|
|
435
|
+
selector: null,
|
|
436
|
+
reason: failure.reason || "responsive_bounds_clipped",
|
|
437
|
+
phase: failure.viewport?.phase || void 0,
|
|
438
|
+
viewport: failure.viewport,
|
|
439
|
+
overflow_px: failure.overflow_px,
|
|
440
|
+
max_allowed_px: failure.max_allowed_px,
|
|
441
|
+
offenders: failure.offenders,
|
|
442
|
+
summary: `${route.name || route.path || "Route"}: responsive bounds exceeded ${failure.max_allowed_px}px on ${failure.viewport?.label || "viewport"} (${failure.overflow_px}px)`
|
|
443
|
+
});
|
|
444
|
+
}
|
|
419
445
|
for (const check of assessBasicGameplayProgressionChecks(route).filter((item) => item.ok === false)) {
|
|
420
446
|
catches.push({
|
|
421
447
|
site: run.site || null,
|
|
@@ -570,13 +596,15 @@ function changed(before, after) {
|
|
|
570
596
|
function canvasHashes(canvases) {
|
|
571
597
|
return (canvases || []).map((canvas) => canvas.hash).filter(Boolean).join("|");
|
|
572
598
|
}
|
|
573
|
-
function augmentRouteAssessmentWithProgressionChecks(result, route) {
|
|
599
|
+
function augmentRouteAssessmentWithProgressionChecks(result, route, options = {}) {
|
|
574
600
|
const progressionFailures = assessBasicGameplayProgressionChecks(route).filter((check) => check.ok === false);
|
|
575
601
|
const responsiveFailures = responsiveSetupFailures(route);
|
|
576
|
-
|
|
602
|
+
const responsiveBounds = responsiveBoundsFailures(route, options.maxMobileOverflowPx ?? 4);
|
|
603
|
+
if (!progressionFailures.length && !responsiveFailures.length && !responsiveBounds.length) return result;
|
|
577
604
|
const failures = new Set(result.failures);
|
|
578
605
|
if (progressionFailures.length) failures.add("progression_assertion_failed");
|
|
579
606
|
for (const failure of responsiveFailures) failures.add(failure.code);
|
|
607
|
+
for (const failure of responsiveBounds) failures.add(failure.code);
|
|
580
608
|
return {
|
|
581
609
|
...result,
|
|
582
610
|
ok: false,
|
|
@@ -592,7 +620,8 @@ function augmentRouteAssessmentWithProgressionChecks(result, route) {
|
|
|
592
620
|
state_call: check.state_call || null,
|
|
593
621
|
property_path: check.property_path || null
|
|
594
622
|
})),
|
|
595
|
-
...responsiveFailures
|
|
623
|
+
...responsiveFailures,
|
|
624
|
+
...responsiveBounds
|
|
596
625
|
]
|
|
597
626
|
};
|
|
598
627
|
}
|
|
@@ -618,6 +647,28 @@ function responsiveSetupFailures(route) {
|
|
|
618
647
|
}
|
|
619
648
|
return failures;
|
|
620
649
|
}
|
|
650
|
+
function responsiveBoundsFailures(route, maxOverflowPx) {
|
|
651
|
+
const failures = [];
|
|
652
|
+
for (const viewport of responsiveViewportsForRoute(route)) {
|
|
653
|
+
const overflowPx = horizontalBoundsOverflowPx(viewport);
|
|
654
|
+
if (overflowPx <= maxOverflowPx) continue;
|
|
655
|
+
failures.push({
|
|
656
|
+
code: "responsive_bounds_clipped",
|
|
657
|
+
label: `${viewport.label || "responsive"} horizontal bounds`,
|
|
658
|
+
reason: `horizontal bounds overflow ${overflowPx}px exceeded ${maxOverflowPx}px`,
|
|
659
|
+
overflow_px: overflowPx,
|
|
660
|
+
max_allowed_px: maxOverflowPx,
|
|
661
|
+
offenders: boundsOffendersForEvidence(viewport).slice(0, 10),
|
|
662
|
+
viewport: {
|
|
663
|
+
label: viewport.label || null,
|
|
664
|
+
width: numberValue(viewport.width) || null,
|
|
665
|
+
height: numberValue(viewport.height) || null,
|
|
666
|
+
phase: viewport.phase || null
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return failures;
|
|
671
|
+
}
|
|
621
672
|
function responsiveViewportsForRoute(route) {
|
|
622
673
|
return listValue(route.responsive_viewports || route.responsiveViewports).filter((item) => Boolean(recordValue(item)));
|
|
623
674
|
}
|
|
@@ -627,6 +678,82 @@ function progressionChecksForRoute(route) {
|
|
|
627
678
|
function metricValue(value) {
|
|
628
679
|
return recordValue(value);
|
|
629
680
|
}
|
|
681
|
+
function horizontalBoundsOverflowPx(value) {
|
|
682
|
+
const record = recordValue(value);
|
|
683
|
+
if (!record) return 0;
|
|
684
|
+
let max = maxPositiveNumber(
|
|
685
|
+
record.overflow_px,
|
|
686
|
+
record.overflow,
|
|
687
|
+
record.bounds_overflow_px,
|
|
688
|
+
record.horizontal_overflow_px,
|
|
689
|
+
record.left_overflow_px,
|
|
690
|
+
record.right_overflow_px
|
|
691
|
+
);
|
|
692
|
+
for (const offender of boundsOffendersForEvidence(record)) {
|
|
693
|
+
max = Math.max(max, horizontalOffenderOverflowPx(offender));
|
|
694
|
+
}
|
|
695
|
+
return roundPixels(max);
|
|
696
|
+
}
|
|
697
|
+
function horizontalOffenderOverflowPx(value) {
|
|
698
|
+
const record = recordValue(value);
|
|
699
|
+
if (!record) return 0;
|
|
700
|
+
let max = maxPositiveNumber(
|
|
701
|
+
record.overflow,
|
|
702
|
+
record.overflow_px,
|
|
703
|
+
record.bounds_overflow_px,
|
|
704
|
+
record.horizontal_overflow_px,
|
|
705
|
+
record.left_overflow_px,
|
|
706
|
+
record.right_overflow_px,
|
|
707
|
+
record.leftOverflowPx,
|
|
708
|
+
record.rightOverflowPx
|
|
709
|
+
);
|
|
710
|
+
const clipped = recordValue(record.clipped || record.clip || record.clipping);
|
|
711
|
+
if (clipped) {
|
|
712
|
+
max = Math.max(max, maxPositiveNumber(
|
|
713
|
+
clipped.left,
|
|
714
|
+
clipped.right,
|
|
715
|
+
clipped.left_px,
|
|
716
|
+
clipped.right_px,
|
|
717
|
+
clipped.leftPx,
|
|
718
|
+
clipped.rightPx
|
|
719
|
+
));
|
|
720
|
+
}
|
|
721
|
+
const rect = recordValue(record.rect || record.bounds || record.bounding_rect || record.boundingRect);
|
|
722
|
+
const viewportWidth = numericValue(record.viewport_width ?? record.viewportWidth);
|
|
723
|
+
if (rect && viewportWidth !== null) {
|
|
724
|
+
const left = numericValue(rect.left);
|
|
725
|
+
const right = numericValue(rect.right);
|
|
726
|
+
if (left !== null && left < 0) max = Math.max(max, Math.abs(left));
|
|
727
|
+
if (right !== null && right > viewportWidth) max = Math.max(max, right - viewportWidth);
|
|
728
|
+
}
|
|
729
|
+
return roundPixels(max);
|
|
730
|
+
}
|
|
731
|
+
function boundsOffendersForEvidence(value) {
|
|
732
|
+
const record = recordValue(value);
|
|
733
|
+
if (!record) return [];
|
|
734
|
+
const offenders = [
|
|
735
|
+
...listValue(record.overflow_offenders),
|
|
736
|
+
...listValue(record.overflowOffenders),
|
|
737
|
+
...listValue(record.bounds_offenders),
|
|
738
|
+
...listValue(record.boundsOffenders),
|
|
739
|
+
...listValue(record.clipped_elements),
|
|
740
|
+
...listValue(record.clippedElements),
|
|
741
|
+
...listValue(record.clipping_offenders),
|
|
742
|
+
...listValue(record.clippingOffenders)
|
|
743
|
+
];
|
|
744
|
+
return offenders.filter((item) => Boolean(recordValue(item))).sort((a, b) => horizontalOffenderOverflowPx(b) - horizontalOffenderOverflowPx(a));
|
|
745
|
+
}
|
|
746
|
+
function maxPositiveNumber(...values) {
|
|
747
|
+
let max = 0;
|
|
748
|
+
for (const value of values) {
|
|
749
|
+
const number = numericValue(value);
|
|
750
|
+
if (number !== null && number > max) max = number;
|
|
751
|
+
}
|
|
752
|
+
return max;
|
|
753
|
+
}
|
|
754
|
+
function roundPixels(value) {
|
|
755
|
+
return Math.round(value * 100) / 100;
|
|
756
|
+
}
|
|
630
757
|
function textMatches(text, pattern, flags) {
|
|
631
758
|
if (!pattern) return Boolean(text);
|
|
632
759
|
try {
|
|
@@ -720,6 +847,24 @@ function countCodes(codes) {
|
|
|
720
847
|
for (const code of codes) counts[code] = (counts[code] || 0) + 1;
|
|
721
848
|
return counts;
|
|
722
849
|
}
|
|
850
|
+
function routeAssessmentMode(route) {
|
|
851
|
+
const raw = route.assessment_mode ?? route.assessmentMode ?? route.proof_mode ?? route.proofMode ?? null;
|
|
852
|
+
if (raw === null || raw === void 0) return route.audit === true ? "audit" : null;
|
|
853
|
+
const mode = String(raw).trim().toLowerCase().replace(/_/g, "-");
|
|
854
|
+
return mode || (route.audit === true ? "audit" : null);
|
|
855
|
+
}
|
|
856
|
+
function isAuditAssessmentRoute(route) {
|
|
857
|
+
const mode = routeAssessmentMode(route);
|
|
858
|
+
return route.audit === true || [
|
|
859
|
+
"audit",
|
|
860
|
+
"audit-only",
|
|
861
|
+
"no-diff",
|
|
862
|
+
"not-required",
|
|
863
|
+
"profile",
|
|
864
|
+
"report-only",
|
|
865
|
+
"static"
|
|
866
|
+
].includes(String(mode || ""));
|
|
867
|
+
}
|
|
723
868
|
function firstNumber(...values) {
|
|
724
869
|
for (const value of values) {
|
|
725
870
|
const number = numericValue(value);
|
|
@@ -38,6 +38,79 @@ function stringValue(value) {
|
|
|
38
38
|
function numberValue(value) {
|
|
39
39
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
40
40
|
}
|
|
41
|
+
function horizontalBoundsOverflowPx(value) {
|
|
42
|
+
if (!isRecord(value)) return 0;
|
|
43
|
+
let max = maxPositiveNumber(
|
|
44
|
+
value.overflow_px,
|
|
45
|
+
value.overflow,
|
|
46
|
+
value.bounds_overflow_px,
|
|
47
|
+
value.horizontal_overflow_px,
|
|
48
|
+
value.left_overflow_px,
|
|
49
|
+
value.right_overflow_px
|
|
50
|
+
);
|
|
51
|
+
for (const offender of boundsOffendersForEvidence(value)) {
|
|
52
|
+
max = Math.max(max, horizontalOffenderOverflowPx(offender));
|
|
53
|
+
}
|
|
54
|
+
return roundPixels(max);
|
|
55
|
+
}
|
|
56
|
+
function horizontalOffenderOverflowPx(value) {
|
|
57
|
+
if (!isRecord(value)) return 0;
|
|
58
|
+
let max = maxPositiveNumber(
|
|
59
|
+
value.overflow,
|
|
60
|
+
value.overflow_px,
|
|
61
|
+
value.bounds_overflow_px,
|
|
62
|
+
value.horizontal_overflow_px,
|
|
63
|
+
value.left_overflow_px,
|
|
64
|
+
value.right_overflow_px,
|
|
65
|
+
value.leftOverflowPx,
|
|
66
|
+
value.rightOverflowPx
|
|
67
|
+
);
|
|
68
|
+
const clipped = isRecord(value.clipped || value.clip || value.clipping) ? value.clipped || value.clip || value.clipping : void 0;
|
|
69
|
+
if (isRecord(clipped)) {
|
|
70
|
+
max = Math.max(max, maxPositiveNumber(
|
|
71
|
+
clipped.left,
|
|
72
|
+
clipped.right,
|
|
73
|
+
clipped.left_px,
|
|
74
|
+
clipped.right_px,
|
|
75
|
+
clipped.leftPx,
|
|
76
|
+
clipped.rightPx
|
|
77
|
+
));
|
|
78
|
+
}
|
|
79
|
+
const rect = isRecord(value.rect || value.bounds || value.bounding_rect || value.boundingRect) ? value.rect || value.bounds || value.bounding_rect || value.boundingRect : void 0;
|
|
80
|
+
const viewportWidth = numberValue(value.viewport_width ?? value.viewportWidth);
|
|
81
|
+
if (isRecord(rect) && viewportWidth !== void 0) {
|
|
82
|
+
const left = numberValue(rect.left);
|
|
83
|
+
const right = numberValue(rect.right);
|
|
84
|
+
if (left !== void 0 && left < 0) max = Math.max(max, Math.abs(left));
|
|
85
|
+
if (right !== void 0 && right > viewportWidth) max = Math.max(max, right - viewportWidth);
|
|
86
|
+
}
|
|
87
|
+
return roundPixels(max);
|
|
88
|
+
}
|
|
89
|
+
function boundsOffendersForEvidence(value) {
|
|
90
|
+
if (!isRecord(value)) return [];
|
|
91
|
+
const offenders = [
|
|
92
|
+
...Array.isArray(value.overflow_offenders) ? value.overflow_offenders : [],
|
|
93
|
+
...Array.isArray(value.overflowOffenders) ? value.overflowOffenders : [],
|
|
94
|
+
...Array.isArray(value.bounds_offenders) ? value.bounds_offenders : [],
|
|
95
|
+
...Array.isArray(value.boundsOffenders) ? value.boundsOffenders : [],
|
|
96
|
+
...Array.isArray(value.clipped_elements) ? value.clipped_elements : [],
|
|
97
|
+
...Array.isArray(value.clippedElements) ? value.clippedElements : [],
|
|
98
|
+
...Array.isArray(value.clipping_offenders) ? value.clipping_offenders : [],
|
|
99
|
+
...Array.isArray(value.clippingOffenders) ? value.clippingOffenders : []
|
|
100
|
+
];
|
|
101
|
+
return offenders.filter((item) => isRecord(item)).sort((a, b) => horizontalOffenderOverflowPx(b) - horizontalOffenderOverflowPx(a));
|
|
102
|
+
}
|
|
103
|
+
function maxPositiveNumber(...values) {
|
|
104
|
+
let max = 0;
|
|
105
|
+
for (const value of values) {
|
|
106
|
+
const number = numberValue(value);
|
|
107
|
+
if (number !== void 0 && number > max) max = number;
|
|
108
|
+
}
|
|
109
|
+
return max;
|
|
110
|
+
}
|
|
111
|
+
function roundPixels(value) {
|
|
112
|
+
return Math.round(value * 100) / 100;
|
|
113
|
+
}
|
|
41
114
|
function timeoutSecValue(value) {
|
|
42
115
|
const number = numberValue(value);
|
|
43
116
|
return number && number > 0 ? Math.ceil(number) : void 0;
|
|
@@ -380,7 +453,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
380
453
|
message: "No applicable viewport evidence was captured for overflow check."
|
|
381
454
|
};
|
|
382
455
|
}
|
|
383
|
-
const failed = applicable.filter((viewport) => (viewport
|
|
456
|
+
const failed = applicable.filter((viewport) => horizontalBoundsOverflowPx(viewport) > maxOverflow);
|
|
384
457
|
return {
|
|
385
458
|
type: check.type,
|
|
386
459
|
label: checkLabel(check),
|
|
@@ -388,9 +461,11 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
388
461
|
evidence: {
|
|
389
462
|
max_overflow_px: maxOverflow,
|
|
390
463
|
overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null),
|
|
464
|
+
bounds_overflow_px: applicable.map((viewport) => horizontalBoundsOverflowPx(viewport)),
|
|
465
|
+
overflow_offender_counts: applicable.map((viewport) => boundsOffendersForEvidence(viewport).length),
|
|
391
466
|
viewports: applicable.map((viewport) => viewport.name)
|
|
392
467
|
},
|
|
393
|
-
message: failed.length ? `Horizontal overflow exceeded ${maxOverflow}px in ${failed.length} viewport(s).` : void 0
|
|
468
|
+
message: failed.length ? `Horizontal bounds overflow exceeded ${maxOverflow}px in ${failed.length} viewport(s).` : void 0
|
|
394
469
|
};
|
|
395
470
|
}
|
|
396
471
|
if (check.type === "no_fatal_console_errors") {
|
|
@@ -612,6 +687,77 @@ function textMatches(sample, check) {
|
|
|
612
687
|
}
|
|
613
688
|
return String(sample || "").includes(check.text || "");
|
|
614
689
|
}
|
|
690
|
+
function numberValue(value) {
|
|
691
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
692
|
+
}
|
|
693
|
+
function maxPositiveNumber() {
|
|
694
|
+
let max = 0;
|
|
695
|
+
for (const value of arguments) {
|
|
696
|
+
const number = numberValue(value);
|
|
697
|
+
if (number !== undefined && number > max) max = number;
|
|
698
|
+
}
|
|
699
|
+
return max;
|
|
700
|
+
}
|
|
701
|
+
function roundPixels(value) {
|
|
702
|
+
return Math.round(value * 100) / 100;
|
|
703
|
+
}
|
|
704
|
+
function isRecord(value) {
|
|
705
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
706
|
+
}
|
|
707
|
+
function boundsOffendersForEvidence(value) {
|
|
708
|
+
if (!isRecord(value)) return [];
|
|
709
|
+
const offenders = []
|
|
710
|
+
.concat(Array.isArray(value.overflow_offenders) ? value.overflow_offenders : [])
|
|
711
|
+
.concat(Array.isArray(value.overflowOffenders) ? value.overflowOffenders : [])
|
|
712
|
+
.concat(Array.isArray(value.bounds_offenders) ? value.bounds_offenders : [])
|
|
713
|
+
.concat(Array.isArray(value.boundsOffenders) ? value.boundsOffenders : [])
|
|
714
|
+
.concat(Array.isArray(value.clipped_elements) ? value.clipped_elements : [])
|
|
715
|
+
.concat(Array.isArray(value.clippedElements) ? value.clippedElements : [])
|
|
716
|
+
.concat(Array.isArray(value.clipping_offenders) ? value.clipping_offenders : [])
|
|
717
|
+
.concat(Array.isArray(value.clippingOffenders) ? value.clippingOffenders : []);
|
|
718
|
+
return offenders.filter(isRecord).sort((a, b) => horizontalOffenderOverflowPx(b) - horizontalOffenderOverflowPx(a));
|
|
719
|
+
}
|
|
720
|
+
function horizontalOffenderOverflowPx(value) {
|
|
721
|
+
if (!isRecord(value)) return 0;
|
|
722
|
+
let max = maxPositiveNumber(
|
|
723
|
+
value.overflow,
|
|
724
|
+
value.overflow_px,
|
|
725
|
+
value.bounds_overflow_px,
|
|
726
|
+
value.horizontal_overflow_px,
|
|
727
|
+
value.left_overflow_px,
|
|
728
|
+
value.right_overflow_px,
|
|
729
|
+
value.leftOverflowPx,
|
|
730
|
+
value.rightOverflowPx
|
|
731
|
+
);
|
|
732
|
+
const clipped = value.clipped || value.clip || value.clipping;
|
|
733
|
+
if (isRecord(clipped)) {
|
|
734
|
+
max = Math.max(max, maxPositiveNumber(clipped.left, clipped.right, clipped.left_px, clipped.right_px, clipped.leftPx, clipped.rightPx));
|
|
735
|
+
}
|
|
736
|
+
const rect = value.rect || value.bounds || value.bounding_rect || value.boundingRect;
|
|
737
|
+
const viewportWidth = numberValue(value.viewport_width != null ? value.viewport_width : value.viewportWidth);
|
|
738
|
+
if (isRecord(rect) && viewportWidth !== undefined) {
|
|
739
|
+
const left = numberValue(rect.left);
|
|
740
|
+
const right = numberValue(rect.right);
|
|
741
|
+
if (left !== undefined && left < 0) max = Math.max(max, Math.abs(left));
|
|
742
|
+
if (right !== undefined && right > viewportWidth) max = Math.max(max, right - viewportWidth);
|
|
743
|
+
}
|
|
744
|
+
return roundPixels(max);
|
|
745
|
+
}
|
|
746
|
+
function horizontalBoundsOverflowPx(value) {
|
|
747
|
+
if (!isRecord(value)) return 0;
|
|
748
|
+
let max = maxPositiveNumber(
|
|
749
|
+
value.overflow_px,
|
|
750
|
+
value.overflow,
|
|
751
|
+
value.bounds_overflow_px,
|
|
752
|
+
value.horizontal_overflow_px,
|
|
753
|
+
value.left_overflow_px,
|
|
754
|
+
value.right_overflow_px
|
|
755
|
+
);
|
|
756
|
+
for (const offender of boundsOffendersForEvidence(value)) {
|
|
757
|
+
max = Math.max(max, horizontalOffenderOverflowPx(offender));
|
|
758
|
+
}
|
|
759
|
+
return roundPixels(max);
|
|
760
|
+
}
|
|
615
761
|
function assessProfile(profile, evidence) {
|
|
616
762
|
const checks = [];
|
|
617
763
|
const viewports = evidence.viewports || [];
|
|
@@ -729,13 +875,19 @@ function assessProfile(profile, evidence) {
|
|
|
729
875
|
});
|
|
730
876
|
continue;
|
|
731
877
|
}
|
|
732
|
-
const failed = applicable.filter((viewport) => (viewport
|
|
878
|
+
const failed = applicable.filter((viewport) => horizontalBoundsOverflowPx(viewport) > maxOverflow);
|
|
733
879
|
checks.push({
|
|
734
880
|
type: check.type,
|
|
735
881
|
label: check.label || check.type,
|
|
736
882
|
status: failed.length ? "failed" : "passed",
|
|
737
|
-
evidence: {
|
|
738
|
-
|
|
883
|
+
evidence: {
|
|
884
|
+
max_overflow_px: maxOverflow,
|
|
885
|
+
overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null),
|
|
886
|
+
bounds_overflow_px: applicable.map((viewport) => horizontalBoundsOverflowPx(viewport)),
|
|
887
|
+
overflow_offender_counts: applicable.map((viewport) => boundsOffendersForEvidence(viewport).length),
|
|
888
|
+
viewports: applicable.map((viewport) => viewport.name)
|
|
889
|
+
},
|
|
890
|
+
message: failed.length ? "Horizontal bounds overflow exceeded " + maxOverflow + "px in " + failed.length + " viewport(s)." : undefined,
|
|
739
891
|
});
|
|
740
892
|
continue;
|
|
741
893
|
}
|
|
@@ -965,14 +1117,55 @@ async function captureViewport(viewport) {
|
|
|
965
1117
|
const body = document.body;
|
|
966
1118
|
const documentElement = document.documentElement;
|
|
967
1119
|
const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
|
|
1120
|
+
const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
|
|
1121
|
+
const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
|
|
1122
|
+
const viewportWidth = clientWidth || window.innerWidth;
|
|
1123
|
+
const overflowOffenders = [];
|
|
1124
|
+
for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
|
|
1125
|
+
const rect = element.getBoundingClientRect();
|
|
1126
|
+
if (!rect || rect.width < 1 || rect.height < 1) continue;
|
|
1127
|
+
const style = window.getComputedStyle(element);
|
|
1128
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
|
|
1129
|
+
const leftOverflow = Math.max(0, -rect.left);
|
|
1130
|
+
const rightOverflow = Math.max(0, rect.right - viewportWidth);
|
|
1131
|
+
const overflow = Math.max(leftOverflow, rightOverflow);
|
|
1132
|
+
if (overflow <= 0.5) continue;
|
|
1133
|
+
const tag = element.tagName ? element.tagName.toLowerCase() : "element";
|
|
1134
|
+
const id = element.id ? "#" + element.id : "";
|
|
1135
|
+
const className = typeof element.className === "string"
|
|
1136
|
+
? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
|
|
1137
|
+
: "";
|
|
1138
|
+
overflowOffenders.push({
|
|
1139
|
+
selector: tag + id + (className ? "." + className : ""),
|
|
1140
|
+
tag,
|
|
1141
|
+
text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
|
|
1142
|
+
overflow,
|
|
1143
|
+
left_overflow_px: leftOverflow,
|
|
1144
|
+
right_overflow_px: rightOverflow,
|
|
1145
|
+
viewport_width: viewportWidth,
|
|
1146
|
+
rect: {
|
|
1147
|
+
left: rect.left,
|
|
1148
|
+
right: rect.right,
|
|
1149
|
+
width: rect.width,
|
|
1150
|
+
},
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
overflowOffenders.sort((a, b) => b.overflow - a.overflow);
|
|
1154
|
+
const boundsOverflowPx = Math.max(
|
|
1155
|
+
0,
|
|
1156
|
+
scrollWidth - (clientWidth || viewportWidth),
|
|
1157
|
+
...overflowOffenders.map((offender) => offender.overflow),
|
|
1158
|
+
);
|
|
968
1159
|
return {
|
|
969
1160
|
url: location.href,
|
|
970
1161
|
pathname: location.pathname,
|
|
971
1162
|
title: document.title,
|
|
972
1163
|
body_text_length: text.length,
|
|
973
1164
|
body_text_sample: text.slice(0, 8000),
|
|
974
|
-
scroll_width:
|
|
975
|
-
client_width:
|
|
1165
|
+
scroll_width: scrollWidth,
|
|
1166
|
+
client_width: clientWidth,
|
|
1167
|
+
bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
|
|
1168
|
+
overflow_offenders: overflowOffenders.slice(0, 10),
|
|
976
1169
|
};
|
|
977
1170
|
}).catch((error) => ({
|
|
978
1171
|
url: page.url(),
|
|
@@ -982,6 +1175,8 @@ async function captureViewport(viewport) {
|
|
|
982
1175
|
body_text_sample: "",
|
|
983
1176
|
scroll_width: 0,
|
|
984
1177
|
client_width: viewport.width,
|
|
1178
|
+
bounds_overflow_px: 0,
|
|
1179
|
+
overflow_offenders: [],
|
|
985
1180
|
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
986
1181
|
}));
|
|
987
1182
|
const selectors = {};
|
|
@@ -1020,6 +1215,8 @@ async function captureViewport(viewport) {
|
|
|
1020
1215
|
scroll_width: dom.scroll_width,
|
|
1021
1216
|
client_width: dom.client_width,
|
|
1022
1217
|
overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
|
|
1218
|
+
bounds_overflow_px: dom.bounds_overflow_px,
|
|
1219
|
+
overflow_offenders: dom.overflow_offenders || [],
|
|
1023
1220
|
selectors,
|
|
1024
1221
|
text_matches,
|
|
1025
1222
|
setup_action_results: setupActionResults,
|
|
@@ -1051,6 +1248,8 @@ function buildProfileEvidence(currentViewports) {
|
|
|
1051
1248
|
routes: currentViewports.map((viewport) => viewport.route),
|
|
1052
1249
|
titles: currentViewports.map((viewport) => viewport.title),
|
|
1053
1250
|
overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
|
|
1251
|
+
bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
|
|
1252
|
+
overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
|
|
1054
1253
|
},
|
|
1055
1254
|
};
|
|
1056
1255
|
}
|