// @ts-check
require('./helper')
const expect = require('expect')
+const { normalizeRecord } = require('../src/matcher')
const { extractComponentsGuards } = require('../src/utils')
const { START_LOCATION_NORMALIZED } = require('../src/types')
const { components } = require('./utils')
/**
*
- * @param {MatchedRouteRecord[]} components
+ * @param {Exclude<RouteRecord, RouteRecordRedirect>[]} components
*/
async function checkGuards(components, n) {
beforeRouteEnter.mockClear()
+ const ncomps = components.map(normalizeRecord)
const guards = await extractComponentsGuards(
- components,
+ // type is fine as we excluded RouteRecordRedirect in components argument
+ components.map(normalizeRecord),
'beforeRouteEnter',
to,
from
*
* @param {RouteRecord | RouteRecord[]} record Record or records we are testing the matcher against
* @param {MatcherLocation} location location we want to reolve against
- * @param {Partial<MatcherLocationNormalized>} resolved Expected resolved location given by the matcher
+ * @param {Partial<MatcherLocationNormalized & { component: any }>} resolved Expected resolved location given by the matcher
* @param {MatcherLocationNormalized} [start] Optional currentLocation used when resolving
*/
function assertRecordMatch(
resolved.params = resolved.params || {}
}
+ for (const matched of resolved.matched) {
+ if ('component' in matched) {
+ // @ts-ignore
+ matched.components = { default: matched.component }
+ // @ts-ignore
+ delete matched.component
+ }
+ }
+
const result = matcher.resolve(
{
...targetLocation,
},
{
...NestedChildWithParam,
- path: `${Foo.path}/${NestedWithParam.path}/${
- NestedChildWithParam.path
- }`,
+ path: `${Foo.path}/${NestedWithParam.path}/${NestedChildWithParam.path}`,
},
],
}
},
{
...NestedChildWithParam,
- path: `${Foo.path}/${NestedWithParam.path}/${
- NestedChildWithParam.path
- }`,
+ path: `${Foo.path}/${NestedWithParam.path}/${NestedChildWithParam.path}`,
},
],
}
*
*/
// @ts-check
-require("./helper");
-const expect = require("expect");
-const { default: RouterView } = require("../src/components/View");
-const { components, isMocha } = require("./utils");
+require('./helper')
+const expect = require('expect')
+const { default: RouterView } = require('../src/components/View')
+const { components, isMocha } = require('./utils')
-describe("RouterView", () => {
+/** @typedef {import('../src/types').RouteLocationNormalized} RouteLocationNormalized */
+
+/** @type {Record<string, RouteLocationNormalized>} */
+const routes = {
+ root: {
+ fullPath: '/',
+ name: undefined,
+ path: '/',
+ query: {},
+ params: {},
+ hash: '',
+ meta: {},
+ matched: [{ components: { default: components.Home }, path: '/' }],
+ },
+}
+
+describe('RouterView', () => {
// skip these tests on mocha because @vue/test-utils
// do not work correctly
- if (isMocha()) return;
- const { mount } = require("@vue/test-utils");
+ if (isMocha()) return
+ const { mount } = require('@vue/test-utils')
- it("displays current route component", async () => {
+ it('displays current route component', async () => {
const wrapper = await mount(RouterView, {
mocks: {
- $route: {
- fullPath: "/",
- name: undefined,
- path: "/",
- query: {},
- params: {},
- hash: "",
- meta: {},
- matched: [{ component: components.Home, path: "/", name: undefined }]
- }
- }
- });
- expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Home</div>"`);
- });
-});
+ $route: routes.root,
+ },
+ })
+ expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Home</div>"`)
+ })
+})
name: 'RouterView',
functional: true,
- render(_, { children, parent, data }) {
+ props: {
+ name: {
+ type: String,
+ default: 'default',
+ },
+ },
+
+ render(_, { children, parent, data, props }) {
// used by devtools to display a router-view badge
// @ts-ignore
data.routerView = true
// render empty node if no matched route
if (!matched) return h()
- const component = matched.component
+ const component = matched.components[props.name]
return h(component, data, children)
},
MatcherLocation,
MatcherLocationNormalized,
MatcherLocationRedirect,
+ // TODO: add it to matched
+ // MatchedRouteRecord,
} from './types/index'
import { NoRouteMatchError, InvalidRouteMatch } from './errors'
+type NormalizedRouteRecord = Exclude<RouteRecord, { component: any }> // normalize component/components into components
+
interface RouteMatcher {
re: RegExp
resolve: (params?: RouteParams) => string
- record: RouteRecord // TODO: NormalizedRouteRecord?
+ record: NormalizedRouteRecord
parent: RouteMatcher | void
// TODO: children so they can be removed
// children: RouteMatcher[]
keys: string[]
}
+/**
+ * Normalizes a RouteRecord into a MatchedRouteRecord. Creates a copy
+ * @param record
+ * @returns the normalized version
+ */
+export function normalizeRecord(
+ record: Readonly<RouteRecord>
+): NormalizedRouteRecord {
+ if ('component' in record) {
+ const { component, ...rest } = record
+ // @ts-ignore
+ rest.components = { default: component }
+ return rest as NormalizedRouteRecord
+ }
+
+ // otherwise just create a copy
+ return { ...record }
+}
+
export class RouterMatcher {
private matchers: RouteMatcher[] = []
const keys: pathToRegexp.Key[] = []
const options: pathToRegexp.RegExpOptions = {}
- const recordCopy = { ...record }
+ const recordCopy = normalizeRecord(record)
+
if (parent) {
// if the child isn't an absolute route
if (record.path[0] !== '/') {
const value = result[i + 1]
if (!value) {
throw new Error(
- `Error parsing path "${
- location.path
- }" when looking for param "${key}"`
+ `Error parsing path "${location.path}" when looking for param "${key}"`
)
}
params[key] = value
| RouteQueryAndHash & LocationAsName & RouteLocationOptions
| RouteQueryAndHash & LocationAsRelative & RouteLocationOptions
-// exposed to the user in a very consistant way
-export type MatchedRouteRecord = Exclude<RouteRecord, RouteRecordRedirect>
+// A matched record cannot be a redirection and must contain
+// a normalized version of components with { default: Component } instead of `component`
+export type MatchedRouteRecord = Exclude<
+ RouteRecord,
+ RouteRecordRedirect | RouteRecordSingleView
+>
export interface RouteLocationNormalized
extends Required<RouteQueryAndHash & LocationAsRelative & LocationAsPath> {
interface RouteRecordMultipleViews extends RouteRecordCommon {
components: Record<string, RouteComponent | Lazy<RouteComponent>>
+ // TODO: add tests
+ children?: RouteRecord[]
}
export type RouteRecord =
await Promise.all(
matched.map(async record => {
// TODO: cache async routes per record
- if ('component' in record) {
- const { component } = record
+ for (const name in record.components) {
+ const component = record.components[name]
const resolvedComponent = await (typeof component === 'function'
? component()
: component)
if (guard) {
guards.push(guardToPromiseFn(guard, to, from))
}
- } else {
- for (const name in record.components) {
- const component = record.components[name]
- const resolvedComponent = await (typeof component === 'function'
- ? component()
- : component)
-
- const guard = resolvedComponent[guardType]
- if (guard) {
- guards.push(guardToPromiseFn(guard, to, from))
- }
- }
}
})
)