@applicaster/quick-brick-core 13.0.0-alpha.6179233127 → 13.0.0-alpha.6400875052
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/App/ErrorBoundary/__tests__/errorBoundary.test.jsx +232 -0
- package/App/ErrorBoundary/__tests__/store.test.js +143 -0
- package/App/ErrorBoundary/index.tsx +45 -21
- package/App/ErrorBoundary/store.ts +26 -0
- package/App/ModalProvider/ModalBottomSheet/Overlay.tsx +5 -7
- package/App/ModalProvider/index.tsx +2 -2
- package/App/NavigationProvider/NavigationProvider.tsx +18 -43
- package/App/index.tsx +3 -3
- package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +14 -0
- package/package.json +8 -9
- package/App/ErrorBoundary/__tests__/errorBoundary.test.js +0 -55
- package/App/ModalProvider/ModalChildrenWrapper/index.web.ts +0 -1
- /package/App/ModalProvider/ModalChildrenWrapper/{index.ios.ts → index.ts} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity } from "react-native";
|
|
3
|
+
import { render, fireEvent, act } from "@testing-library/react-native";
|
|
4
|
+
import { useErrorStore } from "../store";
|
|
5
|
+
import { ErrorBoundary } from "../index";
|
|
6
|
+
|
|
7
|
+
jest.mock(
|
|
8
|
+
"@applicaster/zapp-react-native-ui-components/Components/ErrorScreen",
|
|
9
|
+
() => ({
|
|
10
|
+
// eslint-disable-next-line react/prop-types
|
|
11
|
+
ErrorScreen: ({ dismissError, message, buttons }) => (
|
|
12
|
+
<View testID="error-screen">
|
|
13
|
+
<Text testID="error-message">{message || "Default error message"}</Text>
|
|
14
|
+
{/* eslint-disable-next-line react/prop-types */}
|
|
15
|
+
{buttons?.map((button, index) => (
|
|
16
|
+
<TouchableOpacity
|
|
17
|
+
key={index}
|
|
18
|
+
onPress={button.onPress}
|
|
19
|
+
testID={`button-${index}`}
|
|
20
|
+
>
|
|
21
|
+
<Text>{button.text}</Text>
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
))}
|
|
24
|
+
|
|
25
|
+
{dismissError ? (
|
|
26
|
+
<TouchableOpacity onPress={dismissError} testID="dismiss-button">
|
|
27
|
+
<Text>Dismiss Error</Text>
|
|
28
|
+
</TouchableOpacity>
|
|
29
|
+
) : null}
|
|
30
|
+
</View>
|
|
31
|
+
),
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
|
|
36
|
+
sendQuickBrickEvent: jest.fn(),
|
|
37
|
+
QUICK_BRICK_EVENTS: {
|
|
38
|
+
FORCE_APP_RELOAD: "force_app_reload",
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => ({
|
|
43
|
+
coreLogger: {
|
|
44
|
+
addSubsystem: () => ({
|
|
45
|
+
error: jest.fn(),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line react/prop-types
|
|
51
|
+
const TestComponent = ({ error, errorMessage = "error !!" }) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
throw new Error(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View>
|
|
58
|
+
<Text>all is good</Text>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe("<ErrorBoundary />", () => {
|
|
64
|
+
// Clear all error states between tests
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
useErrorStore.getState().dismissError();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("when there is no error", () => {
|
|
70
|
+
it("renders children correctly", () => {
|
|
71
|
+
const { getByText } = render(
|
|
72
|
+
<ErrorBoundary>
|
|
73
|
+
<TestComponent />
|
|
74
|
+
</ErrorBoundary>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(getByText("all is good")).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("when an error is caught", () => {
|
|
82
|
+
const _consoleLogs = {
|
|
83
|
+
error: console.error,
|
|
84
|
+
log: console.log,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
beforeAll(() => {
|
|
88
|
+
console.error = jest.fn();
|
|
89
|
+
console.log = jest.fn();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterAll(() => {
|
|
93
|
+
console.error = _consoleLogs.error;
|
|
94
|
+
console.log = _consoleLogs.log;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders error state with default values (recoverable error)", () => {
|
|
98
|
+
const { getByTestId } = render(
|
|
99
|
+
<ErrorBoundary>
|
|
100
|
+
<TestComponent error />
|
|
101
|
+
</ErrorBoundary>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
105
|
+
expect(getByTestId("dismiss-button")).toBeTruthy(); // Should be recoverable by default
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("handles non-recoverable errors (no dismiss button)", () => {
|
|
109
|
+
const { getByTestId, queryByTestId } = render(
|
|
110
|
+
<ErrorBoundary recoverable={false}>
|
|
111
|
+
<TestComponent error />
|
|
112
|
+
</ErrorBoundary>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
116
|
+
expect(queryByTestId("dismiss-button")).toBeFalsy(); // Should not be recoverable
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("shows custom error message from caught error", () => {
|
|
120
|
+
const customErrorMessage = "Custom error message for testing";
|
|
121
|
+
|
|
122
|
+
const { getByTestId } = render(
|
|
123
|
+
<ErrorBoundary>
|
|
124
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
125
|
+
</ErrorBoundary>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// The error info should be passed to the ErrorScreen
|
|
129
|
+
expect(getByTestId("error-message").props.children).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("can dismiss the error and restore the UI", () => {
|
|
133
|
+
// First render with error
|
|
134
|
+
const { getByTestId, queryByText, rerender } = render(
|
|
135
|
+
<ErrorBoundary>
|
|
136
|
+
<TestComponent error />
|
|
137
|
+
</ErrorBoundary>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Verify error screen is shown
|
|
141
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
142
|
+
|
|
143
|
+
// Dismiss the error
|
|
144
|
+
fireEvent.press(getByTestId("dismiss-button"));
|
|
145
|
+
|
|
146
|
+
// Force rerender to see the change
|
|
147
|
+
rerender(
|
|
148
|
+
<ErrorBoundary>
|
|
149
|
+
<TestComponent />
|
|
150
|
+
</ErrorBoundary>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Now the regular content should be visible
|
|
154
|
+
expect(queryByText("all is good")).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("error store functionality", () => {
|
|
159
|
+
it("properly sets error state", () => {
|
|
160
|
+
const error = new Error("Test error");
|
|
161
|
+
const errorInfo = "Test error info";
|
|
162
|
+
|
|
163
|
+
act(() => {
|
|
164
|
+
useErrorStore.getState().setError(error, errorInfo, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const state = useErrorStore.getState();
|
|
168
|
+
expect(state.hasError).toBe(true);
|
|
169
|
+
expect(state.error).toBe(error);
|
|
170
|
+
expect(state.info).toBe(errorInfo);
|
|
171
|
+
expect(state.recoverable).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("properly resets error state", () => {
|
|
175
|
+
// First set an error
|
|
176
|
+
act(() => {
|
|
177
|
+
useErrorStore
|
|
178
|
+
.getState()
|
|
179
|
+
.setError(new Error("Test error"), "Info", true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Then dismiss it
|
|
183
|
+
act(() => {
|
|
184
|
+
useErrorStore.getState().dismissError();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const state = useErrorStore.getState();
|
|
188
|
+
expect(state.hasError).toBe(false);
|
|
189
|
+
expect(state.error).toBeUndefined();
|
|
190
|
+
expect(state.info).toBeUndefined();
|
|
191
|
+
expect(state.recoverable).toBe(true); // Default value when reset
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("remembers recoverable state", () => {
|
|
195
|
+
act(() => {
|
|
196
|
+
useErrorStore
|
|
197
|
+
.getState()
|
|
198
|
+
.setError(new Error("Test error"), "Info", false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const state = useErrorStore.getState();
|
|
202
|
+
expect(state.recoverable).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("integration with ErrorScreen", () => {
|
|
207
|
+
it("renders restart app button for non-recoverable errors", () => {
|
|
208
|
+
const { getAllByTestId } = render(
|
|
209
|
+
<ErrorBoundary recoverable={false}>
|
|
210
|
+
<TestComponent error />
|
|
211
|
+
</ErrorBoundary>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Should have the restart app button
|
|
215
|
+
const buttons = getAllByTestId(/button-/);
|
|
216
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("passes error info to the ErrorScreen component", () => {
|
|
220
|
+
const customErrorMessage = "Very specific error message";
|
|
221
|
+
|
|
222
|
+
const { getByTestId } = render(
|
|
223
|
+
<ErrorBoundary>
|
|
224
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
225
|
+
</ErrorBoundary>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// The error info should be visible in the UI
|
|
229
|
+
expect(getByTestId("error-message")).toBeTruthy();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { useErrorStore } from "../store";
|
|
3
|
+
|
|
4
|
+
describe("Error Store", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
act(() => {
|
|
7
|
+
useErrorStore.getState().dismissError();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("Initial state", () => {
|
|
12
|
+
it("should have correct default values", () => {
|
|
13
|
+
const { result } = renderHook(() => useErrorStore());
|
|
14
|
+
|
|
15
|
+
expect(result.current).toMatchObject({
|
|
16
|
+
hasError: false,
|
|
17
|
+
error: undefined,
|
|
18
|
+
info: undefined,
|
|
19
|
+
recoverable: true,
|
|
20
|
+
setError: expect.any(Function),
|
|
21
|
+
dismissError: expect.any(Function),
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("setError action", () => {
|
|
27
|
+
it("should set error state with provided values", () => {
|
|
28
|
+
const testError = new Error("Test error");
|
|
29
|
+
const testInfo = "Test error info";
|
|
30
|
+
const isRecoverable = true;
|
|
31
|
+
|
|
32
|
+
const { result } = renderHook(() => useErrorStore());
|
|
33
|
+
|
|
34
|
+
act(() => {
|
|
35
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result.current).toMatchObject({
|
|
39
|
+
hasError: true,
|
|
40
|
+
error: testError,
|
|
41
|
+
info: testInfo,
|
|
42
|
+
recoverable: isRecoverable,
|
|
43
|
+
setError: expect.any(Function),
|
|
44
|
+
dismissError: expect.any(Function),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should set non-recoverable error when specified", () => {
|
|
49
|
+
const testError = new Error("Test error");
|
|
50
|
+
const testInfo = "Test error info";
|
|
51
|
+
const isRecoverable = false;
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() => useErrorStore());
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result.current).toMatchObject({
|
|
60
|
+
recoverable: false,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should default to recoverable error when not specified", () => {
|
|
65
|
+
const testError = new Error("Test error");
|
|
66
|
+
const testInfo = "Test error info";
|
|
67
|
+
|
|
68
|
+
const { result } = renderHook(() => useErrorStore());
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
result.current.setError(testError, testInfo);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.current).toMatchObject({
|
|
75
|
+
recoverable: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("dismissError action", () => {
|
|
81
|
+
it("should reset error state to defaults", () => {
|
|
82
|
+
const testError = new Error("Test error");
|
|
83
|
+
const { result } = renderHook(() => useErrorStore());
|
|
84
|
+
|
|
85
|
+
act(() => {
|
|
86
|
+
result.current.setError(testError, "info", false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(result.current).toMatchObject({
|
|
90
|
+
hasError: true,
|
|
91
|
+
recoverable: false,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
act(() => {
|
|
95
|
+
result.current.dismissError();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result.current).toMatchObject({
|
|
99
|
+
hasError: false,
|
|
100
|
+
error: undefined,
|
|
101
|
+
info: undefined,
|
|
102
|
+
recoverable: true,
|
|
103
|
+
setError: expect.any(Function),
|
|
104
|
+
dismissError: expect.any(Function),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("Direct store access via API", () => {
|
|
110
|
+
it("should allow direct access to store outside React components", () => {
|
|
111
|
+
act(() => {
|
|
112
|
+
useErrorStore
|
|
113
|
+
.getState()
|
|
114
|
+
.setError(new Error("API error"), "API info", false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { result } = renderHook(() => useErrorStore());
|
|
118
|
+
|
|
119
|
+
expect(result.current).toMatchObject({
|
|
120
|
+
hasError: true,
|
|
121
|
+
error: expect.objectContaining({ message: "API error" }),
|
|
122
|
+
info: "API info",
|
|
123
|
+
recoverable: false,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should allow clearing errors via API", () => {
|
|
128
|
+
act(() => {
|
|
129
|
+
useErrorStore.getState().setError(new Error("API error"), "API info");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
useErrorStore.getState().dismissError();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const { result } = renderHook(() => useErrorStore());
|
|
137
|
+
|
|
138
|
+
expect(result.current).toMatchObject({
|
|
139
|
+
hasError: false,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,42 +1,66 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import pick from "lodash/pick";
|
|
2
3
|
|
|
3
4
|
import { coreLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
5
|
import { ErrorScreen } from "@applicaster/zapp-react-native-ui-components/Components/ErrorScreen";
|
|
5
6
|
|
|
7
|
+
import { useErrorStore } from "./store";
|
|
8
|
+
import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
|
|
9
|
+
|
|
6
10
|
const errorBoundaryLogger = coreLogger.addSubsystem("react_error-boundary");
|
|
7
11
|
|
|
8
12
|
type Props = {
|
|
9
13
|
children: React.ReactNode;
|
|
14
|
+
recoverable?: boolean;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
error?: Error;
|
|
15
|
-
info?: string;
|
|
17
|
+
type ErrorBoundaryContentProps = {
|
|
18
|
+
children: React.ReactNode;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const restartApp = () => {
|
|
22
|
+
QuickBrickManager.sendQuickBrickEvent(
|
|
23
|
+
QuickBrickManager.QUICK_BRICK_EVENTS.FORCE_APP_RELOAD
|
|
24
|
+
);
|
|
25
|
+
};
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const restartButton = {
|
|
28
|
+
text: "Restart the app",
|
|
29
|
+
onPress: restartApp,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const buttons = [restartButton];
|
|
33
|
+
|
|
34
|
+
const ErrorBoundaryContent: React.FC<ErrorBoundaryContentProps> = ({
|
|
35
|
+
children,
|
|
36
|
+
}) => {
|
|
37
|
+
const { hasError, dismissError, info } = pick(
|
|
38
|
+
useErrorStore((state) => state),
|
|
39
|
+
["hasError", "dismissError", "info"]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (hasError) {
|
|
43
|
+
return (
|
|
44
|
+
<ErrorScreen
|
|
45
|
+
dismissError={dismissError}
|
|
46
|
+
message={info}
|
|
47
|
+
buttons={buttons}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
52
|
+
return <>{children}</>;
|
|
53
|
+
};
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
export class ErrorBoundary extends React.Component<Props> {
|
|
56
|
+
componentDidCatch(error, info) {
|
|
57
|
+
errorBoundaryLogger.error({ error, data: info, message: error.message });
|
|
58
|
+
const { recoverable = true } = this.props;
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
60
|
+
useErrorStore.getState().setError(error, info, recoverable);
|
|
61
|
+
}
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
render() {
|
|
64
|
+
return <ErrorBoundaryContent>{this.props.children}</ErrorBoundaryContent>;
|
|
41
65
|
}
|
|
42
66
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
type ErrorState = {
|
|
4
|
+
hasError: boolean;
|
|
5
|
+
error?: Error;
|
|
6
|
+
info?: string;
|
|
7
|
+
recoverable: boolean;
|
|
8
|
+
setError: (error: Error, info: string, recoverable?: boolean) => void;
|
|
9
|
+
dismissError: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useErrorStore = create<ErrorState>((set) => ({
|
|
13
|
+
hasError: false,
|
|
14
|
+
error: undefined,
|
|
15
|
+
info: undefined,
|
|
16
|
+
recoverable: true,
|
|
17
|
+
setError: (error: Error, info: string, recoverable = true) =>
|
|
18
|
+
set({ hasError: true, error, info, recoverable }),
|
|
19
|
+
dismissError: () =>
|
|
20
|
+
set({
|
|
21
|
+
hasError: false,
|
|
22
|
+
error: undefined,
|
|
23
|
+
info: undefined,
|
|
24
|
+
recoverable: true,
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { StyleSheet, TouchableOpacity, Easing, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { AnimatedInOut } from "@applicaster/zapp-react-native-ui-components/Components/AnimatedInOut";
|
|
5
|
-
import {
|
|
5
|
+
import { dismissModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
8
|
overlayColor: string;
|
|
@@ -35,21 +35,19 @@ const fadeInAnimation = {
|
|
|
35
35
|
delay,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const animationConfig =
|
|
38
|
+
const animationConfig = {
|
|
39
39
|
in: fadeInAnimation,
|
|
40
40
|
out: {
|
|
41
41
|
...fadeInAnimation,
|
|
42
|
-
onAnimationEnd,
|
|
42
|
+
onAnimationEnd: dismissModal,
|
|
43
43
|
},
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
45
|
|
|
46
46
|
export function Overlay(props: Props) {
|
|
47
|
-
const navigator = useNavigation();
|
|
48
|
-
|
|
49
47
|
return (
|
|
50
48
|
<AnimatedInOut
|
|
51
49
|
visible={props.visible}
|
|
52
|
-
animationConfig={animationConfig
|
|
50
|
+
animationConfig={animationConfig}
|
|
53
51
|
staticStyles={styles.container}
|
|
54
52
|
>
|
|
55
53
|
<TouchableOpacity
|
|
@@ -6,10 +6,10 @@ import { ModalChildrenWrapper } from "./ModalChildrenWrapper";
|
|
|
6
6
|
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
7
7
|
import { ScreenContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
|
|
8
8
|
import { ROUTE_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/routeTypes";
|
|
9
|
-
import {
|
|
9
|
+
import { useModalStoreState } from "@applicaster/zapp-react-native-utils/modalState";
|
|
10
10
|
|
|
11
11
|
export function ModalProvider() {
|
|
12
|
-
const
|
|
12
|
+
const modalState = useModalStoreState();
|
|
13
13
|
|
|
14
14
|
const [modalVisible, setModalVisible] = React.useState(false);
|
|
15
15
|
const [modalShown, setModalShown] = React.useState(false);
|
|
@@ -64,7 +64,11 @@ import { findParent } from "@applicaster/zapp-react-native-ui-components/Context
|
|
|
64
64
|
import { ZappPipesEntryContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
65
65
|
import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
|
|
66
66
|
import { inflateUrl } from "@applicaster/zapp-react-native-ui-components/Components/LinkHandler/LinkHandler";
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
dismissModal,
|
|
69
|
+
modalStore,
|
|
70
|
+
openModal,
|
|
71
|
+
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
68
72
|
import { useSubscriberFor } from "@applicaster/zapp-react-native-utils/reactHooks/useSubscriberFor";
|
|
69
73
|
import { QBUIComponentEvents } from "@applicaster/zapp-react-native-ui-components/events";
|
|
70
74
|
import { Hook } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager/Hook";
|
|
@@ -120,8 +124,6 @@ export function NavigationProvider({ children }: Props) {
|
|
|
120
124
|
|
|
121
125
|
const { context } = React.useContext(ZappPipesEntryContext.Context);
|
|
122
126
|
|
|
123
|
-
const { openModal, dismissModal } = useModalStore();
|
|
124
|
-
|
|
125
127
|
const playNextOverlayState = React.useRef();
|
|
126
128
|
|
|
127
129
|
const online = useConnectionInfo(true);
|
|
@@ -147,7 +149,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
147
149
|
);
|
|
148
150
|
|
|
149
151
|
const getModalState: () => ModalState = () => {
|
|
150
|
-
const { modalState } =
|
|
152
|
+
const { modalState } = modalStore.getState();
|
|
151
153
|
|
|
152
154
|
return modalState;
|
|
153
155
|
};
|
|
@@ -391,7 +393,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
if (hookPlugin.isModalHook()) {
|
|
394
|
-
|
|
396
|
+
openModal({
|
|
395
397
|
item: payload,
|
|
396
398
|
props: { screenType: "hooks" },
|
|
397
399
|
options: { ModalContainer: hookPlugin.getModalContainer() },
|
|
@@ -411,7 +413,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
if (hookPlugin.isModalHook()) {
|
|
414
|
-
|
|
416
|
+
dismissModal();
|
|
415
417
|
} else {
|
|
416
418
|
goBack(true, true);
|
|
417
419
|
}
|
|
@@ -423,7 +425,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
423
425
|
closeLoadingOverlay();
|
|
424
426
|
};
|
|
425
427
|
|
|
426
|
-
const handleHookCancel = ({ hookPlugin, callback }, dispose) => {
|
|
428
|
+
const handleHookCancel = ({ hookPlugin, callback, abort }, dispose) => {
|
|
427
429
|
dispose();
|
|
428
430
|
setStartUpHooks(false);
|
|
429
431
|
|
|
@@ -431,10 +433,12 @@ export function NavigationProvider({ children }: Props) {
|
|
|
431
433
|
callback();
|
|
432
434
|
}
|
|
433
435
|
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
436
|
+
if (!abort) {
|
|
437
|
+
if (hookPlugin.isModalHook()) {
|
|
438
|
+
dismissModal();
|
|
439
|
+
} else {
|
|
440
|
+
goBack(true, true);
|
|
441
|
+
}
|
|
438
442
|
}
|
|
439
443
|
|
|
440
444
|
closeLoadingOverlay();
|
|
@@ -444,7 +448,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
444
448
|
callback?.();
|
|
445
449
|
|
|
446
450
|
if (hookPlugin.isModalHook()) {
|
|
447
|
-
|
|
451
|
+
dismissModal();
|
|
448
452
|
}
|
|
449
453
|
};
|
|
450
454
|
|
|
@@ -681,33 +685,6 @@ export function NavigationProvider({ children }: Props) {
|
|
|
681
685
|
}
|
|
682
686
|
}, [state?.options?.videoModal?.previousMode]);
|
|
683
687
|
|
|
684
|
-
const openModalBottomSheet = React.useCallback(
|
|
685
|
-
({
|
|
686
|
-
ModalBottomSheetContent,
|
|
687
|
-
modalBottomSheetContentProps,
|
|
688
|
-
props = {},
|
|
689
|
-
options = {},
|
|
690
|
-
}) => {
|
|
691
|
-
openModal({
|
|
692
|
-
item: {
|
|
693
|
-
ModalBottomSheetContent,
|
|
694
|
-
modalBottomSheetContentProps,
|
|
695
|
-
},
|
|
696
|
-
props,
|
|
697
|
-
options: {
|
|
698
|
-
animationType: "none",
|
|
699
|
-
animated: false,
|
|
700
|
-
...options,
|
|
701
|
-
},
|
|
702
|
-
});
|
|
703
|
-
},
|
|
704
|
-
[openModal]
|
|
705
|
-
);
|
|
706
|
-
|
|
707
|
-
const closeModalBottomSheet = React.useCallback(() => {
|
|
708
|
-
dismissModal();
|
|
709
|
-
}, [dismissModal]);
|
|
710
|
-
|
|
711
688
|
const getNestedEntry = () => state?.location?.state?.nested?.entry ?? null;
|
|
712
689
|
|
|
713
690
|
// It will only work for the regular stack and pathname, It won't work for modals
|
|
@@ -745,11 +722,9 @@ export function NavigationProvider({ children }: Props) {
|
|
|
745
722
|
getNestedEntry,
|
|
746
723
|
key: state?.location?.key,
|
|
747
724
|
modalData: state.stack.modal?.state ?? null,
|
|
748
|
-
openModal,
|
|
749
|
-
dismissModal,
|
|
725
|
+
openModal: openModal,
|
|
726
|
+
dismissModal: dismissModal,
|
|
750
727
|
getModalState,
|
|
751
|
-
openModalBottomSheet,
|
|
752
|
-
closeModalBottomSheet,
|
|
753
728
|
videoModalState: state.options.videoModal,
|
|
754
729
|
openVideoModal,
|
|
755
730
|
closeVideoModal,
|
package/App/index.tsx
CHANGED
|
@@ -72,11 +72,11 @@ const App = ({
|
|
|
72
72
|
</SplashLoader>
|
|
73
73
|
<ModalProvider />
|
|
74
74
|
</URLSchemeListener>
|
|
75
|
-
<LogicAggregatorContainer
|
|
76
|
-
components={[InteractionManager, ApplyScreenHooks]}
|
|
77
|
-
/>
|
|
78
75
|
<LoadingOverlay />
|
|
79
76
|
</ErrorBoundary>
|
|
77
|
+
<LogicAggregatorContainer
|
|
78
|
+
components={[InteractionManager, ApplyScreenHooks]}
|
|
79
|
+
/>
|
|
80
80
|
</AppContainer>
|
|
81
81
|
</SafeAreaProvider>
|
|
82
82
|
</ThemeManager>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import isEmpty from "lodash/isEmpty";
|
|
2
3
|
import { Dimensions, NativeModules } from "react-native";
|
|
3
4
|
import { loadAppContextData } from "@applicaster/zapp-react-native-redux";
|
|
4
5
|
import { isTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
@@ -10,6 +11,7 @@ import { logger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
10
11
|
import { ACTIVE_LAYOUT_ID_STORAGE_KEY } from "../consts";
|
|
11
12
|
const { AppLoaderBridge } = NativeModules;
|
|
12
13
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
14
|
+
import { useErrorStore } from "../../ErrorBoundary/store";
|
|
13
15
|
|
|
14
16
|
export async function getNativeRemoteContextData(
|
|
15
17
|
dispatch,
|
|
@@ -58,6 +60,18 @@ export async function getNativeRemoteContextData(
|
|
|
58
60
|
|
|
59
61
|
const layout = await getPromiseForType("layout");
|
|
60
62
|
|
|
63
|
+
const layoutLoadingErrorMessage = "Failed to load layout data";
|
|
64
|
+
|
|
65
|
+
if (isEmpty(layout)) {
|
|
66
|
+
useErrorStore
|
|
67
|
+
.getState()
|
|
68
|
+
.setError(
|
|
69
|
+
new Error(layoutLoadingErrorMessage),
|
|
70
|
+
layoutLoadingErrorMessage,
|
|
71
|
+
false
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
const apiVersion = layout?.layout?.["api_version"]; // Taken from remote configuration
|
|
62
76
|
|
|
63
77
|
dispatch(appDataActions.merge({ layoutVersion: apiVersion || "v1" }));
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-core",
|
|
3
|
-
"version": "13.0.0-alpha.
|
|
3
|
+
"version": "13.0.0-alpha.6400875052",
|
|
4
4
|
"description": "Core package for Applicaster's Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "jest -w=2",
|
|
9
8
|
"build": "echo 'skipping ts build for core packages'"
|
|
10
9
|
},
|
|
11
10
|
"repository": {
|
|
@@ -29,13 +28,13 @@
|
|
|
29
28
|
},
|
|
30
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
31
30
|
"dependencies": {
|
|
32
|
-
"@applicaster/applicaster-types": "13.0.0-alpha.
|
|
33
|
-
"@applicaster/quick-brick-core-plugins": "13.0.0-alpha.
|
|
34
|
-
"@applicaster/zapp-pipes-v2-client": "13.0.0-alpha.
|
|
35
|
-
"@applicaster/zapp-react-native-bridge": "13.0.0-alpha.
|
|
36
|
-
"@applicaster/zapp-react-native-redux": "13.0.0-alpha.
|
|
37
|
-
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.
|
|
38
|
-
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "13.0.0-alpha.6400875052",
|
|
32
|
+
"@applicaster/quick-brick-core-plugins": "13.0.0-alpha.6400875052",
|
|
33
|
+
"@applicaster/zapp-pipes-v2-client": "13.0.0-alpha.6400875052",
|
|
34
|
+
"@applicaster/zapp-react-native-bridge": "13.0.0-alpha.6400875052",
|
|
35
|
+
"@applicaster/zapp-react-native-redux": "13.0.0-alpha.6400875052",
|
|
36
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.6400875052",
|
|
37
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.6400875052",
|
|
39
38
|
"atob": "^2.1.2",
|
|
40
39
|
"axios": "^0.28.0",
|
|
41
40
|
"btoa": "^1.2.1",
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { View, Text } from "react-native";
|
|
3
|
-
import { render } from "@testing-library/react-native";
|
|
4
|
-
|
|
5
|
-
import { ErrorBoundary } from "../index";
|
|
6
|
-
|
|
7
|
-
const TestComponent = ({ error }: { error: any }) => {
|
|
8
|
-
if (error) {
|
|
9
|
-
throw new Error("error !!");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<View>
|
|
14
|
-
<Text>all is good</Text>
|
|
15
|
-
</View>
|
|
16
|
-
);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
describe("<ErrorBoundary />", () => {
|
|
20
|
-
describe("when there is no error", () => {
|
|
21
|
-
it("renders correctly", () => {
|
|
22
|
-
const { getByText } = render(
|
|
23
|
-
<ErrorBoundary>
|
|
24
|
-
<TestComponent />
|
|
25
|
-
</ErrorBoundary>
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(getByText("all is good")).toBeTruthy();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("When an error is caught", () => {
|
|
33
|
-
const _consoleLog = console.log;
|
|
34
|
-
|
|
35
|
-
beforeAll(() => {
|
|
36
|
-
console.log = jest.fn();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterAll(() => {
|
|
40
|
-
console.log = _consoleLog;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("renders error state correctly", () => {
|
|
44
|
-
const { getByText } = render(
|
|
45
|
-
<ErrorBoundary>
|
|
46
|
-
<TestComponent error />
|
|
47
|
-
</ErrorBoundary>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Note: You'll need to verify what your ErrorBoundary actually renders in error state
|
|
51
|
-
// This is just an example assertion:
|
|
52
|
-
expect(getByText(/error/i)).toBeTruthy();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Fragment as ModalChildrenWrapper } from "react";
|
|
File without changes
|