From 6cd39e70c475c22b29711e9ab854552d1b7bb0c9 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 11 Oct 2022 09:25:53 +0200 Subject: [PATCH 1/3] add fileResolve option --- index.d.ts | 5 + src/css-loader-core/loader.js | 85 ++-- src/index.js | 13 +- test/__snapshots__/test.js.snap | 25 + test/fixtures/in/deepCompose.css | 4 + test/fixtures/in/deepDeepCompose.css | 7 + test/test.js | 722 +++++++++++++++------------ 7 files changed, 499 insertions(+), 362 deletions(-) create mode 100644 test/fixtures/in/deepCompose.css create mode 100644 test/fixtures/in/deepDeepCompose.css diff --git a/index.d.ts b/index.d.ts index 6002f86..c7c9428 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,6 +50,11 @@ declare interface Options { Loader?: typeof Loader; resolve?: (file: string) => string | Promise; + + fileResolve?: ( + filerImport: string, + importer: string + ) => string | null | Promise; } declare interface PostcssModulesPlugin { diff --git a/src/css-loader-core/loader.js b/src/css-loader-core/loader.js index 85a7d9f..983ee61 100644 --- a/src/css-loader-core/loader.js +++ b/src/css-loader-core/loader.js @@ -43,7 +43,7 @@ const traceKeySorter = (a, b) => { }; export default class FileSystemLoader { - constructor(root, plugins) { + constructor(root, plugins, fileResolve) { if (root === "/" && process.platform === "win32") { const cwdDrive = process.cwd().slice(0, 3); if (!/^[A-Z]:\\$/.test(cwdDrive)) { @@ -55,6 +55,7 @@ export default class FileSystemLoader { } this.root = root; + this.fileResolve = fileResolve; this.sources = {}; this.traces = {}; this.importNr = 0; @@ -65,43 +66,61 @@ export default class FileSystemLoader { fetch(_newPath, relativeTo, _trace) { let newPath = _newPath.replace(/^["']|["']$/g, ""), trace = _trace || String.fromCharCode(this.importNr++); + const useFileResolve = typeof this.fileResolve === "function"; return new Promise((resolve, reject) => { - let relativeDir = path.dirname(relativeTo), - rootRelativePath = path.resolve(relativeDir, newPath), - fileRelativePath = path.resolve( - path.resolve(this.root, relativeDir), - newPath - ); + (useFileResolve + ? this.fileResolve(newPath, relativeTo) + : Promise.resolve() + ).then((fileResolvedPath) => { + if (fileResolvedPath && !path.isAbsolute(fileResolvedPath)) { + reject( + 'The returned path from the "fileResolve" option must be absolute.' + ); + } + let relativeDir = path.dirname(relativeTo), + rootRelativePath = + fileResolvedPath || path.resolve(relativeDir, newPath), + fileRelativePath = + fileResolvedPath || + path.resolve( + path.resolve(this.root, relativeDir), + newPath + ); - // if the path is not relative or absolute, try to resolve it in node_modules - if (newPath[0] !== "." && !path.isAbsolute(newPath)) { - try { - fileRelativePath = require.resolve(newPath); - } catch (e) { - // noop + // if the path is not relative or absolute, try to resolve it in node_modules + if ( + !useFileResolve && + newPath[0] !== "." && + !path.isAbsolute(newPath) + ) { + try { + fileRelativePath = require.resolve(newPath); + } catch (e) { + // noop + } } - } - const tokens = this.tokensByFile[fileRelativePath]; - if (tokens) { - return resolve(tokens); - } + const tokens = this.tokensByFile[fileRelativePath]; + if (tokens) { + return resolve(tokens); + } - fs.readFile(fileRelativePath, "utf-8", (err, source) => { - if (err) reject(err); - this.core - .load( - source, - rootRelativePath, - trace, - this.fetch.bind(this) - ) - .then(({ injectableSource, exportTokens }) => { - this.sources[fileRelativePath] = injectableSource; - this.traces[trace] = fileRelativePath; - this.tokensByFile[fileRelativePath] = exportTokens; - resolve(exportTokens); - }, reject); + fs.readFile(fileRelativePath, "utf-8", (err, source) => { + if (err) reject(err); + this.core + .load( + source, + rootRelativePath, + trace, + this.fetch.bind(this) + ) + .then(({ injectableSource, exportTokens }) => { + this.sources[fileRelativePath] = injectableSource; + this.traces[trace] = fileRelativePath; + this.tokensByFile[fileRelativePath] = exportTokens; + resolve(exportTokens); + }, reject); + }); }); }); } diff --git a/src/index.js b/src/index.js index e010616..fc9c65f 100644 --- a/src/index.js +++ b/src/index.js @@ -33,8 +33,8 @@ function getScopedNameGenerator(opts) { function getLoader(opts, plugins) { const root = typeof opts.root === "undefined" ? "/" : opts.root; return typeof opts.Loader === "function" - ? new opts.Loader(root, plugins) - : new FileSystemLoader(root, plugins); + ? new opts.Loader(root, plugins, opts.fileResolve) + : new FileSystemLoader(root, plugins, opts.fileResolve); } function isGlobalModule(globalModules, inputFile) { @@ -85,6 +85,15 @@ module.exports = (opts = {}) => { if (resultPluginIndex === -1) { throw new Error("Plugin missing from options."); } + // resolve and fileResolve can't be used together + if ( + typeof opts.resolve === "function" && + typeof opts.fileResolve == "function" + ) { + throw new Error( + 'Please use either the "resolve" or the "fileResolve" option.' + ); + } const earlierPlugins = result.processor.plugins.slice( 0, resultPluginIndex diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 46e1992..f1fe87b 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -328,6 +328,31 @@ Object { } `; +exports[`processes fileResolve option: processes fileResolve option 1`] = ` +"._composes_a_another-mixin { + display: flex; + height: 100px; + width: 200px; +}._composes_a_hello { + foo: bar; +}._composes_mixins_title { + color: black; + font-size: 40px; +}._composes_mixins_title:hover { + color: red; +}._composes_mixins_figure { + text-align: center +}._composes_mixins_title:focus, ._composes_mixins_figure:focus { + outline: none; + border: 1px solid red; +}._deepDeepCompose_deepDeepCompose { +}._deepDeepCompose_dotSlashRelativePath { +}._deepCompose_deepCompose { + content: \\"deepCompose\\"; +} +" +`; + exports[`processes globalModulePaths option: processes globalModulePaths option 1`] = ` ".page { padding: 20px; diff --git a/test/fixtures/in/deepCompose.css b/test/fixtures/in/deepCompose.css new file mode 100644 index 0000000..aa5c345 --- /dev/null +++ b/test/fixtures/in/deepCompose.css @@ -0,0 +1,4 @@ +.deepCompose { + composes: deepDeepCompose from "test-fixture-in/deepDeepCompose.css"; + content: "deepCompose"; +} diff --git a/test/fixtures/in/deepDeepCompose.css b/test/fixtures/in/deepDeepCompose.css new file mode 100644 index 0000000..878e42d --- /dev/null +++ b/test/fixtures/in/deepDeepCompose.css @@ -0,0 +1,7 @@ +.deepDeepCompose { + composes: title from "test-fixture-in/composes.mixins.css"; +} + +.dotSlashRelativePath { + composes: title from "./composes.mixins.css"; +} diff --git a/test/test.js b/test/test.js index 8ed6d8b..bccb616 100644 --- a/test/test.js +++ b/test/test.js @@ -8,410 +8,478 @@ import { behaviours } from "../src/behaviours"; const fixturesPath = path.resolve(__dirname, "./fixtures"); function createPlugin(name, processor) { - const plugin = () => ({ - postcssPlugin: name, - Once: processor, - }); - plugin.postcss = true; - return plugin; + const plugin = () => ({ + postcssPlugin: name, + Once: processor, + }); + plugin.postcss = true; + return plugin; } const cases = { - plugins: "saves origin plugins", - classes: "processes classes", - comments: "preserves comments", - composes: "composes rules", - values: "processes values", - interpolated: "generates scoped name with interpolated string", - global: "allows to make CSS global", - localShorthand: "processes :local shorthand selector", - globalShorthand: "processes :global shorthand selector", + plugins: "saves origin plugins", + classes: "processes classes", + comments: "preserves comments", + composes: "composes rules", + values: "processes values", + interpolated: "generates scoped name with interpolated string", + global: "allows to make CSS global", + localShorthand: "processes :local shorthand selector", + globalShorthand: "processes :global shorthand selector", }; function generateScopedName(name, filename) { - const file = path.basename(filename, ".css").replace(/\./g, "_"); - return `_${file}_${name}`; + const file = path.basename(filename, ".css").replace(/\./g, "_"); + return `_${file}_${name}`; } Object.keys(cases).forEach((name) => { - const description = cases[name]; - - const scopedNameGenerator = - name === "interpolated" - ? "[name]__[local]___[hash:base64:5]" - : generateScopedName; - - const scopeBehaviour = - name === behaviours.GLOBAL || name === "globalShorthand" - ? behaviours.GLOBAL - : behaviours.LOCAL; - - it(description, async () => { - const sourceFile = path.join(fixturesPath, "in", `${name}.css`); - const source = fs.readFileSync(sourceFile).toString(); - - let resultJson; - - const plugins = [ - autoprefixer, - plugin({ - scopeBehaviour, - generateScopedName: scopedNameGenerator, - getJSON: (cssFile, json) => { - resultJson = json; - }, - }), - ]; - - const result = await postcss(plugins).process(source, { from: sourceFile }); - - expect(result.css).toMatchSnapshot(`${description} - CSS`); - expect(resultJson).toMatchSnapshot(`${description} - JSON`); - }); - - it(`only calls plugins once when it ${description}`, async () => { - const sourceFile = path.join(fixturesPath, "in", `${name}.css`); - const source = fs.readFileSync(sourceFile).toString(); - - const rootsSeenBeforePlugin = new Set(); - const rootsSeenAfterPlugin = new Set(); - - const plugins = [ - autoprefixer, - createPlugin("validator-1", (root) => { - if (rootsSeenBeforePlugin.has(root)) { - throw new Error("Plugin before ours was called multiple times."); - } - rootsSeenBeforePlugin.add(root); - root.prepend( - `/* validator-1-start (${path.basename(root.source.input.file)}) */` - ); - root.append( - `/* validator-1-end (${path.basename(root.source.input.file)}) */` - ); - }), - plugin({ - scopeBehaviour, - generateScopedName: scopedNameGenerator, - getJSON: () => {}, - }), - createPlugin("validator-2", (root) => { - if (rootsSeenAfterPlugin.has(root)) { - throw new Error("Plugin after ours was called multiple times."); - } - rootsSeenAfterPlugin.add(root); - root.prepend( - `/* validator-2-start (${path.basename(root.source.input.file)}) */` - ); - root.append( - `/* validator-2-end (${path.basename(root.source.input.file)}) */` - ); - }), - ]; - - const result = await postcss(plugins).process(source, { from: sourceFile }); - - expect(result.css).toMatchSnapshot(`plugins once - ${description} - CSS`); - }); + const description = cases[name]; + + const scopedNameGenerator = + name === "interpolated" + ? "[name]__[local]___[hash:base64:5]" + : generateScopedName; + + const scopeBehaviour = + name === behaviours.GLOBAL || name === "globalShorthand" + ? behaviours.GLOBAL + : behaviours.LOCAL; + + it(description, async () => { + const sourceFile = path.join(fixturesPath, "in", `${name}.css`); + const source = fs.readFileSync(sourceFile).toString(); + + let resultJson; + + const plugins = [ + autoprefixer, + plugin({ + scopeBehaviour, + generateScopedName: scopedNameGenerator, + getJSON: (cssFile, json) => { + resultJson = json; + }, + }), + ]; + + const result = await postcss(plugins).process(source, { + from: sourceFile, + }); + + expect(result.css).toMatchSnapshot(`${description} - CSS`); + expect(resultJson).toMatchSnapshot(`${description} - JSON`); + }); + + it(`only calls plugins once when it ${description}`, async () => { + const sourceFile = path.join(fixturesPath, "in", `${name}.css`); + const source = fs.readFileSync(sourceFile).toString(); + + const rootsSeenBeforePlugin = new Set(); + const rootsSeenAfterPlugin = new Set(); + + const plugins = [ + autoprefixer, + createPlugin("validator-1", (root) => { + if (rootsSeenBeforePlugin.has(root)) { + throw new Error( + "Plugin before ours was called multiple times." + ); + } + rootsSeenBeforePlugin.add(root); + root.prepend( + `/* validator-1-start (${path.basename( + root.source.input.file + )}) */` + ); + root.append( + `/* validator-1-end (${path.basename( + root.source.input.file + )}) */` + ); + }), + plugin({ + scopeBehaviour, + generateScopedName: scopedNameGenerator, + getJSON: () => {}, + }), + createPlugin("validator-2", (root) => { + if (rootsSeenAfterPlugin.has(root)) { + throw new Error( + "Plugin after ours was called multiple times." + ); + } + rootsSeenAfterPlugin.add(root); + root.prepend( + `/* validator-2-start (${path.basename( + root.source.input.file + )}) */` + ); + root.append( + `/* validator-2-end (${path.basename( + root.source.input.file + )}) */` + ); + }), + ]; + + const result = await postcss(plugins).process(source, { + from: sourceFile, + }); + + expect(result.css).toMatchSnapshot( + `plugins once - ${description} - CSS` + ); + }); }); it("works with visitor plugins", async () => { - const source = ` + const source = ` p { color: green; } `; - const plugins = [ - { - postcssPlugin: "turn-values-blue", - Declaration(decl) { - decl.value = "blue"; - }, - }, - plugin(), - ]; - const result = await postcss(plugins).process(source, { from: undefined }); - expect(result.css).toEqual(source.replace("green", "blue")); + const plugins = [ + { + postcssPlugin: "turn-values-blue", + Declaration(decl) { + decl.value = "blue"; + }, + }, + plugin(), + ]; + const result = await postcss(plugins).process(source, { from: undefined }); + expect(result.css).toEqual(source.replace("green", "blue")); }); it("saves JSON next to CSS by default", async () => { - const sourceFile = path.join(fixturesPath, "in", "saveJSON.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "saveJSON.css.json"); + const sourceFile = path.join(fixturesPath, "in", "saveJSON.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "saveJSON.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([plugin({ generateScopedName })]).process(source, { - from: sourceFile, - }); + await postcss([plugin({ generateScopedName })]).process(source, { + from: sourceFile, + }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ title: "_saveJSON_title" }); + expect(JSON.parse(json)).toMatchObject({ title: "_saveJSON_title" }); }); it("processes globalModulePaths option", async () => { - const sourceFile = path.join(fixturesPath, "in", "globalModulePaths.css"); - const source = fs.readFileSync(sourceFile).toString(); + const sourceFile = path.join(fixturesPath, "in", "globalModulePaths.css"); + const source = fs.readFileSync(sourceFile).toString(); - const thePlugin = plugin({ - generateScopedName, - globalModulePaths: [/globalModulePaths/], - getJSON: () => {}, - }); + const thePlugin = plugin({ + generateScopedName, + globalModulePaths: [/globalModulePaths/], + getJSON: () => {}, + }); - const result = await postcss([thePlugin]).process(source, { - from: sourceFile, - }); + const result = await postcss([thePlugin]).process(source, { + from: sourceFile, + }); - expect(result.css).toMatchSnapshot("processes globalModulePaths option"); + expect(result.css).toMatchSnapshot("processes globalModulePaths option"); }); it("processes localsConvention with camelCase option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ generateScopedName, localsConvention: "camelCase" }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - "camel-case": "_camelCase_camel-case", - camelCase: "_camelCase_camel-case", - "camel-case-extra": "_camelCase_camel-case-extra", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - fooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ generateScopedName, localsConvention: "camelCase" }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + "camel-case": "_camelCase_camel-case", + camelCase: "_camelCase_camel-case", + "camel-case-extra": "_camelCase_camel-case-extra", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + fooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with camelCaseOnly option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([ - plugin({ generateScopedName, localsConvention: "camelCaseOnly" }), - ]).process(source, { from: sourceFile }); + await postcss([ + plugin({ generateScopedName, localsConvention: "camelCaseOnly" }), + ]).process(source, { from: sourceFile }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ - camelCase: "_camelCase_camel-case", - camelCaseExtra: "_camelCase_camel-case-extra", - fooBar: "_camelCase_FooBar", - }); + expect(JSON.parse(json)).toMatchObject({ + camelCase: "_camelCase_camel-case", + camelCaseExtra: "_camelCase_camel-case-extra", + fooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with dashes option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ generateScopedName, localsConvention: "dashes" }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - "camel-case": "_camelCase_camel-case", - camelCase: "_camelCase_camel-case", - "camel-case-extra": "_camelCase_camel-case-extra", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ generateScopedName, localsConvention: "dashes" }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + "camel-case": "_camelCase_camel-case", + camelCase: "_camelCase_camel-case", + "camel-case-extra": "_camelCase_camel-case-extra", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with dashes option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([ - plugin({ generateScopedName, localsConvention: "dashes" }), - ]).process(source, { from: sourceFile }); + await postcss([ + plugin({ generateScopedName, localsConvention: "dashes" }), + ]).process(source, { from: sourceFile }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ - camelCase: "_camelCase_camel-case", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + expect(JSON.parse(json)).toMatchObject({ + camelCase: "_camelCase_camel-case", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with function option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ - generateScopedName, - localsConvention: (className) => { - return className.replace("camel-case", "cc"); - }, - }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - cc: "_camelCase_camel-case", - "cc-extra": "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ + generateScopedName, + localsConvention: (className) => { + return className.replace("camel-case", "cc"); + }, + }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + cc: "_camelCase_camel-case", + "cc-extra": "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes hashPrefix option", async () => { - const generateScopedName = "[hash:base64:5]"; - const hashPrefix = "prefix"; - const getJSON = () => {}; + const generateScopedName = "[hash:base64:5]"; + const hashPrefix = "prefix"; + const getJSON = () => {}; - const withoutHashPrefix = plugin({ generateScopedName, getJSON }); - const withHashPrefix = plugin({ generateScopedName, getJSON, hashPrefix }); + const withoutHashPrefix = plugin({ generateScopedName, getJSON }); + const withHashPrefix = plugin({ generateScopedName, getJSON, hashPrefix }); - const css = ".foo {}"; - const params = { from: "test.css" }; + const css = ".foo {}"; + const params = { from: "test.css" }; - const result1 = await postcss([withoutHashPrefix]).process(css, params); - const result2 = await postcss([withHashPrefix]).process(css, params); + const result1 = await postcss([withoutHashPrefix]).process(css, params); + const result2 = await postcss([withHashPrefix]).process(css, params); - expect(result2.css).toMatchSnapshot("processes hashPrefix option"); - expect(result1.css).not.toEqual(result2.css); + expect(result2.css).toMatchSnapshot("processes hashPrefix option"); + expect(result1.css).not.toEqual(result2.css); }); it("different instances have different generateScopedName functions", async () => { - const one = plugin({ - generateScopedName: () => "one", - getJSON: () => {}, - }); + const one = plugin({ + generateScopedName: () => "one", + getJSON: () => {}, + }); - const two = plugin({ - generateScopedName: () => "two", - getJSON: () => {}, - }); + const two = plugin({ + generateScopedName: () => "two", + getJSON: () => {}, + }); - const css = ".foo {}"; - const params = { from: "test.css" }; + const css = ".foo {}"; + const params = { from: "test.css" }; - const resultOne = await postcss([one]).process(css, params); - const resultTwo = await postcss([two]).process(css, params); + const resultOne = await postcss([one]).process(css, params); + const resultTwo = await postcss([two]).process(css, params); - expect(resultOne.css).toEqual(".one {}"); - expect(resultTwo.css).toEqual(".two {}"); + expect(resultOne.css).toEqual(".one {}"); + expect(resultTwo.css).toEqual(".two {}"); }); it("getJSON with outputFileName", async () => { - const sourceFile = path.join(fixturesPath, "in", "test", "getJSON.css"); - const expectedFile = path.join(fixturesPath, "out", "test", "getJSON"); - const source = fs.readFileSync(sourceFile).toString(); - const expectedJSON = fs.readFileSync(`${expectedFile}.json`).toString(); - let jsonFileName; - let resultJson; - - const plugins = [ - plugin({ - generateScopedName, - getJSON: (cssFile, json, outputFileName) => { - jsonFileName = outputFileName.replace(".css", ".json"); - resultJson = json; - }, - }), - ]; - - await postcss(plugins).process(source, { - from: sourceFile, - to: `${expectedFile}.css`, - }); - - expect(jsonFileName).toEqual(`${expectedFile}.json`); - expect(resultJson).toMatchObject(JSON.parse(expectedJSON)); + const sourceFile = path.join(fixturesPath, "in", "test", "getJSON.css"); + const expectedFile = path.join(fixturesPath, "out", "test", "getJSON"); + const source = fs.readFileSync(sourceFile).toString(); + const expectedJSON = fs.readFileSync(`${expectedFile}.json`).toString(); + let jsonFileName; + let resultJson; + + const plugins = [ + plugin({ + generateScopedName, + getJSON: (cssFile, json, outputFileName) => { + jsonFileName = outputFileName.replace(".css", ".json"); + resultJson = json; + }, + }), + ]; + + await postcss(plugins).process(source, { + from: sourceFile, + to: `${expectedFile}.css`, + }); + + expect(jsonFileName).toEqual(`${expectedFile}.json`); + expect(resultJson).toMatchObject(JSON.parse(expectedJSON)); }); it("exposes export tokens for other plugins", async () => { - const sourceFile = path.join(fixturesPath, "in", "values.css"); - const source = fs.readFileSync(sourceFile).toString(); - - const plugins = [ - plugin({ - generateScopedName, - getJSON: () => {}, - }), - ]; - - const result = await postcss(plugins).process(source, { - from: sourceFile, - }); - - expect(result.messages).toMatchSnapshot( - "exposes export tokens for other plugins" - ); + const sourceFile = path.join(fixturesPath, "in", "values.css"); + const source = fs.readFileSync(sourceFile).toString(); + + const plugins = [ + plugin({ + generateScopedName, + getJSON: () => {}, + }), + ]; + + const result = await postcss(plugins).process(source, { + from: sourceFile, + }); + + expect(result.messages).toMatchSnapshot( + "exposes export tokens for other plugins" + ); }); it("processes exportGlobals option", async () => { - const sourceFile = path.join(fixturesPath, "in", "classes.css"); - const source = fs.readFileSync(sourceFile).toString(); - let json; - - await postcss([ - plugin({ - generateScopedName, - exportGlobals: true, - getJSON: (_, result) => { - json = result; - }, - }), - ]).process(source, { from: sourceFile }); - - expect(json).toMatchObject({ - page: "page", - title: "_classes_title", - article: "_classes_article", - }); + const sourceFile = path.join(fixturesPath, "in", "classes.css"); + const source = fs.readFileSync(sourceFile).toString(); + let json; + + await postcss([ + plugin({ + generateScopedName, + exportGlobals: true, + getJSON: (_, result) => { + json = result; + }, + }), + ]).process(source, { from: sourceFile }); + + expect(json).toMatchObject({ + page: "page", + title: "_classes_title", + article: "_classes_article", + }); }); it("processes resolve option", async () => { - const sourceFile = path.join(fixturesPath, "in", "compose.resolve.css"); - const source = fs.readFileSync(sourceFile).toString(); - let json; - const result = await postcss([ - plugin({ - generateScopedName, - resolve: async (file) => { - return file.replace(/^test-fixture-in/, path.dirname(sourceFile)); - }, - getJSON: (_, result) => { - json = result; - }, - }), - ]).process(source, { from: sourceFile }); - - expect(result.css).toMatchSnapshot("processes resolve option"); - expect(json).toStrictEqual({ - figure: "_compose_resolve_figure _composes_a_hello", - "figure-single-quote": - "_compose_resolve_figure-single-quote _composes_a_hello", - }); + const sourceFile = path.join(fixturesPath, "in", "compose.resolve.css"); + const source = fs.readFileSync(sourceFile).toString(); + let json; + const result = await postcss([ + plugin({ + generateScopedName, + resolve: async (file) => { + return file.replace( + /^test-fixture-in/, + path.dirname(sourceFile) + ); + }, + getJSON: (_, result) => { + json = result; + }, + }), + ]).process(source, { from: sourceFile }); + + expect(result.css).toMatchSnapshot("processes resolve option"); + expect(json).toStrictEqual({ + figure: "_compose_resolve_figure _composes_a_hello", + "figure-single-quote": + "_compose_resolve_figure-single-quote _composes_a_hello", + }); +}); + +it("processes fileResolve option", async () => { + const sourceFile = path.join(fixturesPath, "in", "deepCompose.css"); + const source = fs.readFileSync(sourceFile).toString(); + let json; + const result = await postcss([ + plugin({ + generateScopedName, + fileResolve: async (file, importer) => { + return path.resolve( + path.dirname(importer), + file.replace(/^test-fixture-in/, path.dirname(sourceFile)) + ); + }, + getJSON: (_, result) => { + json = result; + }, + }), + ]).process(source, { from: sourceFile }); + + expect(result.css).toMatchSnapshot("processes fileResolve option"); + expect(json).toStrictEqual({ + deepCompose: + "_deepCompose_deepCompose _deepDeepCompose_deepDeepCompose _composes_mixins_title", + }); +}); + +it("processes fileResolve and resolve option", async () => { + const sourceFile = path.join(fixturesPath, "in", "deepCompose.css"); + const source = fs.readFileSync(sourceFile).toString(); + const result = await postcss([ + plugin({ + generateScopedName, + resolve: (file) => file, + fileResolve: async (file, importer) => { + return path.resolve( + path.dirname(importer), + file.replace(/^test-fixture-in/, path.dirname(sourceFile)) + ); + }, + }), + ]) + .process(source, { from: sourceFile }) + .catch((error) => error); + + expect(result instanceof Error).toBe(true); }); From 0bcc9351a5838e415b2111a5508061624790f50d Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 11 Oct 2022 09:42:28 +0200 Subject: [PATCH 2/3] turn off prettier for tests --- test/test.js | 675 +++++++++++++++++++++++++-------------------------- 1 file changed, 327 insertions(+), 348 deletions(-) diff --git a/test/test.js b/test/test.js index bccb616..22143f0 100644 --- a/test/test.js +++ b/test/test.js @@ -8,433 +8,412 @@ import { behaviours } from "../src/behaviours"; const fixturesPath = path.resolve(__dirname, "./fixtures"); function createPlugin(name, processor) { - const plugin = () => ({ - postcssPlugin: name, - Once: processor, - }); - plugin.postcss = true; - return plugin; + const plugin = () => ({ + postcssPlugin: name, + Once: processor, + }); + plugin.postcss = true; + return plugin; } const cases = { - plugins: "saves origin plugins", - classes: "processes classes", - comments: "preserves comments", - composes: "composes rules", - values: "processes values", - interpolated: "generates scoped name with interpolated string", - global: "allows to make CSS global", - localShorthand: "processes :local shorthand selector", - globalShorthand: "processes :global shorthand selector", + plugins: "saves origin plugins", + classes: "processes classes", + comments: "preserves comments", + composes: "composes rules", + values: "processes values", + interpolated: "generates scoped name with interpolated string", + global: "allows to make CSS global", + localShorthand: "processes :local shorthand selector", + globalShorthand: "processes :global shorthand selector", }; function generateScopedName(name, filename) { - const file = path.basename(filename, ".css").replace(/\./g, "_"); - return `_${file}_${name}`; + const file = path.basename(filename, ".css").replace(/\./g, "_"); + return `_${file}_${name}`; } Object.keys(cases).forEach((name) => { - const description = cases[name]; - - const scopedNameGenerator = - name === "interpolated" - ? "[name]__[local]___[hash:base64:5]" - : generateScopedName; - - const scopeBehaviour = - name === behaviours.GLOBAL || name === "globalShorthand" - ? behaviours.GLOBAL - : behaviours.LOCAL; - - it(description, async () => { - const sourceFile = path.join(fixturesPath, "in", `${name}.css`); - const source = fs.readFileSync(sourceFile).toString(); - - let resultJson; - - const plugins = [ - autoprefixer, - plugin({ - scopeBehaviour, - generateScopedName: scopedNameGenerator, - getJSON: (cssFile, json) => { - resultJson = json; - }, - }), - ]; - - const result = await postcss(plugins).process(source, { - from: sourceFile, - }); - - expect(result.css).toMatchSnapshot(`${description} - CSS`); - expect(resultJson).toMatchSnapshot(`${description} - JSON`); - }); - - it(`only calls plugins once when it ${description}`, async () => { - const sourceFile = path.join(fixturesPath, "in", `${name}.css`); - const source = fs.readFileSync(sourceFile).toString(); - - const rootsSeenBeforePlugin = new Set(); - const rootsSeenAfterPlugin = new Set(); - - const plugins = [ - autoprefixer, - createPlugin("validator-1", (root) => { - if (rootsSeenBeforePlugin.has(root)) { - throw new Error( - "Plugin before ours was called multiple times." - ); - } - rootsSeenBeforePlugin.add(root); - root.prepend( - `/* validator-1-start (${path.basename( - root.source.input.file - )}) */` - ); - root.append( - `/* validator-1-end (${path.basename( - root.source.input.file - )}) */` - ); - }), - plugin({ - scopeBehaviour, - generateScopedName: scopedNameGenerator, - getJSON: () => {}, - }), - createPlugin("validator-2", (root) => { - if (rootsSeenAfterPlugin.has(root)) { - throw new Error( - "Plugin after ours was called multiple times." - ); - } - rootsSeenAfterPlugin.add(root); - root.prepend( - `/* validator-2-start (${path.basename( - root.source.input.file - )}) */` - ); - root.append( - `/* validator-2-end (${path.basename( - root.source.input.file - )}) */` - ); - }), - ]; - - const result = await postcss(plugins).process(source, { - from: sourceFile, - }); - - expect(result.css).toMatchSnapshot( - `plugins once - ${description} - CSS` - ); - }); + const description = cases[name]; + + const scopedNameGenerator = + name === "interpolated" + ? "[name]__[local]___[hash:base64:5]" + : generateScopedName; + + const scopeBehaviour = + name === behaviours.GLOBAL || name === "globalShorthand" + ? behaviours.GLOBAL + : behaviours.LOCAL; + + it(description, async () => { + const sourceFile = path.join(fixturesPath, "in", `${name}.css`); + const source = fs.readFileSync(sourceFile).toString(); + + let resultJson; + + const plugins = [ + autoprefixer, + plugin({ + scopeBehaviour, + generateScopedName: scopedNameGenerator, + getJSON: (cssFile, json) => { + resultJson = json; + }, + }), + ]; + + const result = await postcss(plugins).process(source, { from: sourceFile }); + + expect(result.css).toMatchSnapshot(`${description} - CSS`); + expect(resultJson).toMatchSnapshot(`${description} - JSON`); + }); + + it(`only calls plugins once when it ${description}`, async () => { + const sourceFile = path.join(fixturesPath, "in", `${name}.css`); + const source = fs.readFileSync(sourceFile).toString(); + + const rootsSeenBeforePlugin = new Set(); + const rootsSeenAfterPlugin = new Set(); + + const plugins = [ + autoprefixer, + createPlugin("validator-1", (root) => { + if (rootsSeenBeforePlugin.has(root)) { + throw new Error("Plugin before ours was called multiple times."); + } + rootsSeenBeforePlugin.add(root); + root.prepend( + `/* validator-1-start (${path.basename(root.source.input.file)}) */` + ); + root.append( + `/* validator-1-end (${path.basename(root.source.input.file)}) */` + ); + }), + plugin({ + scopeBehaviour, + generateScopedName: scopedNameGenerator, + getJSON: () => {}, + }), + createPlugin("validator-2", (root) => { + if (rootsSeenAfterPlugin.has(root)) { + throw new Error("Plugin after ours was called multiple times."); + } + rootsSeenAfterPlugin.add(root); + root.prepend( + `/* validator-2-start (${path.basename(root.source.input.file)}) */` + ); + root.append( + `/* validator-2-end (${path.basename(root.source.input.file)}) */` + ); + }), + ]; + + const result = await postcss(plugins).process(source, { from: sourceFile }); + + expect(result.css).toMatchSnapshot(`plugins once - ${description} - CSS`); + }); }); it("works with visitor plugins", async () => { - const source = ` + const source = ` p { color: green; } `; - const plugins = [ - { - postcssPlugin: "turn-values-blue", - Declaration(decl) { - decl.value = "blue"; - }, - }, - plugin(), - ]; - const result = await postcss(plugins).process(source, { from: undefined }); - expect(result.css).toEqual(source.replace("green", "blue")); + const plugins = [ + { + postcssPlugin: "turn-values-blue", + Declaration(decl) { + decl.value = "blue"; + }, + }, + plugin(), + ]; + const result = await postcss(plugins).process(source, { from: undefined }); + expect(result.css).toEqual(source.replace("green", "blue")); }); it("saves JSON next to CSS by default", async () => { - const sourceFile = path.join(fixturesPath, "in", "saveJSON.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "saveJSON.css.json"); + const sourceFile = path.join(fixturesPath, "in", "saveJSON.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "saveJSON.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([plugin({ generateScopedName })]).process(source, { - from: sourceFile, - }); + await postcss([plugin({ generateScopedName })]).process(source, { + from: sourceFile, + }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ title: "_saveJSON_title" }); + expect(JSON.parse(json)).toMatchObject({ title: "_saveJSON_title" }); }); it("processes globalModulePaths option", async () => { - const sourceFile = path.join(fixturesPath, "in", "globalModulePaths.css"); - const source = fs.readFileSync(sourceFile).toString(); + const sourceFile = path.join(fixturesPath, "in", "globalModulePaths.css"); + const source = fs.readFileSync(sourceFile).toString(); - const thePlugin = plugin({ - generateScopedName, - globalModulePaths: [/globalModulePaths/], - getJSON: () => {}, - }); + const thePlugin = plugin({ + generateScopedName, + globalModulePaths: [/globalModulePaths/], + getJSON: () => {}, + }); - const result = await postcss([thePlugin]).process(source, { - from: sourceFile, - }); + const result = await postcss([thePlugin]).process(source, { + from: sourceFile, + }); - expect(result.css).toMatchSnapshot("processes globalModulePaths option"); + expect(result.css).toMatchSnapshot("processes globalModulePaths option"); }); it("processes localsConvention with camelCase option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ generateScopedName, localsConvention: "camelCase" }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - "camel-case": "_camelCase_camel-case", - camelCase: "_camelCase_camel-case", - "camel-case-extra": "_camelCase_camel-case-extra", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - fooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ generateScopedName, localsConvention: "camelCase" }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + "camel-case": "_camelCase_camel-case", + camelCase: "_camelCase_camel-case", + "camel-case-extra": "_camelCase_camel-case-extra", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + fooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with camelCaseOnly option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([ - plugin({ generateScopedName, localsConvention: "camelCaseOnly" }), - ]).process(source, { from: sourceFile }); + await postcss([ + plugin({ generateScopedName, localsConvention: "camelCaseOnly" }), + ]).process(source, { from: sourceFile }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ - camelCase: "_camelCase_camel-case", - camelCaseExtra: "_camelCase_camel-case-extra", - fooBar: "_camelCase_FooBar", - }); + expect(JSON.parse(json)).toMatchObject({ + camelCase: "_camelCase_camel-case", + camelCaseExtra: "_camelCase_camel-case-extra", + fooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with dashes option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ generateScopedName, localsConvention: "dashes" }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - "camel-case": "_camelCase_camel-case", - camelCase: "_camelCase_camel-case", - "camel-case-extra": "_camelCase_camel-case-extra", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ generateScopedName, localsConvention: "dashes" }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + "camel-case": "_camelCase_camel-case", + camelCase: "_camelCase_camel-case", + "camel-case-extra": "_camelCase_camel-case-extra", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with dashes option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - await postcss([ - plugin({ generateScopedName, localsConvention: "dashes" }), - ]).process(source, { from: sourceFile }); + await postcss([ + plugin({ generateScopedName, localsConvention: "dashes" }), + ]).process(source, { from: sourceFile }); - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); - expect(JSON.parse(json)).toMatchObject({ - camelCase: "_camelCase_camel-case", - camelCaseExtra: "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + expect(JSON.parse(json)).toMatchObject({ + camelCase: "_camelCase_camel-case", + camelCaseExtra: "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes localsConvention with function option", async () => { - const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); - const source = fs.readFileSync(sourceFile).toString(); - const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); - - if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); - - await postcss([ - plugin({ - generateScopedName, - localsConvention: (className) => { - return className.replace("camel-case", "cc"); - }, - }), - ]).process(source, { from: sourceFile }); - - const json = fs.readFileSync(jsonFile).toString(); - fs.unlinkSync(jsonFile); - - expect(JSON.parse(json)).toMatchObject({ - cc: "_camelCase_camel-case", - "cc-extra": "_camelCase_camel-case-extra", - FooBar: "_camelCase_FooBar", - }); + const sourceFile = path.join(fixturesPath, "in", "camelCase.css"); + const source = fs.readFileSync(sourceFile).toString(); + const jsonFile = path.join(fixturesPath, "in", "camelCase.css.json"); + + if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile); + + await postcss([ + plugin({ + generateScopedName, + localsConvention: (className) => { + return className.replace("camel-case", "cc"); + }, + }), + ]).process(source, { from: sourceFile }); + + const json = fs.readFileSync(jsonFile).toString(); + fs.unlinkSync(jsonFile); + + expect(JSON.parse(json)).toMatchObject({ + cc: "_camelCase_camel-case", + "cc-extra": "_camelCase_camel-case-extra", + FooBar: "_camelCase_FooBar", + }); }); it("processes hashPrefix option", async () => { - const generateScopedName = "[hash:base64:5]"; - const hashPrefix = "prefix"; - const getJSON = () => {}; + const generateScopedName = "[hash:base64:5]"; + const hashPrefix = "prefix"; + const getJSON = () => {}; - const withoutHashPrefix = plugin({ generateScopedName, getJSON }); - const withHashPrefix = plugin({ generateScopedName, getJSON, hashPrefix }); + const withoutHashPrefix = plugin({ generateScopedName, getJSON }); + const withHashPrefix = plugin({ generateScopedName, getJSON, hashPrefix }); - const css = ".foo {}"; - const params = { from: "test.css" }; + const css = ".foo {}"; + const params = { from: "test.css" }; - const result1 = await postcss([withoutHashPrefix]).process(css, params); - const result2 = await postcss([withHashPrefix]).process(css, params); + const result1 = await postcss([withoutHashPrefix]).process(css, params); + const result2 = await postcss([withHashPrefix]).process(css, params); - expect(result2.css).toMatchSnapshot("processes hashPrefix option"); - expect(result1.css).not.toEqual(result2.css); + expect(result2.css).toMatchSnapshot("processes hashPrefix option"); + expect(result1.css).not.toEqual(result2.css); }); it("different instances have different generateScopedName functions", async () => { - const one = plugin({ - generateScopedName: () => "one", - getJSON: () => {}, - }); + const one = plugin({ + generateScopedName: () => "one", + getJSON: () => {}, + }); - const two = plugin({ - generateScopedName: () => "two", - getJSON: () => {}, - }); + const two = plugin({ + generateScopedName: () => "two", + getJSON: () => {}, + }); - const css = ".foo {}"; - const params = { from: "test.css" }; + const css = ".foo {}"; + const params = { from: "test.css" }; - const resultOne = await postcss([one]).process(css, params); - const resultTwo = await postcss([two]).process(css, params); + const resultOne = await postcss([one]).process(css, params); + const resultTwo = await postcss([two]).process(css, params); - expect(resultOne.css).toEqual(".one {}"); - expect(resultTwo.css).toEqual(".two {}"); + expect(resultOne.css).toEqual(".one {}"); + expect(resultTwo.css).toEqual(".two {}"); }); it("getJSON with outputFileName", async () => { - const sourceFile = path.join(fixturesPath, "in", "test", "getJSON.css"); - const expectedFile = path.join(fixturesPath, "out", "test", "getJSON"); - const source = fs.readFileSync(sourceFile).toString(); - const expectedJSON = fs.readFileSync(`${expectedFile}.json`).toString(); - let jsonFileName; - let resultJson; - - const plugins = [ - plugin({ - generateScopedName, - getJSON: (cssFile, json, outputFileName) => { - jsonFileName = outputFileName.replace(".css", ".json"); - resultJson = json; - }, - }), - ]; - - await postcss(plugins).process(source, { - from: sourceFile, - to: `${expectedFile}.css`, - }); - - expect(jsonFileName).toEqual(`${expectedFile}.json`); - expect(resultJson).toMatchObject(JSON.parse(expectedJSON)); + const sourceFile = path.join(fixturesPath, "in", "test", "getJSON.css"); + const expectedFile = path.join(fixturesPath, "out", "test", "getJSON"); + const source = fs.readFileSync(sourceFile).toString(); + const expectedJSON = fs.readFileSync(`${expectedFile}.json`).toString(); + let jsonFileName; + let resultJson; + + const plugins = [ + plugin({ + generateScopedName, + getJSON: (cssFile, json, outputFileName) => { + jsonFileName = outputFileName.replace(".css", ".json"); + resultJson = json; + }, + }), + ]; + + await postcss(plugins).process(source, { + from: sourceFile, + to: `${expectedFile}.css`, + }); + + expect(jsonFileName).toEqual(`${expectedFile}.json`); + expect(resultJson).toMatchObject(JSON.parse(expectedJSON)); }); it("exposes export tokens for other plugins", async () => { - const sourceFile = path.join(fixturesPath, "in", "values.css"); - const source = fs.readFileSync(sourceFile).toString(); - - const plugins = [ - plugin({ - generateScopedName, - getJSON: () => {}, - }), - ]; - - const result = await postcss(plugins).process(source, { - from: sourceFile, - }); - - expect(result.messages).toMatchSnapshot( - "exposes export tokens for other plugins" - ); + const sourceFile = path.join(fixturesPath, "in", "values.css"); + const source = fs.readFileSync(sourceFile).toString(); + + const plugins = [ + plugin({ + generateScopedName, + getJSON: () => {}, + }), + ]; + + const result = await postcss(plugins).process(source, { + from: sourceFile, + }); + + expect(result.messages).toMatchSnapshot( + "exposes export tokens for other plugins" + ); }); it("processes exportGlobals option", async () => { - const sourceFile = path.join(fixturesPath, "in", "classes.css"); - const source = fs.readFileSync(sourceFile).toString(); - let json; - - await postcss([ - plugin({ - generateScopedName, - exportGlobals: true, - getJSON: (_, result) => { - json = result; - }, - }), - ]).process(source, { from: sourceFile }); - - expect(json).toMatchObject({ - page: "page", - title: "_classes_title", - article: "_classes_article", - }); + const sourceFile = path.join(fixturesPath, "in", "classes.css"); + const source = fs.readFileSync(sourceFile).toString(); + let json; + + await postcss([ + plugin({ + generateScopedName, + exportGlobals: true, + getJSON: (_, result) => { + json = result; + }, + }), + ]).process(source, { from: sourceFile }); + + expect(json).toMatchObject({ + page: "page", + title: "_classes_title", + article: "_classes_article", + }); }); it("processes resolve option", async () => { - const sourceFile = path.join(fixturesPath, "in", "compose.resolve.css"); - const source = fs.readFileSync(sourceFile).toString(); - let json; - const result = await postcss([ - plugin({ - generateScopedName, - resolve: async (file) => { - return file.replace( - /^test-fixture-in/, - path.dirname(sourceFile) - ); - }, - getJSON: (_, result) => { - json = result; - }, - }), - ]).process(source, { from: sourceFile }); - - expect(result.css).toMatchSnapshot("processes resolve option"); - expect(json).toStrictEqual({ - figure: "_compose_resolve_figure _composes_a_hello", - "figure-single-quote": - "_compose_resolve_figure-single-quote _composes_a_hello", - }); + const sourceFile = path.join(fixturesPath, "in", "compose.resolve.css"); + const source = fs.readFileSync(sourceFile).toString(); + let json; + const result = await postcss([ + plugin({ + generateScopedName, + resolve: async (file) => { + return file.replace(/^test-fixture-in/, path.dirname(sourceFile)); + }, + getJSON: (_, result) => { + json = result; + }, + }), + ]).process(source, { from: sourceFile }); + + expect(result.css).toMatchSnapshot("processes resolve option"); + expect(json).toStrictEqual({ + figure: "_compose_resolve_figure _composes_a_hello", + "figure-single-quote": + "_compose_resolve_figure-single-quote _composes_a_hello", + }); }); it("processes fileResolve option", async () => { From 53b410f5f9293edce25635f81d519bac3a4060fd Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 11 Oct 2022 10:34:37 +0200 Subject: [PATCH 3/3] improve names --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index c7c9428..497dfd3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -52,7 +52,7 @@ declare interface Options { resolve?: (file: string) => string | Promise; fileResolve?: ( - filerImport: string, + file: string, importer: string ) => string | null | Promise; }