swup 4.3.3 → 4.4.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/Swup.cjs +1 -1
- package/dist/Swup.cjs.map +1 -1
- package/dist/Swup.modern.js +1 -1
- package/dist/Swup.modern.js.map +1 -1
- package/dist/Swup.module.js +1 -1
- package/dist/Swup.module.js.map +1 -1
- package/dist/Swup.umd.js +1 -1
- package/dist/Swup.umd.js.map +1 -1
- package/dist/types/Swup.d.ts +3 -1
- package/dist/types/modules/Hooks.d.ts +5 -0
- package/dist/types/modules/Visit.d.ts +2 -0
- package/dist/types/modules/fetchPage.d.ts +8 -2
- package/dist/types/modules/renderPage.d.ts +1 -2
- package/package.json +12 -17
- package/src/Swup.ts +4 -1
- package/src/modules/Hooks.ts +5 -0
- package/src/modules/Visit.ts +3 -0
- package/src/modules/fetchPage.ts +47 -8
- package/src/modules/navigate.ts +45 -32
- package/src/modules/renderPage.ts +1 -7
- package/dist/types/__test__/index.test.d.ts +0 -1
- package/dist/types/helpers/__test__/matchPath.test.d.ts +0 -1
- package/dist/types/modules/__test__/cache.test.d.ts +0 -1
- package/dist/types/modules/__test__/delegateEvent.test.d.ts +0 -1
- package/dist/types/modules/__test__/hooks.test.d.ts +0 -1
- package/dist/types/modules/__test__/plugins.test.d.ts +0 -1
- package/dist/types/modules/__test__/replaceContent.test.d.ts +0 -1
- package/src/__test__/index.test.ts +0 -83
- package/src/helpers/__test__/matchPath.test.ts +0 -54
- package/src/modules/__test__/cache.test.ts +0 -159
- package/src/modules/__test__/delegateEvent.test.ts +0 -36
- package/src/modules/__test__/hooks.test.ts +0 -319
- package/src/modules/__test__/plugins.test.ts +0 -89
- package/src/modules/__test__/replaceContent.test.ts +0 -91
|
@@ -12,13 +12,19 @@ export interface FetchOptions extends Omit<RequestInit, 'cache'> {
|
|
|
12
12
|
method?: 'GET' | 'POST';
|
|
13
13
|
/** The body of the request: raw string, form data object or URL params. */
|
|
14
14
|
body?: string | FormData | URLSearchParams;
|
|
15
|
+
/** The request timeout in milliseconds. */
|
|
16
|
+
timeout?: number;
|
|
15
17
|
}
|
|
16
18
|
export declare class FetchError extends Error {
|
|
17
19
|
url: string;
|
|
18
|
-
status
|
|
20
|
+
status?: number;
|
|
21
|
+
aborted: boolean;
|
|
22
|
+
timedOut: boolean;
|
|
19
23
|
constructor(message: string, details: {
|
|
20
24
|
url: string;
|
|
21
|
-
status
|
|
25
|
+
status?: number;
|
|
26
|
+
aborted?: boolean;
|
|
27
|
+
timedOut?: boolean;
|
|
22
28
|
});
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
@@ -2,6 +2,5 @@ import Swup from '../Swup.js';
|
|
|
2
2
|
import { PageData } from './fetchPage.js';
|
|
3
3
|
/**
|
|
4
4
|
* Render the next page: replace the content and update scroll position.
|
|
5
|
-
* @returns Promise<void>
|
|
6
5
|
*/
|
|
7
|
-
export declare const renderPage: (this: Swup,
|
|
6
|
+
export declare const renderPage: (this: Swup, page: PageData) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swup",
|
|
3
3
|
"amdName": "Swup",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.4.0",
|
|
5
5
|
"description": "Versatile and extensible page transition library for server-rendered websites",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"source": "./src/Swup.ts",
|
|
@@ -35,17 +35,14 @@
|
|
|
35
35
|
"postinstall": "opencollective-postinstall || true",
|
|
36
36
|
"test": "npm run test:unit && npm run test:e2e",
|
|
37
37
|
"test:ci": "npm run test:unit && npm run test:e2e:ci",
|
|
38
|
-
"test:unit": "vitest run --config ./
|
|
39
|
-
"test:unit:watch": "vitest --config ./
|
|
40
|
-
"test:e2e": "
|
|
41
|
-
"test:e2e:
|
|
42
|
-
"test:e2e:
|
|
43
|
-
"test:e2e:instrument": "nyc instrument --compact=false dist
|
|
44
|
-
"test:e2e:
|
|
45
|
-
"test:e2e:start": "npm run test:e2e:instrument && npm run test:e2e:
|
|
46
|
-
"cy:run": "cypress run",
|
|
47
|
-
"cy:run:record": "cypress run --record",
|
|
48
|
-
"cy:open": "cypress open",
|
|
38
|
+
"test:unit": "vitest run --config ./tests/config/vitest.config.ts",
|
|
39
|
+
"test:unit:watch": "vitest --config ./tests/config/vitest.config.ts",
|
|
40
|
+
"test:e2e": "npx playwright test --config ./tests/config/playwright.config.ts",
|
|
41
|
+
"test:e2e:dev": "npx playwright test --ui --config ./tests/config/playwright.config.ts",
|
|
42
|
+
"test:e2e:install": "npx playwright install --with-deps",
|
|
43
|
+
"test:e2e:instrument": "nyc instrument --compact=false dist tests/fixtures/dist",
|
|
44
|
+
"test:e2e:serve": "npx serve -n -S -L -p 8274 --config ./tests/config/serve.json",
|
|
45
|
+
"test:e2e:start": "npm run test:e2e:instrument && npm run test:e2e:serve",
|
|
49
46
|
"prepare": "husky install"
|
|
50
47
|
},
|
|
51
48
|
"author": "Georgy Marchuk",
|
|
@@ -67,25 +64,23 @@
|
|
|
67
64
|
},
|
|
68
65
|
"devDependencies": {
|
|
69
66
|
"@babel/preset-typescript": "^7.18.6",
|
|
70
|
-
"@
|
|
67
|
+
"@playwright/test": "^1.37.1",
|
|
71
68
|
"@swup/browserslist-config": "^1.0.0",
|
|
72
69
|
"@swup/prettier-config": "^1.0.0",
|
|
73
70
|
"@types/jsdom": "^21.1.1",
|
|
74
71
|
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
|
75
72
|
"@typescript-eslint/parser": "^6.3.0",
|
|
76
|
-
"cypress": "^12.3.0",
|
|
77
73
|
"eslint": "^8.46.0",
|
|
78
74
|
"eslint-config-prettier": "^9.0.0",
|
|
79
75
|
"eslint-plugin-prettier": "^4.2.1",
|
|
80
|
-
"http-server": "^14.1.1",
|
|
81
76
|
"husky": "^8.0.0",
|
|
82
77
|
"istanbul-lib-coverage": "^3.2.0",
|
|
83
78
|
"jsdom": "^22.1.0",
|
|
84
79
|
"microbundle": "^0.15.0",
|
|
85
80
|
"nyc": "^15.1.0",
|
|
86
81
|
"prettier": "^2.8.2",
|
|
87
|
-
"
|
|
88
|
-
"vitest": "^0.
|
|
82
|
+
"serve": "^14.2.1",
|
|
83
|
+
"vitest": "^0.34.3"
|
|
89
84
|
},
|
|
90
85
|
"collective": {
|
|
91
86
|
"type": "opencollective",
|
package/src/Swup.ts
CHANGED
|
@@ -49,6 +49,8 @@ export type Options = {
|
|
|
49
49
|
resolveUrl: (url: string) => string;
|
|
50
50
|
/** Callback for telling swup to ignore certain popstate events. */
|
|
51
51
|
skipPopStateHandling: (event: PopStateEvent) => boolean;
|
|
52
|
+
/** Request timeout in milliseconds. */
|
|
53
|
+
timeout: number;
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
const defaults: Options = {
|
|
@@ -66,7 +68,8 @@ const defaults: Options = {
|
|
|
66
68
|
'X-Requested-With': 'swup',
|
|
67
69
|
'Accept': 'text/html, application/xhtml+xml'
|
|
68
70
|
},
|
|
69
|
-
skipPopStateHandling: (event) => (event.state as HistoryState)?.source !== 'swup'
|
|
71
|
+
skipPopStateHandling: (event) => (event.state as HistoryState)?.source !== 'swup',
|
|
72
|
+
timeout: 0
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
/** Swup page transition library. */
|
package/src/modules/Hooks.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface HookDefinitions {
|
|
|
21
21
|
'disable': undefined;
|
|
22
22
|
'fetch:request': { url: string; options: FetchOptions };
|
|
23
23
|
'fetch:error': { url: string; status: number; response: Response };
|
|
24
|
+
'fetch:timeout': { url: string };
|
|
24
25
|
'history:popstate': { event: PopStateEvent };
|
|
25
26
|
'link:click': { el: HTMLAnchorElement; event: DelegateEvent<MouseEvent> };
|
|
26
27
|
'link:self': undefined;
|
|
@@ -31,6 +32,7 @@ export interface HookDefinitions {
|
|
|
31
32
|
'scroll:top': { options: ScrollIntoViewOptions };
|
|
32
33
|
'scroll:anchor': { hash: string; options: ScrollIntoViewOptions };
|
|
33
34
|
'visit:start': undefined;
|
|
35
|
+
'visit:transition': undefined;
|
|
34
36
|
'visit:end': undefined;
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -40,6 +42,7 @@ export interface HookReturnValues {
|
|
|
40
42
|
'page:load': Promise<PageData>;
|
|
41
43
|
'scroll:top': boolean;
|
|
42
44
|
'scroll:anchor': boolean;
|
|
45
|
+
'visit:transition': Promise<boolean>;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export type HookArguments<T extends HookName> = HookDefinitions[T];
|
|
@@ -131,6 +134,7 @@ export class Hooks {
|
|
|
131
134
|
'disable',
|
|
132
135
|
'fetch:request',
|
|
133
136
|
'fetch:error',
|
|
137
|
+
'fetch:timeout',
|
|
134
138
|
'history:popstate',
|
|
135
139
|
'link:click',
|
|
136
140
|
'link:self',
|
|
@@ -141,6 +145,7 @@ export class Hooks {
|
|
|
141
145
|
'scroll:top',
|
|
142
146
|
'scroll:anchor',
|
|
143
147
|
'visit:start',
|
|
148
|
+
'visit:transition',
|
|
144
149
|
'visit:end'
|
|
145
150
|
];
|
|
146
151
|
|
package/src/modules/Visit.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { HistoryAction, HistoryDirection } from './navigate.js';
|
|
|
3
3
|
|
|
4
4
|
/** An object holding details about the current visit. */
|
|
5
5
|
export interface Visit {
|
|
6
|
+
/** A unique ID to identify this visit */
|
|
7
|
+
id: number;
|
|
6
8
|
/** The previous page, about to leave */
|
|
7
9
|
from: VisitFrom;
|
|
8
10
|
/** The next page, about to enter */
|
|
@@ -92,6 +94,7 @@ export function createVisit(
|
|
|
92
94
|
{ to, from = this.currentPageUrl, hash, el, event }: VisitInitOptions
|
|
93
95
|
): Visit {
|
|
94
96
|
return {
|
|
97
|
+
id: Math.random(),
|
|
95
98
|
from: { url: from },
|
|
96
99
|
to: { url: to, hash },
|
|
97
100
|
containers: this.options.containers,
|
package/src/modules/fetchPage.ts
CHANGED
|
@@ -15,16 +15,25 @@ export interface FetchOptions extends Omit<RequestInit, 'cache'> {
|
|
|
15
15
|
method?: 'GET' | 'POST';
|
|
16
16
|
/** The body of the request: raw string, form data object or URL params. */
|
|
17
17
|
body?: string | FormData | URLSearchParams;
|
|
18
|
+
/** The request timeout in milliseconds. */
|
|
19
|
+
timeout?: number;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export class FetchError extends Error {
|
|
21
23
|
url: string;
|
|
22
|
-
status
|
|
23
|
-
|
|
24
|
+
status?: number;
|
|
25
|
+
aborted: boolean;
|
|
26
|
+
timedOut: boolean;
|
|
27
|
+
constructor(
|
|
28
|
+
message: string,
|
|
29
|
+
details: { url: string; status?: number; aborted?: boolean; timedOut?: boolean }
|
|
30
|
+
) {
|
|
24
31
|
super(message);
|
|
25
32
|
this.name = 'FetchError';
|
|
26
33
|
this.url = details.url;
|
|
27
34
|
this.status = details.status;
|
|
35
|
+
this.aborted = details.aborted || false;
|
|
36
|
+
this.timedOut = details.timedOut || false;
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
|
|
@@ -39,14 +48,44 @@ export async function fetchPage(
|
|
|
39
48
|
url = Location.fromUrl(url).url;
|
|
40
49
|
|
|
41
50
|
const headers = { ...this.options.requestHeaders, ...options.headers };
|
|
42
|
-
|
|
51
|
+
const timeout = options.timeout ?? this.options.timeout;
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const { signal } = controller;
|
|
54
|
+
options = { ...options, headers, signal };
|
|
55
|
+
|
|
56
|
+
let timedOut = false;
|
|
57
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
58
|
+
if (timeout && timeout > 0) {
|
|
59
|
+
timeoutId = setTimeout(() => {
|
|
60
|
+
timedOut = true;
|
|
61
|
+
controller.abort('timeout');
|
|
62
|
+
}, timeout);
|
|
63
|
+
}
|
|
43
64
|
|
|
44
65
|
// Allow hooking before this and returning a custom response-like object (e.g. custom fetch implementation)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
let response: Response;
|
|
67
|
+
try {
|
|
68
|
+
response = await this.hooks.call(
|
|
69
|
+
'fetch:request',
|
|
70
|
+
{ url, options },
|
|
71
|
+
(visit, { url, options }) => fetch(url, options)
|
|
72
|
+
);
|
|
73
|
+
if (timeoutId) {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (timedOut) {
|
|
78
|
+
this.hooks.call('fetch:timeout', { url });
|
|
79
|
+
throw new FetchError(`Request timed out: ${url}`, { url, timedOut });
|
|
80
|
+
}
|
|
81
|
+
if ((error as Error)?.name === 'AbortError' || signal.aborted) {
|
|
82
|
+
throw new FetchError(`Request aborted: ${url}`, {
|
|
83
|
+
url: url,
|
|
84
|
+
aborted: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
50
89
|
|
|
51
90
|
const { status, url: responseUrl } = response;
|
|
52
91
|
const html = await response.text();
|
package/src/modules/navigate.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Swup from '../Swup.js';
|
|
2
2
|
import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js';
|
|
3
|
-
import { FetchOptions, PageData } from './fetchPage.js';
|
|
3
|
+
import { FetchError, FetchOptions, PageData } from './fetchPage.js';
|
|
4
4
|
import { VisitInitOptions } from './Visit.js';
|
|
5
5
|
|
|
6
6
|
export type HistoryAction = 'push' | 'replace';
|
|
@@ -61,37 +61,41 @@ export function navigate(
|
|
|
61
61
|
export async function performNavigation(
|
|
62
62
|
this: Swup,
|
|
63
63
|
options: NavigationOptions & FetchOptions = {}
|
|
64
|
-
) {
|
|
65
|
-
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
// Save this localy to a) allow ignoring the visit if a new one was started in the meantime
|
|
66
|
+
// and b) avoid unintended modifications to any newer visits
|
|
67
|
+
const visit = this.visit;
|
|
68
|
+
|
|
69
|
+
const { el } = visit.trigger;
|
|
66
70
|
options.referrer = options.referrer || this.currentPageUrl;
|
|
67
71
|
|
|
68
72
|
if (options.animate === false) {
|
|
69
|
-
|
|
73
|
+
visit.animation.animate = false;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// Clean up old animation classes
|
|
73
|
-
if (!
|
|
77
|
+
if (!visit.animation.animate) {
|
|
74
78
|
this.classes.clear();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
// Get history action from option or attribute on trigger element
|
|
78
82
|
const history = options.history || el?.getAttribute('data-swup-history') || undefined;
|
|
79
83
|
if (history && ['push', 'replace'].includes(history)) {
|
|
80
|
-
|
|
84
|
+
visit.history.action = history as HistoryAction;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// Get custom animation name from option or attribute on trigger element
|
|
84
88
|
const animation = options.animation || el?.getAttribute('data-swup-animation') || undefined;
|
|
85
89
|
if (animation) {
|
|
86
|
-
|
|
90
|
+
visit.animation.name = animation;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
// Sanitize cache option
|
|
90
94
|
if (typeof options.cache === 'object') {
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
visit.cache.read = options.cache.read ?? visit.cache.read;
|
|
96
|
+
visit.cache.write = options.cache.write ?? visit.cache.write;
|
|
93
97
|
} else if (options.cache !== undefined) {
|
|
94
|
-
|
|
98
|
+
visit.cache = { read: !!options.cache, write: !!options.cache };
|
|
95
99
|
}
|
|
96
100
|
// Delete this so that window.fetch doesn't mis-interpret it
|
|
97
101
|
delete options.cache;
|
|
@@ -103,7 +107,7 @@ export async function performNavigation(
|
|
|
103
107
|
const pagePromise = this.hooks.call('page:load', { options }, async (visit, args) => {
|
|
104
108
|
// Read from cache
|
|
105
109
|
let cachedPage: PageData | undefined;
|
|
106
|
-
if (
|
|
110
|
+
if (visit.cache.read) {
|
|
107
111
|
cachedPage = this.cache.get(visit.to.url);
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -114,13 +118,10 @@ export async function performNavigation(
|
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
// Create/update history record if this is not a popstate call or leads to the same URL
|
|
117
|
-
if (!
|
|
121
|
+
if (!visit.history.popstate) {
|
|
118
122
|
// Add the hash directly from the trigger element
|
|
119
|
-
const newUrl =
|
|
120
|
-
if (
|
|
121
|
-
this.visit.history.action === 'replace' ||
|
|
122
|
-
this.visit.to.url === this.currentPageUrl
|
|
123
|
-
) {
|
|
123
|
+
const newUrl = visit.to.url + visit.to.hash;
|
|
124
|
+
if (visit.history.action === 'replace' || visit.to.url === this.currentPageUrl) {
|
|
124
125
|
updateHistoryRecord(newUrl);
|
|
125
126
|
} else {
|
|
126
127
|
this.currentHistoryIndex++;
|
|
@@ -131,31 +132,43 @@ export async function performNavigation(
|
|
|
131
132
|
this.currentPageUrl = getCurrentUrl();
|
|
132
133
|
|
|
133
134
|
// Wait for page before starting to animate out?
|
|
134
|
-
if (
|
|
135
|
+
if (visit.animation.wait) {
|
|
135
136
|
const { html } = await pagePromise;
|
|
136
|
-
|
|
137
|
+
visit.to.html = html;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
// perform the actual transition: animate and replace content
|
|
141
|
+
await this.hooks.call('visit:transition', undefined, async (visit) => {
|
|
142
|
+
// Start leave animation
|
|
143
|
+
const animationPromise = this.animatePageOut();
|
|
144
|
+
|
|
145
|
+
// Wait for page to load and leave animation to finish
|
|
146
|
+
const [page] = await Promise.all([pagePromise, animationPromise]);
|
|
147
|
+
|
|
148
|
+
// Abort if another visit was started in the meantime
|
|
149
|
+
if (visit.id !== this.visit.id) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Render page: replace content and scroll to top/fragment
|
|
154
|
+
await this.renderPage(page);
|
|
142
155
|
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
// Wait for enter animation
|
|
157
|
+
await this.animatePageIn();
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
148
161
|
|
|
149
162
|
// Finalize visit
|
|
150
163
|
await this.hooks.call('visit:end', undefined, () => this.classes.clear());
|
|
151
164
|
|
|
152
165
|
// Reset visit info after finish?
|
|
153
|
-
// if (
|
|
154
|
-
// this.createVisit({ to: undefined });
|
|
166
|
+
// if (visit.to && this.isSameResolvedUrl(visit.to.url, requestedUrl)) {
|
|
167
|
+
// this.visit = this.createVisit({ to: undefined });
|
|
155
168
|
// }
|
|
156
|
-
} catch (error
|
|
157
|
-
// Return early if error is undefined
|
|
158
|
-
if (!error) {
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// Return early if error is undefined or signals an aborted request
|
|
171
|
+
if (!error || (error as FetchError)?.aborted) {
|
|
159
172
|
return;
|
|
160
173
|
}
|
|
161
174
|
|
|
@@ -164,7 +177,7 @@ export async function performNavigation(
|
|
|
164
177
|
|
|
165
178
|
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
|
|
166
179
|
this.options.skipPopStateHandling = () => {
|
|
167
|
-
window.location.href =
|
|
180
|
+
window.location.href = visit.to.url + visit.to.hash;
|
|
168
181
|
return true;
|
|
169
182
|
};
|
|
170
183
|
|
|
@@ -4,18 +4,12 @@ import { PageData } from './fetchPage.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Render the next page: replace the content and update scroll position.
|
|
7
|
-
* @returns Promise<void>
|
|
8
7
|
*/
|
|
9
|
-
export const renderPage = async function (this: Swup,
|
|
8
|
+
export const renderPage = async function (this: Swup, page: PageData): Promise<void> {
|
|
10
9
|
const { url, html } = page;
|
|
11
10
|
|
|
12
11
|
this.classes.remove('is-leaving');
|
|
13
12
|
|
|
14
|
-
// do nothing if another page was requested in the meantime
|
|
15
|
-
if (!this.isSameResolvedUrl(getCurrentUrl(), requestedUrl)) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
// update state if the url was redirected
|
|
20
14
|
if (!this.isSameResolvedUrl(getCurrentUrl(), url)) {
|
|
21
15
|
updateHistoryRecord(url);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { DelegateEvent } from 'delegate-it';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import pckg from '../../package.json';
|
|
5
|
-
import Swup, { Options, Plugin } from '../index.js';
|
|
6
|
-
import * as SwupTS from '../Swup.js';
|
|
7
|
-
|
|
8
|
-
const baseUrl = window.location.origin;
|
|
9
|
-
|
|
10
|
-
describe('Exports', () => {
|
|
11
|
-
it('should export Swup and Options/Plugin types', () => {
|
|
12
|
-
class SwupPlugin implements Plugin {
|
|
13
|
-
name = 'SwupPlugin';
|
|
14
|
-
isSwupPlugin = true as const;
|
|
15
|
-
mount = () => {};
|
|
16
|
-
unmount = () => {};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const options: Partial<Options> = {
|
|
20
|
-
animateHistoryBrowsing: false,
|
|
21
|
-
animationSelector: '[class*="transition-"]',
|
|
22
|
-
cache: true,
|
|
23
|
-
containers: ['#swup'],
|
|
24
|
-
ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'),
|
|
25
|
-
linkSelector: 'a[href]',
|
|
26
|
-
plugins: [new SwupPlugin()],
|
|
27
|
-
resolveUrl: (url) => url,
|
|
28
|
-
requestHeaders: {
|
|
29
|
-
'X-Requested-With': 'swup',
|
|
30
|
-
'Accept': 'text/html, application/xhtml+xml'
|
|
31
|
-
},
|
|
32
|
-
skipPopStateHandling: (event) => event.state?.source !== 'swup'
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const swup = new Swup(options);
|
|
36
|
-
expect(swup).toBeInstanceOf(Swup);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should define a version', () => {
|
|
40
|
-
const swup = new Swup();
|
|
41
|
-
expect(swup.version).not.toBeUndefined();
|
|
42
|
-
expect(swup.version).toEqual(pckg.version);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('UMD compatibility: Swup.ts should only have a default export', () => {
|
|
46
|
-
expect(Object.keys(SwupTS)).toEqual(['default']);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('ignoreVisit', () => {
|
|
51
|
-
it('should be called with relative URL', () => {
|
|
52
|
-
const ignoreVisit = vi.fn(() => true);
|
|
53
|
-
const swup = new Swup({ ignoreVisit });
|
|
54
|
-
swup.shouldIgnoreVisit(`${baseUrl}/path/?query#hash`);
|
|
55
|
-
|
|
56
|
-
expect(ignoreVisit.mock.calls).toHaveLength(1);
|
|
57
|
-
expect((ignoreVisit.mock.lastCall as any)[0]).toEqual('/path/?query#hash');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should have access to element and event params', () => {
|
|
61
|
-
const el = document.createElement('a');
|
|
62
|
-
el.href = `${baseUrl}/path/?query#hash`;
|
|
63
|
-
const event = new MouseEvent('click') as DelegateEvent<MouseEvent>;
|
|
64
|
-
event.delegateTarget = el;
|
|
65
|
-
|
|
66
|
-
const ignoreVisit = vi.fn(() => true);
|
|
67
|
-
const swup = new Swup({ ignoreVisit });
|
|
68
|
-
swup.navigate(el.href, {}, { el, event });
|
|
69
|
-
|
|
70
|
-
expect(ignoreVisit.mock.calls).toHaveLength(1);
|
|
71
|
-
expect((ignoreVisit.mock.lastCall as any)[1]).toEqual(
|
|
72
|
-
expect.objectContaining({ el, event })
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should be called from visit method', () => {
|
|
77
|
-
const ignoreVisit = vi.fn(() => true);
|
|
78
|
-
const swup = new Swup({ ignoreVisit });
|
|
79
|
-
swup.navigate('/path/');
|
|
80
|
-
|
|
81
|
-
expect(ignoreVisit.mock.calls).toHaveLength(1);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { matchPath } from '../../index.js';
|
|
3
|
-
import { pathToRegexp } from 'path-to-regexp';
|
|
4
|
-
|
|
5
|
-
describe('matchPath', () => {
|
|
6
|
-
it('should return false if not matching', () => {
|
|
7
|
-
const urlMatch = matchPath('/users/:user');
|
|
8
|
-
const match = urlMatch('/posts/');
|
|
9
|
-
expect(match).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should return an object if matching', () => {
|
|
13
|
-
const urlMatch = matchPath('/users/:user');
|
|
14
|
-
const match = urlMatch('/users/bob');
|
|
15
|
-
expect(match).toEqual({
|
|
16
|
-
path: '/users/bob',
|
|
17
|
-
index: 0,
|
|
18
|
-
params: { user: 'bob' }
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should work with primitive strings', () => {
|
|
23
|
-
const urlMatch = matchPath<{ user: string }>('/users/:user');
|
|
24
|
-
const match = urlMatch('/users/bob');
|
|
25
|
-
const params = !match ? false : match.params;
|
|
26
|
-
expect(params).toEqual({ user: 'bob' });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should work with an array of paths', () => {
|
|
30
|
-
const urlMatch = matchPath<{ user: string }>(['/users/', '/users/:user']);
|
|
31
|
-
|
|
32
|
-
const { params: withParams } = urlMatch('/users/bob') || {};
|
|
33
|
-
expect(withParams).toEqual({ user: 'bob' });
|
|
34
|
-
|
|
35
|
-
const { params: withoutParams } = urlMatch('/users/') || {};
|
|
36
|
-
expect(withoutParams).toEqual({});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* When passing a regex to `match`, the params in the response are sorted by appearance.
|
|
41
|
-
* Only helpful for falsy/truthy detection
|
|
42
|
-
*/
|
|
43
|
-
it('should work with regex', () => {
|
|
44
|
-
const re = pathToRegexp('/users/:user');
|
|
45
|
-
const urlMatch = matchPath(re);
|
|
46
|
-
const { params } = urlMatch('/users/bob') || {};
|
|
47
|
-
expect(params).toEqual({ '0': 'bob' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should throw with malformed paths', () => {
|
|
51
|
-
// prettier-ignore
|
|
52
|
-
expect(() => matchPath('/\?user=:user')).toThrowError('[swup] Error parsing path');
|
|
53
|
-
});
|
|
54
|
-
});
|