swup 4.3.3 → 4.3.4

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.
@@ -0,0 +1,92 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import Swup from '../../Swup.js';
3
+ import { Visit, createVisit } from '../Visit.js';
4
+
5
+ class SwupWithPublicVisitMethods extends Swup {
6
+ public createVisit = createVisit;
7
+ }
8
+
9
+ const swup = new SwupWithPublicVisitMethods();
10
+ let visit: Visit;
11
+
12
+ describe('Visit', () => {
13
+ beforeEach(() => {
14
+ visit = swup.createVisit({ to: '' });
15
+ });
16
+
17
+ it('is an object', () => {
18
+ expect(visit).to.be.an('object');
19
+ });
20
+
21
+ it('has an id', () => {
22
+ expect(visit.id).to.be.a('number');
23
+ });
24
+
25
+ it('generates unique ids', () => {
26
+ let id = visit.id;
27
+ visit = swup.createVisit({ to: '' });
28
+ expect(visit.id).to.not.equal(id);
29
+ });
30
+
31
+ it('has a from object with the current URL', () => {
32
+ expect(visit.from).to.be.an('object');
33
+ expect(visit.from.url).to.be.a('string');
34
+ visit = swup.createVisit({ to: '', from: '/from' });
35
+ expect(visit.from).toMatchObject({ url: '/from' });
36
+ });
37
+
38
+ it('has a to object with the next URL', () => {
39
+ expect(visit.to).to.be.an('object');
40
+ expect(visit.to.url).to.be.a('string');
41
+ visit = swup.createVisit({ to: '/to' });
42
+ expect(visit.to).toMatchObject({ url: '/to' });
43
+ });
44
+
45
+ it('has an animation object', () => {
46
+ expect(visit.animation).to.be.an('object');
47
+ expect(visit.animation).toMatchObject({
48
+ animate: true,
49
+ name: undefined,
50
+ scope: swup.options.animationScope,
51
+ selector: swup.options.animationSelector
52
+ });
53
+ });
54
+
55
+ it('has a container array', () => {
56
+ expect(visit.containers).to.be.an('array');
57
+ expect(visit.containers).toEqual(swup.options.containers);
58
+ });
59
+
60
+ it('has a trigger object', () => {
61
+ expect(visit.trigger).to.be.an('object');
62
+ expect(visit.trigger).toMatchObject({
63
+ el: undefined,
64
+ event: undefined
65
+ });
66
+ });
67
+
68
+ it('has a cache object', () => {
69
+ expect(visit.cache).to.be.an('object');
70
+ expect(visit.cache).toEqual({
71
+ read: swup.options.cache,
72
+ write: swup.options.cache
73
+ });
74
+ });
75
+
76
+ it('has a history object', () => {
77
+ expect(visit.history).to.be.an('object');
78
+ expect(visit.history).toEqual({
79
+ action: 'push',
80
+ popstate: false,
81
+ direction: undefined
82
+ });
83
+ });
84
+
85
+ it('has a scroll object', () => {
86
+ expect(visit.scroll).to.be.an('object');
87
+ expect(visit.scroll).toEqual({
88
+ reset: true,
89
+ target: undefined
90
+ });
91
+ });
92
+ });
@@ -61,37 +61,41 @@ export function navigate(
61
61
  export async function performNavigation(
62
62
  this: Swup,
63
63
  options: NavigationOptions & FetchOptions = {}
64
- ) {
65
- const { el } = this.visit.trigger;
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
- this.visit.animation.animate = false;
73
+ visit.animation.animate = false;
70
74
  }
71
75
 
72
76
  // Clean up old animation classes
73
- if (!this.visit.animation.animate) {
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
- this.visit.history.action = history as HistoryAction;
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
- this.visit.animation.name = animation;
90
+ visit.animation.name = animation;
87
91
  }
88
92
 
89
93
  // Sanitize cache option
90
94
  if (typeof options.cache === 'object') {
91
- this.visit.cache.read = options.cache.read ?? this.visit.cache.read;
92
- this.visit.cache.write = options.cache.write ?? this.visit.cache.write;
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
- this.visit.cache = { read: !!options.cache, write: !!options.cache };
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 (this.visit.cache.read) {
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 (!this.visit.history.popstate) {
121
+ if (!visit.history.popstate) {
118
122
  // Add the hash directly from the trigger element
119
- const newUrl = this.visit.to.url + this.visit.to.hash;
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,17 +132,22 @@ export async function performNavigation(
131
132
  this.currentPageUrl = getCurrentUrl();
132
133
 
133
134
  // Wait for page before starting to animate out?
134
- if (this.visit.animation.wait) {
135
+ if (visit.animation.wait) {
135
136
  const { html } = await pagePromise;
136
- this.visit.to.html = html;
137
+ visit.to.html = html;
137
138
  }
138
139
 
139
140
  // Wait for page to load and leave animation to finish
140
141
  const animationPromise = this.animatePageOut();
141
142
  const [page] = await Promise.all([pagePromise, animationPromise]);
142
143
 
144
+ // Abort if another visit was started in the meantime
145
+ if (visit.id !== this.visit.id) {
146
+ return;
147
+ }
148
+
143
149
  // Render page: replace content and scroll to top/fragment
144
- await this.renderPage(this.visit.to.url, page);
150
+ await this.renderPage(page);
145
151
 
146
152
  // Wait for enter animation
147
153
  await this.animatePageIn();
@@ -150,8 +156,8 @@ export async function performNavigation(
150
156
  await this.hooks.call('visit:end', undefined, () => this.classes.clear());
151
157
 
152
158
  // Reset visit info after finish?
153
- // if (this.visit.to && this.isSameResolvedUrl(this.visit.to.url, requestedUrl)) {
154
- // this.createVisit({ to: undefined });
159
+ // if (visit.to && this.isSameResolvedUrl(visit.to.url, requestedUrl)) {
160
+ // this.visit = this.createVisit({ to: undefined });
155
161
  // }
156
162
  } catch (error: unknown) {
157
163
  // Return early if error is undefined (probably aborted preload request)
@@ -164,7 +170,7 @@ export async function performNavigation(
164
170
 
165
171
  // Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
166
172
  this.options.skipPopStateHandling = () => {
167
- window.location.href = this.visit.to.url + this.visit.to.hash;
173
+ window.location.href = visit.to.url + visit.to.hash;
168
174
  return true;
169
175
  };
170
176
 
@@ -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, requestedUrl: string, page: PageData) {
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);