--- /dev/null
+declare global {
+ namespace jest {
+ interface Matchers<R, T> {
+ toHaveBeenWarned(): R
+ toHaveBeenWarnedLast(): R
+ toHaveBeenWarnedTimes(n: number): R
+ }
+ }
+}
+
+export function mockWarn() {
+ expect.extend({
+ toHaveBeenWarned(received: string) {
+ asserted.add(received)
+ const passed = warn.mock.calls.some(
+ args => args[0].indexOf(received) > -1
+ )
+ if (passed) {
+ return {
+ pass: true,
+ message: () => `expected "${received}" not to have been warned.`,
+ }
+ } else {
+ const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
+ return {
+ pass: false,
+ message: () =>
+ `expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`,
+ }
+ }
+ },
+
+ toHaveBeenWarnedLast(received: string) {
+ asserted.add(received)
+ const passed =
+ warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
+ if (passed) {
+ return {
+ pass: true,
+ message: () => `expected "${received}" not to have been warned last.`,
+ }
+ } else {
+ const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
+ return {
+ pass: false,
+ message: () =>
+ `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`,
+ }
+ }
+ },
+
+ toHaveBeenWarnedTimes(received: string, n: number) {
+ asserted.add(received)
+ let found = 0
+ warn.mock.calls.forEach(args => {
+ if (args[0].indexOf(received) > -1) {
+ found++
+ }
+ })
+
+ if (found === n) {
+ return {
+ pass: true,
+ message: () =>
+ `expected "${received}" to have been warned ${n} times.`,
+ }
+ } else {
+ return {
+ pass: false,
+ message: () =>
+ `expected "${received}" to have been warned ${n} times but got ${found}.`,
+ }
+ }
+ },
+ })
+
+ let warn: jest.SpyInstance
+ const asserted: Set<string> = new Set()
+
+ beforeEach(() => {
+ asserted.clear()
+ warn = jest.spyOn(console, 'warn')
+ warn.mockImplementation(() => {})
+ })
+
+ afterEach(() => {
+ const assertedArray = Array.from(asserted)
+ const nonAssertedWarnings = warn.mock.calls
+ .map(args => args[0])
+ .filter(received => {
+ return !assertedArray.some(assertedMsg => {
+ return received.indexOf(assertedMsg) > -1
+ })
+ })
+ warn.mockRestore()
+ if (nonAssertedWarnings.length) {
+ nonAssertedWarnings.forEach(warning => {
+ console.warn(warning)
+ })
+ throw new Error(`test case threw unexpected warnings.`)
+ }
+ })
+}
--- /dev/null
+import { parseQuery } from '../src/history/common'
+import { mockWarn } from './mockWarn'
+
+describe('parseQuery', () => {
+ mockWarn()
+ it('decodes values in query', () => {
+ expect(parseQuery('e=%25')).toEqual({
+ e: '%',
+ })
+ })
+
+ it('decodes array values in query', () => {
+ expect(parseQuery('e=%25&e=%22')).toEqual({
+ e: ['%', '"'],
+ })
+ expect(parseQuery('e=%25&e=a')).toEqual({
+ e: ['%', 'a'],
+ })
+ })
+
+ // this is for browsers like IE that allow invalid characters
+ it('keep invalid values as is', () => {
+ expect(parseQuery('e=%&e=%25')).toEqual({
+ e: ['%', '%'],
+ })
+
+ expect('decoding "%"').toHaveBeenWarnedTimes(1)
+ })
+})
--- /dev/null
+import { stringifyQuery } from '../src/history/common'
+import { mockWarn } from './mockWarn'
+
+describe('stringifyQuery', () => {
+ mockWarn()
+
+ it('stringifies multiple values', () => {
+ expect(stringifyQuery({ e: 'a', b: 'c' })).toEqual('e=a&b=c')
+ })
+
+ it('stringifies arrays', () => {
+ expect(stringifyQuery({ e: ['b', 'a'] })).toEqual('e=b&e=a')
+ })
+
+ it('encodes values', () => {
+ expect(stringifyQuery({ e: '%', b: 'c' })).toEqual('e=%25&b=c')
+ })
+
+ it('encodes values in arrays', () => {
+ expect(stringifyQuery({ e: ['%', 'a'], b: 'c' })).toEqual('e=%25&e=a&b=c')
+ })
+})
query: { a: ['one', 'two', 'three'] },
})
})
+
+ it('calls parseQuery', () => {
+ const parseQuery = jest.fn()
+ originalParseURL(parseQuery, '/?é=é&é=a')
+ expect(parseQuery).toHaveBeenCalledTimes(1)
+ expect(parseQuery).toHaveBeenCalledWith('é=é&é=a')
+ })
})
describe('stringifyURL', () => {
})
).toBe('/path?foo=a&bar=b#hey')
})
+
+ it('calls stringifyQuery', () => {
+ const stringifyQuery = jest.fn()
+ originalStringifyURL(stringifyQuery, {
+ path: '/',
+ query: { é: 'é', b: 'a' },
+ })
+ expect(stringifyQuery).toHaveBeenCalledTimes(1)
+ expect(stringifyQuery).toHaveBeenCalledWith({ é: 'é', b: 'a' })
+ })
})
describe('normalizeLocation', () => {
// const objectLocation = routerLocationAsObject(location)
if (typeof location === 'string') {
// TODO: remove as cast when redirect is removed from matcher
- // TODO: ensure parseURL encodes the query in fullPath but not in query object
let locationNormalized = parseURL(parseQuery, location)
let matchedRoute = matcher.resolve(
{ path: locationNormalized.path },