@solidjs/router 0.16.0 → 0.16.1
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/data/action.js +6 -5
- package/dist/data/createAsync.js +3 -0
- package/dist/data/events.d.ts +8 -1
- package/dist/data/events.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.jsx +1 -1
- package/dist/routers/HashRouter.js +1 -1
- package/dist/routers/MemoryRouter.js +1 -1
- package/dist/routers/Router.js +1 -1
- package/dist/routing.d.ts +2 -1
- package/dist/routing.js +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +8 -0
- package/package.json +2 -2
- package/dist/src/components.d.ts +0 -31
- package/dist/src/components.jsx +0 -39
- package/dist/src/data/action.d.ts +0 -17
- package/dist/src/data/action.js +0 -163
- package/dist/src/data/action.spec.d.ts +0 -1
- package/dist/src/data/action.spec.js +0 -297
- package/dist/src/data/createAsync.d.ts +0 -32
- package/dist/src/data/createAsync.js +0 -96
- package/dist/src/data/createAsync.spec.d.ts +0 -1
- package/dist/src/data/createAsync.spec.js +0 -196
- package/dist/src/data/events.d.ts +0 -9
- package/dist/src/data/events.js +0 -123
- package/dist/src/data/events.spec.d.ts +0 -1
- package/dist/src/data/events.spec.js +0 -567
- package/dist/src/data/index.d.ts +0 -4
- package/dist/src/data/index.js +0 -4
- package/dist/src/data/query.d.ts +0 -23
- package/dist/src/data/query.js +0 -232
- package/dist/src/data/query.spec.d.ts +0 -1
- package/dist/src/data/query.spec.js +0 -354
- package/dist/src/data/response.d.ts +0 -4
- package/dist/src/data/response.js +0 -42
- package/dist/src/data/response.spec.d.ts +0 -1
- package/dist/src/data/response.spec.js +0 -165
- package/dist/src/index.d.ts +0 -7
- package/dist/src/index.jsx +0 -6
- package/dist/src/lifecycle.d.ts +0 -5
- package/dist/src/lifecycle.js +0 -69
- package/dist/src/routers/HashRouter.d.ts +0 -9
- package/dist/src/routers/HashRouter.js +0 -41
- package/dist/src/routers/MemoryRouter.d.ts +0 -24
- package/dist/src/routers/MemoryRouter.js +0 -57
- package/dist/src/routers/Router.d.ts +0 -9
- package/dist/src/routers/Router.js +0 -45
- package/dist/src/routers/StaticRouter.d.ts +0 -6
- package/dist/src/routers/StaticRouter.js +0 -15
- package/dist/src/routers/components.d.ts +0 -27
- package/dist/src/routers/components.jsx +0 -118
- package/dist/src/routers/createRouter.d.ts +0 -10
- package/dist/src/routers/createRouter.js +0 -41
- package/dist/src/routers/index.d.ts +0 -11
- package/dist/src/routers/index.js +0 -6
- package/dist/src/routing.d.ts +0 -175
- package/dist/src/routing.js +0 -560
- package/dist/src/types.d.ts +0 -200
- package/dist/src/types.js +0 -1
- package/dist/src/utils.d.ts +0 -13
- package/dist/src/utils.js +0 -185
- package/dist/test/helpers.d.ts +0 -6
- package/dist/test/helpers.js +0 -50
package/dist/src/data/events.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { delegateEvents } from "solid-js/web";
|
|
2
|
-
import { onCleanup } from "solid-js";
|
|
3
|
-
import { actions } from "./action.js";
|
|
4
|
-
import { mockBase } from "../utils.js";
|
|
5
|
-
export function setupNativeEvents({ preload = true, explicitLinks = false, actionBase = "/_server", transformUrl } = {}) {
|
|
6
|
-
return (router) => {
|
|
7
|
-
const basePath = router.base.path();
|
|
8
|
-
const navigateFromRoute = router.navigatorFactory(router.base);
|
|
9
|
-
let preloadTimeout;
|
|
10
|
-
let lastElement;
|
|
11
|
-
function isSvg(el) {
|
|
12
|
-
return el.namespaceURI === "http://www.w3.org/2000/svg";
|
|
13
|
-
}
|
|
14
|
-
function handleAnchor(evt) {
|
|
15
|
-
if (evt.defaultPrevented ||
|
|
16
|
-
evt.button !== 0 ||
|
|
17
|
-
evt.metaKey ||
|
|
18
|
-
evt.altKey ||
|
|
19
|
-
evt.ctrlKey ||
|
|
20
|
-
evt.shiftKey)
|
|
21
|
-
return;
|
|
22
|
-
const a = evt
|
|
23
|
-
.composedPath()
|
|
24
|
-
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
|
25
|
-
if (!a || (explicitLinks && !a.hasAttribute("link")))
|
|
26
|
-
return;
|
|
27
|
-
const svg = isSvg(a);
|
|
28
|
-
const href = svg ? a.href.baseVal : a.href;
|
|
29
|
-
const target = svg ? a.target.baseVal : a.target;
|
|
30
|
-
if (target || (!href && !a.hasAttribute("state")))
|
|
31
|
-
return;
|
|
32
|
-
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
33
|
-
if (a.hasAttribute("download") || (rel && rel.includes("external")))
|
|
34
|
-
return;
|
|
35
|
-
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
|
36
|
-
if (url.origin !== window.location.origin ||
|
|
37
|
-
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
38
|
-
return;
|
|
39
|
-
return [a, url];
|
|
40
|
-
}
|
|
41
|
-
function handleAnchorClick(evt) {
|
|
42
|
-
const res = handleAnchor(evt);
|
|
43
|
-
if (!res)
|
|
44
|
-
return;
|
|
45
|
-
const [a, url] = res;
|
|
46
|
-
const to = router.parsePath(url.pathname + url.search + url.hash);
|
|
47
|
-
const state = a.getAttribute("state");
|
|
48
|
-
evt.preventDefault();
|
|
49
|
-
navigateFromRoute(to, {
|
|
50
|
-
resolve: false,
|
|
51
|
-
replace: a.hasAttribute("replace"),
|
|
52
|
-
scroll: !a.hasAttribute("noscroll"),
|
|
53
|
-
state: state ? JSON.parse(state) : undefined
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function handleAnchorPreload(evt) {
|
|
57
|
-
const res = handleAnchor(evt);
|
|
58
|
-
if (!res)
|
|
59
|
-
return;
|
|
60
|
-
const [a, url] = res;
|
|
61
|
-
transformUrl && (url.pathname = transformUrl(url.pathname));
|
|
62
|
-
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
|
63
|
-
}
|
|
64
|
-
function handleAnchorMove(evt) {
|
|
65
|
-
clearTimeout(preloadTimeout);
|
|
66
|
-
const res = handleAnchor(evt);
|
|
67
|
-
if (!res)
|
|
68
|
-
return (lastElement = null);
|
|
69
|
-
const [a, url] = res;
|
|
70
|
-
if (lastElement === a)
|
|
71
|
-
return;
|
|
72
|
-
transformUrl && (url.pathname = transformUrl(url.pathname));
|
|
73
|
-
preloadTimeout = setTimeout(() => {
|
|
74
|
-
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
|
75
|
-
lastElement = a;
|
|
76
|
-
}, 20);
|
|
77
|
-
}
|
|
78
|
-
function handleFormSubmit(evt) {
|
|
79
|
-
if (evt.defaultPrevented)
|
|
80
|
-
return;
|
|
81
|
-
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
|
|
82
|
-
? evt.submitter.getAttribute("formaction")
|
|
83
|
-
: evt.target.getAttribute("action");
|
|
84
|
-
if (!actionRef)
|
|
85
|
-
return;
|
|
86
|
-
if (!actionRef.startsWith("https://action/")) {
|
|
87
|
-
// normalize server actions
|
|
88
|
-
const url = new URL(actionRef, mockBase);
|
|
89
|
-
actionRef = router.parsePath(url.pathname + url.search);
|
|
90
|
-
if (!actionRef.startsWith(actionBase))
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (evt.target.method.toUpperCase() !== "POST")
|
|
94
|
-
throw new Error("Only POST forms are supported for Actions");
|
|
95
|
-
const handler = actions.get(actionRef);
|
|
96
|
-
if (handler) {
|
|
97
|
-
evt.preventDefault();
|
|
98
|
-
const data = new FormData(evt.target, evt.submitter);
|
|
99
|
-
handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data"
|
|
100
|
-
? data
|
|
101
|
-
: new URLSearchParams(data));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// ensure delegated event run first
|
|
105
|
-
delegateEvents(["click", "submit"]);
|
|
106
|
-
document.addEventListener("click", handleAnchorClick);
|
|
107
|
-
if (preload) {
|
|
108
|
-
document.addEventListener("mousemove", handleAnchorMove, { passive: true });
|
|
109
|
-
document.addEventListener("focusin", handleAnchorPreload, { passive: true });
|
|
110
|
-
document.addEventListener("touchstart", handleAnchorPreload, { passive: true });
|
|
111
|
-
}
|
|
112
|
-
document.addEventListener("submit", handleFormSubmit);
|
|
113
|
-
onCleanup(() => {
|
|
114
|
-
document.removeEventListener("click", handleAnchorClick);
|
|
115
|
-
if (preload) {
|
|
116
|
-
document.removeEventListener("mousemove", handleAnchorMove);
|
|
117
|
-
document.removeEventListener("focusin", handleAnchorPreload);
|
|
118
|
-
document.removeEventListener("touchstart", handleAnchorPreload);
|
|
119
|
-
}
|
|
120
|
-
document.removeEventListener("submit", handleFormSubmit);
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
import { createRoot } from "solid-js";
|
|
2
|
-
import { vi } from "vitest";
|
|
3
|
-
import { setupNativeEvents } from "./events.js";
|
|
4
|
-
import { createMockRouter } from "../../test/helpers.js";
|
|
5
|
-
vi.mock("../src/data/action.js", () => ({
|
|
6
|
-
actions: new Map()
|
|
7
|
-
}));
|
|
8
|
-
import { actions } from "./action.js";
|
|
9
|
-
vi.mock("../src/utils.js", () => ({
|
|
10
|
-
mockBase: "https://action"
|
|
11
|
-
}));
|
|
12
|
-
class MockNode {
|
|
13
|
-
nodeName;
|
|
14
|
-
namespaceURI;
|
|
15
|
-
hasAttribute;
|
|
16
|
-
getAttribute;
|
|
17
|
-
href;
|
|
18
|
-
target;
|
|
19
|
-
constructor(tagName, attributes = {}) {
|
|
20
|
-
this.nodeName = tagName.toUpperCase();
|
|
21
|
-
this.namespaceURI = tagName === "a" && attributes.svg ? "http://www.w3.org/2000/svg" : null;
|
|
22
|
-
this.hasAttribute = (name) => name in attributes;
|
|
23
|
-
this.getAttribute = (name) => attributes[name] || null;
|
|
24
|
-
this.href = attributes.href || "";
|
|
25
|
-
this.target = attributes.target || "";
|
|
26
|
-
if (tagName === "a" && attributes.svg) {
|
|
27
|
-
this.href = { baseVal: attributes.href || "" };
|
|
28
|
-
this.target = { baseVal: attributes.target || "" };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
global.Node = MockNode;
|
|
33
|
-
const createMockElement = (tagName, attributes = {}) => {
|
|
34
|
-
return new MockNode(tagName, attributes);
|
|
35
|
-
};
|
|
36
|
-
const createMockEvent = (type, target, options = {}) => {
|
|
37
|
-
return {
|
|
38
|
-
type,
|
|
39
|
-
target,
|
|
40
|
-
defaultPrevented: false,
|
|
41
|
-
button: options.button || 0,
|
|
42
|
-
metaKey: options.metaKey || false,
|
|
43
|
-
altKey: options.altKey || false,
|
|
44
|
-
ctrlKey: options.ctrlKey || false,
|
|
45
|
-
shiftKey: options.shiftKey || false,
|
|
46
|
-
submitter: options.submitter || null,
|
|
47
|
-
preventDefault: vi.fn(),
|
|
48
|
-
composedPath: () => options.path || [target]
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
describe("setupNativeEvents", () => {
|
|
52
|
-
let mockRouter;
|
|
53
|
-
let addEventListener;
|
|
54
|
-
let removeEventListener;
|
|
55
|
-
let mockWindow;
|
|
56
|
-
let originalDocument;
|
|
57
|
-
let originalWindow;
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
mockRouter = createMockRouter();
|
|
60
|
-
addEventListener = vi.fn();
|
|
61
|
-
removeEventListener = vi.fn();
|
|
62
|
-
actions.clear();
|
|
63
|
-
originalDocument = global.document;
|
|
64
|
-
global.document = {
|
|
65
|
-
addEventListener,
|
|
66
|
-
removeEventListener,
|
|
67
|
-
baseURI: "https://example.com/"
|
|
68
|
-
};
|
|
69
|
-
originalWindow = global.window;
|
|
70
|
-
mockWindow = {
|
|
71
|
-
location: { origin: "https://example.com" }
|
|
72
|
-
};
|
|
73
|
-
global.window = mockWindow;
|
|
74
|
-
global.URL = class MockURL {
|
|
75
|
-
origin;
|
|
76
|
-
pathname;
|
|
77
|
-
search;
|
|
78
|
-
hash;
|
|
79
|
-
constructor(url, base) {
|
|
80
|
-
const fullUrl = base ? new URL(url, base).href : url;
|
|
81
|
-
const parsed = new URL(fullUrl);
|
|
82
|
-
this.origin = parsed.origin;
|
|
83
|
-
this.pathname = parsed.pathname;
|
|
84
|
-
this.search = parsed.search;
|
|
85
|
-
this.hash = parsed.hash;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
afterEach(() => {
|
|
90
|
-
global.document = originalDocument;
|
|
91
|
-
global.window = originalWindow;
|
|
92
|
-
vi.clearAllMocks();
|
|
93
|
-
});
|
|
94
|
-
test("should set up default event listeners", () => {
|
|
95
|
-
return createRoot(() => {
|
|
96
|
-
setupNativeEvents()(mockRouter);
|
|
97
|
-
expect(addEventListener).toHaveBeenCalledWith("click", expect.any(Function));
|
|
98
|
-
expect(addEventListener).toHaveBeenCalledWith("submit", expect.any(Function));
|
|
99
|
-
expect(addEventListener).toHaveBeenCalledWith("mousemove", expect.any(Function), {
|
|
100
|
-
passive: true
|
|
101
|
-
});
|
|
102
|
-
expect(addEventListener).toHaveBeenCalledWith("focusin", expect.any(Function), {
|
|
103
|
-
passive: true
|
|
104
|
-
});
|
|
105
|
-
expect(addEventListener).toHaveBeenCalledWith("touchstart", expect.any(Function), {
|
|
106
|
-
passive: true
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
test("should skip preload listeners when preload disabled", () => {
|
|
111
|
-
return createRoot(() => {
|
|
112
|
-
setupNativeEvents({ preload: false })(mockRouter);
|
|
113
|
-
expect(addEventListener).toHaveBeenCalledWith("click", expect.any(Function));
|
|
114
|
-
expect(addEventListener).toHaveBeenCalledWith("submit", expect.any(Function));
|
|
115
|
-
expect(addEventListener).not.toHaveBeenCalledWith("mousemove", expect.any(Function), {
|
|
116
|
-
passive: true
|
|
117
|
-
});
|
|
118
|
-
expect(addEventListener).not.toHaveBeenCalledWith("focusin", expect.any(Function), {
|
|
119
|
-
passive: true
|
|
120
|
-
});
|
|
121
|
-
expect(addEventListener).not.toHaveBeenCalledWith("touchstart", expect.any(Function), {
|
|
122
|
-
passive: true
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
test("should clean up event listeners on cleanup", () => {
|
|
127
|
-
return createRoot(dispose => {
|
|
128
|
-
setupNativeEvents()(mockRouter);
|
|
129
|
-
dispose();
|
|
130
|
-
expect(removeEventListener).toHaveBeenCalledWith("click", expect.any(Function));
|
|
131
|
-
expect(removeEventListener).toHaveBeenCalledWith("submit", expect.any(Function));
|
|
132
|
-
expect(removeEventListener).toHaveBeenCalledWith("mousemove", expect.any(Function));
|
|
133
|
-
expect(removeEventListener).toHaveBeenCalledWith("focusin", expect.any(Function));
|
|
134
|
-
expect(removeEventListener).toHaveBeenCalledWith("touchstart", expect.any(Function));
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
describe("anchor link handling", () => {
|
|
139
|
-
let mockRouter;
|
|
140
|
-
let clickHandler;
|
|
141
|
-
let originalDocument;
|
|
142
|
-
let originalWindow;
|
|
143
|
-
beforeEach(() => {
|
|
144
|
-
mockRouter = createMockRouter();
|
|
145
|
-
originalDocument = global.document;
|
|
146
|
-
global.document = {
|
|
147
|
-
addEventListener: (type, handler) => {
|
|
148
|
-
if (type === "click")
|
|
149
|
-
clickHandler = handler;
|
|
150
|
-
},
|
|
151
|
-
removeEventListener: vi.fn(),
|
|
152
|
-
baseURI: "https://example.com/"
|
|
153
|
-
};
|
|
154
|
-
originalWindow = global.window;
|
|
155
|
-
global.window = {
|
|
156
|
-
location: { origin: "https://example.com" }
|
|
157
|
-
};
|
|
158
|
-
global.URL = class MockURL {
|
|
159
|
-
origin;
|
|
160
|
-
pathname;
|
|
161
|
-
search;
|
|
162
|
-
hash;
|
|
163
|
-
constructor(url) {
|
|
164
|
-
if (url.startsWith("/")) {
|
|
165
|
-
this.origin = "https://example.com";
|
|
166
|
-
this.pathname = url;
|
|
167
|
-
this.search = "";
|
|
168
|
-
this.hash = "";
|
|
169
|
-
}
|
|
170
|
-
else if (url.startsWith("https://example.com")) {
|
|
171
|
-
this.origin = "https://example.com";
|
|
172
|
-
this.pathname = url.replace("https://example.com", "") || "/";
|
|
173
|
-
this.search = "";
|
|
174
|
-
this.hash = "";
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
this.origin = "https://other.com";
|
|
178
|
-
this.pathname = "/";
|
|
179
|
-
this.search = "";
|
|
180
|
-
this.hash = "";
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
afterEach(() => {
|
|
186
|
-
global.document = originalDocument;
|
|
187
|
-
global.window = originalWindow;
|
|
188
|
-
});
|
|
189
|
-
test("should handle internal link clicks", () => {
|
|
190
|
-
return createRoot(() => {
|
|
191
|
-
const navigateFromRoute = vi.fn();
|
|
192
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
193
|
-
setupNativeEvents()(mockRouter);
|
|
194
|
-
const link = createMockElement("a", { href: "/test-page" });
|
|
195
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
196
|
-
clickHandler(event);
|
|
197
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
198
|
-
expect(navigateFromRoute).toHaveBeenCalledWith("/test-page", {
|
|
199
|
-
resolve: false,
|
|
200
|
-
replace: false,
|
|
201
|
-
scroll: true,
|
|
202
|
-
state: undefined
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
test("should ignore external link clicks", () => {
|
|
207
|
-
return createRoot(() => {
|
|
208
|
-
setupNativeEvents()(mockRouter);
|
|
209
|
-
const link = createMockElement("a", { href: "https://external.com/page" });
|
|
210
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
211
|
-
clickHandler(event);
|
|
212
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
test("should ignore clicks with modifier keys", () => {
|
|
216
|
-
return createRoot(() => {
|
|
217
|
-
setupNativeEvents()(mockRouter);
|
|
218
|
-
const link = createMockElement("a", { href: "/test-page" });
|
|
219
|
-
const event = createMockEvent("click", link, {
|
|
220
|
-
path: [link],
|
|
221
|
-
metaKey: true
|
|
222
|
-
});
|
|
223
|
-
clickHandler(event);
|
|
224
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
/**
|
|
228
|
-
* @todo ?
|
|
229
|
-
*/
|
|
230
|
-
test("should ignore non-zero button clicks", () => {
|
|
231
|
-
return createRoot(() => {
|
|
232
|
-
setupNativeEvents()(mockRouter);
|
|
233
|
-
const link = createMockElement("a", { href: "/test-page" });
|
|
234
|
-
const event = createMockEvent("click", link, {
|
|
235
|
-
path: [link],
|
|
236
|
-
button: 1
|
|
237
|
-
});
|
|
238
|
-
clickHandler(event);
|
|
239
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
test("should handle replace attribute", () => {
|
|
243
|
-
return createRoot(() => {
|
|
244
|
-
const navigateFromRoute = vi.fn();
|
|
245
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
246
|
-
setupNativeEvents()(mockRouter);
|
|
247
|
-
const link = createMockElement("a", { href: "/test-page", replace: "true" });
|
|
248
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
249
|
-
clickHandler(event);
|
|
250
|
-
expect(navigateFromRoute).toHaveBeenCalledWith("/test-page", {
|
|
251
|
-
resolve: false,
|
|
252
|
-
replace: true,
|
|
253
|
-
scroll: true,
|
|
254
|
-
state: undefined
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
test("should handle noscroll attribute", () => {
|
|
259
|
-
return createRoot(() => {
|
|
260
|
-
const navigateFromRoute = vi.fn();
|
|
261
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
262
|
-
setupNativeEvents()(mockRouter);
|
|
263
|
-
const link = createMockElement("a", { href: "/test-page", noscroll: "true" });
|
|
264
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
265
|
-
clickHandler(event);
|
|
266
|
-
expect(navigateFromRoute).toHaveBeenCalledWith("/test-page", {
|
|
267
|
-
resolve: false,
|
|
268
|
-
replace: false,
|
|
269
|
-
scroll: false,
|
|
270
|
-
state: undefined
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
test("should handle state attribute", () => {
|
|
275
|
-
return createRoot(() => {
|
|
276
|
-
const navigateFromRoute = vi.fn();
|
|
277
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
278
|
-
setupNativeEvents()(mockRouter);
|
|
279
|
-
const stateData = '{"key":"value"}';
|
|
280
|
-
const link = createMockElement("a", { href: "/test-page", state: stateData });
|
|
281
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
282
|
-
clickHandler(event);
|
|
283
|
-
expect(navigateFromRoute).toHaveBeenCalledWith("/test-page", {
|
|
284
|
-
resolve: false,
|
|
285
|
-
replace: false,
|
|
286
|
-
scroll: true,
|
|
287
|
-
state: { key: "value" }
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
/**
|
|
292
|
-
* @todo ?
|
|
293
|
-
*/
|
|
294
|
-
test("should handle SVG links", () => {
|
|
295
|
-
return createRoot(() => {
|
|
296
|
-
const navigateFromRoute = vi.fn();
|
|
297
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
298
|
-
setupNativeEvents()(mockRouter);
|
|
299
|
-
const link = createMockElement("a", { href: "/test-page", svg: "true" });
|
|
300
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
301
|
-
clickHandler(event);
|
|
302
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
303
|
-
expect(navigateFromRoute).toHaveBeenCalledWith("/test-page", {
|
|
304
|
-
resolve: false,
|
|
305
|
-
replace: false,
|
|
306
|
-
scroll: true,
|
|
307
|
-
state: undefined
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
test("should ignore links with download attribute", () => {
|
|
312
|
-
return createRoot(() => {
|
|
313
|
-
setupNativeEvents()(mockRouter);
|
|
314
|
-
const link = createMockElement("a", { href: "/test-page", download: "file.pdf" });
|
|
315
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
316
|
-
clickHandler(event);
|
|
317
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
test("should ignore links with external rel", () => {
|
|
321
|
-
return createRoot(() => {
|
|
322
|
-
setupNativeEvents()(mockRouter);
|
|
323
|
-
const link = createMockElement("a", { href: "/test-page", rel: "external" });
|
|
324
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
325
|
-
clickHandler(event);
|
|
326
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
test("should ignore links with target", () => {
|
|
330
|
-
return createRoot(() => {
|
|
331
|
-
setupNativeEvents()(mockRouter);
|
|
332
|
-
const link = createMockElement("a", { href: "/test-page", target: "_blank" });
|
|
333
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
334
|
-
clickHandler(event);
|
|
335
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
/**
|
|
339
|
-
* @todo ?
|
|
340
|
-
*/
|
|
341
|
-
test("should require `link` attribute when `explicitLinks` enabled", () => {
|
|
342
|
-
return createRoot(() => {
|
|
343
|
-
// Reset with explicitLinks enabled
|
|
344
|
-
setupNativeEvents({ preload: true, explicitLinks: true })(mockRouter);
|
|
345
|
-
const link = createMockElement("a", { href: "/test-page" });
|
|
346
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
347
|
-
clickHandler(event);
|
|
348
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
test("should handle links with `link` attribute when `explicitLinks` enabled", () => {
|
|
352
|
-
return createRoot(() => {
|
|
353
|
-
const navigateFromRoute = vi.fn();
|
|
354
|
-
mockRouter.navigatorFactory = () => navigateFromRoute;
|
|
355
|
-
// Reset with explicitLinks enabled
|
|
356
|
-
setupNativeEvents({ preload: true, explicitLinks: true })(mockRouter);
|
|
357
|
-
const link = createMockElement("a", { href: "/test-page", link: "true" });
|
|
358
|
-
const event = createMockEvent("click", link, { path: [link] });
|
|
359
|
-
clickHandler(event);
|
|
360
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
361
|
-
expect(navigateFromRoute).toHaveBeenCalled();
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
describe("form submit handling", () => {
|
|
366
|
-
let mockRouter;
|
|
367
|
-
let submitHandler;
|
|
368
|
-
let originalDocument;
|
|
369
|
-
let disposeEvents;
|
|
370
|
-
beforeEach(() => {
|
|
371
|
-
mockRouter = createMockRouter();
|
|
372
|
-
actions.clear();
|
|
373
|
-
originalDocument = global.document;
|
|
374
|
-
global.document = {
|
|
375
|
-
addEventListener: (type, handler) => {
|
|
376
|
-
if (type === "submit")
|
|
377
|
-
submitHandler = handler;
|
|
378
|
-
},
|
|
379
|
-
removeEventListener: vi.fn()
|
|
380
|
-
};
|
|
381
|
-
global.URL = class MockURL {
|
|
382
|
-
pathname;
|
|
383
|
-
search;
|
|
384
|
-
constructor(url, base) {
|
|
385
|
-
this.pathname = url.startsWith("/") ? url : "/action";
|
|
386
|
-
this.search = "";
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
disposeEvents = createRoot(dispose => {
|
|
390
|
-
setupNativeEvents()(mockRouter);
|
|
391
|
-
return dispose;
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
afterEach(() => {
|
|
395
|
-
disposeEvents?.();
|
|
396
|
-
global.document = originalDocument;
|
|
397
|
-
});
|
|
398
|
-
test("handle action form submission", () => {
|
|
399
|
-
return createRoot(() => {
|
|
400
|
-
const mockActionFn = vi.fn();
|
|
401
|
-
const mockAction = {
|
|
402
|
-
url: "https://action/test-action",
|
|
403
|
-
with: vi.fn(),
|
|
404
|
-
call: mockActionFn
|
|
405
|
-
};
|
|
406
|
-
actions.set("https://action/test-action", mockAction);
|
|
407
|
-
const form = {
|
|
408
|
-
getAttribute: (name) => (name === "action" ? "https://action/test-action" : null),
|
|
409
|
-
method: "POST",
|
|
410
|
-
enctype: "application/x-www-form-urlencoded"
|
|
411
|
-
};
|
|
412
|
-
const event = {
|
|
413
|
-
defaultPrevented: false,
|
|
414
|
-
target: form,
|
|
415
|
-
submitter: null,
|
|
416
|
-
preventDefault: vi.fn()
|
|
417
|
-
};
|
|
418
|
-
// Mock FormData and URLSearchParams
|
|
419
|
-
global.FormData = vi.fn(() => ({}));
|
|
420
|
-
global.URLSearchParams = vi.fn(() => ({}));
|
|
421
|
-
submitHandler(event);
|
|
422
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
423
|
-
expect(mockActionFn).toHaveBeenCalledWith({ r: mockRouter, f: form }, {});
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
/**
|
|
427
|
-
* @todo ?
|
|
428
|
-
*/
|
|
429
|
-
test("handle multipart form data", () => {
|
|
430
|
-
return createRoot(() => {
|
|
431
|
-
const mockActionFn = vi.fn();
|
|
432
|
-
const mockAction = {
|
|
433
|
-
url: "https://action/test-action",
|
|
434
|
-
with: vi.fn(),
|
|
435
|
-
call: mockActionFn
|
|
436
|
-
};
|
|
437
|
-
actions.set("https://action/test-action", mockAction);
|
|
438
|
-
const form = {
|
|
439
|
-
getAttribute: (name) => (name === "action" ? "https://action/test-action" : null),
|
|
440
|
-
method: "POST",
|
|
441
|
-
enctype: "multipart/form-data"
|
|
442
|
-
};
|
|
443
|
-
const event = {
|
|
444
|
-
defaultPrevented: false,
|
|
445
|
-
target: form,
|
|
446
|
-
submitter: null,
|
|
447
|
-
preventDefault: vi.fn()
|
|
448
|
-
};
|
|
449
|
-
const mockFormData = {};
|
|
450
|
-
global.FormData = vi.fn(() => mockFormData);
|
|
451
|
-
submitHandler(event);
|
|
452
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
453
|
-
expect(mockActionFn).toHaveBeenCalledWith({ r: mockRouter, f: form }, mockFormData);
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
test("Throw when using a `GET` action", () => {
|
|
457
|
-
return createRoot(() => {
|
|
458
|
-
const form = {
|
|
459
|
-
getAttribute: () => "https://action/test-action",
|
|
460
|
-
method: "GET"
|
|
461
|
-
};
|
|
462
|
-
const event = {
|
|
463
|
-
defaultPrevented: false,
|
|
464
|
-
target: form,
|
|
465
|
-
submitter: null,
|
|
466
|
-
preventDefault: vi.fn()
|
|
467
|
-
};
|
|
468
|
-
expect(() => submitHandler(event)).toThrow("Only POST forms are supported for Actions");
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
test("Throw when using a `PATCH` action", () => {
|
|
472
|
-
return createRoot(() => {
|
|
473
|
-
const form = {
|
|
474
|
-
getAttribute: () => "https://action/test-action",
|
|
475
|
-
method: "PATCH"
|
|
476
|
-
};
|
|
477
|
-
const event = {
|
|
478
|
-
defaultPrevented: false,
|
|
479
|
-
target: form,
|
|
480
|
-
submitter: null,
|
|
481
|
-
preventDefault: vi.fn()
|
|
482
|
-
};
|
|
483
|
-
expect(() => submitHandler(event)).toThrow("Only POST forms are supported for Actions");
|
|
484
|
-
});
|
|
485
|
-
});
|
|
486
|
-
test("Throw when using a `DELETE` action", () => {
|
|
487
|
-
return createRoot(() => {
|
|
488
|
-
const form = {
|
|
489
|
-
getAttribute: () => "https://action/test-action",
|
|
490
|
-
method: "DELETE"
|
|
491
|
-
};
|
|
492
|
-
const event = {
|
|
493
|
-
defaultPrevented: false,
|
|
494
|
-
target: form,
|
|
495
|
-
submitter: null,
|
|
496
|
-
preventDefault: vi.fn()
|
|
497
|
-
};
|
|
498
|
-
expect(() => submitHandler(event)).toThrow("Only POST forms are supported for Actions");
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
test("ignore forms without action handlers", () => {
|
|
502
|
-
return createRoot(() => {
|
|
503
|
-
const form = {
|
|
504
|
-
getAttribute: () => "https://action/unknown-action",
|
|
505
|
-
method: "POST"
|
|
506
|
-
};
|
|
507
|
-
const event = {
|
|
508
|
-
defaultPrevented: false,
|
|
509
|
-
target: form,
|
|
510
|
-
submitter: null,
|
|
511
|
-
preventDefault: vi.fn()
|
|
512
|
-
};
|
|
513
|
-
submitHandler(event);
|
|
514
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
test("handle submitter formaction", () => {
|
|
518
|
-
return createRoot(() => {
|
|
519
|
-
const mockActionFn = vi.fn();
|
|
520
|
-
const mockAction = {
|
|
521
|
-
url: "https://action/submitter-action",
|
|
522
|
-
with: vi.fn(),
|
|
523
|
-
call: mockActionFn
|
|
524
|
-
};
|
|
525
|
-
actions.set("https://action/submitter-action", mockAction);
|
|
526
|
-
const form = {
|
|
527
|
-
getAttribute: () => "https://action/form-action",
|
|
528
|
-
method: "POST"
|
|
529
|
-
};
|
|
530
|
-
const submitter = {
|
|
531
|
-
hasAttribute: (name) => name === "formaction",
|
|
532
|
-
getAttribute: (name) => name === "formaction" ? "https://action/submitter-action" : null
|
|
533
|
-
};
|
|
534
|
-
const event = {
|
|
535
|
-
defaultPrevented: false,
|
|
536
|
-
target: form,
|
|
537
|
-
submitter,
|
|
538
|
-
preventDefault: vi.fn()
|
|
539
|
-
};
|
|
540
|
-
global.FormData = vi.fn(() => ({}));
|
|
541
|
-
global.URLSearchParams = vi.fn(() => ({}));
|
|
542
|
-
submitHandler(event);
|
|
543
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
544
|
-
expect(mockActionFn).toHaveBeenCalled();
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
/**
|
|
548
|
-
* @todo ?
|
|
549
|
-
*/
|
|
550
|
-
test("ignore forms with different action base", () => {
|
|
551
|
-
return createRoot(() => {
|
|
552
|
-
mockRouter.parsePath = path => path;
|
|
553
|
-
const form = {
|
|
554
|
-
getAttribute: () => "/different-base/action",
|
|
555
|
-
method: "POST"
|
|
556
|
-
};
|
|
557
|
-
const event = {
|
|
558
|
-
defaultPrevented: false,
|
|
559
|
-
target: form,
|
|
560
|
-
submitter: null,
|
|
561
|
-
preventDefault: vi.fn()
|
|
562
|
-
};
|
|
563
|
-
submitHandler(event);
|
|
564
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
});
|