@admin-layout/gluestack-ui-mobile 12.2.5-alpha.15 → 12.2.5-alpha.17

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/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [12.2.5-alpha.17](https://github.com/CDEBase/admin-layout/compare/v12.2.5-alpha.16...v12.2.5-alpha.17) (2026-10-10)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **gluestack-ui-mobile:** the error screen's button shows and brings the app back ([4d21d32](https://github.com/CDEBase/admin-layout/commit/4d21d32b142d54cc0edd99bb15084b33cf2ab11d))
11
+
6
12
  ## [12.2.5-alpha.15](https://github.com/CDEBase/admin-layout/compare/v12.2.5-alpha.14...v12.2.5-alpha.15) (2026-10-08)
7
13
 
8
14
  ### Bug Fixes
@@ -0,0 +1,7 @@
1
+ // Jest only (the package builds with tsc). The React Native preset already
2
+ // carries TypeScript, JSX, class properties and CommonJS modules; the
3
+ // hand-listed plugins this used to add clashed with it ("'loose' mode
4
+ // configuration must be the same") and the suite never ran.
5
+ module.exports = {
6
+ presets: ['module:@react-native/babel-preset'],
7
+ };
@@ -11,4 +11,4 @@ jest.mock('react-native-reanimated', () => {
11
11
  });
12
12
 
13
13
  // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
14
- jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
14
+ jest.mock('react-native/src/private/animated/NativeAnimatedHelper');
@@ -10,18 +10,21 @@ const mergeData = merge.recursive(
10
10
  babelConfig: true,
11
11
  },
12
12
  },
13
- name: packageJson.name,
14
13
  displayName: packageJson.name,
15
14
  testEnvironment: "jsdom",
16
15
  preset: "react-native",
17
16
  setupFiles: [
18
17
  "<rootDir>/jest-react-navigation-setup.js"
19
18
  ],
20
- setupFilesAfterEnv: ["@testing-library/jest-native/extend-expect", "./jest.env.js"],
19
+ setupFilesAfterEnv: ["./jest.env.js"],
20
+ // React Native ships Flow + ESM source: compile it with this package's
21
+ // RN preset (babel.config.cjs). The repo-wide transform.js has no Flow
22
+ // support, and node_modules is hoisted to the repo root, so a
23
+ // <rootDir>/node_modules pattern never matched it.
21
24
  "transform": {
22
- "\\.(js|jsx)?$": "../../../transform.js",
25
+ "\\.(js|jsx)?$": "babel-jest",
23
26
  },
24
- transformIgnorePatterns: ["<rootDir>/node_modules/(?!(@react-native|react-native|@expo/vector-icons)/).*/"],
27
+ transformIgnorePatterns: ["/node_modules/(?!((jest-)?react-native[^/]*|@react-native[^/]*|@react-navigation|@expo|@common-stack|@cdm-logger|@apollo|@web3-storage)/)"],
25
28
  moduleNameMapper: {
26
29
  '^__mocks__/(.*)$': '<rootDir>/../../__mocks__/$1',
27
30
  // we'll use commonjs version of lodash for tests 👌
@@ -31,7 +34,8 @@ const mergeData = merge.recursive(
31
34
  // '@react-navigation': '<rootDir>/__mocks__/@react-navigation.js',
32
35
  },
33
36
  roots: [
34
- "src/utils"
37
+ "src/utils",
38
+ "src/components/__tests__"
35
39
  ],
36
40
  },
37
41
  );
@@ -12,7 +12,15 @@ export declare class ErrorBoundary extends React.Component<IErrorBoundryProps, I
12
12
  hasError: boolean;
13
13
  error: Error;
14
14
  };
15
- componentDidCatch(error: Error): void;
15
+ componentDidCatch(error: Error, info: React.ErrorInfo): void;
16
+ /**
17
+ * Mount the children again. The app's boundary wraps the navigation
18
+ * container itself, so while the fallback shows there is no navigator to
19
+ * navigate with: "Go to homepage" called navigate on nothing and the app
20
+ * stayed on this screen until it was force-quit. A fresh mount starts the
21
+ * navigator at its initial route.
22
+ */
23
+ resetError: () => void;
16
24
  render(): React.JSX.Element;
17
25
  }
18
26
  export {};
@@ -4,17 +4,27 @@ import { logger } from '@cdm-logger/client';
4
4
  export class ErrorBoundary extends React.Component {
5
5
  constructor(props) {
6
6
  super(props);
7
+ /**
8
+ * Mount the children again. The app's boundary wraps the navigation
9
+ * container itself, so while the fallback shows there is no navigator to
10
+ * navigate with: "Go to homepage" called navigate on nothing and the app
11
+ * stayed on this screen until it was force-quit. A fresh mount starts the
12
+ * navigator at its initial route.
13
+ */
14
+ this.resetError = () => {
15
+ this.setState({ hasError: false, error: null });
16
+ };
7
17
  this.state = { hasError: false, error: null };
8
18
  }
9
19
  static getDerivedStateFromError(error) {
10
20
  return { hasError: true, error };
11
21
  }
12
- componentDidCatch(error) {
13
- logger.debug(error);
22
+ componentDidCatch(error, info) {
23
+ logger.error(error, info?.componentStack ?? '');
14
24
  }
15
25
  render() {
16
26
  if (this.state.hasError) {
17
- return React.createElement(FallbackComponent, { error: this.state.error });
27
+ return React.createElement(FallbackComponent, { error: this.state.error, resetError: this.resetError });
18
28
  }
19
29
  return this.props.children;
20
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorBounday.js","sourceRoot":"","sources":["../../src/components/ErrorBounday.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuB,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAQ5C,MAAM,OAAO,aAAc,SAAQ,KAAK,CAAC,SAAiD;IACtF,YAAY,KAAyB;QACjC,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,iBAAiB,CAAC,KAAY;QAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,MAAM;QACF,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO,oBAAC,iBAAiB,IAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAc,GAAI,CAAC;QACnE,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC/B,CAAC;CACJ"}
1
+ {"version":3,"file":"ErrorBounday.js","sourceRoot":"","sources":["../../src/components/ErrorBounday.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuB,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAO5C,MAAM,OAAO,aAAc,SAAQ,KAAK,CAAC,SAAiD;IACtF,YAAY,KAAyB;QACjC,KAAK,CAAC,KAAK,CAAC,CAAC;QAYjB;;;;;;WAMG;QACH,eAAU,GAAG,GAAG,EAAE;YACd,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC;QApBE,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,IAAqB;QACjD,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAaD,MAAM;QACF,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO,oBAAC,iBAAiB,IAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAc,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,GAAI,CAAC;QAChG,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC/B,CAAC;CACJ"}
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
2
  type Props = {
3
3
  error: Error;
4
+ /** From the boundary that caught the error: mounts the app again. */
5
+ resetError?: () => void;
4
6
  };
5
7
  export declare const FallbackComponent: (props: Props) => React.JSX.Element;
6
8
  export {};
@@ -3,15 +3,20 @@ import { Button, ButtonText } from '@gluestack-ui/themed';
3
3
  import * as React from 'react';
4
4
  import { StyleSheet } from 'react-native';
5
5
  import { SafeAreaView, Text, View } from 'react-native';
6
- import { useDispatch } from 'react-redux';
7
6
  export const FallbackComponent = (props) => {
8
- const dispatch = useDispatch();
7
+ const goHome = () => {
8
+ if (props.resetError) {
9
+ props.resetError();
10
+ return;
11
+ }
12
+ navigationRef.navigate('MainStack.Layout.Home');
13
+ };
9
14
  return (React.createElement(SafeAreaView, { style: styles.container },
10
15
  React.createElement(View, { style: styles.content },
11
16
  React.createElement(Text, { style: styles.title }, "Oops!"),
12
17
  React.createElement(Text, { style: styles.subtitle }, "There's an error"),
13
18
  React.createElement(Text, { style: styles.error }, props?.error?.message),
14
- React.createElement(Button, { style: styles.button, onPress: () => navigationRef.navigate('MainStack.Layout.Home') },
19
+ React.createElement(Button, { style: styles.button, onPress: goHome },
15
20
  React.createElement(ButtonText, { style: styles.buttonText }, "Go to homepage")))));
16
21
  };
17
22
  const styles = StyleSheet.create({
@@ -37,14 +42,18 @@ const styles = StyleSheet.create({
37
42
  error: {
38
43
  paddingVertical: 16,
39
44
  },
45
+ // A fixed height with no vertical padding: `padding: 25` inside
46
+ // `height: 40` left the label -10pt and it never showed.
40
47
  button: {
41
48
  backgroundColor: '#2196f3',
42
49
  borderRadius: 50,
43
- padding: 25,
44
- height: 40,
50
+ height: 52,
51
+ paddingVertical: 0,
52
+ paddingHorizontal: 24,
45
53
  },
46
54
  buttonText: {
47
55
  color: '#fff',
56
+ fontSize: 17,
48
57
  fontWeight: '600',
49
58
  textAlign: 'center',
50
59
  },
@@ -1 +1 @@
1
- {"version":3,"file":"Fallback.js","sourceRoot":"","sources":["../../src/components/Fallback.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAY,EAAE,EAAE;IAC9C,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,CACH,oBAAC,YAAY,IAAC,KAAK,EAAE,MAAM,CAAC,SAAS;QACjC,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,OAAO;YACvB,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,YAAc;YACvC,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,IAAG,kBAAkB,CAAQ;YACzD,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAG,KAAK,EAAE,KAAK,EAAE,OAAO,CAAQ;YACzD,oBAAC,MAAM,IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,uBAAuB,CAAC;gBACxF,oBAAC,UAAU,IAAC,KAAK,EAAE,MAAM,CAAC,UAAU,qBAA6B,CAC5D,CACN,CACI,CAClB,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,MAAM,GAAQ,UAAU,CAAC,MAAM,CAAC;IAClC,SAAS,EAAE;QACP,eAAe,EAAE,SAAS;QAC1B,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;KAC3B;IACD,OAAO,EAAE;QACL,gBAAgB,EAAE,EAAE;KACvB;IACD,KAAK,EAAE;QACH,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,aAAa,EAAE,EAAE;QACjB,KAAK,EAAE,MAAM;KAChB;IACD,QAAQ,EAAE;QACN,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,MAAM;KAChB;IACD,KAAK,EAAE;QACH,eAAe,EAAE,EAAE;KACtB;IACD,MAAM,EAAE;QACJ,eAAe,EAAE,SAAS;QAC1B,YAAY,EAAE,EAAE;QAChB,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,EAAE;KACb;IACD,UAAU,EAAE;QACR,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,QAAQ;KACtB;CACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"Fallback.js","sourceRoot":"","sources":["../../src/components/Fallback.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAQxD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAY,EAAE,EAAE;IAC9C,MAAM,MAAM,GAAG,GAAG,EAAE;QAChB,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACnB,KAAK,CAAC,UAAU,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QACD,aAAa,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IACpD,CAAC,CAAC;IACF,OAAO,CACH,oBAAC,YAAY,IAAC,KAAK,EAAE,MAAM,CAAC,SAAS;QACjC,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,OAAO;YACvB,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,YAAc;YACvC,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,IAAG,kBAAkB,CAAQ;YACzD,oBAAC,IAAI,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAG,KAAK,EAAE,KAAK,EAAE,OAAO,CAAQ;YACzD,oBAAC,MAAM,IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM;gBACzC,oBAAC,UAAU,IAAC,KAAK,EAAE,MAAM,CAAC,UAAU,qBAA6B,CAC5D,CACN,CACI,CAClB,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,MAAM,GAAQ,UAAU,CAAC,MAAM,CAAC;IAClC,SAAS,EAAE;QACP,eAAe,EAAE,SAAS;QAC1B,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;KAC3B;IACD,OAAO,EAAE;QACL,gBAAgB,EAAE,EAAE;KACvB;IACD,KAAK,EAAE;QACH,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,aAAa,EAAE,EAAE;QACjB,KAAK,EAAE,MAAM;KAChB;IACD,QAAQ,EAAE;QACN,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,MAAM;KAChB;IACD,KAAK,EAAE;QACH,eAAe,EAAE,EAAE;KACtB;IACD,gEAAgE;IAChE,yDAAyD;IACzD,MAAM,EAAE;QACJ,eAAe,EAAE,SAAS;QAC1B,YAAY,EAAE,EAAE;QAChB,MAAM,EAAE,EAAE;QACV,eAAe,EAAE,CAAC;QAClB,iBAAiB,EAAE,EAAE;KACxB;IACD,UAAU,EAAE;QACR,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,QAAQ;KACtB;CACJ,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The app-wide boundary wraps the navigation container. Live 2026-10-10: a
3
+ * screen threw, the fallback showed, and "Go to homepage" did nothing (its
4
+ * label did not even show) - the app was stuck until it was force-quit.
5
+ */
6
+ import React from 'react';
7
+ import { Text } from 'react-native';
8
+ import { fireEvent, render } from '@testing-library/react-native';
9
+ import { ErrorBoundary } from '../ErrorBounday';
10
+ const mockNavigate = jest.fn();
11
+ jest.mock('@common-stack/client-react', () => ({
12
+ navigationRef: { navigate: (...a) => mockNavigate(...a) },
13
+ }));
14
+ jest.mock('@cdm-logger/client', () => ({ logger: { error: jest.fn(), debug: jest.fn() } }));
15
+ jest.mock('@gluestack-ui/themed', () => {
16
+ const { Pressable: P, Text: T } = jest.requireActual('react-native');
17
+ return { Button: P, ButtonText: T };
18
+ });
19
+ let crash = true;
20
+ function Screen() {
21
+ if (crash)
22
+ throw new Error("Property 'promoProgress' doesn't exist");
23
+ return React.createElement(Text, null, "Home");
24
+ }
25
+ describe('app error boundary', () => {
26
+ beforeEach(() => {
27
+ crash = true;
28
+ mockNavigate.mockClear();
29
+ jest.spyOn(console, 'error').mockImplementation(() => undefined);
30
+ });
31
+ it('shows the error and a labelled way home', () => {
32
+ const { getByText } = render(React.createElement(ErrorBoundary, null,
33
+ React.createElement(Screen, null)));
34
+ expect(getByText("Property 'promoProgress' doesn't exist")).toBeTruthy();
35
+ expect(getByText('Go to homepage')).toBeTruthy();
36
+ });
37
+ it('"Go to homepage" mounts the app again instead of navigating a navigator that is gone', () => {
38
+ const { getByText, queryByText } = render(React.createElement(ErrorBoundary, null,
39
+ React.createElement(Screen, null)));
40
+ crash = false;
41
+ fireEvent.press(getByText('Go to homepage'));
42
+ expect(getByText('Home')).toBeTruthy();
43
+ expect(queryByText("There's an error")).toBeNull();
44
+ expect(mockNavigate).not.toHaveBeenCalled();
45
+ });
46
+ });
47
+ //# sourceMappingURL=ErrorBoundary.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/ErrorBoundary.test.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,YAAY,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;AAC/B,IAAI,CAAC,IAAI,CAAC,4BAA4B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3C,aAAa,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE;CACvE,CAAC,CAAC,CAAC;AACJ,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAC5F,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACnC,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,IAAI,KAAK,GAAG,IAAI,CAAC;AACjB,SAAS,MAAM;IACX,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACrE,OAAO,oBAAC,IAAI,eAAY,CAAC;AAC7B,CAAC;AAED,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACZ,KAAK,GAAG,IAAI,CAAC;QACb,YAAY,CAAC,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QAC/C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CACxB,oBAAC,aAAa;YACV,oBAAC,MAAM,OAAG,CACE,CACnB,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,wCAAwC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;QACzE,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC5F,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,CACrC,oBAAC,aAAa;YACV,oBAAC,MAAM,OAAG,CACE,CACnB,CAAC;QACF,KAAK,GAAG,KAAK,CAAC;QACd,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;QACvC,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACnD,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAChD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@admin-layout/gluestack-ui-mobile",
3
- "version": "12.2.5-alpha.15",
3
+ "version": "12.2.5-alpha.17",
4
4
  "description": "Glustack-ui for mobile component layout",
5
5
  "license": "ISC",
6
6
  "author": "CDMBase LLC",
@@ -50,5 +50,5 @@
50
50
  "typescript": {
51
51
  "definition": "lib/index.d.ts"
52
52
  },
53
- "gitHead": "d82d5b9a4f67eb3aa67b6b3aa13501e07d5acf3b"
53
+ "gitHead": "c9eb00d88ad357e72bd1fc58b67081a434476dbe"
54
54
  }
@@ -1,7 +1,6 @@
1
1
  import React, { ReactElement } from 'react';
2
2
  import { FallbackComponent } from './Fallback';
3
3
  import { logger } from '@cdm-logger/client';
4
- import { useDispatch } from 'react-redux';
5
4
 
6
5
  type IErrorBoundryState = { hasError: boolean; error: Error | null };
7
6
  type IErrorBoundryProps = {
@@ -18,13 +17,24 @@ export class ErrorBoundary extends React.Component<IErrorBoundryProps, IErrorBou
18
17
  return { hasError: true, error };
19
18
  }
20
19
 
21
- componentDidCatch(error: Error) {
22
- logger.debug(error);
20
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
21
+ logger.error(error, info?.componentStack ?? '');
23
22
  }
24
23
 
24
+ /**
25
+ * Mount the children again. The app's boundary wraps the navigation
26
+ * container itself, so while the fallback shows there is no navigator to
27
+ * navigate with: "Go to homepage" called navigate on nothing and the app
28
+ * stayed on this screen until it was force-quit. A fresh mount starts the
29
+ * navigator at its initial route.
30
+ */
31
+ resetError = () => {
32
+ this.setState({ hasError: false, error: null });
33
+ };
34
+
25
35
  render() {
26
36
  if (this.state.hasError) {
27
- return <FallbackComponent error={this.state.error as Error} />;
37
+ return <FallbackComponent error={this.state.error as Error} resetError={this.resetError} />;
28
38
  }
29
39
  return this.props.children;
30
40
  }
@@ -4,19 +4,28 @@ import * as React from 'react';
4
4
  import { StyleSheet } from 'react-native';
5
5
 
6
6
  import { SafeAreaView, Text, View } from 'react-native';
7
- import { useDispatch } from 'react-redux';
8
7
 
9
- type Props = { error: Error };
8
+ type Props = {
9
+ error: Error;
10
+ /** From the boundary that caught the error: mounts the app again. */
11
+ resetError?: () => void;
12
+ };
10
13
 
11
14
  export const FallbackComponent = (props: Props) => {
12
- const dispatch = useDispatch();
15
+ const goHome = () => {
16
+ if (props.resetError) {
17
+ props.resetError();
18
+ return;
19
+ }
20
+ navigationRef.navigate('MainStack.Layout.Home');
21
+ };
13
22
  return (
14
23
  <SafeAreaView style={styles.container}>
15
24
  <View style={styles.content}>
16
25
  <Text style={styles.title}>Oops!</Text>
17
26
  <Text style={styles.subtitle}>{"There's an error"}</Text>
18
27
  <Text style={styles.error}>{props?.error?.message}</Text>
19
- <Button style={styles.button} onPress={() => navigationRef.navigate('MainStack.Layout.Home')}>
28
+ <Button style={styles.button} onPress={goHome}>
20
29
  <ButtonText style={styles.buttonText}>Go to homepage</ButtonText>
21
30
  </Button>
22
31
  </View>
@@ -47,14 +56,18 @@ const styles: any = StyleSheet.create({
47
56
  error: {
48
57
  paddingVertical: 16,
49
58
  },
59
+ // A fixed height with no vertical padding: `padding: 25` inside
60
+ // `height: 40` left the label -10pt and it never showed.
50
61
  button: {
51
62
  backgroundColor: '#2196f3',
52
63
  borderRadius: 50,
53
- padding: 25,
54
- height: 40,
64
+ height: 52,
65
+ paddingVertical: 0,
66
+ paddingHorizontal: 24,
55
67
  },
56
68
  buttonText: {
57
69
  color: '#fff',
70
+ fontSize: 17,
58
71
  fontWeight: '600',
59
72
  textAlign: 'center',
60
73
  },
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The app-wide boundary wraps the navigation container. Live 2026-10-10: a
3
+ * screen threw, the fallback showed, and "Go to homepage" did nothing (its
4
+ * label did not even show) - the app was stuck until it was force-quit.
5
+ */
6
+ import React from 'react';
7
+ import { Text } from 'react-native';
8
+ import { fireEvent, render } from '@testing-library/react-native';
9
+ import { ErrorBoundary } from '../ErrorBounday';
10
+
11
+ const mockNavigate = jest.fn();
12
+ jest.mock('@common-stack/client-react', () => ({
13
+ navigationRef: { navigate: (...a: unknown[]) => mockNavigate(...a) },
14
+ }));
15
+ jest.mock('@cdm-logger/client', () => ({ logger: { error: jest.fn(), debug: jest.fn() } }));
16
+ jest.mock('@gluestack-ui/themed', () => {
17
+ const { Pressable: P, Text: T } = jest.requireActual('react-native');
18
+ return { Button: P, ButtonText: T };
19
+ });
20
+
21
+ let crash = true;
22
+ function Screen() {
23
+ if (crash) throw new Error("Property 'promoProgress' doesn't exist");
24
+ return <Text>Home</Text>;
25
+ }
26
+
27
+ describe('app error boundary', () => {
28
+ beforeEach(() => {
29
+ crash = true;
30
+ mockNavigate.mockClear();
31
+ jest.spyOn(console, 'error').mockImplementation(() => undefined);
32
+ });
33
+
34
+ it('shows the error and a labelled way home', () => {
35
+ const { getByText } = render(
36
+ <ErrorBoundary>
37
+ <Screen />
38
+ </ErrorBoundary>,
39
+ );
40
+ expect(getByText("Property 'promoProgress' doesn't exist")).toBeTruthy();
41
+ expect(getByText('Go to homepage')).toBeTruthy();
42
+ });
43
+
44
+ it('"Go to homepage" mounts the app again instead of navigating a navigator that is gone', () => {
45
+ const { getByText, queryByText } = render(
46
+ <ErrorBoundary>
47
+ <Screen />
48
+ </ErrorBoundary>,
49
+ );
50
+ crash = false;
51
+ fireEvent.press(getByText('Go to homepage'));
52
+ expect(getByText('Home')).toBeTruthy();
53
+ expect(queryByText("There's an error")).toBeNull();
54
+ expect(mockNavigate).not.toHaveBeenCalled();
55
+ });
56
+ });
package/babel.config.js DELETED
@@ -1,26 +0,0 @@
1
- module.exports = {
2
- compact: false,
3
- presets: [
4
- "module:metro-react-native-babel-preset",
5
- '@babel/preset-typescript',
6
- '@babel/preset-react',
7
- ['@babel/preset-env', { modules: 'commonjs', loose: true }],
8
- ],
9
- plugins: [
10
- '@babel/plugin-transform-modules-commonjs',
11
- '@babel/plugin-transform-destructuring',
12
- '@babel/plugin-transform-for-of',
13
- '@babel/plugin-transform-regenerator',
14
- '@babel/plugin-transform-runtime',
15
- '@babel/plugin-syntax-dynamic-import',
16
- '@babel/plugin-transform-flow-strip-types',
17
- '@babel/plugin-proposal-class-properties',
18
- ['@babel/plugin-proposal-decorators', { legacy: true }],
19
- '@babel/plugin-proposal-object-rest-spread',
20
- ],
21
- env: {
22
- production: {
23
- compact: true,
24
- },
25
- },
26
- };