diff --git a/lib/Compiler.js b/lib/Compiler.js new file mode 100644 index 00000000..c68316b3 --- /dev/null +++ b/lib/Compiler.js @@ -0,0 +1,185 @@ +const path = require("path"); +const createParser = require("./less/parser"); +const clone = require("clone"); + +// Plugins +const ImportCollectorPlugin = require("./plugin/import-collector"); +const VariableCollectorPlugin = require("./plugin/variable-collector"); +const UrlCollector = require("./plugin/url-collector"); + +class Compiler { + constructor({options, fileUtils, customFs}) { + this.options = options; + this.fileUtils = fileUtils; + + // Stores mapping between "virtual" paths (used within LESS) and real filesystem paths. + // Only relevant when using the "rootPaths" option. + this.mFileMappings = {}; + + // Only use custom file handler when "rootPaths" or custom "fs" are used + if ((this.options.rootPaths && this.options.rootPaths.length > 0) || customFs) { + this.fnFileHandler = this.createFileHandler(); + } else { + this.fnFileHandler = undefined; + } + } + createFileHandler() { + return async (file, currentFileInfo, handleDataAndCallCallback, callback) => { + try { + let pathname; + + // support absolute paths such as "/resources/my/base.less" + if (path.posix.isAbsolute(file)) { + pathname = path.posix.normalize(file); + } else { + pathname = path.posix.join(currentFileInfo.currentDirectory, file); + } + + const result = await this.fileUtils.readFile(pathname, this.options.rootPaths); + if (!result) { + // eslint-disable-next-line no-console + console.log("File not found: " + pathname); + callback({type: "File", message: "Could not find file at path '" + pathname + "'"}); + } else { + // Save import mapping to calculate full import paths later on + this.mFileMappings[currentFileInfo.rootFilename] = + this.mFileMappings[currentFileInfo.rootFilename] || {}; + this.mFileMappings[currentFileInfo.rootFilename][pathname] = result.path; + + handleDataAndCallCallback(pathname, result.content); + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + callback(err); + } + }; + } + async compile(config) { + const parserOptions = clone(this.options.parser); + let rootpath; + + if (config.path) { + // Keep rootpath for later usage in the ImportCollectorPlugin + rootpath = config.path; + parserOptions.filename = config.localPath; + } + + // inject the library name as prefix (e.g. "sap.m" as "_sap_m") + if (this.options.library && typeof this.options.library.name === "string") { + const libName = config.libName = this.options.library.name; + config.libPath = libName.replace(/\./g, "/"); + config.prefix = "_" + libName.replace(/\./g, "_") + "_"; + } else { + config.prefix = ""; // no library, no prefix + } + + // Keep filename for later usage (see ImportCollectorPlugin) as less modifies the parserOptions.filename + const filename = parserOptions.filename; + + const parser = createParser(parserOptions, this.fnFileHandler); + + function parseContent(content) { + return new Promise(function(resolve, reject) { + parser.parse(content, function(err, tree) { + if (err) { + reject(err); + } else { + resolve(tree); + } + }); + }); + } + + const tree = await parseContent(config.content); + const result = {tree}; + + // plugins to collect imported files and variable values + const oImportCollector = new ImportCollectorPlugin({ + importMappings: this.mFileMappings[filename] + }); + const oVariableCollector = new VariableCollectorPlugin(this.options.compiler); + const oUrlCollector = new UrlCollector(); + + // render to css + result.css = tree.toCSS(Object.assign({}, this.options.compiler, { + plugins: [oImportCollector, oVariableCollector, oUrlCollector] + })); + + // retrieve imported files + result.imports = oImportCollector.getImports(); + + // retrieve reduced set of variables + result.variables = oVariableCollector.getVariables(Object.keys(this.mFileMappings[filename] || {})); + + // retrieve all variables + result.allVariables = oVariableCollector.getAllVariables(); + + // also compile rtl-version if requested + let oRTL; + if (this.options.rtl) { + const RTLPlugin = require("./plugin/rtl"); + oRTL = new RTLPlugin(); + + const urls = oUrlCollector.getUrls(); + + const existingImgRtlUrls = (await Promise.all( + urls.map(async ({currentDirectory, relativeUrl}) => { + const relativeImgRtlUrl = RTLPlugin.getRtlImgUrl(relativeUrl); + if (relativeImgRtlUrl) { + const resolvedImgRtlUrl = path.posix.join(currentDirectory, relativeImgRtlUrl); + if (await this.fileUtils.findFile(resolvedImgRtlUrl, this.options.rootPaths)) { + return resolvedImgRtlUrl; + } + } + }) + )).filter(Boolean); + + oRTL.setExistingImgRtlPaths(existingImgRtlUrls); + } + + if (oRTL) { + result.cssRtl = tree.toCSS(Object.assign({}, this.options.compiler, { + plugins: [oRTL] + })); + } + + if (rootpath) { + result.imports.unshift(rootpath); + } + + // also compile css-variables version if requested + if (this.options.cssVariables) { + // parse the content again to have a clean tree + const cssVariablesSkeletonTree = await parseContent(config.content); + + // generate the skeleton-css and the less-variables + const CSSVariablesCollectorPlugin = require("./plugin/css-variables-collector"); + const oCSSVariablesCollector = new CSSVariablesCollectorPlugin(config); + const oVariableCollector = new VariableCollectorPlugin(this.options.compiler); + result.cssSkeleton = cssVariablesSkeletonTree.toCSS(Object.assign({}, this.options.compiler, { + plugins: [oCSSVariablesCollector, oVariableCollector] + })); + const varsOverride = oVariableCollector.getAllVariables(); + result.cssVariablesSource = oCSSVariablesCollector.toLessVariables(varsOverride); + + if (oRTL) { + const oCSSVariablesCollectorRTL = new CSSVariablesCollectorPlugin(config); + result.cssSkeletonRtl = cssVariablesSkeletonTree.toCSS(Object.assign({}, this.options.compiler, { + plugins: [oCSSVariablesCollectorRTL, oRTL] + })); + } + + // generate the css-variables content out of the less-variables + const cssVariablesTree = await parseContent(result.cssVariablesSource); + const CSSVariablesPointerPlugin = require("./plugin/css-variables-pointer"); + result.cssVariables = cssVariablesTree.toCSS(Object.assign({}, this.options.compiler, { + plugins: [new CSSVariablesPointerPlugin()] + })); + } + + return result; + } +} + +module.exports = Compiler; diff --git a/lib/index.js b/lib/index.js index f833f8c5..0d7783a6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,19 +1,17 @@ "use strict"; const path = require("path"); -const clone = require("clone"); const css = require("css"); -const createParser = require("./less/parser"); + const diff = require("./diff"); const scope = require("./scope"); const fileUtilsFactory = require("./fileUtils"); -// Plugins -const ImportCollectorPlugin = require("./plugin/import-collector"); -const VariableCollectorPlugin = require("./plugin/variable-collector"); -const UrlCollector = require("./plugin/url-collector"); +const Compiler = require("./Compiler"); + +const themingParametersDataUri = require("./themingParameters/dataUri"); // Workaround for a performance issue in the "css" parser module when used in combination // with the "colors" module that enhances the String prototype. @@ -104,7 +102,7 @@ Builder.prototype.cacheTheme = function(result) { }; /** - * Creates a themebuild + * Runs a theme build * @param {object} options * @param {object} options.compiler compiler object as passed to less * @param {boolean} options.cssVariables whether or not to enable css variables output @@ -115,10 +113,6 @@ Builder.prototype.cacheTheme = function(result) { Builder.prototype.build = function(options) { const that = this; - // Stores mapping between "virtual" paths (used within LESS) and real filesystem paths. - // Only relevant when using the "rootPaths" option. - const mFileMappings = {}; - // Assign default options options = Object.assign({ lessInput: null, @@ -143,216 +137,14 @@ Builder.prototype.build = function(options) { options.parser.relativeUrls = true; } - function fileHandler(file, currentFileInfo, handleDataAndCallCallback, callback) { - let pathname; - - // support absolute paths such as "/resources/my/base.less" - if (path.posix.isAbsolute(file)) { - pathname = path.posix.normalize(file); - } else { - pathname = path.posix.join(currentFileInfo.currentDirectory, file); - } - - that.fileUtils.readFile(pathname, options.rootPaths).then(function(result) { - if (!result) { - // eslint-disable-next-line no-console - console.log("File not found: " + pathname); - callback({type: "File", message: "Could not find file at path '" + pathname + "'"}); - } else { - try { - // Save import mapping to calculate full import paths later on - mFileMappings[currentFileInfo.rootFilename] = mFileMappings[currentFileInfo.rootFilename] || {}; - mFileMappings[currentFileInfo.rootFilename][pathname] = result.path; - - handleDataAndCallCallback(pathname, result.content); - } catch (e) { - // eslint-disable-next-line no-console - console.log(e); - callback(e); - } - } - }, function(err) { - // eslint-disable-next-line no-console - console.log(err); - callback(err); - }); - } - - function compile(config) { - const parserOptions = clone(options.parser); - let rootpath; - - if (config.path) { - // Keep rootpath for later usage in the ImportCollectorPlugin - rootpath = config.path; - parserOptions.filename = config.localPath; - } - - // inject the library name as prefix (e.g. "sap.m" as "_sap_m") - if (options.library && typeof options.library.name === "string") { - const libName = config.libName = options.library.name; - config.libPath = libName.replace(/\./g, "/"); - config.prefix = "_" + libName.replace(/\./g, "_") + "_"; - } else { - config.prefix = ""; // no library, no prefix - } - - // Keep filename for later usage (see ImportCollectorPlugin) as less modifies the parserOptions.filename - const filename = parserOptions.filename; - - // Only use custom file handler when "rootPaths" or custom "fs" are used - let fnFileHandler; - if ((options.rootPaths && options.rootPaths.length > 0) || that.customFs) { - fnFileHandler = fileHandler; - } - - const parser = createParser(parserOptions, fnFileHandler); - - return new Promise(function(resolve, reject) { - parser.parse(config.content, function(err, tree) { - if (err) { - reject(err); - } else { - resolve(tree); - } - }); - }).then(async function(tree) { - const result = {}; - - result.tree = tree; - - // plugins to collect imported files and variable values - const oImportCollector = new ImportCollectorPlugin({ - importMappings: mFileMappings[filename] - }); - const oVariableCollector = new VariableCollectorPlugin(options.compiler); - const oUrlCollector = new UrlCollector(); - - // render to css - result.css = tree.toCSS(Object.assign({}, options.compiler, { - plugins: [oImportCollector, oVariableCollector, oUrlCollector] - })); - - // retrieve imported files - result.imports = oImportCollector.getImports(); - - // retrieve reduced set of variables - result.variables = oVariableCollector.getVariables(Object.keys(mFileMappings[filename] || {})); - - // retrieve all variables - result.allVariables = oVariableCollector.getAllVariables(); - - // also compile rtl-version if requested - let oRTL; - if (options.rtl) { - const RTLPlugin = require("./plugin/rtl"); - oRTL = new RTLPlugin(); - - const urls = oUrlCollector.getUrls(); - - const existingImgRtlUrls = (await Promise.all( - urls.map(async ({currentDirectory, relativeUrl}) => { - const relativeImgRtlUrl = RTLPlugin.getRtlImgUrl(relativeUrl); - if (relativeImgRtlUrl) { - const resolvedImgRtlUrl = path.posix.join(currentDirectory, relativeImgRtlUrl); - if (await that.fileUtils.findFile(resolvedImgRtlUrl, options.rootPaths)) { - return resolvedImgRtlUrl; - } - } - }) - )).filter(Boolean); - - oRTL.setExistingImgRtlPaths(existingImgRtlUrls); - } - - if (oRTL) { - result.cssRtl = tree.toCSS(Object.assign({}, options.compiler, { - plugins: [oRTL] - })); - } - - if (rootpath) { - result.imports.unshift(rootpath); - } - - // also compile css-variables version if requested - if (options.cssVariables) { - return new Promise(function(resolve, reject) { - // parse the content again to have a clean tree - parser.parse(config.content, function(err, tree) { - if (err) { - reject(err); - } else { - resolve(tree); - } - }); - }).then(function(tree) { - // generate the skeleton-css and the less-variables - const CSSVariablesCollectorPlugin = require("./plugin/css-variables-collector"); - const oCSSVariablesCollector = new CSSVariablesCollectorPlugin(config); - const oVariableCollector = new VariableCollectorPlugin(options.compiler); - result.cssSkeleton = tree.toCSS(Object.assign({}, options.compiler, { - plugins: [oCSSVariablesCollector, oVariableCollector] - })); - const varsOverride = oVariableCollector.getAllVariables(); - result.cssVariablesSource = oCSSVariablesCollector.toLessVariables(varsOverride); - if (oRTL) { - const oCSSVariablesCollectorRTL = new CSSVariablesCollectorPlugin(config); - result.cssSkeletonRtl = tree.toCSS(Object.assign({}, options.compiler, { - plugins: [oCSSVariablesCollectorRTL, oRTL] - })); - } - return tree; - }).then(function(tree) { - // generate the css-variables content out of the less-variables - return new Promise(function(resolve, reject) { - parser.parse(result.cssVariablesSource, function(err, tree) { - if (err) { - reject(err); - } else { - const CSSVariablesPointerPlugin = require("./plugin/css-variables-pointer"); - result.cssVariables = tree.toCSS(Object.assign({}, options.compiler, { - plugins: [new CSSVariablesPointerPlugin()] - })); - resolve(result); - } - }); - }); - }); - } - - return result; - }); - } + const compiler = new Compiler({ + options, + fileUtils: this.fileUtils, + customFs: this.customFs + }); function addInlineParameters(result) { - return new Promise(function(resolve, reject) { - if (typeof options.library === "object" && typeof options.library.name === "string") { - const parameters = JSON.stringify(result.variables); - - // properly escape the parameters to be part of a data-uri - // + escaping single quote (') as it is used to surround the data-uri: url('...') - const escapedParameters = encodeURIComponent(parameters).replace(/'/g, function(char) { - return escape(char); - }); - - // embed parameter variables as plain-text string into css - const parameterStyleRule = "\n/* Inline theming parameters */\n#sap-ui-theme-" + - options.library.name.replace(/\./g, "\\.") + - "{background-image:url('data:text/plain;utf-8," + escapedParameters + "')}\n"; - - // embed parameter variables as plain-text string into css - result.css += parameterStyleRule; - if (options.rtl) { - result.cssRtl += parameterStyleRule; - } - if (options.cssVariables) { - // for the css variables build we just add it to the variables - result.cssVariables += parameterStyleRule; - } - } - resolve(result); - }); + return themingParametersDataUri.addInlineParameters({result, options}); } function getScopeVariables(options) { @@ -385,13 +177,13 @@ Builder.prototype.build = function(options) { if (!config) { throw new Error("Could not find embeddedCompareFile at path '" + scopeOptions.embeddedCompareFilePath + "'"); } - return compile(config); + return compiler.compile(config); }), that.fileUtils.readFile(scopeOptions.embeddedFilePath, options.rootPaths).then(function(config) { if (!config) { throw new Error("Could not find embeddedFile at path '" + scopeOptions.embeddedFilePath + "'"); } - return compile(config); + return compiler.compile(config); }) ]).then(function(results) { return { @@ -533,7 +325,9 @@ Builder.prototype.build = function(options) { } // No css diffing and scoping - return that.fileUtils.readFile(options.lessInputPath, options.rootPaths).then(compile); + return that.fileUtils.readFile(options.lessInputPath, options.rootPaths).then((config) => { + return compiler.compile(config); + }); }); } @@ -546,7 +340,7 @@ Builder.prototype.build = function(options) { } if (options.lessInput) { - return compile({ + return compiler.compile({ content: options.lessInput }).then(addInlineParameters).then(that.cacheTheme.bind(that)); } else { diff --git a/lib/plugin/css-variables-collector.js b/lib/plugin/css-variables-collector.js index 6b9fdc35..458dec71 100644 --- a/lib/plugin/css-variables-collector.js +++ b/lib/plugin/css-variables-collector.js @@ -44,34 +44,31 @@ CSSVariablesCollectorPlugin.prototype = { toLessVariables(varsOverride) { const vars = {}; - Object.keys(this.vars).forEach((key) => { - const override = this.vars[key].updateAfterEval && varsOverride[key] !== undefined; - /* - if (override) { - console.log(`Override variable "${key}" from "${this.vars[key].css}" to "${varsOverride[key]}"`); - } - */ - vars[key] = { - css: override ? varsOverride[key] : this.vars[key].css, - export: this.vars[key].export + Object.keys(this.vars).forEach((variableName) => { + const override = this.vars[variableName].updateAfterEval && varsOverride[variableName] !== undefined; + vars[variableName] = { + css: override ? varsOverride[variableName] : this.vars[variableName].css, + export: this.vars[variableName].export }; }); let lessVariables = ""; - Object.keys(vars).forEach((value, index) => { - lessVariables += "@" + value + ": " + vars[value].css + ";\n"; + Object.keys(vars).forEach((variableName) => { + const variableValue = vars[variableName].css; + lessVariables += `@${variableName}: ${variableValue};\n`; }); - Object.keys(this.calcVars).forEach((value, index) => { - lessVariables += "@" + value + ": " + this.calcVars[value].css + ";\n"; + Object.keys(this.calcVars).forEach((variableName) => { + const variableValue = this.calcVars[variableName].css; + lessVariables += `@${variableName}: ${variableValue};\n`; }); lessVariables += "\n:root {\n"; - Object.keys(vars).forEach((value, index) => { - if (vars[value].export) { - lessVariables += "--" + value + ": @" + value + ";\n"; + Object.keys(vars).forEach((variableName) => { + if (vars[variableName].export) { + lessVariables += `--${variableName}: @${variableName};\n`; } }); - Object.keys(this.calcVars).forEach((value, index) => { - if (this.calcVars[value].export) { - lessVariables += "--" + value + ": @" + value + ";\n"; + Object.keys(this.calcVars).forEach((variableName) => { + if (this.calcVars[variableName].export) { + lessVariables += `--${variableName}: @${variableName};\n`; } }); lessVariables += "}\n"; diff --git a/lib/themingParameters/dataUri.js b/lib/themingParameters/dataUri.js new file mode 100644 index 00000000..fcbf1ecf --- /dev/null +++ b/lib/themingParameters/dataUri.js @@ -0,0 +1,33 @@ +module.exports = { + addInlineParameters: function({result, options}) { + // Inline parameters can only be added when the library name is known + if (typeof options.library !== "object" || typeof options.library.name !== "string") { + return result; + } + + const parameters = JSON.stringify(result.variables); + + // properly escape the parameters to be part of a data-uri + // + escaping single quote (') as it is used to surround the data-uri: url('...') + const escapedParameters = encodeURIComponent(parameters).replace(/'/g, function(char) { + return escape(char); + }); + + // embed parameter variables as plain-text string into css + const parameterStyleRule = "\n/* Inline theming parameters */\n#sap-ui-theme-" + + options.library.name.replace(/\./g, "\\.") + + "{background-image:url('data:text/plain;utf-8," + escapedParameters + "')}\n"; + + // embed parameter variables as plain-text string into css + result.css += parameterStyleRule; + if (options.rtl) { + result.cssRtl += parameterStyleRule; + } + if (options.cssVariables) { + // for the css variables build we just add it to the variables + result.cssVariables += parameterStyleRule; + } + + return result; + } +}; diff --git a/package.json b/package.json index 58c27666..ae19c199 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ }, "scripts": { "lint": "eslint ./", - "unit": "mocha test/*.js", - "unit-debug": "mocha --inspect --inspect-brk test/*.js", + "unit": "mocha test/*.js test/lib/**", + "unit-debug": "mocha --inspect --inspect-brk test/*.js test/lib/**", "coverage": "nyc npm run unit", "test": "npm run lint && npm run coverage && npm run depcheck", "preversion": "npm test", @@ -52,10 +52,10 @@ "lib/thirdparty/**" ], "check-coverage": true, - "statements": 90, + "statements": 95, "branches": 85, - "functions": 90, - "lines": 90, + "functions": 100, + "lines": 95, "watermarks": { "statements": [ 70, diff --git a/test/lib/Compiler.js b/test/lib/Compiler.js new file mode 100644 index 00000000..cac9886c --- /dev/null +++ b/test/lib/Compiler.js @@ -0,0 +1,97 @@ +/* eslint-env mocha */ + +const assert = require("assert"); +const sinon = require("sinon"); + +// tested module +const Compiler = require("../../lib/Compiler"); + +describe("Compiler#createFileHandler", function() { + before(() => { + sinon.stub(console, "log"); + }); + after(() => { + sinon.restore(); + }); + + it("should propagate errors via callback function (TypeError)", async function() { + const compiler = new Compiler({ + options: { + rootPaths: ["foo"] + }, + fileUtils: undefined // This will cause a TypeError when calling fileUtils.readFile + }); + + const file = "someFile"; + const currentFileInfo = { + currentDirectory: "someFolder" + }; + const handleDataAndCallCallback = sinon.stub(); + const callback = sinon.stub(); + + await compiler.fnFileHandler(file, currentFileInfo, handleDataAndCallCallback, callback); + + assert.equal(handleDataAndCallCallback.callCount, 0); + assert.equal(callback.callCount, 1); + assert.equal(callback.getCall(0).args.length, 1); + assert.equal(callback.getCall(0).args[0].name, "TypeError"); + assert.equal(callback.getCall(0).args[0].message, + `Cannot read property 'readFile' of undefined` + ); + }); + it("should propagate errors via callback function (File not found)", async function() { + const compiler = new Compiler({ + options: { + rootPaths: ["foo"] + }, + fileUtils: { + readFile: sinon.stub().resolves(null) + }, + }); + + const file = "someFile"; + const currentFileInfo = { + currentDirectory: "someFolder" + }; + const handleDataAndCallCallback = sinon.stub(); + const callback = sinon.stub(); + + await compiler.fnFileHandler(file, currentFileInfo, handleDataAndCallCallback, callback); + + assert.equal(handleDataAndCallCallback.callCount, 0); + assert.equal(callback.callCount, 1); + assert.equal(callback.getCall(0).args.length, 1); + assert.equal(callback.getCall(0).args[0].type, "File"); + assert.equal(callback.getCall(0).args[0].message, + `Could not find file at path 'someFolder/someFile'` + ); + }); + it("should propagate errors via callback function (error within handleDataAndCallCallback)", async function() { + const compiler = new Compiler({ + options: { + rootPaths: ["foo"] + }, + fileUtils: { + readFile: sinon.stub().resolves({ + path: "", content: "" + }) + }, + }); + + const file = "someFile"; + const currentFileInfo = { + currentDirectory: "someFolder" + }; + const handleDataAndCallCallback = sinon.stub().throws(new Error("Error from handleDataAndCallCallback")); + const callback = sinon.stub(); + + await compiler.fnFileHandler(file, currentFileInfo, handleDataAndCallCallback, callback); + + assert.equal(handleDataAndCallCallback.callCount, 1); + assert.equal(callback.callCount, 1); + assert.equal(callback.getCall(0).args.length, 1); + assert.equal(callback.getCall(0).args[0].message, + `Error from handleDataAndCallCallback` + ); + }); +}); diff --git a/test/lib/themingParameters/dataUri.js b/test/lib/themingParameters/dataUri.js new file mode 100644 index 00000000..a7d64ea8 --- /dev/null +++ b/test/lib/themingParameters/dataUri.js @@ -0,0 +1,158 @@ +/* eslint-env mocha */ + +const assert = require("assert"); + +// tested module +const themingParametersDataUri = require("../../../lib/themingParameters/dataUri"); + +describe("themingParameters/dataUri", function() { + it("should not add theming parameters when library name is missing", function() { + const result = {}; + const options = {}; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, {}, "result object should not be modified"); + }); + + it("should add theming parameters to css", function() { + const result = { + css: "/* css */", + variables: {foo: "bar"} + }; + const options = { + library: { + name: "sap.ui.test" + } + }; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, { + variables: {foo: "bar"}, + css: `/* css */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +` + }, "result.css should be enhanced"); + }); + + it("should encode variables to be included as data-uri", function() { + const result = { + css: "/* css */", + variables: { + foo: "50%", + bar: "'\"'" + } + }; + const options = { + library: { + name: "sap.ui.test" + } + }; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, { + variables: { + foo: "50%", + bar: "'\"'" + }, + css: `/* css */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('\ +data:text/plain;utf-8,%7B%22foo%22%3A%2250%25%22%2C%22bar%22%3A%22%27%5C%22%27%22%7D')} +` + }, "result.css should be enhanced"); + }); + + it("should add theming parameters to cssRtl when options.rtl=true", function() { + const result = { + css: "/* css */", + cssRtl: "/* cssRtl */", + variables: {foo: "bar"} + }; + const options = { + library: { + name: "sap.ui.test" + }, + rtl: true + }; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, { + variables: {foo: "bar"}, + css: `/* css */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +`, + cssRtl: `/* cssRtl */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +` + }, "result.css should be enhanced"); + }); + + it("should add theming parameters to cssVariables when options.cssVariables=true", function() { + const result = { + css: "/* css */", + cssVariables: "/* cssVariables */", + variables: {foo: "bar"} + }; + const options = { + library: { + name: "sap.ui.test" + }, + cssVariables: true + }; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, { + variables: {foo: "bar"}, + css: `/* css */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +`, + cssVariables: `/* cssVariables */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +` + }, "result.css should be enhanced"); + }); + + it("should add theming parameters to cssRtl / cssVariables when options.rtl/cssVariables=true", function() { + const result = { + css: "/* css */", + cssRtl: "/* cssRtl */", + cssVariables: "/* cssVariables */", + variables: {foo: "bar"} + }; + const options = { + library: { + name: "sap.ui.test" + }, + rtl: true, + cssVariables: true + }; + const returnedResult = themingParametersDataUri.addInlineParameters({result, options}); + + assert.equal(returnedResult, result, "result object reference should be returned"); + assert.deepEqual(result, { + variables: {foo: "bar"}, + css: `/* css */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +`, + cssRtl: `/* cssRtl */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +`, + cssVariables: `/* cssVariables */ +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.test{background-image:url('data:text/plain;utf-8,%7B%22foo%22%3A%22bar%22%7D')} +` + }, "result.css should be enhanced"); + }); +});