@buoy-gg/highlight-updates 7.0.27 → 7.0.29
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/lib/commonjs/highlight-updates/components/HighlightFilterView.js +102 -0
- package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +5 -1
- package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +7 -1
- package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +6 -0
- package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +26 -3
- package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +7 -1
- package/lib/commonjs/highlight-updates/components/RenderDetailView.js +40 -1
- package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +29 -6
- package/lib/commonjs/highlight-updates/components/RenderListItem.js +15 -0
- package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +11 -0
- package/lib/commonjs/highlight-updates/components/StatsDisplay.js +3 -0
- package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +6 -1
- package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +65 -3
- package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +15 -3
- package/lib/commonjs/highlight-updates/utils/RenderTracker.js +6 -1
- package/lib/commonjs/highlight-updates/utils/namedComposite.js +41 -0
- package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +103 -0
- package/lib/commonjs/highlight-updates/utils/screenElements.js +50 -25
- package/lib/commonjs/highlight-updates/utils/tapMatch.js +58 -0
- package/lib/commonjs/highlight-updates/utils/testIDs.js +68 -0
- package/lib/commonjs/index.js +34 -0
- package/lib/module/highlight-updates/components/HighlightFilterView.js +102 -0
- package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +5 -1
- package/lib/module/highlight-updates/components/IdentifierBadge.js +7 -1
- package/lib/module/highlight-updates/components/IsolatedRenderList.js +6 -0
- package/lib/module/highlight-updates/components/ModalHeaderContent.js +26 -3
- package/lib/module/highlight-updates/components/RenderCauseBadge.js +7 -1
- package/lib/module/highlight-updates/components/RenderDetailView.js +40 -1
- package/lib/module/highlight-updates/components/RenderHistoryViewer.js +29 -6
- package/lib/module/highlight-updates/components/RenderListItem.js +15 -0
- package/lib/module/highlight-updates/components/RendersCopySettingsView.js +11 -0
- package/lib/module/highlight-updates/components/StatsDisplay.js +3 -0
- package/lib/module/highlight-updates/utils/CommitProfiler.js +5 -1
- package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +65 -3
- package/lib/module/highlight-updates/utils/RenderCauseDetector.js +15 -3
- package/lib/module/highlight-updates/utils/RenderTracker.js +6 -1
- package/lib/module/highlight-updates/utils/namedComposite.js +36 -0
- package/lib/module/highlight-updates/utils/ownStateChanged.js +98 -0
- package/lib/module/highlight-updates/utils/screenElements.js +50 -25
- package/lib/module/highlight-updates/utils/tapMatch.js +52 -0
- package/lib/module/highlight-updates/utils/testIDs.js +60 -0
- package/lib/module/index.js +5 -1
- package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts +7 -1
- package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts +8 -1
- package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts +6 -0
- package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/namedComposite.d.ts +17 -0
- package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts +66 -0
- package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/tapMatch.d.ts +42 -0
- package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/testIDs.d.ts +44 -0
- package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isHostViewName = isHostViewName;
|
|
7
|
+
exports.isNamedComposite = isNamedComposite;
|
|
8
|
+
/**
|
|
9
|
+
* Host views (RCTView / View / Text) drown the Highlight Updates list.
|
|
10
|
+
* Named composites are what you actually want when asking "what re-rendered?"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const HOST_NAMES = new Set(["View", "RCTView", "Text", "RCTText", "RawText", "RCTRawText", "VirtualText", "RCTVirtualText", "ScrollView", "RCTScrollView", "RCTScrollContentView", "SafeAreaView", "RCTSafeAreaView", "Pressable", "TouchableOpacity", "Image", "RCTImageView"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fiber names that look like identity but are list chrome / RN internals.
|
|
17
|
+
* Only provably-anonymous names belong here — real components (CopyButton,
|
|
18
|
+
* a user's `Copy`…) must stay visible. "Comp Copy"-style truncated
|
|
19
|
+
* placeholders are handled by the regex in isNamedComposite instead.
|
|
20
|
+
*/
|
|
21
|
+
const PLACEHOLDER_NAMES = new Set(["Unknown", "Anonymous", "CellRenderer", "NativeComponent"]);
|
|
22
|
+
/**
|
|
23
|
+
* True when this row is a named user composite (or has a testID). Host views
|
|
24
|
+
* and anonymous "Comp Copy" wrappers return false.
|
|
25
|
+
*/
|
|
26
|
+
function isNamedComposite(r) {
|
|
27
|
+
if (r.testID && r.testID.trim()) return true;
|
|
28
|
+
const name = (r.componentName ?? "").trim();
|
|
29
|
+
if (!name) return false;
|
|
30
|
+
if (HOST_NAMES.has(name)) return false;
|
|
31
|
+
if (PLACEHOLDER_NAMES.has(name)) return false;
|
|
32
|
+
const view = (r.viewType ?? "").trim();
|
|
33
|
+
if (view && HOST_NAMES.has(view) && name === view) return false;
|
|
34
|
+
// "Comp Copy" / "1x" style placeholders — no real component identity.
|
|
35
|
+
if (/^comp(\s|$)/i.test(name)) return false;
|
|
36
|
+
if (name.startsWith("_")) return false;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function isHostViewName(name) {
|
|
40
|
+
return !!name && HOST_NAMES.has(name);
|
|
41
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.hookStateChanged = hookStateChanged;
|
|
7
|
+
exports.ownStateChanged = ownStateChanged;
|
|
8
|
+
/**
|
|
9
|
+
* "Did this component's own state actually change?" — one implementation.
|
|
10
|
+
*
|
|
11
|
+
* This package classifies render causes twice, on purpose. `RenderCauseDetector`
|
|
12
|
+
* serves the overlay and rides React DevTools' `traceUpdates`, which hands it
|
|
13
|
+
* fibers at an ambiguous moment — hence its WeakMap fallbacks and fiber-swap
|
|
14
|
+
* detection. `CommitProfiler` serves Bench's render capture from inside
|
|
15
|
+
* `onCommitFiberRoot`, where `fiber` is current and `fiber.alternate` is
|
|
16
|
+
* previous by definition, and where it runs on every composite fiber of every
|
|
17
|
+
* commit and so counts allocations. Those are genuinely different problems and
|
|
18
|
+
* the two classifiers should stay separate.
|
|
19
|
+
*
|
|
20
|
+
* This ONE question is not different between them, and it is the question they
|
|
21
|
+
* both got wrong:
|
|
22
|
+
*
|
|
23
|
+
* `prev.memoizedState !== next.memoizedState`
|
|
24
|
+
*
|
|
25
|
+
* For a function component `memoizedState` is the head of the hook linked list,
|
|
26
|
+
* and React rebuilds that list on every update — `updateWorkInProgressHook`
|
|
27
|
+
* clones each node — so the head is a fresh object whether or not a single
|
|
28
|
+
* hook's value moved. Read as "state changed" it makes "state"/"hooks" the
|
|
29
|
+
* answer for every component that uses ANY hook (a lone `useEffect` is enough)
|
|
30
|
+
* and leaves "parent" reachable only by the vanishingly rare component that
|
|
31
|
+
* uses none.
|
|
32
|
+
*
|
|
33
|
+
* That is the one distinction in either tool a developer acts on: "parent"
|
|
34
|
+
* means memoize it or lift the state, "state" means it genuinely updated and
|
|
35
|
+
* should be left alone. Backwards, it sends you to optimise the thing that was
|
|
36
|
+
* already fine — and in Bench it also silently shrank `wastedRenders`, the
|
|
37
|
+
* number that says whether there is anything to fix at all.
|
|
38
|
+
*
|
|
39
|
+
* Both tools reached that wrong line independently, which is exactly why the
|
|
40
|
+
* answer lives here now rather than in two places that happen to agree.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** React work tag for a class component — its `memoizedState` IS the state. */
|
|
44
|
+
const CLASS_COMPONENT = 1;
|
|
45
|
+
|
|
46
|
+
/** Guardrail on the hook-list walk; deep hook lists are pathological. */
|
|
47
|
+
const MAX_HOOKS_WALKED = 64;
|
|
48
|
+
|
|
49
|
+
/** Does this look like React's hook linked list rather than class state? */
|
|
50
|
+
function isHookList(head) {
|
|
51
|
+
return !!head && typeof head === "object" && ("next" in head || "queue" in head || "memoizedState" in head);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* True when a STATE hook's value actually moved between two hook-list heads.
|
|
56
|
+
*
|
|
57
|
+
* `queue` is the marker: `useState` and `useReducer` — and everything built on
|
|
58
|
+
* them — own one; effects, memos, callbacks and refs do not. An effect's
|
|
59
|
+
* `memoizedState` is a brand-new object on every single render, which is the
|
|
60
|
+
* specific thing that broke the identity check.
|
|
61
|
+
*
|
|
62
|
+
* Note that a `useRef` mutation is deliberately NOT a state change here. It
|
|
63
|
+
* cannot cause a render, so a render attributed to one is a render blamed on
|
|
64
|
+
* something that did not do it.
|
|
65
|
+
*
|
|
66
|
+
* Conservative where it cannot tell: a class component, or a `memoizedState`
|
|
67
|
+
* that is not a recognisable hook list, falls back to the identity comparison.
|
|
68
|
+
* There the identity IS meaningful (or is the best signal available), so the
|
|
69
|
+
* old behaviour is the right behaviour.
|
|
70
|
+
*
|
|
71
|
+
* Takes the HEADS rather than the fibers because only one of the two callers
|
|
72
|
+
* has a previous fiber to hand — the overlay may be working from a WeakMap
|
|
73
|
+
* snapshot of the previous `memoizedState` instead. Use `ownStateChanged` when
|
|
74
|
+
* you do have both fibers.
|
|
75
|
+
*/
|
|
76
|
+
function hookStateChanged(prevHead, nextHead, options) {
|
|
77
|
+
if (prevHead === nextHead) return false;
|
|
78
|
+
if (options?.isClassComponent) return true;
|
|
79
|
+
if (!isHookList(nextHead)) return true;
|
|
80
|
+
let p = prevHead;
|
|
81
|
+
let n = nextHead;
|
|
82
|
+
for (let depth = 0; n != null && depth < MAX_HOOKS_WALKED; depth++) {
|
|
83
|
+
if (n.queue != null) {
|
|
84
|
+
// A hook the previous render did not have is a real change.
|
|
85
|
+
if (p == null) return true;
|
|
86
|
+
if (!Object.is(p.memoizedState, n.memoizedState)) return true;
|
|
87
|
+
}
|
|
88
|
+
n = n.next;
|
|
89
|
+
p = p != null ? p.next : null;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* `hookStateChanged` for callers holding both fibers — the common case inside
|
|
96
|
+
* `onCommitFiberRoot`, where `fiber.alternate` is the previous fiber by
|
|
97
|
+
* definition.
|
|
98
|
+
*/
|
|
99
|
+
function ownStateChanged(prev, next) {
|
|
100
|
+
return hookStateChanged(prev?.memoizedState, next?.memoizedState, {
|
|
101
|
+
isClassComponent: next?.tag === CLASS_COMPONENT
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.describeScreen = describeScreen;
|
|
7
7
|
exports.tapElement = tapElement;
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
|
+
var _tapMatch = require("./tapMatch");
|
|
9
10
|
/**
|
|
10
11
|
* On-device screen element discovery + in-JS tap.
|
|
11
12
|
*
|
|
@@ -499,9 +500,10 @@ async function describeScreen() {
|
|
|
499
500
|
if (r) {
|
|
500
501
|
const cx = r.x + r.width / 2;
|
|
501
502
|
const cy = r.y + r.height / 2;
|
|
502
|
-
//
|
|
503
|
+
// Drop previous Expo Router screens (centers well outside the window).
|
|
504
|
+
// Keep a 10% slack so the floating menu / home-indicator-clipped rows stay.
|
|
503
505
|
if (cy < -screenH * 0.1 || cy > screenH * 1.1 || cx < -screenW * 0.1 || cx > screenW * 1.1) {
|
|
504
|
-
|
|
506
|
+
continue;
|
|
505
507
|
}
|
|
506
508
|
tap = {
|
|
507
509
|
x: round2(cx / screenW),
|
|
@@ -554,20 +556,24 @@ function round2(n) {
|
|
|
554
556
|
|
|
555
557
|
// --- tapElement -----------------------------------------------------------
|
|
556
558
|
|
|
559
|
+
function fieldsOf(c) {
|
|
560
|
+
return {
|
|
561
|
+
nativeTag: c.nativeTag,
|
|
562
|
+
testID: c.testID,
|
|
563
|
+
label: c.label,
|
|
564
|
+
text: c.text,
|
|
565
|
+
name: c.name
|
|
566
|
+
};
|
|
567
|
+
}
|
|
557
568
|
function matchScore(c, params) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
// testID exact match should win over a fuzzy name hit.
|
|
567
|
-
if (params.testID && c.testID && c.testID.toLowerCase() === params.testID.toLowerCase()) {
|
|
568
|
-
best = Math.max(best, 200);
|
|
569
|
-
}
|
|
570
|
-
return best;
|
|
569
|
+
return (0, _tapMatch.scoreTapMatch)(fieldsOf(c), params);
|
|
570
|
+
}
|
|
571
|
+
function tapPoint(rect, screenW, screenH) {
|
|
572
|
+
if (!rect || !screenW || !screenH) return undefined;
|
|
573
|
+
return {
|
|
574
|
+
x: (rect.x + rect.width / 2) / screenW,
|
|
575
|
+
y: (rect.y + rect.height / 2) / screenH
|
|
576
|
+
};
|
|
571
577
|
}
|
|
572
578
|
function makeSyntheticPressEvent(rect) {
|
|
573
579
|
const pageX = rect ? rect.x + rect.width / 2 : 0;
|
|
@@ -623,7 +629,28 @@ async function tapElement(params) {
|
|
|
623
629
|
}))
|
|
624
630
|
};
|
|
625
631
|
}
|
|
626
|
-
|
|
632
|
+
|
|
633
|
+
// Measure the top matches so an on-screen DialIcon beats an off-screen nav
|
|
634
|
+
// chip whose testID happens to contain the same substring.
|
|
635
|
+
const win = _reactNative.Dimensions.get("window");
|
|
636
|
+
const screenW = win.width || 1;
|
|
637
|
+
const screenH = win.height || 1;
|
|
638
|
+
const top = ranked.slice(0, 15);
|
|
639
|
+
await Promise.all(top.map(async r => {
|
|
640
|
+
if (r.c.instance && !r.c.rect) r.c.rect = await measureInstance(r.c.instance);
|
|
641
|
+
}));
|
|
642
|
+
for (const r of top) {
|
|
643
|
+
r.score = (0, _tapMatch.scoreTapMatch)({
|
|
644
|
+
...fieldsOf(r.c),
|
|
645
|
+
tap: tapPoint(r.c.rect, screenW, screenH)
|
|
646
|
+
}, params);
|
|
647
|
+
}
|
|
648
|
+
top.sort((a, b) => b.score - a.score);
|
|
649
|
+
|
|
650
|
+
// Fall back to the FULL ranked list for an interactive match — the top-15
|
|
651
|
+
// re-score is only a tiebreak, and on a busy screen the pressable match can
|
|
652
|
+
// sit below 15 text-score peers.
|
|
653
|
+
const target = top.find(r => isInteractive(r.c)) ?? ranked.find(r => isInteractive(r.c)) ?? top[0];
|
|
627
654
|
const c = target.c;
|
|
628
655
|
if (!isInteractive(c)) {
|
|
629
656
|
return {
|
|
@@ -642,20 +669,18 @@ async function tapElement(params) {
|
|
|
642
669
|
|
|
643
670
|
// Measure the target so we can decide whether to scroll it on-screen (and,
|
|
644
671
|
// for a press, give the synthetic event real coordinates).
|
|
645
|
-
if (c.instance) c.rect = await measureInstance(c.instance);
|
|
672
|
+
if (c.instance && !c.rect) c.rect = await measureInstance(c.instance);
|
|
646
673
|
|
|
647
674
|
// Bring it into view if it's off-screen and scrolling is wanted. The action
|
|
648
675
|
// doesn't NEED this (handlers are invoked directly), but it keeps the element
|
|
649
676
|
// visible for the human watching / a follow-up describe.
|
|
650
677
|
let scrolled = false;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (scrolled) c.rect = await measureInstance(c.instance); // fresh coords
|
|
658
|
-
}
|
|
678
|
+
// A failed measure (rect == null) counts as off-screen: the node may be in
|
|
679
|
+
// a virtualized list that hasn't materialized it, and scrolling can help.
|
|
680
|
+
const offScreen = c.rect == null || !(0, _tapMatch.isRectCenterOnScreen)(c.rect, screenW, screenH);
|
|
681
|
+
if (offScreen && params.scrollIntoView !== false && c.instance) {
|
|
682
|
+
scrolled = await scrollIntoView(c.fiber, c.instance, c.stateNode, 80);
|
|
683
|
+
if (scrolled) c.rect = await measureInstance(c.instance); // fresh coords
|
|
659
684
|
}
|
|
660
685
|
const base = {
|
|
661
686
|
nativeTag: c.nativeTag,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isOnScreenTap = isOnScreenTap;
|
|
7
|
+
exports.isRectCenterOnScreen = isRectCenterOnScreen;
|
|
8
|
+
exports.scoreTapMatch = scoreTapMatch;
|
|
9
|
+
/**
|
|
10
|
+
* Shared tap-target scoring for describe_screen / tap_element.
|
|
11
|
+
*
|
|
12
|
+
* Ranking used to be substring-on-testID only, so `tap: "ASSETS"` preferred
|
|
13
|
+
* `nav-assets-tool-test` (score 25) over a visible dial icon, and off-screen
|
|
14
|
+
* Expo Router screens (y > 1) stayed in the candidate list.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** True when the measured center is inside the window (10% slack for home-indicator / portal hosts). */
|
|
18
|
+
function isOnScreenTap(tap) {
|
|
19
|
+
if (!tap) return false;
|
|
20
|
+
return tap.x >= -0.1 && tap.x <= 1.1 && tap.y >= -0.1 && tap.y <= 1.1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Point-in-window test in *points* (measureInWindow coords), with slack so
|
|
25
|
+
* the floating menu and rows clipped at the home indicator still count.
|
|
26
|
+
* Previous Expo Router screens typically measure at y > 1.2× window height.
|
|
27
|
+
*/
|
|
28
|
+
function isRectCenterOnScreen(rect, screenW, screenH) {
|
|
29
|
+
if (!rect || screenW <= 0 || screenH <= 0) return false;
|
|
30
|
+
const cx = rect.x + rect.width / 2;
|
|
31
|
+
const cy = rect.y + rect.height / 2;
|
|
32
|
+
const slackX = screenW * 0.1;
|
|
33
|
+
const slackY = screenH * 0.1;
|
|
34
|
+
return cx >= -slackX && cx <= screenW + slackX && cy >= -slackY && cy <= screenH + slackY;
|
|
35
|
+
}
|
|
36
|
+
function scoreTapMatch(c, params) {
|
|
37
|
+
if (params.nativeTag != null && c.nativeTag === params.nativeTag) return 1000;
|
|
38
|
+
const q = (params.query ?? params.testID ?? "").trim().toLowerCase();
|
|
39
|
+
if (!q) return 0;
|
|
40
|
+
const fields = [c.testID, c.label, c.text, c.name].filter(Boolean).map(s => s.toLowerCase());
|
|
41
|
+
let best = 0;
|
|
42
|
+
for (const f of fields) {
|
|
43
|
+
if (f === q) best = Math.max(best, 100);else if (f.startsWith(q)) best = Math.max(best, 50);else if (f.includes(q)) best = Math.max(best, 25);
|
|
44
|
+
}
|
|
45
|
+
if (params.testID && c.testID && c.testID.toLowerCase() === params.testID.toLowerCase()) {
|
|
46
|
+
best = Math.max(best, 200);
|
|
47
|
+
}
|
|
48
|
+
if (best === 0) return 0;
|
|
49
|
+
const id = (c.testID ?? "").toLowerCase();
|
|
50
|
+
// Dial icons are the thing agents mean when they tap a tool name. (No
|
|
51
|
+
// penalties for app testID patterns — this ships in the library, and we
|
|
52
|
+
// don't know consumer apps' naming conventions.)
|
|
53
|
+
if (id.startsWith("buoy-dial-")) best += 40;
|
|
54
|
+
if (c.tap) {
|
|
55
|
+
if (isOnScreenTap(c.tap)) best += 20;else best -= 40;
|
|
56
|
+
}
|
|
57
|
+
return best;
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.detectedItemTestID = detectedItemTestID;
|
|
7
|
+
exports.identifierSlug = identifierSlug;
|
|
8
|
+
exports.patternChipTestID = patternChipTestID;
|
|
9
|
+
exports.renderRowTestID = renderRowTestID;
|
|
10
|
+
exports.settingTestID = settingTestID;
|
|
11
|
+
/**
|
|
12
|
+
* Stable e2e handles for the Render Highlighter (highlight-updates).
|
|
13
|
+
*
|
|
14
|
+
* This tool's list is the hardest thing in Buoy to address, because the thing
|
|
15
|
+
* it lists is a NATIVE VIEW and the only key it has for one is the fiber's
|
|
16
|
+
* `nativeTag` — a per-session counter that changes on every launch, on every
|
|
17
|
+
* remount, and (on a dynamic screen) between two runs of the same test. A
|
|
18
|
+
* handle built from it would name a different component every time.
|
|
19
|
+
*
|
|
20
|
+
* So rows are addressed by their IDENTIFIER, in exactly the priority the list
|
|
21
|
+
* item itself uses to label them: testID → nativeID → component name →
|
|
22
|
+
* accessibility label → nativeTag. That is the same string the developer sees
|
|
23
|
+
* on the row, it is stable as long as the component is, and it is what a test
|
|
24
|
+
* can name up front. `nativeTag` remains the last resort for an anonymous host
|
|
25
|
+
* view, which no test should be naming anyway.
|
|
26
|
+
*
|
|
27
|
+
* Slug rules are mirrored in `example-dev-build/utils/renderBench.js` so the
|
|
28
|
+
* fixture and the tool agree about what a row is called; the suite's
|
|
29
|
+
* `fixture.js` spot-checks that they still do.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Any identifier string → a testID-safe slug. */
|
|
33
|
+
function identifierSlug(value) {
|
|
34
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `buoy-highlight-row-<slug>` for one row of the render list.
|
|
39
|
+
*
|
|
40
|
+
* Sub-handles hang off it: `-name`, `-count`, `-type`, `-cause`, `-identifier`,
|
|
41
|
+
* `-time`. The count is the tool's whole claim, so it gets its own handle
|
|
42
|
+
* rather than living inside the row's merged label.
|
|
43
|
+
*/
|
|
44
|
+
function renderRowTestID(identifier) {
|
|
45
|
+
return `buoy-highlight-row-${identifierSlug(identifier)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `buoy-highlight-detected-<type>-<slug>` for one chip in DETECTED ITEMS.
|
|
50
|
+
*
|
|
51
|
+
* The type is in the handle because the same string legitimately appears under
|
|
52
|
+
* two categories — a component called `Pressable` and a viewType called
|
|
53
|
+
* `Pressable` are different filters with the same name, and a suite that added
|
|
54
|
+
* the wrong one would still see "a pattern was added".
|
|
55
|
+
*/
|
|
56
|
+
function detectedItemTestID(type, value) {
|
|
57
|
+
return `buoy-highlight-detected-${identifierSlug(type)}-${identifierSlug(value)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `buoy-highlight-<mode>-chip-<type>-<slug>` for an active filter pattern. */
|
|
61
|
+
function patternChipTestID(mode, type, value) {
|
|
62
|
+
return `buoy-highlight-${mode}-chip-${identifierSlug(type)}-${identifierSlug(value)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `buoy-highlight-setting-<key>` for a settings row's control. */
|
|
66
|
+
function settingTestID(key) {
|
|
67
|
+
return `buoy-highlight-setting-${key}`;
|
|
68
|
+
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -141,6 +141,12 @@ Object.defineProperty(exports, "detectRenderCause", {
|
|
|
141
141
|
return _RenderCauseDetector.detectRenderCause;
|
|
142
142
|
}
|
|
143
143
|
});
|
|
144
|
+
Object.defineProperty(exports, "detectedItemTestID", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
get: function () {
|
|
147
|
+
return _testIDs.detectedItemTestID;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
144
150
|
Object.defineProperty(exports, "disableProfilerLogging", {
|
|
145
151
|
enumerable: true,
|
|
146
152
|
get: function () {
|
|
@@ -207,6 +213,12 @@ Object.defineProperty(exports, "highlightUpdatesSyncAdapter", {
|
|
|
207
213
|
return _highlightUpdatesSyncAdapter.highlightUpdatesSyncAdapter;
|
|
208
214
|
}
|
|
209
215
|
});
|
|
216
|
+
Object.defineProperty(exports, "identifierSlug", {
|
|
217
|
+
enumerable: true,
|
|
218
|
+
get: function () {
|
|
219
|
+
return _testIDs.identifierSlug;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
210
222
|
Object.defineProperty(exports, "installProfilerInterceptor", {
|
|
211
223
|
enumerable: true,
|
|
212
224
|
get: function () {
|
|
@@ -238,12 +250,24 @@ Object.defineProperty(exports, "isLoggingEnabled", {
|
|
|
238
250
|
}
|
|
239
251
|
});
|
|
240
252
|
exports.onRenderEvent = void 0;
|
|
253
|
+
Object.defineProperty(exports, "patternChipTestID", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function () {
|
|
256
|
+
return _testIDs.patternChipTestID;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
241
259
|
Object.defineProperty(exports, "removeRenderCauseState", {
|
|
242
260
|
enumerable: true,
|
|
243
261
|
get: function () {
|
|
244
262
|
return _RenderCauseDetector.removeRenderCauseState;
|
|
245
263
|
}
|
|
246
264
|
});
|
|
265
|
+
Object.defineProperty(exports, "renderRowTestID", {
|
|
266
|
+
enumerable: true,
|
|
267
|
+
get: function () {
|
|
268
|
+
return _testIDs.renderRowTestID;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
247
271
|
Object.defineProperty(exports, "safeCloneForHistory", {
|
|
248
272
|
enumerable: true,
|
|
249
273
|
get: function () {
|
|
@@ -262,6 +286,12 @@ Object.defineProperty(exports, "setComparisonCallback", {
|
|
|
262
286
|
return _ProfilerInterceptor.setComparisonCallback;
|
|
263
287
|
}
|
|
264
288
|
});
|
|
289
|
+
Object.defineProperty(exports, "settingTestID", {
|
|
290
|
+
enumerable: true,
|
|
291
|
+
get: function () {
|
|
292
|
+
return _testIDs.settingTestID;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
265
295
|
Object.defineProperty(exports, "uninstallProfilerInterceptor", {
|
|
266
296
|
enumerable: true,
|
|
267
297
|
get: function () {
|
|
@@ -287,6 +317,7 @@ var _HighlightFilterView = require("./highlight-updates/components/HighlightFilt
|
|
|
287
317
|
var _RenderCauseBadge = require("./highlight-updates/components/RenderCauseBadge");
|
|
288
318
|
var _RenderHistoryViewer = require("./highlight-updates/components/RenderHistoryViewer");
|
|
289
319
|
var _renderExportFormatter = require("./highlight-updates/utils/renderExportFormatter");
|
|
320
|
+
var _testIDs = require("./highlight-updates/utils/testIDs");
|
|
290
321
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
291
322
|
/**
|
|
292
323
|
* @buoy-gg/highlight-updates
|
|
@@ -392,4 +423,7 @@ const onRenderEvent = callback => {
|
|
|
392
423
|
// footer) instead of inside a JsModal — for hosts that show tools in a panel.
|
|
393
424
|
|
|
394
425
|
// Simple JSON export for renders
|
|
426
|
+
|
|
427
|
+
// e2e handle builders — exported so the example app's fixture can derive the
|
|
428
|
+
// same row handles the tool renders instead of hardcoding a second copy.
|
|
395
429
|
exports.onRenderEvent = onRenderEvent;
|