@tramvai/test-mocks 7.20.1 → 7.20.3
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/README.md +30 -1
- package/lib/router.es.js +152 -15
- package/lib/router.js +151 -14
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -115,7 +115,13 @@ it('test', async () => {
|
|
|
115
115
|
|
|
116
116
|
### Router
|
|
117
117
|
|
|
118
|
-
Creates mock instance for `@tinkoff/router`
|
|
118
|
+
Creates mock instance for `@tinkoff/router` with full history support.
|
|
119
|
+
|
|
120
|
+
**Features:**
|
|
121
|
+
|
|
122
|
+
- Full history stack with `back()`, `forward()`, `go()` navigation
|
|
123
|
+
- Proper redirect handling (redirect is part of single navigation)
|
|
124
|
+
- Query and hash change support
|
|
119
125
|
|
|
120
126
|
```ts
|
|
121
127
|
import { createMockRouter } from '@tramvai/test-mocks';
|
|
@@ -134,5 +140,28 @@ describe('test', () => {
|
|
|
134
140
|
expect(router.getCurrentRoute()).toMatchObject({ path: '/page/test/' });
|
|
135
141
|
expect(router.getCurrentUrl()).toMatchObject({ path: '/page/test/' });
|
|
136
142
|
});
|
|
143
|
+
|
|
144
|
+
it('should support history navigation', async () => {
|
|
145
|
+
const router = createMockRouter();
|
|
146
|
+
|
|
147
|
+
await router.navigate('/page1');
|
|
148
|
+
await router.navigate('/page2');
|
|
149
|
+
|
|
150
|
+
await router.back();
|
|
151
|
+
expect(router.getCurrentRoute()).toMatchObject({ path: '/page1' });
|
|
152
|
+
|
|
153
|
+
await router.forward();
|
|
154
|
+
expect(router.getCurrentRoute()).toMatchObject({ path: '/page2' });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle static redirect configuration', async () => {
|
|
158
|
+
const router = createMockRouter({
|
|
159
|
+
currentRoute: { name: 'redirected', path: '/new-page', redirect: '/new-page' } as any,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await router.navigate('/old-page');
|
|
163
|
+
|
|
164
|
+
expect(router.getCurrentRoute()).toMatchObject({ path: '/new-page' });
|
|
165
|
+
});
|
|
137
166
|
});
|
|
138
167
|
```
|
package/lib/router.es.js
CHANGED
|
@@ -1,10 +1,73 @@
|
|
|
1
|
-
import { AbstractRouter } from '@tinkoff/router';
|
|
1
|
+
import { AbstractRouter, History } from '@tinkoff/router';
|
|
2
2
|
import { parse } from '@tinkoff/url';
|
|
3
3
|
|
|
4
4
|
const isNavigationRoute = (route) => {
|
|
5
5
|
return (route.actualPath !== undefined &&
|
|
6
6
|
route.params !== undefined);
|
|
7
7
|
};
|
|
8
|
+
class MockHistory extends History {
|
|
9
|
+
historyStack = [];
|
|
10
|
+
currentIndex = -1;
|
|
11
|
+
keyCounter = 0;
|
|
12
|
+
getNextKey() {
|
|
13
|
+
return ++this.keyCounter;
|
|
14
|
+
}
|
|
15
|
+
save(navigation) {
|
|
16
|
+
if (!navigation.replace && this.currentIndex < this.historyStack.length - 1) {
|
|
17
|
+
this.historyStack = this.historyStack.slice(0, this.currentIndex + 1);
|
|
18
|
+
}
|
|
19
|
+
if (navigation.replace && this.currentIndex >= 0) {
|
|
20
|
+
this.historyStack[this.currentIndex] = navigation;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.historyStack.push(navigation);
|
|
24
|
+
this.currentIndex = this.historyStack.length - 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async go(to) {
|
|
28
|
+
const targetIndex = this.currentIndex + to;
|
|
29
|
+
if (targetIndex < 0 || targetIndex >= this.historyStack.length) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.currentIndex = targetIndex;
|
|
33
|
+
await this.notifyListener(this.historyStack[targetIndex], to);
|
|
34
|
+
}
|
|
35
|
+
async notifyListener(navigation, to = 0) {
|
|
36
|
+
if (this.listener && navigation.url) {
|
|
37
|
+
await this.listener({
|
|
38
|
+
url: navigation.url.path,
|
|
39
|
+
type: navigation.type,
|
|
40
|
+
navigateState: navigation.navigateState,
|
|
41
|
+
replace: navigation.replace,
|
|
42
|
+
history: true,
|
|
43
|
+
isBack: to < 0,
|
|
44
|
+
hasUAVisualTransition: navigation.hasUAVisualTransition,
|
|
45
|
+
viewTransition: navigation.viewTransition,
|
|
46
|
+
viewTransitionTypes: navigation.viewTransitionTypes,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
getCurrentState() {
|
|
51
|
+
if (this.currentIndex < 0 || this.currentIndex >= this.historyStack.length) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const navigation = this.historyStack[this.currentIndex];
|
|
55
|
+
return {
|
|
56
|
+
key: String(navigation.key ?? this.currentIndex),
|
|
57
|
+
type: navigation.type,
|
|
58
|
+
navigateState: navigation.navigateState,
|
|
59
|
+
index: this.currentIndex,
|
|
60
|
+
viewTransition: navigation.viewTransition,
|
|
61
|
+
viewTransitionTypes: navigation.viewTransitionTypes,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
getNavigation(index) {
|
|
65
|
+
return this.historyStack[index];
|
|
66
|
+
}
|
|
67
|
+
getCurrentIndex() {
|
|
68
|
+
return this.currentIndex;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
8
71
|
const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentUrl = parse(currentRoute.path), } = {}) => {
|
|
9
72
|
let route = isNavigationRoute(currentRoute)
|
|
10
73
|
? currentRoute
|
|
@@ -14,16 +77,42 @@ const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentU
|
|
|
14
77
|
...currentRoute,
|
|
15
78
|
};
|
|
16
79
|
let url = currentUrl;
|
|
17
|
-
let
|
|
18
|
-
|
|
80
|
+
let redirectTarget = null;
|
|
81
|
+
const router = new (class extends AbstractRouter {
|
|
19
82
|
onRedirect = async (navigation) => {
|
|
20
|
-
|
|
21
|
-
blocked = true;
|
|
83
|
+
redirectTarget = navigation;
|
|
22
84
|
};
|
|
85
|
+
async navigate(navigateOptions) {
|
|
86
|
+
const options = typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions;
|
|
87
|
+
return super.navigate(options);
|
|
88
|
+
}
|
|
89
|
+
async updateCurrentRoute(updateRouteOptions) {
|
|
90
|
+
return super.updateCurrentRoute(updateRouteOptions);
|
|
91
|
+
}
|
|
23
92
|
getCurrentRoute() {
|
|
93
|
+
const history = this.history;
|
|
94
|
+
const state = history.getCurrentState();
|
|
95
|
+
if (state && state.index >= 0) {
|
|
96
|
+
const navigation = history.getNavigation(state.index);
|
|
97
|
+
if (navigation?.url) {
|
|
98
|
+
return {
|
|
99
|
+
...route,
|
|
100
|
+
path: navigation.url.path,
|
|
101
|
+
actualPath: navigation.url.path,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
24
105
|
return route;
|
|
25
106
|
}
|
|
26
107
|
getCurrentUrl() {
|
|
108
|
+
const history = this.history;
|
|
109
|
+
const state = history.getCurrentState();
|
|
110
|
+
if (state && state.index >= 0) {
|
|
111
|
+
const navigation = history.getNavigation(state.index);
|
|
112
|
+
if (navigation?.url) {
|
|
113
|
+
return navigation.url;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
27
116
|
return url;
|
|
28
117
|
}
|
|
29
118
|
getLastRoute() {
|
|
@@ -32,22 +121,70 @@ const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentU
|
|
|
32
121
|
getLastUrl() {
|
|
33
122
|
return url;
|
|
34
123
|
}
|
|
35
|
-
resolveRoute() {
|
|
124
|
+
resolveRoute({ url } = {}) {
|
|
125
|
+
if (url) {
|
|
126
|
+
return {
|
|
127
|
+
...route,
|
|
128
|
+
path: url.path,
|
|
129
|
+
actualPath: url.path,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
36
132
|
return route;
|
|
37
133
|
}
|
|
38
134
|
commitNavigation(navigation) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
135
|
+
const finalNavigation = redirectTarget ?? navigation;
|
|
136
|
+
redirectTarget = null;
|
|
137
|
+
if (!finalNavigation.history) {
|
|
138
|
+
this.history.save(finalNavigation);
|
|
139
|
+
}
|
|
140
|
+
if (finalNavigation.url) {
|
|
141
|
+
route = {
|
|
142
|
+
name: 'changed-after-navigate',
|
|
143
|
+
path: finalNavigation.url.path,
|
|
144
|
+
actualPath: finalNavigation.url.path,
|
|
145
|
+
params: {},
|
|
146
|
+
};
|
|
147
|
+
url = finalNavigation.url;
|
|
42
148
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
149
|
+
}
|
|
150
|
+
setupHistoryListener(history) {
|
|
151
|
+
history.listen(async ({ url: historyUrl, navigateState, replace, isBack }) => {
|
|
152
|
+
const currentUrl = this.getCurrentUrl();
|
|
153
|
+
const { pathname, query, hash } = this.resolveUrl({ url: historyUrl });
|
|
154
|
+
const resolvedUrl = this.resolveUrl({ url: historyUrl, query, hash });
|
|
155
|
+
const isSameUrlNavigation = currentUrl?.path === resolvedUrl.path;
|
|
156
|
+
if (isSameUrlNavigation) {
|
|
157
|
+
const navigation = {
|
|
158
|
+
type: 'updateCurrentRoute',
|
|
159
|
+
url: resolvedUrl,
|
|
160
|
+
navigateState,
|
|
161
|
+
replace,
|
|
162
|
+
isBack,
|
|
163
|
+
history: true,
|
|
164
|
+
key: this.history.getNextKey(),
|
|
165
|
+
};
|
|
166
|
+
this.commitNavigation(navigation);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
await this.internalNavigate({
|
|
170
|
+
url: historyUrl,
|
|
171
|
+
replace,
|
|
172
|
+
navigateState,
|
|
173
|
+
isBack,
|
|
174
|
+
}, { history: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
49
177
|
}
|
|
50
178
|
})({});
|
|
179
|
+
const history = new MockHistory();
|
|
180
|
+
history.save({
|
|
181
|
+
url: currentUrl,
|
|
182
|
+
type: 'navigate',
|
|
183
|
+
key: 0,
|
|
184
|
+
});
|
|
185
|
+
router.setupHistoryListener(history);
|
|
186
|
+
router.history = history;
|
|
187
|
+
return router;
|
|
51
188
|
};
|
|
52
189
|
|
|
53
190
|
export { createMockRouter };
|
package/lib/router.js
CHANGED
|
@@ -9,6 +9,69 @@ const isNavigationRoute = (route) => {
|
|
|
9
9
|
return (route.actualPath !== undefined &&
|
|
10
10
|
route.params !== undefined);
|
|
11
11
|
};
|
|
12
|
+
class MockHistory extends router.History {
|
|
13
|
+
historyStack = [];
|
|
14
|
+
currentIndex = -1;
|
|
15
|
+
keyCounter = 0;
|
|
16
|
+
getNextKey() {
|
|
17
|
+
return ++this.keyCounter;
|
|
18
|
+
}
|
|
19
|
+
save(navigation) {
|
|
20
|
+
if (!navigation.replace && this.currentIndex < this.historyStack.length - 1) {
|
|
21
|
+
this.historyStack = this.historyStack.slice(0, this.currentIndex + 1);
|
|
22
|
+
}
|
|
23
|
+
if (navigation.replace && this.currentIndex >= 0) {
|
|
24
|
+
this.historyStack[this.currentIndex] = navigation;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.historyStack.push(navigation);
|
|
28
|
+
this.currentIndex = this.historyStack.length - 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async go(to) {
|
|
32
|
+
const targetIndex = this.currentIndex + to;
|
|
33
|
+
if (targetIndex < 0 || targetIndex >= this.historyStack.length) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.currentIndex = targetIndex;
|
|
37
|
+
await this.notifyListener(this.historyStack[targetIndex], to);
|
|
38
|
+
}
|
|
39
|
+
async notifyListener(navigation, to = 0) {
|
|
40
|
+
if (this.listener && navigation.url) {
|
|
41
|
+
await this.listener({
|
|
42
|
+
url: navigation.url.path,
|
|
43
|
+
type: navigation.type,
|
|
44
|
+
navigateState: navigation.navigateState,
|
|
45
|
+
replace: navigation.replace,
|
|
46
|
+
history: true,
|
|
47
|
+
isBack: to < 0,
|
|
48
|
+
hasUAVisualTransition: navigation.hasUAVisualTransition,
|
|
49
|
+
viewTransition: navigation.viewTransition,
|
|
50
|
+
viewTransitionTypes: navigation.viewTransitionTypes,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getCurrentState() {
|
|
55
|
+
if (this.currentIndex < 0 || this.currentIndex >= this.historyStack.length) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const navigation = this.historyStack[this.currentIndex];
|
|
59
|
+
return {
|
|
60
|
+
key: String(navigation.key ?? this.currentIndex),
|
|
61
|
+
type: navigation.type,
|
|
62
|
+
navigateState: navigation.navigateState,
|
|
63
|
+
index: this.currentIndex,
|
|
64
|
+
viewTransition: navigation.viewTransition,
|
|
65
|
+
viewTransitionTypes: navigation.viewTransitionTypes,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
getNavigation(index) {
|
|
69
|
+
return this.historyStack[index];
|
|
70
|
+
}
|
|
71
|
+
getCurrentIndex() {
|
|
72
|
+
return this.currentIndex;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
12
75
|
const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentUrl = url.parse(currentRoute.path), } = {}) => {
|
|
13
76
|
let route = isNavigationRoute(currentRoute)
|
|
14
77
|
? currentRoute
|
|
@@ -18,16 +81,42 @@ const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentU
|
|
|
18
81
|
...currentRoute,
|
|
19
82
|
};
|
|
20
83
|
let url = currentUrl;
|
|
21
|
-
let
|
|
22
|
-
|
|
84
|
+
let redirectTarget = null;
|
|
85
|
+
const router$1 = new (class extends router.AbstractRouter {
|
|
23
86
|
onRedirect = async (navigation) => {
|
|
24
|
-
|
|
25
|
-
blocked = true;
|
|
87
|
+
redirectTarget = navigation;
|
|
26
88
|
};
|
|
89
|
+
async navigate(navigateOptions) {
|
|
90
|
+
const options = typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions;
|
|
91
|
+
return super.navigate(options);
|
|
92
|
+
}
|
|
93
|
+
async updateCurrentRoute(updateRouteOptions) {
|
|
94
|
+
return super.updateCurrentRoute(updateRouteOptions);
|
|
95
|
+
}
|
|
27
96
|
getCurrentRoute() {
|
|
97
|
+
const history = this.history;
|
|
98
|
+
const state = history.getCurrentState();
|
|
99
|
+
if (state && state.index >= 0) {
|
|
100
|
+
const navigation = history.getNavigation(state.index);
|
|
101
|
+
if (navigation?.url) {
|
|
102
|
+
return {
|
|
103
|
+
...route,
|
|
104
|
+
path: navigation.url.path,
|
|
105
|
+
actualPath: navigation.url.path,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
28
109
|
return route;
|
|
29
110
|
}
|
|
30
111
|
getCurrentUrl() {
|
|
112
|
+
const history = this.history;
|
|
113
|
+
const state = history.getCurrentState();
|
|
114
|
+
if (state && state.index >= 0) {
|
|
115
|
+
const navigation = history.getNavigation(state.index);
|
|
116
|
+
if (navigation?.url) {
|
|
117
|
+
return navigation.url;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
31
120
|
return url;
|
|
32
121
|
}
|
|
33
122
|
getLastRoute() {
|
|
@@ -36,22 +125,70 @@ const createMockRouter = ({ currentRoute = { name: 'root', path: '/' }, currentU
|
|
|
36
125
|
getLastUrl() {
|
|
37
126
|
return url;
|
|
38
127
|
}
|
|
39
|
-
resolveRoute() {
|
|
128
|
+
resolveRoute({ url } = {}) {
|
|
129
|
+
if (url) {
|
|
130
|
+
return {
|
|
131
|
+
...route,
|
|
132
|
+
path: url.path,
|
|
133
|
+
actualPath: url.path,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
40
136
|
return route;
|
|
41
137
|
}
|
|
42
138
|
commitNavigation(navigation) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
139
|
+
const finalNavigation = redirectTarget ?? navigation;
|
|
140
|
+
redirectTarget = null;
|
|
141
|
+
if (!finalNavigation.history) {
|
|
142
|
+
this.history.save(finalNavigation);
|
|
143
|
+
}
|
|
144
|
+
if (finalNavigation.url) {
|
|
145
|
+
route = {
|
|
146
|
+
name: 'changed-after-navigate',
|
|
147
|
+
path: finalNavigation.url.path,
|
|
148
|
+
actualPath: finalNavigation.url.path,
|
|
149
|
+
params: {},
|
|
150
|
+
};
|
|
151
|
+
url = finalNavigation.url;
|
|
46
152
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
153
|
+
}
|
|
154
|
+
setupHistoryListener(history) {
|
|
155
|
+
history.listen(async ({ url: historyUrl, navigateState, replace, isBack }) => {
|
|
156
|
+
const currentUrl = this.getCurrentUrl();
|
|
157
|
+
const { pathname, query, hash } = this.resolveUrl({ url: historyUrl });
|
|
158
|
+
const resolvedUrl = this.resolveUrl({ url: historyUrl, query, hash });
|
|
159
|
+
const isSameUrlNavigation = currentUrl?.path === resolvedUrl.path;
|
|
160
|
+
if (isSameUrlNavigation) {
|
|
161
|
+
const navigation = {
|
|
162
|
+
type: 'updateCurrentRoute',
|
|
163
|
+
url: resolvedUrl,
|
|
164
|
+
navigateState,
|
|
165
|
+
replace,
|
|
166
|
+
isBack,
|
|
167
|
+
history: true,
|
|
168
|
+
key: this.history.getNextKey(),
|
|
169
|
+
};
|
|
170
|
+
this.commitNavigation(navigation);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await this.internalNavigate({
|
|
174
|
+
url: historyUrl,
|
|
175
|
+
replace,
|
|
176
|
+
navigateState,
|
|
177
|
+
isBack,
|
|
178
|
+
}, { history: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
53
181
|
}
|
|
54
182
|
})({});
|
|
183
|
+
const history = new MockHistory();
|
|
184
|
+
history.save({
|
|
185
|
+
url: currentUrl,
|
|
186
|
+
type: 'navigate',
|
|
187
|
+
key: 0,
|
|
188
|
+
});
|
|
189
|
+
router$1.setupHistoryListener(history);
|
|
190
|
+
router$1.history = history;
|
|
191
|
+
return router$1;
|
|
55
192
|
};
|
|
56
193
|
|
|
57
194
|
exports.createMockRouter = createMockRouter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/test-mocks",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@tinkoff/hook-runner": "0.9.1",
|
|
21
21
|
"@tinkoff/pubsub": "0.10.1",
|
|
22
|
-
"@tinkoff/router": "0.7.
|
|
22
|
+
"@tinkoff/router": "0.7.71",
|
|
23
23
|
"@tinkoff/url": "0.13.1",
|
|
24
|
-
"@tramvai/core": "7.20.
|
|
25
|
-
"@tramvai/module-common": "7.20.
|
|
26
|
-
"@tramvai/module-cookie": "7.20.
|
|
27
|
-
"@tramvai/state": "7.20.
|
|
28
|
-
"@tramvai/tokens-common": "7.20.
|
|
29
|
-
"@tramvai/tokens-core": "7.20.
|
|
24
|
+
"@tramvai/core": "7.20.3",
|
|
25
|
+
"@tramvai/module-common": "7.20.3",
|
|
26
|
+
"@tramvai/module-cookie": "7.20.3",
|
|
27
|
+
"@tramvai/state": "7.20.3",
|
|
28
|
+
"@tramvai/tokens-common": "7.20.3",
|
|
29
|
+
"@tramvai/tokens-core": "7.20.3"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@tinkoff/dippy": "^1.0.0",
|