"use strict"; const fs = require("fs"); const path = require("path"); const raw = require("jest-snapshot-serializer-raw").wrap; const AST_COMPARE = process.env["AST_COMPARE"]; const TEST_STANDALONE = process.env["TEST_STANDALONE"]; const TEST_CRLF = process.env["TEST_CRLF"]; const CURSOR_PLACEHOLDER = "<|>"; const RANGE_START_PLACEHOLDER = "<<>>"; const RANGE_END_PLACEHOLDER = "<<>>"; const prettier = !TEST_STANDALONE ? require("prettier/local") : require("prettier/standalone"); global.run_spec = (dirname, parsers, options) => { // istanbul ignore next if (!parsers || !parsers.length) { throw new Error(`No parsers were specified for ${dirname}`); } fs.readdirSync(dirname).forEach(basename => { const filename = path.join(dirname, basename); if ( path.extname(basename) === ".snap" || !fs.lstatSync(filename).isFile() || basename[0] === "." || basename === "jsfmt.spec.js" ) { return; } let rangeStart; let rangeEnd; let cursorOffset; const text = fs.readFileSync(filename, "utf8"); const source = (TEST_CRLF ? text.replace(/\n/g, "\r\n") : text) .replace(RANGE_START_PLACEHOLDER, (match, offset) => { rangeStart = offset; return ""; }) .replace(RANGE_END_PLACEHOLDER, (match, offset) => { rangeEnd = offset; return ""; }); const input = source.replace(CURSOR_PLACEHOLDER, (match, offset) => { cursorOffset = offset; return ""; }); const baseOptions = Object.assign({ printWidth: 80 }, options, { rangeStart, rangeEnd, cursorOffset }); const mainOptions = Object.assign({}, baseOptions, { parser: parsers[0] }); const hasEndOfLine = "endOfLine" in mainOptions; const output = format(input, filename, mainOptions); const visualizedOutput = visualizeEndOfLine(output); test(basename, () => { expect(visualizedOutput).toEqual( visualizeEndOfLine(consistentEndOfLine(output)) ); expect( raw( createSnapshot( hasEndOfLine ? visualizeEndOfLine( text .replace(RANGE_START_PLACEHOLDER, "") .replace(RANGE_END_PLACEHOLDER, "") ) : source, hasEndOfLine ? visualizedOutput : output, Object.assign({}, baseOptions, { parsers }) ) ) ).toMatchSnapshot(); }); for (const parser of parsers.slice(1)) { const verifyOptions = Object.assign({}, baseOptions, { parser }); test(`${basename} - ${parser}-verify`, () => { const verifyOutput = format(input, filename, verifyOptions); expect(visualizedOutput).toEqual(visualizeEndOfLine(verifyOutput)); }); } if (AST_COMPARE) { test(`${filename} parse`, () => { const parseOptions = Object.assign({}, mainOptions); delete parseOptions.cursorOffset; const originalAst = parse(input, parseOptions); let formattedAst; expect(() => { formattedAst = parse( output.replace(CURSOR_PLACEHOLDER, ""), parseOptions ); }).not.toThrow(); expect(originalAst).toEqual(formattedAst); }); } }); }; function parse(source, options) { return prettier.__debug.parse(source, options, /* massage */ true).ast; } function format(source, filename, options) { const result = prettier.formatWithCursor( source, Object.assign({ filepath: filename }, options) ); return options.cursorOffset >= 0 ? result.formatted.slice(0, result.cursorOffset) + CURSOR_PLACEHOLDER + result.formatted.slice(result.cursorOffset) : result.formatted; } function consistentEndOfLine(text) { let firstEndOfLine; return text.replace(/\r\n?|\n/g, endOfLine => { if (!firstEndOfLine) { firstEndOfLine = endOfLine; } return firstEndOfLine; }); } function visualizeEndOfLine(text) { return text.replace(/\r\n?|\n/g, endOfLine => { switch (endOfLine) { case "\n": return "\n"; case "\r\n": return "\n"; case "\r": return "\n"; default: throw new Error(`Unexpected end of line ${JSON.stringify(endOfLine)}`); } }); } function createSnapshot(input, output, options) { const separatorWidth = 80; const printWidthIndicator = options.printWidth > 0 && Number.isFinite(options.printWidth) ? " ".repeat(options.printWidth) + "| printWidth" : []; return [] .concat( printSeparator(separatorWidth, "options"), printOptions( omit( options, k => k === "rangeStart" || k === "rangeEnd" || k === "cursorOffset" ) ), printWidthIndicator, printSeparator(separatorWidth, "input"), input, printSeparator(separatorWidth, "output"), output, printSeparator(separatorWidth) ) .join("\n"); } function printSeparator(width, description) { description = description || ""; const leftLength = Math.floor((width - description.length) / 2); const rightLength = width - leftLength - description.length; return "=".repeat(leftLength) + description + "=".repeat(rightLength); } function printOptions(options) { const keys = Object.keys(options).sort(); return keys.map(key => `${key}: ${stringify(options[key])}`).join("\n"); function stringify(value) { return value === Infinity ? "Infinity" : Array.isArray(value) ? `[${value.map(v => JSON.stringify(v)).join(", ")}]` : JSON.stringify(value); } } function omit(obj, fn) { return Object.keys(obj).reduce((reduced, key) => { const value = obj[key]; if (!fn(key, value)) { reduced[key] = value; } return reduced; }, {}); }