diff --git a/lib/processors/cssOptimizer.js b/lib/processors/cssOptimizer.js new file mode 100644 index 000000000..fd4f2112d --- /dev/null +++ b/lib/processors/cssOptimizer.js @@ -0,0 +1,32 @@ +const CleanCSS = require("clean-css"); +const log = require("@ui5/logger").getLogger("builder:processors:cssOptimizer"); + +/** + * Optimizes the supplied CSS resources. + * + * @public + * @alias module:@ui5/builder.processors.cssOptimizer + * @param {Object} parameters Parameters + * @param {module:@ui5/fs.Resource[]} parameters.resources List of resources to be processed + * @returns {Promise} Promise resolving with optimized resources + */ +module.exports = function({resources}) { + // use clean-css default options + const options = { + returnPromise: true + }; + const cleanCSSInstance = new CleanCSS(options); + return Promise.all(resources.map((resource) => { + return resource.getString().then((css) => { + return cleanCSSInstance.minify(css).then((result) => { + if (Array.isArray(result.warnings) && result.warnings.length) { + log.warn(`Warnings occurred: ${result.warnings.join(", ")}`); + } + resource.setString(result.styles); + return resource; + }, (error) => { + throw new Error(`Errors occurred: ${error.join(", ")}`); + }); + }); + })); +}; diff --git a/lib/processors/themeBuilder.js b/lib/processors/themeBuilder.js index 5df68ab40..63b1c8c5a 100644 --- a/lib/processors/themeBuilder.js +++ b/lib/processors/themeBuilder.js @@ -30,9 +30,20 @@ class ThemeBuilder { * @param {module:@ui5/fs.Resource[]} resources Library files * @param {Object} [options] Build options * @param {boolean} [options.compress=false] Compress build output (CSS / JSON) + * DEPRECATED: Use [cssOptimizer]{@link module:@ui5/builder.processors.cssOptimizer} to compress CSS output. + * Use compressJSON option to compress JSON output. + * @param {boolean} [options.compressJSON=false] Compress build output (JSON) * @returns {Promise} Resolving with array of created files */ - build(resources, {compress = false} = {}) { + build(resources, {compress = false, compressJSON = false} = {}) { + // TODO 2.0: Remove deprecated "compress" parameter + if (compress) { + log.warn(`themeBuilder.build called with deprecated parameter "compress". ` + + `This parameter will be removed in @ui5/fs version 2.0. ` + + `Use processor "cssOptimizer" instead` + ); + } + const files = []; const compile = (resource) => { @@ -67,7 +78,7 @@ class ThemeBuilder { const libParams = new Resource({ path: themeDir + "/library-parameters.json", - string: JSON.stringify(result.variables, null, compress ? null : "\t") + string: JSON.stringify(result.variables, null, (compress || compressJSON) ? null : "\t") }); files.push(libCss, libCssRtl, libParams); @@ -99,13 +110,15 @@ class ThemeBuilder { * @param {module:@ui5/fs.Resource[]} parameters.resources List of library.source.less resources to be processed * @param {fs|module:@ui5/fs.fsInterface} parameters.fs Node fs or custom [fs interface]{@link module:resources/module:@ui5/fs.fsInterface} * @param {Object} [parameters.options] Options - * @param {Object} [parameters.options.compress=false] Compress build output (CSS / JSON) + * @param {boolean} [parameters.options.compress=false] Compress build output (CSS / JSON) + * @param {boolean} [parameters.options.compressJSON=false] Compress build output (JSON) * @returns {Promise} Promise resolving with theme resources */ module.exports = ({resources, fs, options}) => { const themeBuilder = new ThemeBuilder({fs}); const compress = options && options.compress; - return themeBuilder.build(resources, {compress}).then((files) => { + const compressJSON = options && options.compressJSON; + return themeBuilder.build(resources, {compress, compressJSON}).then((files) => { themeBuilder.clearCache(); return files; }); diff --git a/lib/tasks/buildThemes.js b/lib/tasks/buildThemes.js index a8a80b987..2bd4f126e 100644 --- a/lib/tasks/buildThemes.js +++ b/lib/tasks/buildThemes.js @@ -1,4 +1,5 @@ const themeBuilder = require("../processors/themeBuilder"); +const cssOptimizer = require("../processors/cssOptimizer"); const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; const fsInterface = require("@ui5/fs").fsInterface; @@ -14,10 +15,10 @@ const fsInterface = require("@ui5/fs").fsInterface; * @param {string} parameters.options.projectName Project name * @param {string} parameters.options.inputPattern Search pattern for *.less files to be built * @param {string} [parameters.options.librariesPattern] Search pattern for .library files - * @param {boolean} [parameters.options.compress=true] + * @param {boolean} [parameters.options.compress=true] Whether or not to compress the theme resources * @returns {Promise} Promise resolving with undefined once data has been written */ -module.exports = function({workspace, dependencies, options}) { +module.exports = async function({workspace, dependencies, options}) { const combo = new ReaderCollectionPrioritized({ name: `theme - prioritize workspace over dependencies: ${options.projectName}`, readers: [workspace, dependencies] @@ -30,13 +31,13 @@ module.exports = function({workspace, dependencies, options}) { promises.push(combo.byGlob(options.librariesPattern)); } - const compress = options.compress === undefined ? true : options.compress; + const [allResources, availableLibraries] = await Promise.all(promises); - return Promise.all(promises).then(([allResources, availableLibraries]) => { - if (!availableLibraries || availableLibraries.length === 0) { - // Try to build all themes - return allResources; - } + let resources; + if (!availableLibraries || availableLibraries.length === 0) { + // Try to build all themes + resources = allResources; + } else { /* Don't try to build themes for libraries that are not available (maybe replace this with something more aware of which dependencies are optional and therefore legitimately missing and which not (fault case)) @@ -54,18 +55,28 @@ module.exports = function({workspace, dependencies, options}) { return false; }; - return allResources.filter(isAvailable); - }).then((resources) => { - return themeBuilder({ - resources, - fs: fsInterface(combo), - options: { - compress - } - }); - }).then((processedResources) => { - return Promise.all(processedResources.map((resource) => { - return workspace.write(resource); - })); + resources = await allResources.filter(isAvailable); + } + + const compress = options.compress === undefined ? true : options.compress; + // do not use compression flag of less.js css (deprecated) + const processedResources = await themeBuilder({ + resources, + fs: fsInterface(combo), + options: { + compressJSON: compress, + compress: false + } }); + + if (compress) { + const cssResources = processedResources.filter((resource) => { + return resource.getPath().endsWith(".css"); + }); + await cssOptimizer({resources: cssResources, + fs: fsInterface(combo)}); + } + return Promise.all(processedResources.map((resource) => { + return workspace.write(resource); + })); }; diff --git a/package-lock.json b/package-lock.json index 5e059a646..e24b53c1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1371,6 +1371,14 @@ "integrity": "sha512-u6dx20FBXm+apMi+5x7UVm6EH7BL1gc4XrcnQewjcB7HWRcor/V5qWc3RG2HwpgDJ26gIi2DSEu3B7sXynAw/g==", "dev": true }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "requires": { + "source-map": "~0.6.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", diff --git a/package.json b/package.json index 2f3d8c08d..7d606f82c 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@ui5/fs": "^1.1.2", "@ui5/logger": "^1.0.2", "cheerio": "^0.22.0", + "clean-css": "^4.2.1", "escape-unicode": "^0.2.0", "escodegen": "^1.12.1", "escope": "^3.6.0", diff --git a/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css b/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css index 5398b3f08..5fcc13025 100644 --- a/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css +++ b/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css b/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css index ba056b3c0..b5e1bd92f 100644 --- a/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css +++ b/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css b/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css index 5398b3f08..5fcc13025 100644 --- a/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css +++ b/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css b/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css index ba056b3c0..b5e1bd92f 100644 --- a/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css +++ b/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css b/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css index 5398b3f08..5fcc13025 100644 --- a/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css +++ b/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css b/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css index ba056b3c0..b5e1bd92f 100644 --- a/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css +++ b/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css @@ -1,3 +1 @@ -.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} -/* Inline theming parameters */ -#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px}#sap-ui-theme-library\.a{background-image:url(data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D)} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/Button.less b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/Button.less new file mode 100644 index 000000000..dc0114a2a --- /dev/null +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/Button.less @@ -0,0 +1,24 @@ +/* function */ +._sapUiTableVScrWithActionsInnerMixin_CalcMarginRight(@ScrollbarSize, @RowActionWidth) { + margin-right: calc(@ScrollbarSize ~"+" @RowActionWidth); +} + +._sapUiTableVScrWithActionsInnerMixin(@ScrollbarSize, @RowActionWidth) { + + .meinnerle { + ._sapUiTableVScrWithActionsInnerMixin_CalcMarginRight(@ScrollbarSize, @RowActionWidth); + } + + .myele { + margin-right: @ScrollbarSize; + } +} + +.supi:not(.thehe) { + ._sapUiTableVScrWithActionsInnerMixin(0px, @_mywidth); +} + +.button { + color: @_mycolor1; + background-color: @_mycolor2; +} diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-RTL.css b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-RTL.css new file mode 100644 index 000000000..c9760e4a6 --- /dev/null +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-RTL.css @@ -0,0 +1 @@ +.supi:not(.thehe) .meinnerle{margin-left:calc(0px + .25rem)}.supi:not(.thehe) .myele{margin-left:0}.button{color:#f0f;background-color:#f0f}#sap-ui-theme-theme\.j{background-image:url(data:text/plain;utf-8,%7B%22_mywidth%22%3A%220.25rem%22%2C%22_mycolor1%22%3A%22%23ff00ff%22%2C%22_mycolor2%22%3A%22%23ff00ff%22%7D)} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-parameters.json b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-parameters.json new file mode 100644 index 000000000..fc651c3bf --- /dev/null +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library-parameters.json @@ -0,0 +1 @@ +{"_mywidth":"0.25rem","_mycolor1":"#ff00ff","_mycolor2":"#ff00ff"} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.css b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.css new file mode 100644 index 000000000..9b2b8694b --- /dev/null +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.css @@ -0,0 +1 @@ +.supi:not(.thehe) .meinnerle{margin-right:calc(0px + .25rem)}.supi:not(.thehe) .myele{margin-right:0}.button{color:#f0f;background-color:#f0f}#sap-ui-theme-theme\.j{background-image:url(data:text/plain;utf-8,%7B%22_mywidth%22%3A%220.25rem%22%2C%22_mycolor1%22%3A%22%23ff00ff%22%2C%22_mycolor2%22%3A%22%23ff00ff%22%7D)} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.source.less b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.source.less new file mode 100644 index 000000000..4caf020a5 --- /dev/null +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/compressiontheme/library.source.less @@ -0,0 +1,4 @@ +@_mywidth: 0.25rem; +@_mycolor1: #ff00ff; +@_mycolor2: #f0f; +@import "Button.less"; diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css index 5009ca50e..f5c88f04d 100644 --- a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -1,3 +1 @@ -.someClass{color:#000} -/* Inline theming parameters */ -#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} +.someClass{color:#000}#sap-ui-theme-theme\.j{background-image:url(data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000000%22%7D)} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json index a190cda03..96cecdfae 100644 --- a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -1 +1 @@ -{"someColor":"#000"} \ No newline at end of file +{"someColor":"#000000"} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css index 5009ca50e..f5c88f04d 100644 --- a/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css +++ b/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css @@ -1,3 +1 @@ -.someClass{color:#000} -/* Inline theming parameters */ -#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} +.someClass{color:#000}#sap-ui-theme-theme\.j{background-image:url(data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000000%22%7D)} \ No newline at end of file diff --git a/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/Button.less b/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/Button.less new file mode 100644 index 000000000..dc0114a2a --- /dev/null +++ b/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/Button.less @@ -0,0 +1,24 @@ +/* function */ +._sapUiTableVScrWithActionsInnerMixin_CalcMarginRight(@ScrollbarSize, @RowActionWidth) { + margin-right: calc(@ScrollbarSize ~"+" @RowActionWidth); +} + +._sapUiTableVScrWithActionsInnerMixin(@ScrollbarSize, @RowActionWidth) { + + .meinnerle { + ._sapUiTableVScrWithActionsInnerMixin_CalcMarginRight(@ScrollbarSize, @RowActionWidth); + } + + .myele { + margin-right: @ScrollbarSize; + } +} + +.supi:not(.thehe) { + ._sapUiTableVScrWithActionsInnerMixin(0px, @_mywidth); +} + +.button { + color: @_mycolor1; + background-color: @_mycolor2; +} diff --git a/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/library.source.less b/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/library.source.less new file mode 100644 index 000000000..4caf020a5 --- /dev/null +++ b/test/fixtures/theme.j/main/src/theme/j/themes/compressiontheme/library.source.less @@ -0,0 +1,4 @@ +@_mywidth: 0.25rem; +@_mycolor1: #ff00ff; +@_mycolor2: #f0f; +@import "Button.less"; diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index 8b1bcf5d4..b2cd63b39 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -68,7 +68,7 @@ async function checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, des expectedContent = JSON.parse(expectedContent.replace(/(:\s+)(\d+)/g, ": 0")); assert.deepEqual(currentContent, expectedContent); } else { - assert.equal(currentContent.replace(newLineRegexp, "\n"), expectedContent.replace(newLineRegexp, "\n")); + assert.equal(currentContent.replace(newLineRegexp, "\n"), expectedContent.replace(newLineRegexp, "\n"), `file contents do not match. Paths actual: '${destFile}', expected: '${expectedFile}'`); } }; await Promise.all([currentFileContentPromise, expectedFileContentPromise]).then(assertContents); diff --git a/test/lib/processors/cssOptimizer.js b/test/lib/processors/cssOptimizer.js new file mode 100644 index 000000000..fcbb3a9e7 --- /dev/null +++ b/test/lib/processors/cssOptimizer.js @@ -0,0 +1,89 @@ +const test = require("ava"); + +const resourceFactory = require("@ui5/fs").resourceFactory; + +const mock = require("mock-require"); +const sinon = require("sinon"); + +const logger = require("@ui5/logger"); + + +function prepareResources() { + const input = + `@someColor: black; +.someClass { + color: @someColor; + padding: 1px 2px 3px 4px; +}`; + + const memoryAdapter = resourceFactory.createAdapter({ + virBasePath: "/" + }); + + const lessFilePath = "/resources/foo.less"; + + const resource = resourceFactory.createResource({ + path: lessFilePath, + string: input + }); + + memoryAdapter.write(resource); + + return { + resource, + memoryAdapter + }; +} + + +test.before((t) => { + t.context.cleanCss = {}; + const cleanCssStub = sinon.stub().returns(t.context.cleanCss); + mock("clean-css", cleanCssStub); + + t.context.warnLogStub = sinon.stub(); + sinon.stub(logger, "getLogger").returns({ + warn: t.context.warnLogStub + }); +}); + +test.after((t) => { + mock.stop("clean-css"); + + sinon.restore(); + mock.reRequire("clean-css"); +}); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("cssOptimizer: Runtime error case", async (t) => { + const {resource} = prepareResources(); + const cssOptimizer = require("../../../lib/processors/cssOptimizer"); + + t.context.cleanCss.minify = function() { + // eslint-disable-next-line prefer-promise-reject-errors + return Promise.reject(["something bad happened"]); + }; + + const error = await t.throwsAsync(async ()=>{ + await cssOptimizer({resources: [resource]}); + }); + t.is(error.message, "Errors occurred: something bad happened", "Error message should be the same"); +}); + +test.serial("cssOptimizer: warnings", async (t) => { + const {resource} = prepareResources(); + + t.context.cleanCss.minify = function() { + return Promise.resolve({ + warnings: ["my warning"], + styles: "mystyle" + }); + }; + const cssOptimizer = require("../../../lib/processors/cssOptimizer"); + await cssOptimizer({resources: [resource]}); + t.deepEqual(t.context.warnLogStub.callCount, 1, "One message has been logged"); + t.deepEqual(t.context.warnLogStub.getCall(0).args[0], "Warnings occurred: my warning", "Warning message should be the same"); +}); diff --git a/test/lib/tasks/buildThemes.integration.js b/test/lib/tasks/buildThemes.integration.js index 91df4647b..315437a1b 100644 --- a/test/lib/tasks/buildThemes.integration.js +++ b/test/lib/tasks/buildThemes.integration.js @@ -25,15 +25,9 @@ test("integration: simple", (t) => { padding: 1px 2px 3px 4px; }`; const cssExpected = -`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px} -/* Inline theming parameters */ -#sap-ui-theme-super\\.duper\\.looper{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} -`; +`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px}#sap-ui-theme-super\\.duper\\.looper{background-image:url(data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D)}`; const cssRtlExpected = -`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px} -/* Inline theming parameters */ -#sap-ui-theme-super\\.duper\\.looper{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} -`; +`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px}#sap-ui-theme-super\\.duper\\.looper{background-image:url(data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D)}`; const parametersExpected = `{"deepSea":"#123456"}`; const lessPath = "/resources/super/duper/looper/themes/brightlight/library.source.less"; @@ -96,15 +90,9 @@ test("integration: imports", (t) => { const lessVariablesContent = "@deepSea: #123456;"; const cssExpected = -`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px} -/* Inline theming parameters */ -#sap-ui-theme-super\\.duper\\.looper{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} -`; +`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px}#sap-ui-theme-super\\.duper\\.looper{background-image:url(data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D)}`; const cssRtlExpected = -`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px} -/* Inline theming parameters */ -#sap-ui-theme-super\\.duper\\.looper{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} -`; +`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px}#sap-ui-theme-super\\.duper\\.looper{background-image:url(data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D)}`; const parametersExpected = `{"deepSea":"#123456"}`; const lessPath = "/resources/super/duper/looper/themes/brightlight/library.source.less"; diff --git a/test/lib/tasks/buildThemes.js b/test/lib/tasks/buildThemes.js index 166fc20e3..ae34b328e 100644 --- a/test/lib/tasks/buildThemes.js +++ b/test/lib/tasks/buildThemes.js @@ -8,9 +8,11 @@ let buildThemes = require("../../../lib/tasks/buildThemes"); test.beforeEach((t) => { // Stubbing processors/themeBuilder t.context.themeBuilderStub = sinon.stub(); + t.context.cssOptimizerStub = sinon.stub(); t.context.fsInterfaceStub = sinon.stub(require("@ui5/fs"), "fsInterface"); t.context.fsInterfaceStub.returns({}); mock("../../../lib/processors/themeBuilder", t.context.themeBuilderStub); + mock("../../../lib/processors/cssOptimizer", t.context.cssOptimizerStub); // Re-require tested module buildThemes = mock.reRequire("../../../lib/tasks/buildThemes"); @@ -19,10 +21,11 @@ test.beforeEach((t) => { test.afterEach.always((t) => { t.context.fsInterfaceStub.restore(); mock.stop("../../../lib/processors/themeBuilder"); + mock.stop("../../../lib/processors/cssOptimizer"); }); test.serial("buildThemes", async (t) => { - t.plan(6); + t.plan(8); const lessResource = {}; @@ -37,9 +40,9 @@ test.serial("buildThemes", async (t) => { write: sinon.stub() }; - const cssResource = {}; - const cssRtlResource = {}; - const jsonParametersResource = {}; + const cssResource = {getPath: () => "fu.css"}; + const cssRtlResource = {getPath: () => "fu-rtl.css"}; + const jsonParametersResource = {getPath: () => "fu.json"}; t.context.themeBuilderStub.returns([ cssResource, @@ -56,15 +59,22 @@ test.serial("buildThemes", async (t) => { }); t.deepEqual(t.context.themeBuilderStub.callCount, 1, - "Processor should be called once"); + "Theme Builder should be called once"); t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { resources: [lessResource], fs: {}, options: { - compress: true // default + compressJSON: true, // default + compress: false } - }, "Processor should be called with expected arguments"); + }, "Theme Builder should be called with expected arguments"); + + t.deepEqual(t.context.cssOptimizerStub.callCount, 1, "CSS Optimizer should be called once"); + t.deepEqual(t.context.cssOptimizerStub.getCall(0).args[0], { + resources: [cssResource, cssRtlResource], + fs: {} + }, "CSS Optimizer should be called with expected arguments"); t.deepEqual(workspace.write.callCount, 3, "workspace.write should be called 3 times"); @@ -75,7 +85,7 @@ test.serial("buildThemes", async (t) => { test.serial("buildThemes (compress = false)", async (t) => { - t.plan(6); + t.plan(7); const lessResource = {}; @@ -90,9 +100,9 @@ test.serial("buildThemes (compress = false)", async (t) => { write: sinon.stub() }; - const cssResource = {}; - const cssRtlResource = {}; - const jsonParametersResource = {}; + const cssResource = {getPath: () => "fu.css"}; + const cssRtlResource = {getPath: () => "fu-rtl.css"}; + const jsonParametersResource = {getPath: () => "fu.json"}; t.context.themeBuilderStub.returns([ cssResource, @@ -110,15 +120,18 @@ test.serial("buildThemes (compress = false)", async (t) => { }); t.deepEqual(t.context.themeBuilderStub.callCount, 1, - "Processor should be called once"); + "Theme Builder should be called once"); t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { resources: [lessResource], fs: {}, options: { + compressJSON: false, compress: false } - }, "Processor should be called with expected arguments"); + }, "Theme Builder should be called with expected arguments"); + + t.deepEqual(t.context.cssOptimizerStub.callCount, 0, "CSS Optimizer should not be called"); t.deepEqual(workspace.write.callCount, 3, "workspace.write should be called 3 times");