/** @type {import('../src/types').RouteRecord[]} */
const routes = [
- { path: '/%25', name: 'home', component: components.Home },
+ { path: '/', name: 'home', component: components.Home },
+ { path: '/%25', name: 'percent', component: components.Home },
{ path: '/to-p/:p', redirect: to => `/p/${to.params.p}` },
{ path: '/p/:p', component: components.Bar },
]
await router.doInitialNavigation()
expect(router.currentRoute).toEqual(
expect.objectContaining({
- name: 'home',
+ name: 'percent',
fullPath: '/%25',
path: '/%25',
})
})
)
})
+
+ it('decodes params in query', async () => {
+ const history = createHistory('/?q=%25%E2%82%AC')
+ const router = new Router({ history, routes })
+ await router.doInitialNavigation()
+ expect(router.currentRoute).toEqual(
+ expect.objectContaining({
+ name: 'home',
+ fullPath: '/?q=' + encodeURIComponent('%€'),
+ query: {
+ q: '%€',
+ },
+ path: '/',
+ })
+ )
+ })
+
+ it('allow unencoded params in query (IE Edge)', async () => {
+ const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
+ const history = createHistory('/?q=€%notvalid')
+ const router = new Router({ history, routes })
+ await router.doInitialNavigation()
+ expect(spy).toHaveBeenCalledTimes(1)
+ spy.mockRestore()
+ expect(router.currentRoute).toEqual(
+ expect.objectContaining({
+ name: 'home',
+ fullPath: '/?q=' + encodeURIComponent('€%notvalid'),
+ query: {
+ q: '€%notvalid',
+ },
+ path: '/',
+ })
+ )
+ })
})
})
import { ListenerRemover } from '../types'
export type HistoryQuery = Record<string, string | string[]>
+// TODO: is it reall worth allowing null to form queries like ?q&b&c
+// When parsing using URLSearchParams, `q&c=` yield an empty string for q and c
export type RawHistoryQuery = Record<string, string | string[] | null>
export interface HistoryLocation {
hashPos > -1 ? hashPos : location.length
)
- query = parseQuery(searchString)
+ query = normalizeQuery(parseQuery(searchString))
}
if (hashPos > -1) {
if (search === '' || search === '?') return query
const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&')
for (let i = 0; i < searchParams.length; ++i) {
- const [key, value] = searchParams[i].split('=')
+ let [key, value] = searchParams[i].split('=')
+ try {
+ value = decodeURIComponent(value)
+ } catch (err) {
+ // TODO: handling only URIError?
+ console.warn(
+ `[vue-router] error decoding "${value}" while parsing query. Sticking to the original value.`
+ )
+ }
if (key in query) {
// an extra variable for ts types
let currentValue = query[key]
// TODO: util function?
for (const key in query) {
if (search.length > 1) search += '&'
- // TODO: handle array
const value = query[key]
- if (Array.isArray(value)) {
- search += `${key}=${value[0]}`
- for (let i = 1; i < value.length; i++) {
- search += `&${key}=${value[i]}`
- }
- } else {
- search += `${key}=${query[key]}`
+ if (value === null) {
+ // TODO: should we just add the empty string value?
+ search += key
+ continue
+ }
+
+ let values: string[] = Array.isArray(value) ? value : [value]
+ try {
+ values = values.map(encodeURIComponent)
+ } catch (err) {
+ // TODO: handling only URIError?
+
+ console.warn(
+ `[vue-router] invalid query parameter while stringifying query: "${key}": "${values}"`
+ )
+ }
+ search += `${key}=${values[0]}`
+ for (let i = 1; i < values.length; i++) {
+ search += `&${key}=${values[i]}`
}
}
export function normalizeQuery(query: RawHistoryQuery): HistoryQuery {
// TODO: implem
- return query as HistoryQuery
+ const normalizedQuery: HistoryQuery = {}
+ for (const key in query) {
+ const value = query[key]
+ if (value === null) normalizedQuery[key] = ''
+ else normalizedQuery[key] = value
+ }
+ return normalizedQuery
}
/**