})
})
- it('parses ? after the hash', () => {
+ it('avoids ? after the hash', () => {
expect(parseURL('/foo#?a=one')).toEqual({
fullPath: '/foo#?a=one',
path: '/foo',
})
})
+ it('works with empty query', () => {
+ expect(parseURL('/foo?#hash')).toEqual({
+ fullPath: '/foo?#hash',
+ path: '/foo',
+ hash: '#hash',
+ query: {},
+ })
+ expect(parseURL('/foo?')).toEqual({
+ fullPath: '/foo?',
+ path: '/foo',
+ hash: '',
+ query: {},
+ })
+ })
+
+ it('works with empty hash', () => {
+ expect(parseURL('/foo#')).toEqual({
+ fullPath: '/foo#',
+ path: '/foo',
+ hash: '#',
+ query: {},
+ })
+ expect(parseURL('/foo?#')).toEqual({
+ fullPath: '/foo?#',
+ path: '/foo',
+ hash: '#',
+ query: {},
+ })
+ })
+
+ it('works with a relative paths', () => {
+ expect(parseURL('foo', '/parent/bar')).toEqual({
+ fullPath: '/parent/foo',
+ path: '/parent/foo',
+ hash: '',
+ query: {},
+ })
+ expect(parseURL('./foo', '/parent/bar')).toEqual({
+ fullPath: '/parent/foo',
+ path: '/parent/foo',
+ hash: '',
+ query: {},
+ })
+ expect(parseURL('../foo', '/parent/bar')).toEqual({
+ fullPath: '/foo',
+ path: '/foo',
+ hash: '',
+ query: {},
+ })
+
+ expect(parseURL('#foo', '/parent/bar')).toEqual({
+ fullPath: '/parent/bar#foo',
+ path: '/parent/bar',
+ hash: '#foo',
+ query: {},
+ })
+ expect(parseURL('?o=o', '/parent/bar')).toEqual({
+ fullPath: '/parent/bar?o=o',
+ path: '/parent/bar',
+ hash: '',
+ query: { o: 'o' },
+ })
+ })
+
it('calls parseQuery', () => {
const parseQuery = vi.fn()
originalParseURL(parseQuery, '/?é=é&é=a')
expect(parseQuery).toHaveBeenCalledTimes(1)
- expect(parseQuery).toHaveBeenCalledWith('é=é&é=a')
+ expect(parseQuery).toHaveBeenCalledWith('?é=é&é=a')
})
})
import { vi, describe, expect, it, beforeAll } from 'vitest'
import { mockWarn } from './vitest-mock-warn'
-declare var __DEV__: boolean
-
const routes: RouteRecordRaw[] = [
{ path: '/', component: components.Home, name: 'home' },
{ path: '/home', redirect: '/' },
const parseQuery = vi.fn(_ => ({}))
const { router } = await newRouter({ parseQuery })
const to = router.resolve('/foo?bar=baz')
- expect(parseQuery).toHaveBeenCalledWith('bar=baz')
+ expect(parseQuery).toHaveBeenCalledWith('?bar=baz')
expect(to.query).toEqual({})
})
searchString = '',
hash = ''
- // Could use URL and URLSearchParams but IE 11 doesn't support it
- // TODO: move to new URL()
+ // NOTE: we could use URL and URLSearchParams but they are 2 to 5 times slower than this method
const hashPos = location.indexOf('#')
- let searchPos = location.indexOf('?')
- // the hash appears before the search, so it's not part of the search string
- if (hashPos < searchPos && hashPos >= 0) {
- searchPos = -1
- }
+ // let searchPos = location.indexOf('?')
+ let searchPos =
+ hashPos >= 0
+ ? // find the query string before the hash to avoid including a ? in the hash
+ // e.g. /foo#hash?query -> has no query
+ location.lastIndexOf('?', hashPos)
+ : location.indexOf('?')
- if (searchPos > -1) {
+ if (searchPos >= 0) {
path = location.slice(0, searchPos)
- searchString = location.slice(
- searchPos + 1,
- hashPos > -1 ? hashPos : location.length
- )
+ searchString =
+ '?' +
+ location.slice(searchPos + 1, hashPos > 0 ? hashPos : location.length)
query = parseQuery(searchString)
}
- if (hashPos > -1) {
+ if (hashPos >= 0) {
+ // TODO(major): path ||=
path = path || location.slice(0, hashPos)
// keep the # character
hash = location.slice(hashPos, location.length)
}
- // no search and no query
- path = resolveRelativePath(path != null ? path : location, currentLocation)
- // empty path means a relative query or hash `?foo=f`, `#thing`
+ // TODO(major): path ?? location
+ path = resolveRelativePath(
+ path != null
+ ? path
+ : // empty path means a relative query or hash `?foo=f`, `#thing`
+ location,
+ currentLocation
+ )
return {
- fullPath: path + (searchString && '?') + searchString + hash,
+ fullPath: path + searchString + hash,
path,
query,
hash: decode(hash),
return to
}
+ // resolve '' with '/anything' -> '/anything'
if (!to) return from
const fromSegments = from.split('/')
const toSegments = to.split('/')
- const lastToSegment = toSegments[toSegments.length - 1]
+ const lastToSegment: string | undefined = toSegments[toSegments.length - 1]
// make . and ./ the same (../ === .., ../../ === ../..)
// this is the same behavior as new URL()
// avoid creating an object with an empty key and empty value
// because of split('&')
if (search === '' || search === '?') return query
- const hasLeadingIM = search[0] === '?'
- const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&')
+ const searchParams = (search[0] === '?' ? search.slice(1) : search).split('&')
for (let i = 0; i < searchParams.length; ++i) {
// pre decode the + into space
const searchParam = searchParams[i].replace(PLUS_RE, ' ')