From b56a0b674c73ba21b2ede2ae60be4c1d3ef87484 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sat, 27 Jan 2024 19:53:36 +0000 Subject: [PATCH 01/14] chore: install tanstack/react-router --- package.json | 2 ++ vite.config.ts | 2 ++ yarn.lock | 88 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cb36dc1..09d9d9f 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "@linaria/core": "6.0.0", "@linaria/react": "6.0.0", "@reduxjs/toolkit": "^2.1.0", + "@tanstack/react-router": "^1.14.0", + "@tanstack/router-vite-plugin": "^1.12.16", "dayjs": "^1.11.10", "plausible-tracker": "^0.3.8", "react": "^18.2.0", diff --git a/vite.config.ts b/vite.config.ts index 79d269d..9c4e5ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,5 @@ // @ts-nocheck +import { TanStackRouterVite } from "@tanstack/router-vite-plugin"; import react from "@vitejs/plugin-react"; import { injectManifest } from "rollup-plugin-workbox"; import { defineConfig } from "vite"; @@ -23,6 +24,7 @@ export default defineConfig({ babelrc: true }), tsconfigPaths(), + TanStackRouterVite(), linaria({ sourceMap: isDev, extension: ".scss", diff --git a/yarn.lock b/yarn.lock index 5d13fb6..f49840c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2612,6 +2612,67 @@ __metadata: languageName: node linkType: hard +"@tanstack/history@npm:1.12.16": + version: 1.12.16 + resolution: "@tanstack/history@npm:1.12.16" + checksum: bb555aab21d71892fb58aa9e3be2150068f24ca69995ecd7c264e92ee63bf4e5e4bbfa201ceba0028324562099ccb649403c2b15bbbeced7509140b35f874a6e + languageName: node + linkType: hard + +"@tanstack/react-router@npm:^1.14.0": + version: 1.14.0 + resolution: "@tanstack/react-router@npm:1.14.0" + dependencies: + "@tanstack/history": 1.12.16 + "@tanstack/react-store": ^0.2.1 + tiny-invariant: ^1.3.1 + tiny-warning: ^1.0.3 + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 3a3de3491db2fff5530e295c283529dae6c5891ef5b164604bcca5ac66b4c861c3c3bfad59872008cf781f7f43d14d56bbce4d3c9d5d9b1a4bd6554b347b7fcf + languageName: node + linkType: hard + +"@tanstack/react-store@npm:^0.2.1": + version: 0.2.1 + resolution: "@tanstack/react-store@npm:0.2.1" + dependencies: + "@tanstack/store": 0.1.3 + use-sync-external-store: ^1.2.0 + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 44e1ff82cee4d963eb926f5976f938da089446e8613315117629b2c99f0dca35259529a1203e88d3507ede6cc44dc53cf2913a25f45e2210f78ee9603d20b6e1 + languageName: node + linkType: hard + +"@tanstack/router-generator@npm:1.12.16": + version: 1.12.16 + resolution: "@tanstack/router-generator@npm:1.12.16" + dependencies: + prettier: ^3.1.1 + zod: ^3.22.4 + checksum: f2b3612a6324b40a503133bdf289620f75680d558fb1a67b137391ddb20702d932420ceb279c7c4327511770cf9bf842e17989a32f43b4bb9355f10bcefb652a + languageName: node + linkType: hard + +"@tanstack/router-vite-plugin@npm:^1.12.16": + version: 1.12.16 + resolution: "@tanstack/router-vite-plugin@npm:1.12.16" + dependencies: + "@tanstack/router-generator": 1.12.16 + checksum: cf65960aaf090ca64a93168cd162ab479e769725282054aaae7e2dff31f375ed29b7a04da823fc7ebee9b2c946467a7ded6a13ce03466999fcacf14462dbc90e + languageName: node + linkType: hard + +"@tanstack/store@npm:0.1.3": + version: 0.1.3 + resolution: "@tanstack/store@npm:0.1.3" + checksum: 47f6a6d2e0cd7e896eb838f25081761e3fbbc5988b826f54136c23dd00971247d0493117efa4fe3c870df6598c1d48e1d47d1dab959a9b911bae5935dcc01f4b + languageName: node + linkType: hard + "@testing-library/dom@npm:^9.0.0": version: 9.3.4 resolution: "@testing-library/dom@npm:9.3.4" @@ -3171,6 +3232,8 @@ __metadata: "@linaria/react": 6.0.0 "@reduxjs/toolkit": ^2.1.0 "@rollup/pluginutils": ^5.1.0 + "@tanstack/react-router": ^1.14.0 + "@tanstack/router-vite-plugin": ^1.12.16 "@testing-library/jest-dom": ^6.3.0 "@testing-library/react": ^14.1.2 "@testing-library/react-hooks": ^8.0.1 @@ -6169,7 +6232,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.2.4": +"prettier@npm:3.2.4, prettier@npm:^3.1.1": version: 3.2.4 resolution: "prettier@npm:3.2.4" bin: @@ -7193,6 +7256,20 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.1": + version: 1.3.1 + resolution: "tiny-invariant@npm:1.3.1" + checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c + languageName: node + linkType: hard + +"tiny-warning@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-warning@npm:1.0.3" + checksum: da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71 + languageName: node + linkType: hard + "tinybench@npm:^2.5.1": version: 2.6.0 resolution: "tinybench@npm:2.6.0" @@ -7470,7 +7547,7 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.0.0": +"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" peerDependencies: @@ -7984,3 +8061,10 @@ __metadata: checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 languageName: node linkType: hard + +"zod@npm:^3.22.4": + version: 3.22.4 + resolution: "zod@npm:3.22.4" + checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f + languageName: node + linkType: hard From b945306c61864c9bb33b92c9d8c0023be0b7da63 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sat, 27 Jan 2024 21:07:59 +0000 Subject: [PATCH 02/14] feat: log env in dev --- src/lib/global/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/global/index.ts b/src/lib/global/index.ts index c4480fb..ae60b41 100644 --- a/src/lib/global/index.ts +++ b/src/lib/global/index.ts @@ -18,12 +18,19 @@ import { versionString } from "./version"; export const globalInit = () => { // Inject global functions. - injectLog(); injectEnv(); + injectLog(); injectFeature(); - if (!env.isProduction) injectDevTools(); - if (!env.isTesting) console.log(`%c${versionString()}`, "font-size: 1.1em;"); + // Log app name+version. Hide for tests to reduce clutter in console. + if (!env.isTesting) { + console.log(`%c${versionString()}`, "font-size: 1.1em;padding: 1rem 0;"); + } + + if (env.isDevelopment) { + injectDevTools(); + console.log("env", env); + } }; globalInit(); From 2a3f03d3d7d3c73a40f4a0a17b83147dbb07e8b8 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sat, 27 Jan 2024 22:25:03 +0000 Subject: [PATCH 03/14] feat: implement file-based tanstack/react-router --- package.json | 1 - src/App.tsx | 38 ++++++++++---- src/index.tsx | 13 +---- src/routeTree.gen.ts | 51 +++++++++++++++++++ src/view/routes/__root.tsx | 34 +++++++++++++ .../{screens/Home.tsx => routes/index.tsx} | 21 +++----- src/view/routes/user.$userId.tsx | 35 +++++++++++++ src/view/routes/user.tsx | 24 +++++++++ src/view/screens/NotFound.tsx | 11 ---- src/view/screens/index.ts | 2 - vite.config.ts | 4 +- yarn.lock | 32 ------------ 12 files changed, 182 insertions(+), 84 deletions(-) create mode 100644 src/routeTree.gen.ts create mode 100644 src/view/routes/__root.tsx rename src/view/{screens/Home.tsx => routes/index.tsx} (73%) create mode 100644 src/view/routes/user.$userId.tsx create mode 100644 src/view/routes/user.tsx delete mode 100644 src/view/screens/NotFound.tsx delete mode 100644 src/view/screens/index.ts diff --git a/package.json b/package.json index 09d9d9f..a90795c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-redux": "^9.1.0", - "react-router-dom": "^6.21.3", "redux-logger": "^3.0.6", "sass": "^1.70.0" }, diff --git a/src/App.tsx b/src/App.tsx index 1edda5f..8e515b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,35 @@ -import { Routes, Route } from "react-router-dom"; +import { ErrorComponent, RouterProvider, createRouter } from "@tanstack/react-router"; +import { Provider as Redux } from "react-redux"; -import { Home, NotFound } from "view/screens"; +import store from "state/index"; + +import { HaloProvider } from "view/components"; + +import { routeTree } from "./routeTree.gen"; + +const router = createRouter({ + routeTree, + defaultPendingComponent: () =>
Loading...
, + defaultErrorComponent: ({ error }) => , + // context: { + // auth: undefined! // We'll inject this when we render + // }, + defaultPreload: "intent" +}); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} function App() { return ( -
- - } /> - - {/* 404 */} - } /> - -
+ + + + + ); } diff --git a/src/index.tsx b/src/index.tsx index 067e84c..9b63969 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,9 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { Provider as Redux } from "react-redux"; -import { BrowserRouter as Router } from "react-router-dom"; -import store from "state"; import { plausibleBootstrap } from "lib/analytics"; import "lib/styles/global/index.scss"; -import { HaloProvider } from "view/components"; - import * as serviceWorkerRegistration from "./serviceWorkerRegistration"; import App from "./App"; @@ -19,13 +14,7 @@ const root = createRoot(rootElement as HTMLElement); root.render( - - - - - - - + ); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts new file mode 100644 index 0000000..7514742 --- /dev/null +++ b/src/routeTree.gen.ts @@ -0,0 +1,51 @@ +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './view/routes/__root' +import { Route as UserImport } from './view/routes/user' +import { Route as IndexImport } from './view/routes/index' +import { Route as UserUserIdImport } from './view/routes/user.$userId' + +// Create/Update Routes + +const UserRoute = UserImport.update({ + path: '/user', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const UserUserIdRoute = UserUserIdImport.update({ + path: '/$userId', + getParentRoute: () => UserRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/user': { + preLoaderRoute: typeof UserImport + parentRoute: typeof rootRoute + } + '/user/$userId': { + preLoaderRoute: typeof UserUserIdImport + parentRoute: typeof UserImport + } + } +} + +// Create and export the route tree + +export const routeTree = rootRoute.addChildren([ + IndexRoute, + UserRoute.addChildren([UserUserIdRoute]), +]) diff --git a/src/view/routes/__root.tsx b/src/view/routes/__root.tsx new file mode 100644 index 0000000..7f83cad --- /dev/null +++ b/src/view/routes/__root.tsx @@ -0,0 +1,34 @@ +import { Outlet, rootRouteWithContext, useRouterState } from "@tanstack/react-router"; + +import { Icon } from "view/components"; + +function RouterSpinner() { + const isLoading = useRouterState({ select: (s) => s.status === "pending" }); + return isLoading ? : null; +} + +export const Route = rootRouteWithContext()({ + component: RootRoute, + notFoundComponent: NotFoundRoute +}); + +function RootRoute() { + return ( +
+ {/* Show a global spinner when the router is transitioning */} + + {/* Render our first route match */} + +
+ ); +} + +function NotFoundRoute() { + return ( +
+

+ 404, Page not found :( +

+
+ ); +} diff --git a/src/view/screens/Home.tsx b/src/view/routes/index.tsx similarity index 73% rename from src/view/screens/Home.tsx rename to src/view/routes/index.tsx index 66a880f..188cf4f 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/routes/index.tsx @@ -1,24 +1,15 @@ -import { useState } from "react"; import { css } from "@linaria/core"; +import { createFileRoute } from "@tanstack/react-router"; import theme from "lib/styles"; -import { countIncrement } from "state/actions"; -import { useDispatch, useSelector, useInterval } from "lib/hooks"; import { Fullscreen, Stack, Waves } from "view/components"; -export const Home = () => { - const dispatch = useDispatch(); - const count = useSelector((state) => state.count.current); - - const [data, setData] = useState([...Array(8)].map((e, i) => ({ id: String(i) }))); - - useInterval(() => { - if (feature("timerIncrement")) { - dispatch(countIncrement(0.1)); - } - }, 1000); +export const Route = createFileRoute("/")({ + component: IndexRoute +}); +function IndexRoute() { return ( { ); -}; +} // This will get compiled at build time into a css file. // Why? - Performance is *greatly* improved over something like styled-components which compiles at run time! diff --git a/src/view/routes/user.$userId.tsx b/src/view/routes/user.$userId.tsx new file mode 100644 index 0000000..e89b8a6 --- /dev/null +++ b/src/view/routes/user.$userId.tsx @@ -0,0 +1,35 @@ +import { css } from "@linaria/core"; +import { Link, createFileRoute } from "@tanstack/react-router"; + +import theme from "lib/styles"; + +import { Stack } from "view/components"; + +export const Route = createFileRoute("/user/$userId")({ + component: UserRoute, + parseParams: (params) => ({ + userId: Number(params.userId) + }), + stringifyParams: ({ userId }) => ({ userId: `${userId}` }) +}); + +function UserRoute() { + const userId = Route.useParams({ select: (p) => p.userId }); + + return ( + +

User {userId}

+ Back +
+ ); +} + +const header = css` + ${theme} + text-transform: lowercase; + font-style: italic; + font-size: 10rem; + font-weight: thin; + color: $blue-100; + text-shadow: shadowBlock($blue-400); +`; diff --git a/src/view/routes/user.tsx b/src/view/routes/user.tsx new file mode 100644 index 0000000..8e14a84 --- /dev/null +++ b/src/view/routes/user.tsx @@ -0,0 +1,24 @@ +import { Outlet, createFileRoute } from "@tanstack/react-router"; + +import { Fullscreen, Stack } from "view/components"; + +export const Route = createFileRoute("/user")({ + component: UserLayoutComponent +}); + +function UserLayoutComponent() { + return ( + + + {/* Render sub routes */} + + + + ); +} diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx deleted file mode 100644 index a154081..0000000 --- a/src/view/screens/NotFound.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const NotFound = () => { - return ( -
-
-

- 404, Page not found :( -

-
-
- ); -}; diff --git a/src/view/screens/index.ts b/src/view/screens/index.ts deleted file mode 100644 index 44fc4bb..0000000 --- a/src/view/screens/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./Home"; -export * from "./NotFound"; diff --git a/vite.config.ts b/vite.config.ts index 9c4e5ca..dd818bf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,7 +24,9 @@ export default defineConfig({ babelrc: true }), tsconfigPaths(), - TanStackRouterVite(), + TanStackRouterVite({ + routesDirectory: "src/view/routes" + }), linaria({ sourceMap: isDev, extension: ".scss", diff --git a/yarn.lock b/yarn.lock index f49840c..b55932f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2428,13 +2428,6 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.14.2": - version: 1.14.2 - resolution: "@remix-run/router@npm:1.14.2" - checksum: 8be55596f64563de95dea04c147ab67c4e6c9b72277c92d4de257dbb326e2aa16ad2adbdec32eb2c985808c642933ac895220654b8c899e4f4bd38f9fd97ff6e - languageName: node - linkType: hard - "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.1 resolution: "@rollup/plugin-babel@npm:5.3.1" @@ -3263,7 +3256,6 @@ __metadata: react-device-detect: ^2.2.3 react-dom: ^18.2.0 react-redux: ^9.1.0 - react-router-dom: ^6.21.3 redux-logger: ^3.0.6 rollup-plugin-workbox: ^8.1.0 sass: ^1.70.0 @@ -6397,30 +6389,6 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.21.3": - version: 6.21.3 - resolution: "react-router-dom@npm:6.21.3" - dependencies: - "@remix-run/router": 1.14.2 - react-router: 6.21.3 - peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: bcf668aa1428ca3048d7675f1ae3fe983c8792357a0ed59a1414cb1d682227727aad7c44c4222c6774b8d01bf352478845f7f3f668ebfcaa177208ef64e10bdc - languageName: node - linkType: hard - -"react-router@npm:6.21.3": - version: 6.21.3 - resolution: "react-router@npm:6.21.3" - dependencies: - "@remix-run/router": 1.14.2 - peerDependencies: - react: ">=16.8" - checksum: 7e6297d5b67ae07d2e8564e036dbb15ebd706b41c22238940f47eee9b21ffb76d41336803c3b33435f1ebe210226577e32df3838bcbf2cd7c813fa357c0a4fac - languageName: node - linkType: hard - "react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" From c7996560204121152735221eefb4f11f08a29e62 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sat, 27 Jan 2024 23:10:23 +0000 Subject: [PATCH 04/14] wip: testing --- src/App.test.tsx | 24 +++++++++--------------- src/tests/render.tsx | 26 ++++++++++++++------------ src/tests/utils.tsx | 32 ++++++++++++++++++++++++++++++++ src/view/routes/__root.tsx | 15 ++++++++++----- src/view/routes/index.tsx | 2 +- src/view/routes/user.$userId.tsx | 15 +-------------- 6 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 1f5f08c..287d75d 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -2,26 +2,13 @@ import { screen } from "@testing-library/react"; import { getStyle, render, select } from "tests"; import { expect, test } from "vitest"; -import { Home } from "view/screens"; - -import App from "./App"; +import { IndexRoute } from "./view/routes/index"; test("renders app", () => { - const r = render(); + const r = render(); const linkElement = screen.getByText(/Template react app with batteries included/i); expect(linkElement).toBeInTheDocument(); -}); - -test("renders 404 page", () => { - render(, "/wow"); - - const linkElement = screen.getByText(/Page not found/i); - expect(linkElement).toBeInTheDocument(); -}); - -test("renders Home page", () => { - const r = render(); // Test @linaria styles are working. // Worth doing for a few components to test the `theme` object imports properly. @@ -31,3 +18,10 @@ test("renders Home page", () => { "0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1" ); }); + +// test("renders 404 page", () => { +// render(, "/wow"); + +// const linkElement = screen.getByText(/Page not found/i); +// expect(linkElement).toBeInTheDocument(); +// }); diff --git a/src/tests/render.tsx b/src/tests/render.tsx index ba39cd8..36c007a 100644 --- a/src/tests/render.tsx +++ b/src/tests/render.tsx @@ -1,21 +1,23 @@ -import { ReactElement, JSXElementConstructor } from "react"; -import { Provider } from "react-redux"; -import { MemoryRouter } from "react-router-dom"; +import { RouterProvider } from "@tanstack/react-router"; import { render as reactRender } from "@testing-library/react"; - +import { JSXElementConstructor, ReactElement } from "react"; +import { Provider } from "react-redux"; import store from "state"; -type element = ReactElement>; +import { createTestRouter } from "./utils"; + +export type Element = ReactElement>; -type children = { - children: element; +type Children = { + children: Element; }; -export const render = (ui: element, route = "") => { - const Wrapper = ({ children }: children) => { +export const render = (ui: Element, route = "") => { + const Wrapper = ({ children }: Children) => { return ( - {children} + {/* */} + {children} ); }; @@ -23,8 +25,8 @@ export const render = (ui: element, route = "") => { return reactRender(ui, { wrapper: Wrapper }); }; -export const renderBasic = (ui: element) => { - const Wrapper = ({ children }: children) => { +export const renderBasic = (ui: Element) => { + const Wrapper = ({ children }: Children) => { return {children}; }; diff --git a/src/tests/utils.tsx b/src/tests/utils.tsx index 774b227..5cca067 100644 --- a/src/tests/utils.tsx +++ b/src/tests/utils.tsx @@ -1,5 +1,37 @@ +import { + Outlet, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter +} from "@tanstack/react-router"; + import { render } from "./render"; +/** + * Create test router from element. + * + * https://github.com/TanStack/router/discussions/604 + */ +export const createTestRouter = (element: any) => { + const rootRoute = createRootRoute({ + component: Outlet + }); + + const componentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: () => element + }); + + const router = createRouter({ + routeTree: rootRoute.addChildren([componentRoute]), + history: createMemoryHistory() + }); + + return router; +}; + /** * Shorthand for `document.querySelector`. * diff --git a/src/view/routes/__root.tsx b/src/view/routes/__root.tsx index 7f83cad..22369c9 100644 --- a/src/view/routes/__root.tsx +++ b/src/view/routes/__root.tsx @@ -2,11 +2,11 @@ import { Outlet, rootRouteWithContext, useRouterState } from "@tanstack/react-ro import { Icon } from "view/components"; -function RouterSpinner() { - const isLoading = useRouterState({ select: (s) => s.status === "pending" }); - return isLoading ? : null; -} - +/** + * `@tanstack/react-router` file-based routing. + * + * https://tanstack.com/router/latest/docs/framework/react/overview + */ export const Route = rootRouteWithContext()({ component: RootRoute, notFoundComponent: NotFoundRoute @@ -23,6 +23,11 @@ function RootRoute() { ); } +function RouterSpinner() { + const isLoading = useRouterState({ select: (s) => s.status === "pending" }); + return isLoading ? : null; +} + function NotFoundRoute() { return (
diff --git a/src/view/routes/index.tsx b/src/view/routes/index.tsx index 188cf4f..34d925d 100644 --- a/src/view/routes/index.tsx +++ b/src/view/routes/index.tsx @@ -9,7 +9,7 @@ export const Route = createFileRoute("/")({ component: IndexRoute }); -function IndexRoute() { +export function IndexRoute() { return ( -

User {userId}

+

User {userId}

Back
); } - -const header = css` - ${theme} - text-transform: lowercase; - font-style: italic; - font-size: 10rem; - font-weight: thin; - color: $blue-100; - text-shadow: shadowBlock($blue-400); -`; From fef0a7567232dae2a560ba2b33cea31a3160a8ef Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sat, 27 Jan 2024 23:14:29 +0000 Subject: [PATCH 05/14] fix: update linaria rollup config --- config/linaria-rollup.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/config/linaria-rollup.ts b/config/linaria-rollup.ts index 5244746..6d9d134 100644 --- a/config/linaria-rollup.ts +++ b/config/linaria-rollup.ts @@ -159,11 +159,29 @@ export default function wywInJS({ let { cssText, dependencies } = result; - if (!cssText) return; + // Heads up, there are three cases: + // 1. cssText is undefined, it means that file was not transformed + // 2. cssText is empty, it means that file was transformed, but it does not contain any styles + // 3. cssText is not empty, it means that file was transformed and it contains styles + + if (typeof cssText === "undefined") { + return; + } + + if (cssText === "") { + /* eslint-disable-next-line consistent-return */ + return { + code: result.code, + map: result.sourceMap + }; + } + dependencies ??= []; const slug = slugify(cssText); + // @IMPORTANT: We *need* to use `.scss` extension here. + // This tiny change is the only difference between this file and using `https://github.com/Anber/wyw-in-js/blob/main/packages/vite/src/index.ts`. const cssFilename = path .normalize(`${id.replace(/\.[jt]sx?$/, "")}_${slug}.scss`) .replace(/\\/g, path.posix.sep); From 04b35cab5ea1f613775811d72a2aca18637c2526 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sun, 28 Jan 2024 19:43:45 +0000 Subject: [PATCH 06/14] feat: tan stack router devtools --- package.json | 1 + src/view/routes/__root.tsx | 3 +++ yarn.lock | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/package.json b/package.json index a90795c..1a02fd2 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@linaria/react": "6.0.0", "@reduxjs/toolkit": "^2.1.0", "@tanstack/react-router": "^1.14.0", + "@tanstack/router-devtools": "^1.14.2", "@tanstack/router-vite-plugin": "^1.12.16", "dayjs": "^1.11.10", "plausible-tracker": "^0.3.8", diff --git a/src/view/routes/__root.tsx b/src/view/routes/__root.tsx index 22369c9..8ab8954 100644 --- a/src/view/routes/__root.tsx +++ b/src/view/routes/__root.tsx @@ -1,4 +1,5 @@ import { Outlet, rootRouteWithContext, useRouterState } from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/router-devtools"; import { Icon } from "view/components"; @@ -19,6 +20,8 @@ function RootRoute() { {/* Render our first route match */} + {/* Router dev tools */} + {env.isDevelopment && }
); } diff --git a/yarn.lock b/yarn.lock index b55932f..01333cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1861,6 +1861,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.21.0": + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 6bbebe8d27c0c2dd275d1ac197fc1a6c00e18dab68cc7aaff0adc3195b45862bae9c4cc58975629004b0213955b2ed91e99eccb3d9b39cabea246c657323d667 + languageName: node + linkType: hard + "@babel/template@npm:^7.22.15": version: 7.22.15 resolution: "@babel/template@npm:7.22.15" @@ -2612,6 +2621,21 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-router@npm:1.14.2": + version: 1.14.2 + resolution: "@tanstack/react-router@npm:1.14.2" + dependencies: + "@tanstack/history": 1.12.16 + "@tanstack/react-store": ^0.2.1 + tiny-invariant: ^1.3.1 + tiny-warning: ^1.0.3 + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 233440617e6c239943cbcd276f0bc975d27f13dcbcf9bf9b9b1cbe503ad4c9633901027853d8aaae4a90c18a58d0af40963603d0a4500deabae8001fdbb76a24 + languageName: node + linkType: hard + "@tanstack/react-router@npm:^1.14.0": version: 1.14.0 resolution: "@tanstack/react-router@npm:1.14.0" @@ -2640,6 +2664,19 @@ __metadata: languageName: node linkType: hard +"@tanstack/router-devtools@npm:^1.14.2": + version: 1.14.2 + resolution: "@tanstack/router-devtools@npm:1.14.2" + dependencies: + "@tanstack/react-router": 1.14.2 + date-fns: ^2.29.1 + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 7cb9b29488c1cc08c0f578ed4b31ea0feef228be078f3fcaf09f5c93c621a3034a37992a5836549a0388361d492e99b1649884df052ab8af66f12f74d234aecd + languageName: node + linkType: hard + "@tanstack/router-generator@npm:1.12.16": version: 1.12.16 resolution: "@tanstack/router-generator@npm:1.12.16" @@ -3226,6 +3263,7 @@ __metadata: "@reduxjs/toolkit": ^2.1.0 "@rollup/pluginutils": ^5.1.0 "@tanstack/react-router": ^1.14.0 + "@tanstack/router-devtools": ^1.14.2 "@tanstack/router-vite-plugin": ^1.12.16 "@testing-library/jest-dom": ^6.3.0 "@testing-library/react": ^14.1.2 @@ -3956,6 +3994,15 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.29.1": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": ^7.21.0 + checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4 + languageName: node + linkType: hard + "dayjs@npm:^1.11.10": version: 1.11.10 resolution: "dayjs@npm:1.11.10" @@ -6485,6 +6532,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regenerator-transform@npm:^0.15.1": version: 0.15.1 resolution: "regenerator-transform@npm:0.15.1" From 26782d1e101ee5e0149d8b19802a33d15cbe60c7 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sun, 28 Jan 2024 20:35:10 +0000 Subject: [PATCH 07/14] feat: parseEnv util to parse string into a primitive --- src/lib/global/env.ts | 14 ++++++++------ src/lib/global/utils.ts | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/lib/global/env.ts b/src/lib/global/env.ts index 4047a11..221460b 100644 --- a/src/lib/global/env.ts +++ b/src/lib/global/env.ts @@ -1,4 +1,4 @@ -import { setGlobalValue } from "./utils"; +import { parseEnv, setGlobalValue } from "./utils"; /** * Environment variables. @@ -11,15 +11,17 @@ export const env = Object.freeze({ appVersion: import.meta.env.VITE_VERSION, gitBranch: import.meta.env.VITE_GIT_BRANCH, gitCommitHash: import.meta.env.VITE_GIT_COMMIT, - mode: import.meta.env.MODE, - isDevelopment: import.meta.env.MODE === "development", - isProduction: import.meta.env.MODE === "production", - isTesting: import.meta.env.MODE === "test" || import.meta.env.MODE === "testing", + showDevTools: parseEnv(import.meta.env.VITE_SHOW_DEVTOOLS) || true, plausible: { - enable: import.meta.env.VITE_PLAUSIBLE_ENABLE === "true", + enable: parseEnv(import.meta.env.VITE_PLAUSIBLE_ENABLE), domain: import.meta.env.VITE_PLAUSIBLE_DOMAIN, apiHost: import.meta.env.VITE_PLAUSIBLE_API_HOST }, + mode: import.meta.env.MODE, + isDevelopment: import.meta.env.MODE === "development", + isProduction: import.meta.env.MODE === "production", + isTesting: import.meta.env.MODE === "test" || import.meta.env.MODE === "testing", + isStaging: import.meta.env.MODE === "stage" || import.meta.env.MODE === "staging", // Features timerIncrement: import.meta.env.VITE_FEATURE_INCREMENT, someOtherFeature: false diff --git a/src/lib/global/utils.ts b/src/lib/global/utils.ts index 4a1f3c3..1dd4b59 100644 --- a/src/lib/global/utils.ts +++ b/src/lib/global/utils.ts @@ -25,3 +25,21 @@ export const setGlobalValue = (key: string, value: any) => { }; export const $global = getGlobal(); + +/** + * Parse string environment variable into a primitive. + * + * @exmaple `parseEnv("VITE_FEATURE_ENABLED") => true` + */ +export const parseEnv = (value: any, isJson = false) => { + if (value === "true") return true; + if (value === "false") return false; + if (value === "undefined") return undefined; + if (value === "null") return null; + if (isJson) { + try { + return JSON.parse(value); + } catch (e) {} + } + return value; +}; From fccaa2d014169840f8cc2253f1c1a3dd42fcf8e6 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sun, 28 Jan 2024 20:35:33 +0000 Subject: [PATCH 08/14] fix: lazy import router devtools --- src/view/routes/__root.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/view/routes/__root.tsx b/src/view/routes/__root.tsx index 8ab8954..8d2ebd1 100644 --- a/src/view/routes/__root.tsx +++ b/src/view/routes/__root.tsx @@ -1,8 +1,17 @@ import { Outlet, rootRouteWithContext, useRouterState } from "@tanstack/react-router"; -import { TanStackRouterDevtools } from "@tanstack/router-devtools"; +import { lazy } from "react"; import { Icon } from "view/components"; +const TanStackRouterDevtools = + env.isDevelopment && env.showDevTools + ? lazy(() => + import("@tanstack/router-devtools").then((res) => ({ + default: res.TanStackRouterDevtools + })) + ) + : () => null; + /** * `@tanstack/react-router` file-based routing. * @@ -21,7 +30,7 @@ function RootRoute() { {/* Render our first route match */} {/* Router dev tools */} - {env.isDevelopment && } + ); } From 827bdf1decba39543680e4f05f5e609b334df734 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sun, 28 Jan 2024 20:51:15 +0000 Subject: [PATCH 09/14] fix: link param typing and lazy load --- src/App.tsx | 5 +--- src/routeTree.gen.ts | 29 +++++++++++++-------- src/view/routes/user.$userId.lazy.tsx | 18 +++++++++++++ src/view/routes/user.$userId.tsx | 22 ---------------- src/view/routes/{user.tsx => user.lazy.tsx} | 7 +++-- 5 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 src/view/routes/user.$userId.lazy.tsx delete mode 100644 src/view/routes/user.$userId.tsx rename src/view/routes/{user.tsx => user.lazy.tsx} (63%) diff --git a/src/App.tsx b/src/App.tsx index 8e515b6..e1f43ec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,11 +9,8 @@ import { routeTree } from "./routeTree.gen"; const router = createRouter({ routeTree, - defaultPendingComponent: () =>
Loading...
, + defaultPendingComponent: () => null, defaultErrorComponent: ({ error }) => , - // context: { - // auth: undefined! // We'll inject this when we render - // }, defaultPreload: "intent" }); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 7514742..f9d78eb 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -1,28 +1,35 @@ // This file is auto-generated by TanStack Router +import { createFileRoute } from '@tanstack/react-router' + // Import Routes import { Route as rootRoute } from './view/routes/__root' -import { Route as UserImport } from './view/routes/user' import { Route as IndexImport } from './view/routes/index' -import { Route as UserUserIdImport } from './view/routes/user.$userId' + +// Create Virtual Routes + +const UserLazyImport = createFileRoute('/user')() +const UserUserIdLazyImport = createFileRoute('/user/$userId')() // Create/Update Routes -const UserRoute = UserImport.update({ +const UserLazyRoute = UserLazyImport.update({ path: '/user', getParentRoute: () => rootRoute, -} as any) +} as any).lazy(() => import('./view/routes/user.lazy').then((d) => d.Route)) const IndexRoute = IndexImport.update({ path: '/', getParentRoute: () => rootRoute, } as any) -const UserUserIdRoute = UserUserIdImport.update({ +const UserUserIdLazyRoute = UserUserIdLazyImport.update({ path: '/$userId', - getParentRoute: () => UserRoute, -} as any) + getParentRoute: () => UserLazyRoute, +} as any).lazy(() => + import('./view/routes/user.$userId.lazy').then((d) => d.Route), +) // Populate the FileRoutesByPath interface @@ -33,12 +40,12 @@ declare module '@tanstack/react-router' { parentRoute: typeof rootRoute } '/user': { - preLoaderRoute: typeof UserImport + preLoaderRoute: typeof UserLazyImport parentRoute: typeof rootRoute } '/user/$userId': { - preLoaderRoute: typeof UserUserIdImport - parentRoute: typeof UserImport + preLoaderRoute: typeof UserUserIdLazyImport + parentRoute: typeof UserLazyImport } } } @@ -47,5 +54,5 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, - UserRoute.addChildren([UserUserIdRoute]), + UserLazyRoute.addChildren([UserUserIdLazyRoute]), ]) diff --git a/src/view/routes/user.$userId.lazy.tsx b/src/view/routes/user.$userId.lazy.tsx new file mode 100644 index 0000000..322b47a --- /dev/null +++ b/src/view/routes/user.$userId.lazy.tsx @@ -0,0 +1,18 @@ +import { Link, createLazyFileRoute } from "@tanstack/react-router"; + +import { Stack } from "view/components"; + +export const Route = createLazyFileRoute("/user/$userId")({ + component: UserRoute +}); + +function UserRoute() { + const { userId } = Route.useParams(); + + return ( + +

User {userId}

+ Back +
+ ); +} diff --git a/src/view/routes/user.$userId.tsx b/src/view/routes/user.$userId.tsx deleted file mode 100644 index 1eabdd9..0000000 --- a/src/view/routes/user.$userId.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Link, createFileRoute } from "@tanstack/react-router"; - -import { Stack } from "view/components"; - -export const Route = createFileRoute("/user/$userId")({ - component: UserRoute, - parseParams: (params) => ({ - userId: Number(params.userId) - }), - stringifyParams: ({ userId }) => ({ userId: `${userId}` }) -}); - -function UserRoute() { - const userId = Route.useParams({ select: (p) => p.userId }); - - return ( - -

User {userId}

- Back -
- ); -} diff --git a/src/view/routes/user.tsx b/src/view/routes/user.lazy.tsx similarity index 63% rename from src/view/routes/user.tsx rename to src/view/routes/user.lazy.tsx index 8e14a84..b7d57da 100644 --- a/src/view/routes/user.tsx +++ b/src/view/routes/user.lazy.tsx @@ -1,8 +1,8 @@ -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router"; import { Fullscreen, Stack } from "view/components"; -export const Route = createFileRoute("/user")({ +export const Route = createLazyFileRoute("/user")({ component: UserLayoutComponent }); @@ -16,6 +16,9 @@ function UserLayoutComponent() { padding="1rem 2rem" style={{ height: "70vh" }} > + + User 123 + {/* Render sub routes */} From 0342270ce582ecbe20b9ab15d1d6499eb192969d Mon Sep 17 00:00:00 2001 From: hmerritt Date: Sun, 28 Jan 2024 23:10:42 +0000 Subject: [PATCH 10/14] fix: allow logStore to be mutable --- src/lib/global/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/global/log.ts b/src/lib/global/log.ts index b51ad2e..e703a89 100644 --- a/src/lib/global/log.ts +++ b/src/lib/global/log.ts @@ -141,7 +141,7 @@ export const debugn = (namespace: string, logLevel: any, ...args: any[]) => { }; export const injectLog = () => { - setGlobalValue("logStore", new LogStore()); + $global.logStore = new LogStore(); setGlobalValue("log", log); setGlobalValue("logn", logn); setGlobalValue("debug", debug); From cee95956bcda42c9a1f8c5070f80f3c75c34bdbb Mon Sep 17 00:00:00 2001 From: hmerritt Date: Tue, 30 Jan 2024 21:17:58 +0000 Subject: [PATCH 11/14] fix: react router tests --- package.json | 6 ++-- src/App.test.tsx | 38 ++++++++++++++--------- src/App.tsx | 4 +-- src/lib/styles/index.test.tsx | 49 ++++++++++++++++++------------ src/tests/render.tsx | 5 ++- src/tests/utils.tsx | 8 +++-- src/view/routes/__root.tsx | 10 ++++-- yarn.lock | 57 +++++++++++++---------------------- 8 files changed, 94 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index 1a02fd2..35b698f 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@linaria/core": "6.0.0", "@linaria/react": "6.0.0", "@reduxjs/toolkit": "^2.1.0", - "@tanstack/react-router": "^1.14.0", - "@tanstack/router-devtools": "^1.14.2", - "@tanstack/router-vite-plugin": "^1.12.16", + "@tanstack/react-router": "^1.15.5", + "@tanstack/router-devtools": "^1.15.5", + "@tanstack/router-vite-plugin": "^1.15.4", "dayjs": "^1.11.10", "plausible-tracker": "^0.3.8", "react": "^18.2.0", diff --git a/src/App.test.tsx b/src/App.test.tsx index 287d75d..31831af 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,26 +1,36 @@ -import { screen } from "@testing-library/react"; -import { getStyle, render, select } from "tests"; +import { screen, waitFor } from "@testing-library/react"; +import { getStyle, render, renderBasic, select } from "tests"; import { expect, test } from "vitest"; +import App from "./App"; +import { NotFoundRoute } from "./view/routes/__root"; import { IndexRoute } from "./view/routes/index"; -test("renders app", () => { - const r = render(); +test("renders app", async () => { + const r = render(); - const linkElement = screen.getByText(/Template react app with batteries included/i); + await waitFor(() => r.getByText(/Template react app with batteries included/i)); + const linkElement = r.getByText(/Template react app with batteries included/i); expect(linkElement).toBeInTheDocument(); - - // Test @linaria styles are working. - // Worth doing for a few components to test the `theme` object imports properly. - const style = getStyle(select(r, "h1")); - expect(style.color).toBe("#bee3f8"); - expect(style.textShadow).toBe( - "0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1" - ); }); +// test("renders home", () => { +// const r = renderBasic(); + +// const linkElement = screen.getByText(/Template react app with batteries included/i); +// expect(linkElement).toBeInTheDocument(); + +// // Test @linaria styles are working. +// // Worth doing for a few components to test the `theme` object imports properly. +// const style = getStyle(select(r, "h1")); +// expect(style.color).toBe("#bee3f8"); +// expect(style.textShadow).toBe( +// "0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1" +// ); +// }); + // test("renders 404 page", () => { -// render(, "/wow"); +// renderBasic(); // const linkElement = screen.getByText(/Page not found/i); // expect(linkElement).toBeInTheDocument(); diff --git a/src/App.tsx b/src/App.tsx index e1f43ec..23a24d0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,9 +9,9 @@ import { routeTree } from "./routeTree.gen"; const router = createRouter({ routeTree, + defaultPreload: "intent", defaultPendingComponent: () => null, - defaultErrorComponent: ({ error }) => , - defaultPreload: "intent" + defaultErrorComponent: ({ error }) => }); declare module "@tanstack/react-router" { diff --git a/src/lib/styles/index.test.tsx b/src/lib/styles/index.test.tsx index 259c66a..b3def99 100644 --- a/src/lib/styles/index.test.tsx +++ b/src/lib/styles/index.test.tsx @@ -1,7 +1,8 @@ +import { waitFor } from "@testing-library/react"; +import { getStyle, render, select } from "tests"; import { describe, expect, test } from "vitest"; import StylesMock from "tests/StylesMock"; -import { getStyle, render, select } from "tests"; /** * Test to see if the styles are being compiled and injected correctly. @@ -12,39 +13,47 @@ import { getStyle, render, select } from "tests"; */ describe("@linaria with theme injection", () => { - test("renders colors", () => { + test("renders colors", async () => { const { container } = render(); - const styleTitle = getStyle(select(container, "h1")); - expect(styleTitle.color).toBe("#38a169"); + await waitFor(() => { + const styleTitle = getStyle(select(container, "h1")); + expect(styleTitle.color).toBe("#38a169"); - const styleSubTitle = getStyle(select(container, "h2")); - expect(styleSubTitle.color).toBe("#dd6b20"); + const styleSubTitle = getStyle(select(container, "h2")); + expect(styleSubTitle.color).toBe("#dd6b20"); + }); }); - test("renders mixins", () => { + test("renders mixins", async () => { const { container } = render(); - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.maxWidth).toBe("567px"); - expect(styleContainer.marginLeft).toBe("auto"); - expect(styleContainer.marginRight).toBe("auto"); - expect(styleContainer.transition).toBe("all, 80ms, ease"); + await waitFor(() => { + const styleContainer = getStyle(select(container, "div")); + expect(styleContainer.maxWidth).toBe("567px"); + expect(styleContainer.marginLeft).toBe("auto"); + expect(styleContainer.marginRight).toBe("auto"); + expect(styleContainer.transition).toBe("all, 80ms, ease"); + }); }); - test("renders shadows", () => { + test("renders shadows", async () => { const { container } = render(); - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.boxShadow).toBe( - "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)" - ); + await waitFor(() => { + const styleContainer = getStyle(select(container, "div")); + expect(styleContainer.boxShadow).toBe( + "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)" + ); + }); }); - test("renders variables", () => { + test("renders variables", async () => { const { container } = render(); - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.width).toBe("5678px"); + await waitFor(() => { + const styleContainer = getStyle(select(container, "div")); + expect(styleContainer.width).toBe("5678px"); + }); }); }); diff --git a/src/tests/render.tsx b/src/tests/render.tsx index 36c007a..2b06759 100644 --- a/src/tests/render.tsx +++ b/src/tests/render.tsx @@ -12,12 +12,11 @@ type Children = { children: Element; }; -export const render = (ui: Element, route = "") => { +export const render = (ui: Element) => { const Wrapper = ({ children }: Children) => { return ( - {/* */} - {children} + ); }; diff --git a/src/tests/utils.tsx b/src/tests/utils.tsx index 5cca067..427764d 100644 --- a/src/tests/utils.tsx +++ b/src/tests/utils.tsx @@ -1,6 +1,6 @@ import { Outlet, - createMemoryHistory, + createHashHistory, createRootRoute, createRoute, createRouter @@ -11,7 +11,11 @@ import { render } from "./render"; /** * Create test router from element. * + * @Note There is little to no documentation on best practices here. + * * https://github.com/TanStack/router/discussions/604 + * https://github.com/TanStack/router/discussions/583 + * https://github.com/TanStack/router/discussions/198 */ export const createTestRouter = (element: any) => { const rootRoute = createRootRoute({ @@ -26,7 +30,7 @@ export const createTestRouter = (element: any) => { const router = createRouter({ routeTree: rootRoute.addChildren([componentRoute]), - history: createMemoryHistory() + history: createHashHistory() }); return router; diff --git a/src/view/routes/__root.tsx b/src/view/routes/__root.tsx index 8d2ebd1..435cef4 100644 --- a/src/view/routes/__root.tsx +++ b/src/view/routes/__root.tsx @@ -1,4 +1,8 @@ -import { Outlet, rootRouteWithContext, useRouterState } from "@tanstack/react-router"; +import { + Outlet, + createRootRouteWithContext, + useRouterState +} from "@tanstack/react-router"; import { lazy } from "react"; import { Icon } from "view/components"; @@ -17,7 +21,7 @@ const TanStackRouterDevtools = * * https://tanstack.com/router/latest/docs/framework/react/overview */ -export const Route = rootRouteWithContext()({ +export const Route = createRootRouteWithContext()({ component: RootRoute, notFoundComponent: NotFoundRoute }); @@ -40,7 +44,7 @@ function RouterSpinner() { return isLoading ? : null; } -function NotFoundRoute() { +export function NotFoundRoute() { return (

diff --git a/yarn.lock b/yarn.lock index 01333cf..7397666 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2621,9 +2621,9 @@ __metadata: languageName: node linkType: hard -"@tanstack/react-router@npm:1.14.2": - version: 1.14.2 - resolution: "@tanstack/react-router@npm:1.14.2" +"@tanstack/react-router@npm:1.15.5, @tanstack/react-router@npm:^1.15.5": + version: 1.15.5 + resolution: "@tanstack/react-router@npm:1.15.5" dependencies: "@tanstack/history": 1.12.16 "@tanstack/react-store": ^0.2.1 @@ -2632,22 +2632,7 @@ __metadata: peerDependencies: react: ">=16" react-dom: ">=16" - checksum: 233440617e6c239943cbcd276f0bc975d27f13dcbcf9bf9b9b1cbe503ad4c9633901027853d8aaae4a90c18a58d0af40963603d0a4500deabae8001fdbb76a24 - languageName: node - linkType: hard - -"@tanstack/react-router@npm:^1.14.0": - version: 1.14.0 - resolution: "@tanstack/react-router@npm:1.14.0" - dependencies: - "@tanstack/history": 1.12.16 - "@tanstack/react-store": ^0.2.1 - tiny-invariant: ^1.3.1 - tiny-warning: ^1.0.3 - peerDependencies: - react: ">=16" - react-dom: ">=16" - checksum: 3a3de3491db2fff5530e295c283529dae6c5891ef5b164604bcca5ac66b4c861c3c3bfad59872008cf781f7f43d14d56bbce4d3c9d5d9b1a4bd6554b347b7fcf + checksum: aab47f31c45e8f168107577ed559cab7b1efe5b158855128dedc54894e32a296d2b4c4d0b4c44fdbe1e6cbc16f4c2c5b329c80aa8940b280e93866466a1a659b languageName: node linkType: hard @@ -2664,35 +2649,35 @@ __metadata: languageName: node linkType: hard -"@tanstack/router-devtools@npm:^1.14.2": - version: 1.14.2 - resolution: "@tanstack/router-devtools@npm:1.14.2" +"@tanstack/router-devtools@npm:^1.15.5": + version: 1.15.5 + resolution: "@tanstack/router-devtools@npm:1.15.5" dependencies: - "@tanstack/react-router": 1.14.2 + "@tanstack/react-router": 1.15.5 date-fns: ^2.29.1 peerDependencies: react: ">=16" react-dom: ">=16" - checksum: 7cb9b29488c1cc08c0f578ed4b31ea0feef228be078f3fcaf09f5c93c621a3034a37992a5836549a0388361d492e99b1649884df052ab8af66f12f74d234aecd + checksum: 2971009a3afcc5b578a5fe9f7604f8f13bfb9524eed256f711c162db0fda18f7f7e0a0238303999215b16c6c6790f260f3ef9dcdd011762bb1cf0b0469ed21c0 languageName: node linkType: hard -"@tanstack/router-generator@npm:1.12.16": - version: 1.12.16 - resolution: "@tanstack/router-generator@npm:1.12.16" +"@tanstack/router-generator@npm:1.15.4": + version: 1.15.4 + resolution: "@tanstack/router-generator@npm:1.15.4" dependencies: prettier: ^3.1.1 zod: ^3.22.4 - checksum: f2b3612a6324b40a503133bdf289620f75680d558fb1a67b137391ddb20702d932420ceb279c7c4327511770cf9bf842e17989a32f43b4bb9355f10bcefb652a + checksum: b9dfdd7144f4934594d9a6779868680d2eea06569f5e23da3ef9d4f19a800042abb9a335c6d5b8ad46347293c5df32984cf4a17435978190522db385ac4dd309 languageName: node linkType: hard -"@tanstack/router-vite-plugin@npm:^1.12.16": - version: 1.12.16 - resolution: "@tanstack/router-vite-plugin@npm:1.12.16" +"@tanstack/router-vite-plugin@npm:^1.15.4": + version: 1.15.4 + resolution: "@tanstack/router-vite-plugin@npm:1.15.4" dependencies: - "@tanstack/router-generator": 1.12.16 - checksum: cf65960aaf090ca64a93168cd162ab479e769725282054aaae7e2dff31f375ed29b7a04da823fc7ebee9b2c946467a7ded6a13ce03466999fcacf14462dbc90e + "@tanstack/router-generator": 1.15.4 + checksum: 3cba78f37a650ff63b8e43f64499c10931b0b05cf331373e82355f528d7a09ce1dbeb33aa994ae94f4058f8cc8026ab5eae00644904413a1b1e0a8d387e7b386 languageName: node linkType: hard @@ -3262,9 +3247,9 @@ __metadata: "@linaria/react": 6.0.0 "@reduxjs/toolkit": ^2.1.0 "@rollup/pluginutils": ^5.1.0 - "@tanstack/react-router": ^1.14.0 - "@tanstack/router-devtools": ^1.14.2 - "@tanstack/router-vite-plugin": ^1.12.16 + "@tanstack/react-router": ^1.15.5 + "@tanstack/router-devtools": ^1.15.5 + "@tanstack/router-vite-plugin": ^1.15.4 "@testing-library/jest-dom": ^6.3.0 "@testing-library/react": ^14.1.2 "@testing-library/react-hooks": ^8.0.1 From 7a26d1f670fc8b03567b900e3a8d398393f64e48 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Tue, 30 Jan 2024 21:48:55 +0000 Subject: [PATCH 12/14] refactor: improve test utils and usability with router tests --- src/App.test.tsx | 41 +++++++++++++-------------- src/lib/styles/index.test.tsx | 53 +++++++++++++++-------------------- src/tests/StylesMock.tsx | 2 +- src/tests/render.tsx | 36 +++++++++++++++++++----- src/tests/setupTests.ts | 6 ++++ src/tests/utils.tsx | 19 +++++++++++-- 6 files changed, 94 insertions(+), 63 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 31831af..6b87230 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,5 +1,5 @@ -import { screen, waitFor } from "@testing-library/react"; -import { getStyle, render, renderBasic, select } from "tests"; +import { screen } from "@testing-library/react"; +import { getStyle, render, select } from "tests"; import { expect, test } from "vitest"; import App from "./App"; @@ -7,31 +7,30 @@ import { NotFoundRoute } from "./view/routes/__root"; import { IndexRoute } from "./view/routes/index"; test("renders app", async () => { - const r = render(); + const r = await render(); - await waitFor(() => r.getByText(/Template react app with batteries included/i)); const linkElement = r.getByText(/Template react app with batteries included/i); expect(linkElement).toBeInTheDocument(); }); -// test("renders home", () => { -// const r = renderBasic(); +test("renders home", async () => { + const r = await render(); -// const linkElement = screen.getByText(/Template react app with batteries included/i); -// expect(linkElement).toBeInTheDocument(); + const linkElement = screen.getByText(/Template react app with batteries included/i); + expect(linkElement).toBeInTheDocument(); -// // Test @linaria styles are working. -// // Worth doing for a few components to test the `theme` object imports properly. -// const style = getStyle(select(r, "h1")); -// expect(style.color).toBe("#bee3f8"); -// expect(style.textShadow).toBe( -// "0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1" -// ); -// }); + // Test @linaria styles are working. + // Worth doing for a few components to test the `theme` object imports properly. + const style = getStyle(select(r.container, "h1")); + expect(style.color).toBe("#bee3f8"); + expect(style.textShadow).toBe( + "0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1" + ); +}); -// test("renders 404 page", () => { -// renderBasic(); +test("renders 404 page", async () => { + await render(); -// const linkElement = screen.getByText(/Page not found/i); -// expect(linkElement).toBeInTheDocument(); -// }); + const linkElement = screen.getByText(/Page not found/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/src/lib/styles/index.test.tsx b/src/lib/styles/index.test.tsx index b3def99..88338d8 100644 --- a/src/lib/styles/index.test.tsx +++ b/src/lib/styles/index.test.tsx @@ -1,5 +1,4 @@ -import { waitFor } from "@testing-library/react"; -import { getStyle, render, select } from "tests"; +import { getStyle, render, select, selectTestId } from "tests"; import { describe, expect, test } from "vitest"; import StylesMock from "tests/StylesMock"; @@ -14,46 +13,38 @@ import StylesMock from "tests/StylesMock"; describe("@linaria with theme injection", () => { test("renders colors", async () => { - const { container } = render(); + const { container } = await render(); - await waitFor(() => { - const styleTitle = getStyle(select(container, "h1")); - expect(styleTitle.color).toBe("#38a169"); + const styleTitle = getStyle(select(container, "h1")); + expect(styleTitle.color).toBe("#38a169"); - const styleSubTitle = getStyle(select(container, "h2")); - expect(styleSubTitle.color).toBe("#dd6b20"); - }); + const styleSubTitle = getStyle(select(container, "h2")); + expect(styleSubTitle.color).toBe("#dd6b20"); }); test("renders mixins", async () => { - const { container } = render(); - - await waitFor(() => { - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.maxWidth).toBe("567px"); - expect(styleContainer.marginLeft).toBe("auto"); - expect(styleContainer.marginRight).toBe("auto"); - expect(styleContainer.transition).toBe("all, 80ms, ease"); - }); + const { container } = await render(); + + const styleContainer = getStyle(selectTestId(container, "StylesMock")); + expect(styleContainer.maxWidth).toBe("567px"); + expect(styleContainer.marginLeft).toBe("auto"); + expect(styleContainer.marginRight).toBe("auto"); + expect(styleContainer.transition).toBe("all, 80ms, ease"); }); test("renders shadows", async () => { - const { container } = render(); - - await waitFor(() => { - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.boxShadow).toBe( - "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)" - ); - }); + const { container } = await render(); + + const styleContainer = getStyle(selectTestId(container, "StylesMock")); + expect(styleContainer.boxShadow).toBe( + "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)" + ); }); test("renders variables", async () => { - const { container } = render(); + const { container } = await render(); - await waitFor(() => { - const styleContainer = getStyle(select(container, "div")); - expect(styleContainer.width).toBe("5678px"); - }); + const styleContainer = getStyle(selectTestId(container, "StylesMock")); + expect(styleContainer.width).toBe("5678px"); }); }); diff --git a/src/tests/StylesMock.tsx b/src/tests/StylesMock.tsx index 5ac039d..b26d6f2 100644 --- a/src/tests/StylesMock.tsx +++ b/src/tests/StylesMock.tsx @@ -9,7 +9,7 @@ import theme from "lib/styles"; */ export const StylesMock = () => ( -
+

Title

Sub Title

diff --git a/src/tests/render.tsx b/src/tests/render.tsx index 2b06759..f484a71 100644 --- a/src/tests/render.tsx +++ b/src/tests/render.tsx @@ -1,5 +1,5 @@ import { RouterProvider } from "@tanstack/react-router"; -import { render as reactRender } from "@testing-library/react"; +import { render as reactRender, waitFor } from "@testing-library/react"; import { JSXElementConstructor, ReactElement } from "react"; import { Provider } from "react-redux"; import store from "state"; @@ -12,22 +12,44 @@ type Children = { children: Element; }; -export const render = (ui: Element) => { +const internalTestId = "__routerHasMounted"; + +export const render = async (ui: Element) => { const Wrapper = ({ children }: Children) => { return ( - + {children}
+ )} + /> ); }; - return reactRender(ui, { wrapper: Wrapper }); + const r = reactRender(ui, { wrapper: Wrapper }); + + await waitFor(() => { + r.getByTestId(internalTestId); + }); + + return r; }; -export const renderBasic = (ui: Element) => { +export const renderBasic = async (ui: Element) => { const Wrapper = ({ children }: Children) => { - return {children}; + return ( + +
{children}
+
+ ); }; - return reactRender(ui, { wrapper: Wrapper }); + const r = reactRender(ui, { wrapper: Wrapper }); + + await waitFor(() => { + r.getByTestId(internalTestId); + }); + + return r; }; diff --git a/src/tests/setupTests.ts b/src/tests/setupTests.ts index 774825f..3e576a4 100644 --- a/src/tests/setupTests.ts +++ b/src/tests/setupTests.ts @@ -1,5 +1,11 @@ import "@testing-library/jest-dom/vitest"; +import { cleanup } from "@testing-library/react"; +import { afterEach } from "vitest"; import globalInit from "lib/global/index"; globalInit(); + +afterEach(() => { + cleanup(); +}); diff --git a/src/tests/utils.tsx b/src/tests/utils.tsx index 427764d..01fee71 100644 --- a/src/tests/utils.tsx +++ b/src/tests/utils.tsx @@ -5,8 +5,7 @@ import { createRoute, createRouter } from "@tanstack/react-router"; - -import { render } from "./render"; +import { render as reactRender } from "@testing-library/react"; /** * Create test router from element. @@ -41,11 +40,25 @@ export const createTestRouter = (element: any) => { * * `const { container } = render();` */ -export const select = (input: Element | ReturnType, selectors: string) => { +export const select = ( + input: Element | ReturnType, + selectors: string +) => { const el = input instanceof Element ? input : input?.container; return el.querySelector(selectors); }; +/** + * Wrapper for `select` that returns a `data-testid`. + */ +export const selectTestId = ( + input: Element | ReturnType, + testId: string +) => { + const el = input instanceof Element ? input : input?.container; + return el.querySelector(`[data-testid=${testId}]`); +}; + /** * Shorthand for `window.getComputedStyle`. */ From 54bacccaad98e1b59db90712424db210a571d0e9 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Tue, 30 Jan 2024 21:54:32 +0000 Subject: [PATCH 13/14] docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61efcea..bae6351 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Template react app with batteries included 🔋 - [Vitest (testing for Vite)](https://vitest.dev/) - [Typescript](https://www.typescriptlang.org) - [Redux](https://redux.js.org) -- [React-Router](https://reactrouter.com) +- [TanStack Router](https://tanstack.com/router/latest) - [Linaria (SASS-in-JS)](https://github.com/callstack/linaria) - Custom (hackable) build script - Custom utils and helper functions From 284febc2ca60e37294d3e65c78fe556b1f283bf2 Mon Sep 17 00:00:00 2001 From: hmerritt Date: Tue, 30 Jan 2024 21:54:53 +0000 Subject: [PATCH 14/14] docs: update contributing commit rules --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5b2a67..aa4f775 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,11 +17,9 @@ It should: - hotfix : urgent bug fix - feat : new or updated feature - docs : documentation updates - - BREAKING : if commit is a breaking change - refactor : code refactoring (no functional change) - - style : UX and display updates - - test : tests and CI updates - - chore : updates on build, tools, configuration ... + - test : tests updates + - chore : miscellaneous housekeeping updates - Merge branch : when merging branch - Merge pull request : when merging PR