reactive-route 0.0.1-alpha.26 → 0.0.1-alpha.27
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/LICENSE +1 -1
- package/dist/cjs/adapters/vue/index.js +39 -0
- package/dist/cjs/adapters/vue/package.json +1 -0
- package/dist/cjs/vue/index.js +116 -0
- package/dist/cjs/vue/package.json +1 -0
- package/dist/esm/adapters/vue/index.js +18 -0
- package/dist/esm/adapters/vue/package.json +1 -0
- package/dist/esm/vue/index.js +93 -0
- package/dist/esm/vue/package.json +1 -0
- package/e2e/app.test.ts +130 -0
- package/e2e/teardown.ts +12 -0
- package/package.json +22 -7
- package/playwright.config.ts +52 -0
- package/vitepress/.vitepress/cache/deps/_metadata.json +52 -0
- package/vitepress/.vitepress/cache/deps/chunk-FL23S3EK.js +12705 -0
- package/vitepress/.vitepress/cache/deps/chunk-FL23S3EK.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/chunk-VGAXUAUJ.js +9952 -0
- package/vitepress/.vitepress/cache/deps/chunk-VGAXUAUJ.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/package.json +3 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +3844 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_core.js +588 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1153 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1665 -0
- package/vitepress/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___minisearch.js +1812 -0
- package/vitepress/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vue.js +342 -0
- package/vitepress/.vitepress/cache/deps/vue.js.map +7 -0
- package/vitepress/.vitepress/config.mts +72 -0
- package/vitepress/.vitepress/theme/custom.css +9 -0
- package/vitepress/.vitepress/theme/index.ts +5 -0
- package/vitepress/examples/preact.md +22 -0
- package/vitepress/examples/react.md +22 -0
- package/vitepress/examples/solid.md +21 -0
- package/vitepress/file.svg +79 -0
- package/vitepress/guide/advanced.md +131 -0
- package/vitepress/guide/getting-started.md +139 -0
- package/vitepress/guide/index.md +44 -0
- package/vitepress/guide/preact.md +33 -0
- package/vitepress/guide/react.md +33 -0
- package/vitepress/guide/router-component.md +67 -0
- package/vitepress/guide/router-configuration.md +185 -0
- package/vitepress/guide/routes-configuration.md +191 -0
- package/vitepress/guide/solid.md +68 -0
- package/vitepress/guide/ssr.md +70 -0
- package/vitepress/index.md +29 -0
package/LICENSE
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/adapters/vue.ts
|
|
21
|
+
var vue_exports = {};
|
|
22
|
+
__export(vue_exports, {
|
|
23
|
+
adapters: () => adapters
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(vue_exports);
|
|
26
|
+
var import_vue = require("vue");
|
|
27
|
+
var adapters = {
|
|
28
|
+
batch: (cb) => cb(),
|
|
29
|
+
autorun: import_vue.watchEffect,
|
|
30
|
+
replaceObject: (obj, newObj) => {
|
|
31
|
+
for (const variableKey in obj) {
|
|
32
|
+
if (obj.hasOwnProperty(variableKey)) {
|
|
33
|
+
delete obj[variableKey];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
Object.assign(obj, newObj);
|
|
37
|
+
},
|
|
38
|
+
makeObservable: import_vue.reactive
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/vue/index.ts
|
|
21
|
+
var vue_exports = {};
|
|
22
|
+
__export(vue_exports, {
|
|
23
|
+
Router: () => Router
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(vue_exports);
|
|
26
|
+
|
|
27
|
+
// packages/vue/Router.ts
|
|
28
|
+
var import_reactive_route = require("reactive-route");
|
|
29
|
+
var import_vue = require("vue");
|
|
30
|
+
var Router = (0, import_vue.defineComponent)({
|
|
31
|
+
name: "ReactiveRouteRouter",
|
|
32
|
+
props: {
|
|
33
|
+
// We keep the type generic via TS, but at runtime it's just any
|
|
34
|
+
router: { type: Object, required: true },
|
|
35
|
+
beforeSetPageComponent: { type: Function, required: false },
|
|
36
|
+
beforeUpdatePageComponent: { type: Function, required: false },
|
|
37
|
+
beforeMount: { type: Function, required: false }
|
|
38
|
+
},
|
|
39
|
+
// TS helper, runtime ignored
|
|
40
|
+
setup(props) {
|
|
41
|
+
const disposerRef = (0, import_vue.reactive)({ current: null });
|
|
42
|
+
const config = props.router.adapters.makeObservable({
|
|
43
|
+
loadedComponentName: void 0,
|
|
44
|
+
loadedComponentPage: void 0,
|
|
45
|
+
currentProps: {}
|
|
46
|
+
});
|
|
47
|
+
function redirectOnHistoryPop() {
|
|
48
|
+
if (!import_reactive_route.history) return;
|
|
49
|
+
import_reactive_route.history.listen((params) => {
|
|
50
|
+
if (params.action !== "POP") return;
|
|
51
|
+
const previousRoutePathname = props.router.routesHistory[props.router.routesHistory.length - 2];
|
|
52
|
+
if (previousRoutePathname === params.location.pathname) {
|
|
53
|
+
props.router.routesHistory.pop();
|
|
54
|
+
}
|
|
55
|
+
void props.router.restoreFromURL({
|
|
56
|
+
pathname: import_reactive_route.history.location.pathname,
|
|
57
|
+
noHistoryPush: true
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function setLoadedComponent() {
|
|
62
|
+
const currentRouteName = props.router.currentRoute.name;
|
|
63
|
+
const currentRoutePage = props.router.currentRoute.pageId;
|
|
64
|
+
const componentConfig = props.router.routes[currentRouteName];
|
|
65
|
+
let preventRedirect = false;
|
|
66
|
+
if (props.router.isRedirecting) preventRedirect = true;
|
|
67
|
+
else if (config.loadedComponentName === currentRouteName) {
|
|
68
|
+
preventRedirect = true;
|
|
69
|
+
} else if (config.loadedComponentPage != null && currentRouteName != null) {
|
|
70
|
+
if (config.loadedComponentPage === currentRoutePage) {
|
|
71
|
+
props.router.adapters.replaceObject(
|
|
72
|
+
config.currentProps,
|
|
73
|
+
"props" in componentConfig ? componentConfig.props : {}
|
|
74
|
+
);
|
|
75
|
+
preventRedirect = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (preventRedirect) return;
|
|
79
|
+
props.router.adapters.batch(() => {
|
|
80
|
+
if (config.loadedComponentName) {
|
|
81
|
+
props.beforeUpdatePageComponent?.();
|
|
82
|
+
}
|
|
83
|
+
props.beforeSetPageComponent?.(componentConfig);
|
|
84
|
+
props.router.adapters.batch(() => {
|
|
85
|
+
props.router.adapters.replaceObject(
|
|
86
|
+
config.currentProps,
|
|
87
|
+
"props" in componentConfig ? componentConfig.props : {}
|
|
88
|
+
);
|
|
89
|
+
config.loadedComponentName = currentRouteName;
|
|
90
|
+
config.loadedComponentPage = componentConfig.pageId;
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
setLoadedComponent();
|
|
95
|
+
(0, import_vue.onMounted)(() => {
|
|
96
|
+
props.beforeMount?.();
|
|
97
|
+
redirectOnHistoryPop();
|
|
98
|
+
if (props.router.adapters.immediateSetComponent) {
|
|
99
|
+
props.router.adapters.batch(() => {
|
|
100
|
+
setLoadedComponent();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const disposer = props.router.adapters.autorun(() => setLoadedComponent());
|
|
104
|
+
if (typeof disposer === "function") disposerRef.current = disposer;
|
|
105
|
+
});
|
|
106
|
+
(0, import_vue.onBeforeUnmount)(() => {
|
|
107
|
+
disposerRef.current?.();
|
|
108
|
+
});
|
|
109
|
+
return () => {
|
|
110
|
+
if (!config.loadedComponentName) return null;
|
|
111
|
+
const LoadedComponent = props.router.routes[config.loadedComponentName]?.component || null;
|
|
112
|
+
if (!LoadedComponent) return null;
|
|
113
|
+
return (0, import_vue.h)(LoadedComponent, { ...config.currentProps || {}, router: props.router });
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// packages/adapters/vue.ts
|
|
2
|
+
import { reactive, watchEffect } from "vue";
|
|
3
|
+
var adapters = {
|
|
4
|
+
batch: (cb) => cb(),
|
|
5
|
+
autorun: watchEffect,
|
|
6
|
+
replaceObject: (obj, newObj) => {
|
|
7
|
+
for (const variableKey in obj) {
|
|
8
|
+
if (obj.hasOwnProperty(variableKey)) {
|
|
9
|
+
delete obj[variableKey];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
Object.assign(obj, newObj);
|
|
13
|
+
},
|
|
14
|
+
makeObservable: reactive
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
adapters
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "module"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// packages/vue/Router.ts
|
|
2
|
+
import { history } from "reactive-route";
|
|
3
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, reactive } from "vue";
|
|
4
|
+
var Router = defineComponent({
|
|
5
|
+
name: "ReactiveRouteRouter",
|
|
6
|
+
props: {
|
|
7
|
+
// We keep the type generic via TS, but at runtime it's just any
|
|
8
|
+
router: { type: Object, required: true },
|
|
9
|
+
beforeSetPageComponent: { type: Function, required: false },
|
|
10
|
+
beforeUpdatePageComponent: { type: Function, required: false },
|
|
11
|
+
beforeMount: { type: Function, required: false }
|
|
12
|
+
},
|
|
13
|
+
// TS helper, runtime ignored
|
|
14
|
+
setup(props) {
|
|
15
|
+
const disposerRef = reactive({ current: null });
|
|
16
|
+
const config = props.router.adapters.makeObservable({
|
|
17
|
+
loadedComponentName: void 0,
|
|
18
|
+
loadedComponentPage: void 0,
|
|
19
|
+
currentProps: {}
|
|
20
|
+
});
|
|
21
|
+
function redirectOnHistoryPop() {
|
|
22
|
+
if (!history) return;
|
|
23
|
+
history.listen((params) => {
|
|
24
|
+
if (params.action !== "POP") return;
|
|
25
|
+
const previousRoutePathname = props.router.routesHistory[props.router.routesHistory.length - 2];
|
|
26
|
+
if (previousRoutePathname === params.location.pathname) {
|
|
27
|
+
props.router.routesHistory.pop();
|
|
28
|
+
}
|
|
29
|
+
void props.router.restoreFromURL({
|
|
30
|
+
pathname: history.location.pathname,
|
|
31
|
+
noHistoryPush: true
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function setLoadedComponent() {
|
|
36
|
+
const currentRouteName = props.router.currentRoute.name;
|
|
37
|
+
const currentRoutePage = props.router.currentRoute.pageId;
|
|
38
|
+
const componentConfig = props.router.routes[currentRouteName];
|
|
39
|
+
let preventRedirect = false;
|
|
40
|
+
if (props.router.isRedirecting) preventRedirect = true;
|
|
41
|
+
else if (config.loadedComponentName === currentRouteName) {
|
|
42
|
+
preventRedirect = true;
|
|
43
|
+
} else if (config.loadedComponentPage != null && currentRouteName != null) {
|
|
44
|
+
if (config.loadedComponentPage === currentRoutePage) {
|
|
45
|
+
props.router.adapters.replaceObject(
|
|
46
|
+
config.currentProps,
|
|
47
|
+
"props" in componentConfig ? componentConfig.props : {}
|
|
48
|
+
);
|
|
49
|
+
preventRedirect = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (preventRedirect) return;
|
|
53
|
+
props.router.adapters.batch(() => {
|
|
54
|
+
if (config.loadedComponentName) {
|
|
55
|
+
props.beforeUpdatePageComponent?.();
|
|
56
|
+
}
|
|
57
|
+
props.beforeSetPageComponent?.(componentConfig);
|
|
58
|
+
props.router.adapters.batch(() => {
|
|
59
|
+
props.router.adapters.replaceObject(
|
|
60
|
+
config.currentProps,
|
|
61
|
+
"props" in componentConfig ? componentConfig.props : {}
|
|
62
|
+
);
|
|
63
|
+
config.loadedComponentName = currentRouteName;
|
|
64
|
+
config.loadedComponentPage = componentConfig.pageId;
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
setLoadedComponent();
|
|
69
|
+
onMounted(() => {
|
|
70
|
+
props.beforeMount?.();
|
|
71
|
+
redirectOnHistoryPop();
|
|
72
|
+
if (props.router.adapters.immediateSetComponent) {
|
|
73
|
+
props.router.adapters.batch(() => {
|
|
74
|
+
setLoadedComponent();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const disposer = props.router.adapters.autorun(() => setLoadedComponent());
|
|
78
|
+
if (typeof disposer === "function") disposerRef.current = disposer;
|
|
79
|
+
});
|
|
80
|
+
onBeforeUnmount(() => {
|
|
81
|
+
disposerRef.current?.();
|
|
82
|
+
});
|
|
83
|
+
return () => {
|
|
84
|
+
if (!config.loadedComponentName) return null;
|
|
85
|
+
const LoadedComponent = props.router.routes[config.loadedComponentName]?.component || null;
|
|
86
|
+
if (!LoadedComponent) return null;
|
|
87
|
+
return h(LoadedComponent, { ...config.currentProps || {}, router: props.router });
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
export {
|
|
92
|
+
Router
|
|
93
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "module"}
|
package/e2e/app.test.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const h1 = 'h1';
|
|
4
|
+
|
|
5
|
+
test.describe('App routing E2E', () => {
|
|
6
|
+
test('home redirects to /static', async ({ page }) => {
|
|
7
|
+
await page.goto('/');
|
|
8
|
+
await page.waitForLoadState('networkidle');
|
|
9
|
+
await expect(page).toHaveURL('/static');
|
|
10
|
+
|
|
11
|
+
await expect(page.locator(h1)).toHaveText('Static Page');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('redirect from /static to /page/example works', async ({ page }) => {
|
|
15
|
+
await page.goto('/static');
|
|
16
|
+
await page.waitForLoadState('networkidle');
|
|
17
|
+
await expect(page).toHaveURL('/static');
|
|
18
|
+
|
|
19
|
+
await expect(page.locator(h1)).toHaveText('Static Page');
|
|
20
|
+
|
|
21
|
+
await page.getByText('Go to Dynamic Page').click();
|
|
22
|
+
await page.waitForLoadState('networkidle');
|
|
23
|
+
await expect(page).toHaveURL('/page/example');
|
|
24
|
+
|
|
25
|
+
await expect(page.locator(h1)).toHaveText('Dynamic Page');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('dynamic route with valid param renders and shows params', async ({ page }) => {
|
|
29
|
+
await page.goto('/page/example');
|
|
30
|
+
await page.waitForLoadState('networkidle');
|
|
31
|
+
await expect(page).toHaveURL('/page/example');
|
|
32
|
+
|
|
33
|
+
const preLocator = page.locator('.dynamic-page pre').nth(1);
|
|
34
|
+
|
|
35
|
+
await expect(page.locator(h1)).toHaveText('Dynamic Page');
|
|
36
|
+
await expect(preLocator).toContainText('"foo": "example"');
|
|
37
|
+
|
|
38
|
+
await page.getByText('Go to random dynamic value').click();
|
|
39
|
+
|
|
40
|
+
const randomFoo = ((await preLocator.textContent()) || '').match(/foo": "(\d+)"/)?.[1];
|
|
41
|
+
|
|
42
|
+
await expect(preLocator).toContainText(`"foo": "${randomFoo}"`);
|
|
43
|
+
await expect(page).toHaveURL(`/page/${randomFoo}`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('redirect from /static to /query works', async ({ page }) => {
|
|
47
|
+
await page.goto('/static');
|
|
48
|
+
await page.waitForLoadState('networkidle');
|
|
49
|
+
await expect(page).toHaveURL('/static');
|
|
50
|
+
|
|
51
|
+
await expect(page.locator(h1)).toHaveText('Static Page');
|
|
52
|
+
|
|
53
|
+
await page.getByText('Go to Query Page').click();
|
|
54
|
+
await page.waitForLoadState('networkidle');
|
|
55
|
+
await expect(page).toHaveURL('/query?foo=example');
|
|
56
|
+
|
|
57
|
+
await expect(page.locator(h1)).toHaveText('Query Page');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('query route shows params', async ({ page }) => {
|
|
61
|
+
await page.goto('/query?foo=1');
|
|
62
|
+
await page.waitForLoadState('networkidle');
|
|
63
|
+
await expect(page).toHaveURL('/query');
|
|
64
|
+
|
|
65
|
+
const preLocator = page.locator('.query-page pre').nth(1);
|
|
66
|
+
|
|
67
|
+
await expect(page.locator(h1)).toHaveText('Query Page');
|
|
68
|
+
await expect(preLocator).toContainText('{}');
|
|
69
|
+
|
|
70
|
+
await page.getByText('Go to random query value').click();
|
|
71
|
+
|
|
72
|
+
const randomFoo = ((await preLocator.textContent()) || '').match(/foo": "(\d+)"/)?.[1];
|
|
73
|
+
|
|
74
|
+
await expect(preLocator).toContainText(`"foo": "${randomFoo}"`);
|
|
75
|
+
await expect(page).toHaveURL(`/query?foo=${randomFoo}`);
|
|
76
|
+
|
|
77
|
+
await page.goto('/query?foo=bar');
|
|
78
|
+
await page.waitForLoadState('networkidle');
|
|
79
|
+
await expect(page).toHaveURL('/query?foo=bar');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('invalid routes redirect to 404', async ({ page }) => {
|
|
83
|
+
await page.goto('/page/ab');
|
|
84
|
+
await page.waitForLoadState('networkidle');
|
|
85
|
+
|
|
86
|
+
await expect(page.locator('.error-code')).toHaveText('404');
|
|
87
|
+
await expect(page.locator(h1)).toHaveText('Page Not Found');
|
|
88
|
+
await expect(page).toHaveURL('/error404');
|
|
89
|
+
|
|
90
|
+
await page.goto('/ab');
|
|
91
|
+
await page.waitForLoadState('networkidle');
|
|
92
|
+
|
|
93
|
+
await expect(page.locator('.error-code')).toHaveText('404');
|
|
94
|
+
await expect(page.locator(h1)).toHaveText('Page Not Found');
|
|
95
|
+
await expect(page).toHaveURL('/error404');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('prevent: coming from Dynamic to Prevent redirects to Static (beforeEnter)', async ({
|
|
99
|
+
page,
|
|
100
|
+
}) => {
|
|
101
|
+
await page.goto('/page/example');
|
|
102
|
+
await page.waitForLoadState('networkidle');
|
|
103
|
+
|
|
104
|
+
await expect(page.locator(h1)).toHaveText('Dynamic Page');
|
|
105
|
+
|
|
106
|
+
await page
|
|
107
|
+
.getByRole('button', { name: 'Go to Prevent Page (will redirect to Static)' })
|
|
108
|
+
.click();
|
|
109
|
+
await page.waitForLoadState('networkidle');
|
|
110
|
+
|
|
111
|
+
await expect(page.locator(h1)).toHaveText('Static Page');
|
|
112
|
+
await expect(page).toHaveURL('/static');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('prevent: from Prevent trying to go to Query is blocked (beforeLeave) and URL remains the same', async ({
|
|
116
|
+
page,
|
|
117
|
+
}) => {
|
|
118
|
+
await page.goto('/prevent');
|
|
119
|
+
await page.waitForLoadState('networkidle');
|
|
120
|
+
|
|
121
|
+
await expect(page.locator(h1)).toHaveText('Prevent Redirect Page');
|
|
122
|
+
|
|
123
|
+
await page.getByRole('button', { name: 'Try to go to Query Page (will be blocked)' }).click();
|
|
124
|
+
|
|
125
|
+
await page.waitForTimeout(100);
|
|
126
|
+
|
|
127
|
+
await expect(page.locator(h1)).toHaveText('Prevent Redirect Page');
|
|
128
|
+
await expect(page).toHaveURL('/prevent');
|
|
129
|
+
});
|
|
130
|
+
});
|
package/e2e/teardown.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export default function teardown() {
|
|
5
|
+
fs.readdirSync(path.resolve('examples')).map((project) =>
|
|
6
|
+
fs
|
|
7
|
+
.readdirSync(path.resolve('examples', project))
|
|
8
|
+
.filter((folderName) => folderName.startsWith('dist'))
|
|
9
|
+
.map((folderName) => path.resolve('examples', project, folderName))
|
|
10
|
+
.forEach((folderPath) => fs.rmSync(folderPath, { recursive: true, force: true }))
|
|
11
|
+
);
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "reactive-route",
|
|
3
3
|
"author": "Dmitry Kazakov",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.0.1-alpha.
|
|
5
|
+
"version": "0.0.1-alpha.27",
|
|
6
6
|
"description": "Reactive Router for different frameworks",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"upd": "rm -rf ./node_modules&&pnpm i --prefer-offline",
|
|
13
13
|
"build": "rm -rf ./dist&&node --import tsx ./scripts/build.ts&&tsc -b ./tsconfig.types.react.json&&tsc -b ./tsconfig.types.solid.json",
|
|
14
|
-
"test": "vitest run&&node --import tsx ./scripts/genCoverageBadge.ts",
|
|
15
|
-
"test-watch": "vitest --coverage=false",
|
|
16
14
|
"npm-publish": "pnpm run build&&pnpm run test&&npm publish --access public",
|
|
17
15
|
"analyze:js": "biome check --no-errors-on-unmatched .",
|
|
18
16
|
"format:js": "biome check --no-errors-on-unmatched --write",
|
|
@@ -21,9 +19,14 @@
|
|
|
21
19
|
"prepare": "husky",
|
|
22
20
|
"upgrade-examples": "pnpm upgrade -r reactive-route@latest",
|
|
23
21
|
"upgrade-lib": "bash -c 'pnpm upgrade -r $@' bash",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
22
|
+
"test": "vitest run&&node --import tsx ./scripts/genCoverageBadge.ts",
|
|
23
|
+
"test-watch": "vitest --coverage=false",
|
|
24
|
+
"e2e": "playwright test",
|
|
25
|
+
"e2e:ui": "playwright test --ui",
|
|
26
|
+
"e2e:install": "playwright install --with-deps",
|
|
27
|
+
"docs:dev": "vitepress dev vitepress",
|
|
28
|
+
"docs:build": "vitepress build vitepress --outDir docs",
|
|
29
|
+
"docs:preview": "vitepress preview vitepress"
|
|
27
30
|
},
|
|
28
31
|
"lint-staged": {
|
|
29
32
|
"(*.js|*.ts|*.tsx|*.mjs)": [
|
|
@@ -43,6 +46,8 @@
|
|
|
43
46
|
"@biomejs/biome": "2.2.5",
|
|
44
47
|
"@espcom/esbuild-plugin-compress": "1.2.0",
|
|
45
48
|
"@espcom/esbuild-plugin-replace": "1.3.2",
|
|
49
|
+
"playwright": "1.56.1",
|
|
50
|
+
"@playwright/test": "1.56.1",
|
|
46
51
|
"@preact/preset-vite": "2.10.2",
|
|
47
52
|
"@solidjs/testing-library": "0.8.10",
|
|
48
53
|
"@testing-library/preact": "3.2.4",
|
|
@@ -102,6 +107,16 @@
|
|
|
102
107
|
"require": "./dist/cjs/solid/index.js",
|
|
103
108
|
"import": "./dist/esm/solid/index.js"
|
|
104
109
|
},
|
|
110
|
+
"./vue": {
|
|
111
|
+
"types": "./dist/vue/index.d.ts",
|
|
112
|
+
"require": "./dist/cjs/vue/index.js",
|
|
113
|
+
"import": "./dist/esm/vue/index.js"
|
|
114
|
+
},
|
|
115
|
+
"./adapters/vue": {
|
|
116
|
+
"types": "./dist/adapters/vue.d.ts",
|
|
117
|
+
"require": "./dist/cjs/adapters/vue/index.js",
|
|
118
|
+
"import": "./dist/esm/adapters/vue/index.js"
|
|
119
|
+
},
|
|
105
120
|
"./adapters/mobx-react": {
|
|
106
121
|
"types": "./dist/adapters/mobx-react.d.ts",
|
|
107
122
|
"require": "./dist/cjs/adapters/mobx-react/index.js",
|
|
@@ -141,4 +156,4 @@
|
|
|
141
156
|
"main": "dist/cjs/index.js",
|
|
142
157
|
"module": "dist/esm/index.js",
|
|
143
158
|
"types": "dist/core/index.d.ts"
|
|
144
|
-
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const csrVariants: Array<{ name: string; scriptName: string }> = [
|
|
4
|
+
{ name: '[React+Mobx]', scriptName: './examples/react mobx' },
|
|
5
|
+
{ name: '[React+Observable]', scriptName: './examples/react observable' },
|
|
6
|
+
{ name: '[Preact+Mobx]', scriptName: './examples/preact mobx' },
|
|
7
|
+
{ name: '[Preact+Observable]', scriptName: './examples/preact observable' },
|
|
8
|
+
{ name: '[Solid+Mobx]', scriptName: './examples/solid mobx' },
|
|
9
|
+
{ name: '[Solid+Observable]', scriptName: './examples/solid observable' },
|
|
10
|
+
{ name: '[Solid+Solid]', scriptName: './examples/solid solid' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const ssrVariants: Array<{ name: string; scriptName: string }> = [
|
|
14
|
+
{ name: '[React+Mobx+SSR]', scriptName: './examples/react ssr-mobx' },
|
|
15
|
+
{ name: '[React+Observable+SSR]', scriptName: './examples/react ssr-observable' },
|
|
16
|
+
{ name: '[Preact+Mobx+SSR]', scriptName: './examples/preact ssr-mobx' },
|
|
17
|
+
{ name: '[Preact+Observable+SSR]', scriptName: './examples/preact ssr-observable' },
|
|
18
|
+
{ name: '[Solid+Mobx+SSR]', scriptName: './examples/solid ssr-mobx' },
|
|
19
|
+
{ name: '[Solid+Observable+SSR]', scriptName: './examples/solid ssr-observable' },
|
|
20
|
+
{ name: '[Solid+Solid+SSR]', scriptName: './examples/solid ssr-solid' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const variants: Array<{ name: string; port: number; scriptName: string }> = [
|
|
24
|
+
...csrVariants,
|
|
25
|
+
...ssrVariants,
|
|
26
|
+
].map((variant, i) => ({ ...variant, port: 8002 + i * 2 }));
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
testDir: './e2e',
|
|
30
|
+
fullyParallel: true,
|
|
31
|
+
forbidOnly: false,
|
|
32
|
+
retries: 0,
|
|
33
|
+
// workers: 10,
|
|
34
|
+
reporter: [['list']],
|
|
35
|
+
webServer: variants.map((variant) => ({
|
|
36
|
+
name: variant.name,
|
|
37
|
+
command: `pnpm --filter ${variant.scriptName} ${variant.port} test`,
|
|
38
|
+
port: variant.port,
|
|
39
|
+
reuseExistingServer: false,
|
|
40
|
+
timeout: 10000,
|
|
41
|
+
cwd: __dirname,
|
|
42
|
+
stdout: 'pipe',
|
|
43
|
+
})),
|
|
44
|
+
projects: variants.map((variant) => ({
|
|
45
|
+
name: variant.name,
|
|
46
|
+
use: {
|
|
47
|
+
baseURL: `http://localhost:${variant.port}`,
|
|
48
|
+
...devices['Desktop Chrome'],
|
|
49
|
+
},
|
|
50
|
+
})),
|
|
51
|
+
globalTeardown: require.resolve('./e2e/teardown'),
|
|
52
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hash": "5a633cf9",
|
|
3
|
+
"configHash": "c2aca7c2",
|
|
4
|
+
"lockfileHash": "81207e34",
|
|
5
|
+
"browserHash": "ffb49e63",
|
|
6
|
+
"optimized": {
|
|
7
|
+
"vue": {
|
|
8
|
+
"src": "../../../../node_modules/.pnpm/vue@3.5.22_typescript@5.9.2/node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
|
9
|
+
"file": "vue.js",
|
|
10
|
+
"fileHash": "acc43dc7",
|
|
11
|
+
"needsInterop": false
|
|
12
|
+
},
|
|
13
|
+
"vitepress > @vue/devtools-api": {
|
|
14
|
+
"src": "../../../../node_modules/.pnpm/@vue+devtools-api@8.0.1/node_modules/@vue/devtools-api/dist/index.js",
|
|
15
|
+
"file": "vitepress___@vue_devtools-api.js",
|
|
16
|
+
"fileHash": "f6e97e3d",
|
|
17
|
+
"needsInterop": false
|
|
18
|
+
},
|
|
19
|
+
"vitepress > @vueuse/core": {
|
|
20
|
+
"src": "../../../../node_modules/.pnpm/@vueuse+core@13.9.0_vue@3.5.22_typescript@5.9.2_/node_modules/@vueuse/core/index.mjs",
|
|
21
|
+
"file": "vitepress___@vueuse_core.js",
|
|
22
|
+
"fileHash": "10a3e7ca",
|
|
23
|
+
"needsInterop": false
|
|
24
|
+
},
|
|
25
|
+
"vitepress > @vueuse/integrations/useFocusTrap": {
|
|
26
|
+
"src": "../../../../node_modules/.pnpm/@vueuse+integrations@13.9.0_focus-trap@7.6.5_vue@3.5.22_typescript@5.9.2_/node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
|
27
|
+
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
|
28
|
+
"fileHash": "a8fdd658",
|
|
29
|
+
"needsInterop": false
|
|
30
|
+
},
|
|
31
|
+
"vitepress > mark.js/src/vanilla.js": {
|
|
32
|
+
"src": "../../../../node_modules/.pnpm/mark.js@8.11.1/node_modules/mark.js/src/vanilla.js",
|
|
33
|
+
"file": "vitepress___mark__js_src_vanilla__js.js",
|
|
34
|
+
"fileHash": "be042848",
|
|
35
|
+
"needsInterop": false
|
|
36
|
+
},
|
|
37
|
+
"vitepress > minisearch": {
|
|
38
|
+
"src": "../../../../node_modules/.pnpm/minisearch@7.1.2/node_modules/minisearch/dist/es/index.js",
|
|
39
|
+
"file": "vitepress___minisearch.js",
|
|
40
|
+
"fileHash": "f4e7a04e",
|
|
41
|
+
"needsInterop": false
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"chunks": {
|
|
45
|
+
"chunk-VGAXUAUJ": {
|
|
46
|
+
"file": "chunk-VGAXUAUJ.js"
|
|
47
|
+
},
|
|
48
|
+
"chunk-FL23S3EK": {
|
|
49
|
+
"file": "chunk-FL23S3EK.js"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|