const onError = jest.fn()
function createRouter() {
+ const history = new AbstractHistory()
const router = new Router({
- history: new AbstractHistory(),
+ history,
routes,
})
router.onError(onError)
- return router
+ return { router, history }
}
describe('Errors', () => {
})
it('triggers onError when navigation is aborted', async () => {
- const router = createRouter()
+ const { router } = createRouter()
router.beforeEach((to, from, next) => {
next(false)
})
})
it('triggers erros caused by new navigations of a next(redirect) trigered by history', async () => {
- const router = createRouter()
+ const { router, history } = createRouter()
await router.push('/p/0')
await router.push('/p/other')
else next({ name: 'Param', params: { p: '' + p } })
})
- // @ts-ignore
- router.history.back()
+ history.back()
await tick()
expect(onError).toHaveBeenCalledTimes(2)
const history = new AbstractHistory()
const spy = jest.fn()
history.listen(spy)
- // @ts-ignore
+ // @ts-ignore we need to check internals here
expect(history.listeners).toHaveLength(1)
history.destroy()
- // @ts-ignore
+ // @ts-ignore we need to check internals here
expect(history.listeners).toHaveLength(0)
})
const component = null
/** @typedef {import('../src/types').RouteRecord} RouteRecord */
+/** @typedef {import('../src/types').RouteComponent} RouteComponent */
/** @typedef {import('../src/types').MatchedRouteRecord} MatchedRouteRecord */
/** @typedef {import('../src/types').MatcherLocation} MatcherLocation */
/** @typedef {import('../src/types').MatcherLocationRedirect} MatcherLocationRedirect */
* @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 & { component: any }>} resolved Expected resolved location given by the matcher
- * @param {MatcherLocationNormalized} [start] Optional currentLocation used when resolving
+ * @param {MatcherLocationNormalized | (MatcherLocationNormalized & { matched: Array<MatchedRouteRecord | Exclude<RouteRecord, { redirect: any}>> })} [start] Optional currentLocation used when resolving
*/
function assertRecordMatch(
record,
if ('redirect' in record) {
} else {
// use one single record
- // @ts-ignore
- if (!('matched' in resolved)) resolved.matched = record
+ if (!('matched' in resolved))
+ resolved.matched = record.map(normalizeRouteRecord)
}
// allows not passing params
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
- }
+ if (!('matched' in resolved)) resolved.matched = []
+
+ const startCopy = {
+ ...start,
+ matched: start.matched.map(normalizeRouteRecord),
}
+ // make matched non enumerable
+ Object.defineProperty(startCopy, 'matched', { enumerable: false })
+
const result = matcher.resolve(
{
...targetLocation,
// override anything provided in location
...location,
},
- start
+ startCopy
)
expect(result).toEqual(resolved)
}
* @param {RouteLocationNormalized} $route
*/
function factory($route) {
- // @ts-ignore
+ // @ts-ignore cannot mount functional component?
const wrapper = mount(RouterView, {
mocks: { $route },
})
export const NAVIGATION_TYPES = ['push', 'replace']
+declare global {
+ namespace NodeJS {
+ interface Global {
+ window: JSDOM['window']
+ location: JSDOM['window']['location']
+ document: JSDOM['window']['document']
+ before?: Function
+ }
+ }
+}
+
export function createDom(options?: ConstructorOptions) {
const dom = new JSDOM(
`<!DOCTYPE html><html><head></head><body></body></html>`,
}
)
- // @ts-ignore
global.window = dom.window
- // @ts-ignore
global.location = dom.window.location
- // @ts-ignore
global.document = dom.window.document
return dom
// allow using a .jest modifider to skip some tests on mocha
// specifically, skip component tests as they are a pain to correctly
// adapt to mocha
-// @ts-ignore
export const isMocha = () => typeof global.before === 'function'
/**
declare global {
interface Window {
vm: Vue
+ h: HTML5History
+ r: Router
}
}
},
}
+const html5History = new HTML5History()
const router = new Router({
- history: new HTML5History(),
+ history: html5History,
routes: [
{ path: '/', component: Home },
{ path: '/users/:id', name: 'user', component: User },
],
})
+// for testing purposes
const r = router
+const h = html5History
+window.h = h
+window.r = r
const delay = (t: number) => new Promise(resolve => setTimeout(resolve, t))
next()
})
-// const h = new HTML5History()
-// @ts-ignore
-const h = r.history
-// @ts-ignore
-window.h = h
-// @ts-ignore
-window.r = r
-
h.listen((to, from, { direction }) => {
console.log(`popstate(${direction})`, { to, from })
})
import { Component } from 'vue'
import { Router } from '../router'
import { RouteLocationNormalized, RouteLocation } from '../types'
+import { HistoryLocationNormalized } from '../history/base'
const Link: Component = {
name: 'RouterLink',
},
render(h) {
- // @ts-ignore
+ // @ts-ignore can't get `this`
const router = this.$router as Router
- // @ts-ignore
+ // @ts-ignore can't get `this`
const from = this.$route as RouteLocationNormalized
- // @ts-ignore
+ // @ts-ignore can't get `this`
const to = this.to as RouteLocation
- // @ts-ignore
+ // @ts-ignore can't get `this`
+ const history = router.history
let url: HistoryLocationNormalized
let location: RouteLocationNormalized
// TODO: refactor router code and use its function istead of having a copied version here
if (typeof to === 'string' || 'path' in to) {
- // @ts-ignore
- url = router.history.utils.normalizeLocation(to)
+ url = history.utils.normalizeLocation(to)
// TODO: should allow a non matching url to allow dynamic routing to work
location = router.resolveLocation(url, from)
} else {
// named or relative route
- // @ts-ignore
- const query = router.history.utils.normalizeQuery(
- to.query ? to.query : {}
- )
+ const query = history.utils.normalizeQuery(to.query ? to.query : {})
const hash = to.hash || ''
// we need to resolve first
location = router.resolveLocation({ ...to, query, hash }, from)
// intentionally drop current query and hash
- // @ts-ignore
- url = router.history.utils.normalizeLocation({
+ url = history.utils.normalizeLocation({
query,
hash,
...location,
// don't redirect on right click
if (e.button !== undefined && e.button !== 0) return
// don't redirect if `target="_blank"`
- // @ts-ignore
+ // @ts-ignore getAttribute does exist
if (e.currentTarget && e.currentTarget.getAttribute) {
- // @ts-ignore
+ // @ts-ignore getAttribute exists
const target = e.currentTarget.getAttribute('target')
if (/\b_blank\b/i.test(target)) return
}
},
render(_, { children, parent, data, props }) {
- // used by devtools to display a router-view badge
- // @ts-ignore
+ // @ts-ignore used by devtools to display a router-view badge
data.routerView = true
// directly use parent context's createElement() function
// so that components rendered by router-view can resolve named slots
const h = parent.$createElement
- // @ts-ignore
+ // @ts-ignore $route is added by our typings
const route = parent.$route
// TODO: support nested router-views
// TODO: implement the mock instead
/* istanbul ignore next */
-// @ts-ignore
if (process.env.NODE_ENV === 'test') cs.mockTypes(() => jest.fn())
type PopStateListener = (this: Window, ev: PopStateEvent) => any
const plugin: PluginFunction<void> = Vue => {
Vue.mixin({
beforeCreate() {
- // @ts-ignore
- if (this.$options.router) {
- // @ts-ignore
+ if ('router' in this.$options) {
+ // @ts-ignore we are adding this
this._routerRoot = this
- // @ts-ignore
- this._router = this.$options.router as Router
+ // @ts-ignore should be able to be removed once we add the typing
+ const router = this.$options.router as Router
+ // @ts-ignore _router is internal
+ this._router = router
// this._router.init(this)
// @ts-ignore
this._router.app = this
- // @ts-ignore
+ // @ts-ignore we can use but should not be used by others
Vue.util.defineReactive(
- // @ts-ignore
this,
'_route',
- // @ts-ignore
- this._router.currentRoute
+ router.currentRoute
// undefined,
// true
)
- // @ts-ignore
- this._router.doInitialNavigation()
+ router.doInitialNavigation()
} else {
- // @ts-ignore
+ // @ts-ignore we are adding this
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
},
})
- // @ts-ignore
+ // @ts-ignore FIXME: should work
Vue.component('RouterView', View)
- // @ts-ignore
+ // @ts-ignore FIXME: should work
Vue.component('RouterLink', Link)
// Vue.component('RouterLink', Link)
): NormalizedRouteRecord {
if ('component' in record) {
const { component, ...rest } = record
- // @ts-ignore
+ // @ts-ignore I could do it type safe by copying again rest:
+ // return {
+ // ...rest,
+ // components: { default: component }
+ // }
+ // but it's slower
rest.components = { default: component }
return rest as NormalizedRouteRecord
}