@buoy-gg/time-machine 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/components/TimeMachineBody.js +13 -1
- package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
- package/lib/commonjs/components/list/RestoreOutcomeCard.js +15 -4
- package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +1 -1
- package/lib/commonjs/components/list/SnapshotRow.js +5 -0
- package/lib/commonjs/components/list/SnapshotRow.js.map +1 -1
- package/lib/commonjs/components/list/SourcesStrip.js +2 -0
- package/lib/commonjs/components/list/SourcesStrip.js.map +1 -1
- package/lib/commonjs/components/list/UndoDeleteBar.js +2 -0
- package/lib/commonjs/components/list/UndoDeleteBar.js.map +1 -1
- package/lib/commonjs/components/preview/ItemDiffPanel.js +3 -1
- package/lib/commonjs/components/preview/ItemDiffPanel.js.map +1 -1
- package/lib/commonjs/components/preview/RestorePreviewScreen.js +41 -7
- package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -1
- package/lib/commonjs/ui/primitives.js +9 -3
- package/lib/commonjs/ui/primitives.js.map +1 -1
- package/lib/module/components/TimeMachineBody.js +13 -1
- package/lib/module/components/TimeMachineBody.js.map +1 -1
- package/lib/module/components/list/RestoreOutcomeCard.js +15 -4
- package/lib/module/components/list/RestoreOutcomeCard.js.map +1 -1
- package/lib/module/components/list/SnapshotRow.js +5 -0
- package/lib/module/components/list/SnapshotRow.js.map +1 -1
- package/lib/module/components/list/SourcesStrip.js +2 -0
- package/lib/module/components/list/SourcesStrip.js.map +1 -1
- package/lib/module/components/list/UndoDeleteBar.js +2 -0
- package/lib/module/components/list/UndoDeleteBar.js.map +1 -1
- package/lib/module/components/preview/ItemDiffPanel.js +3 -1
- package/lib/module/components/preview/ItemDiffPanel.js.map +1 -1
- package/lib/module/components/preview/RestorePreviewScreen.js +41 -7
- package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -1
- package/lib/module/ui/primitives.js +9 -3
- package/lib/module/ui/primitives.js.map +1 -1
- package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +1 -1
- package/lib/typescript/components/list/SnapshotRow.d.ts.map +1 -1
- package/lib/typescript/components/list/SourcesStrip.d.ts.map +1 -1
- package/lib/typescript/components/preview/ItemDiffPanel.d.ts +2 -1
- package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +1 -1
- package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/primitives.d.ts +6 -3
- package/lib/typescript/ui/primitives.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/components/TimeMachineBody.tsx +13 -4
- package/src/components/list/RestoreOutcomeCard.tsx +27 -5
- package/src/components/list/SnapshotRow.tsx +13 -4
- package/src/components/list/SourcesStrip.tsx +4 -1
- package/src/components/list/UndoDeleteBar.tsx +2 -2
- package/src/components/preview/ItemDiffPanel.tsx +8 -2
- package/src/components/preview/RestorePreviewScreen.tsx +77 -18
- package/src/ui/primitives.tsx +20 -3
|
@@ -126,6 +126,13 @@ function RestorePointList({
|
|
|
126
126
|
<ScrollView
|
|
127
127
|
style={styles.fill}
|
|
128
128
|
contentContainerStyle={[styles.content, { paddingBottom: insets.bottom }]}
|
|
129
|
+
testID="time-machine-scroll"
|
|
130
|
+
// Without this, the first tap after typing a name only DISMISSES the
|
|
131
|
+
// keyboard — Capture never fires and the restore point is silently not
|
|
132
|
+
// taken. Every other Buoy screen with a field inside a scroller sets it;
|
|
133
|
+
// this one didn't, and only the XCTest lane could see it (Detox's
|
|
134
|
+
// replaceText never opens a keyboard, so the tap always landed).
|
|
135
|
+
keyboardShouldPersistTaps="handled"
|
|
129
136
|
>
|
|
130
137
|
{/* Only when the restore did something the app itself won't show you. */}
|
|
131
138
|
{lastOutcome && restoreNeedsAttention(lastOutcome) ? (
|
|
@@ -149,7 +156,7 @@ function RestorePointList({
|
|
|
149
156
|
/>
|
|
150
157
|
</View>
|
|
151
158
|
{atCaptureLimit ? (
|
|
152
|
-
<Footnote tone="warning">
|
|
159
|
+
<Footnote tone="warning" testID="time-machine-limit">
|
|
153
160
|
Free plan keeps {snapshotLimit} restore points. Upgrade to Pro
|
|
154
161
|
for unlimited snapshots.
|
|
155
162
|
</Footnote>
|
|
@@ -162,7 +169,7 @@ function RestorePointList({
|
|
|
162
169
|
{sources
|
|
163
170
|
.filter((s) => s.detail && !(s.canCapture && s.canRestore))
|
|
164
171
|
.map((s) => (
|
|
165
|
-
<Footnote key={s.id}>
|
|
172
|
+
<Footnote key={s.id} testID={`time-machine-source-${s.id}-detail`}>
|
|
166
173
|
{s.label}: {s.detail}
|
|
167
174
|
</Footnote>
|
|
168
175
|
))}
|
|
@@ -180,7 +187,7 @@ function RestorePointList({
|
|
|
180
187
|
/>
|
|
181
188
|
))}
|
|
182
189
|
{visible.length === 0 ? (
|
|
183
|
-
<Text style={styles.empty}>
|
|
190
|
+
<Text style={styles.empty} testID="time-machine-empty">
|
|
184
191
|
{!hasLoaded
|
|
185
192
|
? "Loading restore points…"
|
|
186
193
|
: snapshots.length > 0
|
|
@@ -190,7 +197,9 @@ function RestorePointList({
|
|
|
190
197
|
) : null}
|
|
191
198
|
</Group>
|
|
192
199
|
{visible.length > 0 ? (
|
|
193
|
-
<Footnote
|
|
200
|
+
<Footnote testID="time-machine-swipe-hint">
|
|
201
|
+
Swipe a restore point to copy or delete it.
|
|
202
|
+
</Footnote>
|
|
194
203
|
) : null}
|
|
195
204
|
|
|
196
205
|
<View style={styles.spacer} />
|
|
@@ -32,15 +32,18 @@ function ResultLine({
|
|
|
32
32
|
glyph,
|
|
33
33
|
text,
|
|
34
34
|
ok,
|
|
35
|
+
testID,
|
|
35
36
|
}: {
|
|
36
37
|
glyph: string;
|
|
37
38
|
text: string;
|
|
38
39
|
ok: boolean;
|
|
40
|
+
testID?: string;
|
|
39
41
|
}) {
|
|
40
42
|
return (
|
|
41
43
|
<Text
|
|
42
44
|
style={[styles.line, { color: ok ? tm.color.accent : tm.color.warning }]}
|
|
43
45
|
numberOfLines={2}
|
|
46
|
+
testID={testID}
|
|
44
47
|
>
|
|
45
48
|
<Text style={styles.glyph}>{glyph} </Text>
|
|
46
49
|
{text}
|
|
@@ -48,18 +51,33 @@ function ResultLine({
|
|
|
48
51
|
);
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
const ROUTE_TEST_ID = "time-machine-outcome-route";
|
|
55
|
+
|
|
51
56
|
function RouteResult({ route }: { route: NonNullable<RestoreOutcome["route"]> }) {
|
|
52
57
|
if (route.status === "navigated")
|
|
53
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
<ResultLine
|
|
60
|
+
glyph="✓"
|
|
61
|
+
ok
|
|
62
|
+
text={`route — ${route.href}`}
|
|
63
|
+
testID={ROUTE_TEST_ID}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
54
66
|
if (route.status === "deferred")
|
|
55
67
|
return (
|
|
56
|
-
<ResultLine
|
|
68
|
+
<ResultLine
|
|
69
|
+
glyph="↻"
|
|
70
|
+
ok
|
|
71
|
+
text={`route — ${route.href} after reload`}
|
|
72
|
+
testID={ROUTE_TEST_ID}
|
|
73
|
+
/>
|
|
57
74
|
);
|
|
58
75
|
return (
|
|
59
76
|
<ResultLine
|
|
60
77
|
glyph="✕"
|
|
61
78
|
ok={false}
|
|
62
79
|
text={`route — ${route.error ?? route.status}`}
|
|
80
|
+
testID={ROUTE_TEST_ID}
|
|
63
81
|
/>
|
|
64
82
|
);
|
|
65
83
|
}
|
|
@@ -69,9 +87,9 @@ export function RestoreOutcomeCard({ outcome }: { outcome: RestoreOutcome }) {
|
|
|
69
87
|
const reloading = outcome.mode === "reload" || outcome.willReload;
|
|
70
88
|
|
|
71
89
|
return (
|
|
72
|
-
<View style={styles.card}>
|
|
90
|
+
<View style={styles.card} testID="time-machine-outcome">
|
|
73
91
|
<View style={styles.headerRow}>
|
|
74
|
-
<Text style={styles.title} numberOfLines={1}>
|
|
92
|
+
<Text style={styles.title} numberOfLines={1} testID="time-machine-outcome-title">
|
|
75
93
|
Restored “{outcome.snapshotName}”{reloading ? " · reloading" : ""}
|
|
76
94
|
</Text>
|
|
77
95
|
<TouchableOpacity
|
|
@@ -79,13 +97,16 @@ export function RestoreOutcomeCard({ outcome }: { outcome: RestoreOutcome }) {
|
|
|
79
97
|
hitSlop={HIT}
|
|
80
98
|
accessibilityRole="button"
|
|
81
99
|
accessibilityLabel="Dismiss restore result"
|
|
100
|
+
testID="time-machine-outcome-dismiss"
|
|
82
101
|
>
|
|
83
102
|
<X size={13} color={tm.color.labelSecondary} />
|
|
84
103
|
</TouchableOpacity>
|
|
85
104
|
</View>
|
|
86
105
|
|
|
87
106
|
{entries.length === 0 && !outcome.route ? (
|
|
88
|
-
<Text style={styles.empty}
|
|
107
|
+
<Text style={styles.empty} testID="time-machine-outcome-empty">
|
|
108
|
+
No sources restored.
|
|
109
|
+
</Text>
|
|
89
110
|
) : null}
|
|
90
111
|
|
|
91
112
|
{outcome.route ? <RouteResult route={outcome.route} /> : null}
|
|
@@ -93,6 +114,7 @@ export function RestoreOutcomeCard({ outcome }: { outcome: RestoreOutcome }) {
|
|
|
93
114
|
{entries.map(([sourceId, result]) => (
|
|
94
115
|
<View key={sourceId}>
|
|
95
116
|
<ResultLine
|
|
117
|
+
testID={`time-machine-outcome-${sourceId}`}
|
|
96
118
|
glyph={result.ok ? "✓" : "✕"}
|
|
97
119
|
ok={result.ok}
|
|
98
120
|
text={`${sourceId} — applied ${result.applied}${
|
|
@@ -78,19 +78,27 @@ export function SnapshotRow({
|
|
|
78
78
|
testID={testID}
|
|
79
79
|
>
|
|
80
80
|
<View style={styles.text}>
|
|
81
|
-
<Text style={styles.name} numberOfLines={1}>
|
|
81
|
+
<Text style={styles.name} numberOfLines={1} testID={`${testID}-name`}>
|
|
82
82
|
{meta.name}
|
|
83
83
|
</Text>
|
|
84
|
-
<Text style={styles.meta} numberOfLines={1}>
|
|
84
|
+
<Text style={styles.meta} numberOfLines={1} testID={`${testID}-meta`}>
|
|
85
85
|
{describe(meta)}
|
|
86
86
|
</Text>
|
|
87
87
|
{meta.lastRestoredAt ? (
|
|
88
|
-
<Text
|
|
88
|
+
<Text
|
|
89
|
+
style={styles.restored}
|
|
90
|
+
numberOfLines={1}
|
|
91
|
+
testID={`${testID}-restored`}
|
|
92
|
+
>
|
|
89
93
|
Restored {timeAgo(meta.lastRestoredAt)}
|
|
90
94
|
</Text>
|
|
91
95
|
) : null}
|
|
92
96
|
{meta.route ? (
|
|
93
|
-
<Text
|
|
97
|
+
<Text
|
|
98
|
+
style={styles.route}
|
|
99
|
+
numberOfLines={1}
|
|
100
|
+
testID={`${testID}-route`}
|
|
101
|
+
>
|
|
94
102
|
{meta.route.href}
|
|
95
103
|
</Text>
|
|
96
104
|
) : null}
|
|
@@ -104,6 +112,7 @@ export function SnapshotRow({
|
|
|
104
112
|
testID={`${testID}-restore`}
|
|
105
113
|
>
|
|
106
114
|
<PillLabel
|
|
115
|
+
testID={`${testID}-restore-label`}
|
|
107
116
|
label={meta.baseline ? "Wipe" : "Restore"}
|
|
108
117
|
color={meta.baseline ? tm.color.danger : tm.color.accent}
|
|
109
118
|
background={meta.baseline ? tm.color.dangerSoft : tm.color.accentSoft}
|
|
@@ -39,6 +39,7 @@ function SourceTile({ source }: { source: SourceStatus }) {
|
|
|
39
39
|
<Text
|
|
40
40
|
style={[styles.label, !source.canCapture ? styles.labelOff : null]}
|
|
41
41
|
numberOfLines={1}
|
|
42
|
+
testID={`time-machine-source-${source.id}-label`}
|
|
42
43
|
>
|
|
43
44
|
{source.label}
|
|
44
45
|
</Text>
|
|
@@ -59,7 +60,9 @@ export function SourcesStrip({ sources }: { sources: SourceStatus[] }) {
|
|
|
59
60
|
<SourceTile key={source.id} source={source} />
|
|
60
61
|
))}
|
|
61
62
|
{sources.length === 0 ? (
|
|
62
|
-
<Text style={styles.empty}
|
|
63
|
+
<Text style={styles.empty} testID="time-machine-sources-empty">
|
|
64
|
+
No state tools installed.
|
|
65
|
+
</Text>
|
|
63
66
|
) : null}
|
|
64
67
|
</ScrollView>
|
|
65
68
|
</View>
|
|
@@ -33,8 +33,8 @@ export function UndoDeleteBar({
|
|
|
33
33
|
}, [at]);
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
|
-
<View style={styles.bar}>
|
|
37
|
-
<Text style={styles.text} numberOfLines={1}>
|
|
36
|
+
<View style={styles.bar} testID="time-machine-undo-bar">
|
|
37
|
+
<Text style={styles.text} numberOfLines={1} testID="time-machine-undo-text">
|
|
38
38
|
Deleted “{name}”
|
|
39
39
|
</Text>
|
|
40
40
|
<TouchableOpacity
|
|
@@ -30,7 +30,13 @@ import { tm } from "../../ui/tmTheme";
|
|
|
30
30
|
|
|
31
31
|
const EMPTY: unknown[] = [];
|
|
32
32
|
|
|
33
|
-
export function ItemDiffPanel({
|
|
33
|
+
export function ItemDiffPanel({
|
|
34
|
+
item,
|
|
35
|
+
testID,
|
|
36
|
+
}: {
|
|
37
|
+
item: StateSnapshotPreviewItem;
|
|
38
|
+
testID?: string;
|
|
39
|
+
}) {
|
|
34
40
|
// ABSENT_VALUE keeps added/removed reading as pure additions/removals
|
|
35
41
|
// instead of diffs against the literal "null".
|
|
36
42
|
const { oldValue, newValue } = useMemo(
|
|
@@ -44,7 +50,7 @@ export function ItemDiffPanel({ item }: { item: StateSnapshotPreviewItem }) {
|
|
|
44
50
|
);
|
|
45
51
|
|
|
46
52
|
return (
|
|
47
|
-
<View style={styles.panel}>
|
|
53
|
+
<View style={styles.panel} testID={testID}>
|
|
48
54
|
{item.reason ? <Text style={styles.reason}>⚠ {item.reason}</Text> : null}
|
|
49
55
|
<ThemedSplitView
|
|
50
56
|
oldValue={oldValue}
|
|
@@ -171,14 +171,16 @@ function CountPill({
|
|
|
171
171
|
glyph,
|
|
172
172
|
count,
|
|
173
173
|
color,
|
|
174
|
+
testID,
|
|
174
175
|
}: {
|
|
175
176
|
glyph: string;
|
|
176
177
|
count: number;
|
|
177
178
|
color: string;
|
|
179
|
+
testID?: string;
|
|
178
180
|
}) {
|
|
179
181
|
return (
|
|
180
182
|
<View style={[styles.countPill, { backgroundColor: `${color}26` }]}>
|
|
181
|
-
<Text style={[styles.countPillText, { color }]}>
|
|
183
|
+
<Text style={[styles.countPillText, { color }]} testID={testID}>
|
|
182
184
|
{glyph}
|
|
183
185
|
{count}
|
|
184
186
|
</Text>
|
|
@@ -215,6 +217,10 @@ const ItemRow = memo(function ItemRow({
|
|
|
215
217
|
row.item.verdict === "changed" ||
|
|
216
218
|
row.item.verdict === "added" ||
|
|
217
219
|
row.item.verdict === "removed";
|
|
220
|
+
// One stem for the row and every part of it, so a test that found the row
|
|
221
|
+
// can read its label, its before/after and its exclusion note without
|
|
222
|
+
// rebuilding the compound key three times.
|
|
223
|
+
const id = `tm-preview-item-${row.sourceId}-${row.item.key}`;
|
|
218
224
|
return (
|
|
219
225
|
<CardRow position={row}>
|
|
220
226
|
<View style={[styles.itemRow, dimmed && styles.dimmed]}>
|
|
@@ -223,6 +229,9 @@ const ItemRow = memo(function ItemRow({
|
|
|
223
229
|
disabled={!meta.applies}
|
|
224
230
|
hitSlop={{ top: 10, bottom: 10, left: 8, right: 8 }}
|
|
225
231
|
testID={`tm-preview-toggle-${row.sourceId}-${row.item.key}`}
|
|
232
|
+
accessibilityRole="checkbox"
|
|
233
|
+
accessibilityState={{ checked: !(!meta.applies || row.excluded) }}
|
|
234
|
+
accessibilityLabel={`Include ${row.item.label}`}
|
|
226
235
|
>
|
|
227
236
|
<Checkbox state={!meta.applies || row.excluded ? "none" : "all"} />
|
|
228
237
|
</TouchableOpacity>
|
|
@@ -230,23 +239,31 @@ const ItemRow = memo(function ItemRow({
|
|
|
230
239
|
style={styles.itemBodyTouch}
|
|
231
240
|
onPress={() => openable && onOpen()}
|
|
232
241
|
disabled={!openable}
|
|
233
|
-
testID={
|
|
242
|
+
testID={id}
|
|
234
243
|
>
|
|
235
244
|
<Text style={[styles.itemGlyph, { color: meta.color }]}>
|
|
236
245
|
{meta.glyph}
|
|
237
246
|
</Text>
|
|
238
247
|
<View style={styles.itemBody}>
|
|
239
|
-
<Text style={styles.itemLabel} numberOfLines={1}>
|
|
248
|
+
<Text style={styles.itemLabel} numberOfLines={1} testID={`${id}-label`}>
|
|
240
249
|
{row.item.label}
|
|
241
250
|
</Text>
|
|
242
251
|
{row.item.verdict === "changed" && (
|
|
243
|
-
<Text
|
|
252
|
+
<Text
|
|
253
|
+
style={styles.itemPreview}
|
|
254
|
+
numberOfLines={1}
|
|
255
|
+
testID={`${id}-values`}
|
|
256
|
+
>
|
|
244
257
|
{shortPreview(row.item.oldValue)} →{" "}
|
|
245
258
|
{shortPreview(row.item.newValue)}
|
|
246
259
|
</Text>
|
|
247
260
|
)}
|
|
248
261
|
{row.item.verdict === "added" && (
|
|
249
|
-
<Text
|
|
262
|
+
<Text
|
|
263
|
+
style={styles.itemPreview}
|
|
264
|
+
numberOfLines={1}
|
|
265
|
+
testID={`${id}-values`}
|
|
266
|
+
>
|
|
250
267
|
{shortPreview(row.item.newValue)}
|
|
251
268
|
</Text>
|
|
252
269
|
)}
|
|
@@ -254,17 +271,22 @@ const ItemRow = memo(function ItemRow({
|
|
|
254
271
|
<Text
|
|
255
272
|
style={[styles.itemPreview, styles.itemPreviewRemoved]}
|
|
256
273
|
numberOfLines={1}
|
|
274
|
+
testID={`${id}-values`}
|
|
257
275
|
>
|
|
258
276
|
{shortPreview(row.item.oldValue)}
|
|
259
277
|
</Text>
|
|
260
278
|
)}
|
|
261
279
|
{row.excluded ? (
|
|
262
|
-
<Text style={styles.itemExcluded}>
|
|
280
|
+
<Text style={styles.itemExcluded} testID={`${id}-excluded`}>
|
|
263
281
|
excluded — restore leaves this untouched
|
|
264
282
|
</Text>
|
|
265
283
|
) : (
|
|
266
284
|
row.item.reason && (
|
|
267
|
-
<Text
|
|
285
|
+
<Text
|
|
286
|
+
style={styles.itemReason}
|
|
287
|
+
numberOfLines={2}
|
|
288
|
+
testID={`${id}-reason`}
|
|
289
|
+
>
|
|
268
290
|
{row.item.reason}
|
|
269
291
|
</Text>
|
|
270
292
|
)
|
|
@@ -649,10 +671,18 @@ export function RestorePreviewScreen({
|
|
|
649
671
|
testID={`tm-preview-source-${src.id}`}
|
|
650
672
|
>
|
|
651
673
|
<View style={styles.sourceNameBlock}>
|
|
652
|
-
<Text
|
|
674
|
+
<Text
|
|
675
|
+
style={styles.sourceName}
|
|
676
|
+
numberOfLines={1}
|
|
677
|
+
testID={`tm-preview-source-${src.id}-name`}
|
|
678
|
+
>
|
|
653
679
|
{src.label}
|
|
654
680
|
</Text>
|
|
655
|
-
<Text
|
|
681
|
+
<Text
|
|
682
|
+
style={styles.sourceSummary}
|
|
683
|
+
numberOfLines={1}
|
|
684
|
+
testID={`tm-preview-source-${src.id}-summary`}
|
|
685
|
+
>
|
|
656
686
|
{row.includeState === "none" && row.applicableCount > 0
|
|
657
687
|
? `${row.summary} · excluded`
|
|
658
688
|
: row.summary}
|
|
@@ -660,13 +690,28 @@ export function RestorePreviewScreen({
|
|
|
660
690
|
</View>
|
|
661
691
|
<View style={styles.sourceBadges}>
|
|
662
692
|
{counts.removed > 0 ? (
|
|
663
|
-
<CountPill
|
|
693
|
+
<CountPill
|
|
694
|
+
glyph="−"
|
|
695
|
+
count={counts.removed}
|
|
696
|
+
color={tm.color.danger}
|
|
697
|
+
testID={`tm-preview-source-${src.id}-removed`}
|
|
698
|
+
/>
|
|
664
699
|
) : null}
|
|
665
700
|
{counts.added > 0 ? (
|
|
666
|
-
<CountPill
|
|
701
|
+
<CountPill
|
|
702
|
+
glyph="+"
|
|
703
|
+
count={counts.added}
|
|
704
|
+
color={tm.color.accent}
|
|
705
|
+
testID={`tm-preview-source-${src.id}-added`}
|
|
706
|
+
/>
|
|
667
707
|
) : null}
|
|
668
708
|
{counts.changed > 0 ? (
|
|
669
|
-
<CountPill
|
|
709
|
+
<CountPill
|
|
710
|
+
glyph="≈"
|
|
711
|
+
count={counts.changed}
|
|
712
|
+
color={tm.color.info}
|
|
713
|
+
testID={`tm-preview-source-${src.id}-changed`}
|
|
714
|
+
/>
|
|
670
715
|
) : null}
|
|
671
716
|
</View>
|
|
672
717
|
{row.expandable ? (
|
|
@@ -684,7 +729,9 @@ export function RestorePreviewScreen({
|
|
|
684
729
|
if (row.kind === "note") {
|
|
685
730
|
return (
|
|
686
731
|
<CardRow position={row}>
|
|
687
|
-
<Text style={styles.noteRow}
|
|
732
|
+
<Text style={styles.noteRow} testID={`tm-preview-note-${row.id}`}>
|
|
733
|
+
{row.text}
|
|
734
|
+
</Text>
|
|
688
735
|
</CardRow>
|
|
689
736
|
);
|
|
690
737
|
}
|
|
@@ -702,7 +749,11 @@ export function RestorePreviewScreen({
|
|
|
702
749
|
<Checkbox state={row.on ? "all" : "none"} />
|
|
703
750
|
<View style={styles.routeText}>
|
|
704
751
|
<Text style={styles.routeTitle}>Return to this screen</Text>
|
|
705
|
-
<Text
|
|
752
|
+
<Text
|
|
753
|
+
style={styles.routeHref}
|
|
754
|
+
numberOfLines={1}
|
|
755
|
+
testID="tm-preview-route-href"
|
|
756
|
+
>
|
|
706
757
|
{row.href}
|
|
707
758
|
</Text>
|
|
708
759
|
</View>
|
|
@@ -713,7 +764,10 @@ export function RestorePreviewScreen({
|
|
|
713
764
|
if (row.kind === "diff") {
|
|
714
765
|
return (
|
|
715
766
|
<CardRow position={row}>
|
|
716
|
-
<ItemDiffPanel
|
|
767
|
+
<ItemDiffPanel
|
|
768
|
+
item={row.item}
|
|
769
|
+
testID={`tm-preview-diff-${row.sourceId}-${row.item.key}`}
|
|
770
|
+
/>
|
|
717
771
|
</CardRow>
|
|
718
772
|
);
|
|
719
773
|
}
|
|
@@ -773,6 +827,7 @@ export function RestorePreviewScreen({
|
|
|
773
827
|
return (
|
|
774
828
|
<View style={styles.container}>
|
|
775
829
|
<FlatList
|
|
830
|
+
testID="tm-preview-list"
|
|
776
831
|
data={rows}
|
|
777
832
|
renderItem={renderRow}
|
|
778
833
|
keyExtractor={keyExtractor}
|
|
@@ -785,7 +840,7 @@ export function RestorePreviewScreen({
|
|
|
785
840
|
color={tm.color.accent}
|
|
786
841
|
/>
|
|
787
842
|
) : (
|
|
788
|
-
<Text style={styles.emptyText}>
|
|
843
|
+
<Text style={styles.emptyText} testID="tm-preview-empty">
|
|
789
844
|
{search
|
|
790
845
|
? "Nothing matches this filter."
|
|
791
846
|
: "No changes — restoring would change nothing."}
|
|
@@ -797,7 +852,7 @@ export function RestorePreviewScreen({
|
|
|
797
852
|
{totals.wontApply > 0 ? (
|
|
798
853
|
<View style={styles.warnNote}>
|
|
799
854
|
<AlertTriangle size={12} color={tm.color.warning} />
|
|
800
|
-
<Text style={styles.warnNoteText}>
|
|
855
|
+
<Text style={styles.warnNoteText} testID="tm-preview-wont-apply">
|
|
801
856
|
{totals.wontApply} item{totals.wontApply === 1 ? "" : "s"}{" "}
|
|
802
857
|
won't apply — expand{" "}
|
|
803
858
|
{blockedSources.length > 0
|
|
@@ -809,6 +864,10 @@ export function RestorePreviewScreen({
|
|
|
809
864
|
) : null}
|
|
810
865
|
</View>
|
|
811
866
|
}
|
|
867
|
+
// The name row is a TextInput inside this list: without "handled",
|
|
868
|
+
// the first tap after renaming only dismisses the keyboard, so the
|
|
869
|
+
// Restore CTA and every checkbox silently swallow one press.
|
|
870
|
+
keyboardShouldPersistTaps="handled"
|
|
812
871
|
removeClippedSubviews
|
|
813
872
|
initialNumToRender={20}
|
|
814
873
|
windowSize={7}
|
|
@@ -837,7 +896,7 @@ export function RestorePreviewScreen({
|
|
|
837
896
|
{isBaseline ? "Wipe & Reload" : "Restore State"}
|
|
838
897
|
</Text>
|
|
839
898
|
</TouchableOpacity>
|
|
840
|
-
<Text style={styles.ctaCaption}>
|
|
899
|
+
<Text style={styles.ctaCaption} testID="tm-preview-cta-caption">
|
|
841
900
|
{isBaseline
|
|
842
901
|
? "This wipes all app storage and reloads the app."
|
|
843
902
|
: totals.excludedCount > 0
|
package/src/ui/primitives.tsx
CHANGED
|
@@ -20,8 +20,18 @@ import { ChevronRight } from "@buoy-gg/shared-ui";
|
|
|
20
20
|
import { tm } from "./tmTheme";
|
|
21
21
|
|
|
22
22
|
/** Uppercase section label that sits above a Group. */
|
|
23
|
-
export function SectionLabel({
|
|
24
|
-
|
|
23
|
+
export function SectionLabel({
|
|
24
|
+
children,
|
|
25
|
+
testID,
|
|
26
|
+
}: {
|
|
27
|
+
children: string;
|
|
28
|
+
testID?: string;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<Text style={styles.sectionLabel} testID={testID}>
|
|
32
|
+
{children}
|
|
33
|
+
</Text>
|
|
34
|
+
);
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
/**
|
|
@@ -32,10 +42,12 @@ export function Footnote({
|
|
|
32
42
|
children,
|
|
33
43
|
centered,
|
|
34
44
|
tone = "secondary",
|
|
45
|
+
testID,
|
|
35
46
|
}: {
|
|
36
47
|
children: ReactNode;
|
|
37
48
|
centered?: boolean;
|
|
38
49
|
tone?: "secondary" | "warning";
|
|
50
|
+
testID?: string;
|
|
39
51
|
}) {
|
|
40
52
|
return (
|
|
41
53
|
<Text
|
|
@@ -44,6 +56,7 @@ export function Footnote({
|
|
|
44
56
|
centered ? styles.footnoteCentered : null,
|
|
45
57
|
tone === "warning" ? styles.footnoteWarning : null,
|
|
46
58
|
]}
|
|
59
|
+
testID={testID}
|
|
47
60
|
>
|
|
48
61
|
{children}
|
|
49
62
|
</Text>
|
|
@@ -95,14 +108,18 @@ export function PillLabel({
|
|
|
95
108
|
label,
|
|
96
109
|
color,
|
|
97
110
|
background,
|
|
111
|
+
testID,
|
|
98
112
|
}: {
|
|
99
113
|
label: string;
|
|
100
114
|
color: string;
|
|
101
115
|
background: string;
|
|
116
|
+
testID?: string;
|
|
102
117
|
}) {
|
|
103
118
|
return (
|
|
104
119
|
<View style={[styles.pill, { backgroundColor: background }]}>
|
|
105
|
-
<Text style={[styles.pillText, { color }]}
|
|
120
|
+
<Text style={[styles.pillText, { color }]} testID={testID}>
|
|
121
|
+
{label}
|
|
122
|
+
</Text>
|
|
106
123
|
</View>
|
|
107
124
|
);
|
|
108
125
|
}
|