From 775529c2a406ba50e3728b9fbcd9c504939e0c21 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 10 Aug 2026 20:27:04 +0800 Subject: [PATCH] test: reduce duplicate test setup --- packages/create-rstack/tests/create.test.ts | 359 +++++------------- packages/rstack/tests/fmt/helpers.ts | 15 + packages/rstack/tests/fmt/runner.test.ts | 19 +- packages/rstack/tests/fmt/runnerCache.test.ts | 77 ++-- .../tests/fmt/runnerWorkerPreflight.test.ts | 53 ++- 5 files changed, 183 insertions(+), 340 deletions(-) diff --git a/packages/create-rstack/tests/create.test.ts b/packages/create-rstack/tests/create.test.ts index a0e28522..d215dbf9 100644 --- a/packages/create-rstack/tests/create.test.ts +++ b/packages/create-rstack/tests/create.test.ts @@ -18,9 +18,73 @@ const templatesWithoutTypeCheck = new Set([ 'lib-vue-ts', ]); +type ProjectPackage = { + name: string; + scripts: Record; +}; + +type SourceTemplate = { + template: string; + sourceExtension: string; + testFile: string; +}; + +const sourceTemplates: SourceTemplate[] = [ + { template: 'app-vanilla-js', sourceExtension: 'js', testFile: 'dom.test.js' }, + { template: 'app-vanilla-ts', sourceExtension: 'ts', testFile: 'dom.test.ts' }, + { template: 'app-react-js', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, + { template: 'app-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { template: 'app-preact-js', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, + { template: 'app-preact-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { template: 'app-vue-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'app-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'app-lit-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'app-lit-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'app-svelte-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'app-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'app-solid-js', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, + { template: 'app-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { template: 'lib-node-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'lib-node-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'lib-react-js', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, + { template: 'lib-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { template: 'lib-vue-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'lib-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'lib-svelte-js', sourceExtension: 'js', testFile: 'index.test.js' }, + { template: 'lib-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { template: 'lib-solid-js', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, + { template: 'lib-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, +]; + +const docTemplates = [ + { + template: 'doc-basic', + files: [ + 'README.md', + '.gitignore', + 'rstack.config.ts', + 'docs/index.md', + 'docs/guide/start/introduction.md', + ], + }, + { + template: 'doc-i18n', + files: ['rstack.config.ts', 'docs/en/index.md', 'docs/zh/index.md'], + }, +]; + const getCheckScript = (template: string, hasTypeScript: boolean): string => hasTypeScript && !templatesWithoutTypeCheck.has(template) ? tsCheckScript : jsCheckScript; +const readProjectPackage = async (projectDirectory: string): Promise => + JSON.parse(await readFile(path.join(projectDirectory, 'package.json'), 'utf8')) as ProjectPackage; + +const expectFiles = async (projectDirectory: string, files: string[]): Promise => { + for (const file of files) { + await expect(access(path.join(projectDirectory, file))).resolves.toBeUndefined(); + } +}; + const expectStagedSetup = async ( projectDirectory: string, configExtension: string, @@ -49,6 +113,26 @@ const expectNoStagedSetup = async ( ).not.toContain('define.staged({'); }; +const expectProjectSetup = async ( + projectDirectory: string, + template: string, + configExtension: string, + hasTypeScript: boolean, +): Promise => { + const packageJson = await readProjectPackage(projectDirectory); + + expect(packageJson.name).toBe('my-app'); + expect(packageJson.scripts.check).toBe(getCheckScript(template, hasTypeScript)); + await expectStagedSetup(projectDirectory, configExtension, packageJson.scripts); + + const tsconfig = access(path.join(projectDirectory, 'tsconfig.json')); + if (hasTypeScript) { + await expect(tsconfig).resolves.toBeUndefined(); + } else { + await expect(tsconfig).rejects.toThrow(); + } +}; + afterEach(async () => { await Promise.all( tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })), @@ -101,270 +185,35 @@ test.each([ }, ])('omits staged setup when $scenario', async ({ options }) => { const projectDirectory = await createProject('app-vanilla-ts', options); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); + const packageJson = await readProjectPackage(projectDirectory); await expectNoStagedSetup(projectDirectory, 'ts', packageJson.scripts); }); -test.each([ - { - template: 'app-vanilla-js', - configExtension: 'js', - sourceExtension: 'js', - testFile: 'dom.test.js', - hasTypeScript: false, - }, - { - template: 'app-vanilla-ts', - configExtension: 'ts', - sourceExtension: 'ts', - testFile: 'dom.test.ts', - hasTypeScript: true, - }, - { - template: 'app-react-js', - configExtension: 'js', - sourceExtension: 'jsx', - testFile: 'index.test.jsx', - hasTypeScript: false, - }, - { - template: 'app-react-ts', - configExtension: 'ts', - sourceExtension: 'tsx', - testFile: 'index.test.tsx', - hasTypeScript: true, - }, - { - template: 'app-preact-js', - configExtension: 'js', - sourceExtension: 'jsx', - testFile: 'index.test.jsx', - hasTypeScript: false, - }, - { - template: 'app-preact-ts', - configExtension: 'ts', - sourceExtension: 'tsx', - testFile: 'index.test.tsx', - hasTypeScript: true, - }, - { - template: 'app-vue-js', - configExtension: 'js', - sourceExtension: 'js', - testFile: 'index.test.js', - hasTypeScript: false, - }, - { - template: 'app-vue-ts', - configExtension: 'ts', - sourceExtension: 'ts', - testFile: 'index.test.ts', - hasTypeScript: true, - }, - { - template: 'app-lit-js', - configExtension: 'js', - sourceExtension: 'js', - testFile: 'index.test.js', - hasTypeScript: false, - }, - { - template: 'app-lit-ts', - configExtension: 'ts', - sourceExtension: 'ts', - testFile: 'index.test.ts', - hasTypeScript: true, - }, - { - template: 'app-svelte-js', - configExtension: 'js', - sourceExtension: 'js', - testFile: 'index.test.js', - hasTypeScript: false, - }, - { - template: 'app-svelte-ts', - configExtension: 'ts', - sourceExtension: 'ts', - testFile: 'index.test.ts', - hasTypeScript: true, - }, - { - template: 'app-solid-js', - configExtension: 'js', - sourceExtension: 'jsx', - testFile: 'index.test.jsx', - hasTypeScript: false, - }, - { - template: 'app-solid-ts', - configExtension: 'ts', - sourceExtension: 'tsx', - testFile: 'index.test.tsx', - hasTypeScript: true, - }, -])( +test.each(sourceTemplates)( 'creates the $template template', - async ({ template, configExtension, sourceExtension, testFile, hasTypeScript }) => { + async ({ template, sourceExtension, testFile }) => { + const hasTypeScript = template.endsWith('-ts'); + const configExtension = hasTypeScript ? 'ts' : 'js'; const projectDirectory = await createProject(template); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); - - expect(packageJson.name).toBe('my-app'); - expect(packageJson.scripts.check).toBe(getCheckScript(template, hasTypeScript)); - await expectStagedSetup(projectDirectory, configExtension, packageJson.scripts); - - await expect(access(path.join(projectDirectory, 'README.md'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, '.gitignore'))).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `rstack.config.${configExtension}`)), - ).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `src/index.${sourceExtension}`)), - ).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, 'tests', testFile))).resolves.toBeUndefined(); - - const tsconfigPath = path.join(projectDirectory, 'tsconfig.json'); - if (hasTypeScript) { - await expect(access(tsconfigPath)).resolves.toBeUndefined(); - } else { - await expect(access(tsconfigPath)).rejects.toThrow(); + const files = [ + `rstack.config.${configExtension}`, + `src/index.${sourceExtension}`, + `tests/${testFile}`, + ]; + + if (template.startsWith('app-')) { + files.push('README.md', '.gitignore'); } + + await expectProjectSetup(projectDirectory, template, configExtension, hasTypeScript); + await expectFiles(projectDirectory, files); }, ); -test('creates the doc-basic template', async () => { - const projectDirectory = await createProject('doc-basic'); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); - - expect(packageJson.name).toBe('my-app'); - expect(packageJson.scripts.check).toBe(tsCheckScript); - await expectStagedSetup(projectDirectory, 'ts', packageJson.scripts); - - await expect(access(path.join(projectDirectory, 'README.md'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, '.gitignore'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, 'rstack.config.ts'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, 'tsconfig.json'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, 'docs', 'index.md'))).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, 'docs', 'guide', 'start', 'introduction.md')), - ).resolves.toBeUndefined(); -}); - -test('creates the doc-i18n template', async () => { - const projectDirectory = await createProject('doc-i18n'); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); - - expect(packageJson.name).toBe('my-app'); - expect(packageJson.scripts.check).toBe(tsCheckScript); - await expectStagedSetup(projectDirectory, 'ts', packageJson.scripts); +test.each(docTemplates)('creates the $template template', async ({ template, files }) => { + const projectDirectory = await createProject(template); - await expect(access(path.join(projectDirectory, 'rstack.config.ts'))).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, 'docs', 'en', 'index.md')), - ).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, 'docs', 'zh', 'index.md')), - ).resolves.toBeUndefined(); + await expectProjectSetup(projectDirectory, template, 'ts', true); + await expectFiles(projectDirectory, files); }); - -test.each([ - { - template: 'lib-node-js', - configExtension: 'js', - sourceExtension: 'js', - hasTypeScript: false, - }, - { - template: 'lib-node-ts', - configExtension: 'ts', - sourceExtension: 'ts', - hasTypeScript: true, - }, - { - template: 'lib-react-js', - configExtension: 'js', - sourceExtension: 'jsx', - hasTypeScript: false, - }, - { - template: 'lib-react-ts', - configExtension: 'ts', - sourceExtension: 'tsx', - hasTypeScript: true, - }, - { - template: 'lib-vue-js', - configExtension: 'js', - sourceExtension: 'js', - hasTypeScript: false, - }, - { - template: 'lib-vue-ts', - configExtension: 'ts', - sourceExtension: 'ts', - hasTypeScript: true, - }, - { - template: 'lib-svelte-js', - configExtension: 'js', - sourceExtension: 'js', - hasTypeScript: false, - }, - { - template: 'lib-svelte-ts', - configExtension: 'ts', - sourceExtension: 'ts', - hasTypeScript: true, - }, - { - template: 'lib-solid-js', - configExtension: 'js', - sourceExtension: 'jsx', - hasTypeScript: false, - }, - { - template: 'lib-solid-ts', - configExtension: 'ts', - sourceExtension: 'tsx', - hasTypeScript: true, - }, -])( - 'creates the $template template', - async ({ template, configExtension, sourceExtension, hasTypeScript }) => { - const projectDirectory = await createProject(template); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); - - expect(packageJson.name).toBe('my-app'); - expect(packageJson.scripts.check).toBe(getCheckScript(template, hasTypeScript)); - await expectStagedSetup(projectDirectory, configExtension, packageJson.scripts); - - await expect( - access(path.join(projectDirectory, `rstack.config.${configExtension}`)), - ).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `src/index.${sourceExtension}`)), - ).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `tests/index.test.${sourceExtension}`)), - ).resolves.toBeUndefined(); - - const tsconfigPath = path.join(projectDirectory, 'tsconfig.json'); - if (hasTypeScript) { - await expect(access(tsconfigPath)).resolves.toBeUndefined(); - } else { - await expect(access(tsconfigPath)).rejects.toThrow(); - } - }, -); diff --git a/packages/rstack/tests/fmt/helpers.ts b/packages/rstack/tests/fmt/helpers.ts index 89d280c5..2a6c8ac3 100644 --- a/packages/rstack/tests/fmt/helpers.ts +++ b/packages/rstack/tests/fmt/helpers.ts @@ -1,5 +1,20 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import path from 'node:path'; +import { fmtCacheFileName } from '../../src/fmt/cacheStore.ts'; +import type { FmtCacheContext, FmtFileRequest, ResolvedFmtOptions } from '../../src/fmt/types.ts'; + +export const createFmtRequest = ( + filePath: string, + options: ResolvedFmtOptions = { parser: 'typescript' }, +): FmtFileRequest => ({ + path: filePath, + options, +}); + +export const createFmtCacheContext = (rootPath: string): FmtCacheContext => ({ + filePath: path.join(rootPath, '.rstack', 'cache', 'fmt', fmtCacheFileName), + rootPath, +}); export const withTempProject = async ( callback: (rootPath: string) => Promise, diff --git a/packages/rstack/tests/fmt/runner.test.ts b/packages/rstack/tests/fmt/runner.test.ts index 7a31d387..dbcd59e6 100644 --- a/packages/rstack/tests/fmt/runner.test.ts +++ b/packages/rstack/tests/fmt/runner.test.ts @@ -3,14 +3,7 @@ import path from 'node:path'; import { expect, test } from 'rstack/test'; import { runFmtFiles } from '../../src/fmt/runner.ts'; import type { FmtFileRequest, FmtMode } from '../../src/fmt/types.ts'; -import { withTempProject } from './helpers.ts'; - -const createRequest = (filePath: string): FmtFileRequest => ({ - path: filePath, - options: { - parser: 'typescript', - }, -}); +import { createFmtRequest, withTempProject } from './helpers.ts'; const run = (files: FmtFileRequest[], mode: FmtMode = 'write') => runFmtFiles({ @@ -26,7 +19,7 @@ test('does not rewrite unchanged files', async () => { utimesSync(filePath, timestamp, timestamp); const mtimeMs = statSync(filePath).mtimeMs; - const result = await run([createRequest(filePath)]); + const result = await run([createFmtRequest(filePath)]); expect(result).toMatchObject({ exitCode: 0, @@ -42,7 +35,7 @@ test('writes changed files', async () => { const filePath = path.join(rootPath, 'changed.ts'); writeFileSync(filePath, 'const value=1'); - const result = await run([createRequest(filePath)]); + const result = await run([createFmtRequest(filePath)]); expect(result).toMatchObject({ exitCode: 0, @@ -59,7 +52,7 @@ test.runIf(process.platform !== 'win32')('preserves file mode when writing', asy writeFileSync(filePath, 'const value=1'); chmodSync(filePath, 0o744); - await run([createRequest(filePath)]); + await run([createFmtRequest(filePath)]); expect(statSync(filePath).mode & 0o777).toBe(0o744); }); @@ -72,7 +65,7 @@ for (const mode of ['check', 'list-different'] as const) { const source = 'const value=1'; writeFileSync(filePath, source); - const result = await run([createRequest(filePath)], mode); + const result = await run([createFmtRequest(filePath)], mode); expect(result).toMatchObject({ exitCode: 1, @@ -91,7 +84,7 @@ test('continues after a file fails and gives errors exit-code precedence', async writeFileSync(invalidPath, 'const value = ;'); writeFileSync(validPath, 'const value=1'); - const result = await run([createRequest(invalidPath), createRequest(validPath)], 'check'); + const result = await run([createFmtRequest(invalidPath), createFmtRequest(validPath)], 'check'); expect(result).toMatchObject({ exitCode: 2, diff --git a/packages/rstack/tests/fmt/runnerCache.test.ts b/packages/rstack/tests/fmt/runnerCache.test.ts index 943b225c..f9432a94 100644 --- a/packages/rstack/tests/fmt/runnerCache.test.ts +++ b/packages/rstack/tests/fmt/runnerCache.test.ts @@ -5,26 +5,13 @@ import { expect, test } from 'rstack/test'; import { cacheNamespace, createOptionsHasher, sha256 } from '../../src/fmt/cacheIdentity.ts'; import { loadFmtCacheStore } from '../../src/fmt/cacheStore.ts'; import { runFmtFiles } from '../../src/fmt/runner.ts'; -import type { - FmtCacheContext, - FmtFileRequest, - FmtMode, - ResolvedFmtOptions, -} from '../../src/fmt/types.ts'; -import { withTempProject, writeProjectFile } from './helpers.ts'; - -const createRequest = ( - filePath: string, - options: ResolvedFmtOptions = { parser: 'typescript' }, -): FmtFileRequest => ({ - path: filePath, - options, -}); - -const createCache = (rootPath: string): FmtCacheContext => ({ - filePath: path.join(rootPath, 'cache', 'fmt-v1.json'), - rootPath, -}); +import type { FmtCacheContext, FmtFileRequest, FmtMode } from '../../src/fmt/types.ts'; +import { + createFmtCacheContext, + createFmtRequest, + withTempProject, + writeProjectFile, +} from './helpers.ts'; const run = (files: FmtFileRequest[], mode: FmtMode, cache: FmtCacheContext) => runFmtFiles({ files, mode, cache }); @@ -34,11 +21,11 @@ for (const mode of ['check', 'list-different'] as const) { await withTempProject(async (rootPath) => { const cleanPath = path.join(rootPath, 'clean.ts'); const dirtyPath = path.join(rootPath, 'dirty.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); writeFileSync(cleanPath, 'const clean = 1;\n'); writeFileSync(dirtyPath, 'const dirty=1'); - const files = [createRequest(cleanPath), createRequest(dirtyPath)]; + const files = [createFmtRequest(cleanPath), createFmtRequest(dirtyPath)]; const first = await run(files, mode, cache); expect(first).toMatchObject({ @@ -67,14 +54,14 @@ for (const mode of ['check', 'list-different'] as const) { test('uses content hashes instead of file metadata', async () => { await withTempProject(async (rootPath) => { const filePath = path.join(rootPath, 'index.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); const timestamp = new Date('2020-01-01T00:00:00.000Z'); const clean = 'const value = 1;\n'; const dirty = 'const value= 1;\n'; writeFileSync(filePath, clean); utimesSync(filePath, timestamp, timestamp); - await run([createRequest(filePath)], 'check', cache); + await run([createFmtRequest(filePath)], 'check', cache); const firstStore = await loadFmtCacheStore(cache.filePath, cacheNamespace); const firstEntry = firstStore.get('index.ts'); @@ -85,7 +72,7 @@ test('uses content hashes instead of file metadata', async () => { size: Buffer.byteLength(clean), }); - await expect(run([createRequest(filePath)], 'check', cache)).resolves.toMatchObject({ + await expect(run([createFmtRequest(filePath)], 'check', cache)).resolves.toMatchObject({ exitCode: 1, files: [{ path: filePath, status: 'different' }], }); @@ -100,13 +87,13 @@ test('uses content hashes instead of file metadata', async () => { test('invalidates entries when final options change', async () => { await withTempProject(async (rootPath) => { const filePath = path.join(rootPath, 'index.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); writeFileSync(filePath, 'const value = "text";\n'); - const initial = createRequest(filePath, { parser: 'typescript', singleQuote: false }); + const initial = createFmtRequest(filePath, { parser: 'typescript', singleQuote: false }); await run([initial], 'check', cache); - const changed = createRequest(filePath, { parser: 'typescript', singleQuote: true }); + const changed = createFmtRequest(filePath, { parser: 'typescript', singleQuote: true }); await expect(run([changed], 'check', cache)).resolves.toMatchObject({ exitCode: 1, files: [{ path: filePath, status: 'different' }], @@ -124,8 +111,8 @@ test('invalidates entries when final options change', async () => { test('caches unsupported parser results until final options change', async () => { await withTempProject(async (rootPath) => { const filePath = writeProjectFile(rootPath, 'data.unknown', '{"value":true}'); - const cache = createCache(rootPath); - const unsupported = createRequest(filePath, {}); + const cache = createFmtCacheContext(rootPath); + const unsupported = createFmtRequest(filePath, {}); const first = await run([unsupported], 'check', cache); expect(first).toEqual({ @@ -141,7 +128,7 @@ test('caches unsupported parser results until final options change', async () => await expect(run([unsupported], 'check', cache)).resolves.toEqual(first); - const supported = createRequest(filePath, { parser: 'json' }); + const supported = createFmtRequest(filePath, { parser: 'json' }); await expect(run([supported], 'check', cache)).resolves.toMatchObject({ exitCode: 1, files: [{ path: filePath, status: 'different' }], @@ -158,8 +145,8 @@ test('caches unsupported parser results until final options change', async () => test('invalidates cached unsupported parser results when content changes without an extension', async () => { await withTempProject(async (rootPath) => { const filePath = writeProjectFile(rootPath, 'script', 'plain text\n'); - const cache = createCache(rootPath); - const file = createRequest(filePath, {}); + const cache = createFmtCacheContext(rootPath); + const file = createFmtRequest(filePath, {}); const first = await run([file], 'check', cache); expect(first).toEqual({ @@ -211,8 +198,8 @@ test('caches only plugins with stable fingerprints', async () => { ...(version ? { version } : {}), }), ); - const cache = createCache(rootPath); - const file = createRequest(filePath, { plugins: [pathToFileURL(pluginEntry).href] }); + const cache = createFmtCacheContext(rootPath); + const file = createFmtRequest(filePath, { plugins: [pathToFileURL(pluginEntry).href] }); writePackageJson(); await run([file], 'check', cache); @@ -241,16 +228,16 @@ test('preserves entries outside the formatted subset', async () => { await withTempProject(async (rootPath) => { const firstPath = path.join(rootPath, 'first.ts'); const secondPath = path.join(rootPath, 'second.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); writeFileSync(firstPath, 'const first = 1;\n'); writeFileSync(secondPath, 'const second = 2;\n'); - await run([createRequest(firstPath), createRequest(secondPath)], 'check', cache); + await run([createFmtRequest(firstPath), createFmtRequest(secondPath)], 'check', cache); const firstStore = await loadFmtCacheStore(cache.filePath, cacheNamespace); const secondEntry = firstStore.get('second.ts'); writeFileSync(firstPath, 'const first=1'); - await run([createRequest(firstPath)], 'check', cache); + await run([createFmtRequest(firstPath)], 'check', cache); const secondStore = await loadFmtCacheStore(cache.filePath, cacheNamespace); expect(secondStore.get('second.ts')).toEqual(secondEntry); @@ -261,12 +248,12 @@ test('does not cache formatting errors', async () => { await withTempProject(async (rootPath) => { const validPath = path.join(rootPath, 'valid.ts'); const invalidPath = path.join(rootPath, 'invalid.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); writeFileSync(validPath, 'const valid = 1;\n'); writeFileSync(invalidPath, 'const invalid = ;'); - await run([createRequest(validPath)], 'check', cache); - await expect(run([createRequest(invalidPath)], 'check', cache)).resolves.toMatchObject({ + await run([createFmtRequest(validPath)], 'check', cache); + await expect(run([createFmtRequest(invalidPath)], 'check', cache)).resolves.toMatchObject({ exitCode: 2, files: [{ path: invalidPath, status: 'error' }], }); @@ -281,11 +268,11 @@ test('write persists clean results for misses and hits', async () => { await withTempProject(async (rootPath) => { const cleanPath = path.join(rootPath, 'clean.ts'); const dirtyPath = path.join(rootPath, 'dirty.ts'); - const cache = createCache(rootPath); + const cache = createFmtCacheContext(rootPath); writeFileSync(cleanPath, 'const clean = 1;\n'); writeFileSync(dirtyPath, 'const dirty=1'); - const files = [createRequest(cleanPath), createRequest(dirtyPath)]; + const files = [createFmtRequest(cleanPath), createFmtRequest(dirtyPath)]; await expect(run(files, 'write', cache)).resolves.toMatchObject({ exitCode: 0, files: [{ path: dirtyPath, status: 'written' }], @@ -317,8 +304,8 @@ test('write persists clean results for misses and hits', async () => { test('write converts a dirty entry to clean', async () => { await withTempProject(async (rootPath) => { const filePath = path.join(rootPath, 'index.ts'); - const cache = createCache(rootPath); - const file = createRequest(filePath); + const cache = createFmtCacheContext(rootPath); + const file = createFmtRequest(filePath); writeFileSync(filePath, 'const value=1'); await run([file], 'check', cache); diff --git a/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts b/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts index 9d21cb4c..ce433ec1 100644 --- a/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts +++ b/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts @@ -1,10 +1,13 @@ -import path from 'node:path'; import { beforeEach, expect, rs, test } from 'rstack/test'; import { cacheNamespace, createOptionsHasher } from '../../src/fmt/cacheIdentity.ts'; import { loadFmtCacheStore } from '../../src/fmt/cacheStore.ts'; import { runFmtFiles } from '../../src/fmt/runner.ts'; -import type { FmtFileRequest } from '../../src/fmt/types.ts'; -import { withTempProject, writeProjectFile } from './helpers.ts'; +import { + createFmtCacheContext, + createFmtRequest, + withTempProject, + writeProjectFile, +} from './helpers.ts'; const mocks = rs.hoisted(() => ({ createFmtWorkerPoolCalls: [] as [number, number | undefined][], @@ -21,25 +24,31 @@ beforeEach(() => { mocks.createFmtWorkerPoolCalls.length = 0; }); +const createCachedUnsupportedFile = async (rootPath: string, fileName: string) => { + const filePath = writeProjectFile(rootPath, fileName, 'plain text'); + const cache = createFmtCacheContext(rootPath); + const file = createFmtRequest(filePath, {}); + const optionsHash = createOptionsHasher()(file.options); + if (optionsHash === undefined) { + throw new Error('Expected cacheable formatter options.'); + } + + const store = await loadFmtCacheStore(cache.filePath, cacheNamespace); + store.set(fileName, [null, optionsHash, 'unsupported']); + await expect(store.save()).resolves.toBe(true); + + return { cache, file }; +}; + test('does not start the worker pool when every parser result is cached as unsupported', async () => { await withTempProject(async (rootPath) => { - const filePath = writeProjectFile(rootPath, 'example.unknown', 'plain text'); - const cachePath = path.join(rootPath, 'cache', 'fmt-v1.json'); - const file: FmtFileRequest = { path: filePath, options: {} }; - const optionsHash = createOptionsHasher()(file.options); - if (optionsHash === undefined) { - throw new Error('Expected cacheable formatter options.'); - } - - const store = await loadFmtCacheStore(cachePath, cacheNamespace); - store.set('example.unknown', [null, optionsHash, 'unsupported']); - await expect(store.save()).resolves.toBe(true); + const { cache, file } = await createCachedUnsupportedFile(rootPath, 'example.unknown'); await expect( runFmtFiles({ files: [file], mode: 'check', - cache: { filePath: cachePath, rootPath }, + cache, }), ).resolves.toEqual({ exitCode: 2, @@ -52,23 +61,13 @@ test('does not start the worker pool when every parser result is cached as unsup test('starts the worker pool for a path-only unsupported entry without an extension', async () => { await withTempProject(async (rootPath) => { - const filePath = writeProjectFile(rootPath, 'script', 'plain text'); - const cachePath = path.join(rootPath, 'cache', 'fmt-v1.json'); - const file: FmtFileRequest = { path: filePath, options: {} }; - const optionsHash = createOptionsHasher()(file.options); - if (optionsHash === undefined) { - throw new Error('Expected cacheable formatter options.'); - } - - const store = await loadFmtCacheStore(cachePath, cacheNamespace); - store.set('script', [null, optionsHash, 'unsupported']); - await expect(store.save()).resolves.toBe(true); + const { cache, file } = await createCachedUnsupportedFile(rootPath, 'script'); await expect( runFmtFiles({ files: [file], mode: 'check', - cache: { filePath: cachePath, rootPath }, + cache, }), ).rejects.toThrow('worker startup failed'); expect(mocks.createFmtWorkerPoolCalls).toEqual([[1, undefined]]);