@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.
Files changed (49) hide show
  1. package/lib/commonjs/components/TimeMachineBody.js +13 -1
  2. package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
  3. package/lib/commonjs/components/list/RestoreOutcomeCard.js +15 -4
  4. package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +1 -1
  5. package/lib/commonjs/components/list/SnapshotRow.js +5 -0
  6. package/lib/commonjs/components/list/SnapshotRow.js.map +1 -1
  7. package/lib/commonjs/components/list/SourcesStrip.js +2 -0
  8. package/lib/commonjs/components/list/SourcesStrip.js.map +1 -1
  9. package/lib/commonjs/components/list/UndoDeleteBar.js +2 -0
  10. package/lib/commonjs/components/list/UndoDeleteBar.js.map +1 -1
  11. package/lib/commonjs/components/preview/ItemDiffPanel.js +3 -1
  12. package/lib/commonjs/components/preview/ItemDiffPanel.js.map +1 -1
  13. package/lib/commonjs/components/preview/RestorePreviewScreen.js +41 -7
  14. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -1
  15. package/lib/commonjs/ui/primitives.js +9 -3
  16. package/lib/commonjs/ui/primitives.js.map +1 -1
  17. package/lib/module/components/TimeMachineBody.js +13 -1
  18. package/lib/module/components/TimeMachineBody.js.map +1 -1
  19. package/lib/module/components/list/RestoreOutcomeCard.js +15 -4
  20. package/lib/module/components/list/RestoreOutcomeCard.js.map +1 -1
  21. package/lib/module/components/list/SnapshotRow.js +5 -0
  22. package/lib/module/components/list/SnapshotRow.js.map +1 -1
  23. package/lib/module/components/list/SourcesStrip.js +2 -0
  24. package/lib/module/components/list/SourcesStrip.js.map +1 -1
  25. package/lib/module/components/list/UndoDeleteBar.js +2 -0
  26. package/lib/module/components/list/UndoDeleteBar.js.map +1 -1
  27. package/lib/module/components/preview/ItemDiffPanel.js +3 -1
  28. package/lib/module/components/preview/ItemDiffPanel.js.map +1 -1
  29. package/lib/module/components/preview/RestorePreviewScreen.js +41 -7
  30. package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -1
  31. package/lib/module/ui/primitives.js +9 -3
  32. package/lib/module/ui/primitives.js.map +1 -1
  33. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +1 -1
  34. package/lib/typescript/components/list/SnapshotRow.d.ts.map +1 -1
  35. package/lib/typescript/components/list/SourcesStrip.d.ts.map +1 -1
  36. package/lib/typescript/components/preview/ItemDiffPanel.d.ts +2 -1
  37. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +1 -1
  38. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -1
  39. package/lib/typescript/ui/primitives.d.ts +6 -3
  40. package/lib/typescript/ui/primitives.d.ts.map +1 -1
  41. package/package.json +7 -7
  42. package/src/components/TimeMachineBody.tsx +13 -4
  43. package/src/components/list/RestoreOutcomeCard.tsx +27 -5
  44. package/src/components/list/SnapshotRow.tsx +13 -4
  45. package/src/components/list/SourcesStrip.tsx +4 -1
  46. package/src/components/list/UndoDeleteBar.tsx +2 -2
  47. package/src/components/preview/ItemDiffPanel.tsx +8 -2
  48. package/src/components/preview/RestorePreviewScreen.tsx +77 -18
  49. 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>Swipe a restore point to rename, copy or delete it.</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 <ResultLine glyph="✓" ok text={`route — ${route.href}`} />;
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 glyph="↻" ok text={`route — ${route.href} after reload`} />
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}>No sources restored.</Text>
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 style={styles.restored} numberOfLines={1}>
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 style={styles.route} numberOfLines={1}>
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}>No state tools installed.</Text>
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({ item }: { item: StateSnapshotPreviewItem }) {
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={`tm-preview-item-${row.sourceId}-${row.item.key}`}
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 style={styles.itemPreview} numberOfLines={1}>
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 style={styles.itemPreview} numberOfLines={1}>
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 style={styles.itemReason} numberOfLines={2}>
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 style={styles.sourceName} numberOfLines={1}>
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 style={styles.sourceSummary} numberOfLines={1}>
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 glyph="−" count={counts.removed} color={tm.color.danger} />
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 glyph="+" count={counts.added} color={tm.color.accent} />
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 glyph="≈" count={counts.changed} color={tm.color.info} />
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}>{row.text}</Text>
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 style={styles.routeHref} numberOfLines={1}>
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 item={row.item} />
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
@@ -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({ children }: { children: string }) {
24
- return <Text style={styles.sectionLabel}>{children}</Text>;
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 }]}>{label}</Text>
120
+ <Text style={[styles.pillText, { color }]} testID={testID}>
121
+ {label}
122
+ </Text>
106
123
  </View>
107
124
  );
108
125
  }