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/get-fixtures.js b/tests/config/get-fixtures.js new file mode 100644 index 000000000..405632b7f --- /dev/null +++ b/tests/config/get-fixtures.js @@ -0,0 +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) { + yield* getFiles(context); + yield* getSnippets(context); +} + +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 ( + !file.isFile() || + filename[0] === "." || + filename === FORMAT_SCRIPT_FILENAME || + // VSCode creates this file sometime https://github.com/microsoft/vscode/issues/105191 + filename === "debug.log" || + path.extname(filename) === ".snap" + ) { + continue; + } + + const text = fs.readFileSync(filepath, "utf8"); + + yield { + context, + name: filename, + filename, + filepath, + code: text, + }; + } +} + +function* getSnippets(context) { + for (const [index, snippet] of context.snippets.entries()) { + const testCase = + typeof snippet === "string" + ? { code: snippet, context } + : { ...snippet, context }; + + if (typeof testCase.code !== "string") { + throw Object.assign(new Error("Invalid test"), { snippet }); + } + + 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 1e7ee413f..1804ab2e1 100644 --- a/tests/config/run-format-test.js +++ b/tests/config/run-format-test.js @@ -1,136 +1,82 @@ -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 { getFixtures } from "./get-fixtures.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 { replacePlaceholders } from "./replace-placeholders.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) { + const { 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) => { - 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] = parsers; - const allParsers = [...parsers]; - - const stringifiedOptions = stringifyOptionsForTitle(options); + const context = { + dirname, + stringifiedOptions: stringifyOptionsForTitle(rawOptions), + parsers: [...explicitParsers], + options, + explicitParsers, + rawOptions, + snippets, + }; + + for (const fixture of getFixtures(context)) { + const { name, context, filepath } = fixture; + const { stringifiedOptions, parsers } = context; - for (const { name, filename, code, output } of [...files, ...snippets]) { const title = `${name}${ 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, 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, }); }); } @@ -138,4 +84,36 @@ function runFormatTest(fixtures, parsers, options) { } } +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 2c335bf43..e731a6bca 100644 --- a/tests/config/utilities.js +++ b/tests/config/utilities.js @@ -2,7 +2,10 @@ import path from "node:path"; const normalizeDirectory = (directory) => path.normalize(directory + path.sep); -const shouldThrowOnFormat = (filename, options) => { +const isErrorTest = (dirname) => + normalizeDirectory(dirname).includes(`${path.sep}_errors_${path.sep}`); + +const shouldThrowOnFormat = ({ filename }, options) => { const { errors = {}, parser } = options; if (errors === true) { return true; @@ -17,4 +20,4 @@ const shouldThrowOnFormat = (filename, options) => { return false; }; -export { normalizeDirectory, shouldThrowOnFormat }; +export { normalizeDirectory, isErrorTest, shouldThrowOnFormat };