@forge/react 11.16.2-next.0-experimental-4a332af → 11.16.2-next.1-experimental-49a346a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/out/router/components/ParamsContext.d.ts +3 -0
  3. package/out/router/components/ParamsContext.d.ts.map +1 -0
  4. package/out/router/components/ParamsContext.js +5 -0
  5. package/out/router/components/Route.d.ts +7 -0
  6. package/out/router/components/Route.d.ts.map +1 -0
  7. package/out/router/components/Route.js +25 -0
  8. package/out/router/components/Router.d.ts +7 -0
  9. package/out/router/components/Router.d.ts.map +1 -0
  10. package/out/router/components/Router.js +43 -0
  11. package/out/router/components/RouterContext.d.ts +9 -0
  12. package/out/router/components/RouterContext.d.ts.map +1 -0
  13. package/out/router/components/RouterContext.js +5 -0
  14. package/out/router/components/__test__/Router.test.d.ts +2 -0
  15. package/out/router/components/__test__/Router.test.d.ts.map +1 -0
  16. package/out/router/components/__test__/Router.test.js +77 -0
  17. package/out/router/components/index.d.ts +3 -0
  18. package/out/router/components/index.d.ts.map +1 -0
  19. package/out/router/components/index.js +7 -0
  20. package/out/router/hooks/__test__/useLocation.test.d.ts +2 -0
  21. package/out/router/hooks/__test__/useLocation.test.d.ts.map +1 -0
  22. package/out/router/hooks/__test__/useLocation.test.js +59 -0
  23. package/out/router/hooks/__test__/useNavigate.test.d.ts +2 -0
  24. package/out/router/hooks/__test__/useNavigate.test.d.ts.map +1 -0
  25. package/out/router/hooks/__test__/useNavigate.test.js +159 -0
  26. package/out/router/hooks/__test__/useParams.test.d.ts +2 -0
  27. package/out/router/hooks/__test__/useParams.test.d.ts.map +1 -0
  28. package/out/router/hooks/__test__/useParams.test.js +69 -0
  29. package/out/router/hooks/useLocation.d.ts +3 -0
  30. package/out/router/hooks/useLocation.d.ts.map +1 -0
  31. package/out/router/hooks/useLocation.js +13 -0
  32. package/out/router/hooks/useNavigate.d.ts +6 -0
  33. package/out/router/hooks/useNavigate.d.ts.map +1 -0
  34. package/out/router/hooks/useNavigate.js +45 -0
  35. package/out/router/hooks/useParams.d.ts +2 -0
  36. package/out/router/hooks/useParams.d.ts.map +1 -0
  37. package/out/router/hooks/useParams.js +13 -0
  38. package/out/router/index.d.ts +5 -0
  39. package/out/router/index.d.ts.map +1 -0
  40. package/out/router/index.js +12 -0
  41. package/out/router/utils/__test__/matchPath.test.d.ts +2 -0
  42. package/out/router/utils/__test__/matchPath.test.d.ts.map +1 -0
  43. package/out/router/utils/__test__/matchPath.test.js +56 -0
  44. package/out/router/utils/matchPath.d.ts +5 -0
  45. package/out/router/utils/matchPath.d.ts.map +1 -0
  46. package/out/router/utils/matchPath.js +34 -0
  47. package/out/router/utils/test-utils.d.ts +10 -0
  48. package/out/router/utils/test-utils.d.ts.map +1 -0
  49. package/out/router/utils/test-utils.js +23 -0
  50. package/package.json +7 -2
  51. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,13 +1,33 @@
1
1
  # @forge/react
2
2
 
3
- ## 11.16.2-next.0-experimental-4a332af
3
+ ## 11.16.2-next.1-experimental-49a346a
4
+
5
+ ### Minor Changes
6
+
7
+ - 8dc5a0c: Add routing components and hooks for use in full-page apps.
8
+
9
+ Added the following:
10
+
11
+ - `Router`
12
+ - `Route`
13
+ - `useNavigate`
14
+ - `useLocation`
15
+ - `useParams`
4
16
 
5
17
  ### Patch Changes
6
18
 
7
- - aa757af: Module add
8
- - Updated dependencies [aa757af]
19
+ - Updated dependencies [3cf97c3]
20
+ - Updated dependencies [f7f06c8]
9
21
  - Updated dependencies [dba5074]
10
- - @forge/bridge@5.16.3-next.0-experimental-4a332af
22
+ - Updated dependencies [fc55c83]
23
+ - @forge/bridge@5.17.0-next.6-experimental-49a346a
24
+
25
+ ## 11.16.2-next.1
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [3cf97c3]
30
+ - @forge/bridge@5.17.0-next.1
11
31
 
12
32
  ## 11.16.2-next.0
13
33
 
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ export declare const ParamsContext: import("react").Context<Record<string, string> | null>;
3
+ //# sourceMappingURL=ParamsContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ParamsContext.d.ts","sourceRoot":"","sources":["../../../src/router/components/ParamsContext.ts"],"names":[],"mappings":";AAEA,eAAO,MAAM,aAAa,wDAAqD,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ParamsContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.ParamsContext = (0, react_1.createContext)(null);
@@ -0,0 +1,7 @@
1
+ import { ForgeChildren } from '../../types';
2
+ export interface RouteProps {
3
+ path: string;
4
+ children: ForgeChildren;
5
+ }
6
+ export declare const Route: ({ path, children }: RouteProps) => import("react/jsx-runtime").JSX.Element | null;
7
+ //# sourceMappingURL=Route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Route.d.ts","sourceRoot":"","sources":["../../../src/router/components/Route.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,eAAO,MAAM,KAAK,uBAAwB,UAAU,mDAoBnD,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Route = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const RouterContext_1 = require("./RouterContext");
7
+ const ParamsContext_1 = require("./ParamsContext");
8
+ const matchPath_1 = require("../utils/matchPath");
9
+ const Route = ({ path, children }) => {
10
+ const context = (0, react_1.useContext)(RouterContext_1.RouterContext);
11
+ if (!context) {
12
+ throw new Error('Route must be used within a Router component');
13
+ }
14
+ const { location, hasMatchedRouteRef } = context;
15
+ if (hasMatchedRouteRef.current) {
16
+ return null;
17
+ }
18
+ const match = (0, matchPath_1.matchPath)(path, location.pathname);
19
+ if (!match) {
20
+ return null;
21
+ }
22
+ hasMatchedRouteRef.current = true;
23
+ return (0, jsx_runtime_1.jsx)(ParamsContext_1.ParamsContext.Provider, { value: match.params, children: children });
24
+ };
25
+ exports.Route = Route;
@@ -0,0 +1,7 @@
1
+ import { ForgeChildren, ForgeNode } from '../../types';
2
+ export interface RouterProps {
3
+ children: ForgeChildren;
4
+ fallback?: ForgeNode;
5
+ }
6
+ export declare const Router: ({ children, fallback }: RouterProps) => import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=Router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Router.d.ts","sourceRoot":"","sources":["../../../src/router/components/Router.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,MAAM,2BAAmC,WAAW,4CA0ChE,CAAC"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const bridge_1 = require("@forge/bridge");
7
+ const RouterContext_1 = require("./RouterContext");
8
+ const Router = ({ children, fallback = null }) => {
9
+ const [history, setHistory] = (0, react_1.useState)(null);
10
+ const [location, setLocation] = (0, react_1.useState)(null);
11
+ const [error, setError] = (0, react_1.useState)(null);
12
+ const hasMatchedRouteRef = (0, react_1.useRef)(false);
13
+ hasMatchedRouteRef.current = false;
14
+ (0, react_1.useEffect)(() => {
15
+ let unlisten;
16
+ bridge_1.view
17
+ .createHistory()
18
+ .then((history) => {
19
+ setHistory(history);
20
+ setLocation(history.location);
21
+ unlisten = history.listen((location) => {
22
+ // @ts-ignore - The history object returned by the bridge does not conform to the v5 types.
23
+ // Instead it uses v4 types, so we need to ignore this type error.
24
+ setLocation(location);
25
+ });
26
+ })
27
+ .catch(setError);
28
+ return () => {
29
+ unlisten?.();
30
+ };
31
+ }, []);
32
+ const routerContext = (0, react_1.useMemo)(() => {
33
+ if (!history || !location) {
34
+ return null;
35
+ }
36
+ return { history, location, hasMatchedRouteRef };
37
+ }, [history, location]);
38
+ if (error || !routerContext) {
39
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback });
40
+ }
41
+ return (0, jsx_runtime_1.jsx)(RouterContext_1.RouterContext.Provider, { value: routerContext, children: children });
42
+ };
43
+ exports.Router = Router;
@@ -0,0 +1,9 @@
1
+ import { MutableRefObject } from 'react';
2
+ import type { History, Location } from 'history';
3
+ export interface RouterContextValue {
4
+ history: History;
5
+ location: Location;
6
+ hasMatchedRouteRef: MutableRefObject<boolean>;
7
+ }
8
+ export declare const RouterContext: import("react").Context<RouterContextValue | null>;
9
+ //# sourceMappingURL=RouterContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterContext.d.ts","sourceRoot":"","sources":["../../../src/router/components/RouterContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,kBAAkB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,aAAa,oDAAiD,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouterContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.RouterContext = (0, react_1.createContext)(null);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Router.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Router.test.d.ts","sourceRoot":"","sources":["../../../../src/router/components/__test__/Router.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const testUtils_1 = require("../../../__test__/testUtils");
6
+ const reconcilerTestRenderer_1 = tslib_1.__importDefault(require("../../../__test__/reconcilerTestRenderer"));
7
+ const Router_1 = require("../Router");
8
+ const Route_1 = require("../Route");
9
+ const __1 = require("../../..");
10
+ const test_utils_1 = require("../../utils/test-utils");
11
+ const mockCreateHistory = jest.fn(async () => (0, test_utils_1.createMockHistory)());
12
+ jest.mock('@forge/bridge', () => ({
13
+ view: {
14
+ createHistory: () => mockCreateHistory()
15
+ }
16
+ }));
17
+ describe('Router', () => {
18
+ let bridgeCalls;
19
+ beforeAll(() => {
20
+ bridgeCalls = (0, testUtils_1.setupBridge)();
21
+ });
22
+ beforeEach(() => {
23
+ bridgeCalls.length = 0;
24
+ mockCreateHistory.mockClear();
25
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)());
26
+ });
27
+ afterEach(() => jest.clearAllMocks());
28
+ it('renders children after history is initialized', async () => {
29
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home Page" }) }));
30
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
31
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
32
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Home Page');
33
+ });
34
+ it('renders fallback while history is loading', async () => {
35
+ mockCreateHistory.mockImplementation(() => new Promise(() => { }));
36
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { fallback: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Loading..." }), children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home Page" }) }));
37
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
38
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
39
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Loading...');
40
+ });
41
+ it('renders children when Route path matches', async () => {
42
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { children: (0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home Page" }) }) }));
43
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
44
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
45
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Home Page');
46
+ });
47
+ it('does not render Route children when path does not match', async () => {
48
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { children: (0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/settings", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Settings Page" }) }) }));
49
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
50
+ expect(forgeDoc?.children).toHaveLength(0);
51
+ });
52
+ it('throws when Route is used outside of Router', async () => {
53
+ await expect(reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home" }) }))).rejects.toThrow('Route must be used within a Router component');
54
+ });
55
+ it('renders only the first matching Route when multiple routes match', async () => {
56
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsxs)(Router_1.Router, { children: [(0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "First" }) }), (0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Second" }) })] }));
57
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
58
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
59
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'First');
60
+ expect(forgeDoc?.children).toHaveLength(1);
61
+ });
62
+ it('renders the catchall Route when no other route matches', async () => {
63
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)('/unknown'));
64
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsxs)(Router_1.Router, { children: [(0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home" }) }), (0, jsx_runtime_1.jsx)(Route_1.Route, { path: "*", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Not Found" }) })] }));
65
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
66
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
67
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Not Found');
68
+ expect(forgeDoc?.children).toHaveLength(1);
69
+ });
70
+ it('renders a specific Route over catchall when it matches', async () => {
71
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsxs)(Router_1.Router, { children: [(0, jsx_runtime_1.jsx)(Route_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home" }) }), (0, jsx_runtime_1.jsx)(Route_1.Route, { path: "*", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Not Found" }) })] }));
72
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
73
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
74
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Home');
75
+ expect(forgeDoc?.children).toHaveLength(1);
76
+ });
77
+ });
@@ -0,0 +1,3 @@
1
+ export { Router, type RouterProps } from './Router';
2
+ export { Route, type RouteProps } from './Route';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/router/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Route = exports.Router = void 0;
4
+ var Router_1 = require("./Router");
5
+ Object.defineProperty(exports, "Router", { enumerable: true, get: function () { return Router_1.Router; } });
6
+ var Route_1 = require("./Route");
7
+ Object.defineProperty(exports, "Route", { enumerable: true, get: function () { return Route_1.Route; } });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useLocation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLocation.test.d.ts","sourceRoot":"","sources":["../../../../src/router/hooks/__test__/useLocation.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const testUtils_1 = require("../../../__test__/testUtils");
7
+ const reconcilerTestRenderer_1 = tslib_1.__importDefault(require("../../../__test__/reconcilerTestRenderer"));
8
+ const Router_1 = require("../../components/Router");
9
+ const useNavigate_1 = require("../useNavigate");
10
+ const useLocation_1 = require("../useLocation");
11
+ const components_1 = require("../../../components");
12
+ const test_utils_1 = require("../../utils/test-utils");
13
+ const mockCreateHistory = jest.fn(async () => (0, test_utils_1.createMockHistory)());
14
+ jest.mock('@forge/bridge', () => ({
15
+ view: {
16
+ createHistory: () => mockCreateHistory()
17
+ }
18
+ }));
19
+ describe('useLocation', () => {
20
+ let bridgeCalls;
21
+ beforeAll(() => {
22
+ bridgeCalls = (0, testUtils_1.setupBridge)();
23
+ });
24
+ beforeEach(() => {
25
+ bridgeCalls.length = 0;
26
+ mockCreateHistory.mockClear();
27
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)());
28
+ });
29
+ afterEach(() => jest.clearAllMocks());
30
+ it('returns the current location', async () => {
31
+ const locationListener = jest.fn();
32
+ const LocationTest = () => {
33
+ const location = (0, useLocation_1.useLocation)();
34
+ (0, react_1.useEffect)(() => locationListener(location), [location]);
35
+ return (0, jsx_runtime_1.jsx)(components_1.Text, { children: "Location Test" });
36
+ };
37
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { children: (0, jsx_runtime_1.jsx)(LocationTest, {}) }));
38
+ expect(locationListener).toHaveBeenCalledWith(expect.objectContaining({
39
+ pathname: '/',
40
+ search: '',
41
+ hash: ''
42
+ }));
43
+ });
44
+ it('updates when navigation occurs', async () => {
45
+ const locationListener = jest.fn();
46
+ const LocationTest = () => {
47
+ const location = (0, useLocation_1.useLocation)();
48
+ const navigate = (0, useNavigate_1.useNavigate)();
49
+ (0, react_1.useEffect)(() => locationListener(location), [location]);
50
+ (0, react_1.useEffect)(() => {
51
+ navigate('/about');
52
+ }, [navigate]);
53
+ return (0, jsx_runtime_1.jsx)(components_1.Text, { children: "Location Test" });
54
+ };
55
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(Router_1.Router, { children: (0, jsx_runtime_1.jsx)(LocationTest, {}) }));
56
+ expect(locationListener).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/' }));
57
+ expect(locationListener).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/about' }));
58
+ });
59
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useNavigate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNavigate.test.d.ts","sourceRoot":"","sources":["../../../../src/router/hooks/__test__/useNavigate.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const testUtils_1 = require("../../../__test__/testUtils");
7
+ const reconcilerTestRenderer_1 = tslib_1.__importDefault(require("../../../__test__/reconcilerTestRenderer"));
8
+ const components_1 = require("../../components");
9
+ const useNavigate_1 = require("../useNavigate");
10
+ const useLocation_1 = require("../useLocation");
11
+ const components_2 = require("../../../components");
12
+ const test_utils_1 = require("../../utils/test-utils");
13
+ let mockHistory;
14
+ const mockCreateHistory = jest.fn(async (initialPath) => {
15
+ mockHistory = (0, test_utils_1.createMockHistory)(initialPath);
16
+ return mockHistory;
17
+ });
18
+ jest.mock('@forge/bridge', () => ({
19
+ view: {
20
+ createHistory: () => mockCreateHistory()
21
+ }
22
+ }));
23
+ describe('useNavigate', () => {
24
+ let bridgeCalls;
25
+ beforeAll(() => {
26
+ bridgeCalls = (0, testUtils_1.setupBridge)();
27
+ });
28
+ beforeEach(() => {
29
+ bridgeCalls.length = 0;
30
+ mockCreateHistory.mockClear();
31
+ mockCreateHistory.mockImplementation(async (initialPath) => {
32
+ mockHistory = (0, test_utils_1.createMockHistory)(initialPath);
33
+ return mockHistory;
34
+ });
35
+ });
36
+ afterEach(() => jest.clearAllMocks());
37
+ it('navigates to a new path using push by default', async () => {
38
+ const NavigateTest = () => {
39
+ const navigate = (0, useNavigate_1.useNavigate)();
40
+ (0, react_1.useEffect)(() => {
41
+ navigate('/settings');
42
+ }, [navigate]);
43
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
44
+ };
45
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
46
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings');
47
+ });
48
+ it('navigates with replace when option is set', async () => {
49
+ const NavigateTest = () => {
50
+ const navigate = (0, useNavigate_1.useNavigate)();
51
+ (0, react_1.useEffect)(() => {
52
+ navigate('/settings', { replace: true });
53
+ }, [navigate]);
54
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
55
+ };
56
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
57
+ expect(mockHistory.replace).toHaveBeenCalledWith('/settings');
58
+ });
59
+ it('navigates back in history when passed a negative number', async () => {
60
+ const NavigateTest = () => {
61
+ const navigate = (0, useNavigate_1.useNavigate)();
62
+ (0, react_1.useEffect)(() => {
63
+ navigate(-1);
64
+ }, [navigate]);
65
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
66
+ };
67
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
68
+ expect(mockHistory.go).toHaveBeenCalledWith(-1);
69
+ });
70
+ it('navigates forward in history when passed a positive number', async () => {
71
+ const NavigateTest = () => {
72
+ const navigate = (0, useNavigate_1.useNavigate)();
73
+ (0, react_1.useEffect)(() => {
74
+ navigate(2);
75
+ }, [navigate]);
76
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
77
+ };
78
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
79
+ expect(mockHistory.go).toHaveBeenCalledWith(2);
80
+ });
81
+ it('resolves relative paths against the current location', async () => {
82
+ mockCreateHistory.mockImplementation(async () => { mockHistory = (0, test_utils_1.createMockHistory)('/settings'); return mockHistory; });
83
+ const NavigateTest = () => {
84
+ const navigate = (0, useNavigate_1.useNavigate)();
85
+ (0, react_1.useEffect)(() => {
86
+ navigate('personal');
87
+ }, [navigate]);
88
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
89
+ };
90
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
91
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings/personal');
92
+ });
93
+ it('resolves relative paths against updated location after navigation', async () => {
94
+ const NavigateTest = () => {
95
+ const navigate = (0, useNavigate_1.useNavigate)();
96
+ const location = (0, useLocation_1.useLocation)();
97
+ (0, react_1.useEffect)(() => {
98
+ if (location.pathname === '/') {
99
+ navigate('/settings');
100
+ }
101
+ else if (location.pathname === '/settings') {
102
+ navigate('personal');
103
+ }
104
+ }, [location, navigate]);
105
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
106
+ };
107
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
108
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings');
109
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings/personal');
110
+ });
111
+ it('navigates up one level with ../', async () => {
112
+ mockCreateHistory.mockImplementation(async () => { mockHistory = (0, test_utils_1.createMockHistory)('/settings/general'); return mockHistory; });
113
+ const NavigateTest = () => {
114
+ const navigate = (0, useNavigate_1.useNavigate)();
115
+ (0, react_1.useEffect)(() => {
116
+ navigate('../advanced');
117
+ }, [navigate]);
118
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
119
+ };
120
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
121
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings/advanced');
122
+ });
123
+ it('navigates up multiple levels with ../../', async () => {
124
+ mockCreateHistory.mockImplementation(async () => { mockHistory = (0, test_utils_1.createMockHistory)('/settings/general/details'); return mockHistory; });
125
+ const NavigateTest = () => {
126
+ const navigate = (0, useNavigate_1.useNavigate)();
127
+ (0, react_1.useEffect)(() => {
128
+ navigate('../../other');
129
+ }, [navigate]);
130
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
131
+ };
132
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
133
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings/other');
134
+ });
135
+ it('resolves to root when navigating above the root with ../', async () => {
136
+ mockCreateHistory.mockImplementation(async () => { mockHistory = (0, test_utils_1.createMockHistory)('/settings'); return mockHistory; });
137
+ const NavigateTest = () => {
138
+ const navigate = (0, useNavigate_1.useNavigate)();
139
+ (0, react_1.useEffect)(() => {
140
+ navigate('../../../../../../escaped');
141
+ }, [navigate]);
142
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
143
+ };
144
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
145
+ expect(mockHistory.push).toHaveBeenCalledWith('/escaped');
146
+ });
147
+ it('resolves . as the current directory', async () => {
148
+ mockCreateHistory.mockImplementation(async () => { mockHistory = (0, test_utils_1.createMockHistory)('/settings/general'); return mockHistory; });
149
+ const NavigateTest = () => {
150
+ const navigate = (0, useNavigate_1.useNavigate)();
151
+ (0, react_1.useEffect)(() => {
152
+ navigate('./advanced');
153
+ }, [navigate]);
154
+ return (0, jsx_runtime_1.jsx)(components_2.Text, { children: "Navigate Test" });
155
+ };
156
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(NavigateTest, {}) }));
157
+ expect(mockHistory.push).toHaveBeenCalledWith('/settings/general/advanced');
158
+ });
159
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useParams.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useParams.test.d.ts","sourceRoot":"","sources":["../../../../src/router/hooks/__test__/useParams.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const testUtils_1 = require("../../../__test__/testUtils");
6
+ const reconcilerTestRenderer_1 = tslib_1.__importDefault(require("../../../__test__/reconcilerTestRenderer"));
7
+ const components_1 = require("../../components");
8
+ const useParams_1 = require("../useParams");
9
+ const __1 = require("../../..");
10
+ const test_utils_1 = require("../../utils/test-utils");
11
+ const mockCreateHistory = jest.fn(async () => (0, test_utils_1.createMockHistory)());
12
+ jest.mock('@forge/bridge', () => ({
13
+ view: {
14
+ createHistory: () => mockCreateHistory()
15
+ }
16
+ }));
17
+ describe('useParams', () => {
18
+ let bridgeCalls;
19
+ beforeAll(() => {
20
+ bridgeCalls = (0, testUtils_1.setupBridge)();
21
+ });
22
+ beforeEach(() => {
23
+ bridgeCalls.length = 0;
24
+ mockCreateHistory.mockClear();
25
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)());
26
+ });
27
+ afterEach(() => jest.clearAllMocks());
28
+ it('throws when used outside of Route', async () => {
29
+ const ComponentUsingParams = () => {
30
+ const params = (0, useParams_1.useParams)();
31
+ return (0, jsx_runtime_1.jsx)(__1.Text, { children: JSON.stringify(params) });
32
+ };
33
+ await expect(reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(ComponentUsingParams, {}))).rejects.toThrow('useParams must be used within a Route component');
34
+ });
35
+ it('renders route with no params', async () => {
36
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(components_1.Route, { path: "/", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Home" }) }) }));
37
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
38
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
39
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Home');
40
+ });
41
+ it('renders route with matched params', async () => {
42
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)('/posts/42'));
43
+ const Post = () => {
44
+ const { id } = (0, useParams_1.useParams)();
45
+ return (0, jsx_runtime_1.jsx)(__1.Text, { children: `Post ${id}` });
46
+ };
47
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(components_1.Route, { path: "/posts/:id", children: (0, jsx_runtime_1.jsx)(Post, {}) }) }));
48
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
49
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
50
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Post 42');
51
+ });
52
+ it('renders route with multiple matched params', async () => {
53
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)('/posts/42/comments/7'));
54
+ const Comment = () => {
55
+ const { postId, commentId } = (0, useParams_1.useParams)();
56
+ return (0, jsx_runtime_1.jsx)(__1.Text, { children: `Post ${postId} Comment ${commentId}` });
57
+ };
58
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(components_1.Route, { path: "/posts/:postId/comments/:commentId", children: (0, jsx_runtime_1.jsx)(Comment, {}) }) }));
59
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
60
+ expect(forgeDoc).toHaveProperty('children[0].type', 'Text');
61
+ expect(forgeDoc).toHaveProperty('children[0].children[0].props.text', 'Post 42 Comment 7');
62
+ });
63
+ it('does not render route when dynamic path does not match', async () => {
64
+ mockCreateHistory.mockImplementation(async () => (0, test_utils_1.createMockHistory)('/users/alice'));
65
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(components_1.Router, { children: (0, jsx_runtime_1.jsx)(components_1.Route, { path: "/posts/:id", children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Post Detail" }) }) }));
66
+ const forgeDoc = (0, testUtils_1.getLastBridgeCallForgeDoc)(bridgeCalls);
67
+ expect(forgeDoc?.children).toHaveLength(0);
68
+ });
69
+ });
@@ -0,0 +1,3 @@
1
+ import type { Location } from 'history';
2
+ export declare const useLocation: () => Location;
3
+ //# sourceMappingURL=useLocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLocation.d.ts","sourceRoot":"","sources":["../../../src/router/hooks/useLocation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,eAAO,MAAM,WAAW,QAAO,QAQ9B,CAAC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useLocation = void 0;
4
+ const react_1 = require("react");
5
+ const RouterContext_1 = require("../components/RouterContext");
6
+ const useLocation = () => {
7
+ const context = (0, react_1.useContext)(RouterContext_1.RouterContext);
8
+ if (!context) {
9
+ throw new Error('useLocation must be used within a Router component');
10
+ }
11
+ return context.location;
12
+ };
13
+ exports.useLocation = useLocation;
@@ -0,0 +1,6 @@
1
+ interface NavigateOptions {
2
+ replace?: boolean;
3
+ }
4
+ export declare const useNavigate: () => (to: string | number, options?: NavigateOptions) => void;
5
+ export {};
6
+ //# sourceMappingURL=useNavigate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNavigate.d.ts","sourceRoot":"","sources":["../../../src/router/hooks/useNavigate.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwBD,eAAO,MAAM,WAAW,aAUf,MAAM,GAAG,MAAM,YAAY,eAAe,SAkBlD,CAAC"}