@jobber/components-native 0.95.3 → 0.95.4-improve-co-ca924fd.14

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 (37) hide show
  1. package/dist/package.json +3 -5
  2. package/dist/src/ContentOverlay/ContentOverlay.js +128 -107
  3. package/dist/src/ContentOverlay/ContentOverlay.style.js +8 -12
  4. package/dist/src/ContentOverlay/ContentOverlayProvider.js +5 -0
  5. package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +76 -0
  6. package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +25 -0
  7. package/dist/src/ContentOverlay/index.js +1 -0
  8. package/dist/src/InputText/InputText.js +35 -1
  9. package/dist/src/utils/meta/meta.json +1 -0
  10. package/dist/tsconfig.build.tsbuildinfo +1 -1
  11. package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +1 -5
  12. package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +11 -10
  13. package/dist/types/src/ContentOverlay/ContentOverlayProvider.d.ts +6 -0
  14. package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +32 -0
  15. package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +7 -0
  16. package/dist/types/src/ContentOverlay/index.d.ts +1 -0
  17. package/dist/types/src/ContentOverlay/types.d.ts +5 -12
  18. package/jestSetup.js +2 -0
  19. package/package.json +3 -5
  20. package/src/ContentOverlay/ContentOverlay.stories.tsx +59 -0
  21. package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
  22. package/src/ContentOverlay/ContentOverlay.test.tsx +157 -79
  23. package/src/ContentOverlay/ContentOverlay.tsx +223 -210
  24. package/src/ContentOverlay/ContentOverlayProvider.tsx +12 -0
  25. package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +276 -0
  26. package/src/ContentOverlay/computeContentOverlayBehavior.ts +119 -0
  27. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +81 -0
  28. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +36 -0
  29. package/src/ContentOverlay/index.ts +1 -0
  30. package/src/ContentOverlay/types.ts +5 -13
  31. package/src/InputText/InputText.test.tsx +122 -0
  32. package/src/InputText/InputText.tsx +52 -2
  33. package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +0 -20
  34. package/src/utils/meta/meta.json +1 -0
  35. package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +0 -23
  36. package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +0 -3
  37. package/src/ContentOverlay/UNSAFE_WrappedModalize.tsx +0 -41
@@ -1,7 +1,3 @@
1
1
  import React from "react";
2
- import type { Modalize } from "react-native-modalize";
3
2
  import type { ContentOverlayProps } from "./types";
4
- export declare const ContentOverlay: React.ForwardRefExoticComponent<ContentOverlayProps & React.RefAttributes<{
5
- open?: Modalize["open"];
6
- close?: Modalize["close"];
7
- } | undefined>>;
3
+ export declare function ContentOverlay({ children, title, accessibilityLabel, fullScreen, showDismiss, isDraggable, adjustToContentHeight, keyboardShouldPersistTaps, scrollEnabled, modalBackgroundColor, onClose, onOpen, onBeforeExit, loading, ref, }: ContentOverlayProps): React.JSX.Element;
@@ -1,32 +1,33 @@
1
1
  export declare const useStyles: () => {
2
+ handleWrapper: {
3
+ paddingBottom: number;
4
+ paddingTop: number;
5
+ };
2
6
  handle: {
3
7
  width: number;
4
8
  height: number;
5
9
  backgroundColor: string;
6
- top: number;
7
10
  borderRadius: number;
8
11
  };
9
- overlay: {
12
+ backdrop: {
10
13
  backgroundColor: string;
11
14
  };
12
- modal: {
15
+ background: {
13
16
  borderTopLeftRadius: number;
14
17
  borderTopRightRadius: number;
15
18
  };
16
- modalForLargeScreens: {
17
- width: number;
18
- alignSelf: "center";
19
- };
20
19
  header: {
21
20
  flexDirection: "row";
22
- backgroundColor: string;
23
- paddingTop: number;
24
21
  zIndex: number;
22
+ minHeight: number;
25
23
  borderTopLeftRadius: number;
26
24
  borderTopRightRadius: number;
27
- minHeight: number;
28
25
  };
29
26
  headerShadow: {
27
+ position: "absolute";
28
+ top: number;
29
+ height: number;
30
+ width: "100%";
30
31
  shadowColor: string;
31
32
  shadowOffset: {
32
33
  width: number;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ interface ContentOverlayProviderProps {
3
+ readonly children: React.ReactNode;
4
+ }
5
+ export declare function ContentOverlayProvider({ children, }: ContentOverlayProviderProps): React.JSX.Element;
6
+ export {};
@@ -0,0 +1,32 @@
1
+ export interface ContentOverlayConfig {
2
+ fullScreen: boolean;
3
+ adjustToContentHeight: boolean;
4
+ isDraggable: boolean;
5
+ hasOnBeforeExit: boolean;
6
+ showDismiss: boolean;
7
+ }
8
+ export interface ContentOverlayState {
9
+ isScreenReaderEnabled: boolean;
10
+ position: number;
11
+ }
12
+ export type InitialHeight = "fullScreen" | "contentHeight";
13
+ export interface ContentOverlayBehavior {
14
+ initialHeight: InitialHeight;
15
+ isDraggable: boolean;
16
+ showDismiss: boolean;
17
+ }
18
+ /**
19
+ * Computes the abstract behavior of ContentOverlay from its props and state.
20
+ *
21
+ * This pure function documents and centralizes the complex logic that determines:
22
+ * - Initial height mode (fullScreen vs contentHeight)
23
+ * - Whether the overlay is draggable
24
+ * - Whether the dismiss button should be shown
25
+ *
26
+ * The logic accounts for legacy behavior where:
27
+ * - `onBeforeExit` silently overrides `isDraggable` to false
28
+ * - Default props (neither fullScreen nor adjustToContentHeight) are treated
29
+ * as contentHeight for the new implementation
30
+ * - Dismiss button visibility depends on multiple factors including position state
31
+ */
32
+ export declare function computeContentOverlayBehavior(config: ContentOverlayConfig, state: ContentOverlayState): ContentOverlayBehavior;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Hook that dismisses the bottom sheet on the hardware back button press if it is visible
3
+ * @param bottomSheetModalRef ref to the bottom sheet modal component
4
+ */
5
+ export declare function useBottomSheetModalBackHandler(onCloseController: () => void): {
6
+ handleSheetPositionChange: (index: number) => void;
7
+ };
@@ -1,2 +1,3 @@
1
1
  export { ContentOverlay } from "./ContentOverlay";
2
+ export { ContentOverlayProvider } from "./ContentOverlayProvider";
2
3
  export type { ContentOverlayRef, ModalBackgroundColor } from "./types";
@@ -1,5 +1,4 @@
1
- import type { ReactNode } from "react";
2
- import type { Modalize } from "react-native-modalize";
1
+ import type { ReactNode, Ref } from "react";
3
2
  export interface ContentOverlayProps {
4
3
  /**
5
4
  * Content to be passed into the overlay
@@ -64,24 +63,18 @@ export interface ContentOverlayProps {
64
63
  * Callback that is called between overlay is closed and when the "x" button is pressed
65
64
  */
66
65
  readonly onBeforeExit?: () => void;
67
- /**
68
- * Define the behavior of the keyboard when having inputs inside the modal.
69
- * @default padding
70
- */
71
- readonly keyboardAvoidingBehavior?: "height" | "padding" | "position";
72
66
  /**
73
67
  * Boolean to show a disabled state
74
68
  * @default false
75
69
  */
76
70
  readonly loading?: boolean;
77
71
  /**
78
- * Define keyboard's Android behavior like iOS's one.
79
- * @default Platform.select({ ios: true, android: false })
72
+ * Ref to the content overlay component.
80
73
  */
81
- readonly avoidKeyboardLikeIOS?: boolean;
74
+ readonly ref?: Ref<ContentOverlayRef>;
82
75
  }
83
76
  export type ModalBackgroundColor = "surface" | "background";
84
77
  export type ContentOverlayRef = {
85
- open?: Modalize["open"];
86
- close?: Modalize["close"];
78
+ open?: () => void;
79
+ close?: () => void;
87
80
  } | undefined;
package/jestSetup.js CHANGED
@@ -16,6 +16,8 @@ jest.mock("./dist/src/Button/components/InternalButtonLoading", () => {
16
16
  };
17
17
  });
18
18
 
19
+ // NOTE: this is the old way we used to mock reanimated. We actually do not need to mock it anymore.
20
+ // To ensure correct test behaviour, please add `jest.unmock("react-native-reanimated")` to your test suite.
19
21
  jest.mock("react-native-reanimated", () => {
20
22
  const reanimated = require("react-native-reanimated/mock");
21
23
  const timing = () => ({ start: () => undefined });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.95.3",
3
+ "version": "0.95.4-improve-co-ca924fd.14+ca924fd5a",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -44,9 +44,8 @@
44
44
  "deepmerge": "^4.2.2",
45
45
  "lodash": "^4.17.21",
46
46
  "react-hook-form": "^7.52.0",
47
- "react-intl": "^7.1.11",
47
+ "react-intl": "^6 || ^7",
48
48
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
49
- "react-native-modalize": "^2.0.13",
50
49
  "react-native-portalize": "^1.0.7",
51
50
  "react-native-toast-message": "^2.1.6",
52
51
  "react-native-uuid": "^1.4.9",
@@ -90,11 +89,10 @@
90
89
  "react-native-gesture-handler": ">=2.22.0",
91
90
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
92
91
  "react-native-modal-datetime-picker": " >=13.0.0",
93
- "react-native-modalize": "^2.0.13",
94
92
  "react-native-portalize": "^1.0.7",
95
93
  "react-native-reanimated": "^3.0.0",
96
94
  "react-native-safe-area-context": "^5.4.0",
97
95
  "react-native-svg": ">=12.0.0"
98
96
  },
99
- "gitHead": "d082bc71718efe42e118b1f3adcd9007bb1923cb"
97
+ "gitHead": "ca924fd5a3a8d378218db75aa5b9233c46e7c256"
100
98
  }
@@ -0,0 +1,59 @@
1
+ import React, { useRef } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
3
+ import { View } from "react-native";
4
+ import { SafeAreaProvider } from "react-native-safe-area-context";
5
+ import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
6
+ import { Button, Heading, Text } from "@jobber/components-native";
7
+ import { ContentOverlay } from "./ContentOverlay";
8
+ import type { ContentOverlayRef } from "./types";
9
+
10
+ const meta = {
11
+ title: "Components/Overlays/ContentOverlay",
12
+ component: ContentOverlay,
13
+ } satisfies Meta<typeof ContentOverlay>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ const BasicTemplate = () => {
19
+ const contentOverlayRef = useRef<ContentOverlayRef>(null);
20
+
21
+ const openContentOverlay = () => {
22
+ contentOverlayRef.current?.open?.();
23
+ };
24
+
25
+ const closeContentOverlay = () => {
26
+ contentOverlayRef.current?.close?.();
27
+ };
28
+
29
+ return (
30
+ <SafeAreaProvider>
31
+ <BottomSheetModalProvider>
32
+ <View style={{ display: "flex", flexDirection: "column", gap: 16 }}>
33
+ <Heading>Basic ContentOverlay</Heading>
34
+ <Text>
35
+ Note that due to the differences between React Native Web and React
36
+ Native, this does not render 100% properly
37
+ </Text>
38
+ <Button label="Open Content Overlay" onPress={openContentOverlay} />
39
+ <Button label="Close Content Overlay" onPress={closeContentOverlay} />
40
+ </View>
41
+ <ContentOverlay
42
+ ref={contentOverlayRef}
43
+ title="Content Overlay Title"
44
+ onClose={() => console.log("closed content overlay")}
45
+ onOpen={() => console.log("opened content overlay")}
46
+ >
47
+ <View style={{ padding: 16 }}>
48
+ <Text>This is the content inside the overlay.</Text>
49
+ </View>
50
+ </ContentOverlay>
51
+ </BottomSheetModalProvider>
52
+ </SafeAreaProvider>
53
+ );
54
+ };
55
+
56
+ export const Basic: Story = {
57
+ render: BasicTemplate,
58
+ args: {} as Story["args"],
59
+ };
@@ -2,43 +2,43 @@ import { buildThemedStyles } from "../AtlantisThemeContext";
2
2
 
3
3
  export const useStyles = buildThemedStyles(tokens => {
4
4
  const modalBorderRadius = tokens["radius-larger"];
5
- const titleOffsetFromHandle = tokens["space-base"];
6
5
 
7
6
  return {
7
+ handleWrapper: {
8
+ paddingBottom: tokens["space-smallest"],
9
+ paddingTop: tokens["space-small"],
10
+ },
11
+
8
12
  handle: {
9
13
  width: tokens["space-largest"],
10
14
  height: tokens["space-smaller"] + tokens["space-smallest"],
11
15
  backgroundColor: tokens["color-border"],
12
- top: tokens["space-small"],
13
16
  borderRadius: tokens["radius-circle"],
14
17
  },
15
18
 
16
- overlay: {
19
+ backdrop: {
17
20
  backgroundColor: tokens["color-overlay"],
18
21
  },
19
22
 
20
- modal: {
23
+ background: {
21
24
  borderTopLeftRadius: modalBorderRadius,
22
25
  borderTopRightRadius: modalBorderRadius,
23
26
  },
24
27
 
25
- modalForLargeScreens: {
26
- width: 640,
27
- alignSelf: "center",
28
- },
29
-
30
28
  header: {
31
29
  flexDirection: "row",
32
- backgroundColor: tokens["color-surface"],
33
- paddingTop: titleOffsetFromHandle,
34
30
  zIndex: tokens["elevation-base"],
31
+ minHeight: tokens["space-extravagant"] - tokens["space-base"],
35
32
  borderTopLeftRadius: modalBorderRadius,
36
33
  borderTopRightRadius: modalBorderRadius,
37
- minHeight: tokens["space-extravagant"],
38
34
  },
39
35
 
40
36
  headerShadow: {
41
37
  ...tokens["shadow-base"],
38
+ position: "absolute",
39
+ top: -20,
40
+ height: 20,
41
+ width: "100%",
42
42
  },
43
43
 
44
44
  childrenStyle: {
@@ -1,27 +1,41 @@
1
1
  import React, { createRef } from "react";
2
- import { fireEvent, render, waitFor } from "@testing-library/react-native";
2
+ import {
3
+ act,
4
+ render,
5
+ screen,
6
+ userEvent,
7
+ waitFor,
8
+ } from "@testing-library/react-native";
3
9
  import { AccessibilityInfo, View } from "react-native";
4
- import { Host } from "react-native-portalize";
5
10
  import type { ReactTestInstance } from "react-test-renderer";
6
- import { act } from "react-test-renderer";
7
11
  import type { ContentOverlayRef, ModalBackgroundColor } from "./types";
8
12
  import { ContentOverlay } from "./ContentOverlay";
13
+ import { ContentOverlayProvider } from "./ContentOverlayProvider";
9
14
  import { tokens } from "../utils/design";
10
15
  import { Button } from "../Button";
11
16
  import { Content } from "../Content";
12
17
  import { Text } from "../Text";
13
18
 
14
19
  jest.unmock("../hooks/useIsScreenReaderEnabled");
20
+ jest.unmock("react-native-reanimated");
21
+ jest.unmock("@gorhom/bottom-sheet");
22
+ jest.mock("@gorhom/bottom-sheet/lib/commonjs/hooks/useAnimatedLayout", () => ({
23
+ // Fix for reanimated not actually running in the test environment.
24
+ useAnimatedLayout: () => {
25
+ const value = {
26
+ containerHeight: 600,
27
+ rawContainerHeight: 600,
28
+ handleHeight: 24,
29
+ footerHeight: 0,
30
+ contentHeight: 400,
31
+ containerOffset: { top: 0, bottom: 0, left: 0, right: 0 },
32
+ };
15
33
 
16
- function fireLayoutEvent(childrenContent: ReactTestInstance) {
17
- fireEvent(childrenContent, "onLayout", {
18
- nativeEvent: {
19
- layout: {
20
- height: 100,
21
- },
22
- },
23
- });
24
- }
34
+ return { value, get: () => value };
35
+ },
36
+ }));
37
+
38
+ const user = userEvent.setup();
25
39
 
26
40
  interface testRendererOptions {
27
41
  text: string;
@@ -44,12 +58,6 @@ function getDefaultOptions(): testRendererOptions {
44
58
  fullScreen: false,
45
59
  showDismiss: false,
46
60
  modalBackgroundColor: "surface",
47
- onCloseCallback: () => {
48
- return;
49
- },
50
- onOpenCallback: () => {
51
- return;
52
- },
53
61
  };
54
62
  }
55
63
 
@@ -69,8 +77,8 @@ function renderContentOverlay(
69
77
  ) {
70
78
  const contentOverlayRef = createRef<ContentOverlayRef>();
71
79
 
72
- const renderResult = render(
73
- <Host>
80
+ render(
81
+ <ContentOverlayProvider>
74
82
  <View>
75
83
  <Text>I am a bunch of text</Text>
76
84
  <Button
@@ -95,32 +103,31 @@ function renderContentOverlay(
95
103
  </Content>
96
104
  </ContentOverlay>
97
105
  </View>
98
- </Host>,
106
+ </ContentOverlayProvider>,
99
107
  );
100
-
101
- const childrenView = renderResult.getByTestId("ATL-Overlay-Children");
102
- fireLayoutEvent(childrenView);
103
- const headerComponent = renderResult.getByTestId("ATL-Overlay-Header");
104
- fireLayoutEvent(headerComponent);
105
-
106
- return renderResult;
107
108
  }
108
109
 
109
110
  async function renderAndOpenContentOverlay(
110
111
  defaultOptions = getDefaultOptions(),
111
112
  ) {
112
- const rendered = renderContentOverlay(defaultOptions);
113
+ jest.useFakeTimers();
114
+ const props = {
115
+ onOpenCallback: jest.fn(),
116
+ onCloseCallback: jest.fn(),
117
+ ...defaultOptions,
118
+ };
113
119
 
120
+ renderContentOverlay(props);
121
+
122
+ await user.press(screen.getByLabelText(defaultOptions.buttonLabel));
114
123
  await act(async () => {
115
- fireEvent.press(rendered.getByLabelText(defaultOptions.buttonLabel));
124
+ jest.runAllTimers();
116
125
  });
117
126
 
118
- // Wait for the modal to open asynchronously (due to requestAnimationFrame)
119
127
  await waitFor(() => {
120
- expect(rendered.getByTestId("ATL-Overlay-Header")).toBeDefined();
128
+ expect(screen.getByTestId("ATL-Overlay-Header")).toBeDefined();
129
+ expect(props.onOpenCallback).toHaveBeenCalledTimes(1);
121
130
  });
122
-
123
- return rendered;
124
131
  }
125
132
 
126
133
  describe("when open is called on the content overlay ref", () => {
@@ -129,9 +136,9 @@ describe("when open is called on the content overlay ref", () => {
129
136
  ...getDefaultOptions(),
130
137
  text: "I am text within the content overlay",
131
138
  };
132
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
139
+ await renderAndOpenContentOverlay(options);
133
140
 
134
- expect(contentOverlayScreen.getByText(options.text)).toBeDefined();
141
+ expect(screen.getByText(options.text)).toBeDefined();
135
142
  });
136
143
  });
137
144
 
@@ -142,16 +149,16 @@ describe("when the close button is clicked on an open content overlay", () => {
142
149
  text: "I am text within the content overlay",
143
150
  showDismiss: true,
144
151
  };
145
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
152
+ await renderAndOpenContentOverlay(options);
146
153
 
154
+ const closeButton = await screen.findByTestId("ATL-Overlay-CloseButton");
155
+ await user.press(closeButton);
147
156
  await act(async () => {
148
- fireEvent.press(
149
- contentOverlayScreen.getByTestId("ATL-Overlay-CloseButton"),
150
- );
157
+ jest.runAllTimers();
151
158
  });
152
159
 
153
160
  await waitFor(() => {
154
- expect(contentOverlayScreen.queryByText(options.text)).toBeNull();
161
+ expect(screen.queryByText(options.text)).toBeNull();
155
162
  });
156
163
  });
157
164
  });
@@ -163,12 +170,12 @@ describe("when the close button is clicked on an open content overlay with a def
163
170
  onCloseCallback: jest.fn(),
164
171
  showDismiss: true,
165
172
  };
166
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
173
+ await renderAndOpenContentOverlay(options);
167
174
 
175
+ const closeButton = await screen.findByTestId("ATL-Overlay-CloseButton");
176
+ await user.press(closeButton);
168
177
  await act(async () => {
169
- fireEvent.press(
170
- contentOverlayScreen.getByTestId("ATL-Overlay-CloseButton"),
171
- );
178
+ jest.runAllTimers();
172
179
  });
173
180
 
174
181
  await waitFor(() => {
@@ -213,9 +220,9 @@ describe("when title prop passed to content overlay", () => {
213
220
  ...getDefaultOptions(),
214
221
  title: "Awesome Title",
215
222
  };
216
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
223
+ await renderAndOpenContentOverlay(options);
217
224
 
218
- expect(contentOverlayScreen.getByText(options.title)).toBeDefined();
225
+ expect(screen.getByText(options.title)).toBeDefined();
219
226
  });
220
227
  });
221
228
 
@@ -226,11 +233,9 @@ describe("when accessibilityLabel prop passed to content overlay", () => {
226
233
  a11yLabel: "Awesome a11y Label",
227
234
  showDismiss: true,
228
235
  };
229
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
236
+ await renderAndOpenContentOverlay(options);
230
237
 
231
- expect(
232
- contentOverlayScreen.getByLabelText(options.a11yLabel || "ohno"),
233
- ).toBeDefined();
238
+ expect(screen.getByLabelText(options.a11yLabel || "ohno")).toBeDefined();
234
239
  });
235
240
  });
236
241
 
@@ -241,10 +246,10 @@ describe("when accessibilityLabel prop NOT passed to content overlay", () => {
241
246
  title: "Awesome Title",
242
247
  showDismiss: true,
243
248
  };
244
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
249
+ await renderAndOpenContentOverlay(options);
245
250
 
246
251
  expect(
247
- contentOverlayScreen.getAllByLabelText(`Close ${options.title} modal`),
252
+ screen.getAllByLabelText(`Close ${options.title} modal`),
248
253
  ).toHaveLength(2);
249
254
  });
250
255
  });
@@ -258,11 +263,9 @@ describe("when there is a screen reader enabled", () => {
258
263
  const options: testRendererOptions = {
259
264
  ...getDefaultOptions(),
260
265
  };
261
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
266
+ await renderAndOpenContentOverlay(options);
262
267
 
263
- expect(
264
- await contentOverlayScreen.findByTestId("ATL-Overlay-CloseButton"),
265
- ).toBeDefined();
268
+ expect(await screen.findByTestId("ATL-Overlay-CloseButton")).toBeDefined();
266
269
  });
267
270
  });
268
271
 
@@ -272,10 +275,8 @@ describe("when fullScreen is set to true", () => {
272
275
  ...getDefaultOptions(),
273
276
  fullScreen: true,
274
277
  };
275
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
276
- expect(
277
- contentOverlayScreen.getByTestId("ATL-Overlay-CloseButton"),
278
- ).toBeDefined();
278
+ await renderAndOpenContentOverlay(options);
279
+ expect(screen.getByTestId("ATL-Overlay-CloseButton")).toBeDefined();
279
280
  });
280
281
  });
281
282
 
@@ -285,10 +286,8 @@ describe("when showDismiss is set to true", () => {
285
286
  ...getDefaultOptions(),
286
287
  showDismiss: true,
287
288
  };
288
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
289
- expect(
290
- contentOverlayScreen.getByTestId("ATL-Overlay-CloseButton"),
291
- ).toBeDefined();
289
+ await renderAndOpenContentOverlay(options);
290
+ expect(screen.getByTestId("ATL-Overlay-CloseButton")).toBeDefined();
292
291
  });
293
292
  });
294
293
 
@@ -299,13 +298,9 @@ describe("when the close button is clicked on an open content overlay with a def
299
298
  onBeforeExitCallback: jest.fn(),
300
299
  showDismiss: true,
301
300
  };
302
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
301
+ await renderAndOpenContentOverlay(options);
303
302
 
304
- await act(async () => {
305
- fireEvent.press(
306
- contentOverlayScreen.getByTestId("ATL-Overlay-CloseButton"),
307
- );
308
- });
303
+ await user.press(screen.getByTestId("ATL-Overlay-CloseButton"));
309
304
 
310
305
  await waitFor(() => {
311
306
  expect(options.onBeforeExitCallback).toHaveBeenCalled();
@@ -319,10 +314,9 @@ describe("modalBackgroundColor prop", () => {
319
314
  const options: testRendererOptions = {
320
315
  ...getDefaultOptions(),
321
316
  };
322
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
323
- const OverlayHeader = contentOverlayScreen.getByTestId(
324
- "ATL-Overlay-Header",
325
- ).children[0] as ReactTestInstance;
317
+ await renderAndOpenContentOverlay(options);
318
+ const OverlayHeader = screen.getByTestId("ATL-Overlay-Header")
319
+ .children[0] as ReactTestInstance;
326
320
  const OverlayHeaderStyles = OverlayHeader.props.style;
327
321
 
328
322
  expect(OverlayHeaderStyles).toEqual(
@@ -349,10 +343,9 @@ describe("modalBackgroundColor prop", () => {
349
343
  ...getDefaultOptions(),
350
344
  modalBackgroundColor: "background",
351
345
  };
352
- const contentOverlayScreen = await renderAndOpenContentOverlay(options);
353
- const OverlayHeader = contentOverlayScreen.getByTestId(
354
- "ATL-Overlay-Header",
355
- ).children[0] as ReactTestInstance;
346
+ await renderAndOpenContentOverlay(options);
347
+ const OverlayHeader = screen.getByTestId("ATL-Overlay-Header")
348
+ .children[0] as ReactTestInstance;
356
349
  const OverlayHeaderStyles = OverlayHeader.props.style;
357
350
 
358
351
  expect(OverlayHeaderStyles).toEqual(
@@ -365,3 +358,88 @@ describe("modalBackgroundColor prop", () => {
365
358
  });
366
359
  });
367
360
  });
361
+
362
+ describe("scrollEnabled prop", () => {
363
+ describe("when scrollEnabled is false (default)", () => {
364
+ it("should render content in BottomSheetView", async () => {
365
+ const options: testRendererOptions = {
366
+ ...getDefaultOptions(),
367
+ };
368
+ await renderAndOpenContentOverlay(options);
369
+
370
+ expect(screen.getByText(options.text)).toBeDefined();
371
+ expect(screen.getByTestId("ATL-Overlay-Children")).toBeDefined();
372
+ });
373
+ });
374
+ });
375
+
376
+ describe("loading prop", () => {
377
+ describe("when loading is true", () => {
378
+ it("should show subdued heading text", async () => {
379
+ const overlayRef = createRef<ContentOverlayRef>();
380
+
381
+ render(
382
+ <ContentOverlayProvider>
383
+ <View>
384
+ <ContentOverlay
385
+ ref={overlayRef}
386
+ title="Loading Overlay"
387
+ loading={true}
388
+ showDismiss={true}
389
+ >
390
+ <Text>Loading content</Text>
391
+ </ContentOverlay>
392
+ </View>
393
+ </ContentOverlayProvider>,
394
+ );
395
+
396
+ await act(async () => {
397
+ overlayRef.current?.open?.();
398
+ });
399
+
400
+ await waitFor(() => {
401
+ expect(screen.getByText("Loading Overlay")).toBeDefined();
402
+ });
403
+ });
404
+ });
405
+ });
406
+
407
+ describe("onBeforeExit callback", () => {
408
+ describe("when close button is pressed with onBeforeExit defined", () => {
409
+ it("should call onBeforeExit instead of immediately closing", async () => {
410
+ const overlayRef = createRef<ContentOverlayRef>();
411
+ const onBeforeExitCallback = jest.fn();
412
+
413
+ render(
414
+ <ContentOverlayProvider>
415
+ <View>
416
+ <ContentOverlay
417
+ ref={overlayRef}
418
+ title="Confirmation Required"
419
+ onBeforeExit={onBeforeExitCallback}
420
+ showDismiss={true}
421
+ >
422
+ <Text>Must confirm to close</Text>
423
+ </ContentOverlay>
424
+ </View>
425
+ </ContentOverlayProvider>,
426
+ );
427
+
428
+ await act(async () => {
429
+ overlayRef.current?.open?.();
430
+ });
431
+
432
+ await waitFor(() => {
433
+ expect(screen.getByText("Must confirm to close")).toBeDefined();
434
+ });
435
+
436
+ const closeButton = screen.getByTestId("ATL-Overlay-CloseButton");
437
+ await user.press(closeButton);
438
+
439
+ await waitFor(() => {
440
+ expect(onBeforeExitCallback).toHaveBeenCalled();
441
+ expect(screen.getByText("Must confirm to close")).toBeDefined();
442
+ });
443
+ });
444
+ });
445
+ });