From 5ce40c166871b98d24964132b7d95d4529fe43e5 Mon Sep 17 00:00:00 2001 From: Varixo Date: Fri, 25 Apr 2025 10:25:58 +0200 Subject: [PATCH 1/2] fix: unit tests on Windows # Conflicts: # packages/qwik/src/optimizer/src/plugins/rollup.ts --- packages/qwik/src/cli/add/update-files.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/qwik/src/cli/add/update-files.ts b/packages/qwik/src/cli/add/update-files.ts index 875c8dce0d0..2244ff67d0f 100644 --- a/packages/qwik/src/cli/add/update-files.ts +++ b/packages/qwik/src/cli/add/update-files.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import { extname, join } from 'node:path'; +import { extname, join, normalize } from 'node:path'; import type { FsUpdates, UpdateAppOptions } from '../types'; import { getPackageManager } from '../utils/utils'; @@ -22,12 +22,18 @@ export async function mergeIntegrationDir( const s = await fs.promises.stat(srcChildPath); if (s.isDirectory()) { - await mergeIntegrationDir(fileUpdates, opts, srcChildPath, destRootPath, alwaysInRoot); + await mergeIntegrationDir( + fileUpdates, + opts, + srcChildPath, + normalize(destRootPath).replace(/\\/g, '/'), + alwaysInRoot + ); } else if (s.isFile()) { const finalDestPath = getFinalDestPath(opts, destRootPath, destDir, destName, alwaysInRoot); if (destName === 'package.json') { - await mergePackageJsons(fileUpdates, srcChildPath, destRootPath); + await mergePackageJsons(fileUpdates, srcChildPath, destRootPath.replace(/\\/g, '/')); } else if (destName === 'settings.json') { await mergeJsons(fileUpdates, srcChildPath, finalDestPath); } else if (destName === 'README.md') { @@ -37,7 +43,7 @@ export async function mergeIntegrationDir( destName === '.prettierignore' || destName === '.eslintignore' ) { - await mergeIgnoresFile(fileUpdates, srcChildPath, destRootPath); + await mergeIgnoresFile(fileUpdates, srcChildPath, destRootPath.replace(/\\/g, '/')); } else if (ext === '.css') { await mergeCss(fileUpdates, srcChildPath, finalDestPath, opts); } else if (fs.existsSync(finalDestPath)) { @@ -79,7 +85,8 @@ function getFinalDestPath( ? destRootPath : destChildPath; - return finalDestPath; + // Normalize path separators to forward slashes for cross-platform compatibility + return normalize(finalDestPath).replace(/\\/g, '/'); } async function mergePackageJsons(fileUpdates: FsUpdates, srcPath: string, destPath: string) { From b6ee9357f9a620949acc055367da0923deab9fed Mon Sep 17 00:00:00 2001 From: Varixo Date: Fri, 25 Apr 2025 10:29:00 +0200 Subject: [PATCH 2/2] chore: add Windows to the unit tests CI # Conflicts: # packages/insights/package.json # pnpm-lock.yaml --- .github/workflows/ci.yml | 8 ++- packages/qwik/src/cli/add/update-files.ts | 17 ++--- .../qwik/src/cli/add/update-files.unit.ts | 66 +++++++++++-------- .../src/optimizer/src/plugins/plugin.unit.ts | 16 +++-- .../src/optimizer/src/plugins/rollup.unit.ts | 12 +++- .../src/optimizer/src/plugins/vite.unit.ts | 44 ++++++++++--- 6 files changed, 104 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6dd9ff2e82..0d46cba5197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -592,7 +592,13 @@ jobs: test-unit: name: Unit Tests if: always() && needs.changes.outputs.build-unit == 'true' - runs-on: ubuntu-latest + strategy: + matrix: + settings: + - host: ubuntu-latest + - host: windows-latest + + runs-on: ${{ matrix.settings.host }} needs: - changes - build-other-packages diff --git a/packages/qwik/src/cli/add/update-files.ts b/packages/qwik/src/cli/add/update-files.ts index 2244ff67d0f..875c8dce0d0 100644 --- a/packages/qwik/src/cli/add/update-files.ts +++ b/packages/qwik/src/cli/add/update-files.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import { extname, join, normalize } from 'node:path'; +import { extname, join } from 'node:path'; import type { FsUpdates, UpdateAppOptions } from '../types'; import { getPackageManager } from '../utils/utils'; @@ -22,18 +22,12 @@ export async function mergeIntegrationDir( const s = await fs.promises.stat(srcChildPath); if (s.isDirectory()) { - await mergeIntegrationDir( - fileUpdates, - opts, - srcChildPath, - normalize(destRootPath).replace(/\\/g, '/'), - alwaysInRoot - ); + await mergeIntegrationDir(fileUpdates, opts, srcChildPath, destRootPath, alwaysInRoot); } else if (s.isFile()) { const finalDestPath = getFinalDestPath(opts, destRootPath, destDir, destName, alwaysInRoot); if (destName === 'package.json') { - await mergePackageJsons(fileUpdates, srcChildPath, destRootPath.replace(/\\/g, '/')); + await mergePackageJsons(fileUpdates, srcChildPath, destRootPath); } else if (destName === 'settings.json') { await mergeJsons(fileUpdates, srcChildPath, finalDestPath); } else if (destName === 'README.md') { @@ -43,7 +37,7 @@ export async function mergeIntegrationDir( destName === '.prettierignore' || destName === '.eslintignore' ) { - await mergeIgnoresFile(fileUpdates, srcChildPath, destRootPath.replace(/\\/g, '/')); + await mergeIgnoresFile(fileUpdates, srcChildPath, destRootPath); } else if (ext === '.css') { await mergeCss(fileUpdates, srcChildPath, finalDestPath, opts); } else if (fs.existsSync(finalDestPath)) { @@ -85,8 +79,7 @@ function getFinalDestPath( ? destRootPath : destChildPath; - // Normalize path separators to forward slashes for cross-platform compatibility - return normalize(finalDestPath).replace(/\\/g, '/'); + return finalDestPath; } async function mergePackageJsons(fileUpdates: FsUpdates, srcPath: string, destPath: string) { diff --git a/packages/qwik/src/cli/add/update-files.unit.ts b/packages/qwik/src/cli/add/update-files.unit.ts index 47b9b4c8f9b..b5441066406 100644 --- a/packages/qwik/src/cli/add/update-files.unit.ts +++ b/packages/qwik/src/cli/add/update-files.unit.ts @@ -1,6 +1,7 @@ import { fs } from 'memfs'; import { join } from 'path'; -import { describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { normalizePath } from '../../testing/util'; import type { FsUpdates, UpdateAppOptions } from '../types'; import { mergeIntegrationDir } from './update-files'; @@ -8,30 +9,39 @@ vi.mock('node:fs', () => ({ default: fs, })); -function setup() { - const fakeSrcDir = 'srcDir/subSrcDir'; +let fakeSrcDir: string; +let fakeDestDir: string; +let fakeFileUpdates: FsUpdates; +let fakeOpts: UpdateAppOptions; + +beforeEach(() => { + // Reset the mock filesystem before each test + fs.mkdirSync(join('srcDir', 'subSrcDir'), { recursive: true }); + fs.mkdirSync(join('destDir', 'subDestDir'), { recursive: true }); + + fakeSrcDir = join('srcDir', 'subSrcDir'); createFakeFiles(fakeSrcDir); - const fakeDestDir = 'destDir/subDestDir'; + fakeDestDir = join('destDir', 'subDestDir'); - const fakeFileUpdates: FsUpdates = { + fakeFileUpdates = { files: [], installedDeps: {}, installedScripts: [], }; - const fakeOpts: UpdateAppOptions = { + fakeOpts = { rootDir: fakeDestDir, integration: 'integration', }; +}); - return { - fakeSrcDir, - fakeDestDir, - fakeFileUpdates, - fakeOpts, - }; -} +afterEach(() => { + vi.clearAllMocks(); + // Clean up the mock filesystem + fs.rmSync(join('srcDir'), { recursive: true, force: true }); + fs.rmSync(join('destDir'), { recursive: true, force: true }); +}); function createFakeFiles(dir: string) { // Create fake src files @@ -43,23 +53,19 @@ function createFakeFiles(dir: string) { describe('mergeIntegrationDir', () => { test('should merge integration directory', async () => { - const { fakeSrcDir, fakeDestDir, fakeFileUpdates, fakeOpts } = setup(); - await mergeIntegrationDir(fakeFileUpdates, fakeOpts, fakeSrcDir, fakeDestDir); - const actualResults = fakeFileUpdates.files.map((f) => f.path); + const actualResults = fakeFileUpdates.files.map((f) => normalizePath(f.path)); const expectedResults = [ - 'destDir/subDestDir/fake.ts', - 'destDir/subDestDir/package.json', - 'destDir/subDestDir/src/global.css', + normalizePath(join('destDir', 'subDestDir', 'fake.ts')), + normalizePath(join('destDir', 'subDestDir', 'package.json')), + normalizePath(join('destDir', 'subDestDir', 'src', 'global.css')), ]; expect(actualResults).toEqual(expectedResults); }); test('should merge integration directory in a monorepo', async () => { - const { fakeSrcDir, fakeDestDir, fakeFileUpdates, fakeOpts } = setup(); - // Create a global file in the destination director const monorepoSubDir = join(fakeDestDir, 'apps', 'subpackage', 'src'); fs.mkdirSync(monorepoSubDir, { recursive: true }); @@ -72,25 +78,27 @@ describe('mergeIntegrationDir', () => { fs.mkdirSync(join(fakeSrcDir, 'should-stay'), { recursive: true }); fs.writeFileSync(join(fakeSrcDir, 'should-stay', 'should-also-stay.ts'), 'fake file'); - fakeOpts.projectDir = 'apps/subpackage'; + fakeOpts.projectDir = join('apps', 'subpackage'); fakeOpts.installDeps = true; const fakeAlwaysInRoot = ['should-stay-in-root.ts', 'should-stay']; await mergeIntegrationDir(fakeFileUpdates, fakeOpts, fakeSrcDir, fakeDestDir, fakeAlwaysInRoot); - const actualResults = fakeFileUpdates.files.map((f) => f.path); + const actualResults = fakeFileUpdates.files.map((f) => normalizePath(f.path)); const expectedResults = [ - `destDir/subDestDir/apps/subpackage/fake.ts`, - `destDir/subDestDir/should-stay-in-root.ts`, - `destDir/subDestDir/package.json`, - `destDir/subDestDir/should-stay/should-also-stay.ts`, - `destDir/subDestDir/apps/subpackage/src/global.css`, + normalizePath(join('destDir', 'subDestDir', 'apps', 'subpackage', 'fake.ts')), + normalizePath(join('destDir', 'subDestDir', 'should-stay-in-root.ts')), + normalizePath(join('destDir', 'subDestDir', 'package.json')), + normalizePath(join('destDir', 'subDestDir', 'should-stay', 'should-also-stay.ts')), + normalizePath(join('destDir', 'subDestDir', 'apps', 'subpackage', 'src', 'global.css')), ]; expect(actualResults).toEqual(expectedResults); const actualGlobalCssContent = fakeFileUpdates.files.find( - (f) => f.path === `destDir/subDestDir/apps/subpackage/src/global.css` + (f) => + normalizePath(f.path) === + normalizePath(join('destDir', 'subDestDir', 'apps', 'subpackage', 'src', 'global.css')) )?.content; expect(actualGlobalCssContent).toBe('p{color: red}\n\n/* CSS */\n'); diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts b/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts index d37a262c1cf..32b8fe8de2c 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts @@ -22,7 +22,9 @@ test('defaults', async () => { assert.deepEqual(opts.debug, false); assert.deepEqual(opts.rootDir, normalizePath(cwd)); assert.deepEqual(opts.tsconfigFileNames, ['./tsconfig.json']); - assert.deepEqual(opts.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((opts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); assert.deepEqual(opts.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(opts.manifestInput, null); assert.deepEqual(opts.manifestOutput, null); @@ -39,7 +41,9 @@ test('defaults (buildMode: production)', async () => { assert.deepEqual(opts.debug, false); assert.deepEqual(opts.rootDir, normalizePath(cwd)); assert.deepEqual(opts.tsconfigFileNames, ['./tsconfig.json']); - assert.deepEqual(opts.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((opts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); assert.deepEqual(opts.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(opts.manifestInput, null); assert.deepEqual(opts.manifestOutput, null); @@ -57,7 +61,9 @@ test('defaults (target: ssr)', async () => { assert.deepEqual(opts.debug, false); assert.deepEqual(opts.rootDir, normalizePath(cwd)); assert.deepEqual(opts.tsconfigFileNames, ['./tsconfig.json']); - assert.deepEqual(opts.input, [normalizePath(resolve(cwd, 'src', 'entry.ssr'))]); + assert.deepEqual((opts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'entry.ssr')), + ]); assert.deepEqual(opts.outDir, normalizePath(resolve(cwd, 'server'))); assert.deepEqual(opts.manifestInput, null); assert.deepEqual(opts.manifestOutput, null); @@ -74,7 +80,9 @@ test('defaults (buildMode: production, target: ssr)', async () => { assert.deepEqual(opts.debug, false); assert.deepEqual(opts.rootDir, normalizePath(cwd)); assert.deepEqual(opts.tsconfigFileNames, ['./tsconfig.json']); - assert.deepEqual(opts.input, [normalizePath(resolve(cwd, 'src', 'entry.ssr'))]); + assert.deepEqual((opts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'entry.ssr')), + ]); assert.deepEqual(opts.outDir, normalizePath(resolve(cwd, 'server'))); assert.deepEqual(opts.manifestInput, null); assert.deepEqual(opts.manifestOutput, null); diff --git a/packages/qwik/src/optimizer/src/plugins/rollup.unit.ts b/packages/qwik/src/optimizer/src/plugins/rollup.unit.ts index 01a94687fa4..9288a8a1663 100644 --- a/packages/qwik/src/optimizer/src/plugins/rollup.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/rollup.unit.ts @@ -30,7 +30,9 @@ test('rollup default input options, client', async () => { const rollupInputOpts: Rollup.InputOptions = await plugin.options!({}); assert.deepEqual(typeof rollupInputOpts.onwarn, 'function'); - assert.deepEqual(rollupInputOpts.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((rollupInputOpts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); }); test('rollup default input options, ssr', async () => { @@ -44,8 +46,12 @@ test('rollup default input options, ssr', async () => { assert.deepEqual(typeof rollupInputOpts.onwarn, 'function'); assert.deepEqual(rollupInputOpts.treeshake, undefined); - assert.deepEqual(rollupInputOpts.input, [normalizePath(resolve(cwd, 'src', 'entry.ssr'))]); - assert.deepEqual(opts.input, [normalizePath(resolve(cwd, 'src', 'entry.ssr'))]); + assert.deepEqual((rollupInputOpts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'entry.ssr')), + ]); + assert.deepEqual((opts.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'entry.ssr')), + ]); }); test('rollup default set input options, ssr', async () => { diff --git a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts index 2730bfb0471..7526318d77e 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts @@ -96,8 +96,14 @@ test('command: serve, mode: development', async () => { assert.deepEqual(outputOptions.assetFileNames, 'assets/[hash]-[name].[ext]'); assert.deepEqual(chunkFileNames(chunkInfoMocks[0]), `build/chunk.tsx.js`); assert.deepEqual(entryFileNames(chunkInfoMocks[0]), `build/chunk.tsx.js`); - assert.deepEqual(chunkFileNames(chunkInfoMocks[1]), 'build/app-chunk.tsx.js'); - assert.deepEqual(entryFileNames(chunkInfoMocks[1]), 'build/app-chunk.tsx.js'); + const relDev = path.relative(cwd, chunkInfoMocks[1].name); + const sanitizedDev = relDev + .replace(/^\(\.\.\/\)+/, '') + .replace(/^\/+/, '') + .replace(/\//g, '-'); + const expectedDevChunk = `build/${sanitizedDev}.js`; + assert.deepEqual(chunkFileNames(chunkInfoMocks[1]), expectedDevChunk); + assert.deepEqual(entryFileNames(chunkInfoMocks[1]), expectedDevChunk); assert.deepEqual(outputOptions.format, 'es'); assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); @@ -180,13 +186,21 @@ test('command: build, mode: development', async () => { assert.deepEqual(plugin.enforce, 'pre'); assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.emptyOutDir, undefined); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); assert.deepEqual(outputOptions.assetFileNames, 'assets/[hash]-[name].[ext]'); assert.deepEqual(chunkFileNames(chunkInfoMocks[0]), `build/chunk.tsx.js`); assert.deepEqual(entryFileNames(chunkInfoMocks[0]), `build/chunk.tsx.js`); - assert.deepEqual(chunkFileNames(chunkInfoMocks[1]), 'build/app-chunk.tsx.js'); - assert.deepEqual(entryFileNames(chunkInfoMocks[1]), 'build/app-chunk.tsx.js'); + const relBuildDev = path.relative(cwd, chunkInfoMocks[1].name); + const sanitizedBuildDev = relBuildDev + .replace(/^\(\.\.\/\)+/, '') + .replace(/^\/+/, '') + .replace(/\//g, '-'); + const expectedBuildDevChunk = `build/${sanitizedBuildDev}.js`; + assert.deepEqual(chunkFileNames(chunkInfoMocks[1]), expectedBuildDevChunk); + assert.deepEqual(entryFileNames(chunkInfoMocks[1]), expectedBuildDevChunk); assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, undefined); @@ -225,7 +239,9 @@ test('command: build, mode: production', async () => { assert.deepEqual(plugin.enforce, 'pre'); assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.emptyOutDir, undefined); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); assert.deepEqual(outputOptions.assetFileNames, 'assets/[hash]-[name].[ext]'); assert.deepEqual(outputOptions.chunkFileNames, 'build/q-[hash].js'); @@ -267,7 +283,9 @@ test('command: build, --mode production (client)', async () => { assert.deepEqual(opts.target, 'client'); assert.deepEqual(opts.buildMode, 'production'); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'root'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'root')), + ]); assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'client-dist'))); assert.deepEqual(build.emptyOutDir, undefined); }); @@ -296,7 +314,9 @@ test('command: build, --ssr entry.server.tsx', async () => { assert.deepEqual(plugin.enforce, 'pre'); assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'server'))); assert.deepEqual(build.emptyOutDir, undefined); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'entry.server.tsx'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'entry.server.tsx')), + ]); assert.deepEqual(outputOptions.assetFileNames, 'assets/[hash]-[name].[ext]'); assert.isFunction(outputOptions.chunkFileNames); @@ -337,7 +357,9 @@ test('command: serve, --mode ssr', async () => { assert.deepEqual(opts.buildMode, 'development'); assert.deepEqual(build.minify, undefined); assert.deepEqual(build.ssr, undefined); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'renderz.tsx'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'renderz.tsx')), + ]); assert.deepEqual(c.build.outDir, normalizePath(resolve(cwd, 'ssr-dist'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(c.publicDir, undefined); @@ -366,7 +388,9 @@ test('command: serve, --mode ssr with build.assetsDir', async () => { assert.deepEqual(opts.buildMode, 'development'); assert.deepEqual(build.minify, undefined); assert.deepEqual(build.ssr, undefined); - assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'renderz.tsx'))]); + assert.deepEqual((rollupOptions.input as string[]).map(normalizePath), [ + normalizePath(resolve(cwd, 'src', 'renderz.tsx')), + ]); assert.deepEqual(c.build.outDir, normalizePath(resolve(cwd, 'ssr-dist'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(c.publicDir, undefined);