@ahnseungchan/webviewkit 1.0.8 → 1.2.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.
Files changed (43) hide show
  1. package/README.md +57 -112
  2. package/dist/constants.d.ts +3 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +5 -0
  5. package/dist/{packages/core/src/history.d.ts → history.d.ts} +3 -2
  6. package/dist/history.d.ts.map +1 -0
  7. package/dist/history.js +91 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +22 -0
  11. package/dist/stack-dev/stack-dev-roadmap.d.ts +15 -0
  12. package/dist/stack-dev/stack-dev-roadmap.d.ts.map +1 -0
  13. package/dist/stack-dev/stack-dev-roadmap.js +66 -0
  14. package/dist/stack-router/_components/back-router.d.ts +3 -0
  15. package/dist/stack-router/_components/back-router.d.ts.map +1 -0
  16. package/dist/stack-router/_components/back-router.js +39 -0
  17. package/dist/stack-router/_components/header.d.ts +3 -0
  18. package/dist/stack-router/_components/header.d.ts.map +1 -0
  19. package/dist/stack-router/_components/header.js +42 -0
  20. package/dist/{packages/core/src/stack-router → stack-router}/stack-router-provider.d.ts +4 -5
  21. package/dist/stack-router/stack-router-provider.d.ts.map +1 -0
  22. package/dist/stack-router/stack-router-provider.js +227 -0
  23. package/dist/{packages/core/src/type.d.ts → type.d.ts} +3 -5
  24. package/dist/type.d.ts.map +1 -0
  25. package/dist/type.js +14 -0
  26. package/package.json +22 -26
  27. package/dist/packages/core/src/__tests__/back-router.test.d.ts +0 -0
  28. package/dist/packages/core/src/__tests__/back-router.test.js +0 -62
  29. package/dist/packages/core/src/__tests__/history.test.d.ts +0 -1
  30. package/dist/packages/core/src/__tests__/history.test.js +0 -114
  31. package/dist/packages/core/src/__tests__/stack-router.test.d.ts +0 -0
  32. package/dist/packages/core/src/__tests__/stack-router.test.js +0 -185
  33. package/dist/packages/core/src/history.js +0 -82
  34. package/dist/packages/core/src/index.d.ts +0 -5
  35. package/dist/packages/core/src/index.js +0 -6
  36. package/dist/packages/core/src/stack-router/__tests__/stack-router.test.d.ts +0 -0
  37. package/dist/packages/core/src/stack-router/__tests__/stack-router.test.js +0 -153
  38. package/dist/packages/core/src/stack-router/_components/back-router.d.ts +0 -2
  39. package/dist/packages/core/src/stack-router/_components/back-router.js +0 -34
  40. package/dist/packages/core/src/stack-router/_components/header.d.ts +0 -2
  41. package/dist/packages/core/src/stack-router/_components/header.js +0 -36
  42. package/dist/packages/core/src/stack-router/stack-router-provider.js +0 -211
  43. package/dist/packages/core/src/type.js +0 -11
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.StackRouterProvider = exports.useStackRouter = void 0;
7
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const history_1 = require("../history");
10
+ const styled_1 = __importDefault(require("@emotion/styled"));
11
+ const react_2 = require("@emotion/react");
12
+ const constants_1 = require("../constants");
13
+ const stack_dev_roadmap_1 = require("../stack-dev/stack-dev-roadmap");
14
+ const type_1 = require("../type");
15
+ const StackRouterContext = (0, react_1.createContext)(null);
16
+ const useStackRouter = () => {
17
+ const context = (0, react_1.useContext)(StackRouterContext);
18
+ if (!context) {
19
+ throw new Error("useStackRouter must be used within a StackRouterProvider");
20
+ }
21
+ return context;
22
+ };
23
+ exports.useStackRouter = useStackRouter;
24
+ const StackRouterProvider = ({ maxWidth = "600px", Activities = [], initialActivity, enableDevTools, }) => {
25
+ const showDevTools = (0, react_1.useMemo)(() => {
26
+ var _a;
27
+ if (enableDevTools !== undefined)
28
+ return enableDevTools;
29
+ return typeof process !== "undefined" && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.NODE_ENV) === "development";
30
+ }, [enableDevTools]);
31
+ const [currentPath, setCurrentPath] = (0, react_1.useState)(history_1.history.getCurrentState().path);
32
+ const [currentState, setCurrentState] = (0, react_1.useState)(history_1.history.getCurrentState());
33
+ const [isTransitioning, setIsTransitioning] = (0, react_1.useState)(false);
34
+ const [direction, setDirection] = (0, react_1.useState)(type_1.Direction.FORWARD);
35
+ const [visiblePages, setVisiblePages] = (0, react_1.useState)([]);
36
+ // initialize visible pages
37
+ (0, react_1.useEffect)(() => {
38
+ setVisiblePages([
39
+ {
40
+ path: initialActivity.path,
41
+ element: initialActivity.element,
42
+ isEntering: true,
43
+ },
44
+ ]);
45
+ }, [initialActivity]);
46
+ // listen to history changes
47
+ (0, react_1.useEffect)(() => {
48
+ const removeListener = history_1.history.addListener((state) => {
49
+ if (!isTransitioning || direction !== type_1.Direction.BACKWARD) {
50
+ setCurrentPath(state === null || state === void 0 ? void 0 : state.path);
51
+ setCurrentState(state);
52
+ }
53
+ setIsTransitioning(true);
54
+ setDirection((state === null || state === void 0 ? void 0 : state.action) === type_1.HistoryAction.POP
55
+ ? type_1.Direction.BACKWARD
56
+ : type_1.Direction.FORWARD);
57
+ });
58
+ return () => {
59
+ removeListener();
60
+ };
61
+ }, [isTransitioning, direction]);
62
+ // handle back
63
+ const handleBack = () => {
64
+ setIsTransitioning(true);
65
+ setDirection(type_1.Direction.BACKWARD);
66
+ setTimeout(() => {
67
+ setVisiblePages((prev) => prev.slice(0, -1));
68
+ }, constants_1.STACK_TRANSITION_MS);
69
+ history_1.history.back();
70
+ };
71
+ // update current path
72
+ (0, react_1.useEffect)(() => {
73
+ if (visiblePages.length > 0) {
74
+ setCurrentPath(visiblePages[visiblePages.length - 1].path);
75
+ }
76
+ }, [visiblePages]);
77
+ // handle push
78
+ const handlePush = (path, data) => {
79
+ setIsTransitioning(true);
80
+ setDirection(type_1.Direction.FORWARD);
81
+ const currentActivity = Activities.find((a) => a.path === path);
82
+ if (currentActivity) {
83
+ setVisiblePages((prev) => {
84
+ const existingIndex = prev.findIndex((a) => a.path === path);
85
+ if (existingIndex !== -1) {
86
+ const newActivities = [...prev];
87
+ const [removed] = newActivities.splice(existingIndex, 1);
88
+ return [...newActivities, { ...removed, isExiting: true }];
89
+ }
90
+ return [
91
+ ...prev,
92
+ { path, element: currentActivity.element, isExiting: true },
93
+ ];
94
+ });
95
+ setTimeout(() => {
96
+ setCurrentPath(path);
97
+ }, constants_1.STACK_TRANSITION_MS);
98
+ }
99
+ history_1.history.push(path, data);
100
+ };
101
+ const value = {
102
+ currentPath,
103
+ currentState,
104
+ initialActivity,
105
+ Activities,
106
+ visiblePages,
107
+ push: handlePush,
108
+ replace: history_1.history.replace,
109
+ back: handleBack,
110
+ forward: history_1.history.forward,
111
+ };
112
+ return ((0, jsx_runtime_1.jsxs)(StackRouterContext.Provider, { value: value, children: [(0, jsx_runtime_1.jsx)(LayoutContainer, { children: (0, jsx_runtime_1.jsx)(Layout, { maxWidth: maxWidth, as: "div", "data-testid": "layout", children: (0, jsx_runtime_1.jsx)(PageContainer, { children: visiblePages.map((page, index) => ((0, jsx_runtime_1.jsx)(PageWrapper, { isTransitioning: isTransitioning, direction: direction, currentPage: index === visiblePages.length - 1, isEntering: page.isEntering, isExiting: page.isExiting, zIndex: index, children: page.element }, `${page.path}-${index}`))) }) }) }), (0, jsx_runtime_1.jsx)(stack_dev_roadmap_1.StackDevRoadmap, { visible: showDevTools, pages: visiblePages.map((p) => ({ path: p.path })), currentPath: currentPath })] }));
113
+ };
114
+ exports.StackRouterProvider = StackRouterProvider;
115
+ exports.default = StackRouterProvider;
116
+ /**styles */
117
+ const LayoutContainer = styled_1.default.div `
118
+ width: 100%;
119
+ height: 100vh;
120
+ display: flex;
121
+ justify-content: center;
122
+ background: white;
123
+ `;
124
+ const Layout = styled_1.default.div `
125
+ width: 100%;
126
+ max-width: ${({ maxWidth }) => maxWidth};
127
+ height: 100%;
128
+ position: relative;
129
+ overflow: hidden;
130
+ `;
131
+ const PageContainer = styled_1.default.div `
132
+ position: relative;
133
+ width: 100%;
134
+ height: 100%;
135
+ overflow: hidden;
136
+ background: white;
137
+ `;
138
+ const slideInRight = (0, react_2.keyframes) `
139
+ from {
140
+ transform: translateX(100%);
141
+ opacity: 0.2;
142
+ }
143
+ to {
144
+ transform: translateX(0);
145
+ opacity: 1;
146
+ }
147
+ `;
148
+ const slideOutLeft = (0, react_2.keyframes) `
149
+ from {
150
+ transform: translateX(0);
151
+ opacity: 1;
152
+ }
153
+ to {
154
+ transform: translateX(-30%);
155
+ opacity: 0.2;
156
+ }
157
+ `;
158
+ const slideInLeft = (0, react_2.keyframes) `
159
+ from {
160
+ transform: translateX(-30%);
161
+ opacity: 0.2;
162
+ }
163
+ to {
164
+ transform: translateX(0);
165
+ opacity: 1;
166
+ }
167
+ `;
168
+ const slideOutRight = (0, react_2.keyframes) `
169
+ from {
170
+ transform: translateX(0);
171
+ opacity: 1;
172
+ }
173
+ to {
174
+ transform: translateX(100%);
175
+ opacity: 0.2;
176
+ }
177
+ `;
178
+ const PageWrapper = styled_1.default.div `
179
+ position: absolute;
180
+ top: 0;
181
+ left: 0;
182
+ width: 100%;
183
+ height: 100%;
184
+ background: white;
185
+ cursor: grab;
186
+ user-select: none;
187
+ -webkit-user-select: none;
188
+ -webkit-touch-callout: none;
189
+ -webkit-tap-highlight-color: transparent;
190
+ z-index: ${({ zIndex }) => zIndex};
191
+ will-change: transform, opacity;
192
+ transform: translate3d(0, 0, 0);
193
+ animation: ${({ isTransitioning, direction, currentPage, isEntering, isExiting, }) => {
194
+ if (!isTransitioning)
195
+ return "none";
196
+ if (direction === "forward") {
197
+ if (isExiting) {
198
+ return (0, react_2.css) `
199
+ ${slideInRight} ${constants_1.STACK_TRANSITION_MS}ms ease-in-out forwards
200
+ `;
201
+ }
202
+ if (isEntering) {
203
+ return (0, react_2.css) `
204
+ ${slideOutLeft} ${constants_1.STACK_TRANSITION_MS}ms ease-in-out forwards
205
+ `;
206
+ }
207
+ }
208
+ else {
209
+ if (isExiting) {
210
+ return (0, react_2.css) `
211
+ ${slideOutRight} ${constants_1.STACK_TRANSITION_MS}ms ease-in-out forwards
212
+ `;
213
+ }
214
+ if (isEntering) {
215
+ return (0, react_2.css) `
216
+ ${slideInLeft} ${constants_1.STACK_TRANSITION_MS}ms ease-in-out forwards
217
+ `;
218
+ }
219
+ }
220
+ return "none";
221
+ }};
222
+ animation-fill-mode: forwards;
223
+
224
+ &:active {
225
+ cursor: grabbing;
226
+ }
227
+ `;
@@ -9,11 +9,8 @@ export interface StackRouterProviderProps {
9
9
  path: string;
10
10
  element: React.ReactNode;
11
11
  };
12
- ActivitysLayout?: React.ComponentType<{
13
- children: React.ReactNode;
14
- }>;
15
- onNavigate?: (path: string) => void;
16
- currentPath?: string;
12
+ /** 개발 시 스택 로드맵 오버레이 (기본: NODE_ENV === "development") */
13
+ enableDevTools?: boolean;
17
14
  }
18
15
  export interface VisiblePage {
19
16
  path: string;
@@ -37,3 +34,4 @@ export interface HistoryState {
37
34
  action?: HistoryAction.PUSH | HistoryAction.POP | HistoryAction.REPLACE;
38
35
  }
39
36
  export type HistoryListener = (state: HistoryState) => void;
37
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,EAAE,CAAC;IAC1D,eAAe,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,CAAC;IAC5D,wDAAwD;IACxD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,oBAAY,SAAS;IACnB,OAAO,YAAY;IACnB,QAAQ,aAAa;CACtB;AAED,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC;AAEnE,oBAAY,aAAa;IACvB,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC;CACzE;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC"}
package/dist/type.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HistoryAction = exports.Direction = void 0;
4
+ var Direction;
5
+ (function (Direction) {
6
+ Direction["FORWARD"] = "forward";
7
+ Direction["BACKWARD"] = "backward";
8
+ })(Direction || (exports.Direction = Direction = {}));
9
+ var HistoryAction;
10
+ (function (HistoryAction) {
11
+ HistoryAction["PUSH"] = "PUSH";
12
+ HistoryAction["POP"] = "POP";
13
+ HistoryAction["REPLACE"] = "REPLACE";
14
+ })(HistoryAction || (exports.HistoryAction = HistoryAction = {}));
package/package.json CHANGED
@@ -1,19 +1,14 @@
1
1
  {
2
2
  "name": "@ahnseungchan/webviewkit",
3
- "version": "1.0.8",
4
- "description": "Core WebView functionality for stack-based navigation",
3
+ "version": "1.2.0",
4
+ "description": "Stack-based navigation for React WebView apps with dev stack roadmap",
5
5
  "main": "dist/index.js",
6
- "module": "dist/index.esm.js",
7
6
  "types": "dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
- "publishConfig": {
12
- "access": "public"
13
- },
7
+ "files": ["dist", "README.md"],
8
+ "sideEffects": false,
14
9
  "scripts": {
15
- "build": "tsc",
16
- "test": "jest src/__tests__",
10
+ "build": "tsc -p tsconfig.build.json",
11
+ "test": "jest --config jest.config.js",
17
12
  "clean": "rimraf dist",
18
13
  "lint": "eslint src --ext .ts,.tsx",
19
14
  "prepublishOnly": "npm run clean && npm run build"
@@ -24,23 +19,27 @@
24
19
  "stack-router",
25
20
  "react",
26
21
  "mobile",
27
- "router"
22
+ "router",
23
+ "react-native-webview"
28
24
  ],
29
- "author": "Your Name",
25
+ "author": "Seungchan Ahn <omnipoo@naver.com>",
30
26
  "license": "MIT",
31
27
  "repository": {
32
28
  "type": "git",
33
- "url": "https://github.com/Ahnseungc/webviewkit.git"
29
+ "url": "https://github.com/Ahnseungc/WebViewKit.git"
34
30
  },
35
31
  "bugs": {
36
- "url": "https://github.com/Ahnseungc/webviewkit/issues"
32
+ "url": "https://github.com/Ahnseungc/WebViewKit/issues"
33
+ },
34
+ "homepage": "https://github.com/Ahnseungc/WebViewKit#readme",
35
+ "publishConfig": {
36
+ "access": "public"
37
37
  },
38
- "homepage": "https://github.com/Ahnseungc/webviewkit#readme",
39
38
  "peerDependencies": {
40
- "react": ">=16.8.0",
41
- "react-dom": ">=16.8.0",
42
39
  "@emotion/react": ">=11.0.0",
43
- "@emotion/styled": ">=11.0.0"
40
+ "@emotion/styled": ">=11.0.0",
41
+ "react": ">=16.8.0",
42
+ "react-dom": ">=16.8.0"
44
43
  },
45
44
  "devDependencies": {
46
45
  "@emotion/babel-plugin": "^11.11.0",
@@ -50,17 +49,14 @@
50
49
  "@testing-library/react": "^14.2.1",
51
50
  "@testing-library/user-event": "^14.5.2",
52
51
  "@types/jest": "^29.5.12",
53
- "@types/react": "^19.1.4",
54
- "@types/react-dom": "^19.1.5",
52
+ "@types/react": "^18.3.21",
53
+ "@types/react-dom": "^18.3.7",
55
54
  "jest": "^29.7.0",
56
55
  "jest-environment-jsdom": "^29.7.0",
57
- "react": "^19.1.0",
58
- "react-dom": "^19.1.0",
56
+ "react": "^18.3.1",
57
+ "react-dom": "^18.3.1",
59
58
  "rimraf": "^5.0.5",
60
59
  "ts-jest": "^29.1.2",
61
60
  "typescript": "^5.3.3"
62
- },
63
- "dependencies": {
64
- "gsap": "^3.13.0"
65
61
  }
66
62
  }
@@ -1,62 +0,0 @@
1
- "use strict";
2
- // import React from "react";
3
- // import { render, screen, fireEvent } from "@testing-library/react";
4
- // import BackRouter from "../stack-router/_components/back-router";
5
- // import userEvent from "@testing-library/user-event";
6
- // import { useStackRouter } from "../stack-router/stack-router-provider";
7
- // jest.mock("../stack-router/stack-router-provider", () => ({
8
- // useStackRouter: jest.fn(),
9
- // }));
10
- // describe("BackRouter", () => {
11
- // const mockBack = jest.fn();
12
- // const user = userEvent.setup();
13
- // beforeEach(() => {
14
- // // Reset all mocks before each test
15
- // jest.clearAllMocks();
16
- // // Setup default mock implementation
17
- // (useStackRouter as jest.Mock).mockReturnValue({
18
- // back: mockBack,
19
- // });
20
- // });
21
- // it("renders back button with icon", () => {
22
- // render(<BackRouter />);
23
- // // Check if button exists
24
- // const button = screen.getByRole("button");
25
- // expect(button).toBeInTheDocument();
26
- // // Check if SVG icon exists
27
- // const svg = button.querySelector("svg");
28
- // expect(svg).toBeInTheDocument();
29
- // });
30
- // it("calls back function when clicked", () => {
31
- // render(<BackRouter />);
32
- // const button = screen.getByRole("button");
33
- // fireEvent.click(button);
34
- // expect(mockBack).toHaveBeenCalledTimes(1);
35
- // });
36
- // it("applies correct styles", () => {
37
- // render(<BackRouter />);
38
- // const button = screen.getByRole("button");
39
- // // Check initial styles
40
- // expect(button).toHaveStyle({
41
- // width: "40px",
42
- // height: "40px",
43
- // padding: "8px",
44
- // borderRadius: "50%",
45
- // });
46
- // // Check if SVG has correct attributes
47
- // const svg = button.querySelector("svg");
48
- // expect(svg).toHaveAttribute("width", "32");
49
- // expect(svg).toHaveAttribute("height", "32");
50
- // expect(svg).toHaveAttribute("viewBox", "0 0 24 24");
51
- // });
52
- // it("handles hover and active states", async () => {
53
- // render(<BackRouter />);
54
- // const button = screen.getByRole("button");
55
- // // expect hover
56
- // await user.hover(button);
57
- // expect(button).toHaveStyle({ color: "#666666" });
58
- // // expect active
59
- // await user.pointer({ keys: "[MouseLeft]", target: button });
60
- // expect(button).toHaveStyle({ color: "#999999" });
61
- // });
62
- // });
@@ -1 +0,0 @@
1
- export {};
@@ -1,114 +0,0 @@
1
- import { history } from "../history";
2
- // mocking window.history
3
- const mockHistory = {
4
- state: null,
5
- pushState: jest.fn(),
6
- replaceState: jest.fn(),
7
- back: jest.fn(),
8
- forward: jest.fn(),
9
- go: jest.fn(),
10
- };
11
- // mocking window.location
12
- const mockLocation = {
13
- pathname: "/",
14
- };
15
- // mocking global window
16
- Object.defineProperty(window, "history", {
17
- value: mockHistory,
18
- writable: true,
19
- });
20
- Object.defineProperty(window, "location", {
21
- value: mockLocation,
22
- writable: true,
23
- });
24
- describe("history", () => {
25
- beforeEach(() => {
26
- jest.clearAllMocks();
27
- mockHistory.state = null;
28
- mockLocation.pathname = "/";
29
- });
30
- describe("push", () => {
31
- it("should push new history entry", () => {
32
- const path = "/test";
33
- const data = { title: "Test" };
34
- history.push(path, data);
35
- expect(mockHistory.pushState).toHaveBeenCalledWith({ path, data, action: "PUSH" }, "", path);
36
- });
37
- });
38
- describe("replace", () => {
39
- it("should replace current history entry", () => {
40
- const path = "/test";
41
- const data = { title: "Test" };
42
- history.replace(path, data);
43
- expect(mockHistory.replaceState).toHaveBeenCalledWith({ path, data, action: "REPLACE" }, "", path);
44
- });
45
- });
46
- describe("navigation methods", () => {
47
- it("should call history.back()", () => {
48
- history.back();
49
- expect(mockHistory.back).toHaveBeenCalled();
50
- });
51
- it("should call history.forward()", () => {
52
- history.forward();
53
- expect(mockHistory.forward).toHaveBeenCalled();
54
- });
55
- it("should call history.go()", () => {
56
- const delta = 2;
57
- history.go(delta);
58
- expect(mockHistory.go).toHaveBeenCalledWith(delta);
59
- });
60
- });
61
- describe("getCurrentState", () => {
62
- it("should return current state", () => {
63
- const mockState = {
64
- path: "/test",
65
- data: { title: "Test" },
66
- };
67
- mockHistory.state = mockState;
68
- const state = history.getCurrentState();
69
- expect(state).toEqual(mockState);
70
- });
71
- it("should return default state when history.state is null", () => {
72
- mockHistory.state = null;
73
- mockLocation.pathname = "/default";
74
- const state = history.getCurrentState();
75
- expect(state).toEqual({ path: "/default" });
76
- });
77
- });
78
- describe("addListener", () => {
79
- it("should add and remove listener", () => {
80
- const listener = jest.fn();
81
- const removeListener = history.addListener(listener);
82
- const testState = {
83
- path: "/test",
84
- };
85
- const popStateEvent = new PopStateEvent("popstate", {
86
- state: testState,
87
- });
88
- window.dispatchEvent(popStateEvent);
89
- expect(listener).toHaveBeenCalledWith(testState);
90
- // Delete listener
91
- removeListener();
92
- // change state and dispatch event
93
- const newState = { path: "/new" };
94
- const newPopStateEvent = new PopStateEvent("popstate", {
95
- state: newState,
96
- });
97
- window.dispatchEvent(newPopStateEvent);
98
- // listener should not be called anymore
99
- expect(listener).toHaveBeenCalledTimes(1);
100
- });
101
- it("should not add duplicate listener", () => {
102
- const listener = jest.fn();
103
- // add listener twice
104
- history.addListener(listener);
105
- history.addListener(listener);
106
- // change state and dispatch event
107
- const testState = { path: "/test" };
108
- const popStateEvent = new PopStateEvent("popstate", { state: testState });
109
- window.dispatchEvent(popStateEvent);
110
- // listener should be called only once
111
- expect(listener).toHaveBeenCalledTimes(1);
112
- });
113
- });
114
- });