@terreno/ui 0.14.0 → 0.14.2

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 (98) hide show
  1. package/dist/ActionSheet.d.ts +1 -1
  2. package/dist/ActionSheet.js +17 -29
  3. package/dist/ActionSheet.js.map +1 -1
  4. package/dist/Common.d.ts +8 -2
  5. package/dist/Common.js +4 -4
  6. package/dist/Common.js.map +1 -1
  7. package/dist/ConsentFormScreen.js +3 -3
  8. package/dist/ConsentFormScreen.js.map +1 -1
  9. package/dist/DateUtilities.d.ts +25 -25
  10. package/dist/DateUtilities.js +31 -32
  11. package/dist/DateUtilities.js.map +1 -1
  12. package/dist/MarkdownView.js +20 -7
  13. package/dist/MarkdownView.js.map +1 -1
  14. package/dist/MediaQuery.d.ts +4 -4
  15. package/dist/MediaQuery.js +8 -8
  16. package/dist/MediaQuery.js.map +1 -1
  17. package/dist/Page.d.ts +1 -0
  18. package/dist/Page.js +6 -2
  19. package/dist/Page.js.map +1 -1
  20. package/dist/PickerSelect.d.ts +1 -1
  21. package/dist/PickerSelect.js +2 -2
  22. package/dist/PickerSelect.js.map +1 -1
  23. package/dist/TapToEdit.d.ts +1 -1
  24. package/dist/TapToEdit.js +2 -3
  25. package/dist/TapToEdit.js.map +1 -1
  26. package/dist/ToastNotifications.js +2 -2
  27. package/dist/ToastNotifications.js.map +1 -1
  28. package/dist/Tooltip.d.ts +24 -1
  29. package/dist/Tooltip.js +2 -2
  30. package/dist/Tooltip.js.map +1 -1
  31. package/dist/Unifier.d.ts +1 -1
  32. package/dist/Unifier.js +14 -11
  33. package/dist/Unifier.js.map +1 -1
  34. package/dist/Utilities.d.ts +8 -8
  35. package/dist/Utilities.js +12 -14
  36. package/dist/Utilities.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/signUp/PasswordRequirements.js +3 -3
  41. package/dist/signUp/PasswordRequirements.js.map +1 -1
  42. package/dist/table/TableHeaderCell.js +1 -9
  43. package/dist/table/TableHeaderCell.js.map +1 -1
  44. package/dist/table/tableContext.d.ts +1 -1
  45. package/dist/table/tableContext.js +2 -2
  46. package/dist/table/tableContext.js.map +1 -1
  47. package/dist/useConsentHistory.d.ts +6 -1
  48. package/dist/useConsentHistory.js +2 -1
  49. package/dist/useConsentHistory.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/ActionSheet.test.tsx +554 -0
  52. package/src/ActionSheet.tsx +26 -39
  53. package/src/Banner.test.tsx +107 -1
  54. package/src/Common.ts +10 -4
  55. package/src/ConsentFormScreen.test.tsx +22 -0
  56. package/src/ConsentFormScreen.tsx +9 -3
  57. package/src/DataTable.test.tsx +393 -1
  58. package/src/DateTimeField.test.tsx +716 -2
  59. package/src/DateUtilities.tsx +37 -38
  60. package/src/HeightActionSheet.test.tsx +17 -1
  61. package/src/HeightField.test.tsx +141 -1
  62. package/src/HeightFieldDesktop.test.tsx +19 -0
  63. package/src/MarkdownView.test.tsx +28 -0
  64. package/src/MarkdownView.tsx +69 -7
  65. package/src/MediaQuery.ts +8 -8
  66. package/src/MobileAddressAutoComplete.test.tsx +26 -3
  67. package/src/Page.test.tsx +28 -0
  68. package/src/Page.tsx +17 -2
  69. package/src/PickerSelect.test.tsx +243 -0
  70. package/src/PickerSelect.tsx +3 -3
  71. package/src/SplitPage.test.tsx +299 -43
  72. package/src/TapToEdit.test.tsx +44 -0
  73. package/src/TapToEdit.tsx +2 -3
  74. package/src/ToastNotifications.test.tsx +1412 -0
  75. package/src/ToastNotifications.tsx +2 -2
  76. package/src/Tooltip.test.tsx +1294 -3
  77. package/src/Tooltip.tsx +2 -2
  78. package/src/Unifier.ts +14 -11
  79. package/src/Utilities.tsx +14 -16
  80. package/src/WebAddressAutocomplete.test.tsx +237 -0
  81. package/src/WebDropdownMenu.test.tsx +51 -2
  82. package/src/__snapshots__/Banner.test.tsx.snap +125 -0
  83. package/src/__snapshots__/DataTable.test.tsx.snap +366 -0
  84. package/src/__snapshots__/MarkdownView.test.tsx.snap +284 -74
  85. package/src/__snapshots__/SplitPage.test.tsx.snap +698 -46
  86. package/src/bunSetup.ts +0 -4
  87. package/src/index.tsx +1 -1
  88. package/src/login/LoginScreen.test.tsx +35 -1
  89. package/src/signUp/PasswordRequirements.tsx +9 -6
  90. package/src/signUp/__snapshots__/PasswordRequirements.test.tsx.snap +50 -2
  91. package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +25 -1
  92. package/src/table/TableHeaderCell.tsx +8 -11
  93. package/src/table/TableRow.test.tsx +31 -1
  94. package/src/table/__snapshots__/TableHeaderCell.test.tsx.snap +2 -0
  95. package/src/table/tableContext.tsx +2 -2
  96. package/src/useConsentHistory.test.ts +20 -13
  97. package/src/useConsentHistory.ts +7 -2
  98. package/src/useStoredState.test.tsx +47 -0
package/src/Tooltip.tsx CHANGED
@@ -39,7 +39,7 @@ interface ChildrenProps {
39
39
  onHoverOut?: () => void;
40
40
  }
41
41
 
42
- const getTooltipPosition = ({
42
+ export const getTooltipPosition = ({
43
43
  children,
44
44
  tooltip,
45
45
  measured,
@@ -117,7 +117,7 @@ const getTooltipPosition = ({
117
117
  }
118
118
  };
119
119
 
120
- const Arrow: FC<{position: TooltipPosition; color: string}> = ({position, color}) => {
120
+ export const Arrow: FC<{position: TooltipPosition; color: string}> = ({position, color}) => {
121
121
  const getArrowStyle = (): ViewStyle => {
122
122
  const arrowStyles = {
123
123
  bottom: {
package/src/Unifier.ts CHANGED
@@ -18,13 +18,18 @@ export type PlatformOS = "ios" | "android" | "web";
18
18
  type Luminance = "light" | "lighter" | "dark" | "darker";
19
19
 
20
20
  // Changes a color luminance
21
- export function changeColorLuminance(hex: string, luminanceChange: Luminance) {
22
- // Validate hex string, strip "#" if present.
23
- hex = String(hex).replace(/[^0-9a-f]/gi, "");
24
- if (hex.length === 3) {
25
- hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
26
- } else if (hex.length !== 6) {
27
- throw new Error(`Invalid color hex: ${hex}`);
21
+ export const changeColorLuminance = (hex: string, luminanceChange: Luminance) => {
22
+ let normalizedHex = String(hex).replace(/[^0-9a-f]/gi, "");
23
+ if (normalizedHex.length === 3) {
24
+ normalizedHex =
25
+ normalizedHex[0] +
26
+ normalizedHex[0] +
27
+ normalizedHex[1] +
28
+ normalizedHex[1] +
29
+ normalizedHex[2] +
30
+ normalizedHex[2];
31
+ } else if (normalizedHex.length !== 6) {
32
+ throw new Error(`Invalid color hex: ${normalizedHex}`);
28
33
  }
29
34
  let luminance: number;
30
35
  switch (luminanceChange) {
@@ -44,19 +49,17 @@ export function changeColorLuminance(hex: string, luminanceChange: Luminance) {
44
49
  throw new Error(`Cannot change luminance to ${luminanceChange}`);
45
50
  }
46
51
 
47
- // Convert to decimal and change luminosity
48
52
  let rgb = "#";
49
53
  for (let i = 0; i < 3; i++) {
50
- const decimal = parseInt(hex.substr(i * 2, 2), 16);
54
+ const decimal = parseInt(normalizedHex.substr(i * 2, 2), 16);
51
55
  const appliedLuminance = Math.round(
52
56
  Math.min(Math.max(0, decimal + decimal * luminance), 255)
53
57
  ).toString(16);
54
- // 0 pad, if necessary.
55
58
  rgb += `00${appliedLuminance}`.substr(appliedLuminance.length);
56
59
  }
57
60
 
58
61
  return rgb;
59
- }
62
+ };
60
63
 
61
64
  class UnifierClass {
62
65
  private _web = false;
package/src/Utilities.tsx CHANGED
@@ -6,10 +6,10 @@ import {Platform} from "react-native";
6
6
  import type {APIError, BaseProfile, IconSize} from "./Common";
7
7
  import {COUNTY_AND_COUNTY_EQUIVALENT_ENTITIES} from "./Constants";
8
8
 
9
- export function mergeInlineStyles(
9
+ export const mergeInlineStyles = (
10
10
  inlineStyle?: {__style?: Record<string, unknown>} | undefined,
11
11
  newStyle?: Record<string, unknown> | undefined
12
- ) {
12
+ ) => {
13
13
  const inline = get(inlineStyle, "__style");
14
14
  return {
15
15
  __style: {
@@ -17,14 +17,14 @@ export function mergeInlineStyles(
17
17
  ...newStyle,
18
18
  },
19
19
  };
20
- }
20
+ };
21
21
 
22
- export function isTestUser(profile?: BaseProfile) {
22
+ export const isTestUser = (profile?: BaseProfile) => {
23
23
  return (
24
24
  profile?.email &&
25
25
  (profile.email.indexOf("nang.io") > -1 || profile.email.indexOf("example.com") > -1)
26
26
  );
27
- }
27
+ };
28
28
 
29
29
  export const iconNumberToSize = (size = 16): IconSize => {
30
30
  let iconSize: IconSize;
@@ -170,17 +170,17 @@ export const rangeWithoutZero =
170
170
  // Binds a string classname to the value in an object. Useful when interacting
171
171
  // with ranges that need to come dynamically from a style object. This is
172
172
  // similar to the NPM package 'classnames/bind'.
173
- export function bind<T>(
173
+ export const bind = <T,>(
174
174
  fn: Functor<T>,
175
175
  scope:
176
176
  | {
177
177
  readonly [key: string]: string;
178
178
  }
179
179
  | Record<string, string>
180
- ): (val: T) => Style {
180
+ ): ((val: T) => Style) => {
181
181
  const map = mapClassName((name) => scope[name]);
182
182
  return (val: T): Style => map(fn(val));
183
- }
183
+ };
184
184
 
185
185
  // This takes a series of the previously defined functors, runs them all
186
186
  // against a value and returns the set of their classnames.
@@ -294,15 +294,13 @@ export const isValidGoogleApiKey = (apiKey: string): boolean => {
294
294
  return true;
295
295
  };
296
296
 
297
- export function formattedCountyCode(state: string, countyName: string): string {
298
- // Remove whitespace and convert to lowercase for comparison
297
+ export const formattedCountyCode = (state: string, countyName: string): string => {
299
298
  const stateKey = state
300
299
  .replace(/\s+/g, "")
301
300
  .toLowerCase() as keyof typeof COUNTY_AND_COUNTY_EQUIVALENT_ENTITIES;
302
301
 
303
302
  const stateData = COUNTY_AND_COUNTY_EQUIVALENT_ENTITIES[stateKey];
304
303
 
305
- // Remove whitespace, periods, apostrophes, and dashes for comparison
306
304
  const countyKey = countyName
307
305
  .trim()
308
306
  .toLowerCase()
@@ -317,16 +315,16 @@ export function formattedCountyCode(state: string, countyName: string): string {
317
315
  }
318
316
 
319
317
  return `${countyData.stateFP}${countyData.countyFP}`;
320
- }
318
+ };
321
319
 
322
- export function isAPIError(error: unknown): error is APIError {
320
+ export const isAPIError = (error: unknown): error is APIError => {
323
321
  return Boolean((error as {data?: {title?: unknown}} | null | undefined)?.data?.title);
324
- }
322
+ };
325
323
 
326
- export function printAPIError(error: APIError, details = true): string {
324
+ export const printAPIError = (error: APIError, details = true): string => {
327
325
  let message = error.data?.title;
328
326
  if (error.data?.detail && details) {
329
327
  message = `${message}: ${error.data?.detail}`;
330
328
  }
331
329
  return message;
332
- }
330
+ };
@@ -313,5 +313,242 @@ describe("WebAddressAutocomplete", () => {
313
313
  // The .catch path warns and falls back to plain TextField.
314
314
  expect(warnings.length).toBeGreaterThan(0);
315
315
  });
316
+
317
+ it("cleans up the global callback on unmount", async () => {
318
+ const {unmount} = renderWithTheme(
319
+ <WebAddressAutocomplete
320
+ googleMapsApiKey="my-api-key"
321
+ handleAddressChange={() => {}}
322
+ handleAutoCompleteChange={() => {}}
323
+ inputValue=""
324
+ />
325
+ );
326
+
327
+ const win = testGlobal.window as GoogleMapsWindow;
328
+ expect(win.initAutocomplete).toBeDefined();
329
+
330
+ unmount();
331
+
332
+ expect(win.initAutocomplete).toBeNull();
333
+ });
334
+
335
+ it("re-runs effect when googleMapsApiKey changes", async () => {
336
+ const handleAutoCompleteChange = mock((_arg: AddressInterface) => {});
337
+
338
+ renderWithTheme(
339
+ <WebAddressAutocomplete
340
+ googleMapsApiKey="key-1"
341
+ handleAddressChange={() => {}}
342
+ handleAutoCompleteChange={handleAutoCompleteChange}
343
+ inputValue=""
344
+ />
345
+ );
346
+
347
+ expect(appendedScripts.length).toBe(1);
348
+
349
+ // Simulate successful load for the second key
350
+ const win = testGlobal.window as GoogleMapsWindow;
351
+ const autocompleteConstructor = mock((_input: unknown, _opts: unknown) => ({
352
+ addListener: () => {},
353
+ getPlace: () => null,
354
+ }));
355
+ win.google = {
356
+ maps: {
357
+ places: {
358
+ Autocomplete: autocompleteConstructor,
359
+ },
360
+ },
361
+ };
362
+
363
+ await act(async () => {
364
+ (win.initAutocomplete as () => void)?.();
365
+ await new Promise((resolve) => setTimeout(resolve, 0));
366
+ });
367
+
368
+ expect(autocompleteConstructor).toHaveBeenCalled();
369
+ });
370
+
371
+ it("processes address components with includeCounty", async () => {
372
+ const handleAutoCompleteChange = mock((_arg: AddressInterface) => {});
373
+ let placeChangedCb: (() => void) | undefined;
374
+ let localPlaceResult: PlaceResult | null = null;
375
+
376
+ const autocompleteConstructor = mock((_input: unknown, _opts: unknown) => ({
377
+ addListener: (event: string, cb: () => void) => {
378
+ if (event === "place_changed") {
379
+ placeChangedCb = cb;
380
+ }
381
+ },
382
+ getPlace: () => localPlaceResult,
383
+ }));
384
+
385
+ testGlobal.window = {
386
+ google: {
387
+ maps: {
388
+ places: {
389
+ Autocomplete: autocompleteConstructor,
390
+ },
391
+ },
392
+ },
393
+ };
394
+
395
+ renderWithTheme(
396
+ <WebAddressAutocomplete
397
+ googleMapsApiKey="test-key"
398
+ handleAddressChange={() => {}}
399
+ handleAutoCompleteChange={handleAutoCompleteChange}
400
+ includeCounty
401
+ inputValue=""
402
+ />
403
+ );
404
+
405
+ await act(async () => {
406
+ await new Promise((resolve) => setTimeout(resolve, 0));
407
+ });
408
+
409
+ localPlaceResult = {
410
+ address_components: [
411
+ {long_name: "10", short_name: "10", types: ["street_number"]},
412
+ {long_name: "Main St", short_name: "Main St", types: ["route"]},
413
+ {long_name: "Springfield", short_name: "Springfield", types: ["locality"]},
414
+ {
415
+ long_name: "Sangamon County",
416
+ short_name: "Sangamon County",
417
+ types: ["administrative_area_level_2"],
418
+ },
419
+ {long_name: "Illinois", short_name: "IL", types: ["administrative_area_level_1"]},
420
+ {long_name: "62701", short_name: "62701", types: ["postal_code"]},
421
+ ],
422
+ };
423
+
424
+ await act(async () => {
425
+ placeChangedCb?.();
426
+ });
427
+
428
+ expect(handleAutoCompleteChange).toHaveBeenCalled();
429
+ });
430
+ });
431
+
432
+ describe("no API key behavior", () => {
433
+ it("sets scriptLoaded to false and renders plain TextField", async () => {
434
+ const handleAddressChange = mock(() => {});
435
+ const {UNSAFE_getAllByType} = renderWithTheme(
436
+ <WebAddressAutocomplete
437
+ handleAddressChange={handleAddressChange}
438
+ handleAutoCompleteChange={() => {}}
439
+ inputValue="test"
440
+ />
441
+ );
442
+
443
+ await act(async () => {
444
+ await new Promise((resolve) => setTimeout(resolve, 0));
445
+ });
446
+
447
+ const {TextInput} = require("react-native");
448
+ const inputs = UNSAFE_getAllByType(TextInput);
449
+ expect(inputs.length).toBeGreaterThan(0);
450
+ const {fireEvent: fe} = require("@testing-library/react-native");
451
+ fe.changeText(inputs[0], "new value");
452
+ expect(handleAddressChange).toHaveBeenCalledWith("new value");
453
+ });
454
+
455
+ it("cleans up the global callback on unmount", async () => {
456
+ const handleAutoCompleteChange = mock((_arg: AddressInterface) => {});
457
+
458
+ const {unmount} = renderWithTheme(
459
+ <WebAddressAutocomplete
460
+ googleMapsApiKey="cleanup-key"
461
+ handleAddressChange={() => {}}
462
+ handleAutoCompleteChange={handleAutoCompleteChange}
463
+ inputValue=""
464
+ />
465
+ );
466
+
467
+ await act(async () => {
468
+ await new Promise((resolve) => setTimeout(resolve, 0));
469
+ });
470
+
471
+ const win = (globalThis as Record<string, Record<string, unknown>>).window;
472
+ expect(win.initAutocomplete).toBeDefined();
473
+
474
+ unmount();
475
+
476
+ expect(win.initAutocomplete).toBeNull();
477
+ });
478
+
479
+ it("handles place_changed with includeCounty flag", async () => {
480
+ const handleAutoCompleteChange = mock((_arg: AddressInterface) => {});
481
+
482
+ let placeChangedCb: (() => void) | undefined;
483
+ const autocompleteConstructor = mock((_input: unknown, _opts: unknown) => ({
484
+ addListener: (event: string, cb: () => void) => {
485
+ if (event === "place_changed") {
486
+ placeChangedCb = cb;
487
+ }
488
+ },
489
+ getPlace: () => ({
490
+ address_components: [
491
+ {long_name: "10", short_name: "10", types: ["street_number"]},
492
+ {long_name: "Oak St", short_name: "Oak St", types: ["route"]},
493
+ {long_name: "Portland", short_name: "Portland", types: ["locality"]},
494
+ {long_name: "Oregon", short_name: "OR", types: ["administrative_area_level_1"]},
495
+ {long_name: "97201", short_name: "97201", types: ["postal_code"]},
496
+ {
497
+ long_name: "Multnomah County",
498
+ short_name: "Multnomah",
499
+ types: ["administrative_area_level_2"],
500
+ },
501
+ ],
502
+ }),
503
+ }));
504
+
505
+ const win = (globalThis as Record<string, Record<string, unknown>>).window;
506
+ win.google = {
507
+ maps: {places: {Autocomplete: autocompleteConstructor}},
508
+ };
509
+
510
+ renderWithTheme(
511
+ <WebAddressAutocomplete
512
+ googleMapsApiKey="county-key"
513
+ handleAddressChange={() => {}}
514
+ handleAutoCompleteChange={handleAutoCompleteChange}
515
+ includeCounty
516
+ inputValue=""
517
+ />
518
+ );
519
+
520
+ await act(async () => {
521
+ await new Promise((resolve) => setTimeout(resolve, 10));
522
+ });
523
+
524
+ expect(autocompleteConstructor).toHaveBeenCalled();
525
+ expect(placeChangedCb).toBeDefined();
526
+
527
+ placeChangedCb?.();
528
+ expect(handleAutoCompleteChange).toHaveBeenCalled();
529
+ });
530
+ });
531
+
532
+ describe("without googleMapsApiKey", () => {
533
+ it("sets scriptLoaded to false and renders plain TextField", async () => {
534
+ const handleAddressChange = mock(() => {});
535
+ const {UNSAFE_getAllByType} = renderWithTheme(
536
+ <WebAddressAutocomplete
537
+ handleAddressChange={handleAddressChange}
538
+ handleAutoCompleteChange={() => {}}
539
+ inputValue="test value"
540
+ />
541
+ );
542
+
543
+ await act(async () => {
544
+ await new Promise((resolve) => setTimeout(resolve, 10));
545
+ });
546
+
547
+ const {TextInput} = require("react-native");
548
+ const inputs = UNSAFE_getAllByType(TextInput);
549
+ expect(inputs.length).toBeGreaterThan(0);
550
+ fireEvent.changeText(inputs[0], "new value");
551
+ expect(handleAddressChange).toHaveBeenCalledWith("new value");
552
+ });
316
553
  });
317
554
  });
@@ -141,6 +141,29 @@ describe("WebDropdownMenu", () => {
141
141
  expect(getByText("Placeholder").props.style.fontWeight).toBe("400");
142
142
  expect(getByText("Real").props.style.fontWeight).toBe("400");
143
143
  });
144
+
145
+ it("applies dynamic background via the Pressable style callback", () => {
146
+ const {getByTestId} = renderWithTheme(
147
+ <WebDropdownMenu
148
+ anchor={anchor}
149
+ onClose={() => {}}
150
+ onSelect={() => {}}
151
+ options={options}
152
+ selectedValue="b"
153
+ visible
154
+ />
155
+ );
156
+ const optionPressable = getByTestId("web_dropdown_option_a");
157
+ const styleFn = optionPressable.props.style;
158
+ expect(typeof styleFn).toBe("function");
159
+ const defaultStyle = styleFn({hovered: false, pressed: false});
160
+ const hoveredStyle = styleFn({hovered: true, pressed: false});
161
+ const pressedStyle = styleFn({hovered: false, pressed: true});
162
+ expect(defaultStyle.paddingHorizontal).toBe(12);
163
+ expect(defaultStyle.paddingVertical).toBe(10);
164
+ expect(hoveredStyle.backgroundColor).toBeDefined();
165
+ expect(pressedStyle.backgroundColor).toBeDefined();
166
+ });
144
167
  });
145
168
 
146
169
  describe("useWebDropdownAnchor", () => {
@@ -162,8 +185,6 @@ describe("useWebDropdownAnchor", () => {
162
185
 
163
186
  it("measures the trigger and updates anchor state when the ref has measureInWindow", () => {
164
187
  const {result} = renderHook(() => useWebDropdownAnchor());
165
- // Simulate a mounted native View by assigning a measureInWindow shim to the
166
- // ref. The hook does not care whether the node is a real View instance.
167
188
  const measureInWindow = mock((cb: (x: number, y: number, w: number, h: number) => void) => {
168
189
  cb(10, 20, 100, 40);
169
190
  });
@@ -177,4 +198,32 @@ describe("useWebDropdownAnchor", () => {
177
198
  expect(onMeasured.mock.calls[0][0]).toEqual({height: 40, width: 100, x: 10, y: 20});
178
199
  expect(result.current.anchor).toEqual({height: 40, width: 100, x: 10, y: 20});
179
200
  });
201
+
202
+ it("exercises the Pressable style callback for hover/pressed states", () => {
203
+ const {root} = renderWithTheme(
204
+ <WebDropdownMenu
205
+ anchor={{height: 40, width: 100, x: 0, y: 50}}
206
+ onClose={() => {}}
207
+ onSelect={() => {}}
208
+ options={[
209
+ {label: "A", value: "a"},
210
+ {label: "B", value: "b"},
211
+ ]}
212
+ visible
213
+ />
214
+ );
215
+ // Find a Pressable with the style callback
216
+ const pressables = root.findAll(
217
+ (n) => typeof n.props.style === "function" && n.props["aria-role"] === "button"
218
+ );
219
+ expect(pressables.length).toBeGreaterThan(0);
220
+ // Call the style function with different states to exercise all branches
221
+ const styleFn = pressables[0].props.style;
222
+ const normalStyle = styleFn({hovered: false, pressed: false});
223
+ expect(normalStyle).toHaveProperty("paddingHorizontal");
224
+ const hoveredStyle = styleFn({hovered: true, pressed: false});
225
+ expect(hoveredStyle).toHaveProperty("paddingHorizontal");
226
+ const pressedStyle = styleFn({hovered: false, pressed: true});
227
+ expect(pressedStyle).toHaveProperty("paddingHorizontal");
228
+ });
180
229
  });
@@ -544,3 +544,128 @@ exports[`Banner renders with button and icon 1`] = `
544
544
  "type": "View",
545
545
  }
546
546
  `;
547
+
548
+ exports[`Banner renders with button loading state 1`] = `
549
+ {
550
+ "$$typeof": Symbol(react.test.json),
551
+ "children": [
552
+ {
553
+ "$$typeof": Symbol(react.test.json),
554
+ "children": [
555
+ {
556
+ "$$typeof": Symbol(react.test.json),
557
+ "children": [
558
+ "Banner loading",
559
+ ],
560
+ "props": {
561
+ "style": {
562
+ "color": "#FFFFFF",
563
+ "flexShrink": 1,
564
+ "flexWrap": "wrap",
565
+ "fontWeight": "bold",
566
+ "textAlign": "center",
567
+ },
568
+ },
569
+ "type": "Text",
570
+ },
571
+ {
572
+ "$$typeof": Symbol(react.test.json),
573
+ "children": [
574
+ {
575
+ "$$typeof": Symbol(react.test.json),
576
+ "children": [
577
+ {
578
+ "$$typeof": Symbol(react.test.json),
579
+ "children": [
580
+ {
581
+ "$$typeof": Symbol(react.test.json),
582
+ "children": [
583
+ {
584
+ "$$typeof": Symbol(react.test.json),
585
+ "children": [
586
+ "Loading",
587
+ ],
588
+ "props": {
589
+ "style": {
590
+ "fontSize": 12,
591
+ },
592
+ },
593
+ "type": "Text",
594
+ },
595
+ ],
596
+ "props": {
597
+ "style": {
598
+ "flexDirection": "row-reverse",
599
+ },
600
+ "testID": undefined,
601
+ },
602
+ "type": "View",
603
+ },
604
+ ],
605
+ "props": {
606
+ "style": {
607
+ "flexDirection": "row",
608
+ },
609
+ "testID": undefined,
610
+ },
611
+ "type": "View",
612
+ },
613
+ ],
614
+ "props": {
615
+ "accessibilityHint": "Press to perform action Loading",
616
+ "aria-label": "Loading",
617
+ "aria-role": "button",
618
+ "onPress": [Function: debounced],
619
+ "style": {
620
+ "alignItems": "center",
621
+ "alignSelf": "stretch",
622
+ "backgroundColor": "#FFFFFF",
623
+ "borderRadius": 360,
624
+ "flexDirection": "column",
625
+ "justifyContent": "center",
626
+ "paddingHorizontal": 12,
627
+ "paddingVertical": 4,
628
+ },
629
+ },
630
+ "type": "Pressable",
631
+ },
632
+ ],
633
+ "props": {
634
+ "style": {
635
+ "paddingLeft": 16,
636
+ "paddingRight": 10,
637
+ },
638
+ "testID": undefined,
639
+ },
640
+ "type": "View",
641
+ },
642
+ ],
643
+ "props": {
644
+ "style": {
645
+ "alignItems": "center",
646
+ "flex": 1,
647
+ "flexDirection": "row",
648
+ "justifyContent": "center",
649
+ },
650
+ "testID": undefined,
651
+ },
652
+ "type": "View",
653
+ },
654
+ ],
655
+ "props": {
656
+ "style": {
657
+ "alignItems": "center",
658
+ "backgroundColor": "#2B6072",
659
+ "borderRadius": 4,
660
+ "flexDirection": "row",
661
+ "height": "auto",
662
+ "margin": "auto",
663
+ "minHeight": 32,
664
+ "padding": 4,
665
+ "width": "100%",
666
+ },
667
+ "testID": undefined,
668
+ },
669
+ "type": "View",
670
+ }
671
+ `;