From 43cd9fe8301ac319907e82e207052b053ea7916f Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 24 Jan 2020 17:37:55 +0100 Subject: [PATCH] feat(link): add active class --- __tests__/RouterLink.spec.ts | 61 ++++++++++++++++++- .../__snapshots__/RouterLink.spec.ts.snap | 3 + src/components/Link.ts | 57 +++++++++++------ 3 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 __tests__/__snapshots__/RouterLink.spec.ts.snap diff --git a/__tests__/RouterLink.spec.ts b/__tests__/RouterLink.spec.ts index 5d544c6b..5200cbf5 100644 --- a/__tests__/RouterLink.spec.ts +++ b/__tests__/RouterLink.spec.ts @@ -87,7 +87,7 @@ describe('RouterLink', () => { { to: locations.basic.string }, locations.basic.normalized ) - expect(el.innerHTML).toBe('a link') + expect(el.innerHTML).toBe('a link') }) it('displays a link with an object with path prop', () => { @@ -96,7 +96,18 @@ describe('RouterLink', () => { { to: { path: locations.basic.string } }, locations.basic.normalized ) - expect(el.innerHTML).toBe('a link') + expect(el.innerHTML).toBe('a link') + }) + + it('can be active', () => { + const { el } = factory( + locations.basic.normalized, + { to: locations.basic.string }, + locations.basic.normalized + ) + expect(el.innerHTML).toBe( + 'a link' + ) }) it('calls ensureLocation', () => { @@ -121,4 +132,50 @@ describe('RouterLink', () => { 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: ` + route: {{ JSON.stringify(data.route) }} + href: "{{ data.href }}" + isActive: "{{ data.isActive }}" + `, + components: { RouterLink } as any, + setup() { + const to = ref(propsData.to) + + return { to } + }, + }) + + return { app, router, el } + } + + it('provides information on v-slot', () => { + const { el } = factory( + locations.basic.normalized, + { to: locations.basic.string }, + locations.basic.normalized + ) + + expect(el.innerHTML).toMatchSnapshot() + }) + }) }) diff --git a/__tests__/__snapshots__/RouterLink.spec.ts.snap b/__tests__/__snapshots__/RouterLink.spec.ts.snap new file mode 100644 index 00000000..61bd8ed9 --- /dev/null +++ b/__tests__/__snapshots__/RouterLink.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RouterLink v-slot provides information on v-slot 1`] = `" route: {\\"fullPath\\":\\"/home\\",\\"path\\":\\"/home\\",\\"params\\":{},\\"meta\\":{},\\"query\\":{},\\"hash\\":\\"\\",\\"matched\\":[]} href: \\"/home\\" isActive: \\"true\\" "`; diff --git a/src/components/Link.ts b/src/components/Link.ts index b7dbc114..a07cebce 100644 --- a/src/components/Link.ts +++ b/src/components/Link.ts @@ -1,8 +1,31 @@ import { defineComponent, h, PropType, inject } from '@vue/runtime-core' -import { computed } from '@vue/reactivity' -import { Router } from '../router' +import { computed, reactive, isRef, Ref } from '@vue/reactivity' import { RouteLocation } from '../types' +export function useLink(to: Ref | RouteLocation) { + const router = inject('router') + + const route = computed(() => router.resolve(isRef(to) ? to.value : to)) + const href = computed(() => router.createHref(route.value)) + const isActive = computed( + () => router.currentRoute.value.path.indexOf(route.value.path) === 0 + ) + + // TODO: handle replace prop + + function navigate(e: MouseEvent) { + // TODO: handle navigate with empty parameters for scoped slot and composition api + if (guardEvent(e)) router.push(route.value) + } + + return { + route, + href, + isActive, + navigate, + } +} + const Link = defineComponent({ name: 'RouterLink', props: { @@ -12,30 +35,28 @@ const Link = defineComponent({ }, }, - setup(props, context) { - const router = inject('router')! + setup(props, { slots, attrs }) { + const { route, isActive, href, navigate } = useLink(props.to) - const route = computed(() => router.resolve(props.to)) + const elClass = computed(() => ({ + 'router-link-active': isActive.value, + })) - // TODO: active classes + // TODO: exact active classes // TODO: handle replace prop - const onClick = (e: MouseEvent) => { - // TODO: handle navigate with empty parameters for scoped slot and composition api - if (guardEvent(e)) { - router.push(route.value) - } - } - - return () => - h( + return () => { + return h( 'a', { - onClick, - href: router.createHref(route.value), + class: elClass.value, + onClick: navigate, + href: href.value, + ...attrs, }, - context.slots.default() + slots.default(reactive({ route, href, isActive })) ) + } }, }) -- 2.47.3