From: Eduardo San Martin Morote Date: Thu, 9 Apr 2020 18:50:12 +0000 (+0200) Subject: test: refactor mount function X-Git-Tag: v4.0.0-alpha.6~44 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db85d3fd88ef447454a36a86a50df695010b01f5;p=thirdparty%2Fvuejs%2Frouter.git test: refactor mount function --- diff --git a/__tests__/RouterLink.spec.ts b/__tests__/RouterLink.spec.ts index e92b78b2..ec5325d8 100644 --- a/__tests__/RouterLink.spec.ts +++ b/__tests__/RouterLink.spec.ts @@ -10,9 +10,10 @@ import { RouteLocation, } from '../src/types' import { createMemoryHistory } from '../src' -import { mount, tick } from './mount' -import { ref, markNonReactive } from 'vue' +import { mount } from './mount2' +import { ref, markNonReactive, nextTick } from 'vue' import { RouteRecordNormalized } from '../src/matcher/types' +import { routerKey } from '../src/utils/injectionSymbols' const records = { home: {} as RouteRecordNormalized, @@ -243,296 +244,352 @@ const locations: Record< }, } -describe('RouterLink', () => { - function factory( - currentLocation: RouteLocationNormalized, - propsData: any, - resolvedLocation: RouteLocation, - template: string = `a link` - ) { - const router = { - history: createMemoryHistory(), - createHref(to: RouteLocationNormalized): string { - return this.history.base + to.fullPath - }, - resolve: jest.fn(), - push: jest.fn().mockResolvedValue(resolvedLocation), - currentRoute: ref(markNonReactive(currentLocation)), - setActiveApp: jest.fn(), - } - - router.resolve.mockReturnValueOnce(resolvedLocation) - const { app, el } = mount(router as any, { - template, - components: { RouterLink } as any, - setup() { - return { propsData } - }, - }) - - return { app, router, el } +async function factory( + currentLocation: RouteLocationNormalized, + propsData: any, + resolvedLocation: RouteLocation, + slotTemplate: string = '' +) { + // const route = createMockedRoute(initialRoute) + const router = { + history: createMemoryHistory(), + createHref(to: RouteLocationNormalized): string { + return this.history.base + to.fullPath + }, + resolve: jest.fn(), + push: jest.fn().mockResolvedValue(resolvedLocation), + currentRoute: ref(markNonReactive(currentLocation)), } + router.resolve.mockReturnValueOnce(resolvedLocation) + + const wrapper = await mount(RouterLink, { + propsData, + provide: { + [routerKey as any]: router, + }, + slots: { default: slotTemplate }, + }) + + return { router, wrapper } +} + +describe('RouterLink', () => { + it('displays a link with a string prop', async () => { + const { wrapper } = await factory( + START_LOCATION_NORMALIZED, + { to: locations.basic.string }, + locations.basic.normalized + ) + expect(wrapper.rootEl.querySelector('a')!.getAttribute('href')).toBe( + '/home' + ) + }) - it('displays a link with a string prop', () => { - const { el } = factory( + it('displays a link with a string prop', async () => { + const { wrapper } = await factory( START_LOCATION_NORMALIZED, { to: locations.basic.string }, locations.basic.normalized ) - expect(el.querySelector('a')!.getAttribute('href')).toBe('/home') + expect(wrapper.rootEl.querySelector('a')!.getAttribute('href')).toBe( + '/home' + ) }) it('can change the value', async () => { const to = ref(locations.basic.string) - const { el, router } = factory( + const { wrapper, router } = await factory( START_LOCATION_NORMALIZED, { to }, locations.basic.normalized ) - expect(el.querySelector('a')!.getAttribute('href')).toBe('/home') + expect(wrapper.rootEl.querySelector('a')!.getAttribute('href')).toBe( + '/home' + ) router.resolve.mockReturnValueOnce(locations.foo.normalized) to.value = locations.foo.string - await tick() - expect(el.querySelector('a')!.getAttribute('href')).toBe('/foo') + await nextTick() + expect(wrapper.rootEl.querySelector('a')!.getAttribute('href')).toBe('/foo') }) - it('displays a link with an object with path prop', () => { - const { el } = factory( + it('displays a link with an object with path prop', async () => { + const { wrapper } = await factory( START_LOCATION_NORMALIZED, { to: { path: locations.basic.string } }, locations.basic.normalized ) - expect(el.querySelector('a')!.getAttribute('href')).toBe('/home') + expect(wrapper.rootEl.querySelector('a')!.getAttribute('href')).toBe( + '/home' + ) }) - it('can be active', () => { - const { el } = factory( + it('can be active', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string }, locations.basic.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) }) - it('can customize active class', () => { - const { el } = factory( + it('can customize active class', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string, activeClass: 'is-active' }, locations.basic.normalized ) - expect(el.querySelector('a')!.className).not.toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain('is-active') + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain('is-active') }) - it('can customize exact active class', () => { - const { el } = factory( + it('can customize exact active class', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string, exactActiveClass: 'is-active' }, locations.basic.normalized ) - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) - expect(el.querySelector('a')!.className).toContain('is-active') + expect(wrapper.rootEl.querySelector('a')!.className).toContain('is-active') }) - it('can be active with custom class', () => { - const { el } = factory( + it('can be active with custom class', async () => { + const { wrapper } = await factory( locations.basic.normalized, - { to: locations.basic.string }, - locations.basic.normalized, - `a link` + { to: locations.basic.string, class: 'nav-item' }, + locations.basic.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain('nav-item') + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain('nav-item') }) - it('is not active on a non matched location', () => { - const { el } = factory( + it('is not active on a non matched location', async () => { + const { wrapper } = await factory( locations.notFound.normalized, { to: locations.basic.string }, locations.basic.normalized ) - expect(el.querySelector('a')!.className).toBe('') + expect(wrapper.rootEl.querySelector('a')!.className).toBe('') }) - it('is not active with more repeated params', () => { - const { el } = factory( + it('is not active with more repeated params', async () => { + const { wrapper } = await factory( locations.repeatedParams2.normalized, { to: locations.repeatedParams3.string }, locations.repeatedParams3.normalized ) - expect(el.querySelector('a')!.className).toBe('') + expect(wrapper.rootEl.querySelector('a')!.className).toBe('') }) - it('is not active with partial repeated params', () => { - const { el } = factory( + it('is not active with partial repeated params', async () => { + const { wrapper } = await factory( locations.repeatedParams3.normalized, { to: locations.repeatedParams2.string }, locations.repeatedParams2.normalized ) - expect(el.querySelector('a')!.className).toBe('') + expect(wrapper.rootEl.querySelector('a')!.className).toBe('') }) - it('can be active as an alias', () => { - let { el } = factory( + it('can be active as an alias', async () => { + let { wrapper } = await factory( locations.basic.normalized, { to: locations.alias.string }, locations.alias.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) - el = factory( - locations.alias.normalized, - { to: locations.basic.string }, - locations.basic.normalized - ).el - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + wrapper = ( + await factory( + locations.alias.normalized, + { to: locations.basic.string }, + locations.basic.normalized + ) + ).wrapper + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) }) - it('is active when a child is active', () => { - const { el } = factory( + it('is active when a child is active', async () => { + const { wrapper } = await factory( locations.child.normalized, { to: locations.parent.string }, locations.parent.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) }) - it('only the children is exact-active', () => { - const { el } = factory( + it('only the children is exact-active', async () => { + const { wrapper } = await factory( locations.child.normalized, { to: locations.child.string }, locations.child.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) }) - it('child is not active if the parent is active', () => { - const { el } = factory( + it('child is not active if the parent is active', async () => { + const { wrapper } = await factory( locations.parent.normalized, { to: locations.child.string }, locations.child.normalized ) - expect(el.querySelector('a')!.className).not.toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) }) - it('parent is active if the child is an absolute path', () => { - const { el } = factory( + it('parent is active if the child is an absolute path', async () => { + const { wrapper } = await factory( locations.childAsAbsolute.normalized, { to: locations.parent.string }, locations.parent.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) }) - it('alias parent is active if the child is an absolute path', () => { - const { el } = factory( + it('alias parent is active if the child is an absolute path', async () => { + const { wrapper } = await factory( locations.childAsAbsolute.normalized, { to: locations.parentAlias.string }, locations.parentAlias.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) }) - it('alias parent is active when a child is active', () => { - let { el } = factory( + it('alias parent is active when a child is active', async () => { + let { wrapper } = await factory( locations.child.normalized, { to: locations.parentAlias.string }, locations.parentAlias.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) - el = factory( - locations.childDoubleAlias.normalized, - { to: locations.parentAlias.string }, - locations.parentAlias.normalized - ).el - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).not.toContain( + wrapper = ( + await factory( + locations.childDoubleAlias.normalized, + { to: locations.parentAlias.string }, + locations.parentAlias.normalized + ) + ).wrapper + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).not.toContain( 'router-link-exact-active' ) }) - it('alias parent is active', () => { - let { el } = factory( + it('alias parent is active', async () => { + let { wrapper } = await factory( locations.parent.normalized, { to: locations.parentAlias.string }, locations.parentAlias.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) - el = factory( - locations.parentAlias.normalized, - { to: locations.parent.string }, - locations.parent.normalized - ).el - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + wrapper = ( + await factory( + locations.parentAlias.normalized, + { to: locations.parent.string }, + locations.parent.normalized + ) + ).wrapper + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) }) - it('child and parent with alias', () => { - let { el } = factory( + it('child and parent with alias', async () => { + let { wrapper } = await factory( locations.child.normalized, { to: locations.childDoubleAlias.string }, locations.childDoubleAlias.normalized ) - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) - el = factory( - locations.child.normalized, - { to: locations.childParentAlias.string }, - locations.childParentAlias.normalized - ).el - expect(el.querySelector('a')!.className).toContain('router-link-active') - expect(el.querySelector('a')!.className).toContain( + wrapper = ( + await factory( + locations.child.normalized, + { to: locations.childParentAlias.string }, + locations.childParentAlias.normalized + ) + ).wrapper + expect(wrapper.rootEl.querySelector('a')!.className).toContain( + 'router-link-active' + ) + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) }) - it('can be exact-active', () => { - const { el } = factory( + it('can be exact-active', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string }, locations.basic.normalized ) - expect(el.querySelector('a')!.className).toContain( + expect(wrapper.rootEl.querySelector('a')!.className).toContain( 'router-link-exact-active' ) }) - it('calls ensureLocation', () => { - const { router } = factory( + it('calls ensureLocation', async () => { + const { router } = await factory( START_LOCATION_NORMALIZED, { to: locations.basic.string }, locations.basic.normalized @@ -541,84 +598,60 @@ describe('RouterLink', () => { expect(router.resolve).toHaveBeenCalledWith(locations.basic.string) }) - // TODO: call when we can test this - it.skip('calls router.push when clicked', async () => { - const { router, el } = factory( + it('calls router.push when clicked', async () => { + const { router, wrapper } = await factory( START_LOCATION_NORMALIZED, { to: locations.basic.string }, locations.basic.normalized ) - el.click() - await tick() + wrapper.rootEl.querySelector('a')!.click() + await nextTick() expect(router.push).toHaveBeenCalledTimes(1) expect(router.push).toHaveBeenCalledWith(locations.basic.normalized) }) describe('v-slot', () => { - function factory( - currentLocation: RouteLocationNormalized, - propsData: any, - resolvedLocation: RouteLocationNormalized - ) { - const router = { - history: createMemoryHistory(), - createHref(to: RouteLocationNormalized): string { - return this.history.base + to.fullPath - }, - resolve: jest.fn(), - push: jest.fn().mockResolvedValue(resolvedLocation), - currentRoute: ref(markNonReactive(currentLocation)), - setActiveApp: jest.fn(), - } - - router.resolve.mockReturnValueOnce(resolvedLocation) - const { app, el } = mount(router as any, { - template: ` + const slotTemplate = ` - route: {{ JSON.stringify(data.route) }} - href: "{{ data.href }}" - isActive: "{{ data.isActive }}" - isExactActive: "{{ data.isExactActive }}" + route: {{ JSON.stringify(route) }} + href: "{{ href }}" + isActive: "{{ isActive }}" + isExactActive: "{{ isExactActive }}" - `, - components: { RouterLink } as any, - setup() { - return { propsData } - }, - }) - - return { app, router, el } - } - - it('provides information on v-slot', () => { - const { el } = factory( + ` + + it('provides information on v-slot', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string }, - locations.basic.normalized + locations.basic.normalized, + slotTemplate ) - expect(el.innerHTML).toMatchSnapshot() + expect(wrapper.html()).toMatchSnapshot() }) - it('renders an anchor by default', () => { - const { el } = factory( + it('renders an anchor by default', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string }, - locations.basic.normalized + locations.basic.normalized, + slotTemplate ) - expect(el.children[0].tagName).toBe('A') - expect(el.children).toHaveLength(1) + expect(wrapper.rootEl.children[0].tagName).toBe('A') + expect(wrapper.rootEl.children).toHaveLength(1) }) - it('can customize the rendering and remove the wrapping `a`', () => { - const { el } = factory( + it('can customize the rendering and remove the wrapping `a`', async () => { + const { wrapper } = await factory( locations.basic.normalized, { to: locations.basic.string, custom: true }, - locations.basic.normalized + locations.basic.normalized, + slotTemplate ) - expect(el.innerHTML).not.toContain('') + expect(wrapper.html()).not.toContain('') }) }) }) diff --git a/__tests__/mount2.ts b/__tests__/mount2.ts index 9d0d03d3..a087c87c 100644 --- a/__tests__/mount2.ts +++ b/__tests__/mount2.ts @@ -13,16 +13,20 @@ import { markNonReactive, App, ComponentOptionsWithProps, + VNode, } from 'vue' +import { compile } from '@vue/compiler-dom' +import * as runtimeDom from '@vue/runtime-dom' import { RouteLocationNormalizedLoose } from './utils' import { routeLocationKey } from '../src/utils/injectionSymbols' -interface MountOptions { +export interface MountOptions { propsData: Record provide: Record components: ComponentOptionsWithProps['components'] + slots: Record } -// { app, vm: instance!, el: rootEl, setProps, provide } + interface Wrapper { app: App vm: ComponentPublicInstance @@ -66,18 +70,24 @@ export function mount( return nextTick() } + let slots: Record VNode> = {} + const Wrapper = defineComponent({ setup(_props, { emit }) { const componentInstanceRef = ref() return () => { - return h(TargetComponent, { - ref: componentInstanceRef, - onVnodeMounted() { - emit('ready', componentInstanceRef.value) + return h( + TargetComponent, + { + ref: componentInstanceRef, + onVnodeMounted() { + emit('ready', componentInstanceRef.value) + }, + ...propsData, }, - ...propsData, - }) + slots + ) } }, }) @@ -102,6 +112,12 @@ export function mount( } } + if (options.slots) { + for (let key in options.slots) { + slots[key] = compileSlot(options.slots[key]) + } + } + // TODO: how to cleanup? const rootEl = document.createElement('div') document.body.appendChild(rootEl) @@ -151,3 +167,24 @@ export function createMockedRoute(initialValue: RouteLocationNormalizedLoose) { }, } } + +function compileSlot(template: string) { + const codegen = compile(template, { + mode: 'function', + hoistStatic: true, + prefixIdentifiers: true, + }) + + const render = new Function('Vue', codegen.code)(runtimeDom) + + const ToRender = defineComponent({ + render, + inheritAttrs: false, + + setup(props, { attrs }) { + return { ...attrs } + }, + }) + + return (propsData: any) => h(ToRender, { ...propsData }) +}