From ebb63abc9baca67a65ceb270133e6ecbe30e8bae Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Mar 2026 13:49:24 -0300 Subject: [PATCH 1/5] moving is error test logic into utilities.js --- tests/config/constants.js | 2 ++ tests/config/run-format-test.js | 54 +++++++++++++++------------------ tests/config/utilities.js | 5 ++- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/tests/config/constants.js b/tests/config/constants.js index a1dc3adf7..aa73184cd 100644 --- a/tests/config/constants.js +++ b/tests/config/constants.js @@ -4,6 +4,8 @@ import { normalizeDirectory } from "./utilities.js"; const { __dirname } = createEsmUtils(import.meta); +export const FORMAT_SCRIPT_FILENAME = "format.test.js"; + export const FORMAT_TEST_DIRECTORY = normalizeDirectory( path.join(__dirname, "../format/"), ); diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js index 1e7ee413f..2c68bb2f3 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -1,47 +1,41 @@ import fs from "node:fs"; import path from "node:path"; import url from "node:url"; -import createEsmUtils from "esm-utils"; +import { FORMAT_SCRIPT_FILENAME } from "./constants.js"; import { stringifyOptionsForTitle } from "./utils/stringify-options-for-title.js"; +import { + isErrorTest as isErrorTestDirectory, + normalizeDirectory, +} from "./utilities.js"; import { format } from "./run-prettier.js"; import { runTest } from "./run-test.js"; import { shouldThrowOnFormat } from "./utilities.js"; -const { __dirname } = createEsmUtils(import.meta); - -const isTestDirectory = (dirname, name) => - (dirname + path.sep).startsWith( - path.join(__dirname, "../format", name) + path.sep, - ); - -function runFormatTest(fixtures, parsers, options) { - let { importMeta, snippets = [] } = fixtures.importMeta - ? fixtures - : { importMeta: fixtures }; +function runFormatTest(rawFixtures, explicitParsers, rawOptions) { + let { importMeta, snippets = [] } = rawFixtures.importMeta + ? rawFixtures + : { importMeta: rawFixtures }; const filename = path.basename(new URL(importMeta.url).pathname); - if (filename !== "format.test.js") { - throw new Error(`Format test should run in file named 'format.test.js'.`); + if (filename !== FORMAT_SCRIPT_FILENAME) { + throw new Error( + `Format test should run in file named '${FORMAT_SCRIPT_FILENAME}'.`, + ); } - const dirname = path.dirname(url.fileURLToPath(importMeta.url)); - - // `IS_PARSER_INFERENCE_TESTS` mean to test `inferParser` on `standalone` - const IS_PARSER_INFERENCE_TESTS = isTestDirectory( - dirname, - "misc/parser-inference", + const dirname = normalizeDirectory( + path.dirname(url.fileURLToPath(importMeta.url)), ); - // `IS_ERROR_TESTS` mean to watch errors like: + let options = { ...rawOptions }; + + // `IS_ERROR_TEST` mean to watch errors like: // - syntax parser hasn't supported yet // - syntax errors that should throws - const IS_ERROR_TESTS = isTestDirectory(dirname, "misc/errors"); - if (IS_ERROR_TESTS) { - options = { errors: true, ...options }; - } + const isErrorTest = isErrorTestDirectory(dirname); - if (IS_PARSER_INFERENCE_TESTS) { - parsers = [undefined]; + if (isErrorTest) { + options = { errors: true, ...options }; } snippets = snippets.map((test, index) => { @@ -83,8 +77,8 @@ function runFormatTest(fixtures, parsers, options) { }) .filter(Boolean); - const [parser] = parsers; - const allParsers = [...parsers]; + const [parser] = explicitParsers; + const allParsers = [...explicitParsers]; const stringifiedOptions = stringifyOptionsForTitle(options); @@ -123,7 +117,7 @@ function runFormatTest(fixtures, parsers, options) { test(testTitle, async () => { await runTest({ - parsers, + parsers: explicitParsers, name, filename, code, diff --git a/tests/config/utilities.js b/tests/config/utilities.js index 2c335bf43..a8a63e1f9 100644 --- a/tests/config/utilities.js +++ b/tests/config/utilities.js @@ -2,6 +2,9 @@ import path from "node:path"; const normalizeDirectory = (directory) => path.normalize(directory + path.sep); +const isErrorTest = (dirname) => + normalizeDirectory(dirname).includes(`${path.sep}_errors_${path.sep}`); + const shouldThrowOnFormat = (filename, options) => { const { errors = {}, parser } = options; if (errors === true) { @@ -17,4 +20,4 @@ const shouldThrowOnFormat = (filename, options) => { return false; }; -export { normalizeDirectory, shouldThrowOnFormat }; +export { normalizeDirectory, isErrorTest, shouldThrowOnFormat }; From 5a1aa15612fb2e801aa3ca8f2f9d11e1a2394db8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Mar 2026 15:16:24 -0300 Subject: [PATCH 2/5] moving get fixtures logic into its own file --- tests/config/get-fixtures.js | 52 ++++++++++++++++++++++++++++++ tests/config/run-format-test.js | 57 ++++++++------------------------- 2 files changed, 65 insertions(+), 44 deletions(-) create mode 100644 tests/config/get-fixtures.js diff --git a/tests/config/get-fixtures.js b/tests/config/get-fixtures.js new file mode 100644 index 000000000..8dbca69e7 --- /dev/null +++ b/tests/config/get-fixtures.js @@ -0,0 +1,52 @@ +import fs from "node:fs"; +import path from "node:path"; +import { FORMAT_SCRIPT_FILENAME } from "./constants.js"; + +function getFixtures(context) { + return [...getFiles(context), ...getSnippets(context)]; +} + +function getFiles({ dirname }) { + return fs + .readdirSync(dirname, { withFileTypes: true }) + .map((file) => { + const basename = file.name; + const filename = path.join(dirname, basename); + if ( + path.extname(basename) === ".snap" || + !file.isFile() || + basename[0] === "." || + basename === FORMAT_SCRIPT_FILENAME || + // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 + basename === "debug.log" + ) { + return; + } + + const text = fs.readFileSync(filename, "utf8"); + + return { + name: basename, + filename, + code: text, + }; + }) + .filter(Boolean); +} + +function getSnippets({ snippets }) { + return snippets.map((test, index) => { + test = typeof test === "string" ? { code: test } : test; + + if (typeof test.code !== "string") { + throw Object.assign(new Error("Invalid test"), { test }); + } + + return { + ...test, + name: `snippet: ${test.name || `#${index}`}`, + }; + }); +} + +export { getFixtures }; diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js index 2c68bb2f3..e463ef739 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -1,7 +1,7 @@ -import fs from "node:fs"; import path from "node:path"; import url from "node:url"; import { FORMAT_SCRIPT_FILENAME } from "./constants.js"; +import { getFixtures } from "./get-fixtures.js"; import { stringifyOptionsForTitle } from "./utils/stringify-options-for-title.js"; import { isErrorTest as isErrorTestDirectory, @@ -38,53 +38,22 @@ function runFormatTest(rawFixtures, explicitParsers, rawOptions) { options = { errors: true, ...options }; } - snippets = snippets.map((test, index) => { - test = typeof test === "string" ? { code: test } : test; - - if (typeof test.code !== "string") { - throw Object.assign(new Error("Invalid test"), { test }); - } - - return { - ...test, - name: `snippet: ${test.name || `#${index}`}`, - }; - }); - - const files = fs - .readdirSync(dirname, { withFileTypes: true }) - .map((file) => { - const basename = file.name; - const filename = path.join(dirname, basename); - if ( - path.extname(basename) === ".snap" || - !file.isFile() || - basename[0] === "." || - basename === "format.test.js" || - // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 - basename === "debug.log" - ) { - return; - } - - const text = fs.readFileSync(filename, "utf8"); - - return { - name: basename, - filename, - code: text, - }; - }) - .filter(Boolean); - const [parser] = explicitParsers; const allParsers = [...explicitParsers]; - const stringifiedOptions = stringifyOptionsForTitle(options); - - for (const { name, filename, code, output } of [...files, ...snippets]) { + const context = { + dirname, + stringifiedOptions: stringifyOptionsForTitle(rawOptions), + parsers: allParsers, + options, + explicitParsers, + rawOptions, + snippets, + }; + + for (const { name, filename, code, output } of getFixtures(context)) { const title = `${name}${ - stringifiedOptions ? ` - ${stringifiedOptions}` : "" + context.stringifiedOptions ? ` - ${context.stringifiedOptions}` : "" }`; describe(title, () => { From 0b469b1734c1890e07a49f22be5d721d1867b480 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Mar 2026 16:45:21 -0300 Subject: [PATCH 3/5] updating the fixture logic. returning the new more complete fixture object and using a generator pattern to avoid calling `filter(Boolean)` over the array. --- tests/config/get-fixtures.js | 88 +++++++++++++++++++-------------- tests/config/run-format-test.js | 2 +- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/tests/config/get-fixtures.js b/tests/config/get-fixtures.js index 8dbca69e7..fd4f02e0f 100644 --- a/tests/config/get-fixtures.js +++ b/tests/config/get-fixtures.js @@ -1,52 +1,64 @@ import fs from "node:fs"; import path from "node:path"; import { FORMAT_SCRIPT_FILENAME } from "./constants.js"; +import visualizeEndOfLine from "./utils/visualize-end-of-line.js"; -function getFixtures(context) { - return [...getFiles(context), ...getSnippets(context)]; +function* getFixtures(context) { + yield* getFiles(context); + yield* getSnippets(context); } -function getFiles({ dirname }) { - return fs - .readdirSync(dirname, { withFileTypes: true }) - .map((file) => { - const basename = file.name; - const filename = path.join(dirname, basename); - if ( - path.extname(basename) === ".snap" || - !file.isFile() || - basename[0] === "." || - basename === FORMAT_SCRIPT_FILENAME || - // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 - basename === "debug.log" - ) { - return; - } - - const text = fs.readFileSync(filename, "utf8"); - - return { - name: basename, - filename, - code: text, - }; - }) - .filter(Boolean); +function* getFiles(context) { + const { dirname } = context; + for (const file of fs.readdirSync(dirname, { withFileTypes: true })) { + const filename = file.name; + const filepath = path.join(dirname, filename); + if ( + path.extname(filename) === ".snap" || + !file.isFile() || + filename[0] === "." || + filename === FORMAT_SCRIPT_FILENAME || + // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 + filename === "debug.log" + ) { + continue; + } + + const text = fs.readFileSync(filepath, "utf8"); + + yield { + context, + name: filename, + filename, + filepath, + code: text, + }; + } } -function getSnippets({ snippets }) { - return snippets.map((test, index) => { - test = typeof test === "string" ? { code: test } : test; +function* getSnippets(context) { + for (const [index, snippet] of context.snippets.entries()) { + const testCase = + typeof snippet === "string" + ? { code: snippet, context } + : { ...snippet, context }; - if (typeof test.code !== "string") { - throw Object.assign(new Error("Invalid test"), { test }); + if (typeof testCase.code !== "string") { + throw Object.assign(new Error("Invalid test"), { snippet }); } - return { - ...test, - name: `snippet: ${test.name || `#${index}`}`, - }; - }); + if (typeof testCase.output === "string") { + testCase.output = visualizeEndOfLine(testCase.output); + } + + testCase.name = `snippet: ${testCase.name || `#${index}`}`; + + if (typeof testCase.filename === "string") { + testCase.filepath = path.join(context.dirname, testCase.filename); + } + + yield testCase; + } } export { getFixtures }; diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js index e463ef739..ddd0d051a 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -12,7 +12,7 @@ import { runTest } from "./run-test.js"; import { shouldThrowOnFormat } from "./utilities.js"; function runFormatTest(rawFixtures, explicitParsers, rawOptions) { - let { importMeta, snippets = [] } = rawFixtures.importMeta + const { importMeta, snippets = [] } = rawFixtures.importMeta ? rawFixtures : { importMeta: rawFixtures }; From 4164a6b070b6f612d798fa1c9af23614d77bfe63 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Mar 2026 16:48:59 -0300 Subject: [PATCH 4/5] fail early --- tests/config/get-fixtures.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/config/get-fixtures.js b/tests/config/get-fixtures.js index fd4f02e0f..405632b7f 100644 --- a/tests/config/get-fixtures.js +++ b/tests/config/get-fixtures.js @@ -14,12 +14,12 @@ function* getFiles(context) { const filename = file.name; const filepath = path.join(dirname, filename); if ( - path.extname(filename) === ".snap" || !file.isFile() || filename[0] === "." || filename === FORMAT_SCRIPT_FILENAME || // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 - filename === "debug.log" + filename === "debug.log" || + path.extname(filename) === ".snap" ) { continue; } From 9b7cea7e3dedbf9be71c0a0b079825d1246137bc Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 12 Mar 2026 18:37:24 -0300 Subject: [PATCH 5/5] using the fixture approach, preparing to delegate this section --- tests/config/run-format-test.js | 87 +++++++++++++++++++-------------- tests/config/utilities.js | 2 +- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js index ddd0d051a..1804ab2e1 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -8,6 +8,7 @@ import { normalizeDirectory, } from "./utilities.js"; import { format } from "./run-prettier.js"; +import { replacePlaceholders } from "./replace-placeholders.js"; import { runTest } from "./run-test.js"; import { shouldThrowOnFormat } from "./utilities.js"; @@ -38,62 +39,44 @@ function runFormatTest(rawFixtures, explicitParsers, rawOptions) { options = { errors: true, ...options }; } - const [parser] = explicitParsers; - const allParsers = [...explicitParsers]; - const context = { dirname, stringifiedOptions: stringifyOptionsForTitle(rawOptions), - parsers: allParsers, + parsers: [...explicitParsers], options, explicitParsers, rawOptions, snippets, }; - for (const { name, filename, code, output } of getFixtures(context)) { + for (const fixture of getFixtures(context)) { + const { name, context, filepath } = fixture; + const { stringifiedOptions, parsers } = context; + const title = `${name}${ - context.stringifiedOptions ? ` - ${context.stringifiedOptions}` : "" + stringifiedOptions ? ` - ${stringifiedOptions}` : "" }`; describe(title, () => { - const formatOptions = { - printWidth: 80, - ...options, - filepath: filename, - parser, - }; - const shouldThrowOnMainParserFormat = shouldThrowOnFormat( - name, - formatOptions, - ); - - let mainParserFormatResult; - if (shouldThrowOnMainParserFormat) { - mainParserFormatResult = { options: formatOptions, error: true }; - } else { - beforeAll(async () => { - mainParserFormatResult = await format(code, formatOptions); - }); - } + const testCases = parsers.map((parser) => getTestCase(fixture, parser)); - for (const currentParser of allParsers) { + for (const testCase of testCases) { const testTitle = - shouldThrowOnMainParserFormat || - formatOptions.parser !== currentParser - ? `[${currentParser}] format` + testCase.expectFail || + testCase.formatOptions.parser !== testCase.parser + ? `[${testCase.parser}] format` : "format"; test(testTitle, async () => { await runTest({ - parsers: explicitParsers, + parsers, name, - filename, - code, - output, - parser: currentParser, - mainParserFormatResult, - mainParserFormatOptions: formatOptions, + filename: filepath, + code: testCase.code, + output: testCase.expectedOutput, + parser: testCase.parser, + mainParserFormatResult: await testCase.runFormat(), + mainParserFormatOptions: testCase.formatOptions, }); }); } @@ -101,4 +84,36 @@ function runFormatTest(rawFixtures, explicitParsers, rawOptions) { } } +function getTestCase(fixture, parser) { + const { code: originalText, context, filepath } = fixture; + + const { text: code, options: formatOptions } = replacePlaceholders( + originalText, + { + printWidth: 80, + ...context.options, + filepath, + parser, + }, + ); + + const expectFail = shouldThrowOnFormat(fixture, formatOptions); + + let promise; + + return { + context, + parser, + filepath, + originalText, + code, + formatOptions, + expectFail, + expectedOutput: fixture.output, + isEmpty: code.trim() === "", + runFormat: () => + promise === undefined ? (promise = format(code, formatOptions)) : promise, + }; +} + export default runFormatTest; diff --git a/tests/config/utilities.js b/tests/config/utilities.js index a8a63e1f9..e731a6bca 100644 --- a/tests/config/utilities.js +++ b/tests/config/utilities.js @@ -5,7 +5,7 @@ const normalizeDirectory = (directory) => path.normalize(directory + path.sep); const isErrorTest = (dirname) => normalizeDirectory(dirname).includes(`${path.sep}_errors_${path.sep}`); -const shouldThrowOnFormat = (filename, options) => { +const shouldThrowOnFormat = ({ filename }, options) => { const { errors = {}, parser } = options; if (errors === true) { return true;