From 40cf0198cbdd8a4154eedbc65ee9f675aded5006 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Fri, 1 Dec 2023 12:16:02 +1100 Subject: [PATCH] test(vite): refactor Vite CSS tests (#8190) --- integration/helpers/vite.ts | 61 +++- integration/vite-css-build-test.ts | 198 ------------ integration/vite-css-dev-express-test.ts | 243 --------------- integration/vite-css-dev-test.ts | 371 ----------------------- integration/vite-css-test.ts | 309 +++++++++++++++++++ integration/vite-hmr-hdr-test.ts | 11 +- 6 files changed, 360 insertions(+), 833 deletions(-) delete mode 100644 integration/vite-css-build-test.ts delete mode 100644 integration/vite-css-dev-express-test.ts delete mode 100644 integration/vite-css-dev-test.ts create mode 100644 integration/vite-css-test.ts diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index 8dced0b42b..8ad0fc6bee 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -1,5 +1,6 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process"; import path from "node:path"; +import fs from "node:fs/promises"; import type { Readable } from "node:stream"; import url from "node:url"; import execa from "execa"; @@ -11,7 +12,10 @@ import getPort from "get-port"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -export const VITE_CONFIG = async (args: { port: number }) => { +export const VITE_CONFIG = async (args: { + port: number; + vitePlugins?: string; +}) => { let hmrPort = await getPort(); return String.raw` import { defineConfig } from "vite"; @@ -25,7 +29,7 @@ export const VITE_CONFIG = async (args: { port: number }) => { port: ${hmrPort} } }, - plugins: [remix()], + plugins: [remix(),${args.vitePlugins ?? ""}], }); `; }; @@ -115,28 +119,53 @@ const createDev = (nodeArgs: string[]) => async ({ cwd, port }: ServerArgs): Promise<() => Promise> => { let proc = node(nodeArgs, { cwd }); - await waitForServer(proc, { port: port }); + await waitForServer(proc, { port }); return async () => await kill(proc.pid!); }; -export const viteBuild = (args: { cwd: string }) => { - let vite = resolveBin.sync("vite"); +export const viteBuild = ({ cwd }: { cwd: string }) => { + let nodeBin = process.argv[0]; + let viteBin = resolveBin.sync("vite"); let commands = [ - [vite, "build"], - [vite, "build", "--ssr"], + [viteBin, "build"], + [viteBin, "build", "--ssr"], ]; let results = []; for (let command of commands) { - let result = spawnSync("node", command, { - cwd: args.cwd, - env: { - ...process.env, - }, + let result = spawnSync(nodeBin, command, { + cwd, + env: { ...process.env }, }); results.push(result); } return results; }; + +export const viteRemixServe = async ({ + cwd, + port, +}: { + cwd: string; + port: number; +}) => { + let nodeBin = process.argv[0]; + let serveProc = spawn( + nodeBin, + ["node_modules/@remix-run/serve/dist/cli.js", "build/server/index.js"], + { + cwd, + stdio: "pipe", + env: { NODE_ENV: "production", PORT: port.toFixed(0) }, + } + ); + + await waitForServer(serveProc, { port }); + + return () => { + serveProc.kill(); + }; +}; + export const viteDev = createDev([resolveBin.sync("vite"), "dev"]); export const customDev = createDev(["./server.mjs"]); @@ -212,3 +241,11 @@ function bufferize(stream: Readable): () => string { stream.on("data", (data) => (buffer += data.toString())); return () => buffer; } + +export function createEditor(projectDir: string) { + return async (file: string, transform: (contents: string) => string) => { + let filepath = path.join(projectDir, file); + let contents = await fs.readFile(filepath, "utf8"); + await fs.writeFile(filepath, transform(contents), "utf8"); + }; +} diff --git a/integration/vite-css-build-test.ts b/integration/vite-css-build-test.ts deleted file mode 100644 index 46b917a6c5..0000000000 --- a/integration/vite-css-build-test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from "node:fs/promises"; -import path from "node:path"; -import url from "node:url"; - -import { - createAppFixture, - createFixture, - js, - css, -} from "./helpers/create-fixture.js"; -import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; -import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; - -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); - -const TEST_PADDING_VALUE = "20px"; - -test.describe("Vite CSS build", () => { - let fixture: Fixture; - let appFixture: AppFixture; - - test.beforeAll(async () => { - // set sideEffects for global vanilla extract css - let packageJson = JSON.parse( - await fs.readFile( - path.resolve(__dirname, "helpers", "node-template", "package.json"), - "utf-8" - ) - ); - packageJson.sideEffects = ["*.css.ts"]; - - fixture = await createFixture({ - compiler: "vite", - files: { - "package.json": JSON.stringify(packageJson, null, 2), - "remix.config.js": js` - throw new Error("Remix should not access remix.config.js when using Vite"); - export default {}; - `, - "vite.config.ts": js` - import { defineConfig } from "vite"; - import { unstable_vitePlugin as remix } from "@remix-run/dev"; - import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; - - export default defineConfig({ - plugins: [ - vanillaExtractPlugin(), - remix(), - ], - }); - `, - "app/entry.client.tsx": js` - import "./entry.client.css"; - - import { RemixBrowser } from "@remix-run/react"; - import { startTransition, StrictMode } from "react"; - import { hydrateRoot } from "react-dom/client"; - - startTransition(() => { - hydrateRoot( - document, - - - - ); - }); - `, - "app/root.tsx": js` - import { Links, Meta, Outlet, Scripts } from "@remix-run/react"; - - export default function Root() { - return ( - - - - - - -
- -
- - - - ); - } - `, - "app/entry.client.css": css` - .client-entry { - background: pink; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/routes/_index/styles-bundled.css": css` - .index_bundled { - background: papayawhip; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/routes/_index/styles-linked.css": css` - .index_linked { - background: mintcream; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/routes/_index/styles.module.css": css` - .index { - background: peachpuff; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/routes/_index/styles-vanilla-global.css.ts": js` - import { globalStyle } from "@vanilla-extract/css"; - - globalStyle(".index_vanilla_global", { - background: "lightgreen", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/routes/_index/styles-vanilla-local.css.ts": js` - import { style } from "@vanilla-extract/css"; - - export const index = style({ - background: "lightblue", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/routes/_index/route.tsx": js` - import "./styles-bundled.css"; - import linkedStyles from "./styles-linked.css?url"; - import cssModulesStyles from "./styles.module.css"; - import "./styles-vanilla-global.css"; - import * as stylesVanillaLocal from "./styles-vanilla-local.css"; - - export function links() { - return [{ rel: "stylesheet", href: linkedStyles }]; - } - - export default function IndexRoute() { - return ( -
-
-
-
-
-
-
-

CSS test

-
-
-
-
-
-
-
- ); - } - `, - }, - }); - - appFixture = await createAppFixture(fixture); - }); - - test.afterAll(() => { - appFixture.close(); - }); - - test("renders styles", async ({ page }) => { - let app = new PlaywrightFixture(appFixture, page); - await app.goto("/"); - await expect(page.locator("#index [data-client-entry]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - }); -}); diff --git a/integration/vite-css-dev-express-test.ts b/integration/vite-css-dev-express-test.ts deleted file mode 100644 index 7fd9aeaaf8..0000000000 --- a/integration/vite-css-dev-express-test.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from "node:fs/promises"; -import path from "node:path"; -import getPort from "get-port"; - -import { js, css } from "./helpers/create-fixture.js"; -import { createProject, customDev, EXPRESS_SERVER } from "./helpers/vite.js"; - -const TEST_PADDING_VALUE = "20px"; -const UPDATED_TEST_PADDING_VALUE = "30px"; - -let port: number; -let cwd: string; -let stop: () => Promise; - -test.beforeAll(async () => { - port = await getPort(); - let hmrPort = await getPort(); - cwd = await createProject({ - "remix.config.js": js` - throw new Error("Remix should not access remix.config.js when using Vite"); - export default {}; - `, - "vite.config.ts": js` - import { defineConfig } from "vite"; - import { unstable_vitePlugin as remix } from "@remix-run/dev"; - import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; - - export default defineConfig({ - server: { - hmr: { - port: ${hmrPort} - } - }, - plugins: [ - vanillaExtractPlugin(), - remix(), - ], - }); - `, - "server.mjs": EXPRESS_SERVER({ port }), - "app/styles-bundled.css": css` - .index_bundled { - background: papayawhip; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles-linked.css": css` - .index_linked { - background: salmon; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles.module.css": css` - .index { - background: peachpuff; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles-vanilla-global.css.ts": js` - import { createVar, globalStyle } from "@vanilla-extract/css"; - - globalStyle(".index_vanilla_global", { - background: "lightgreen", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/styles-vanilla-local.css.ts": js` - import { style } from "@vanilla-extract/css"; - - export const index = style({ - background: "lightblue", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/routes/_index.tsx": js` - import "../styles-bundled.css"; - import linkedStyles from "../styles-linked.css?url"; - import cssModulesStyles from "../styles.module.css"; - import "../styles-vanilla-global.css"; - import * as stylesVanillaLocal from "../styles-vanilla-local.css"; - - export function links() { - return [{ rel: "stylesheet", href: linkedStyles }]; - } - - export default function IndexRoute() { - return ( -
- -
-
-
-
-
-

CSS test

-
-
-
-
-
-
- ); - } - `, - }); - - stop = await customDev({ cwd, port }); -}); - -test.afterAll(async () => await stop()); - -test.describe("without JS", () => { - test.use({ javaScriptEnabled: false }); - test("renders CSS", async ({ page }) => { - await page.goto(`http://localhost:${port}/`, { - waitUntil: "networkidle", - }); - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - }); -}); - -test.describe("with JS", () => { - test.use({ javaScriptEnabled: true }); - test("updates CSS", async ({ page }) => { - let pageErrors: unknown[] = []; - page.on("pageerror", (error) => pageErrors.push(error)); - - await page.goto(`http://localhost:${port}/`, { - waitUntil: "networkidle", - }); - - // Ensure no errors on page load - expect(pageErrors).toEqual([]); - - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - - let input = page.locator("#index input"); - await expect(input).toBeVisible(); - await input.type("stateful"); - await expect(input).toHaveValue("stateful"); - - let bundledCssContents = await fs.readFile( - path.join(cwd, "app/styles-bundled.css"), - "utf8" - ); - await fs.writeFile( - path.join(cwd, "app/styles-bundled.css"), - bundledCssContents.replace( - TEST_PADDING_VALUE, - UPDATED_TEST_PADDING_VALUE - ), - "utf8" - ); - - let linkedCssContents = await fs.readFile( - path.join(cwd, "app/styles-linked.css"), - "utf8" - ); - await fs.writeFile( - path.join(cwd, "app/styles-linked.css"), - linkedCssContents.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE), - "utf8" - ); - - let cssModuleContents = await fs.readFile( - path.join(cwd, "app/styles.module.css"), - "utf8" - ); - await fs.writeFile( - path.join(cwd, "app/styles.module.css"), - cssModuleContents.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE), - "utf8" - ); - - await editFile(path.join(cwd, "app/styles-vanilla-global.css.ts"), (data) => - data.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE) - ); - await editFile(path.join(cwd, "app/styles-vanilla-local.css.ts"), (data) => - data.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE) - ); - - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - - await expect(input).toHaveValue("stateful"); - - expect(pageErrors).toEqual([]); - }); -}); - -async function editFile(filepath: string, edit: (content: string) => string) { - let content = await fs.readFile(filepath, "utf-8"); - await fs.writeFile(filepath, edit(content)); -} diff --git a/integration/vite-css-dev-test.ts b/integration/vite-css-dev-test.ts deleted file mode 100644 index 331f9d00cc..0000000000 --- a/integration/vite-css-dev-test.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { test, expect } from "@playwright/test"; -import type { Readable } from "node:stream"; -import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; -import fs from "node:fs/promises"; -import path from "node:path"; -import resolveBin from "resolve-bin"; -import getPort from "get-port"; -import waitOn from "wait-on"; - -import { createFixtureProject, js, css } from "./helpers/create-fixture.js"; -import { killtree } from "./helpers/killtree.js"; - -const TEST_PADDING_VALUE = "20px"; -const UPDATED_TEST_PADDING_VALUE = "30px"; - -test.describe("Vite CSS dev", () => { - let projectDir: string; - let devProc: ChildProcessWithoutNullStreams; - let devPort: number; - - test.beforeAll(async () => { - devPort = await getPort(); - projectDir = await createFixtureProject({ - compiler: "vite", - files: { - "remix.config.js": js` - throw new Error("Remix should not access remix.config.js when using Vite"); - export default {}; - `, - "vite.config.ts": js` - import { defineConfig } from "vite"; - import { unstable_vitePlugin as remix } from "@remix-run/dev"; - import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; - - export default defineConfig({ - server: { - port: ${devPort}, - strictPort: true, - }, - plugins: [ - vanillaExtractPlugin(), - remix(), - ], - }); - `, - "app/entry.client.tsx": js` - import "./entry.client.css"; - - import { RemixBrowser } from "@remix-run/react"; - import { startTransition, StrictMode } from "react"; - import { hydrateRoot } from "react-dom/client"; - - startTransition(() => { - hydrateRoot( - document, - - - - ); - }); - `, - "app/root.tsx": js` - import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react"; - - export default function Root() { - return ( - - - - - - -
- -
- - - - - ); - } - `, - "app/entry.client.css": css` - .client-entry { - background: pink; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles-bundled.css": css` - .index_bundled { - background: papayawhip; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles-linked.css": css` - .index_linked { - background: salmon; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles.module.css": css` - .index { - background: peachpuff; - padding: ${TEST_PADDING_VALUE}; - } - `, - "app/styles-vanilla-global.css.ts": js` - import { createVar, globalStyle } from "@vanilla-extract/css"; - - globalStyle(".index_vanilla_global", { - background: "lightgreen", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/styles-vanilla-local.css.ts": js` - import { style } from "@vanilla-extract/css"; - - export const index = style({ - background: "lightblue", - padding: "${TEST_PADDING_VALUE}", - }); - `, - "app/routes/_index.tsx": js` - import "../styles-bundled.css"; - import linkedStyles from "../styles-linked.css?url"; - import cssModulesStyles from "../styles.module.css"; - import "../styles-vanilla-global.css"; - import * as stylesVanillaLocal from "../styles-vanilla-local.css"; - - export function links() { - return [{ rel: "stylesheet", href: linkedStyles }]; - } - - export default function IndexRoute() { - return ( -
- -
-
-
-
-
-
-

CSS test

-
-
-
-
-
-
-
- ); - } - `, - }, - }); - - let nodeBin = process.argv[0]; - let viteBin = resolveBin.sync("vite"); - devProc = spawn(nodeBin, [viteBin, "dev"], { - cwd: projectDir, - env: process.env, - stdio: "pipe", - }); - let devStdout = bufferize(devProc.stdout); - let devStderr = bufferize(devProc.stderr); - - await waitOn({ - resources: [`http://localhost:${devPort}/`], - timeout: 10000, - }).catch((err) => { - let stdout = devStdout(); - let stderr = devStderr(); - throw new Error( - [ - err.message, - "", - "exit code: " + devProc.exitCode, - "stdout: " + stdout ? `\n${stdout}\n` : "", - "stderr: " + stderr ? `\n${stderr}\n` : "", - ].join("\n") - ); - }); - }); - - test.afterAll(async () => { - devProc.pid && (await killtree(devProc.pid)); - }); - - test.describe("without JS", () => { - test.use({ javaScriptEnabled: false }); - test("renders CSS", async ({ page }) => { - await page.goto(`http://localhost:${devPort}/`, { - waitUntil: "networkidle", - }); - await expect(page.locator("#index [data-client-entry]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - }); - }); - - test.describe("with JS", () => { - test.use({ javaScriptEnabled: true }); - test("updates CSS", async ({ page }) => { - let pageErrors: unknown[] = []; - page.on("pageerror", (error) => pageErrors.push(error)); - - await page.goto(`http://localhost:${devPort}/`, { - waitUntil: "networkidle", - }); - - // Ensure no errors on page load - expect(pageErrors).toEqual([]); - - await expect(page.locator("#index [data-client-entry]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - TEST_PADDING_VALUE - ); - - let input = page.locator("#index input"); - await expect(input).toBeVisible(); - await input.type("stateful"); - await expect(input).toHaveValue("stateful"); - - let bundledCssContents = await fs.readFile( - path.join(projectDir, "app/styles-bundled.css"), - "utf8" - ); - await fs.writeFile( - path.join(projectDir, "app/styles-bundled.css"), - bundledCssContents.replace( - TEST_PADDING_VALUE, - UPDATED_TEST_PADDING_VALUE - ), - "utf8" - ); - - let linkedCssContents = await fs.readFile( - path.join(projectDir, "app/styles-linked.css"), - "utf8" - ); - await fs.writeFile( - path.join(projectDir, "app/styles-linked.css"), - linkedCssContents.replace( - TEST_PADDING_VALUE, - UPDATED_TEST_PADDING_VALUE - ), - "utf8" - ); - - let cssModuleContents = await fs.readFile( - path.join(projectDir, "app/styles.module.css"), - "utf8" - ); - await fs.writeFile( - path.join(projectDir, "app/styles.module.css"), - cssModuleContents.replace( - TEST_PADDING_VALUE, - UPDATED_TEST_PADDING_VALUE - ), - "utf8" - ); - - await editFile( - path.join(projectDir, "app/styles-vanilla-global.css.ts"), - (data) => data.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE) - ); - await editFile( - path.join(projectDir, "app/styles-vanilla-local.css.ts"), - (data) => data.replace(TEST_PADDING_VALUE, UPDATED_TEST_PADDING_VALUE) - ); - - await expect(page.locator("#index [data-css-modules]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-linked]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-bundled]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-global]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - await expect(page.locator("#index [data-css-vanilla-local]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - - // Ensure CSS updates were handled by HMR - await expect(input).toHaveValue("stateful"); - - // The following change triggers a full page reload, so we check it after all the checks for HMR state preservation - let clientEntryCssContents = await fs.readFile( - path.join(projectDir, "app/entry.client.css"), - "utf8" - ); - await fs.writeFile( - path.join(projectDir, "app/entry.client.css"), - clientEntryCssContents.replace( - TEST_PADDING_VALUE, - UPDATED_TEST_PADDING_VALUE - ), - "utf8" - ); - - await expect(page.locator("#index [data-client-entry]")).toHaveCSS( - "padding", - UPDATED_TEST_PADDING_VALUE - ); - - expect(pageErrors).toEqual([]); - }); - }); -}); - -let bufferize = (stream: Readable): (() => string) => { - let buffer = ""; - stream.on("data", (data) => (buffer += data.toString())); - return () => buffer; -}; - -async function editFile(filepath: string, edit: (content: string) => string) { - let content = await fs.readFile(filepath, "utf-8"); - await fs.writeFile(filepath, edit(content)); -} diff --git a/integration/vite-css-test.ts b/integration/vite-css-test.ts new file mode 100644 index 0000000000..e71cd9faa9 --- /dev/null +++ b/integration/vite-css-test.ts @@ -0,0 +1,309 @@ +import type { Page } from "@playwright/test"; +import { test, expect } from "@playwright/test"; +import getPort from "get-port"; + +import { + createProject, + createEditor, + viteDev, + viteBuild, + viteRemixServe, + customDev, + VITE_CONFIG, + EXPRESS_SERVER, +} from "./helpers/vite.js"; + +const PADDING = "20px"; +const NEW_PADDING = "30px"; + +const files = { + "app/entry.client.tsx": ` + import "./entry.client.css"; + + import { RemixBrowser } from "@remix-run/react"; + import { startTransition, StrictMode } from "react"; + import { hydrateRoot } from "react-dom/client"; + + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); + `, + "app/root.tsx": ` + import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react"; + + export default function Root() { + return ( + + + + + + + + + + + + ); + } + `, + "app/entry.client.css": ` + .entry-client { + background: pink; + padding: ${PADDING}; + } + `, + "app/styles-bundled.css": ` + .index_bundled { + background: papayawhip; + padding: ${PADDING}; + } + `, + "app/styles-linked.css": ` + .index_linked { + background: salmon; + padding: ${PADDING}; + } + `, + "app/styles.module.css": ` + .index { + background: peachpuff; + padding: ${PADDING}; + } + `, + "app/styles-vanilla-global.css.ts": ` + import { createVar, globalStyle } from "@vanilla-extract/css"; + + globalStyle(".index_vanilla_global", { + background: "lightgreen", + padding: "${PADDING}", + }); + `, + "app/styles-vanilla-local.css.ts": ` + import { style } from "@vanilla-extract/css"; + + export const index = style({ + background: "lightblue", + padding: "${PADDING}", + }); + `, + "app/routes/_index.tsx": ` + import "../styles-bundled.css"; + import linkedStyles from "../styles-linked.css?url"; + import cssModulesStyles from "../styles.module.css"; + import "../styles-vanilla-global.css"; + import * as stylesVanillaLocal from "../styles-vanilla-local.css"; + + export function links() { + return [{ rel: "stylesheet", href: linkedStyles }]; + } + + export default function IndexRoute() { + return ( + <> + +
+
+
+
+
+
+

CSS test

+
+
+
+
+
+
+ + ); + } + `, +}; + +const vitePlugins = + '(await import("@vanilla-extract/vite-plugin")).vanillaExtractPlugin()'; + +test.describe(() => { + test.describe(async () => { + let port: number; + let cwd: string; + let stop: () => Promise; + + test.beforeAll(async () => { + port = await getPort(); + cwd = await createProject({ + "vite.config.ts": await VITE_CONFIG({ port, vitePlugins }), + ...files, + }); + stop = await viteDev({ cwd, port }); + }); + test.afterAll(async () => await stop()); + + test.describe(() => { + test.use({ javaScriptEnabled: false }); + test("Vite / CSS / vite dev / without JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + }); + }); + + test.describe(() => { + test.use({ javaScriptEnabled: true }); + test("Vite / CSS / vite dev / with JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + await hmrWorkflow({ page, port, cwd }); + }); + }); + }); + + test.describe(async () => { + let port: number; + let cwd: string; + let stop: () => Promise; + + test.beforeAll(async () => { + port = await getPort(); + cwd = await createProject({ + "vite.config.ts": await VITE_CONFIG({ port, vitePlugins }), + "server.mjs": EXPRESS_SERVER({ port }), + ...files, + }); + stop = await customDev({ cwd, port }); + }); + test.afterAll(async () => await stop()); + + test.describe(() => { + test.use({ javaScriptEnabled: false }); + test("Vite / CSS / express / without JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + }); + }); + + test.describe(() => { + test.use({ javaScriptEnabled: true }); + test("Vite / CSS / express / with JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + await hmrWorkflow({ page, port, cwd }); + }); + }); + }); + + test.describe(async () => { + let port: number; + let cwd: string; + let stop: () => void; + + test.beforeAll(async () => { + port = await getPort(); + cwd = await createProject({ + "vite.config.ts": await VITE_CONFIG({ port, vitePlugins }), + ...files, + }); + + let edit = createEditor(cwd); + await edit("package.json", (contents) => + contents.replace('"sideEffects": false', '"sideEffects": ["*.css.ts"]') + ); + + await viteBuild({ cwd }); + stop = await viteRemixServe({ cwd, port }); + }); + test.afterAll(() => stop()); + + test.describe(() => { + test.use({ javaScriptEnabled: false }); + test("Vite / CSS / vite build / without JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + }); + }); + + test.describe(() => { + test.use({ javaScriptEnabled: true }); + test("Vite / CSS / vite build / with JS", async ({ page }) => { + await pageLoadWorkflow({ page, port }); + }); + }); + }); +}); + +async function pageLoadWorkflow({ page, port }: { page: Page; port: number }) { + let pageErrors: Error[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle", + }); + + await Promise.all( + [ + "#css-bundled", + "#css-linked", + "#css-modules", + "#css-vanilla-global", + "#css-vanilla-local", + ].map( + async (selector) => + await expect(page.locator(selector)).toHaveCSS("padding", PADDING) + ) + ); +} + +async function hmrWorkflow({ + page, + cwd, + port, +}: { + page: Page; + cwd: string; + port: number; +}) { + let pageErrors: Error[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle", + }); + + let input = page.locator("input"); + await expect(input).toBeVisible(); + await input.type("stateful"); + await expect(input).toHaveValue("stateful"); + + let edit = createEditor(cwd); + let modifyCss = (contents: string) => contents.replace(PADDING, NEW_PADDING); + + await Promise.all([ + edit("app/styles-bundled.css", modifyCss), + edit("app/styles-linked.css", modifyCss), + edit("app/styles.module.css", modifyCss), + edit("app/styles-vanilla-global.css.ts", modifyCss), + edit("app/styles-vanilla-local.css.ts", modifyCss), + ]); + + await Promise.all( + [ + "#css-bundled", + "#css-linked", + "#css-modules", + "#css-vanilla-global", + "#css-vanilla-local", + ].map( + async (selector) => + await expect(page.locator(selector)).toHaveCSS("padding", NEW_PADDING) + ) + ); + + // Ensure CSS updates were handled by HMR + await expect(input).toHaveValue("stateful"); + + // The following change triggers a full page reload, so we check it after all the checks for HMR state preservation + await edit("app/entry.client.css", modifyCss); + await expect(page.locator("#entry-client")).toHaveCSS("padding", NEW_PADDING); + + expect(pageErrors).toEqual([]); +} diff --git a/integration/vite-hmr-hdr-test.ts b/integration/vite-hmr-hdr-test.ts index 413c5134f3..1608da450d 100644 --- a/integration/vite-hmr-hdr-test.ts +++ b/integration/vite-hmr-hdr-test.ts @@ -6,6 +6,7 @@ import getPort from "get-port"; import { createProject, + createEditor, viteDev, customDev, VITE_CONFIG, @@ -95,7 +96,7 @@ async function workflow({ }) { let pageErrors: Error[] = []; page.on("pageerror", (error) => pageErrors.push(error)); - let edit = editor(cwd); + let edit = createEditor(cwd); // setup: initial render await page.goto(`http://localhost:${port}/`, { @@ -304,11 +305,3 @@ async function workflow({ await expect(input).toHaveValue("stateful"); expect(pageErrors).toEqual([]); } - -const editor = - (projectDir: string) => - async (file: string, transform: (contents: string) => string) => { - let filepath = path.join(projectDir, file); - let contents = await fs.readFile(filepath, "utf8"); - await fs.writeFile(filepath, transform(contents), "utf8"); - };