})
})
+ it('works with partial path with no query', () => {
+ expect(parseURL('foo#hash')).toEqual({
+ fullPath: '/foo#hash',
+ path: '/foo',
+ hash: '#hash',
+ query: {},
+ })
+ })
+
+ it('works with partial path', () => {
+ expect(parseURL('foo?f=foo#hash')).toEqual({
+ fullPath: '/foo?f=foo#hash',
+ path: '/foo',
+ hash: '#hash',
+ query: { f: 'foo' },
+ })
+ })
+
+ it('works with only query', () => {
+ expect(parseURL('?f=foo')).toEqual({
+ fullPath: '/?f=foo',
+ path: '/',
+ hash: '',
+ query: { f: 'foo' },
+ })
+ })
+
+ it('works with only hash', () => {
+ expect(parseURL('#foo')).toEqual({
+ fullPath: '/#foo',
+ path: '/',
+ hash: '#foo',
+ query: {},
+ })
+ })
+
+ it('works with partial path and current location', () => {
+ expect(parseURL('foo', '/parent/bar')).toEqual({
+ fullPath: '/parent/foo',
+ path: '/parent/foo',
+ hash: '',
+ query: {},
+ })
+ })
+
+ it('works with partial path with query and hash and current location', () => {
+ expect(parseURL('foo?f=foo#hash', '/parent/bar')).toEqual({
+ fullPath: '/parent/foo?f=foo#hash',
+ path: '/parent/foo',
+ hash: '#hash',
+ query: { f: 'foo' },
+ })
+ })
+
+ it('works with relative query and current location', () => {
+ expect(parseURL('?f=foo', '/parent/bar')).toEqual({
+ fullPath: '/parent/bar?f=foo',
+ path: '/parent/bar',
+ hash: '',
+ query: { f: 'foo' },
+ })
+ })
+
+ it('works with relative hash and current location', () => {
+ expect(parseURL('#hash', '/parent/bar')).toEqual({
+ fullPath: '/parent/bar#hash',
+ path: '/parent/bar',
+ hash: '#hash',
+ query: {},
+ })
+ })
+
it('extracts the query', () => {
expect(parseURL('/foo?a=one&b=two')).toEqual({
fullPath: '/foo?a=one&b=two',
MatcherLocation,
} from '../../src/types'
import { MatcherLocationNormalizedLoose } from '../utils'
+import { mockWarn } from 'jest-mock-warn'
// @ts-ignore
const component: RouteComponent = null
})
describe('LocationAsRelative', () => {
+ mockWarn()
+ it('warns if a path isn not absolute', () => {
+ const record = {
+ path: '/parent',
+ components,
+ }
+ const matcher = createRouterMatcher([record], {})
+ matcher.resolve(
+ { path: 'two' },
+ {
+ path: '/parent/one',
+ name: undefined,
+ params: {},
+ matched: [] as any,
+ meta: {},
+ }
+ )
+ expect('received "two"').toHaveBeenWarned()
+ })
+
it('matches with nothing', () => {
const record = { path: '/home', name: 'Home', components }
assertRecordMatch(
*
* @param parseQuery
* @param location - URI to normalize
+ * @param currentLocation - current absolute location. Allows resolving relative
+ * paths. Must start with `/`. Defaults to `/`
* @returns a normalized history location
*/
export function parseURL(
parseQuery: (search: string) => LocationQuery,
- location: string
+ location: string,
+ currentLocation: string = '/'
): LocationNormalized {
- let path = '',
+ let path: string | undefined,
query: LocationQuery = {},
searchString = '',
hash = ''
}
// no search and no query
- path = path || location
+ path = path != null ? path : location
+ // empty path means a relative query or hash `?foo=f`, `#thing`
+ if (!path) {
+ path = currentLocation + path
+ } else if (path[0] !== '/') {
+ // relative to current location. Currently we only support simple relative
+ // but no `..`, `.`, or complex like `../.././..`. We will always leave the
+ // leading slash so we can safely append path
+ path = currentLocation.replace(/[^\/]*$/, '') + path
+ }
return {
- fullPath: location,
+ fullPath: path + (searchString && '?') + searchString + hash,
path,
query,
hash,
comparePathParserScore,
PathParserOptions,
} from './pathParserRanker'
+import { warn } from 'vue'
let noop = () => {}
// throws if cannot be stringified
path = matcher.stringify(params)
} else if ('path' in location) {
- matcher = matchers.find(m => m.re.test(location.path))
- // matcher should have a value after the loop
-
// no need to resolve the path with the matcher as it was provided
// this also allows the user to control the encoding
path = location.path
+
+ if (__DEV__ && path[0] !== '/') {
+ warn(
+ `The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-router-next.`
+ )
+ }
+
+ matcher = matchers.find(m => m.re.test(path))
+ // matcher should have a value after the loop
+
if (matcher) {
// TODO: dev warning of unused params if provided
- params = matcher.parse(location.path)!
+ params = matcher.parse(path)!
name = matcher.record.name
}
// location is a relative path
}
function resolve(
- location: RouteLocationRaw,
- currentLocation?: RouteLocationNormalizedLoaded
+ location: Readonly<RouteLocationRaw>,
+ currentLocation?: Readonly<RouteLocationNormalizedLoaded>
): RouteLocation & { href: string } {
// const objectLocation = routerLocationAsObject(location)
currentLocation = currentLocation || currentRoute.value
if (typeof location === 'string') {
- let locationNormalized = parseURL(parseQuery, location)
+ let locationNormalized = parseURL(
+ parseQuery,
+ location,
+ currentLocation.path
+ )
let matchedRoute = matcher.resolve(
{ path: locationNormalized.path },
currentLocation
}
}
+ // TODO: dev warning if params and path at the same time
+
+ // path could be relative in object as well
+ if ('path' in location) {
+ location = {
+ ...location,
+ path: parseURL(parseQuery, location.path, currentLocation.path).path,
+ }
+ }
+
let matchedRoute: MatcherLocation = // relative or named location, path is ignored
// for same reason TS thinks location.params can be undefined
matcher.resolve(
return runGuardQueue(guards)
})
.then(() => {
- // check global guards beforeEach
+ // check global guards beforeResolve
guards = []
for (const guard of beforeResolveGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))