Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tests/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/"),
);
Expand Down
64 changes: 64 additions & 0 deletions tests/config/get-fixtures.js
Original file line number Diff line number Diff line change
@@ -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 };
180 changes: 79 additions & 101 deletions tests/config/run-format-test.js
Original file line number Diff line number Diff line change
@@ -1,141 +1,119 @@
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,
});
});
}
});
}
}

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;
7 changes: 5 additions & 2 deletions tests/config/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,4 +20,4 @@ const shouldThrowOnFormat = (filename, options) => {
return false;
};

export { normalizeDirectory, shouldThrowOnFormat };
export { normalizeDirectory, isErrorTest, shouldThrowOnFormat };