From 4bbbaca218486a9eb9966b7a03a19222b579f739 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti Date: Thu, 25 Jul 2024 06:59:40 +0200 Subject: [PATCH] test: use snapshot --- package-lock.json | 4 +- package.json | 5 +- test/index.test.ts | 983 +++++++++++++------------ test/snapshots/test/index.test.ts.md | 158 +++- test/snapshots/test/index.test.ts.snap | Bin 348 -> 885 bytes 5 files changed, 692 insertions(+), 458 deletions(-) diff --git a/package-lock.json b/package-lock.json index b41422c..3e26868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rollup-plugin-sass", - "version": "1.13.1", + "version": "1.13.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rollup-plugin-sass", - "version": "1.13.1", + "version": "1.13.2", "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3 || ^4 || ^5", diff --git a/package.json b/package.json index a4ee651..2524d33 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "build": "npm run build-downlevel-dts && tsc --project tsconfig.build.plugin.json && tsc --project tsconfig.build.insertStyle.json", "build-downlevel-dts": "node scripts/clean-and-run-downlevel-dts.js", "downlevel-dts": "downlevel-dts . ts3.5 [--to=3.5]", - "test": "nyc --reporter=html --reporter=text ava ./test/*.test.ts -s && npm run test:rollup.config.spec.ts", + "test": "nyc --reporter=html --reporter=text ava && npm run test:rollup.config.spec.ts", "coverage": "nyc report --reporter=text-lcov | coveralls", "output-coverage-lcov": "nyc report --reporter=text-lcov > coverage/tests.lcov", "test:rollup.config.spec.ts": "tsc --project tsconfig.spec.json --noEmit", @@ -50,6 +50,9 @@ "dist" ], "ava": { + "files": [ + "./test/*.test.ts" + ], "extensions": [ "ts" ], diff --git a/test/index.test.ts b/test/index.test.ts index 3b63f7f..c27611e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,201 +1,197 @@ -import test from 'ava'; -import {promises as fs, constants as fsConstants} from 'fs'; -import * as path from 'path'; -import sinon from 'sinon'; -import {OutputOptions, rollup, RollupOutput} from 'rollup'; -import * as sassJs from 'sass'; +import test from "ava"; +import Sinon from "sinon"; +import { readFileSync, promises as fs, constants as fsConstants } from "fs"; +import * as path from "path"; +import { rollup } from "rollup"; +import type { + OutputOptions, + RollupOutput, + WarningHandlerWithDefault, +} from "rollup"; +import * as sassRuntime from "sass"; import postcss from "postcss"; -import {extractICSS} from "icss-utils"; +import { extractICSS } from "icss-utils"; import sass from "../src/index"; -import { SassOptions } from "../src/types"; +import { RollupPluginSassOutputFn, SassOptions } from "../src/types"; -const repoRoot = path.join(__dirname, '../'), +const repoRoot = path.join(__dirname, "../"); - tmpDir = path.join(repoRoot, '.tests-output/'), +const TEST_OUTPUT_DIR = path.join(repoRoot, ".tests-output/"); - sassOptions = { - outputStyle: 'compressed', - } as SassOptions, +const TEST_SASS_OPTIONS_DEFAULT = { + outputStyle: "compressed", +} as SassOptions; - baseConfig = { - plugins: [ - sass({ - options: sassOptions, - }), - ], - }, - - generateOptions = { - format: 'es', - } as OutputOptions, - - outputOptions = { - format: 'es', - file: path.join(tmpDir, 'bundle.js'), - }, - - squash = str => str.trim().replace(/[\n\r\f]/g, ''), +const TEST_BASE_CONFIG = { + plugins: [ + sass({ + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], +}; - reverse = str => str.split('').reverse().join(''), +const TEST_GENERATE_OPTIONS = { + format: "es", +} as OutputOptions; - unwrap = output => output[0].code; +const TEST_OUTPUT_OPTIONS = { + format: "es", + file: path.join(TEST_OUTPUT_DIR, "bundle.js"), +} as const; -let expectA, expectA2, expectB, expectC, expectD, expectE; +function stripNewLines(str: string): string { + return str.trim().replace(/[\n\r\f]/g, ""); +} -test.before(async () => { - const mkDir = () => fs.mkdir(tmpDir); +function getFirstChunkCode(outputChunks: RollupOutput["output"]): string { + return outputChunks[0].code; +} - // @note Conditional check here since we have a relaxed 'package.json.engines.node' value, - // and `fs.rmdir` is being deprecated in later versions of node (node v18+). - // ---- - await (fs.rm ? fs.rm : fs.rmdir)(tmpDir, {recursive: true}) - .then(mkDir, mkDir) - .then(() => Promise.all([ - 'test/assets/expect_a.css', - 'test/assets/expect_a--with-icss-exports.css', - 'test/assets/expect_b.css', - 'test/assets/expect_c.css', - 'test/assets/expect_d.css', - 'test/assets/expect_e.css' - ] - .map(xs => fs.readFile(xs).then(buff => buff.toString())) - )) - .then(([a, a2, b, c, d, e]) => { - expectA = squash(a); - expectA2 = squash(a2); - expectB = squash(b); - expectC = squash(c); - expectD = squash(d); - expectE = squash(e); - }); -}); - -test('should import *.scss and *.sass files', async t => { +test("should import *.scss and *.sass files", async (t) => { const outputBundle = await rollup({ - ...baseConfig, - input: 'test/fixtures/basic/index.js', - }), - - {output} = await outputBundle.generate({ - format: 'es', - file: path.join(tmpDir, 'import_scss_and_sass.js') - }), - rslt = squash(unwrap(output)); + input: "test/fixtures/basic/index.js", + ...TEST_BASE_CONFIG, + }); + const { output } = await outputBundle.generate({ + format: "es", + file: path.join(TEST_OUTPUT_DIR, "import_scss_and_sass.js"), + }); + const result = getFirstChunkCode(output); - t.true([expectA, expectB, expectC].every(xs => rslt.includes(xs))); + t.snapshot(result); }); test("should import *.scss and *.sass files with default configuration", async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/basic/index.js", - plugins: [ - sass(), - ], - }), - { output } = await outputBundle.generate({ - format: "es", - file: path.join(tmpDir, "import_scss_and_sass_default_options.js"), - }), - rslt = squash(unwrap(output)); + input: "test/fixtures/basic/index.js", + plugins: [sass()], + }); + const { output } = await outputBundle.generate({ + ...TEST_GENERATE_OPTIONS, + file: path.join(TEST_OUTPUT_DIR, "import_scss_and_sass_default_options.js"), + }); + const result = getFirstChunkCode(output); - t.snapshot(rslt) + t.snapshot(result); }); -test('should compress the dest CSS', async t => { +test("should compress the dest CSS", async (t) => { const outputBundle = await rollup({ - ...baseConfig, - input: 'test/fixtures/compress/index.js', - }), - {output} = await outputBundle.generate(generateOptions); + ...TEST_BASE_CONFIG, + input: "test/fixtures/compress/index.js", + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectD) > -1); + t.snapshot(result); }); -test('should custom importer works', async t => { +test("should custom importer works", async (t) => { const outputBundle = await rollup({ - input: 'test/fixtures/custom-importer/index.js', - plugins: [ - sass({ - options: { - ...sassOptions, - importer: [ - (url, prev, done) => { - done({ - file: url.replace('${name}', 'actual_a'), - }); - }, - ], - } - }), - ], - }), - {output} = await outputBundle.generate(generateOptions); + input: "test/fixtures/custom-importer/index.js", + plugins: [ + sass({ + options: { + ...TEST_SASS_OPTIONS_DEFAULT, + importer: [ + (url, prev, done) => { + done({ + file: url.replace("${name}", "actual_a"), + }); + }, + ], + } as SassOptions, + }), + ], + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectA) > -1); + t.snapshot(result); }); -test('should support options.data', async t => { - const jsVars = { - color_red: 'red', - }; - const scssVars = Object.keys(jsVars).reduce((prev, key) => `${prev}$${key}:${jsVars[key]};`, ''); +test("should support options.data", async (t) => { + const jsVars = { color_red: "red" }; + const scssVars = Object.entries(jsVars).reduce( + (prev, [varName, varValue]) => `${prev}$${varName}:${varValue};`, + "" + ); const outputBundle = await rollup({ - input: 'test/fixtures/data/index.js', + input: "test/fixtures/data/index.js", plugins: [ sass({ options: { - ...sassOptions, + ...TEST_SASS_OPTIONS_DEFAULT, data: scssVars, }, }), ], }); - const {output} = await outputBundle.generate(generateOptions); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectE) > -1); + t.snapshot(result); }); -test('should insert CSS into head tag', async t => { - const outputBundle = await rollup({ - input: 'test/fixtures/insert/index.js', +// #region insert option +{ + /** + * In unit tests we are targeting `src` folder, + * so there is no `insertStyle.js` file so `rollup` issues a warning + * that we can silence. + */ + const onwarn: WarningHandlerWithDefault = (warning, defaultHandler) => { + if ( + warning.code === "UNRESOLVED_IMPORT" && + warning.exporter?.includes("insertStyle.js") + ) { + return; + } + defaultHandler(warning); + }; + + test("should insert CSS into head tag", async (t) => { + const outputBundle = await rollup({ + input: "test/fixtures/insert/index.js", plugins: [ sass({ insert: true, - options: sassOptions, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], - }), - {output} = await outputBundle.generate(generateOptions); - - t.snapshot(unwrap(output)); -}); + onwarn, + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); -test("should generate chunks with import insertStyle when `insert` is true", async (t) => { - const outputBundle = await rollup({ - input: { - entryA: "test/fixtures/multiple-entry-points/entryA.js", - entryB: "test/fixtures/multiple-entry-points/entryB.js", - }, - plugins: [ - sass({ - insert: true, - options: sassOptions, - }), - ], - output: { - preserveModules: true, - preserveModulesRoot: "src", - }, - external: [/\/insertStyle\.js$/], + t.snapshot(result); }); - const { output } = await outputBundle.generate(generateOptions); + test("should generate chunks with import insertStyle when `insert` is true", async (t) => { + const outputBundle = await rollup({ + input: { + entryA: "test/fixtures/multiple-entry-points/entryA.js", + entryB: "test/fixtures/multiple-entry-points/entryB.js", + }, + plugins: [ + sass({ + insert: true, + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + output: { + preserveModules: true, + preserveModulesRoot: "src", + }, + onwarn, + }); + + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); - t.is(output.length, 2, "has 2 chunks"); - t.true( - output.every( - (outputItem) => { + t.is(output.length, 2, "has 2 chunks"); + t.true( + output.every((outputItem) => { if (outputItem.type === "chunk") { const insertStyleImportsCount = outputItem.imports.filter((it) => it.includes("/insertStyle.js") @@ -204,399 +200,478 @@ test("should generate chunks with import insertStyle when `insert` is true", asy } // if is an assets there is no need to check imports return true; - } - ), - "each chunk must include insertStyle once" - ); + }), + "each chunk must include insertStyle once" + ); + }); +} +// #endregion + +// #region output option +{ + const onwarn: WarningHandlerWithDefault = (warning, defaultHandler) => { + if (warning.code === "EMPTY_BUNDLE") { + return; + } + defaultHandler(warning); + }; - // outputBundle.write({ - // format: 'es', - // dir: path.join(tmpDir, 'insert-style-preserve-modules'), - // }); -}); + const [expectA, expectB] = [ + "test/assets/expect_a.css", + "test/assets/expect_b.css", + ].map((filePath) => + stripNewLines(readFileSync(filePath, { encoding: "utf-8" })) + ); -test('should support output as function', async t => { - const outputSpy = sinon.spy(), - file = path.join(tmpDir, 'import_scss_and_sass.js'), - outputBundle = await rollup({ - input: 'test/fixtures/output-function/index.js', + /** + * @warning + * Right now we can't use snapshot testing on this tests because sometimes rollup mess with the order of imports. + * Detailed information can be found here: https://github.com/elycruz/rollup-plugin-sass/pull/143#issuecomment-2227274405 + */ + + test("should support output as function", async (t) => { + const outputSpy = Sinon.spy() as Sinon.SinonSpy< + Parameters, + ReturnType + >; + const outputFilePath = path.join( + TEST_OUTPUT_DIR, + "support_output_function.js" + ); + + const outputBundle = await rollup({ + input: "test/fixtures/output-function/index.js", plugins: [ sass({ output: outputSpy, - options: sassOptions, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], - }), - expectedSpyArg = squash(`${expectA}${expectB}`); - - await outputBundle.write({...outputOptions, file} as OutputOptions); - - await fs.readFile(file) - .then(rslt => t.true(squash(rslt.toString()) === '')) - .then(() => t.true( - outputSpy.calledWith(expectedSpyArg), - `\`outputSpy\` should've been called with \`${expectedSpyArg}\`. `+ - `Spy called with \`${outputSpy.args[0][0]}\`, other args ` + - `${JSON.stringify(outputSpy.args[0].slice(1))}` - )) - .catch(() => t.true(false, `Trouble reading back written file "${file}".`)); -}); + onwarn, + }); -test('should support output as (non-previously existent) path', async t => { - const outputStylePath = path.join(tmpDir, 'output-path/style.css'), - outputBundle = await rollup({ - input: 'test/fixtures/output-path/index.js', + await outputBundle.write({ ...TEST_OUTPUT_OPTIONS, file: outputFilePath }); + + const result = await fs + .readFile(outputFilePath) + .catch(() => + t.true(false, `Trouble reading back written file "${outputFilePath}".`) + ); + + t.is(stripNewLines(result.toString()), "", "JS bundle mus be empty"); + + t.true(outputSpy.calledOnce, "output function has been called"); + + const [actualCSSstring, ...otherOutputParams] = outputSpy.args[0]; + + [expectA, expectB].forEach((expectedChunkCSS) => { + t.true( + actualCSSstring.includes(expectedChunkCSS), + [ + `expect "${actualCSSstring}" to include "${expectedChunkCSS}"`, + `Additional params are: ${JSON.stringify(otherOutputParams)}`, + ].join("\n") + ); + }); + }); + + test("should support output as (non-previously existent) path", async (t) => { + const outputStylePath = path.join(TEST_OUTPUT_DIR, "output-path/style.css"); + + const outputBundle = await rollup({ + input: "test/fixtures/output-path/index.js", plugins: [ sass({ output: outputStylePath, - options: sassOptions, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], - }), - filePath = path.join(tmpDir, 'support_output_prev-non-exist.js'); - - await outputBundle.write({...outputOptions, file: filePath} as OutputOptions); - - await fs.readFile(filePath) - .then((rslt) => { - t.is(squash(rslt.toString()), ''); - }) - .then(() => fs.readFile(outputStylePath)) - .then((rslt) => { - t.not(squash(rslt.toString()).indexOf(expectA), -1); - t.not(squash(rslt.toString()).indexOf(expectB), -1); + onwarn, }); -}); -test('should support output as true', async t => { - const outputBundle = await rollup({ - input: 'test/fixtures/output-true/index.js', + const outputFilePath = path.join( + TEST_OUTPUT_DIR, + "support_output_prev-non-exist.js" + ); + await outputBundle.write({ ...TEST_OUTPUT_OPTIONS, file: outputFilePath }); + + const outputFileContent = await fs.readFile(outputFilePath); + t.is(stripNewLines(outputFileContent.toString()), ""); + + const outputStyleContent = await fs.readFile(outputStylePath); + t.true(stripNewLines(outputStyleContent.toString()).includes(expectA)); + t.true(stripNewLines(outputStyleContent.toString()).includes(expectB)); + }); + + test("should support output as true", async (t) => { + const outputBundle = await rollup({ + input: "test/fixtures/output-true/index.js", plugins: [ sass({ output: true, - options: sassOptions, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], - }), - filePath = path.join(tmpDir, 'support-output-as-true.js'); + onwarn, + }); - await outputBundle.write({...outputOptions, file: filePath} as OutputOptions); + const outputFilePath = path.join( + TEST_OUTPUT_DIR, + "support-output-as-true.js" + ); + await outputBundle.write({ ...TEST_OUTPUT_OPTIONS, file: outputFilePath }); - await fs.readFile(filePath) - .then(rslt => t.is(squash(rslt.toString()), '')) - .then(() => fs.readFile(filePath.replace('.js', '.css'))) - .then(rslt => squash(rslt.toString())) - .then(rslt => { - t.not(rslt.indexOf(expectA), -1); - t.not(rslt.indexOf(expectB), -1); - }); -}); + const outputFileContent = await fs.readFile(outputFilePath); + t.is(stripNewLines(outputFileContent.toString()), ""); -test('should processor return as string', async t => { - const outputBundle = await rollup({ - input: 'test/fixtures/processor-string/index.js', - plugins: [ - sass({ - processor: css => reverse(css), - options: sassOptions, - }), - ], - }), - {output} = await outputBundle.generate(generateOptions); + const outputStylePath = outputFilePath.replace(".js", ".css"); + const outputStyleContent = await fs.readFile(outputStylePath); + t.true(outputStyleContent.toString().includes(expectA)); + t.true(outputStyleContent.toString().includes(expectB)); + }); +} +// #endregion - t.true(squash(unwrap(output)).indexOf(reverse(expectA)) > -1); - t.true(squash(unwrap(output)).indexOf(reverse(expectB)) > -1); -}); +// #region processor option +test("should processor return as string", async (t) => { + const reverse = (str: string): string => str.split("").reverse().join(""); -test('should processor return as object', async t => { const outputBundle = await rollup({ - input: 'test/fixtures/processor-object/index.js', + input: "test/fixtures/processor-string/index.js", plugins: [ sass({ - processor(css) { - return { - css, - foo: 'foo', - bar: 'bar', - }; - }, - options: sassOptions, + processor: (css) => reverse(css), + options: TEST_SASS_OPTIONS_DEFAULT, }), ], }); - const {output} = await outputBundle.generate(generateOptions); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectA) > -1); - t.true(squash(unwrap(output)).indexOf(expectB) > -1); - t.true(squash(unwrap(output)).indexOf('foo') > -1); - t.true(squash(unwrap(output)).indexOf('bar') > -1); + t.snapshot(result); }); -test('should support processor return type `Promise`', async t => { +test("should processor return as object", async (t) => { const outputBundle = await rollup({ - input: 'test/fixtures/processor-promise/index.js', - plugins: [ - sass({ - processor: (css) => new Promise(resolve => - setTimeout(() => resolve(css), 100)), - options: sassOptions, + input: "test/fixtures/processor-object/index.js", + plugins: [ + sass({ + processor: (css) => ({ + css, + foo: "foo", + bar: "bar", }), - ], - }), - {output} = await outputBundle.generate(generateOptions); + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectA) > -1); - t.true(squash(unwrap(output)).indexOf(expectB) > -1); + t.snapshot(result); }); -test('should support processor return type `Promise<{css: string, icssExport: {}, icssImport: {}}}>', async t => { +test("should support processor return type `Promise`", async (t) => { const outputBundle = await rollup({ - input: 'test/fixtures/processor-promise/with-icss-exports.js', - plugins: [ - sass({ - processor: (css) => new Promise((resolve) => { - const pcssRootNodeRslt = postcss.parse(css), - extractedIcss = extractICSS(pcssRootNodeRslt, true), - cleanedCss = pcssRootNodeRslt.toString(), - out = Object.assign({}, extractedIcss.icssExports, { - css: cleanedCss, - }); - // console.table(extractedIcss); - // console.log(out); - resolve(out); - }), - options: sassOptions, - }), - ], - }), - {output} = await outputBundle.generate(generateOptions), - rslt = squash(unwrap(output)); + input: "test/fixtures/processor-promise/index.js", + plugins: [ + sass({ + processor: (css) => + new Promise((resolve) => setTimeout(() => resolve(css), 100)), + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(rslt.includes(expectA2)); - t.true(rslt.includes(expectB)); + t.snapshot(result); }); -test('should throw an error when processor returns an object type missing the `css` prop.', async t => { - await t.throwsAsync(async () => rollup({ - input: 'test/fixtures/processor-error/index.js', +test("should support processor return type `Promise<{css: string, icssExport: {}, icssImport: {}}}>", async (t) => { + const outputBundle = await rollup({ + input: "test/fixtures/processor-promise/with-icss-exports.js", plugins: [ sass({ - // @ts-ignore - Ignoring incorrect type (for test). - processor: () => ({}), - options: sassOptions, + processor: (css) => { + const pcssRootNodeRslt = postcss.parse(css); + const extractedIcss = extractICSS(pcssRootNodeRslt, true); + const cleanedCss = pcssRootNodeRslt.toString(); + const out = { + ...extractedIcss.icssExports, + css: cleanedCss, + }; + // console.table(extractedIcss); + // console.log(out); + return out; + }, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], - }), { - message: 'You need to return the styles using the `css` property. See https://github.com/differui/rollup-plugin-sass#processor', }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); + + t.snapshot(result); +}); + +test("should throw an error when processor returns an object type missing the `css` prop.", async (t) => { + const sassPlugin = sass({ + // @ts-expect-error - Ignoring incorrect type (for test). + processor: () => ({}), + options: TEST_SASS_OPTIONS_DEFAULT, + }); + const message = + "You need to return the styles using the `css` property. See https://github.com/differui/rollup-plugin-sass#processor"; + + await t.throwsAsync( + () => + rollup({ + input: "test/fixtures/processor-error/index.js", + plugins: [sassPlugin], + }), + { message } + ); }); +// #endregion -test('should resolve ~ as node_modules', async t => { +// #region node resolution +test("should resolve ~ as node_modules", async (t) => { const outputBundle = await rollup({ - input: 'test/fixtures/import/index.js', + input: "test/fixtures/import/index.js", plugins: [ sass({ - options: sassOptions, + options: TEST_SASS_OPTIONS_DEFAULT, }), ], }); - const {output} = await outputBundle.generate(generateOptions); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(squash(unwrap(output)).indexOf(expectA) > -1); - t.true(squash(unwrap(output)).indexOf(expectB) > -1); - t.true(squash(unwrap(output)).indexOf(expectC) > -1); + t.snapshot(result); }); -test('should resolve ~ as node_modules and output javascript modules', async t => { +test("should resolve ~ as node_modules and output javascript modules", async (t) => { + const outputFilePathES = path.join( + TEST_OUTPUT_DIR, + "import_scss_and_sass.es.js" + ); + const outputFilePathCJS = path.join( + TEST_OUTPUT_DIR, + "import_scss_and_sass.cjs.js" + ); + const outputBundle = await rollup({ - input: 'test/fixtures/import/index.js', - plugins: [ - sass({ - options: sassOptions, - }), - ], - }), - outputFilePath = path.join(tmpDir, 'import_scss_and_sass.es.js'), - outputFilePath2 = path.join(tmpDir, 'import_scss_and_sass.cjs.js'), - expectedInOutput = `${expectA + expectB + expectC}`, - {output} = await outputBundle.generate(generateOptions), - outputRslt = squash(unwrap(output)); + input: "test/fixtures/import/index.js", + plugins: [ + sass({ + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true(outputRslt.includes(expectedInOutput), - `${JSON.stringify(outputRslt)}.includes(\`${expectedInOutput}\`)`); + t.snapshot(result, "check output result code"); // Test 'es' module output // ---- - await Promise.all([ - outputBundle.write({ - format: 'es', - file: outputFilePath - }) - .then(() => fs.readFile(outputFilePath)) - .then(data => { - const rslt = squash(data.toString()); - - // Ensure content exist in output file - t.true(rslt.includes(expectedInOutput), - `${JSON.stringify(rslt)}.includes(\`${expectedInOutput}\`)`) - }), + await outputBundle.write({ + format: "es", + file: outputFilePathES, + }); + + const esFileContent = await fs.readFile(outputFilePathES); + t.snapshot( + esFileContent.toString(), + "Ensure content exist in ESM output file" + ); - // Test 'cjs' module output - // ---- - outputBundle.write({ - format: 'cjs', - file: outputFilePath2 - }) - .then(() => fs.readFile(outputFilePath2)) - .then(data => { - const rslt = squash(data.toString()); - - // Ensure content exist in output file - t.true(rslt.includes(expectedInOutput), - `${JSON.stringify(rslt)}.includes(\`${expectedInOutput}\`)`) - }) - ]); + // Test 'cjs' module output + // ---- + await outputBundle.write({ + format: "cjs", + file: outputFilePathCJS, + }); + + const cjsFileContent = await fs.readFile(outputFilePathCJS); + t.snapshot( + cjsFileContent.toString(), + "Ensure content exist in CJS output file" + ); }); +// #endregion -test('should support options.runtime', async t => { +test("should support options.runtime", async (t) => { const outputBundle = await rollup({ - input: 'test/fixtures/runtime/index.js', - plugins: [ - sass({ - runtime: sassJs, - options: sassOptions, - }), - ], - }), - {output} = await outputBundle.generate(generateOptions), - rslt = squash(unwrap(output)); + input: "test/fixtures/runtime/index.js", + plugins: [ + sass({ + runtime: sassRuntime, + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const result = getFirstChunkCode(output); - t.true([expectA, expectB, expectC].every(xs => rslt.includes(xs))); + t.snapshot(result); }); -test('When `sourcemap` isn\'t set adjacent source map files should not be output, and ' + - 'rollup output chunk shouldn\'t contain a `map` entry', async t => { - const outputFilePath = path.join(tmpDir, 'with-no-adjacent-source-map.js'), - - bundle = await rollup({ - input: 'test/fixtures/basic/index.js', - plugins: [ - sass({ - options: sassOptions - }), - ], - }), +// #region sourcemap +test("When `sourcemap` isn't set adjacent source map files should not be output, and rollup output chunk shouldn't contain a `map` entry", async (t) => { + const outputFilePath = path.join( + TEST_OUTPUT_DIR, + "with-no-adjacent-source-map.js" + ); - sourceMapFilePath = `${outputFilePath}.map`; + const bundle = await rollup({ + input: "test/fixtures/basic/index.js", + plugins: [ + sass({ + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const sourceMapFilePath = `${outputFilePath}.map`; // Run test - await bundle.write({file: outputFilePath, format: 'esm'}) - .then((rslt: RollupOutput): Promise => { - // Check for output chunk - t.true(rslt && rslt.output && !!rslt.output.length, 'output should contain an output chunk'); - - // Check absence of 'map' entry in chunk - t.true(rslt.output[0].map === null || rslt.output[0].map === undefined, - 'output chunk\'s `map` property should not be set. It should equal `null` or `undefined`'); - - // Check for absence of source map file - return fs.access(sourceMapFilePath, fsConstants.R_OK) - .then(() => t.false(true, `'${sourceMapFilePath}' should not exist.`), - () => t.true(true) - ); - }); + const writeResult = await bundle.write({ + file: outputFilePath, + format: "esm", + }); + + const writeResultOutput = writeResult.output; + + // Check for output chunk + t.true(!!writeResult.output?.length, "output should contain an output chunk"); + + // Check absence of 'map' entry in chunk + t.falsy( + writeResultOutput[0].map, + "output chunk's `map` property should not be set. It should equal `null` or `undefined`" + ); + + // Check for absence of source map file + await fs.access(sourceMapFilePath, fsConstants.R_OK).then( + () => t.false(true, `'${sourceMapFilePath}' should not exist.`), + () => t.true(true) + ); }); -test('When `sourcemap` is set, to `true`, adjacent source map file should be output, and ' + - 'rollup output chunk should contain `map` entry', async t => { - const outputFilePath = path.join(tmpDir, 'with-adjacent-source-map.js'), +test("When `sourcemap` is set, to `true`, adjacent source map file should be output, and rollup output chunk should contain `map` entry", async (t) => { + const outputFilePath = path.join( + TEST_OUTPUT_DIR, + "with-adjacent-source-map.js" + ); + const bundle = await rollup({ + input: "test/fixtures/basic/index.js", + plugins: [ + sass({ + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + const sourceMapFilePath = `${outputFilePath}.map`; - bundle = await rollup({ - input: 'test/fixtures/basic/index.js', - plugins: [ - sass({ - options: sassOptions - }), - ], - }), + const writeResult = await bundle.write({ + file: outputFilePath, + sourcemap: true, + format: "esm", + }); - sourceMapFilePath = `${outputFilePath}.map`; + // Check for output chunk + t.true(!!writeResult.output?.length, "output should contain an output chunk"); - await bundle.write({file: outputFilePath, sourcemap: true, format: 'esm'}) - .then((rslt: RollupOutput): Promise => { - // Check for output chunk - t.true(rslt && rslt.output && !!rslt.output.length, 'output should contain an output chunk'); + // Check for 'map' entry in chunk + t.true( + !!writeResult.output[0].map, + "rollup output output chunk's `map` property should be set" + ); - // Check for 'map' entry in chunk - t.true(!!rslt.output[0].map, 'rollup output output chunk\'s `map` property should be set'); + // Check for source map file + const contents = await fs.readFile(sourceMapFilePath); - // Check for source map file - return fs.readFile(sourceMapFilePath) - .then(contents => { - t.true(!!contents.toString(), `${sourceMapFilePath} should have been written.`); - }); - }); + t.true( + !!contents.toString(), + `${sourceMapFilePath} should have been written.` + ); }); +// #endregion -test('module stylesheets graph should be added to watch list', t => { - const inputFilePath = 'test/fixtures/dependencies/index.js'; +test("module stylesheets graph should be added to watch list", async (t) => { + const inputFilePath = "test/fixtures/dependencies/index.js"; // Bundle our dependencies fixture module // ---- - return rollup({ + const bundle = await rollup({ input: inputFilePath, plugins: [ sass({ - options: sassOptions - }) - ] - }) - // Load nested style sheet contents and return associated list of filename and content tuples - // ---- - .then(bundle => { - return Promise.all([ - 'test/fixtures/dependencies/style1.scss', - 'test/fixtures/dependencies/empty-style1.scss', - 'test/fixtures/dependencies/style2.sass', - 'test/fixtures/dependencies/style3.scss', - 'test/fixtures/dependencies/empty-style3.scss', - 'test/fixtures/dependencies/empty-style2.sass', - ] - .map(filePath => fs.readFile(filePath).then(buf => [filePath, squash(buf.toString())])) - ) - // Run tests - // ---- - .then(async nestedFilePathsAndContents => { - const expectedWatchedFiles = ['test/fixtures/dependencies/index.js'] - .concat(nestedFilePathsAndContents.map(([fp]) => fp)); - - // Check `watchFiles` count (watched ones plus 'index.js' one) - t.deepEqual(bundle.watchFiles.length, expectedWatchedFiles.length, 'should contain expected number of "watched" files'); - - // Ensure 'index.js' module, and other files in dep tree are watched - bundle.watchFiles.forEach((filePath, i) => { - const expectedTail = expectedWatchedFiles[i]; - t.true(filePath.endsWith(expectedTail), `${filePath} should end with ${expectedTail}`); - }); - - // Get target module. - // ---- - const targetModule = bundle?.cache?.modules[0]; - t.true(!!targetModule, 'Expected bundle data'); - - // Ensure target module transform dependencies indeed end with expected file path tails. - // ---- - t.true(targetModule.transformDependencies?.every(filePath => { - return !!expectedWatchedFiles.find(fp => filePath.endsWith(fp)); - }), '`bundle.cache.modules[0].transformDependencies` entries should' + - ' each end with expected file-path tails'); - - // Test final content output - // ---- - const expectedFinalContent = await fs.readFile('test/fixtures/dependencies/expected.js') - .then(x => x.toString()); - - t.is(targetModule.code.trim(), expectedFinalContent.trim()); - }); - }); + options: TEST_SASS_OPTIONS_DEFAULT, + }), + ], + }); + + // List of nested stylesheets paths + // ---- + const nestedFilePaths = [ + "test/fixtures/dependencies/style1.scss", + "test/fixtures/dependencies/empty-style1.scss", + "test/fixtures/dependencies/style2.sass", + "test/fixtures/dependencies/style3.scss", + "test/fixtures/dependencies/empty-style3.scss", + "test/fixtures/dependencies/empty-style2.sass", + ]; + + const expectedWatchedFiles = [ + "test/fixtures/dependencies/index.js", + ...nestedFilePaths, + ]; + + // Run tests + // ---- + + // Check `watchFiles` count (watched ones plus 'index.js' one) + t.deepEqual( + bundle.watchFiles.length, + expectedWatchedFiles.length, + 'should contain expected number of "watched" files' + ); + + // Ensure our initial 'index.js' module is being watched + t.true( + bundle.watchFiles[0].endsWith(inputFilePath), + 'Expected `bundle.watchFiles[0]` to end with "index.js"' + ); + + // Ensure 'index.js' module, and other files in dep tree are watched + bundle.watchFiles.forEach((filePath, i) => { + const expectedTail = expectedWatchedFiles[i]; + t.true( + filePath.endsWith(expectedTail), + `${filePath} should end with ${expectedTail}` + ); + }); + + // Get target module. + // ---- + const targetModule = bundle?.cache?.modules[0]!; + t.true(!!targetModule, "Expected bundle data"); + + // Ensure target module transform dependencies indeed end with expected file path tails. + // ---- + t.true( + targetModule.transformDependencies?.every( + (filePath) => !!expectedWatchedFiles.find((fp) => filePath.endsWith(fp)) + ), + "`bundle.cache.modules[0].transformDependencies` entries should each end with expected file-path tails" + ); + + // Test final content output + // ---- + const expectedFinalContent = await fs.readFile( + "test/fixtures/dependencies/expected.js" + ); + t.is(targetModule.code.trim(), expectedFinalContent.toString().trim()); }); diff --git a/test/snapshots/test/index.test.ts.md b/test/snapshots/test/index.test.ts.md index b10cd6c..6a4cfab 100644 --- a/test/snapshots/test/index.test.ts.md +++ b/test/snapshots/test/index.test.ts.md @@ -4,11 +4,62 @@ The actual snapshot is saved in `index.test.ts.snap`. Generated by [AVA](https://avajs.dev). +## should import *.scss and *.sass files + +> Snapshot 1 + + `var atualA = "body{color:red}";␊ + ␊ + var atualB = "body{color:green}";␊ + ␊ + var atualC = "body{color:blue}";␊ + ␊ + var index = atualA + atualB + atualC;␊ + ␊ + export { index as default };␊ + ` + ## should import *.scss and *.sass files with default configuration > Snapshot 1 - 'var atualA = "body {\\n color: red;\\n}";var atualB = "body {\\n color: green;\\n}";var atualC = "body {\\n color: blue;\\n}";var index = atualA + atualB + atualC;export { index as default };' + `var atualA = "body {\\n color: red;\\n}";␊ + ␊ + var atualB = "body {\\n color: green;\\n}";␊ + ␊ + var atualC = "body {\\n color: blue;\\n}";␊ + ␊ + var index = atualA + atualB + atualC;␊ + ␊ + export { index as default };␊ + ` + +## should compress the dest CSS + +> Snapshot 1 + + `var actualD = "body{color:red;background-color:green}";␊ + ␊ + export { actualD as default };␊ + ` + +## should custom importer works + +> Snapshot 1 + + `var style = "body{color:red}";␊ + ␊ + export { style as default };␊ + ` + +## should support options.data + +> Snapshot 1 + + `var actualE = "body{color:red}";␊ + ␊ + export { actualE as default };␊ + ` ## should insert CSS into head tag @@ -20,3 +71,108 @@ Generated by [AVA](https://avajs.dev). ␊ ___$insertStyle("body{color:green}");␊ ` + +## should processor return as string + +> Snapshot 1 + + `var atualA = "}der:roloc{ydob";␊ + ␊ + var atualB = "}neerg:roloc{ydob";␊ + ␊ + var index = atualA + atualB;␊ + ␊ + export { index as default };␊ + ` + +## should processor return as object + +> Snapshot 1 + + `var atualA = "body{color:red}";␊ + const foo = "foo";␊ + ␊ + var atualB = "body{color:green}";␊ + const bar = "bar";␊ + ␊ + var index = atualA + atualB + foo + bar;␊ + ␊ + export { index as default };␊ + ` + +## should support processor return type `Promise` + +> Snapshot 1 + + `var atualA = "body{color:red}";␊ + ␊ + var atualB = "body{color:green}";␊ + ␊ + var index = atualA + atualB;␊ + ␊ + export { index as default };␊ + ` + +## should support processor return type `Promise<{css: string, icssExport: {}, icssImport: {}}}> + +> Snapshot 1 + + `var actualA2 = "body{color:red;background:blue}";␊ + const color = "red";␊ + const color2 = "blue";␊ + ␊ + var actualB = "body{color:green}";␊ + ␊ + var withIcssExports = actualA2 + actualB;␊ + ␊ + export { color, color2, withIcssExports as default };␊ + ` + +## should resolve ~ as node_modules + +> Snapshot 1 + + `var style = "body{color:red}body{color:green}body{color:blue}";␊ + ␊ + export { style as default };␊ + ` + +## should resolve ~ as node_modules and output javascript modules + +> check output result code + + `var style = "body{color:red}body{color:green}body{color:blue}";␊ + ␊ + export { style as default };␊ + ` + +> Ensure content exist in ESM output file + + `var style = "body{color:red}body{color:green}body{color:blue}";␊ + ␊ + export { style as default };␊ + ` + +> Ensure content exist in CJS output file + + `'use strict';␊ + ␊ + var style = "body{color:red}body{color:green}body{color:blue}";␊ + ␊ + module.exports = style;␊ + ` + +## should support options.runtime + +> Snapshot 1 + + `var atualA = "body{color:red}";␊ + ␊ + var atualB = "body{color:green}";␊ + ␊ + var atualC = "body{color:blue}";␊ + ␊ + var index = atualA + atualB + atualC;␊ + ␊ + export { index as default };␊ + ` diff --git a/test/snapshots/test/index.test.ts.snap b/test/snapshots/test/index.test.ts.snap index 6f2f8ff56a4bca793ed8961b360982320802a6fb..ad1931905ef6366fe6a97c32eace4442613ae38f 100644 GIT binary patch literal 885 zcmV-*1B(1XRzVA;CP@ z9jF_PzaNVT00000000BsRy}VMK@fF72+18C5(tI}WI}=?QBcGrBA65bA&}gp2srEA zv3>E~?rCSwaby{UD5%oXCnX&v9S#40pTIxh51_rTyYIc3*`337 z%T>Hb2S;ce?s?F7=rPW&-A}!z#?Tl-M6~{?z!c#~ zfdg1gc3VtVjepc@xEcr)=B8!^6Pr+7gwIIkqAr|K_D5ZHyU{_ z+9$o3+X_i@yW!J(fz(=z_c~eyQrsUeij-RtJ%wFBMtN~kQNy0ny@K+6o?*9Y*^$|I zaYFH?BqC5Tg=;Yd3j7eQd|M?{wHxLcyv#6IKlW#18ixc{%;IKJ){;<%xdBHS1zltU z4C~~Xe9Bbq$5Px7&1Um6R)egRH>EAV?lRwLKdcg;>0f; z3%V5x^0nefq%>$WftFT38STgdNe3Bo-(~x7Bv7wt&R+_bg zPE(>km#nVAIPq%6SN}0UAa2&uAGpz|=%O-9>v_Zl-F;`k9bgOcpq4X}HEx zyx~$Vyx41qz1B#|?IXOkIH?3SJtcy|amCFP&70HC`<%9-L1d;2VQm`EaN1CT@dE?9 ztj~yR$2X8u@!{wns6dys&>dXmUF5xlrACV8Ac6moQ)NR^pwW(F2BiTUIAp+)u+eyt zFtyWLOtGJ#*sTPFVe4^IiOcp}jH7f_F)jIUwdrK*&l(xBan; literal 348 zcmV-i0i*swRzVS-&{PIqs#ZYz4g)5B$J()T$a1nCK z3~1G=MP|sfA%-U{1t`j}Gy!BMa}dxydC{zveL z9#Bd&H!y(i*r^%JM;0J;#`!Doq@s~sbfXP-yOYa3k=-KWo#fb*^bKmOsDeTJh0AtF z)5H-~b@X{dB5#5&qE^#MtI0BXfp|CsZN))Bs6a6PYvJveh1Z}FoFvKV`jMf`3_<4I u1bSiEUl;qd|KmEm=U&w5Yz>{&k>~5Qp46gy{tr~UtFbQu9%VrR0ssK=L8lx5