diff --git a/packages/actions/__tests__/createRoutes.test.ts b/packages/actions/__tests__/createRoutes.test.ts deleted file mode 100644 index ab284272c2..0000000000 --- a/packages/actions/__tests__/createRoutes.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { describe, it } from 'vitest' -import { createMemoryHistory, Route } from '../src' -import { z } from 'zod' -import { Router, AllRouteInfo, createRouteConfig } from '../src' - -// Write a test -describe('everything', () => { - it('should work', () => { - // Build our routes. We could do this in our component, too. - const rootRoute = createRouteConfig() - const indexRoute = rootRoute.createRoute({ - path: '/', - validateSearch: (search) => - z - .object({ - version: z.number(), - }) - .parse(search), - }) - const testRoute = rootRoute.createRoute({ - path: 'test', - validateSearch: (search) => - z - .object({ - version: z.number(), - isGood: z.boolean(), - }) - .parse(search), - }) - const dashboardRoute = rootRoute.createRoute({ - path: 'dashboard', - onLoad: async () => { - console.log('Fetching all invoices...') - return { - invoices: 'await fetchInvoices()', - } - }, - }) - const dashboardIndexRoute = dashboardRoute.createRoute({ path: '/' }) - const invoicesRoute = dashboardRoute.createRoute({ - path: 'invoices', - }) - const invoicesIndexRoute = invoicesRoute.createRoute({ - path: '/', - }) - const invoiceRoute = invoicesRoute.createRoute({ - path: '$invoiceId', - parseParams: ({ invoiceId }) => ({ invoiceId: Number(invoiceId) }), - stringifyParams: ({ invoiceId }) => ({ - invoiceId: String(invoiceId), - }), - onLoad: async ({ params: { invoiceId } }) => { - console.log('Fetching invoice...') - return { - invoice: 'await fetchInvoiceById(invoiceId!)', - } - }, - }) - const usersRoute = dashboardRoute.createRoute({ - path: 'users', - onLoad: async () => { - return { - users: 'await fetchUsers()', - } - }, - validateSearch: (search) => - z - .object({ - usersView: z - .object({ - sortBy: z.enum(['name', 'id', 'email']).optional(), - filterBy: z.string().optional(), - }) - .optional(), - }) - .parse(search), - preSearchFilters: [ - // Keep the usersView search param around - // while in this route (or it's children!) - (search) => ({ - ...search, - usersView: { - ...search.usersView, - }, - }), - ], - }) - const userRoute = usersRoute.createRoute({ - path: '$userId', - onLoad: async ({ params: { userId }, search }) => { - return { - user: 'await fetchUserById(userId!)', - } - }, - }) - const authenticatedRoute = rootRoute.createRoute({ - path: 'authenticated/', // Trailing slash doesn't mean anything - }) - const authenticatedIndexRoute = authenticatedRoute.createRoute({ - path: '/', - }) - const layoutRoute = rootRoute.createRoute({ - id: 'layout', - component: () => 'layout-wrapper', - validateSearch: (search) => - z - .object({ - isLayout: z.boolean(), - }) - .parse(search), - }) - const layoutARoute = layoutRoute.createRoute({ - path: 'layout-a', - component: () => 'layout-a', - }) - const layoutBRoute = layoutRoute.createRoute({ - path: 'layout-b', - component: () => 'layout-b', - }) - - const routeConfig = rootRoute.addChildren([ - indexRoute, - testRoute, - dashboardRoute.addChildren([ - dashboardIndexRoute, - invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]), - usersRoute.addChildren([userRoute]), - ]), - authenticatedRoute.addChildren([authenticatedIndexRoute]), - layoutRoute.addChildren([layoutARoute, layoutBRoute]), - ]) - - type MyRoutesInfo = AllRouteInfo - // ^? - type RouteInfo = MyRoutesInfo['routeInfo'] - type RoutesById = MyRoutesInfo['routeInfoById'] - type RoutesTest = Route< - MyRoutesInfo, - MyRoutesInfo['routeInfoByFullPath']['/'] - > - // ^? - type RoutePaths = MyRoutesInfo['routeInfoByFullPath'] - // ^? - type InvoiceRouteInfo = RoutesById['/dashboard/invoices/$invoiceId'] - // ^? - type InvoiceLoaderData = InvoiceRouteInfo['loaderData'] - // ^?// - - const router = new Router({ - routeConfig, - history: createMemoryHistory(), - }) - - const loaderData = router.getRoute('/dashboard/users/$userId') - // ^? - const route = router.getRoute('/dashboard/users/$userId') - // ^? - - router.buildLink({ - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'email', - }, - }), - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/test', - }) - - router.buildLink({ - from: '/', - to: '/test', - search: () => { - return { - version: 2, - isGood: true, - } - }, - }) - - router.buildLink({ - from: '/test', - to: '/', - }) - - router.buildLink({ - from: route.id, - to: '', - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices/$invoiceId', - params: { - // @ts-expect-error - invoiceId: '2', - }, - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - to: '/', - search: { - version: 2, - }, - }) - - router.buildLink({ - to: '/dashboard/users/$userId', - // @ts-expect-error - params: (current) => ({ - userId: current?.invoiceId, - }), - search: (old) => ({ - usersView: { - sortBy: 'email' as const, - filterBy: String(old.version), - }, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '/dashboard/users/$userId', - params: (current) => ({ - userId: `${current?.invoiceId}`, - }), - search: (prev) => { - return { - usersView: { - sortBy: 'name' as const, - filterBy: 'tanner', - }, - } - }, - }) - - router.buildLink({ - from: '/dashboard/users/$userId', - to: '/', - search: (prev) => { - return { - version: 2, - } - }, - }) - - router.buildLink({ - from: '/', - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'id', - filterBy: `${prev.version}`, - }, - }), - }) - - router.navigate({ - search: (prev: any) => ({ - version: prev.version, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices', - to: '/dashboard', - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/does-not-exist', - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '.', - params: (d) => ({ - invoiceId: d.invoiceId, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: testRoute.id, - search: { - version: 2, - isGood: true, - }, - }) - - router.buildLink({ - to: '/layout-a', - search: (current) => ({ - isLayout: !!current.version, - }), - }) - }) -}) diff --git a/packages/actions/__tests__/index.test.tsx b/packages/actions/__tests__/index.test.tsx deleted file mode 100644 index 17a5e172e7..0000000000 --- a/packages/actions/__tests__/index.test.tsx +++ /dev/null @@ -1,587 +0,0 @@ -import { describe, test, expect } from 'vitest' - -import { - // Location, - matchPathname, - // Route, - // createMemoryHistory, - resolvePath, -} from '../src' - -import { createTimer, sleep } from './utils' - -// function RouterInstance(opts?: { initialEntries?: string[] }) { -// return new RouterInstance({ -// routes: [], -// history: createMemoryHistory({ -// initialEntries: opts?.initialEntries ?? ['/'], -// }), -// }) -// } - -function createLocation(location: Partial): Location { - return { - pathname: '', - href: '', - search: {}, - searchStr: '', - state: {}, - hash: '', - ...location, - } -} - -// describe('Router', () => { -// test('mounts to /', async () => { -// const router = RouterInstance() - -// const routes = [ -// { -// path: '/', -// }, -// ] - -// router.update({ -// routes, -// }) - -// const promise = router.mount() -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') -// }) - -// test('mounts to /a', async () => { -// const router = RouterInstance({ initialEntries: ['/a'] }) -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/a') -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/a') -// }) - -// test('mounts to /a/b', async () => { -// const router = RouterInstance({ -// initialEntries: ['/a/b'], -// }) - -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// children: [ -// { -// path: '/b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[1].id).toBe('/a/b') -// await promise -// expect(router.store.state.currentMatches[1].id).toBe('/a/b') -// }) - -// test('navigates to /a', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') - -// promise = router.navigate({ to: 'a' }) -// expect(router.store.state.currentMatches[0].id).toBe('/') -// expect(router.store.pendingMatches[0].id).toBe('a') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('a') -// expect(router.store.pending).toBe(undefined) -// }) - -// test('navigates to /a to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// children: [ -// { -// path: 'b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() -// expect(router.store.state.currentLocation.href).toBe('/') - -// let promise = router.navigate({ to: 'a' }) -// expect(router.store.pendingLocation.href).toBe('/a') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a') - -// promise = router.navigate({ to: './b' }) -// expect(router.store.pendingLocation.href).toBe('/a/b') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a/b') - -// expect(router.store.pending).toBe(undefined) -// }) - -// test('async navigates to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// children: [ -// { -// path: 'b', -// onLoad: () => sleep(10).then((d) => ({ b: true })), -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(30) -// }) - -// test('async navigates with import + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => -// sleep(10).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(28) -// }) - -// test('async navigates with import + elements + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => sleep(30).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => -// sleep(30).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(55) -// }) - -// test('async navigates with pending state', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// pendingMs: 10, -// onLoad: () => sleep(20), -// children: [ -// { -// path: 'b', -// pendingMs: 30, -// onLoad: () => sleep(40), -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() - -// const timer = createTimer() -// await router.navigate({ to: 'a/b' }) -// expect(timer.getTime()).toBeLessThan(46) -// }) -// }) - -describe('matchRoute', () => { - describe('fuzzy', () => { - ;( - [ - [ - '/', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a' }, - ], - [ - '/a/b', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b' }, - ], - [ - '/a/b/c', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b/c' }, - ], - [ - '/a/b/c', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a/b', - { - to: '/a/b/', - fuzzy: true, - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('exact', () => { - ;( - [ - [ - '/a/b/c', - { - to: '/', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('basepath', () => { - ;( - [ - [ - '/base', - '/base', - { - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/a', - }, - {}, - ], - [ - '/base', - '/base/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - [ - '/base', - '/base/posts', - { - fuzzy: true, - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/b', - }, - undefined, - ], - ] as const - ).forEach(([a, b, c, eq]) => { - test(`${b} == ${a} + ${c.to}`, () => { - expect(matchPathname(a, b, c)).toEqual(eq) - }) - }) - }) - - describe('params', () => { - ;( - [ - [ - '/a/b', - { - to: '/a/$b', - }, - { b: 'b' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/$c', - }, - { b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/$b/$c', - }, - { a: 'a', b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/*', - }, - { a: 'a', '*': 'b/c' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/c', - }, - { b: 'b' }, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) -}) - -describe('resolvePath', () => { - describe('basic resolution', () => { - ;[ - ['/', '/', '/', '/'], - ['/', '/', '/a', '/a'], - ['/', '/', 'a/', '/a/'], - ['/', '/', '/a/b', '/a/b'], - ['/', 'a', 'b', '/a/b'], - ['/a/b', 'c', '/a/b/c', '/a/b/c'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('relative', () => { - ;[ - ['/a/b', '/', './c', '/a/b/c'], - ['/', '/', './a/b', '/a/b'], - ['/', '/a/b/c', './d', '/a/b/c/d'], - ['/', '/a/b/c', '../d', '/a/b/d'], - ['/', '/a/b/c', '../../d', '/a/d'], - ['/', '/a/b/c', '../..', '/a'], - ['/', '/a/b/c/', '../..', '/a'], - ['/', '/a/b/c', '../../..', '/'], - ['/', '/a/b/c/', '../../..', '/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('trailing slash', () => { - ;[ - ['/', '/a', './b/', '/a/b/'], - ['/', '/', 'a/b/c/', '/a/b/c/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) -}) diff --git a/packages/actions/__tests__/utils.ts b/packages/actions/__tests__/utils.ts deleted file mode 100644 index adf8b76607..0000000000 --- a/packages/actions/__tests__/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -export function createTimer() { - let time = Date.now() - - return { - start: () => { - time = Date.now() - }, - getTime: () => { - return Date.now() - time - }, - } -} diff --git a/packages/loaders/__tests__/createRoutes.test.ts b/packages/loaders/__tests__/createRoutes.test.ts deleted file mode 100644 index ab284272c2..0000000000 --- a/packages/loaders/__tests__/createRoutes.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { describe, it } from 'vitest' -import { createMemoryHistory, Route } from '../src' -import { z } from 'zod' -import { Router, AllRouteInfo, createRouteConfig } from '../src' - -// Write a test -describe('everything', () => { - it('should work', () => { - // Build our routes. We could do this in our component, too. - const rootRoute = createRouteConfig() - const indexRoute = rootRoute.createRoute({ - path: '/', - validateSearch: (search) => - z - .object({ - version: z.number(), - }) - .parse(search), - }) - const testRoute = rootRoute.createRoute({ - path: 'test', - validateSearch: (search) => - z - .object({ - version: z.number(), - isGood: z.boolean(), - }) - .parse(search), - }) - const dashboardRoute = rootRoute.createRoute({ - path: 'dashboard', - onLoad: async () => { - console.log('Fetching all invoices...') - return { - invoices: 'await fetchInvoices()', - } - }, - }) - const dashboardIndexRoute = dashboardRoute.createRoute({ path: '/' }) - const invoicesRoute = dashboardRoute.createRoute({ - path: 'invoices', - }) - const invoicesIndexRoute = invoicesRoute.createRoute({ - path: '/', - }) - const invoiceRoute = invoicesRoute.createRoute({ - path: '$invoiceId', - parseParams: ({ invoiceId }) => ({ invoiceId: Number(invoiceId) }), - stringifyParams: ({ invoiceId }) => ({ - invoiceId: String(invoiceId), - }), - onLoad: async ({ params: { invoiceId } }) => { - console.log('Fetching invoice...') - return { - invoice: 'await fetchInvoiceById(invoiceId!)', - } - }, - }) - const usersRoute = dashboardRoute.createRoute({ - path: 'users', - onLoad: async () => { - return { - users: 'await fetchUsers()', - } - }, - validateSearch: (search) => - z - .object({ - usersView: z - .object({ - sortBy: z.enum(['name', 'id', 'email']).optional(), - filterBy: z.string().optional(), - }) - .optional(), - }) - .parse(search), - preSearchFilters: [ - // Keep the usersView search param around - // while in this route (or it's children!) - (search) => ({ - ...search, - usersView: { - ...search.usersView, - }, - }), - ], - }) - const userRoute = usersRoute.createRoute({ - path: '$userId', - onLoad: async ({ params: { userId }, search }) => { - return { - user: 'await fetchUserById(userId!)', - } - }, - }) - const authenticatedRoute = rootRoute.createRoute({ - path: 'authenticated/', // Trailing slash doesn't mean anything - }) - const authenticatedIndexRoute = authenticatedRoute.createRoute({ - path: '/', - }) - const layoutRoute = rootRoute.createRoute({ - id: 'layout', - component: () => 'layout-wrapper', - validateSearch: (search) => - z - .object({ - isLayout: z.boolean(), - }) - .parse(search), - }) - const layoutARoute = layoutRoute.createRoute({ - path: 'layout-a', - component: () => 'layout-a', - }) - const layoutBRoute = layoutRoute.createRoute({ - path: 'layout-b', - component: () => 'layout-b', - }) - - const routeConfig = rootRoute.addChildren([ - indexRoute, - testRoute, - dashboardRoute.addChildren([ - dashboardIndexRoute, - invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]), - usersRoute.addChildren([userRoute]), - ]), - authenticatedRoute.addChildren([authenticatedIndexRoute]), - layoutRoute.addChildren([layoutARoute, layoutBRoute]), - ]) - - type MyRoutesInfo = AllRouteInfo - // ^? - type RouteInfo = MyRoutesInfo['routeInfo'] - type RoutesById = MyRoutesInfo['routeInfoById'] - type RoutesTest = Route< - MyRoutesInfo, - MyRoutesInfo['routeInfoByFullPath']['/'] - > - // ^? - type RoutePaths = MyRoutesInfo['routeInfoByFullPath'] - // ^? - type InvoiceRouteInfo = RoutesById['/dashboard/invoices/$invoiceId'] - // ^? - type InvoiceLoaderData = InvoiceRouteInfo['loaderData'] - // ^?// - - const router = new Router({ - routeConfig, - history: createMemoryHistory(), - }) - - const loaderData = router.getRoute('/dashboard/users/$userId') - // ^? - const route = router.getRoute('/dashboard/users/$userId') - // ^? - - router.buildLink({ - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'email', - }, - }), - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/test', - }) - - router.buildLink({ - from: '/', - to: '/test', - search: () => { - return { - version: 2, - isGood: true, - } - }, - }) - - router.buildLink({ - from: '/test', - to: '/', - }) - - router.buildLink({ - from: route.id, - to: '', - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices/$invoiceId', - params: { - // @ts-expect-error - invoiceId: '2', - }, - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - to: '/', - search: { - version: 2, - }, - }) - - router.buildLink({ - to: '/dashboard/users/$userId', - // @ts-expect-error - params: (current) => ({ - userId: current?.invoiceId, - }), - search: (old) => ({ - usersView: { - sortBy: 'email' as const, - filterBy: String(old.version), - }, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '/dashboard/users/$userId', - params: (current) => ({ - userId: `${current?.invoiceId}`, - }), - search: (prev) => { - return { - usersView: { - sortBy: 'name' as const, - filterBy: 'tanner', - }, - } - }, - }) - - router.buildLink({ - from: '/dashboard/users/$userId', - to: '/', - search: (prev) => { - return { - version: 2, - } - }, - }) - - router.buildLink({ - from: '/', - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'id', - filterBy: `${prev.version}`, - }, - }), - }) - - router.navigate({ - search: (prev: any) => ({ - version: prev.version, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices', - to: '/dashboard', - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/does-not-exist', - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '.', - params: (d) => ({ - invoiceId: d.invoiceId, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: testRoute.id, - search: { - version: 2, - isGood: true, - }, - }) - - router.buildLink({ - to: '/layout-a', - search: (current) => ({ - isLayout: !!current.version, - }), - }) - }) -}) diff --git a/packages/loaders/__tests__/index.test.tsx b/packages/loaders/__tests__/index.test.tsx deleted file mode 100644 index 17a5e172e7..0000000000 --- a/packages/loaders/__tests__/index.test.tsx +++ /dev/null @@ -1,587 +0,0 @@ -import { describe, test, expect } from 'vitest' - -import { - // Location, - matchPathname, - // Route, - // createMemoryHistory, - resolvePath, -} from '../src' - -import { createTimer, sleep } from './utils' - -// function RouterInstance(opts?: { initialEntries?: string[] }) { -// return new RouterInstance({ -// routes: [], -// history: createMemoryHistory({ -// initialEntries: opts?.initialEntries ?? ['/'], -// }), -// }) -// } - -function createLocation(location: Partial): Location { - return { - pathname: '', - href: '', - search: {}, - searchStr: '', - state: {}, - hash: '', - ...location, - } -} - -// describe('Router', () => { -// test('mounts to /', async () => { -// const router = RouterInstance() - -// const routes = [ -// { -// path: '/', -// }, -// ] - -// router.update({ -// routes, -// }) - -// const promise = router.mount() -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') -// }) - -// test('mounts to /a', async () => { -// const router = RouterInstance({ initialEntries: ['/a'] }) -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/a') -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/a') -// }) - -// test('mounts to /a/b', async () => { -// const router = RouterInstance({ -// initialEntries: ['/a/b'], -// }) - -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// children: [ -// { -// path: '/b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[1].id).toBe('/a/b') -// await promise -// expect(router.store.state.currentMatches[1].id).toBe('/a/b') -// }) - -// test('navigates to /a', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') - -// promise = router.navigate({ to: 'a' }) -// expect(router.store.state.currentMatches[0].id).toBe('/') -// expect(router.store.pendingMatches[0].id).toBe('a') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('a') -// expect(router.store.pending).toBe(undefined) -// }) - -// test('navigates to /a to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// children: [ -// { -// path: 'b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() -// expect(router.store.state.currentLocation.href).toBe('/') - -// let promise = router.navigate({ to: 'a' }) -// expect(router.store.pendingLocation.href).toBe('/a') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a') - -// promise = router.navigate({ to: './b' }) -// expect(router.store.pendingLocation.href).toBe('/a/b') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a/b') - -// expect(router.store.pending).toBe(undefined) -// }) - -// test('async navigates to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// children: [ -// { -// path: 'b', -// onLoad: () => sleep(10).then((d) => ({ b: true })), -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(30) -// }) - -// test('async navigates with import + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => -// sleep(10).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(28) -// }) - -// test('async navigates with import + elements + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => sleep(30).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => -// sleep(30).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(55) -// }) - -// test('async navigates with pending state', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// pendingMs: 10, -// onLoad: () => sleep(20), -// children: [ -// { -// path: 'b', -// pendingMs: 30, -// onLoad: () => sleep(40), -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() - -// const timer = createTimer() -// await router.navigate({ to: 'a/b' }) -// expect(timer.getTime()).toBeLessThan(46) -// }) -// }) - -describe('matchRoute', () => { - describe('fuzzy', () => { - ;( - [ - [ - '/', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a' }, - ], - [ - '/a/b', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b' }, - ], - [ - '/a/b/c', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b/c' }, - ], - [ - '/a/b/c', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a/b', - { - to: '/a/b/', - fuzzy: true, - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('exact', () => { - ;( - [ - [ - '/a/b/c', - { - to: '/', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('basepath', () => { - ;( - [ - [ - '/base', - '/base', - { - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/a', - }, - {}, - ], - [ - '/base', - '/base/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - [ - '/base', - '/base/posts', - { - fuzzy: true, - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/b', - }, - undefined, - ], - ] as const - ).forEach(([a, b, c, eq]) => { - test(`${b} == ${a} + ${c.to}`, () => { - expect(matchPathname(a, b, c)).toEqual(eq) - }) - }) - }) - - describe('params', () => { - ;( - [ - [ - '/a/b', - { - to: '/a/$b', - }, - { b: 'b' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/$c', - }, - { b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/$b/$c', - }, - { a: 'a', b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/*', - }, - { a: 'a', '*': 'b/c' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/c', - }, - { b: 'b' }, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) -}) - -describe('resolvePath', () => { - describe('basic resolution', () => { - ;[ - ['/', '/', '/', '/'], - ['/', '/', '/a', '/a'], - ['/', '/', 'a/', '/a/'], - ['/', '/', '/a/b', '/a/b'], - ['/', 'a', 'b', '/a/b'], - ['/a/b', 'c', '/a/b/c', '/a/b/c'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('relative', () => { - ;[ - ['/a/b', '/', './c', '/a/b/c'], - ['/', '/', './a/b', '/a/b'], - ['/', '/a/b/c', './d', '/a/b/c/d'], - ['/', '/a/b/c', '../d', '/a/b/d'], - ['/', '/a/b/c', '../../d', '/a/d'], - ['/', '/a/b/c', '../..', '/a'], - ['/', '/a/b/c/', '../..', '/a'], - ['/', '/a/b/c', '../../..', '/'], - ['/', '/a/b/c/', '../../..', '/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('trailing slash', () => { - ;[ - ['/', '/a', './b/', '/a/b/'], - ['/', '/', 'a/b/c/', '/a/b/c/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) -}) diff --git a/packages/loaders/__tests__/utils.ts b/packages/loaders/__tests__/utils.ts deleted file mode 100644 index adf8b76607..0000000000 --- a/packages/loaders/__tests__/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -export function createTimer() { - let time = Date.now() - - return { - start: () => { - time = Date.now() - }, - getTime: () => { - return Date.now() - time - }, - } -} diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 3bb5ef625f..76fae74c86 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -45,7 +45,6 @@ "@tanstack/react-store": "workspace:*" }, "devDependencies": { - "@types/use-sync-external-store": "^0.0.3", "babel-plugin-transform-async-to-promises": "^0.8.18" } } diff --git a/packages/react-store/package.json b/packages/react-store/package.json index f901f407de..3ced1dd4df 100644 --- a/packages/react-store/package.json +++ b/packages/react-store/package.json @@ -37,5 +37,8 @@ "dependencies": { "@tanstack/store": "workspace:*", "use-sync-external-store": "^1.2.0" + }, + "devDependencies": { + "@types/use-sync-external-store": "^0.0.3" } } diff --git a/packages/router/__tests__/createRoutes.test.ts b/packages/router/__tests__/createRoutes.test.ts index ab284272c2..28d2a3ea06 100644 --- a/packages/router/__tests__/createRoutes.test.ts +++ b/packages/router/__tests__/createRoutes.test.ts @@ -142,13 +142,13 @@ describe('everything', () => { type RoutePaths = MyRoutesInfo['routeInfoByFullPath'] // ^? type InvoiceRouteInfo = RoutesById['/dashboard/invoices/$invoiceId'] - // ^? - type InvoiceLoaderData = InvoiceRouteInfo['loaderData'] // ^?// const router = new Router({ routeConfig, - history: createMemoryHistory(), + history: createMemoryHistory({ + initialEntries: ['/?version=1'], + }), }) const loaderData = router.getRoute('/dashboard/users/$userId') diff --git a/packages/router/__tests__/index.test.tsx b/packages/router/__tests__/index.test.tsx index 17a5e172e7..1225e2fd03 100644 --- a/packages/router/__tests__/index.test.tsx +++ b/packages/router/__tests__/index.test.tsx @@ -3,6 +3,7 @@ import { describe, test, expect } from 'vitest' import { // Location, matchPathname, + ParsedLocation, // Route, // createMemoryHistory, resolvePath, @@ -19,7 +20,7 @@ import { createTimer, sleep } from './utils' // }) // } -function createLocation(location: Partial): Location { +function createLocation(location: Partial): ParsedLocation { return { pathname: '', href: '', diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index e1a57c508b..bb73f7c977 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -348,10 +348,12 @@ export class Router< this.options.history ?? (isServer ? createMemoryHistory() : createBrowserHistory()!) + const parsedLocation = this.#parseLocation() + this.store.setState((s) => ({ ...s, - latestLocation: this.#parseLocation(), - currentLocation: s.latestLocation, + latestLocation: parsedLocation, + currentLocation: parsedLocation, })) this.#unsubHistory = this.history.listen(() => { diff --git a/packages/store/__tests__/createRoutes.test.ts b/packages/store/__tests__/createRoutes.test.ts deleted file mode 100644 index ab284272c2..0000000000 --- a/packages/store/__tests__/createRoutes.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { describe, it } from 'vitest' -import { createMemoryHistory, Route } from '../src' -import { z } from 'zod' -import { Router, AllRouteInfo, createRouteConfig } from '../src' - -// Write a test -describe('everything', () => { - it('should work', () => { - // Build our routes. We could do this in our component, too. - const rootRoute = createRouteConfig() - const indexRoute = rootRoute.createRoute({ - path: '/', - validateSearch: (search) => - z - .object({ - version: z.number(), - }) - .parse(search), - }) - const testRoute = rootRoute.createRoute({ - path: 'test', - validateSearch: (search) => - z - .object({ - version: z.number(), - isGood: z.boolean(), - }) - .parse(search), - }) - const dashboardRoute = rootRoute.createRoute({ - path: 'dashboard', - onLoad: async () => { - console.log('Fetching all invoices...') - return { - invoices: 'await fetchInvoices()', - } - }, - }) - const dashboardIndexRoute = dashboardRoute.createRoute({ path: '/' }) - const invoicesRoute = dashboardRoute.createRoute({ - path: 'invoices', - }) - const invoicesIndexRoute = invoicesRoute.createRoute({ - path: '/', - }) - const invoiceRoute = invoicesRoute.createRoute({ - path: '$invoiceId', - parseParams: ({ invoiceId }) => ({ invoiceId: Number(invoiceId) }), - stringifyParams: ({ invoiceId }) => ({ - invoiceId: String(invoiceId), - }), - onLoad: async ({ params: { invoiceId } }) => { - console.log('Fetching invoice...') - return { - invoice: 'await fetchInvoiceById(invoiceId!)', - } - }, - }) - const usersRoute = dashboardRoute.createRoute({ - path: 'users', - onLoad: async () => { - return { - users: 'await fetchUsers()', - } - }, - validateSearch: (search) => - z - .object({ - usersView: z - .object({ - sortBy: z.enum(['name', 'id', 'email']).optional(), - filterBy: z.string().optional(), - }) - .optional(), - }) - .parse(search), - preSearchFilters: [ - // Keep the usersView search param around - // while in this route (or it's children!) - (search) => ({ - ...search, - usersView: { - ...search.usersView, - }, - }), - ], - }) - const userRoute = usersRoute.createRoute({ - path: '$userId', - onLoad: async ({ params: { userId }, search }) => { - return { - user: 'await fetchUserById(userId!)', - } - }, - }) - const authenticatedRoute = rootRoute.createRoute({ - path: 'authenticated/', // Trailing slash doesn't mean anything - }) - const authenticatedIndexRoute = authenticatedRoute.createRoute({ - path: '/', - }) - const layoutRoute = rootRoute.createRoute({ - id: 'layout', - component: () => 'layout-wrapper', - validateSearch: (search) => - z - .object({ - isLayout: z.boolean(), - }) - .parse(search), - }) - const layoutARoute = layoutRoute.createRoute({ - path: 'layout-a', - component: () => 'layout-a', - }) - const layoutBRoute = layoutRoute.createRoute({ - path: 'layout-b', - component: () => 'layout-b', - }) - - const routeConfig = rootRoute.addChildren([ - indexRoute, - testRoute, - dashboardRoute.addChildren([ - dashboardIndexRoute, - invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]), - usersRoute.addChildren([userRoute]), - ]), - authenticatedRoute.addChildren([authenticatedIndexRoute]), - layoutRoute.addChildren([layoutARoute, layoutBRoute]), - ]) - - type MyRoutesInfo = AllRouteInfo - // ^? - type RouteInfo = MyRoutesInfo['routeInfo'] - type RoutesById = MyRoutesInfo['routeInfoById'] - type RoutesTest = Route< - MyRoutesInfo, - MyRoutesInfo['routeInfoByFullPath']['/'] - > - // ^? - type RoutePaths = MyRoutesInfo['routeInfoByFullPath'] - // ^? - type InvoiceRouteInfo = RoutesById['/dashboard/invoices/$invoiceId'] - // ^? - type InvoiceLoaderData = InvoiceRouteInfo['loaderData'] - // ^?// - - const router = new Router({ - routeConfig, - history: createMemoryHistory(), - }) - - const loaderData = router.getRoute('/dashboard/users/$userId') - // ^? - const route = router.getRoute('/dashboard/users/$userId') - // ^? - - router.buildLink({ - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'email', - }, - }), - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/test', - }) - - router.buildLink({ - from: '/', - to: '/test', - search: () => { - return { - version: 2, - isGood: true, - } - }, - }) - - router.buildLink({ - from: '/test', - to: '/', - }) - - router.buildLink({ - from: route.id, - to: '', - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard', - to: '/dashboard/invoices/$invoiceId', - params: { - // @ts-expect-error - invoiceId: '2', - }, - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - to: '/', - search: { - version: 2, - }, - }) - - router.buildLink({ - to: '/dashboard/users/$userId', - // @ts-expect-error - params: (current) => ({ - userId: current?.invoiceId, - }), - search: (old) => ({ - usersView: { - sortBy: 'email' as const, - filterBy: String(old.version), - }, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '/dashboard/users/$userId', - params: (current) => ({ - userId: `${current?.invoiceId}`, - }), - search: (prev) => { - return { - usersView: { - sortBy: 'name' as const, - filterBy: 'tanner', - }, - } - }, - }) - - router.buildLink({ - from: '/dashboard/users/$userId', - to: '/', - search: (prev) => { - return { - version: 2, - } - }, - }) - - router.buildLink({ - from: '/', - to: '/dashboard/users/$userId', - params: { - userId: '2', - }, - search: (prev) => ({ - usersView: { - sortBy: 'id', - filterBy: `${prev.version}`, - }, - }), - }) - - router.navigate({ - search: (prev: any) => ({ - version: prev.version, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices', - to: '/dashboard', - }) - - // @ts-expect-error - router.buildLink({ - from: '/', - to: '/does-not-exist', - }) - - router.buildLink({ - to: '/dashboard/invoices/$invoiceId', - params: { - invoiceId: 2, - }, - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: '.', - params: (d) => ({ - invoiceId: d.invoiceId, - }), - }) - - router.buildLink({ - from: '/dashboard/invoices/$invoiceId', - to: testRoute.id, - search: { - version: 2, - isGood: true, - }, - }) - - router.buildLink({ - to: '/layout-a', - search: (current) => ({ - isLayout: !!current.version, - }), - }) - }) -}) diff --git a/packages/store/__tests__/index.test.tsx b/packages/store/__tests__/index.test.tsx deleted file mode 100644 index 17a5e172e7..0000000000 --- a/packages/store/__tests__/index.test.tsx +++ /dev/null @@ -1,587 +0,0 @@ -import { describe, test, expect } from 'vitest' - -import { - // Location, - matchPathname, - // Route, - // createMemoryHistory, - resolvePath, -} from '../src' - -import { createTimer, sleep } from './utils' - -// function RouterInstance(opts?: { initialEntries?: string[] }) { -// return new RouterInstance({ -// routes: [], -// history: createMemoryHistory({ -// initialEntries: opts?.initialEntries ?? ['/'], -// }), -// }) -// } - -function createLocation(location: Partial): Location { - return { - pathname: '', - href: '', - search: {}, - searchStr: '', - state: {}, - hash: '', - ...location, - } -} - -// describe('Router', () => { -// test('mounts to /', async () => { -// const router = RouterInstance() - -// const routes = [ -// { -// path: '/', -// }, -// ] - -// router.update({ -// routes, -// }) - -// const promise = router.mount() -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') -// }) - -// test('mounts to /a', async () => { -// const router = RouterInstance({ initialEntries: ['/a'] }) -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/a') -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/a') -// }) - -// test('mounts to /a/b', async () => { -// const router = RouterInstance({ -// initialEntries: ['/a/b'], -// }) - -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: '/a', -// children: [ -// { -// path: '/b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[1].id).toBe('/a/b') -// await promise -// expect(router.store.state.currentMatches[1].id).toBe('/a/b') -// }) - -// test('navigates to /a', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// }, -// ] - -// router.update({ -// routes, -// }) - -// let promise = router.mount() - -// expect(router.store.pendingMatches[0].id).toBe('/') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('/') - -// promise = router.navigate({ to: 'a' }) -// expect(router.store.state.currentMatches[0].id).toBe('/') -// expect(router.store.pendingMatches[0].id).toBe('a') - -// await promise -// expect(router.store.state.currentMatches[0].id).toBe('a') -// expect(router.store.pending).toBe(undefined) -// }) - -// test('navigates to /a to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// children: [ -// { -// path: 'b', -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() -// expect(router.store.state.currentLocation.href).toBe('/') - -// let promise = router.navigate({ to: 'a' }) -// expect(router.store.pendingLocation.href).toBe('/a') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a') - -// promise = router.navigate({ to: './b' }) -// expect(router.store.pendingLocation.href).toBe('/a/b') -// await promise -// expect(router.store.state.currentLocation.href).toBe('/a/b') - -// expect(router.store.pending).toBe(undefined) -// }) - -// test('async navigates to /a/b', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// children: [ -// { -// path: 'b', -// onLoad: () => sleep(10).then((d) => ({ b: true })), -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(30) -// }) - -// test('async navigates with import + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => sleep(10).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// onLoad: () => -// sleep(10).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// timer.start() -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(28) -// }) - -// test('async navigates with import + elements + loader', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => sleep(30).then((d) => ({ a: true })), -// } -// }, -// children: [ -// { -// path: 'b', -// import: async () => { -// await sleep(10) -// return { -// element: async () => { -// await sleep(20) -// return 'element' -// }, -// onLoad: () => -// sleep(30).then((d) => ({ -// b: true, -// })), -// } -// }, -// }, -// ], -// }, -// ] - -// const timer = createTimer() - -// router.update({ -// routes, -// }) - -// router.mount() - -// await router.navigate({ to: 'a/b' }) -// expect(router.store.loaderData).toEqual({ -// a: true, -// b: true, -// }) -// expect(timer.getTime()).toBeLessThan(55) -// }) - -// test('async navigates with pending state', async () => { -// const router = RouterInstance() -// const routes: Route[] = [ -// { -// path: '/', -// }, -// { -// path: 'a', -// pendingMs: 10, -// onLoad: () => sleep(20), -// children: [ -// { -// path: 'b', -// pendingMs: 30, -// onLoad: () => sleep(40), -// }, -// ], -// }, -// ] - -// router.update({ -// routes, -// }) - -// await router.mount() - -// const timer = createTimer() -// await router.navigate({ to: 'a/b' }) -// expect(timer.getTime()).toBeLessThan(46) -// }) -// }) - -describe('matchRoute', () => { - describe('fuzzy', () => { - ;( - [ - [ - '/', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a' }, - ], - [ - '/a/b', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b' }, - ], - [ - '/a/b/c', - { - to: '/*', - fuzzy: true, - }, - { '*': 'a/b/c' }, - ], - [ - '/a/b/c', - { - to: '/', - fuzzy: true, - }, - {}, - ], - [ - '/a/b', - { - to: '/a/b/', - fuzzy: true, - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('exact', () => { - ;( - [ - [ - '/a/b/c', - { - to: '/', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b', - }, - undefined, - ], - [ - '/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) - - describe('basepath', () => { - ;( - [ - [ - '/base', - '/base', - { - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/a', - }, - {}, - ], - [ - '/base', - '/base/a/b/c', - { - to: '/a/b/c', - }, - {}, - ], - [ - '/base', - '/base/posts', - { - fuzzy: true, - to: '/', - }, - {}, - ], - [ - '/base', - '/base/a', - { - to: '/b', - }, - undefined, - ], - ] as const - ).forEach(([a, b, c, eq]) => { - test(`${b} == ${a} + ${c.to}`, () => { - expect(matchPathname(a, b, c)).toEqual(eq) - }) - }) - }) - - describe('params', () => { - ;( - [ - [ - '/a/b', - { - to: '/a/$b', - }, - { b: 'b' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/$c', - }, - { b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/$b/$c', - }, - { a: 'a', b: 'b', c: 'c' }, - ], - [ - '/a/b/c', - { - to: '/$a/*', - }, - { a: 'a', '*': 'b/c' }, - ], - [ - '/a/b/c', - { - to: '/a/$b/c', - }, - { b: 'b' }, - ], - ] as const - ).forEach(([a, b, eq]) => { - test(`${a} == ${b.to}`, () => { - expect(matchPathname('', a, b)).toEqual(eq) - }) - }) - }) -}) - -describe('resolvePath', () => { - describe('basic resolution', () => { - ;[ - ['/', '/', '/', '/'], - ['/', '/', '/a', '/a'], - ['/', '/', 'a/', '/a/'], - ['/', '/', '/a/b', '/a/b'], - ['/', 'a', 'b', '/a/b'], - ['/a/b', 'c', '/a/b/c', '/a/b/c'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('relative', () => { - ;[ - ['/a/b', '/', './c', '/a/b/c'], - ['/', '/', './a/b', '/a/b'], - ['/', '/a/b/c', './d', '/a/b/c/d'], - ['/', '/a/b/c', '../d', '/a/b/d'], - ['/', '/a/b/c', '../../d', '/a/d'], - ['/', '/a/b/c', '../..', '/a'], - ['/', '/a/b/c/', '../..', '/a'], - ['/', '/a/b/c', '../../..', '/'], - ['/', '/a/b/c/', '../../..', '/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) - - describe('trailing slash', () => { - ;[ - ['/', '/a', './b/', '/a/b/'], - ['/', '/', 'a/b/c/', '/a/b/c/'], - ].forEach(([base, a, b, eq]) => { - test(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath(base, a, b)).toEqual(eq) - }) - }) - }) -}) diff --git a/packages/store/__tests__/utils.ts b/packages/store/__tests__/utils.ts deleted file mode 100644 index adf8b76607..0000000000 --- a/packages/store/__tests__/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -export function createTimer() { - let time = Date.now() - - return { - start: () => { - time = Date.now() - }, - getTime: () => { - return Date.now() - time - }, - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cd182baa7..b31f7d4837 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -571,14 +571,12 @@ importers: '@babel/runtime': ^7.16.7 '@tanstack/react-store': workspace:* '@tanstack/router': workspace:* - '@types/use-sync-external-store': ^0.0.3 babel-plugin-transform-async-to-promises: ^0.8.18 dependencies: '@babel/runtime': 7.20.6 '@tanstack/react-store': link:../react-store '@tanstack/router': link:../router devDependencies: - '@types/use-sync-external-store': 0.0.3 babel-plugin-transform-async-to-promises: 0.8.18 packages/react-router-devtools: @@ -593,11 +591,14 @@ importers: packages/react-store: specifiers: - '@tanstack/loaders': workspace:* + '@tanstack/store': workspace:* + '@types/use-sync-external-store': ^0.0.3 use-sync-external-store: ^1.2.0 dependencies: - '@tanstack/loaders': link:../loaders + '@tanstack/store': link:../store use-sync-external-store: 1.2.0 + devDependencies: + '@types/use-sync-external-store': 0.0.3 packages/router: specifiers: