@buoy-gg/highlight-updates 7.0.28 → 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 +80 -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 +10 -0
- package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +15 -3
- package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +103 -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 +80 -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 +10 -0
- package/lib/module/highlight-updates/utils/RenderCauseDetector.js +15 -3
- package/lib/module/highlight-updates/utils/ownStateChanged.js +98 -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/ownStateChanged.d.ts +66 -0
- package/lib/typescript/highlight-updates/utils/ownStateChanged.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
|
@@ -95,21 +95,25 @@ function RenderDetailView({
|
|
|
95
95
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
96
96
|
style: styles.headerLeft,
|
|
97
97
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
98
|
+
testID: "buoy-highlight-detail-name",
|
|
98
99
|
style: styles.componentName,
|
|
99
100
|
numberOfLines: 1,
|
|
100
101
|
children: componentName
|
|
101
102
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
102
103
|
style: styles.nativeTypeBadge,
|
|
103
104
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
105
|
+
testID: "buoy-highlight-detail-type",
|
|
104
106
|
style: styles.nativeTypeText,
|
|
105
107
|
children: nativeType
|
|
106
108
|
})
|
|
107
109
|
})]
|
|
108
110
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton, {
|
|
109
111
|
value: copyData,
|
|
110
|
-
size: 14
|
|
112
|
+
size: 14,
|
|
113
|
+
testID: "buoy-highlight-detail-copy"
|
|
111
114
|
})]
|
|
112
115
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
116
|
+
testID: "buoy-highlight-detail-scroll",
|
|
113
117
|
style: styles.scrollView,
|
|
114
118
|
contentContainerStyle: [styles.scrollContent, showHistoryFooter && {
|
|
115
119
|
paddingBottom: 100
|
|
@@ -122,11 +126,13 @@ function RenderDetailView({
|
|
|
122
126
|
render: render,
|
|
123
127
|
rendersPerSec: rendersPerSec
|
|
124
128
|
}), hiddenEventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
129
|
+
testID: "buoy-highlight-detail-locked",
|
|
125
130
|
style: styles.lockedBanner,
|
|
126
131
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
127
132
|
size: 14,
|
|
128
133
|
color: _sharedUi.night.color.warning
|
|
129
134
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
135
|
+
testID: "buoy-highlight-detail-locked-text",
|
|
130
136
|
style: styles.lockedBannerText,
|
|
131
137
|
children: [hiddenEventCount, " ", hiddenEventCount === 1 ? 'event' : 'events', " hidden"]
|
|
132
138
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -171,10 +177,12 @@ function AnswerCard({
|
|
|
171
177
|
return "Changed:";
|
|
172
178
|
};
|
|
173
179
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
180
|
+
testID: "buoy-highlight-detail-cause",
|
|
174
181
|
style: styles.answerCard,
|
|
175
182
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
176
183
|
style: styles.causeBadgeRow,
|
|
177
184
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
185
|
+
testID: "buoy-highlight-detail-cause-label",
|
|
178
186
|
style: styles.causeLabel,
|
|
179
187
|
children: getLabel()
|
|
180
188
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -182,6 +190,7 @@ function AnswerCard({
|
|
|
182
190
|
backgroundColor: config.color + "20"
|
|
183
191
|
}],
|
|
184
192
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
193
|
+
testID: "buoy-highlight-detail-cause-badge",
|
|
185
194
|
style: [styles.causeBadgeLargeText, {
|
|
186
195
|
color: config.color
|
|
187
196
|
}],
|
|
@@ -213,6 +222,7 @@ function AnswerCard({
|
|
|
213
222
|
children: cause.changedKeys.filter(k => !k.includes("(ref only)")).slice(0, 5).map((key, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
214
223
|
style: styles.propChangeChip,
|
|
215
224
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
225
|
+
testID: `buoy-highlight-detail-changed-${index}`,
|
|
216
226
|
style: styles.propChangeText,
|
|
217
227
|
children: key
|
|
218
228
|
})
|
|
@@ -234,6 +244,7 @@ function DetailsSection({
|
|
|
234
244
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
235
245
|
style: styles.detailsSection,
|
|
236
246
|
children: [hasIdentifiers && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
247
|
+
testID: "buoy-highlight-detail-identifiers",
|
|
237
248
|
style: styles.detailsCard,
|
|
238
249
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
239
250
|
style: styles.detailsCardTitle,
|
|
@@ -249,10 +260,13 @@ function DetailsSection({
|
|
|
249
260
|
value: render.accessibilityLabel
|
|
250
261
|
})]
|
|
251
262
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
263
|
+
testID: "buoy-highlight-detail-stats",
|
|
252
264
|
style: styles.statsRow,
|
|
253
265
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
266
|
+
testID: "buoy-highlight-detail-stat-renders",
|
|
254
267
|
style: styles.statItem,
|
|
255
268
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
269
|
+
testID: "buoy-highlight-detail-stat-renders-value",
|
|
256
270
|
style: [styles.statValue, {
|
|
257
271
|
color: render.color
|
|
258
272
|
}],
|
|
@@ -264,8 +278,10 @@ function DetailsSection({
|
|
|
264
278
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
265
279
|
style: styles.statDivider
|
|
266
280
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
281
|
+
testID: "buoy-highlight-detail-stat-rate",
|
|
267
282
|
style: styles.statItem,
|
|
268
283
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
284
|
+
testID: "buoy-highlight-detail-stat-rate-value",
|
|
269
285
|
style: styles.statValue,
|
|
270
286
|
children: rendersPerSec
|
|
271
287
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -275,8 +291,10 @@ function DetailsSection({
|
|
|
275
291
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
276
292
|
style: styles.statDivider
|
|
277
293
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
294
|
+
testID: "buoy-highlight-detail-stat-tag",
|
|
278
295
|
style: styles.statItem,
|
|
279
296
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
297
|
+
testID: "buoy-highlight-detail-stat-tag-value",
|
|
280
298
|
style: styles.statValue,
|
|
281
299
|
children: render.nativeTag
|
|
282
300
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -285,6 +303,7 @@ function DetailsSection({
|
|
|
285
303
|
})]
|
|
286
304
|
})]
|
|
287
305
|
}), hasMeasurements && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
306
|
+
testID: "buoy-highlight-detail-measure",
|
|
288
307
|
style: styles.measurementsRow,
|
|
289
308
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
290
309
|
style: styles.measurementItem,
|
|
@@ -292,6 +311,7 @@ function DetailsSection({
|
|
|
292
311
|
style: styles.measurementLabel,
|
|
293
312
|
children: "x"
|
|
294
313
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
314
|
+
testID: "buoy-highlight-detail-measure-x",
|
|
295
315
|
style: styles.measurementValue,
|
|
296
316
|
children: Math.round(render.measurements.x)
|
|
297
317
|
})]
|
|
@@ -301,6 +321,7 @@ function DetailsSection({
|
|
|
301
321
|
style: styles.measurementLabel,
|
|
302
322
|
children: "y"
|
|
303
323
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
324
|
+
testID: "buoy-highlight-detail-measure-y",
|
|
304
325
|
style: styles.measurementValue,
|
|
305
326
|
children: Math.round(render.measurements.y)
|
|
306
327
|
})]
|
|
@@ -310,6 +331,7 @@ function DetailsSection({
|
|
|
310
331
|
style: styles.measurementLabel,
|
|
311
332
|
children: "w"
|
|
312
333
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
334
|
+
testID: "buoy-highlight-detail-measure-w",
|
|
313
335
|
style: styles.measurementValue,
|
|
314
336
|
children: Math.round(render.measurements.width)
|
|
315
337
|
})]
|
|
@@ -319,6 +341,7 @@ function DetailsSection({
|
|
|
319
341
|
style: styles.measurementLabel,
|
|
320
342
|
children: "h"
|
|
321
343
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
344
|
+
testID: "buoy-highlight-detail-measure-h",
|
|
322
345
|
style: styles.measurementValue,
|
|
323
346
|
children: Math.round(render.measurements.height)
|
|
324
347
|
})]
|
|
@@ -334,12 +357,15 @@ function DetailRow({
|
|
|
334
357
|
label,
|
|
335
358
|
value
|
|
336
359
|
}) {
|
|
360
|
+
const handle = `buoy-highlight-detail-id-${label.toLowerCase()}`;
|
|
337
361
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
362
|
+
testID: handle,
|
|
338
363
|
style: styles.detailRow,
|
|
339
364
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
340
365
|
style: styles.detailLabel,
|
|
341
366
|
children: label
|
|
342
367
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
368
|
+
testID: `${handle}-value`,
|
|
343
369
|
style: styles.detailValue,
|
|
344
370
|
numberOfLines: 1,
|
|
345
371
|
children: value
|
|
@@ -421,6 +447,7 @@ function QuickActionsSection({
|
|
|
421
447
|
}
|
|
422
448
|
}, [selectedOption, onAddFilter]);
|
|
423
449
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
450
|
+
testID: "buoy-highlight-quick",
|
|
424
451
|
style: styles.quickActionsSection,
|
|
425
452
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
426
453
|
style: styles.quickActionsTitle,
|
|
@@ -428,6 +455,7 @@ function QuickActionsSection({
|
|
|
428
455
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
429
456
|
style: styles.filterOptionsList,
|
|
430
457
|
children: filterOptions.map((option, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(FilterOptionCard, {
|
|
458
|
+
testID: `buoy-highlight-quick-${option.type.toLowerCase()}`,
|
|
431
459
|
label: option.label,
|
|
432
460
|
value: option.value,
|
|
433
461
|
isSelected: selectedIndex === index,
|
|
@@ -436,6 +464,9 @@ function QuickActionsSection({
|
|
|
436
464
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
437
465
|
style: styles.filterActionButtons,
|
|
438
466
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
467
|
+
testID: "buoy-highlight-quick-include",
|
|
468
|
+
accessibilityRole: "button",
|
|
469
|
+
accessibilityLabel: "Only show this",
|
|
439
470
|
style: [styles.filterActionButton, styles.filterActionButtonInclude, !selectedOption && styles.filterActionButtonDisabled],
|
|
440
471
|
onPress: handleInclude,
|
|
441
472
|
disabled: !selectedOption,
|
|
@@ -450,6 +481,9 @@ function QuickActionsSection({
|
|
|
450
481
|
children: "Only Show This"
|
|
451
482
|
})]
|
|
452
483
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
484
|
+
testID: "buoy-highlight-quick-exclude",
|
|
485
|
+
accessibilityRole: "button",
|
|
486
|
+
accessibilityLabel: "Hide this",
|
|
453
487
|
style: [styles.filterActionButton, styles.filterActionButtonExclude, !selectedOption && styles.filterActionButtonDisabled],
|
|
454
488
|
onPress: handleExclude,
|
|
455
489
|
disabled: !selectedOption,
|
|
@@ -472,12 +506,16 @@ function QuickActionsSection({
|
|
|
472
506
|
* FilterOptionCard - A selectable filter option card
|
|
473
507
|
*/
|
|
474
508
|
function FilterOptionCard({
|
|
509
|
+
testID,
|
|
475
510
|
label,
|
|
476
511
|
value,
|
|
477
512
|
isSelected,
|
|
478
513
|
onSelect
|
|
479
514
|
}) {
|
|
480
515
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
516
|
+
testID: testID,
|
|
517
|
+
accessibilityRole: "button",
|
|
518
|
+
accessibilityLabel: `${label}, ${value}`,
|
|
481
519
|
style: [styles.filterOptionCard, isSelected && styles.filterOptionCardSelected],
|
|
482
520
|
onPress: onSelect,
|
|
483
521
|
activeOpacity: 0.7,
|
|
@@ -485,6 +523,7 @@ function FilterOptionCard({
|
|
|
485
523
|
style: [styles.filterOptionLabel, isSelected && styles.filterOptionLabelSelected],
|
|
486
524
|
children: label
|
|
487
525
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
526
|
+
testID: testID ? `${testID}-value` : undefined,
|
|
488
527
|
style: [styles.filterOptionValue, isSelected && styles.filterOptionValueSelected],
|
|
489
528
|
numberOfLines: 1,
|
|
490
529
|
children: value
|
|
@@ -22,15 +22,22 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* `HH:MM:SS.mmm` — the same shape Redux, Zustand and Jotai print for the same
|
|
26
|
+
* job, and for the same reason: the milliseconds are the point.
|
|
27
|
+
*
|
|
28
|
+
* Built by hand rather than from `toLocaleTimeString`, which appends the day
|
|
29
|
+
* period on a 12-hour locale — so concatenating the milliseconds to the end of
|
|
30
|
+
* it produced "8:21:44 AM.351", with the fraction attached to the wrong part
|
|
31
|
+
* of the time. It also made the string depend on the hour of day, which is a
|
|
32
|
+
* fine way to have a test that only passes after 10am.
|
|
26
33
|
*/
|
|
27
34
|
function formatTimeWithMs(timestamp) {
|
|
28
35
|
const date = new Date(timestamp);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
const h = date.getHours().toString().padStart(2, "0");
|
|
37
|
+
const m = date.getMinutes().toString().padStart(2, "0");
|
|
38
|
+
const s = date.getSeconds().toString().padStart(2, "0");
|
|
39
|
+
const ms = date.getMilliseconds().toString().padStart(3, "0");
|
|
40
|
+
return `${h}:${m}:${s}.${ms}`;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
@@ -90,14 +97,17 @@ function RenderHistoryViewer({
|
|
|
90
97
|
// If no history, show empty state
|
|
91
98
|
if (totalEvents === 0) {
|
|
92
99
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
100
|
+
testID: "buoy-highlight-history-empty",
|
|
93
101
|
style: styles.emptyContainer,
|
|
94
102
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
|
|
95
103
|
size: 32,
|
|
96
104
|
color: _sharedUi.night.color.textTertiary
|
|
97
105
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
106
|
+
testID: "buoy-highlight-history-empty-title",
|
|
98
107
|
style: styles.emptyTitle,
|
|
99
108
|
children: "No Render History"
|
|
100
109
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
110
|
+
testID: "buoy-highlight-history-empty-text",
|
|
101
111
|
style: styles.emptyText,
|
|
102
112
|
children: "Enable render history tracking in settings to see render events here."
|
|
103
113
|
})]
|
|
@@ -109,11 +119,13 @@ function RenderHistoryViewer({
|
|
|
109
119
|
paddingBottom: !disableInternalFooter && totalEvents > 1 ? 80 : 0
|
|
110
120
|
}],
|
|
111
121
|
children: [lockedEventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
122
|
+
testID: "buoy-highlight-history-locked",
|
|
112
123
|
style: styles.lockedBanner,
|
|
113
124
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
114
125
|
size: 14,
|
|
115
126
|
color: _sharedUi.night.color.warning
|
|
116
127
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
128
|
+
testID: "buoy-highlight-history-locked-text",
|
|
117
129
|
style: styles.lockedBannerText,
|
|
118
130
|
children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'render' : 'renders', " locked"]
|
|
119
131
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -123,6 +135,9 @@ function RenderHistoryViewer({
|
|
|
123
135
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
124
136
|
style: styles.viewToggleContainer,
|
|
125
137
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
138
|
+
testID: "buoy-highlight-history-mode-current",
|
|
139
|
+
accessibilityRole: "button",
|
|
140
|
+
accessibilityLabel: "Current state",
|
|
126
141
|
style: [styles.viewToggleCard, activeView === "current" && styles.viewToggleCardActive],
|
|
127
142
|
onPress: () => setActiveView("current"),
|
|
128
143
|
activeOpacity: 0.8,
|
|
@@ -142,6 +157,8 @@ function RenderHistoryViewer({
|
|
|
142
157
|
children: ["View render #", currentEvent?.renderNumber ?? 0, " details"]
|
|
143
158
|
})]
|
|
144
159
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
160
|
+
testID: "buoy-highlight-history-mode-diff",
|
|
161
|
+
accessibilityLabel: "Diff view, coming soon",
|
|
145
162
|
style: [styles.viewToggleCard, styles.viewToggleCardDisabled],
|
|
146
163
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
147
164
|
style: styles.viewToggleContent,
|
|
@@ -166,6 +183,7 @@ function RenderHistoryViewer({
|
|
|
166
183
|
})]
|
|
167
184
|
})]
|
|
168
185
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
186
|
+
testID: "buoy-highlight-history-scroll",
|
|
169
187
|
style: styles.contentScroll,
|
|
170
188
|
contentContainerStyle: styles.contentContainer,
|
|
171
189
|
showsVerticalScrollIndicator: false,
|
|
@@ -198,6 +216,7 @@ function CurrentStateView({
|
|
|
198
216
|
}) {
|
|
199
217
|
if (!event) {
|
|
200
218
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
219
|
+
testID: "buoy-highlight-history-no-event",
|
|
201
220
|
style: styles.noEventContainer,
|
|
202
221
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
203
222
|
style: styles.noEventText,
|
|
@@ -213,9 +232,11 @@ function CurrentStateView({
|
|
|
213
232
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
214
233
|
style: styles.eventHeaderLeft,
|
|
215
234
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
235
|
+
testID: "buoy-highlight-history-event-title",
|
|
216
236
|
style: styles.eventTitle,
|
|
217
237
|
children: ["Render #", event.renderNumber]
|
|
218
238
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
239
|
+
testID: "buoy-highlight-history-event-time",
|
|
219
240
|
style: styles.eventTime,
|
|
220
241
|
children: formatTimeWithMs(event.timestamp)
|
|
221
242
|
})]
|
|
@@ -228,6 +249,7 @@ function CurrentStateView({
|
|
|
228
249
|
backgroundColor: render.color
|
|
229
250
|
}]
|
|
230
251
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
252
|
+
testID: "buoy-highlight-history-event-cause",
|
|
231
253
|
style: [styles.eventBadgeText, {
|
|
232
254
|
color: render.color
|
|
233
255
|
}],
|
|
@@ -268,6 +290,7 @@ function CurrentStateView({
|
|
|
268
290
|
showsHorizontalScrollIndicator: false,
|
|
269
291
|
style: styles.propsScrollView,
|
|
270
292
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
293
|
+
testID: "buoy-highlight-history-props",
|
|
271
294
|
style: styles.propsJson,
|
|
272
295
|
children: JSON.stringify(event.capturedProps, null, 2)
|
|
273
296
|
})
|
|
@@ -9,6 +9,7 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _IdentifierBadge = require("./IdentifierBadge");
|
|
11
11
|
var _RenderCauseBadge = require("./RenderCauseBadge");
|
|
12
|
+
var _testIDs = require("../utils/testIDs");
|
|
12
13
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
14
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
14
15
|
/**
|
|
@@ -59,7 +60,15 @@ function RenderListItemInner({
|
|
|
59
60
|
|
|
60
61
|
// Format time since last render (live-updating)
|
|
61
62
|
const timeSinceRender = (0, _sharedUi.useRelativeTime)(render.lastRenderTime);
|
|
63
|
+
|
|
64
|
+
// The row is addressed by the identifier it SHOWS, never by nativeTag — see
|
|
65
|
+
// utils/testIDs.ts for why a tag can't be a handle. The label spells the row
|
|
66
|
+
// out for assistive tech, which otherwise gets a coloured bar and a count.
|
|
67
|
+
const rowTestID = (0, _testIDs.renderRowTestID)(identifier.value);
|
|
62
68
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
69
|
+
testID: rowTestID,
|
|
70
|
+
accessibilityRole: "button",
|
|
71
|
+
accessibilityLabel: `${render.displayName}, ${identifier.type} ${identifier.value}, ${render.renderCount} renders`,
|
|
63
72
|
style: styles.container,
|
|
64
73
|
onPress: () => onPress(render),
|
|
65
74
|
activeOpacity: 0.7,
|
|
@@ -74,6 +83,7 @@ function RenderListItemInner({
|
|
|
74
83
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
75
84
|
style: styles.viewTypeContainer,
|
|
76
85
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
86
|
+
testID: `${rowTestID}-name`,
|
|
77
87
|
style: styles.viewType,
|
|
78
88
|
numberOfLines: 1,
|
|
79
89
|
children: render.displayName
|
|
@@ -83,6 +93,7 @@ function RenderListItemInner({
|
|
|
83
93
|
compact: true,
|
|
84
94
|
shortLabel: true
|
|
85
95
|
}) : render.displayName !== render.viewType ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
96
|
+
testID: `${rowTestID}-type`,
|
|
86
97
|
style: styles.nativeType,
|
|
87
98
|
numberOfLines: 1,
|
|
88
99
|
children: render.viewType
|
|
@@ -92,6 +103,7 @@ function RenderListItemInner({
|
|
|
92
103
|
backgroundColor: render.color + "30"
|
|
93
104
|
}],
|
|
94
105
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
106
|
+
testID: `${rowTestID}-count`,
|
|
95
107
|
style: [styles.renderCount, {
|
|
96
108
|
color: render.color
|
|
97
109
|
}],
|
|
@@ -101,6 +113,7 @@ function RenderListItemInner({
|
|
|
101
113
|
}), render.lastRenderCause && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
102
114
|
style: styles.causeRow,
|
|
103
115
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_RenderCauseBadge.RenderCauseBadge, {
|
|
116
|
+
testID: `${rowTestID}-cause`,
|
|
104
117
|
cause: render.lastRenderCause,
|
|
105
118
|
compact: true,
|
|
106
119
|
showKeys: true,
|
|
@@ -111,12 +124,14 @@ function RenderListItemInner({
|
|
|
111
124
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
112
125
|
style: styles.identifierContainer,
|
|
113
126
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_IdentifierBadge.IdentifierBadge, {
|
|
127
|
+
testID: `${rowTestID}-identifier`,
|
|
114
128
|
type: identifier.type,
|
|
115
129
|
value: identifier.value,
|
|
116
130
|
compact: true,
|
|
117
131
|
shortLabel: true
|
|
118
132
|
})
|
|
119
133
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
134
|
+
testID: `${rowTestID}-time`,
|
|
120
135
|
style: styles.timing,
|
|
121
136
|
children: timeSinceRender
|
|
122
137
|
})]
|
|
@@ -200,6 +200,9 @@ function RendersCopySettingsView({
|
|
|
200
200
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
201
201
|
style: styles.collapsedPreview,
|
|
202
202
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
203
|
+
testID: "buoy-highlight-copy-preview-show",
|
|
204
|
+
accessibilityRole: "button",
|
|
205
|
+
accessibilityLabel: "Show preview",
|
|
203
206
|
style: styles.expandButton,
|
|
204
207
|
onPress: () => setIsPreviewExpanded(true),
|
|
205
208
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -213,6 +216,7 @@ function RendersCopySettingsView({
|
|
|
213
216
|
style: styles.expandButtonTitle,
|
|
214
217
|
children: "Show Preview"
|
|
215
218
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
219
|
+
testID: "buoy-highlight-copy-preview-summary",
|
|
216
220
|
style: styles.expandButtonSubtitle,
|
|
217
221
|
children: [totalComponents, " component", totalComponents !== 1 ? "s" : "", " \xB7 ", totalRenders, " renders \xB7 ~", formatBytes(estimatedSize), !hasLiveData ? " (mock data)" : ""]
|
|
218
222
|
})]
|
|
@@ -222,6 +226,7 @@ function RendersCopySettingsView({
|
|
|
222
226
|
})]
|
|
223
227
|
})
|
|
224
228
|
}), sizeWarningLevel !== "none" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
229
|
+
testID: "buoy-highlight-copy-size-warning",
|
|
225
230
|
style: [styles.sizeWarning, sizeWarningLevel === "danger" && styles.sizeWarningDanger],
|
|
226
231
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
|
|
227
232
|
size: 12,
|
|
@@ -238,6 +243,9 @@ function RendersCopySettingsView({
|
|
|
238
243
|
const exportText = generateCopyText();
|
|
239
244
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
240
245
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
246
|
+
testID: "buoy-highlight-copy-preview-hide",
|
|
247
|
+
accessibilityRole: "button",
|
|
248
|
+
accessibilityLabel: "Hide preview",
|
|
241
249
|
style: styles.collapseButton,
|
|
242
250
|
onPress: () => setIsPreviewExpanded(false),
|
|
243
251
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -248,9 +256,11 @@ function RendersCopySettingsView({
|
|
|
248
256
|
color: _sharedUi.night.color.textSecondary
|
|
249
257
|
})]
|
|
250
258
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
259
|
+
testID: "buoy-highlight-copy-preview-scroll",
|
|
251
260
|
style: styles.previewScroll,
|
|
252
261
|
nestedScrollEnabled: true,
|
|
253
262
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
263
|
+
testID: "buoy-highlight-copy-preview-text",
|
|
254
264
|
style: styles.previewText,
|
|
255
265
|
selectable: true,
|
|
256
266
|
children: exportText
|
|
@@ -265,6 +275,7 @@ function RendersCopySettingsView({
|
|
|
265
275
|
const statusLabel = hasLiveData ? `${totalComponents} components` : "Mock data (no renders captured)";
|
|
266
276
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
267
277
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
278
|
+
testID: "buoy-highlight-copy-status",
|
|
268
279
|
style: styles.statusLabel,
|
|
269
280
|
children: statusLabel
|
|
270
281
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
|
|
@@ -36,11 +36,14 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
36
36
|
}, []);
|
|
37
37
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
38
38
|
nativeID: "__rn_buoy__stats-row",
|
|
39
|
+
testID: "buoy-highlight-stats",
|
|
40
|
+
accessibilityLabel: `${stats.totalComponents} components tracked`,
|
|
39
41
|
style: styles.headerChip,
|
|
40
42
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Activity, {
|
|
41
43
|
size: 12,
|
|
42
44
|
color: _sharedUi.night.color.accent
|
|
43
45
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
46
|
+
testID: "buoy-highlight-stats-value",
|
|
44
47
|
style: [styles.headerChipValue, {
|
|
45
48
|
color: _sharedUi.night.color.accent
|
|
46
49
|
}],
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.CommitProfiler = void 0;
|
|
7
7
|
var _devtoolsComponentNames = require("./devtoolsComponentNames");
|
|
8
|
+
var _ownStateChanged = require("./ownStateChanged");
|
|
8
9
|
/**
|
|
9
10
|
* CommitProfiler — per-component render counts + durations across a capture
|
|
10
11
|
* window, React-DevTools-profiler style, without React DevTools attached.
|
|
@@ -23,6 +24,10 @@ var _devtoolsComponentNames = require("./devtoolsComponentNames");
|
|
|
23
24
|
* registry (see the registration in this package's index.tsx).
|
|
24
25
|
*/
|
|
25
26
|
|
|
27
|
+
// One implementation, shared with the overlay's RenderCauseDetector — the two
|
|
28
|
+
// classifiers are separate on purpose, but this question is not. See the file
|
|
29
|
+
// for why the obvious identity check is wrong.
|
|
30
|
+
|
|
26
31
|
// React work tags for components that render user code.
|
|
27
32
|
const FunctionComponent = 0;
|
|
28
33
|
const ClassComponent = 1;
|
|
@@ -209,7 +214,7 @@ function detectCause(fiber, wantKeys) {
|
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
// No prop change of substance. Own state/hooks next.
|
|
212
|
-
if (prev
|
|
217
|
+
if ((0, _ownStateChanged.ownStateChanged)(prev, fiber)) return {
|
|
213
218
|
cause: "hooks"
|
|
214
219
|
};
|
|
215
220
|
return {
|
|
@@ -1626,6 +1626,16 @@ function disable() {
|
|
|
1626
1626
|
devToolsNodeCache.clear(); // Clear detection cache
|
|
1627
1627
|
(0, _RenderCauseDetector.clearRenderCauseState)(); // Clear render cause state to prevent memory leaks
|
|
1628
1628
|
|
|
1629
|
+
// Freeze is a view state on a LIVE overlay, and the overlay is going away:
|
|
1630
|
+
// the boxes it was holding open were just cleared with the tags above. Left
|
|
1631
|
+
// latched it outlived the thing it froze — switch tracking off and back on
|
|
1632
|
+
// and the button still read "Unfreeze frame" on a tool with nothing frozen,
|
|
1633
|
+
// while new highlights (correctly) refused to fade.
|
|
1634
|
+
if (isFrozen) {
|
|
1635
|
+
isFrozen = false;
|
|
1636
|
+
notifyFreezeListeners();
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1629
1639
|
// Only unsubscribe if background tracking doesn't need it
|
|
1630
1640
|
if (!backgroundTrackingEnabled) {
|
|
1631
1641
|
unsubscribeFromTraceUpdates();
|
|
@@ -36,6 +36,11 @@ exports.detectRenderCause = detectRenderCause;
|
|
|
36
36
|
exports.getRenderCauseStats = getRenderCauseStats;
|
|
37
37
|
exports.removeRenderCauseState = removeRenderCauseState;
|
|
38
38
|
exports.safeCloneForHistory = safeCloneForHistory;
|
|
39
|
+
var _ownStateChanged = require("./ownStateChanged");
|
|
40
|
+
// One implementation of "did this component's own state actually change?",
|
|
41
|
+
// shared with CommitProfiler. The two classifiers are separate on purpose;
|
|
42
|
+
// this question is not, and both of them got it wrong the same way.
|
|
43
|
+
|
|
39
44
|
// ============================================================================
|
|
40
45
|
// TYPE DEFINITIONS
|
|
41
46
|
// Based on React Native source: packages/react-native/Libraries/Renderer/
|
|
@@ -660,9 +665,16 @@ function detectComponentCause(componentFiber) {
|
|
|
660
665
|
|
|
661
666
|
// Check if state changed (for hooks, memoizedState is a linked list)
|
|
662
667
|
// This means useState/useReducer triggered a re-render
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
668
|
+
// NOT `prevMemoizedState !== currentFiber.memoizedState`, which is what this
|
|
669
|
+
// used to be — see utils/ownStateChanged.ts for why that is wrong and what
|
|
670
|
+
// it cost. Shared with `CommitProfiler`, which reached the same wrong line
|
|
671
|
+
// independently; the two classifiers stay separate, this one question does
|
|
672
|
+
// not.
|
|
673
|
+
if ((0, _ownStateChanged.hookStateChanged)(prevMemoizedState, currentFiber.memoizedState, {
|
|
674
|
+
isClassComponent: currentFiber.tag === 1
|
|
675
|
+
})) {
|
|
676
|
+
// Phase 3: the actual before/after values, for the UI. Only needed once
|
|
677
|
+
// the cause is settled, and only the overlay displays them.
|
|
666
678
|
const hookChanges = compareHookStates(prevExtractedHooks, currentHooks);
|
|
667
679
|
return {
|
|
668
680
|
cause: "state",
|
|
@@ -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
|
+
}
|