@hubspot/ui-extensions 0.12.3 → 0.13.0
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/dist/__tests__/crm/hooks/useAssociations.spec.js +28 -0
- package/dist/__tests__/crm/utils/fetchAssociations.spec.js +2 -1
- package/dist/__tests__/hooks/useDebounce.spec.js +123 -0
- package/dist/__tests__/hooks/utils/useFetchLifecycle.spec.js +324 -0
- package/dist/__tests__/internal/hook-utils.spec.d.ts +1 -0
- package/dist/__tests__/internal/hook-utils.spec.js +17 -0
- package/dist/__tests__/test-d/extension-points.test-d.js +1 -0
- package/dist/crm/hooks/useAssociations.d.ts +4 -12
- package/dist/crm/hooks/useAssociations.js +46 -138
- package/dist/crm/hooks/useCrmProperties.js +29 -125
- package/dist/crm/utils/fetchAssociations.d.ts +0 -8
- package/dist/crm/utils/fetchAssociations.js +0 -10
- package/dist/hooks/useDebounce.d.ts +19 -0
- package/dist/hooks/useDebounce.js +32 -0
- package/dist/hooks/utils/useFetchLifecycle.d.ts +35 -0
- package/dist/hooks/utils/useFetchLifecycle.js +103 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/hook-utils.d.ts +13 -6
- package/dist/internal/hook-utils.js +24 -10
- package/dist/pages/components/index.d.ts +1 -0
- package/dist/pages/components/index.js +1 -0
- package/dist/{shared/types/pages → pages}/components/page-routes.d.ts +6 -39
- package/dist/pages/components/page-routes.js +62 -0
- package/dist/pages/create-page-router.d.ts +33 -0
- package/dist/pages/create-page-router.js +121 -0
- package/dist/pages/create-page-router.test.d.ts +1 -0
- package/dist/pages/create-page-router.test.js +296 -0
- package/dist/pages/hooks.d.ts +7 -0
- package/dist/pages/hooks.js +14 -0
- package/dist/pages/index.d.ts +6 -0
- package/dist/pages/index.js +4 -0
- package/dist/pages/internal/app-page-route-context.d.ts +16 -0
- package/dist/pages/internal/app-page-route-context.js +12 -0
- package/dist/pages/internal/convert-page-routes-react-elements.d.ts +9 -0
- package/dist/pages/internal/convert-page-routes-react-elements.js +138 -0
- package/dist/pages/internal/page-router-internal-types.d.ts +40 -0
- package/dist/pages/internal/page-router-internal-types.js +5 -0
- package/dist/pages/internal/trie-router.d.ts +31 -0
- package/dist/pages/internal/trie-router.js +141 -0
- package/dist/pages/internal/trie-router.test.d.ts +1 -0
- package/dist/pages/internal/trie-router.test.js +263 -0
- package/dist/pages/internal/useAppPageLocation.d.ts +1 -0
- package/dist/pages/internal/useAppPageLocation.js +13 -0
- package/dist/pages/types.d.ts +28 -0
- package/dist/pages/types.js +1 -0
- package/dist/shared/remoteComponents.d.ts +24 -0
- package/dist/shared/remoteComponents.js +28 -0
- package/dist/shared/types/actions.d.ts +12 -2
- package/dist/shared/types/components/button.d.ts +2 -2
- package/dist/shared/types/components/image.d.ts +2 -2
- package/dist/shared/types/components/inputs.d.ts +7 -1
- package/dist/shared/types/components/link.d.ts +2 -2
- package/dist/shared/types/context.d.ts +5 -0
- package/dist/shared/types/crm.d.ts +4 -0
- package/dist/shared/types/extension-points.d.ts +9 -3
- package/dist/shared/types/extension-points.js +1 -0
- package/dist/shared/types/pages/components/index.d.ts +3 -1
- package/dist/shared/types/pages/components/page-breadcrumbs.d.ts +34 -0
- package/dist/shared/types/pages/components/page-breadcrumbs.js +1 -0
- package/dist/shared/types/pages/components/page-header.d.ts +82 -0
- package/dist/shared/types/pages/components/page-header.js +1 -0
- package/dist/shared/types/pages/components/page-link.d.ts +4 -2
- package/dist/shared/types/pages/components/page-title.d.ts +12 -0
- package/dist/shared/types/pages/components/page-title.js +1 -0
- package/dist/shared/types/shared.d.ts +8 -0
- package/dist/shared/types/worker-globals.d.ts +3 -12
- package/dist/testing/__tests__/createRenderer.spec.js +1 -1
- package/dist/testing/internal/mocks/index.d.ts +14 -6
- package/dist/testing/internal/mocks/index.js +19 -5
- package/dist/testing/internal/mocks/mock-app-page-location.d.ts +7 -0
- package/dist/testing/internal/mocks/mock-app-page-location.js +35 -0
- package/dist/testing/internal/mocks/mock-extension-point-api.js +21 -1
- package/dist/testing/internal/mocks/mock-hooks.js +12 -7
- package/dist/testing/render.js +7 -6
- package/dist/testing/types.d.ts +19 -3
- package/dist/utils/pagination.d.ts +17 -0
- package/dist/utils/pagination.js +10 -0
- package/package.json +2 -2
- package/dist/experimental/pages/index.d.ts +0 -4
- package/dist/experimental/pages/index.js +0 -3
- package/dist/shared/types/pages/app-pages-types.d.ts +0 -75
- /package/dist/{shared/types/pages/app-pages-types.js → __tests__/hooks/useDebounce.spec.d.ts} +0 -0
- /package/dist/{shared/types/pages/components/page-routes.js → __tests__/hooks/utils/useFetchLifecycle.spec.d.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
2
|
-
const
|
|
1
|
+
import { createContext, useContext, useRef } from 'react';
|
|
2
|
+
const ReactRenderMocksContext = createContext(null);
|
|
3
3
|
/**
|
|
4
4
|
* Creates a mock-aware hook function that can be used to mock the original hook function.
|
|
5
5
|
* The mock-aware hook function will return the mocked hook function if a mock is found, otherwise it will return the original hook function.
|
|
@@ -10,29 +10,28 @@ const MocksContext = createContext(null);
|
|
|
10
10
|
*/
|
|
11
11
|
export const createMockAwareHook = (hookName, originalHookFunction) => {
|
|
12
12
|
const useWrapper = (...args) => {
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
13
|
+
const mocksContext = useMocksContext();
|
|
14
|
+
if (!mocksContext) {
|
|
15
15
|
// If no mocks are provided, call the original hook function
|
|
16
16
|
return originalHookFunction(...args);
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
const { mocks } = mocksContext;
|
|
19
19
|
const mockHook = mocks[hookName];
|
|
20
20
|
if (!mockHook) {
|
|
21
21
|
throw new Error(`Illegal State: Mock for hook ${hookName} not found.`);
|
|
22
22
|
}
|
|
23
|
-
// Call the mocked hook function with the same arguments as the original hook function and return the result
|
|
24
23
|
return mockHook(...args);
|
|
25
24
|
};
|
|
26
25
|
return useWrapper;
|
|
27
26
|
};
|
|
28
27
|
/**
|
|
29
28
|
* A hook that provides access to the Mocks context.
|
|
30
|
-
* Returns the mocks
|
|
29
|
+
* Returns the mocks context value if inside a MocksContextProvider, otherwise returns null.
|
|
31
30
|
*
|
|
32
|
-
* @returns The mocks
|
|
31
|
+
* @returns The mocks context value or null if not in a test environment.
|
|
33
32
|
*/
|
|
34
33
|
export function useMocksContext() {
|
|
35
|
-
return useContext(
|
|
34
|
+
return useContext(ReactRenderMocksContext);
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
37
|
* A React component that provides the Mocks context that can be used to provide mocks to the mock-aware hook functions.
|
|
@@ -40,4 +39,19 @@ export function useMocksContext() {
|
|
|
40
39
|
* @param children The children to render.
|
|
41
40
|
* @returns The children wrapped in the Mocks context provider.
|
|
42
41
|
*/
|
|
43
|
-
export const MocksContextProvider =
|
|
42
|
+
export const MocksContextProvider = ReactRenderMocksContext.Provider;
|
|
43
|
+
/**
|
|
44
|
+
* Stabilizes a value's reference identity across re-renders using deep
|
|
45
|
+
* comparison via JSON.stringify.
|
|
46
|
+
*/
|
|
47
|
+
export function useStableValue(value) {
|
|
48
|
+
const stableRef = useRef({
|
|
49
|
+
key: JSON.stringify(value),
|
|
50
|
+
value,
|
|
51
|
+
});
|
|
52
|
+
const key = JSON.stringify(value);
|
|
53
|
+
if (key !== stableRef.current.key) {
|
|
54
|
+
stableRef.current = { key, value };
|
|
55
|
+
}
|
|
56
|
+
return stableRef.current.value;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './page-routes.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./page-routes.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import type { EmptyProps } from '../../shared.ts';
|
|
2
|
+
import type { EmptyProps } from '../../shared/types/shared.ts';
|
|
3
3
|
/**
|
|
4
4
|
* The props type for the layout component of [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes).
|
|
5
5
|
*/
|
|
@@ -72,44 +72,11 @@ export interface PageRoutesProps {
|
|
|
72
72
|
*
|
|
73
73
|
* See [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes) for more information.
|
|
74
74
|
*
|
|
75
|
-
* @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
76
75
|
*/
|
|
77
|
-
export
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* Example usage:
|
|
83
|
-
*
|
|
84
|
-
* ```tsx
|
|
85
|
-
* <PageRoutes.IndexRoute component={HomePage} />
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
89
|
-
*/
|
|
90
|
-
IndexRoute: (props: IndexRouteProps) => null;
|
|
91
|
-
/**
|
|
92
|
-
* Used as a descriptor for a route with a path pattern.
|
|
93
|
-
*
|
|
94
|
-
* Example usage:
|
|
95
|
-
*
|
|
96
|
-
* ```tsx
|
|
97
|
-
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
98
|
-
* ```
|
|
99
|
-
* @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
100
|
-
*/
|
|
101
|
-
Route: (props: RouteProps) => null;
|
|
102
|
-
/**
|
|
103
|
-
* Used as a descriptor for a route that matches any path.
|
|
104
|
-
*
|
|
105
|
-
* Example usage:
|
|
106
|
-
*
|
|
107
|
-
* ```tsx
|
|
108
|
-
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
109
|
-
* ```
|
|
110
|
-
*
|
|
111
|
-
* @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
112
|
-
*/
|
|
113
|
-
AnyRoute: (props: AnyRouteProps) => null;
|
|
76
|
+
export declare function PageRoutes(__props: PageRoutesProps): null;
|
|
77
|
+
export declare namespace PageRoutes {
|
|
78
|
+
var IndexRoute: (__props: IndexRouteProps) => null;
|
|
79
|
+
var Route: (__props: RouteProps) => null;
|
|
80
|
+
var AnyRoute: (__props: AnyRouteProps) => null;
|
|
114
81
|
}
|
|
115
82
|
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class PageRoutesRenderError extends Error {
|
|
2
|
+
constructor(componentName) {
|
|
3
|
+
super(`<${componentName}> should not be rendered directly. Use createPageRouter instead.`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Used as a descriptor for a collection of page routes.
|
|
8
|
+
*
|
|
9
|
+
* Example usage:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const PageRouter = createPageRouter(<PageRoutes>
|
|
13
|
+
* <PageRoutes.IndexRoute component={HomePage} />
|
|
14
|
+
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
15
|
+
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
16
|
+
* </PageRoutes>);
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* See [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes) for more information.
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
export function PageRoutes(__props) {
|
|
23
|
+
throw new PageRoutesRenderError('PageRoutes');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Used as a descriptor for an index (i.e., "/") route.
|
|
27
|
+
*
|
|
28
|
+
* Example usage:
|
|
29
|
+
*
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <PageRoutes.IndexRoute component={HomePage} />
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
PageRoutes.IndexRoute = function IndexRoute(__props) {
|
|
36
|
+
throw new PageRoutesRenderError('PageRoutes.IndexRoute');
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Used as a descriptor for a route with a path pattern.
|
|
40
|
+
*
|
|
41
|
+
* Example usage:
|
|
42
|
+
*
|
|
43
|
+
* ```tsx
|
|
44
|
+
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
PageRoutes.Route = function Route(__props) {
|
|
48
|
+
throw new PageRoutesRenderError('PageRoutes.Route');
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Used as a descriptor for a route that matches any path.
|
|
52
|
+
*
|
|
53
|
+
* Example usage:
|
|
54
|
+
*
|
|
55
|
+
* ```tsx
|
|
56
|
+
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
*/
|
|
60
|
+
PageRoutes.AnyRoute = function AnyRoute(__props) {
|
|
61
|
+
throw new PageRoutesRenderError('PageRoutes.AnyRoute');
|
|
62
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { ReactFragmentProps } from '../shared/types/index.ts';
|
|
3
|
+
import type { PageRoutesProps } from './components/page-routes.ts';
|
|
4
|
+
import type { PageRouterComponent } from './types.ts';
|
|
5
|
+
/**
|
|
6
|
+
* The function type for [createPageRouter](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#createpagerouter).
|
|
7
|
+
*
|
|
8
|
+
* Example usage:
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const PageRouter = createPageRouter(
|
|
12
|
+
* <PageRoutes layoutComponent={AppLayout}>
|
|
13
|
+
* <PageRoutes.IndexRoute component={HomePage} id="home" />
|
|
14
|
+
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
15
|
+
* <PageRoutes path="/customers" layoutComponent={CustomersLayout}>
|
|
16
|
+
* <PageRoutes.IndexRoute component={ListCustomersPage} />
|
|
17
|
+
* <PageRoutes.Route path="/:customerId" component={ViewCustomerPage} />
|
|
18
|
+
* </PageRoutes>
|
|
19
|
+
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
20
|
+
* </PageRoutes>
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* function AppPages() {
|
|
24
|
+
* return <PageRouter />;
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* hubspot.extend<"pages">(() => <AppPages />);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param routes The routes to render.
|
|
31
|
+
* @returns The page router component.
|
|
32
|
+
*/
|
|
33
|
+
export declare const createPageRouter: (reactPageRoutesElement: ReactElement<PageRoutesProps> | ReactElement<ReactFragmentProps>) => PageRouterComponent;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { EmptyState, Text } from "../shared/remoteComponents.js";
|
|
4
|
+
import { AppPageRouteProvider } from "./internal/app-page-route-context.js";
|
|
5
|
+
import { convertReactPageRoutesElement } from "./internal/convert-page-routes-react-elements.js";
|
|
6
|
+
import { RouteNodeType, } from "./internal/page-router-internal-types.js";
|
|
7
|
+
import { createTrieRouter } from "./internal/trie-router.js";
|
|
8
|
+
import { useAppPageLocation } from "./internal/useAppPageLocation.js";
|
|
9
|
+
/**
|
|
10
|
+
* Adds the routes to the trie router.
|
|
11
|
+
*
|
|
12
|
+
* @param trieRouter - The trie router to add the routes to.
|
|
13
|
+
* @param seenRouteIds - The set of route ids that have already been seen.
|
|
14
|
+
* @param routeNode - The node to add the routes to.
|
|
15
|
+
* @param parentPath - The path to the parent node.
|
|
16
|
+
* @param parentLayouts - The layouts to apply to the node.
|
|
17
|
+
*/
|
|
18
|
+
const addRoutes = (trieRouter, seenRouteIds, routeNode, parentPath, parentLayouts) => {
|
|
19
|
+
if (routeNode.type === RouteNodeType.Routes) {
|
|
20
|
+
const prefix = parentPath + (routeNode.path ?? '');
|
|
21
|
+
const layouts = routeNode.layoutComponent
|
|
22
|
+
? [...parentLayouts, routeNode.layoutComponent]
|
|
23
|
+
: parentLayouts;
|
|
24
|
+
if (routeNode.children) {
|
|
25
|
+
for (const childRouteNode of routeNode.children) {
|
|
26
|
+
addRoutes(trieRouter, seenRouteIds, childRouteNode /* routeNode */, prefix /* parentPath */, layouts /* parentLayouts */);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const fullPath = parentPath + routeNode.path;
|
|
32
|
+
if (routeNode.id !== undefined) {
|
|
33
|
+
if (seenRouteIds.has(routeNode.id)) {
|
|
34
|
+
throw new Error(`Duplicate route id: "${routeNode.id}"`);
|
|
35
|
+
}
|
|
36
|
+
seenRouteIds.add(routeNode.id);
|
|
37
|
+
}
|
|
38
|
+
trieRouter.addRoute(fullPath, {
|
|
39
|
+
component: routeNode.component,
|
|
40
|
+
layouts: parentLayouts,
|
|
41
|
+
id: routeNode.id,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The function type for [createPageRouter](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#createpagerouter).
|
|
47
|
+
*
|
|
48
|
+
* Example usage:
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* const PageRouter = createPageRouter(
|
|
52
|
+
* <PageRoutes layoutComponent={AppLayout}>
|
|
53
|
+
* <PageRoutes.IndexRoute component={HomePage} id="home" />
|
|
54
|
+
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
55
|
+
* <PageRoutes path="/customers" layoutComponent={CustomersLayout}>
|
|
56
|
+
* <PageRoutes.IndexRoute component={ListCustomersPage} />
|
|
57
|
+
* <PageRoutes.Route path="/:customerId" component={ViewCustomerPage} />
|
|
58
|
+
* </PageRoutes>
|
|
59
|
+
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
60
|
+
* </PageRoutes>
|
|
61
|
+
* );
|
|
62
|
+
*
|
|
63
|
+
* function AppPages() {
|
|
64
|
+
* return <PageRouter />;
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* hubspot.extend<"pages">(() => <AppPages />);
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param routes The routes to render.
|
|
71
|
+
* @returns The page router component.
|
|
72
|
+
*/
|
|
73
|
+
export const createPageRouter = (reactPageRoutesElement) => {
|
|
74
|
+
// Convert the React element to a route node and add them to a trie router.
|
|
75
|
+
const rootRouteNode = convertReactPageRoutesElement(reactPageRoutesElement);
|
|
76
|
+
const trieRouter = createTrieRouter();
|
|
77
|
+
const seenRouteIds = new Set();
|
|
78
|
+
addRoutes(trieRouter, seenRouteIds, rootRouteNode /* routeNode */, '' /* parentPath */, [] /* parentLayouts */);
|
|
79
|
+
// Now create the PageRouter component that uses the trie router to match the current app page location.
|
|
80
|
+
// The component will render the content of the matched route.
|
|
81
|
+
// The component will also provide the matched app page route to the content of the matched route.
|
|
82
|
+
const PageRouter = () => {
|
|
83
|
+
const appPageLocation = useAppPageLocation();
|
|
84
|
+
const { path: appPagePath, params: appPageParams } = appPageLocation;
|
|
85
|
+
const matchedRoute = useMemo(() => {
|
|
86
|
+
return trieRouter.matchPath(appPagePath);
|
|
87
|
+
}, [appPagePath]);
|
|
88
|
+
const matchedAppPageRoute = useMemo(() => {
|
|
89
|
+
return matchedRoute
|
|
90
|
+
? {
|
|
91
|
+
routeId: matchedRoute.data.id,
|
|
92
|
+
path: appPagePath,
|
|
93
|
+
params: {
|
|
94
|
+
...matchedRoute.params,
|
|
95
|
+
...appPageParams,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
: {
|
|
99
|
+
path: appPagePath,
|
|
100
|
+
params: appPageParams,
|
|
101
|
+
};
|
|
102
|
+
}, [matchedRoute, appPagePath, appPageParams]);
|
|
103
|
+
const routeData = matchedRoute?.data;
|
|
104
|
+
const content = useMemo(() => {
|
|
105
|
+
if (!routeData) {
|
|
106
|
+
// TODO: Localize the title and body text.
|
|
107
|
+
return (_jsx(EmptyState, { title: "Page not found", layout: "vertical", reverseOrder: true, children: _jsx(Text, { children: "This app page does not exist." }) }));
|
|
108
|
+
}
|
|
109
|
+
const Component = routeData.component;
|
|
110
|
+
let node = _jsx(Component, {});
|
|
111
|
+
// Wrap the route component with any ancestor layout components found in the route tree.
|
|
112
|
+
for (let i = routeData.layouts.length - 1; i >= 0; i--) {
|
|
113
|
+
const Layout = routeData.layouts[i];
|
|
114
|
+
node = _jsx(Layout, { children: node });
|
|
115
|
+
}
|
|
116
|
+
return node;
|
|
117
|
+
}, [routeData]);
|
|
118
|
+
return (_jsx(AppPageRouteProvider, { pageRoute: matchedAppPageRoute, children: content }));
|
|
119
|
+
};
|
|
120
|
+
return PageRouter;
|
|
121
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { Box, Text } from "../index.js";
|
|
4
|
+
import { createRenderer } from "../testing/index.js";
|
|
5
|
+
import { PageRoutes } from "./components/page-routes.js";
|
|
6
|
+
import { createPageRouter } from "./create-page-router.js";
|
|
7
|
+
import { usePageRoute } from "./hooks.js";
|
|
8
|
+
const HomePage = () => _jsx(Text, { testId: "page", children: "home" });
|
|
9
|
+
const DocsPage = () => _jsx(Text, { testId: "page", children: "docs" });
|
|
10
|
+
const SupportIndexPage = () => _jsx(Text, { testId: "page", children: "support-index" });
|
|
11
|
+
const ContactUsPage = () => _jsx(Text, { testId: "page", children: "contact-us" });
|
|
12
|
+
const FAQPage = () => _jsx(Text, { testId: "page", children: "faq" });
|
|
13
|
+
const NotFoundPage = () => _jsx(Text, { testId: "page", children: "not-found-custom" });
|
|
14
|
+
const RouteInfoDisplay = () => {
|
|
15
|
+
const { routeId, path, params } = usePageRoute();
|
|
16
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { testId: "route-id", children: routeId ?? '' }), _jsx(Text, { testId: "route-path", children: path }), _jsx(Text, { testId: "route-params", children: JSON.stringify(params) })] }));
|
|
17
|
+
};
|
|
18
|
+
const ContactDetailsPage = () => (_jsxs(_Fragment, { children: [_jsx(Text, { testId: "page", children: "contact-details" }), _jsx(RouteInfoDisplay, {})] }));
|
|
19
|
+
describe('createPageRouter', () => {
|
|
20
|
+
describe('basic routing', () => {
|
|
21
|
+
it('renders the index route at the default location', () => {
|
|
22
|
+
const { render, findByTestId } = createRenderer('home');
|
|
23
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage })] }));
|
|
24
|
+
render(_jsx(PageRouter, {}));
|
|
25
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
26
|
+
});
|
|
27
|
+
it('renders a named route when path matches', () => {
|
|
28
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
29
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
30
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage })] }));
|
|
31
|
+
render(_jsx(PageRouter, {}));
|
|
32
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
33
|
+
});
|
|
34
|
+
it('shows "Page not found" when no route matches', () => {
|
|
35
|
+
const { mocks, render, find } = createRenderer('home');
|
|
36
|
+
mocks.setPageLocation({ path: '/unknown', params: {} });
|
|
37
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.IndexRoute, { component: HomePage }) }));
|
|
38
|
+
render(_jsx(PageRouter, {}));
|
|
39
|
+
expect(find(Text, (node) => node.text === 'This app page does not exist.')).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('route exclusivity', () => {
|
|
43
|
+
it('renders only the index route and not other routes at the default location', () => {
|
|
44
|
+
const { render, findAll, findByTestId, maybeFind } = createRenderer('home');
|
|
45
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }), _jsx(PageRoutes.Route, { path: "/support", component: SupportIndexPage })] }));
|
|
46
|
+
render(_jsx(PageRouter, {}));
|
|
47
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
48
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
49
|
+
expect(maybeFind(Text, (node) => node.text === 'docs')).toBeNull();
|
|
50
|
+
expect(maybeFind(Text, (node) => node.text === 'support-index')).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
it('renders only the matched named route and not the index or other routes', () => {
|
|
53
|
+
const { mocks, render, findAll, findByTestId, maybeFind } = createRenderer('home');
|
|
54
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
55
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }), _jsx(PageRoutes.Route, { path: "/support", component: SupportIndexPage })] }));
|
|
56
|
+
render(_jsx(PageRouter, {}));
|
|
57
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
58
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
59
|
+
expect(maybeFind(Text, (node) => node.text === 'home')).toBeNull();
|
|
60
|
+
expect(maybeFind(Text, (node) => node.text === 'support-index')).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
it('renders only the matched nested route and not sibling or parent-level routes', () => {
|
|
63
|
+
const { mocks, render, findAll, findByTestId, maybeFind } = createRenderer('home');
|
|
64
|
+
mocks.setPageLocation({ path: '/support/faq', params: {} });
|
|
65
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsxs(PageRoutes, { path: "/support", children: [_jsx(PageRoutes.IndexRoute, { component: SupportIndexPage }), _jsx(PageRoutes.Route, { path: "/contact-us", component: ContactUsPage }), _jsx(PageRoutes.Route, { path: "/faq", component: FAQPage })] })] }));
|
|
66
|
+
render(_jsx(PageRouter, {}));
|
|
67
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
68
|
+
expect(findByTestId(Text, 'page').text).toEqual('faq');
|
|
69
|
+
expect(maybeFind(Text, (node) => node.text === 'home')).toBeNull();
|
|
70
|
+
expect(maybeFind(Text, (node) => node.text === 'support-index')).toBeNull();
|
|
71
|
+
expect(maybeFind(Text, (node) => node.text === 'contact-us')).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
it('renders only one route at a time when location changes', () => {
|
|
74
|
+
const { mocks, render, findAll, findByTestId, maybeFind } = createRenderer('home');
|
|
75
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }), _jsx(PageRoutes.Route, { path: "/support", component: SupportIndexPage })] }));
|
|
76
|
+
render(_jsx(PageRouter, {}));
|
|
77
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
78
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
79
|
+
expect(maybeFind(Text, (node) => node.text === 'docs')).toBeNull();
|
|
80
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
81
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
82
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
83
|
+
expect(maybeFind(Text, (node) => node.text === 'home')).toBeNull();
|
|
84
|
+
mocks.setPageLocation({ path: '/support', params: {} });
|
|
85
|
+
expect(findAll(Text, { testId: 'page' })).toHaveLength(1);
|
|
86
|
+
expect(findByTestId(Text, 'page').text).toEqual('support-index');
|
|
87
|
+
expect(maybeFind(Text, (node) => node.text === 'docs')).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('nested routes', () => {
|
|
91
|
+
const createNestedRouter = () => createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsxs(PageRoutes, { path: "/support", children: [_jsx(PageRoutes.IndexRoute, { component: SupportIndexPage }), _jsx(PageRoutes.Route, { path: "/contact-us", component: ContactUsPage }), _jsx(PageRoutes.Route, { path: "/faq", component: FAQPage })] })] }));
|
|
92
|
+
it('renders nested index route', () => {
|
|
93
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
94
|
+
mocks.setPageLocation({ path: '/support', params: {} });
|
|
95
|
+
const PageRouter = createNestedRouter();
|
|
96
|
+
render(_jsx(PageRouter, {}));
|
|
97
|
+
expect(findByTestId(Text, 'page').text).toEqual('support-index');
|
|
98
|
+
});
|
|
99
|
+
it('renders nested named route', () => {
|
|
100
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
101
|
+
mocks.setPageLocation({ path: '/support/contact-us', params: {} });
|
|
102
|
+
const PageRouter = createNestedRouter();
|
|
103
|
+
render(_jsx(PageRouter, {}));
|
|
104
|
+
expect(findByTestId(Text, 'page').text).toEqual('contact-us');
|
|
105
|
+
});
|
|
106
|
+
it('renders a different nested named route', () => {
|
|
107
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
108
|
+
mocks.setPageLocation({ path: '/support/faq', params: {} });
|
|
109
|
+
const PageRouter = createNestedRouter();
|
|
110
|
+
render(_jsx(PageRouter, {}));
|
|
111
|
+
expect(findByTestId(Text, 'page').text).toEqual('faq');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('path parameters', () => {
|
|
115
|
+
it('extracts path params from the route pattern', () => {
|
|
116
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
117
|
+
mocks.setPageLocation({ path: '/contacts/abc-123', params: {} });
|
|
118
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.Route, { path: "/contacts/:contactId", component: ContactDetailsPage }) }));
|
|
119
|
+
render(_jsx(PageRouter, {}));
|
|
120
|
+
expect(findByTestId(Text, 'page').text).toEqual('contact-details');
|
|
121
|
+
expect(findByTestId(Text, 'route-params').text).toEqual('{"contactId":"abc-123"}');
|
|
122
|
+
});
|
|
123
|
+
it('merges path params with appPageLocation params', () => {
|
|
124
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
125
|
+
mocks.setPageLocation({
|
|
126
|
+
path: '/contacts/abc-123',
|
|
127
|
+
params: { tab: 'activity' },
|
|
128
|
+
});
|
|
129
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.Route, { path: "/contacts/:contactId", component: ContactDetailsPage }) }));
|
|
130
|
+
render(_jsx(PageRouter, {}));
|
|
131
|
+
expect(findByTestId(Text, 'route-params').text).toEqual('{"contactId":"abc-123","tab":"activity"}');
|
|
132
|
+
});
|
|
133
|
+
it('gives appPageLocation params precedence over path params on conflict', () => {
|
|
134
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
135
|
+
mocks.setPageLocation({
|
|
136
|
+
path: '/contacts/from-path',
|
|
137
|
+
params: { contactId: 'from-query' },
|
|
138
|
+
});
|
|
139
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.Route, { path: "/contacts/:contactId", component: ContactDetailsPage }) }));
|
|
140
|
+
render(_jsx(PageRouter, {}));
|
|
141
|
+
expect(findByTestId(Text, 'route-params').text).toEqual('{"contactId":"from-query"}');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('layout components', () => {
|
|
145
|
+
it('wraps matched route content in the layout component', () => {
|
|
146
|
+
const { render, findByTestId } = createRenderer('home');
|
|
147
|
+
const AppLayout = ({ children }) => (_jsx(Box, { testId: "layout-app", children: children }));
|
|
148
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { layoutComponent: AppLayout, children: _jsx(PageRoutes.IndexRoute, { component: HomePage }) }));
|
|
149
|
+
render(_jsx(PageRouter, {}));
|
|
150
|
+
expect(findByTestId(Box, 'layout-app')).toBeTruthy();
|
|
151
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
152
|
+
});
|
|
153
|
+
it('nests layout components from outer to inner', () => {
|
|
154
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
155
|
+
const OuterLayout = ({ children }) => (_jsx(Box, { testId: "layout-outer", children: children }));
|
|
156
|
+
const InnerLayout = ({ children }) => (_jsx(Box, { testId: "layout-inner", children: children }));
|
|
157
|
+
mocks.setPageLocation({ path: '/section/page-a', params: {} });
|
|
158
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { layoutComponent: OuterLayout, children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes, { path: "/section", layoutComponent: InnerLayout, children: _jsx(PageRoutes.Route, { path: "/page-a", component: DocsPage }) })] }));
|
|
159
|
+
render(_jsx(PageRouter, {}));
|
|
160
|
+
const outerLayout = findByTestId(Box, 'layout-outer');
|
|
161
|
+
const innerLayout = outerLayout.find(Box, { testId: 'layout-inner' });
|
|
162
|
+
expect(outerLayout).toBeTruthy();
|
|
163
|
+
expect(innerLayout).toBeTruthy();
|
|
164
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('catch-all and wildcard routes', () => {
|
|
168
|
+
it('renders AnyRoute for unmatched paths', () => {
|
|
169
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
170
|
+
mocks.setPageLocation({ path: '/does-not-exist', params: {} });
|
|
171
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }), _jsx(PageRoutes.AnyRoute, { component: NotFoundPage })] }));
|
|
172
|
+
render(_jsx(PageRouter, {}));
|
|
173
|
+
expect(findByTestId(Text, 'page').text).toEqual('not-found-custom');
|
|
174
|
+
});
|
|
175
|
+
it('renders the wildcard route and captures the remaining path', () => {
|
|
176
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
177
|
+
mocks.setPageLocation({
|
|
178
|
+
path: '/files/documents/report.pdf',
|
|
179
|
+
params: {},
|
|
180
|
+
});
|
|
181
|
+
const WildcardDisplay = () => {
|
|
182
|
+
const { params } = usePageRoute();
|
|
183
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { testId: "page", children: "file-browser" }), _jsx(Text, { testId: "wildcard", children: params['*'] })] }));
|
|
184
|
+
};
|
|
185
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/files/*", component: WildcardDisplay })] }));
|
|
186
|
+
render(_jsx(PageRouter, {}));
|
|
187
|
+
expect(findByTestId(Text, 'page').text).toEqual('file-browser');
|
|
188
|
+
expect(findByTestId(Text, 'wildcard').text).toEqual('documents/report.pdf');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe('route IDs', () => {
|
|
192
|
+
it('provides routeId via usePageRoute when route has an id', () => {
|
|
193
|
+
const { render, findByTestId } = createRenderer('home');
|
|
194
|
+
const PageWithRouteInfo = () => _jsx(RouteInfoDisplay, {});
|
|
195
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { id: "home", component: PageWithRouteInfo }), _jsx(PageRoutes.Route, { id: "docs", path: "/docs", component: PageWithRouteInfo })] }));
|
|
196
|
+
render(_jsx(PageRouter, {}));
|
|
197
|
+
expect(findByTestId(Text, 'route-id').text).toEqual('home');
|
|
198
|
+
});
|
|
199
|
+
it('provides the correct routeId for a named route', () => {
|
|
200
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
201
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
202
|
+
const PageWithRouteInfo = () => _jsx(RouteInfoDisplay, {});
|
|
203
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { id: "home", component: PageWithRouteInfo }), _jsx(PageRoutes.Route, { id: "docs", path: "/docs", component: PageWithRouteInfo })] }));
|
|
204
|
+
render(_jsx(PageRouter, {}));
|
|
205
|
+
expect(findByTestId(Text, 'route-id').text).toEqual('docs');
|
|
206
|
+
});
|
|
207
|
+
it('throws on duplicate route IDs', () => {
|
|
208
|
+
expect(() => createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { id: "same-id", component: HomePage }), _jsx(PageRoutes.Route, { id: "same-id", path: "/other", component: DocsPage })] }))).toThrowError('Duplicate route id: "same-id"');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe('location reactivity', () => {
|
|
212
|
+
it('re-renders with new content when location changes after mount', () => {
|
|
213
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
214
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }), _jsx(PageRoutes.Route, { path: "/support", component: SupportIndexPage })] }));
|
|
215
|
+
render(_jsx(PageRouter, {}));
|
|
216
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
217
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
218
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
219
|
+
mocks.setPageLocation({ path: '/support', params: {} });
|
|
220
|
+
expect(findByTestId(Text, 'page').text).toEqual('support-index');
|
|
221
|
+
});
|
|
222
|
+
it('updates route params when location params change', () => {
|
|
223
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
224
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.Route, { path: "/contacts/:contactId", component: ContactDetailsPage }) }));
|
|
225
|
+
mocks.setPageLocation({ path: '/contacts/111', params: {} });
|
|
226
|
+
render(_jsx(PageRouter, {}));
|
|
227
|
+
expect(findByTestId(Text, 'route-params').text).toEqual('{"contactId":"111"}');
|
|
228
|
+
mocks.setPageLocation({ path: '/contacts/222', params: {} });
|
|
229
|
+
expect(findByTestId(Text, 'route-params').text).toEqual('{"contactId":"222"}');
|
|
230
|
+
});
|
|
231
|
+
it('transitions from a matched route to "Page not found"', () => {
|
|
232
|
+
const { mocks, render, findByTestId, find } = createRenderer('home');
|
|
233
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: _jsx(PageRoutes.IndexRoute, { component: HomePage }) }));
|
|
234
|
+
render(_jsx(PageRouter, {}));
|
|
235
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
236
|
+
mocks.setPageLocation({ path: '/missing', params: {} });
|
|
237
|
+
expect(find(Text, (node) => node.text === 'This app page does not exist.')).toBeTruthy();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe('conditional routes', () => {
|
|
241
|
+
it('excludes a route when condition is false', () => {
|
|
242
|
+
const { mocks, render, find } = createRenderer('home');
|
|
243
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
244
|
+
const docsEnabled = false;
|
|
245
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), docsEnabled && (_jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }))] }));
|
|
246
|
+
render(_jsx(PageRouter, {}));
|
|
247
|
+
expect(find(Text, (node) => node.text === 'This app page does not exist.')).toBeTruthy();
|
|
248
|
+
});
|
|
249
|
+
it('includes a route when condition is true', () => {
|
|
250
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
251
|
+
mocks.setPageLocation({ path: '/docs', params: {} });
|
|
252
|
+
const docsEnabled = true;
|
|
253
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), docsEnabled && (_jsx(PageRoutes.Route, { path: "/docs", component: DocsPage }))] }));
|
|
254
|
+
render(_jsx(PageRouter, {}));
|
|
255
|
+
expect(findByTestId(Text, 'page').text).toEqual('docs');
|
|
256
|
+
});
|
|
257
|
+
it('handles null children', () => {
|
|
258
|
+
const { render, findByTestId } = createRenderer('home');
|
|
259
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), null] }));
|
|
260
|
+
render(_jsx(PageRouter, {}));
|
|
261
|
+
expect(findByTestId(Text, 'page').text).toEqual('home');
|
|
262
|
+
});
|
|
263
|
+
it('skips conditional nested routes when falsy', () => {
|
|
264
|
+
const { mocks, render, find } = createRenderer('home');
|
|
265
|
+
mocks.setPageLocation({ path: '/support', params: {} });
|
|
266
|
+
const supportEnabled = false;
|
|
267
|
+
const PageRouter = createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), supportEnabled && (_jsxs(PageRoutes, { path: "/support", children: [_jsx(PageRoutes.IndexRoute, { component: SupportIndexPage }), _jsx(PageRoutes.Route, { path: "/contact-us", component: ContactUsPage })] }))] }));
|
|
268
|
+
render(_jsx(PageRouter, {}));
|
|
269
|
+
expect(find(Text, (node) => node.text === 'This app page does not exist.')).toBeTruthy();
|
|
270
|
+
});
|
|
271
|
+
it('supports routes generated from an array via .map()', () => {
|
|
272
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
273
|
+
mocks.setPageLocation({ path: '/support', params: {} });
|
|
274
|
+
const rootPages = {
|
|
275
|
+
'/': HomePage,
|
|
276
|
+
'/docs': DocsPage,
|
|
277
|
+
'/support': SupportIndexPage,
|
|
278
|
+
};
|
|
279
|
+
const PageRouter = createPageRouter(_jsx(PageRoutes, { children: Object.entries(rootPages).map(([path, PageComponent]) => (_jsx(PageRoutes.Route, { path: path, component: PageComponent }, path))) }));
|
|
280
|
+
render(_jsx(PageRouter, {}));
|
|
281
|
+
expect(findByTestId(Text, 'page').text).toEqual('support-index');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
describe('invalid routes', () => {
|
|
285
|
+
it('throws for an invalid top-level element', () => {
|
|
286
|
+
expect(() => createPageRouter(_jsx("div", {}))).toThrowError('Invalid React node for page routes: <div />');
|
|
287
|
+
});
|
|
288
|
+
it('throws for an invalid child element', () => {
|
|
289
|
+
expect(() => createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx("span", {})] }))).toThrowError('Invalid React node for page routes: <span />');
|
|
290
|
+
});
|
|
291
|
+
it('throws for a named component child', () => {
|
|
292
|
+
const Sidebar = () => _jsx("div", { children: "sidebar" });
|
|
293
|
+
expect(() => createPageRouter(_jsxs(PageRoutes, { children: [_jsx(PageRoutes.IndexRoute, { component: HomePage }), _jsx(Sidebar, {})] }))).toThrowError('Invalid React node for page routes: <Sidebar />');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|