diff --git a/src/services/createRoute.spec.ts b/src/services/createRoute.spec.ts index 915f7f3c..59b034d1 100644 --- a/src/services/createRoute.spec.ts +++ b/src/services/createRoute.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, vi } from 'vitest' +import { describe, expect, test, vi } from 'vitest' import { createRoute } from '@/services/createRoute' import { component } from '@/utilities/testHelpers' import { createRouter } from '@/services/createRouter' @@ -6,298 +6,348 @@ import { DuplicateParamsError } from '@/errors/duplicateParamsError' import { withParams } from '@/services/withParams' import { createRejection } from './createRejection' -test('given parent, path is combined', () => { - const parent = createRoute({ - path: '/parent', - }) +describe('combine', () => { + test('given parent, path is combined', () => { + const parent = createRoute({ + path: '/parent', + }) + + const child = createRoute({ + parent: parent, + path: withParams('/child/[id]', { id: Number }), + }) - const child = createRoute({ - parent: parent, - path: withParams('/child/[id]', { id: Number }), + expect(child.stringify({ id: 123 })).toBe('/parent/child/123') }) - expect(child.stringify({ id: 123 })).toBe('/parent/child/123') -}) + test('given undefined path, path is combined', () => { + const parent = createRoute({ + path: '/parent', + }) -test('given undefined path, path is combined', () => { - const parent = createRoute({ - path: '/parent', - }) + const child = createRoute({ + parent: parent, + }) - const child = createRoute({ - parent: parent, - }) + const grandChild = createRoute({ + parent: child, + path: '/grand-child', + }) + + const kinless = createRoute({}) - const grandChild = createRoute({ - parent: child, - path: '/grand-child', + expect(kinless.stringify()).toBe('/') + expect(child.stringify()).toBe('/parent') + expect(grandChild.stringify()).toBe('/parent/grand-child') }) - const kinless = createRoute({}) + test('given parent, query is combined', () => { + const parent = createRoute({ + query: 'static=123', + }) - expect(kinless.stringify()).toBe('/') - expect(child.stringify()).toBe('/parent') - expect(grandChild.stringify()).toBe('/parent/grand-child') -}) + const child = createRoute({ + parent: parent, + query: withParams('sort=[sort]', { sort: Boolean }), + }) -test('given parent, query is combined', () => { - const parent = createRoute({ - query: 'static=123', + expect(child.stringify({ sort: true })).toBe('/?static=123&sort=true') }) - const child = createRoute({ - parent: parent, - query: withParams('sort=[sort]', { sort: Boolean }), - }) + test('given parent, state is combined into state', () => { + const parent = createRoute({ + state: { + foo: Number, + }, + }) - expect(child.stringify({ sort: true })).toBe('/?static=123&sort=true') -}) + const child = createRoute({ + parent: parent, + state: { + bar: String, + }, + }) -test('given parent, state is combined into state', () => { - const parent = createRoute({ - state: { + expect(child.state).toMatchObject({ foo: Number, - }, - }) - - const child = createRoute({ - parent: parent, - state: { bar: String, - }, + }) }) - expect(child.state).toMatchObject({ - foo: Number, - bar: String, - }) -}) + test('given parent and child without state, state matches parent', () => { + const parent = createRoute({ + state: { + foo: Number, + }, + }) + + const child = createRoute({ + parent: parent, + }) -test('given parent and child without state, state matches parent', () => { - const parent = createRoute({ - state: { + expect(child.state).toMatchObject({ foo: Number, - }, + }) }) - const child = createRoute({ - parent: parent, - }) + test('given parent, meta is combined', () => { + const parent = createRoute({ + meta: { + foo: 123, + }, + }) - expect(child.state).toMatchObject({ - foo: Number, - }) -}) + const child = createRoute({ + parent: parent, + meta: { + bar: 'zoo', + }, + }) -test('given parent, meta is combined', () => { - const parent = createRoute({ - meta: { + expect(child.meta).toMatchObject({ foo: 123, - }, - }) - - const child = createRoute({ - parent: parent, - meta: { bar: 'zoo', - }, + }) }) - expect(child.meta).toMatchObject({ - foo: 123, - bar: 'zoo', - }) -}) + test('given parent, context is combined', () => { + const parentRejection = createRejection({ type: 'aRejection' }) + const childRelated = createRoute({ name: 'bRoute' }) -test('given parent, context is combined', () => { - const parentRejection = createRejection({ type: 'aRejection' }) - const childRelated = createRoute({ name: 'bRoute' }) + const parent = createRoute({ + meta: { + foo: 123, + }, + context: [parentRejection], + }) - const parent = createRoute({ - meta: { - foo: 123, - }, - context: [parentRejection], - }) + const child = createRoute({ + parent, + context: [childRelated], + meta: { + bar: 'zoo', + }, + }) - const child = createRoute({ - parent, - context: [childRelated], - meta: { - bar: 'zoo', - }, + expect(child.context).toMatchObject([parentRejection, childRelated]) }) - expect(child.context).toMatchObject([parentRejection, childRelated]) -}) + test('given parent and child without meta, meta matches parent', () => { + const parent = createRoute({ + meta: { + foo: 123, + }, + }) + + const child = createRoute({ + parent: parent, + }) -test('given parent and child without meta, meta matches parent', () => { - const parent = createRoute({ - meta: { + expect(child.meta).toMatchObject({ foo: 123, - }, + }) }) - const child = createRoute({ - parent: parent, - }) + test('given child has hoist, everything is combined except url', () => { + const parent = createRoute({ + path: '/parent/[?parent]', + query: 'parent=123', + hash: 'parent', + state: { + parent: 'parent', + }, + meta: { + parent: 'parent', + }, + }) - expect(child.meta).toMatchObject({ - foo: 123, - }) -}) + const child = createRoute({ + parent, + hoist: true, + path: '/child/[?child]', + query: 'child=456', + hash: 'child', + state: { + child: 'child', + }, + meta: { + child: 'child', + }, + }) -test('parent context is passed to child props', async () => { - const spy = vi.fn() - const parent = createRoute({ - name: 'parent', - }) + const params = child.parse('/child/42?child=456#child') + expect(params).toMatchObject({ + child: '42', + }) - const child = createRoute({ - name: 'child', - parent: parent, - path: '/child', - }, (_, { parent }) => { - return spy(parent) - }) + // @ts-expect-error - parent is not a param + params.parent = true - const router = createRouter([parent, child], { - initialUrl: '/child', + expect(child.stringify({ child: '42' })).toBe('/child/42?child=456#child') + expect(child.state).toMatchObject({ + parent: 'parent', + child: 'child', + }) + expect(child.meta).toMatchObject({ + parent: 'parent', + child: 'child', + }) }) +}) - await router.start() +describe('props', () => { + test('parent context is passed to child props', async () => { + const spy = vi.fn() + const parent = createRoute({ + name: 'parent', + }) - expect(spy).toHaveBeenCalledWith({ name: 'parent', props: undefined }) -}) + const child = createRoute({ + name: 'child', + parent: parent, + path: '/child', + }, (_, { parent }) => { + return spy(parent) + }) -test('sync parent props are passed to child props', async () => { - const spy = vi.fn() + const router = createRouter([parent, child], { + initialUrl: '/child', + }) - const parent = createRoute({ - name: 'parent', - }, () => ({ foo: 123 })) + await router.start() - const child = createRoute({ - name: 'child', - parent: parent, - path: '/child', - }, (__, { parent }) => { - return spy({ value: parent.props.foo }) + expect(spy).toHaveBeenCalledWith({ name: 'parent', props: undefined }) }) - const router = createRouter([parent, child], { - initialUrl: '/child', - }) + test('sync parent props are passed to child props', async () => { + const spy = vi.fn() - await router.start() + const parent = createRoute({ + name: 'parent', + }, () => ({ foo: 123 })) - expect(spy).toHaveBeenCalledWith({ value: 123 }) -}) + const child = createRoute({ + name: 'child', + parent: parent, + path: '/child', + }, (__, { parent }) => { + return spy({ value: parent.props.foo }) + }) -test('async parent props are passed to child props', async () => { - const spy = vi.fn() + const router = createRouter([parent, child], { + initialUrl: '/child', + }) - const parent = createRoute({ - name: 'parent', - }, async () => ({ foo: 123 })) + await router.start() - const child = createRoute({ - name: 'child', - parent: parent, - path: '/child', - }, async (__, { parent }) => { - expect(parent.props).toBeDefined() - expect(parent.props).toBeInstanceOf(Promise) + expect(spy).toHaveBeenCalledWith({ value: 123 }) + }) - const { foo: value } = await parent.props + test('async parent props are passed to child props', async () => { + const spy = vi.fn() - return spy({ value }) - }) + const parent = createRoute({ + name: 'parent', + }, async () => ({ foo: 123 })) - const router = createRouter([parent, child], { - initialUrl: '/child', - }) + const child = createRoute({ + name: 'child', + parent: parent, + path: '/child', + }, async (__, { parent }) => { + expect(parent.props).toBeDefined() + expect(parent.props).toBeInstanceOf(Promise) - await router.start() + const { foo: value } = await parent.props - expect(spy).toHaveBeenCalledWith({ value: 123 }) -}) + return spy({ value }) + }) + + const router = createRouter([parent, child], { + initialUrl: '/child', + }) -test('sync parent props with multiple views are passed to child props', async () => { - const spy = vi.fn() - - const parent = createRoute({ - name: 'parent', - components: { - one: component, - two: component, - three: component, - }, - }, { - one: () => ({ foo: 123 }), - two: () => ({ bar: 456 }), + await router.start() + + expect(spy).toHaveBeenCalledWith({ value: 123 }) }) - const child = createRoute({ - name: 'child', - parent: parent, - path: '/child', - }, (__, { parent }) => { - return spy({ - value1: parent.props.one.foo, - value2: parent.props.two.bar, + test('sync parent props with multiple views are passed to child props', async () => { + const spy = vi.fn() + + const parent = createRoute({ + name: 'parent', + components: { + one: component, + two: component, + three: component, + }, + }, { + one: () => ({ foo: 123 }), + two: () => ({ bar: 456 }), }) - }) - const router = createRouter([parent, child], { - initialUrl: '/child', - }) + const child = createRoute({ + name: 'child', + parent: parent, + path: '/child', + }, (__, { parent }) => { + return spy({ + value1: parent.props.one.foo, + value2: parent.props.two.bar, + }) + }) - await router.start() + const router = createRouter([parent, child], { + initialUrl: '/child', + }) - expect(spy).toHaveBeenCalledWith({ value1: 123, value2: 456 }) -}) + await router.start() -test('async parent props with multiple views are passed to child props', async () => { - const spy = vi.fn() - - const parent = createRoute({ - name: 'parent', - components: { - one: component, - two: component, - three: component, - }, - }, { - one: async () => ({ foo: 123 }), - two: async () => ({ bar: 456 }), + expect(spy).toHaveBeenCalledWith({ value1: 123, value2: 456 }) }) - const child = createRoute({ - name: 'child', - parent: parent, - path: '/child', - }, async (__, { parent }) => { - expect(parent.props).toBeDefined() - expect(parent.props.one).toBeInstanceOf(Promise) - expect(parent.props.two).toBeInstanceOf(Promise) + test('async parent props with multiple views are passed to child props', async () => { + const spy = vi.fn() - const { foo: value1 } = await parent.props.one - const { bar: value2 } = await parent.props.two + const parent = createRoute({ + name: 'parent', + components: { + one: component, + two: component, + three: component, + }, + }, { + one: async () => ({ foo: 123 }), + two: async () => ({ bar: 456 }), + }) - return spy({ - value1, - value2, + const child = createRoute({ + name: 'child', + parent: parent, + path: '/child', + }, async (__, { parent }) => { + expect(parent.props).toBeDefined() + expect(parent.props.one).toBeInstanceOf(Promise) + expect(parent.props.two).toBeInstanceOf(Promise) + + const { foo: value1 } = await parent.props.one + const { bar: value2 } = await parent.props.two + + return spy({ + value1, + value2, + }) }) - }) - const router = createRouter([parent, child], { - initialUrl: '/child', - }) + const router = createRouter([parent, child], { + initialUrl: '/child', + }) - await router.start() + await router.start() - expect(spy).toHaveBeenCalledWith({ value1: 123, value2: 456 }) + expect(spy).toHaveBeenCalledWith({ value1: 123, value2: 456 }) + }) }) test.each([ diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 576b42de..fdee1f48 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -84,6 +84,10 @@ export function createRoute(options: CreateRouteOptions, props?: CreateRouteProp if (isWithParent(options)) { const merged = combineRoutes(options.parent, route) + if (options.hoist) { + return merged + } + const url = combineUrl(options.parent, { path, query, diff --git a/src/services/createRouter.browser.spec.ts b/src/services/createRouter.browser.spec.ts index 0a1326aa..f2044035 100644 --- a/src/services/createRouter.browser.spec.ts +++ b/src/services/createRouter.browser.spec.ts @@ -75,3 +75,42 @@ describe('options.rejections', () => { expect(wrapper.html()).toBe('