From 186ceed47d8e725c2b5d6ca62a95bfc780eb6fc1 Mon Sep 17 00:00:00 2001 From: Tommy Vinh Lam Date: Wed, 22 Mar 2017 10:49:50 +0100 Subject: [PATCH] BREAKING: Rewrite theme build and support scopes (Belize Themes) - Introduced "Builder" class to enable caching of build results - Added "lessInputPath" option to provide a path relative to the "rootPaths" - Added diffing and scoping to support Belize contrast areas - Analyze .theming files as theme scope indicators - Added new tests --- README.md | 52 +- lib/diff.js | 162 ++++ lib/fileUtils.js | 81 ++ lib/index.js | 442 +++++++++-- lib/less/fileLoader.js | 67 ++ lib/less/importsPush.js | 83 ++ lib/less/parser.js | 52 ++ lib/plugin/import-collector.js | 12 +- lib/scope.js | 154 ++++ package.json | 4 + .../my/ui/lib/themes/base/library-RTL.css | 9 + .../lib1/my/ui/lib/themes/base/library.css | 9 + .../lib1/my/ui/lib/themes/foo/library-RTL.css | 13 + .../lib1/my/ui/lib/themes/foo/library.css | 13 + .../lib2/my/ui/lib/themes/bar/library-RTL.css | 13 + .../lib2/my/ui/lib/themes/bar/library.css | 13 + .../lib1/comments/themes/foo/library-RTL.css | 13 + .../lib1/comments/themes/foo/library.css | 13 + .../lib2/comments/themes/bar/library-RTL.css | 13 + .../lib2/comments/themes/bar/library.css | 13 + .../css-scope-root/themes/foo/library-RTL.css | 19 + .../css-scope-root/themes/foo/library.css | 19 + .../css-scope-root/themes/bar/library-RTL.css | 19 + .../css-scope-root/themes/bar/library.css | 19 + .../lib1/default/themes/foo/library-RTL.css | 40 + .../lib1/default/themes/foo/library.css | 40 + .../lib2/default/themes/bar/library-RTL.css | 43 + .../lib2/default/themes/bar/library.css | 43 + .../dom/lib1/dom/themes/foo/library-RTL.css | 13 + .../dom/lib1/dom/themes/foo/library.css | 13 + .../dom/lib2/dom/themes/bar/library-RTL.css | 13 + .../dom/lib2/dom/themes/bar/library.css | 13 + .../themes/foo/library-RTL.css | 12 + .../themes/foo/library.css | 12 + .../themes/bar/library-RTL.css | 12 + .../themes/bar/library.css | 12 + .../html/lib1/html/themes/foo/library-RTL.css | 55 ++ .../html/lib1/html/themes/foo/library.css | 37 + .../html/lib2/html/themes/bar/library-RTL.css | 37 + .../html/lib2/html/themes/bar/library.css | 37 + .../media-queries/themes/foo/library-RTL.css | 45 ++ .../lib1/media-queries/themes/foo/library.css | 45 ++ .../media-queries/themes/bar/library-RTL.css | 45 ++ .../lib2/media-queries/themes/bar/library.css | 45 ++ .../lib1/mixins/themes/foo/library-RTL.css | 20 + .../mixins/lib1/mixins/themes/foo/library.css | 20 + .../lib2/mixins/themes/bar/library-RTL.css | 34 + .../mixins/lib2/mixins/themes/bar/library.css | 34 + .../themes/foo/library-RTL.css | 21 + .../multiple-imports/themes/foo/library.css | 21 + .../themes/bar/library-RTL.css | 32 + .../multiple-imports/themes/bar/library.css | 32 + .../my/ui/lib/themes/base/library.source.less | 9 + .../my/ui/lib/themes/foo/library.source.less | 3 + .../lib1/sap/ui/core/themes/foo/.theming | 14 + .../my/ui/lib/themes/bar/library.source.less | 3 + .../lib2/sap/ui/core/themes/bar/.theming | 14 + .../comments/themes/foo/library.source.less | 10 + .../comments/themes/bar/library.source.less | 11 + .../themes/foo/library.source.less | 7 + .../themes/bar/library.source.less | 11 + .../default/themes/foo/library.source.less | 24 + .../default/themes/bar/library.source.less | 28 + .../lib1/dom/themes/foo/library.source.less | 7 + .../lib2/dom/themes/bar/library.source.less | 7 + .../themes/foo/library.source.less | 9 + .../themes/bar/library.source.less | 9 + .../lib1/html/themes/foo/library.source.less | 16 + .../lib2/html/themes/bar/library.source.less | 16 + .../themes/foo/library.source.less | 40 + .../themes/bar/library.source.less | 40 + .../mixins/themes/foo/library.source.less | 10 + .../mixins/themes/bar/library.source.less | 25 + .../themes/foo/library.source.less | 15 + .../themes/bar/library.source.less | 27 + test/test.js | 744 +++++++++++++++--- 76 files changed, 3036 insertions(+), 196 deletions(-) create mode 100644 lib/diff.js create mode 100644 lib/fileUtils.js create mode 100644 lib/less/fileLoader.js create mode 100644 lib/less/importsPush.js create mode 100644 lib/less/parser.js create mode 100644 lib/scope.js create mode 100644 test/expected/libraries/lib1/my/ui/lib/themes/base/library-RTL.css create mode 100644 test/expected/libraries/lib1/my/ui/lib/themes/base/library.css create mode 100644 test/expected/libraries/lib1/my/ui/lib/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/lib1/my/ui/lib/themes/foo/library.css create mode 100644 test/expected/libraries/lib2/my/ui/lib/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/lib2/my/ui/lib/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/default/lib1/default/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/default/lib1/default/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/default/lib2/default/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/default/lib2/default/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/html/lib1/html/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/html/lib2/html/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/html/lib2/html/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library.css create mode 100644 test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library-RTL.css create mode 100644 test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.css create mode 100644 test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library-RTL.css create mode 100644 test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.css create mode 100644 test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less create mode 100644 test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/lib1/sap/ui/core/themes/foo/.theming create mode 100644 test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/lib2/sap/ui/core/themes/bar/.theming create mode 100644 test/fixtures/libraries/scopes/comments/lib1/comments/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/comments/lib2/comments/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/default/lib1/default/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/default/lib2/default/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/dom/lib1/dom/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/dom/lib2/dom/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/html/lib1/html/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/html/lib2/html/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/mixins/lib1/mixins/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/mixins/lib2/mixins/themes/bar/library.source.less create mode 100644 test/fixtures/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.source.less create mode 100644 test/fixtures/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.source.less diff --git a/README.md b/README.md index 99d17774..a3add475 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,14 @@ npm install less-openui5 ```js var lessOpenUI5 = require('less-openui5'); -lessOpenUI5.build({ +// Create a builder instance +var builder = new lessOpenUI5.Builder(); + +// Build a theme +builder.build({ lessInput: '@var: #ffffff; .class { color: @var; float: left }' -}, function(err, result) { +}) +.then(function(result) { console.log(result.css); // => regular css /* @@ -48,22 +53,41 @@ lessOpenUI5.build({ [] */ + // Clear builder cache when finished to cleanup memory + builder.clearCache(); + }); ``` ## API -### build(options, callback) +### new Builder() + +Creates a new `Builder` instance. + +It caches build results to only rebuild a theme when related files have been changed. +This is mainly relevant when building themes as part of a server middleware like [`connect-openui5`](https://github.com/SAP/connect-openui5). + +### .build(options) +Returns a Promise resolving with a [`result`](#result) object. #### options ##### lessInput -*Required* +*Required (either `lessInput` or `lessInputPath`, not both)* Type: `string` Input less content. +##### lessInputPath + +*Required (either `lessInput` or `lessInputPath`, not both)* +Type: `string` + +Path to input less file. +When `rootPaths` is given this must be a relative path inside one of the provided `rootPaths`, otherwise just a regular filesystem path. + ##### rtl Type: `boolean` @@ -79,9 +103,6 @@ Root paths to use for import directives. This option differs from the less `compiler.paths` option. It is useful if less files are located in separate folders but referenced as they would all be in one. -If `rootPaths` are provided and a file can not be found, the `compiler.paths` option will be used instead. - -*Note:* `parser.filename` has to be set to the path of the `input` file in order to get this working. ###### Example @@ -128,35 +149,36 @@ Type `string` Dot-separated name of the corresponding library. It will be used to inline the `variables` JSON as data-uri which can be retrieved at runtime. -#### callback(error, result) - -*Required* -Type: `function` +#### result -##### result.css +##### css Type: `string` Regular css output. -##### result.cssRtl +##### cssRtl Type: `string` Mirrored css for right-to-left support (if rtl option was enabled). -##### result.variables +##### variables Type: `object` Key-value map of all global less variables (without @ prefix). -##### result.imports +##### imports Type: `array` Paths to files imported via import directives. +### .clearCache() +Clears all cached build results. +Use this method to prevent high memory consumption when building many themes within the same process. + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/lib/diff.js b/lib/diff.js new file mode 100644 index 00000000..4d60b21b --- /dev/null +++ b/lib/diff.js @@ -0,0 +1,162 @@ +// Copyright 2017 SAP SE. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http: //www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific +// language governing permissions and limitations under the License. + +'use strict'; + +// Regular expression to match type of property in order to check whether +// it possibly contains color values. +var rProperties = /(color|background|border|text|outline)(?!\-(width|radius|offset|style|align|overflow|transform))(\-(color|shadow|image))?/; + +function selectorEquals(s1, s2) { + + // Make sure there is the same number of select parts + if (s1.length !== s2.length) { + return false; + } + + // Check if all the parts are the same strings + for (var i = 0; i < s1.length; i++) { + if (s1[i] !== s2[i]) { + return false; + } + } + + return true; +} + +function Diffing(oBase, oCompare) { + this.oBase = oBase; + this.oCompare = oCompare; + + this.oDiff = { + type: "stylesheet", + stylesheet: { + rules: [] + }, + }; + + this.oStack = { + type: "stylesheet", + stylesheet: { + rules: [] + }, + }; +} + +Diffing.prototype.diffRules = function(oBaseRules, oCompareRules) { + var aDiffRules = []; + var iBaseNode, iCompareNode; + + for (iBaseNode = 0, iCompareNode = 0; iBaseNode < oBaseRules.length; iBaseNode++, iCompareNode++) { + var oBaseNode = oBaseRules[iBaseNode]; + var oCompareNode = oCompareRules[iCompareNode]; + var oDiffNode = null; + + // Add all different compare nodes to stack and check for next one + while (oBaseNode.type !== oCompareNode.type) { + this.oStack.stylesheet.rules.push(oCompareNode); + iCompareNode++; + oCompareNode = oCompareRules[iCompareNode]; + } + + if (oBaseNode.type === "comment") { + var sBaseComment = oBaseNode.comment; + var sCompareComment = oCompareNode.comment; + + if (sBaseComment !== sCompareComment) { + oDiffNode = oCompareNode; + } + } + + if (oBaseNode.type === "rule") { + + // Add all rules with different selector to stack and check for next one + while (!selectorEquals(oBaseNode.selectors, oCompareNode.selectors)) { + this.oStack.stylesheet.rules.push(oCompareNode); + iCompareNode++; + oCompareNode = oCompareRules[iCompareNode]; + } + + var aBaseDeclarations = oBaseNode.declarations; + var aCompareDeclarations = oCompareNode.declarations; + for (var j = 0; j < aBaseDeclarations.length; j++) { + var oBaseDeclaration = aBaseDeclarations[j]; + var oCompareDeclaration = aCompareDeclarations[j]; + + if (oBaseDeclaration.type === "declaration") { + + // TODO: Also check for different node and add to stack??? + if (oBaseDeclaration.type === oCompareDeclaration.type) { + + if (oBaseDeclaration.property === oCompareDeclaration.property) { + + // Always add color properties to diff to prevent unexpected CSS overrides + // due to selectors with more importance + if (oBaseDeclaration.value !== oCompareDeclaration.value + || oCompareDeclaration.property.match(rProperties)) { + + // Add compared rule to diff + if (!oDiffNode) { + oDiffNode = oCompareNode; + oDiffNode.declarations = []; + } + oDiffNode.declarations.push(oCompareDeclaration); + } + + } + } + } + } + + } else if (oBaseNode.type === "media") { + + var aMediaDiffRules = this.diffRules(oBaseNode.rules, oCompareNode.rules); + + if (aMediaDiffRules.length > 0) { + oDiffNode = oCompareNode; + oDiffNode.rules = aMediaDiffRules; + } + + } + + if (oDiffNode) { + aDiffRules.push(oDiffNode); + } + + } + + // Add all leftover compare nodes to stack + for (; iCompareNode < oCompareRules.length; iCompareNode++) { + this.oStack.stylesheet.rules.push(oCompareRules[iCompareNode]); + } + + return aDiffRules; +}; + +Diffing.prototype.run = function() { + var oBaseRules = this.oBase.stylesheet.rules; + var oCompareRules = this.oCompare.stylesheet.rules; + + this.oDiff.stylesheet.rules = this.diffRules(oBaseRules, oCompareRules); + + return { + diff: this.oDiff, + stack: this.oStack + }; +}; + + +module.exports = function diff(oBase, oCompare) { + return new Diffing(oBase, oCompare).run(); +}; diff --git a/lib/fileUtils.js b/lib/fileUtils.js new file mode 100644 index 00000000..350c5615 --- /dev/null +++ b/lib/fileUtils.js @@ -0,0 +1,81 @@ +// Copyright 2017 SAP SE. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http: //www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific +// language governing permissions and limitations under the License. + +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +function statFile(filePath) { + return new Promise(function(resolve, reject) { + fs.stat(filePath, function(err, stat) { + // No rejection here as it is ok if the file was not found + resolve(stat ? { path: filePath, stat: stat } : null); + }); + }); +} + +function statFiles(files) { + return Promise.all(files.map(statFile)); +} + +function findFile(filePath, rootPaths) { + if (rootPaths && rootPaths.length > 0) { + return statFiles( + rootPaths.map(function(rootPath) { + return path.join(rootPath, filePath); + }) + ).then(function(results) { + for (var i = 0; i < results.length; i++) { + if (results[i] !== null) { + return results[i]; + } + } + + // File not found + return null; + }); + } else { + return statFile(filePath); + } +} + +function readFile(lessInputPath, rootPaths) { + return findFile(lessInputPath, rootPaths).then(function(fileInfo) { + if (!fileInfo) { + return null; + } + return new Promise(function(resolve, reject) { + fs.readFile(fileInfo.path, { + encoding: 'utf8' + }, function(fileErr, content) { + if (fileErr) { + reject(fileErr); + } else { + resolve({ + content: content, + path: fileInfo.path, + localPath: lessInputPath, + stats: fileInfo.stats + }); + } + }); + }); + }); +} + +module.exports.statFile = statFile; +module.exports.statFiles = statFiles; +module.exports.findFile = findFile; +module.exports.readFile = readFile; diff --git a/lib/index.js b/lib/index.js index 193cd128..10836ec7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -14,25 +14,106 @@ 'use strict'; -var less = require('less'); var path = require('path'); var assign = require('object-assign'); +var clone = require('clone'); +var css = require('css'); + +var createParser = require('./less/parser'); +var diff = require('./diff'); +var scope = require('./scope'); + +var fileUtils = require('./fileUtils'); // Plugins var RTLPlugin = require('./plugin/rtl'); var ImportCollectorPlugin = require('./plugin/import-collector'); var VariableCollectorPlugin = require('./plugin/variable-collector'); -/** - * Compiles the provided LESS string to CSS using the less compiler. - * - * @param {Object} options An object with build options - * @param {Function} callback Function to call when finished. - */ -module.exports.build = function(options, callback) { +// Workaround for a performance issue in the "css" parser module when used in combination +// with the "colors" module that enhances the String prototype. +// See: https://github.com/reworkcss/css/issues/88 +// +// This function removes all properties added by "colors" and returns a function +// that restores them afterwards. +// To be used before/after calling "css.parse" +function cleanupStringPrototype() { + var customProps = {}, s = ""; + + for (var key in s) { + if (!s.hasOwnProperty(key)) { + customProps[key] = String.prototype.__lookupGetter__(key); + Reflect.deleteProperty(String.prototype, key); + } + } + + return function restore() { + for (var key in customProps) { + if (customProps.hasOwnProperty(key)) { + String.prototype.__defineGetter__(key, customProps[key]); + } + } + } +} + +function dotThemingFileToPath(s) { + if (s.indexOf(".") > -1) { + s = "../" + s.replace(/\./g, "/"); + } + + return s; +} + +var Builder = function() { + this.themeCacheMapping = {}; +}; + +Builder.prototype.getThemeCache = function(rootPath) { + return this.themeCacheMapping[rootPath]; +}; + +Builder.prototype.setThemeCache = function(rootPath, cache) { + return this.themeCacheMapping[rootPath] = cache; +}; + +Builder.prototype.clearCache = function() { + this.themeCacheMapping = {}; +} + +Builder.prototype.cacheTheme = function(result) { + var that = this; + var rootpath; + + // Theme can only be cached if list of imports is available + if (result.imports.length === 0) { + return Promise.resolve(result); + } + + // Rootpath of theme is always the first entry + rootpath = result.imports[0]; + + return fileUtils.statFiles(result.imports).then(function(stats) { + that.setThemeCache(rootpath, { + result: result, + stats: stats + }); + + return result; + }); + +}; + +Builder.prototype.build = function(options) { + var that = this; + // Stores mapping between "virtual" paths (used within LESS) and real filesystem paths. + // Only relevant when using the "rootPaths" option. + var mFileMappings = {}; + + // Assign default options options = assign({ lessInput: null, + lessInputPath: null, rtl: true, rootPaths: [], parser: {}, @@ -45,56 +126,72 @@ module.exports.build = function(options, callback) { options.parser.relativeUrls = true; } - var result = { - variables: {}, - css: '', - tree: null - }; - - var rootPathsMapping = {}; - - // Keep reference of original less function - var fnFileLoader = less.Parser.fileLoader; - less.Parser.fileLoader = function fileLoaderHook(file, currentFileInfo, fn, env) { - var paths = rootPathsMapping[currentFileInfo.currentDirectory]; - if (typeof paths === 'undefined') { - var base = options.rootPaths.filter(function(p) { - p = path.normalize(p); - return path.normalize(currentFileInfo.currentDirectory).substr(0, p.length) === p; - })[0]; - if (base) { - var suffix = path.relative(base, currentFileInfo.currentDirectory); - paths = options.rootPaths.map(function(p) { - return path.join(p, suffix); - }); + function fileHandler(file, currentFileInfo, handleDataAndCallCallback, callback) { + + var pathname = path.normalize(currentFileInfo.currentDirectory + file); + + fileUtils.readFile(pathname, options.rootPaths).then(function(result) { + if (!result) { + console.log("File not found"); + callback({ type: 'File', message: "'" + file + "' wasn't found" }) } else { - paths = null; // set paths to null to identify if dir has already been checked + + 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) { + console.log(e); + callback(e); + } + + } + }, function(err) { + console.log(err); + callback(err); + }); + } + + function compile(config) { + return new Promise(function(resolve, reject) { + + var parserOptions = clone(options.parser); + var rootpath; + + if (config.path) { + // Keep rootpath for later usage in the ImportCollectorPlugin + rootpath = config.path; + parserOptions.filename = config.localPath; } - rootPathsMapping[currentFileInfo.currentDirectory] = paths; - } - if (paths) { - env.paths = paths; - } else if (options.parser.paths) { - env.paths = options.parser.paths; - } - return fnFileLoader.apply(this, arguments); - }; - var parser = new less.Parser(options.parser); + // Keep filename for later usage (see ImportCollectorPlugin) as less modifies the parserOptions.filename + var filename = parserOptions.filename; - parser.parse(options.lessInput, function(err, tree) { + // Only use custom file handler when "rootPaths" are defined + var fnFileHandler; + if (options.rootPaths && options.rootPaths.length > 0) { + fnFileHandler = fileHandler; + } + + var parser = createParser(parserOptions, fnFileHandler); - // restore fileLoader function - less.Parser.fileLoader = fnFileLoader; + parser.parse(config.content, function(err, tree) { - result.tree = tree; + if (err) { + reject(err); + return; + } - if (!err) { + var result = {}; - try { + result.tree = tree; // plugins to collect imported files and variable values - var oImportCollector = new ImportCollectorPlugin(); + var oImportCollector = new ImportCollectorPlugin({ + importMappings: mFileMappings[filename] + }); var oVariableCollector = new VariableCollectorPlugin(); // render to css @@ -104,6 +201,8 @@ module.exports.build = function(options, callback) { // retrieve imported files result.imports = oImportCollector.getImports(); + + // retrieve imported variables result.variables = oVariableCollector.getVariables(); // also compile rtl-version if requested @@ -113,35 +212,238 @@ module.exports.build = function(options, callback) { })); } - if (typeof options.library === "object" && typeof options.library.name === "string") { - var parameters = JSON.stringify(result.variables); + if (rootpath) { + result.imports.unshift(rootpath); + } - // escape all chars that could cause problems with css parsers using URI-Encoding (% + HEX-Code) - var escapedChars = "%{}()'\"\\"; - for (var i = 0; i < escapedChars.length; i++) { - var char = escapedChars.charAt(i); - var hex = char.charCodeAt(0).toString(16).toUpperCase(); - parameters = parameters.replace(new RegExp("\\" + char, "g"), "%" + hex); - } + resolve(result); - var parameterStyleRule = - "\n/* Inline theming parameters */\n#sap-ui-theme-" + - options.library.name.replace(/\./g, "\\.") + - " { background-image: url('data:text/plain;utf-8," + parameters + "'); }\n"; + }); + }); + } - // embed parameter variables as plain-text string into css - result.css += parameterStyleRule; - if (result.cssRtl) { - result.cssRtl += parameterStyleRule; + function addInlineParameters(result) { + return new Promise(function(resolve, reject) { + if (typeof options.library === "object" && typeof options.library.name === "string") { + var parameters = JSON.stringify(result.variables); + + // escape all chars that could cause problems with css parsers using URI-Encoding (% + HEX-Code) + var escapedChars = "%{}()'\"\\"; + for (var i = 0; i < escapedChars.length; i++) { + var char = escapedChars.charAt(i); + var hex = char.charCodeAt(0).toString(16).toUpperCase(); + parameters = parameters.replace(new RegExp("\\" + char, "g"), "%" + hex); + } + + var parameterStyleRule = + "\n/* Inline theming parameters */\n#sap-ui-theme-" + + options.library.name.replace(/\./g, "\\.") + + " { background-image: url('data:text/plain;utf-8," + parameters + "'); }\n"; + + // embed parameter variables as plain-text string into css + result.css += parameterStyleRule; + if (result.cssRtl) { + result.cssRtl += parameterStyleRule; + } + } + resolve(result); + }); + } + + function readDotTheming(dotThemingInputPath) { + return fileUtils.readFile(dotThemingInputPath, options.rootPaths).then(function(result) { + + var dotTheming; + var dotThemingFilePath; + + if (result) { + dotTheming = JSON.parse(result.content); + dotThemingFilePath = result.path; + } + + if (dotTheming && dotTheming.mCssScopes) { + + // Currently only the "library" target is supported + var cssScope = dotTheming.mCssScopes["library"]; + + if (cssScope) { + + var aScopes = cssScope.aScopes; + var oScopeConfig = aScopes[0]; // Currenlty only one scope is supported + + var sBaseFile = dotThemingFileToPath(cssScope.sBaseFile || cssScopeTarget); + var sEmbeddedCompareFile = dotThemingFileToPath(oScopeConfig.sEmbeddedCompareFile); + var sEmbeddedFile = dotThemingFileToPath(oScopeConfig.sEmbeddedFile); + + // Currently, only support the use case when "sBaseFile" equals "sEmbeddedCompareFile" + if (sBaseFile !== sEmbeddedCompareFile) { + throw new Error("Unsupported content in \"" + dotThemingInputPath + "\": " + + "\"sBaseFile\" (\"" + (cssScope.sBaseFile || cssScopeTarget) + "\") must be identical with " + + "\"sEmbeddedCompareFile\" (\"" + oScopeConfig.sEmbeddedCompareFile + "\")"); } + + var embeddedCompareFilePath = path.posix.join(themeDir, sEmbeddedCompareFile) + '.source.less'; + var embeddedFilePath = path.posix.join(themeDir, sEmbeddedFile) + '.source.less'; + + // 1. Compile base + embedded files (both default + RTL variants) + return Promise.all([ + fileUtils.readFile(embeddedCompareFilePath, options.rootPaths).then(compile), + fileUtils.readFile(embeddedFilePath, options.rootPaths).then(compile) + ]).then(function(results) { + return { + embeddedCompare: results[0], + embedded: results[1] + }; + }).then(function(results) { + + // baseFile or embeddedFile + var sLibraryBaseFile = dotTheming.mCssScopes.library.sBaseFile; + var sScopeName = oScopeConfig.sSelector; + + function applyScope(embeddedCompareCss, embeddedCss, bRtl) { + + var restoreStringPrototype = cleanupStringPrototype(); + + // Create diff object between embeddedCompare and embedded + var oBase = css.parse(embeddedCompareCss); + var oEmbedded = css.parse(embeddedCss); + + restoreStringPrototype(); + + var oDiff = diff(oBase, oEmbedded); + + // Create scope + var sScopeSelector = '.' + sScopeName; + var oScope = scope(oDiff.diff, sScopeSelector); + + var oCssScopeRoot; + + if (oDiff.stack) { + oCssScopeRoot = scope.scopeCssRoot(oDiff.stack.stylesheet.rules, sScopeName); + + if (oCssScopeRoot) { + oScope.stylesheet.rules.unshift(oCssScopeRoot); + } + } + + // Append scope + stack to embeddedCompareFile (actually target file, which is currently always the same i.e. "library.css") + // The stack gets appended to the embeddedFile only + var sAppend = css.stringify(oScope); + + if (sLibraryBaseFile !== "library" && oDiff.stack && oDiff.stack.stylesheet.rules.length > 0) { + sAppend += "\n" + css.stringify(oDiff.stack); + } + + return sAppend + "\n"; + + } + + results.embeddedCompare.css += applyScope(results.embeddedCompare.css, results.embedded.css); + if (options.rtl) { + results.embeddedCompare.cssRtl += applyScope(results.embeddedCompare.cssRtl, results.embedded.cssRtl, true); + } + + // Create diff between embeddedCompare and embeded variables + var oVariablesBase = results.embeddedCompare.variables; + var oVariablesEmbedded = results.embedded.variables; + var oVariablesDiff = {}; + + for (var sKey in oVariablesEmbedded) { + if (sKey in oVariablesBase) { + if (oVariablesBase[sKey] != oVariablesEmbedded[sKey]) { + oVariablesDiff[sKey] = oVariablesEmbedded[sKey]; + } + } + } + + // Merge variables + var oVariables = {}; + oVariables["default"] = oVariablesBase; + oVariables["scopes"] = {}; + oVariables["scopes"][sScopeName] = oVariablesDiff; + + results.embeddedCompare.variables = oVariables; + + var concatImports = function(aImportsBase, aImportsEmbedded) { + var aConcats = aImportsBase.concat(aImportsEmbedded); + + return aConcats.filter(function(sImport, sIndex) { + return aConcats.indexOf(sImport) == sIndex; + }) + }; + + if (sLibraryBaseFile !== "library") { + results.embeddedCompare.imports = concatImports(results.embedded.imports, results.embeddedCompare.imports); + } else { + results.embeddedCompare.imports = concatImports(results.embeddedCompare.imports, results.embedded.imports); + } + + // add .theming file to result.embeddedCompare.imports + results.embeddedCompare.imports.push(dotThemingFilePath); + + // 6. Resolve promise with complete result object (css, cssRtl, variables, imports) + return results.embeddedCompare; + + }); } + } + + // No css diffing and scoping + return fileUtils.readFile(options.lessInputPath, options.rootPaths).then(compile); + }); + } + + if (options.lessInput && options.lessInputPath) { + return Promise.reject(new Error("Please only provide either `lessInput` or `lessInputPath` but not both.")); + } + + if (!options.lessInput && !options.lessInputPath) { + return Promise.reject(new Error("Missing required option. Please provide either `lessInput` or `lessInputPath`.")); + } + + if (options.lessInput) { - } catch (ex) { - err = ex; + return compile({ + content: options.lessInput + }).then(addInlineParameters).then(that.cacheTheme.bind(that)); + + } else { + var themeDir = path.posix.dirname(options.lessInputPath); + + // Always use the sap/ui/core library to lookup .theming files + var coreThemeDir = themeDir.replace(/^.*\/themes\//, "/sap/ui/core/themes/"); + var dotThemingInputPath = path.posix.join(coreThemeDir, '.theming'); + + return fileUtils.findFile(options.lessInputPath, options.rootPaths).then(function(fileInfo) { + + if (!fileInfo) { + throw new Error("`lessInputPath` " + options.lessInputPath + " could not be found."); } - } + // check theme has been already cached + var themeCache; + if (fileInfo.path) { + themeCache = that.getThemeCache(fileInfo.path); + } - callback(err, result); - }); + // Compile theme if not cached or RTL is requested and missing in cache + if (!themeCache || (options.rtl && !themeCache.result.cssRtl)) { + return readDotTheming(dotThemingInputPath).then(addInlineParameters).then(that.cacheTheme.bind(that)); + } else { + return fileUtils.statFiles(themeCache.result.imports).then(function(newStats) { + for (var i = 0; i < newStats.length; i++) { + // check if .theming and less files have changed since last less compilation + if (!newStats[i] || newStats[i].stat.mtime.getTime() !== themeCache.stats[i].stat.mtime.getTime()) { + return readDotTheming(dotThemingInputPath).then(addInlineParameters).then(that.cacheTheme.bind(that)); + } + } + + // serve from cache + return themeCache.result; + }); + } + }); + } }; + +module.exports.Builder = Builder; diff --git a/lib/less/fileLoader.js b/lib/less/fileLoader.js new file mode 100644 index 00000000..bf64ae95 --- /dev/null +++ b/lib/less/fileLoader.js @@ -0,0 +1,67 @@ +/*! + * LESS - Leaner CSS v1.6.3 + * http://lesscss.org + * + * Copyright (c) 2009-2014, Alexis Sellier + * Licensed under the Apache v2 License. + * + * + * This file contains a modified version of the function "less.Parser.fileLoader" + * which has been taken from LESS v1.6.3 (https://github.com/less/less.js/blob/v1.6.3/lib/less/index.js#L127-L233). + * Modifications are marked with comments in the code. + * + */ + +'use strict'; + +module.exports = function createFileLoader(fileHandler) { + return function fileLoader(file, currentFileInfo, callback, env) { + + /* BEGIN MODIFICATION */ + + // Removed unused variables "dirname, data" + var pathname, + newFileInfo = { + relativeUrls: env.relativeUrls, + entryPath: currentFileInfo.entryPath, + rootpath: currentFileInfo.rootpath, + rootFilename: currentFileInfo.rootFilename + }; + + /* END MODIFICATION */ + + function handleDataAndCallCallback(data) { + var j = file.lastIndexOf('/'); + + // Pass on an updated rootpath if path of imported file is relative and file + // is in a (sub|sup) directory + // + // Examples: + // - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/', + // then rootpath should become 'less/module/nav/' + // - If path of imported file is '../mixins.less' and rootpath is 'less/', + // then rootpath should become 'less/../' + if (newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) { + var relativeSubDirectory = file.slice(0, j+1); + newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file + } + newFileInfo.currentDirectory = pathname.replace(/[^\\\/]*$/, ""); + newFileInfo.filename = pathname; + + callback(null, data, pathname, newFileInfo); + } + + /* BEGIN MODIFICATION */ + + // Call custom function to handle file loading + fileHandler(file, currentFileInfo, function(resolvedPathname, data) { + pathname = resolvedPathname; + handleDataAndCallCallback(data); + }, callback); + + // The remainder of this function has been completely removed + + /* END MODIFICATION */ + + }; +}; diff --git a/lib/less/importsPush.js b/lib/less/importsPush.js new file mode 100644 index 00000000..7a68cc5e --- /dev/null +++ b/lib/less/importsPush.js @@ -0,0 +1,83 @@ +/*! + * LESS - Leaner CSS v1.6.3 + * http://lesscss.org + * + * Copyright (c) 2009-2014, Alexis Sellier + * Licensed under the Apache v2 License. + * + * + * This file contains a modified version of the local function "imports.push" within the constructor of "less.Parser" + * which has been taken from LESS v1.6.3 (https://github.com/less/less.js/blob/v1.6.3/lib/less/parser.js#L70-L111). + * Modifications are marked with comments in the code. + * + */ + +/* eslint-disable consistent-this, new-cap */ +'use strict'; + +var less = require('less'); +var tree = less.tree; + +module.exports = function createImportsPushFunction(env, fileLoader, parserFactory) { + return function importsPush(path, currentFileInfo, importOptions, callback) { + var parserImports = this; + this.queue.push(path); + + var fileParsedFunc = function (e, root, fullPath) { + parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue + + /* BEGIN MODIFICATION */ + + // Replaced "rootFilename" with "env.filename" + var importedPreviously = fullPath in parserImports.files || fullPath === env.filename; + + /* END MODIFICATION */ + + parserImports.files[fullPath] = root; // Store the root + + if (e && !parserImports.error) { parserImports.error = e; } + + callback(e, root, importedPreviously, fullPath); + }; + + if (less.Parser.importer) { + less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); + } else { + + /* BEGIN MODIFICATION */ + + // Call custom fileLoader instead of "less.Parser.fileLoader" + fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) { + + /* END MODIFICATION */ + + if (e) {fileParsedFunc(e); return;} + + var newEnv = new tree.parseEnv(env); + + newEnv.currentFileInfo = newFileInfo; + newEnv.processImports = false; + newEnv.contents[fullPath] = contents; + + if (currentFileInfo.reference || importOptions.reference) { + newFileInfo.reference = true; + } + + if (importOptions.inline) { + fileParsedFunc(null, contents, fullPath); + } else { + + /* BEGIN MODIFICATION */ + + // Create custom parser when resolving imports + parserFactory(newEnv, fileLoader).parse(contents, function (e, root) { + + /* END MODIFICATION */ + + fileParsedFunc(e, root, fullPath); + }); + } + }, env); + } + }; +}; diff --git a/lib/less/parser.js b/lib/less/parser.js new file mode 100644 index 00000000..2a0b99b0 --- /dev/null +++ b/lib/less/parser.js @@ -0,0 +1,52 @@ +// Copyright 2017 SAP SE. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http: //www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific +// language governing permissions and limitations under the License. + +/* eslint-disable new-cap */ +'use strict'; + +var less = require('less'); +var createFileLoader = require('./fileLoader'); +var createImportsPushFunction = require('./importsPush'); + +function parserFactory(env, fileLoader) { + // Make sure that provided "env" is an instance + if (!(env instanceof less.tree.parseEnv)) { + env = new less.tree.parseEnv(env); + } + + var parser = new less.Parser(env); + + if (fileLoader) { + patchParserImportsPush(parser, env, fileLoader); + } + + return parser; +} + +function patchParserImportsPush(parser, env, fileLoader) { + // Hooks into the parser to be able to use custom file loader and use custom + // parser factory for imports + parser.imports.push = createImportsPushFunction(env, fileLoader, parserFactory); +} + +module.exports = function createParser(env, fileHandler) { + + // Create fileLoader function if a fileHandler is provided + var fileLoader; + if (fileHandler) { + fileLoader = createFileLoader(fileHandler); + } + + return parserFactory(env, fileLoader); +}; diff --git a/lib/plugin/import-collector.js b/lib/plugin/import-collector.js index f00ffb6b..c9781011 100644 --- a/lib/plugin/import-collector.js +++ b/lib/plugin/import-collector.js @@ -16,11 +16,14 @@ var less = require('less'); -var ImportCollector = module.exports = function() { +var ImportCollector = module.exports = function(options) { /*eslint-disable new-cap */ this.oVisitor = new less.tree.visitor(this); /*eslint-enable new-cap */ + this.aImports = []; + + this.mImportMappings = options && options.importMappings ? options.importMappings : {}; }; ImportCollector.prototype = { @@ -30,7 +33,12 @@ ImportCollector.prototype = { }, visitImport: function(importNode, visitArgs) { if (importNode.importedFilename) { - this.aImports.push(importNode.importedFilename); + var fullImportPath = this.mImportMappings[importNode.importedFilename]; + if (fullImportPath) { + this.aImports.push(fullImportPath); + } else { + this.aImports.push(importNode.importedFilename); + } } }, getImports: function() { diff --git a/lib/scope.js b/lib/scope.js new file mode 100644 index 00000000..9749191a --- /dev/null +++ b/lib/scope.js @@ -0,0 +1,154 @@ +// Copyright 2017 SAP SE. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http: //www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific +// language governing permissions and limitations under the License. + +'use strict'; + +/** + * DOM-Elements like html, body as CSS selector needs be handled differently in + * the scoping. Those selectors will be identified by matching the respective + * regEx. + */ +var rRegex = /(html[^\s]*|body[^\s]*)/; + +var rPoint = /(^\s?\.{1}\w*)/; + +function handleScoping(sSelector, sScopeName) { + + /** + * Match the selector to regex, by splitting into two array elements, + * where the first element is the resulting matching group. + */ + var aCaptureGroups = sSelector.split(rRegex); + + var aSelectors = []; + var sSelector1, sSelector2; + + // filter empty strings and undefined objects + var aMatch = aCaptureGroups.filter(function(n) { + return !!n; + }); + + if (aMatch.length > 1) { + // set scope name after matching group. + sSelector1 = aMatch[0] + " " + sScopeName + (aMatch[1] || ""); + + // match rPoint to check if following capture group + // starts with a css class or dom element + if (aMatch[1].match(rPoint)) { + sSelector2 = aMatch[0] + " " + sScopeName + + aMatch[1].replace(/\s/, ''); + } else { + // no match, selector is a dom element + sSelector2 = null; + } + } else { + // match if capture group starts with css rule + if (aMatch[0].match(rPoint)) { + // set scope before selector + sSelector1 = sScopeName + aMatch[0]; + sSelector2 = sScopeName + " " + aMatch[0]; + } else { + // if selector matches custom css rule + if (aMatch[0].match(rRegex)) { + sSelector1 = aMatch[0] + sScopeName; + sSelector2 = aMatch[0] + " " + sScopeName; + } else { + // DOM element, add space + sSelector1 = sScopeName + " " + aMatch[0]; + sSelector2 = null; + } + } + } + + aSelectors.push(sSelector1); + aSelectors.push(sSelector2); + + return aSelectors; +} + +function Scoping(oSheet, sScopeName) { + this.oSheet = oSheet; + this.sScopeName = sScopeName; +} + +Scoping.prototype.scopeRules = function(oRules) { + + for (var iNode = 0; iNode < oRules.length; iNode++) { + var oNode = oRules[iNode]; + + if (oNode.type === "rule") { + + var aNewSelectors = []; + + for (var i = 0; i < oNode.selectors.length; i++) { + var sSelector = oNode.selectors[i]; + var sSelector2; + + if (!(sSelector.match(/.sapContrast/))) { + + var aScopedSelectors = handleScoping(sSelector, this.sScopeName); + sSelector = (aScopedSelectors[0] ? aScopedSelectors[0] : sSelector); + sSelector2 = (aScopedSelectors[1] ? aScopedSelectors[1] : null); + + aNewSelectors.push(sSelector); + if (sSelector2) { + aNewSelectors.push(sSelector2); + } + } + + } + + if (aNewSelectors.length > 0) { + oNode.selectors = aNewSelectors; + } + + } else if (oNode.type === "media") { + this.scopeRules(oNode.rules); + } + } + +}; + +Scoping.prototype.run = function() { + this.scopeRules(this.oSheet.stylesheet.rules); + return this.oSheet; +}; + + +module.exports = function scope(oSheet, sScopeName) { + return new Scoping(oSheet, sScopeName).run(); +}; + +module.exports.scopeCssRoot = function scopeCssRoot(oRules, sScopeName) { + + for (var iNode = 0; iNode < oRules.length; iNode++) { + var oNode = oRules[iNode]; + + if (oNode.type === "rule") { + + for (var i = 0; i < oNode.selectors.length; i++) { + var sSelector = oNode.selectors[i]; + + if (sSelector.match(/#CSS_SCOPE_ROOT\b/)) { + oNode.selectors[i] = "." + sScopeName; + + oRules.splice(iNode, 1); + + return oNode; + } + + } + } + } +}; diff --git a/package.json b/package.json index b93af34b..f49c25e9 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "lint": "eslint lib test", "unit": "mocha test/*.js", + "unit-debug": "mocha --inspect --debug-brk test/*.js", "test": "npm run lint && npm run unit" }, "main": "lib/index.js", @@ -24,10 +25,13 @@ "node": ">= 0.12.0" }, "dependencies": { + "clone": "^2.1.0", + "css": "^2.2.1", "less": "1.6.3", "object-assign": "^4.0.1" }, "devDependencies": { + "clean-css": "^3.4.23", "eslint": "^1.10.3", "mocha": "^3.2.0" } diff --git a/test/expected/libraries/lib1/my/ui/lib/themes/base/library-RTL.css b/test/expected/libraries/lib1/my/ui/lib/themes/base/library-RTL.css new file mode 100644 index 00000000..5b61f9d9 --- /dev/null +++ b/test/expected/libraries/lib1/my/ui/lib/themes/base/library-RTL.css @@ -0,0 +1,9 @@ +.myUiLibRule1 { + color: #fefefe; +} +.myUiLibRule2 { + padding: 1px 4px 3px 2px; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22color1%22:%22#fefefe%22%7D'); } \ No newline at end of file diff --git a/test/expected/libraries/lib1/my/ui/lib/themes/base/library.css b/test/expected/libraries/lib1/my/ui/lib/themes/base/library.css new file mode 100644 index 00000000..7c2d07e3 --- /dev/null +++ b/test/expected/libraries/lib1/my/ui/lib/themes/base/library.css @@ -0,0 +1,9 @@ +.myUiLibRule1 { + color: #fefefe; +} +.myUiLibRule2 { + padding: 1px 2px 3px 4px; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22color1%22:%22#fefefe%22%7D'); } \ No newline at end of file diff --git a/test/expected/libraries/lib1/my/ui/lib/themes/foo/library-RTL.css b/test/expected/libraries/lib1/my/ui/lib/themes/foo/library-RTL.css new file mode 100644 index 00000000..bf4c799f --- /dev/null +++ b/test/expected/libraries/lib1/my/ui/lib/themes/foo/library-RTL.css @@ -0,0 +1,13 @@ +.myUiLibRule1 { + color: #ffffff; +} +.myUiLibRule2 { + padding: 1px 4px 3px 2px; +} +.fooContrast.myUiLibRule1, +.fooContrast .myUiLibRule1 { + color: #000000; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22default%22:%7B%22color1%22:%22#ffffff%22%7D,%22scopes%22:%7B%22fooContrast%22:%7B%22color1%22:%22#000000%22%7D%7D%7D'); } diff --git a/test/expected/libraries/lib1/my/ui/lib/themes/foo/library.css b/test/expected/libraries/lib1/my/ui/lib/themes/foo/library.css new file mode 100644 index 00000000..85b1e124 --- /dev/null +++ b/test/expected/libraries/lib1/my/ui/lib/themes/foo/library.css @@ -0,0 +1,13 @@ +.myUiLibRule1 { + color: #ffffff; +} +.myUiLibRule2 { + padding: 1px 2px 3px 4px; +} +.fooContrast.myUiLibRule1, +.fooContrast .myUiLibRule1 { + color: #000000; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22default%22:%7B%22color1%22:%22#ffffff%22%7D,%22scopes%22:%7B%22fooContrast%22:%7B%22color1%22:%22#000000%22%7D%7D%7D'); } diff --git a/test/expected/libraries/lib2/my/ui/lib/themes/bar/library-RTL.css b/test/expected/libraries/lib2/my/ui/lib/themes/bar/library-RTL.css new file mode 100644 index 00000000..393920b4 --- /dev/null +++ b/test/expected/libraries/lib2/my/ui/lib/themes/bar/library-RTL.css @@ -0,0 +1,13 @@ +.myUiLibRule1 { + color: #ffffff; +} +.myUiLibRule2 { + padding: 1px 4px 3px 2px; +} +.barContrast.myUiLibRule1, +.barContrast .myUiLibRule1 { + color: #000000; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22default%22:%7B%22color1%22:%22#ffffff%22%7D,%22scopes%22:%7B%22barContrast%22:%7B%22color1%22:%22#000000%22%7D%7D%7D'); } diff --git a/test/expected/libraries/lib2/my/ui/lib/themes/bar/library.css b/test/expected/libraries/lib2/my/ui/lib/themes/bar/library.css new file mode 100644 index 00000000..9c4582e9 --- /dev/null +++ b/test/expected/libraries/lib2/my/ui/lib/themes/bar/library.css @@ -0,0 +1,13 @@ +.myUiLibRule1 { + color: #ffffff; +} +.myUiLibRule2 { + padding: 1px 2px 3px 4px; +} +.barContrast.myUiLibRule1, +.barContrast .myUiLibRule1 { + color: #000000; +} + +/* Inline theming parameters */ +#sap-ui-theme-my\.ui\.lib { background-image: url('data:text/plain;utf-8,%7B%22default%22:%7B%22color1%22:%22#ffffff%22%7D,%22scopes%22:%7B%22barContrast%22:%7B%22color1%22:%22#000000%22%7D%7D%7D'); } \ No newline at end of file diff --git a/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library-RTL.css b/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library-RTL.css new file mode 100644 index 00000000..d9c7791d --- /dev/null +++ b/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library-RTL.css @@ -0,0 +1,13 @@ +/** + * This is a sample comment! + */ +.myRule1 { + /** + * This is a sample comment inside a rule! + */ + color: #000000; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library.css b/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library.css new file mode 100644 index 00000000..d9c7791d --- /dev/null +++ b/test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library.css @@ -0,0 +1,13 @@ +/** + * This is a sample comment! + */ +.myRule1 { + /** + * This is a sample comment inside a rule! + */ + color: #000000; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library-RTL.css b/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library-RTL.css new file mode 100644 index 00000000..d1e24842 --- /dev/null +++ b/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library-RTL.css @@ -0,0 +1,13 @@ +/** + * This is a sample comment! + */ +.myRule1 { + /** + * This is a sample comment inside a rule! + */ + color: #000000; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library.css b/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library.css new file mode 100644 index 00000000..d1e24842 --- /dev/null +++ b/test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library.css @@ -0,0 +1,13 @@ +/** + * This is a sample comment! + */ +.myRule1 { + /** + * This is a sample comment inside a rule! + */ + color: #000000; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library-RTL.css b/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library-RTL.css new file mode 100644 index 00000000..474ae0fe --- /dev/null +++ b/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library-RTL.css @@ -0,0 +1,19 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #c3c3c3; +} +.fooContrast { + color: #aaaaaa; +} + +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule2, +.fooContrast .myRule2 { + color: #444444; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.css b/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.css new file mode 100644 index 00000000..474ae0fe --- /dev/null +++ b/test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.css @@ -0,0 +1,19 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #c3c3c3; +} +.fooContrast { + color: #aaaaaa; +} + +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule2, +.fooContrast .myRule2 { + color: #444444; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library-RTL.css b/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library-RTL.css new file mode 100644 index 00000000..d98290a7 --- /dev/null +++ b/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library-RTL.css @@ -0,0 +1,19 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #c3c3c3; +} +.barContrast { + color: #aaaaaa; +} + +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule2, +.barContrast .myRule2 { + color: #444444; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.css b/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.css new file mode 100644 index 00000000..d98290a7 --- /dev/null +++ b/test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.css @@ -0,0 +1,19 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #c3c3c3; +} +.barContrast { + color: #aaaaaa; +} + +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule2, +.barContrast .myRule2 { + color: #444444; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/default/lib1/default/themes/foo/library-RTL.css b/test/expected/libraries/scopes/default/lib1/default/themes/foo/library-RTL.css new file mode 100644 index 00000000..2e41f801 --- /dev/null +++ b/test/expected/libraries/scopes/default/lib1/default/themes/foo/library-RTL.css @@ -0,0 +1,40 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #ffffff; + float: right; +} +.myRule3 { + display: -webkit-flex; + display: flex; +} +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} +.myRule5, +.myRule6, +.myRule7 { + color: #000000; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule2, +.fooContrast .myRule2 { + color: #ffffff; +} + +.fooContrast.myRule5, +.fooContrast .myRule5, +.fooContrast.myRule6, +.fooContrast .myRule6, +.fooContrast.myRule7, +.fooContrast .myRule7 { + color: #ffffff; +} diff --git a/test/expected/libraries/scopes/default/lib1/default/themes/foo/library.css b/test/expected/libraries/scopes/default/lib1/default/themes/foo/library.css new file mode 100644 index 00000000..539e7d11 --- /dev/null +++ b/test/expected/libraries/scopes/default/lib1/default/themes/foo/library.css @@ -0,0 +1,40 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #ffffff; + float: left; +} +.myRule3 { + display: -webkit-flex; + display: flex; +} +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} +.myRule5, +.myRule6, +.myRule7 { + color: #000000; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule2, +.fooContrast .myRule2 { + color: #ffffff; +} + +.fooContrast.myRule5, +.fooContrast .myRule5, +.fooContrast.myRule6, +.fooContrast .myRule6, +.fooContrast.myRule7, +.fooContrast .myRule7 { + color: #ffffff; +} diff --git a/test/expected/libraries/scopes/default/lib2/default/themes/bar/library-RTL.css b/test/expected/libraries/scopes/default/lib2/default/themes/bar/library-RTL.css new file mode 100644 index 00000000..3a22e7b0 --- /dev/null +++ b/test/expected/libraries/scopes/default/lib2/default/themes/bar/library-RTL.css @@ -0,0 +1,43 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #ffffff; + float: right; +} +.myRule3 { + display: -webkit-flex; + display: flex; +} +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} +.myRule5, +.myRule6, +.myRule7 { + color: #000000; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule2, +.barContrast .myRule2 { + color: #ffffff; +} + +.barContrast.myRule5, +.barContrast .myRule5, +.barContrast.myRule6, +.barContrast .myRule6, +.barContrast.myRule7, +.barContrast .myRule7 { + color: #ffffff; +} +.myRule8 { + display: block; +} diff --git a/test/expected/libraries/scopes/default/lib2/default/themes/bar/library.css b/test/expected/libraries/scopes/default/lib2/default/themes/bar/library.css new file mode 100644 index 00000000..3b90c70c --- /dev/null +++ b/test/expected/libraries/scopes/default/lib2/default/themes/bar/library.css @@ -0,0 +1,43 @@ +.myRule1 { + color: #000000; +} +.myRule2 { + color: #ffffff; + float: left; +} +.myRule3 { + display: -webkit-flex; + display: flex; +} +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} +.myRule5, +.myRule6, +.myRule7 { + color: #000000; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule2, +.barContrast .myRule2 { + color: #ffffff; +} + +.barContrast.myRule5, +.barContrast .myRule5, +.barContrast.myRule6, +.barContrast .myRule6, +.barContrast.myRule7, +.barContrast .myRule7 { + color: #ffffff; +} +.myRule8 { + display: block; +} diff --git a/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library-RTL.css b/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library-RTL.css new file mode 100644 index 00000000..5d5b2820 --- /dev/null +++ b/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library-RTL.css @@ -0,0 +1,13 @@ +div.myRule1 { + color: #000000; +} +div.myRule1 > .myRule2 { + color: #000000; +} +.fooContrast div.myRule1 { + color: #ffffff; +} + +.fooContrast div.myRule1 > .myRule2 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library.css b/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library.css new file mode 100644 index 00000000..2ac2f047 --- /dev/null +++ b/test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library.css @@ -0,0 +1,13 @@ +div.myRule1 { + color: #000000; +} +div.myRule1 > .myRule2 { + color: #000000; +} +.fooContrast div.myRule1 { + color: #ffffff; +} + +.fooContrast div.myRule1 > .myRule2 { + color: #ffffff; +} diff --git a/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library-RTL.css b/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library-RTL.css new file mode 100644 index 00000000..85e5cafe --- /dev/null +++ b/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library-RTL.css @@ -0,0 +1,13 @@ +div.myRule1 { + color: #000000; +} +div.myRule1 > .myRule2 { + color: #000000; +} +.barContrast div.myRule1 { + color: #ffffff; +} + +.barContrast div.myRule1 > .myRule2 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library.css b/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library.css new file mode 100644 index 00000000..de9b28c9 --- /dev/null +++ b/test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library.css @@ -0,0 +1,13 @@ +div.myRule1 { + color: #000000; +} +div.myRule1 > .myRule2 { + color: #000000; +} +.barContrast div.myRule1 { + color: #ffffff; +} + +.barContrast div.myRule1 > .myRule2 { + color: #ffffff; +} diff --git a/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library-RTL.css b/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library-RTL.css new file mode 100644 index 00000000..e4167eab --- /dev/null +++ b/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library-RTL.css @@ -0,0 +1,12 @@ +.myRule1 { + color: #000000; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.css b/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.css new file mode 100644 index 00000000..e4167eab --- /dev/null +++ b/test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.css @@ -0,0 +1,12 @@ +.myRule1 { + color: #000000; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library-RTL.css b/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library-RTL.css new file mode 100644 index 00000000..cd882f05 --- /dev/null +++ b/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library-RTL.css @@ -0,0 +1,12 @@ +.myRule1 { + color: #000000; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.css b/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.css new file mode 100644 index 00000000..cd882f05 --- /dev/null +++ b/test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.css @@ -0,0 +1,12 @@ +.myRule1 { + color: #000000; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/html/lib1/html/themes/foo/library-RTL.css b/test/expected/libraries/scopes/html/lib1/html/themes/foo/library-RTL.css new file mode 100644 index 00000000..594f4220 --- /dev/null +++ b/test/expected/libraries/scopes/html/lib1/html/themes/foo/library-RTL.css @@ -0,0 +1,55 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast.sapUiGrid th > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast.sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast.sapUiGrid th > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast.sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .fooContrast th > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .fooContrast td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .fooContrast th > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .fooContrast td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .fooContrast .testClass, +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .fooContrast.testClass { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css b/test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css new file mode 100644 index 00000000..e40124b4 --- /dev/null +++ b/test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css @@ -0,0 +1,37 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .fooContrast.sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .fooContrast.sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .fooContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .fooContrast td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .fooContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .fooContrast td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .fooContrast .testClass, +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .fooContrast.testClass { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/html/lib2/html/themes/bar/library-RTL.css b/test/expected/libraries/scopes/html/lib2/html/themes/bar/library-RTL.css new file mode 100644 index 00000000..137cf7d4 --- /dev/null +++ b/test/expected/libraries/scopes/html/lib2/html/themes/bar/library-RTL.css @@ -0,0 +1,37 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast.sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast.sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .barContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .barContrast td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .barContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .barContrast td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .barContrast .testClass, +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .barContrast.testClass { + color: #ffffff; +} diff --git a/test/expected/libraries/scopes/html/lib2/html/themes/bar/library.css b/test/expected/libraries/scopes/html/lib2/html/themes/bar/library.css new file mode 100644 index 00000000..38ccecda --- /dev/null +++ b/test/expected/libraries/scopes/html/lib2/html/themes/bar/library.css @@ -0,0 +1,37 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .barContrast.sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast.sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .barContrast.sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .barContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid .barContrast td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .barContrast th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid .barContrast td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .barContrast .testClass, +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .barContrast.testClass { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library-RTL.css b/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library-RTL.css new file mode 100644 index 00000000..f36843a4 --- /dev/null +++ b/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library-RTL.css @@ -0,0 +1,45 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #000000; + } + .myRule2 { + color: #ffffff; + } + .myRule3 { + display: -webkit-flex; + display: flex; + } + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + .sapMALI { + height: 3rem; + } +} +@media (min-width: 600px) and (max-width: 1023px) { + .fooContrast.myRule1, + .fooContrast .myRule1 { + color: #ffffff; + } + + .fooContrast.myRule2, + .fooContrast .myRule2 { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.css b/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.css new file mode 100644 index 00000000..f36843a4 --- /dev/null +++ b/test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.css @@ -0,0 +1,45 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #000000; + } + .myRule2 { + color: #ffffff; + } + .myRule3 { + display: -webkit-flex; + display: flex; + } + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + .sapMALI { + height: 3rem; + } +} +@media (min-width: 600px) and (max-width: 1023px) { + .fooContrast.myRule1, + .fooContrast .myRule1 { + color: #ffffff; + } + + .fooContrast.myRule2, + .fooContrast .myRule2 { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library-RTL.css b/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library-RTL.css new file mode 100644 index 00000000..75bcbb13 --- /dev/null +++ b/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library-RTL.css @@ -0,0 +1,45 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #000000; + } + .myRule2 { + color: #ffffff; + } + .myRule3 { + display: -webkit-flex; + display: flex; + } + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + .sapMALI { + height: 3rem; + } +} +@media (min-width: 600px) and (max-width: 1023px) { + .barContrast.myRule1, + .barContrast .myRule1 { + color: #ffffff; + } + + .barContrast.myRule2, + .barContrast .myRule2 { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.css b/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.css new file mode 100644 index 00000000..75bcbb13 --- /dev/null +++ b/test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.css @@ -0,0 +1,45 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #000000; + } + .myRule2 { + color: #ffffff; + } + .myRule3 { + display: -webkit-flex; + display: flex; + } + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + .sapMALI { + height: 3rem; + } +} +@media (min-width: 600px) and (max-width: 1023px) { + .barContrast.myRule1, + .barContrast .myRule1 { + color: #ffffff; + } + + .barContrast.myRule2, + .barContrast .myRule2 { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library-RTL.css b/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library-RTL.css new file mode 100644 index 00000000..a2b0d207 --- /dev/null +++ b/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library-RTL.css @@ -0,0 +1,20 @@ +@media screen and (min-width: 600px) { + body { + background: #000000; + font-size: 21px; + } + html.myRule1 td { + color: #000000; + } +} +@media screen and (min-width: 600px) { + body.fooContrast, + body .fooContrast { + background: #ffffff; + font-size: 20px; + } + + html.myRule1 .fooContrast td { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library.css b/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library.css new file mode 100644 index 00000000..a2b0d207 --- /dev/null +++ b/test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library.css @@ -0,0 +1,20 @@ +@media screen and (min-width: 600px) { + body { + background: #000000; + font-size: 21px; + } + html.myRule1 td { + color: #000000; + } +} +@media screen and (min-width: 600px) { + body.fooContrast, + body .fooContrast { + background: #ffffff; + font-size: 20px; + } + + html.myRule1 .fooContrast td { + color: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library-RTL.css b/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library-RTL.css new file mode 100644 index 00000000..c286aee8 --- /dev/null +++ b/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library-RTL.css @@ -0,0 +1,34 @@ +@media screen and (min-width: 600px) { + body { + background: #000000; + font-size: 21px; + } + html.myRule1 td { + color: #000000; + } +} +@media screen and (min-width: 600px) { + body.barContrast, + body .barContrast { + background: #ffffff; + font-size: 20px; + } + + html.myRule1 .barContrast td { + color: #ffffff; + } +} +.myRule2 { + background: #ffffff; +} + +@media screen and (min-width: 600px) { + body { + background: #ffffff; + font-size: 20px; + } + + .myRule2 { + background: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library.css b/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library.css new file mode 100644 index 00000000..c286aee8 --- /dev/null +++ b/test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library.css @@ -0,0 +1,34 @@ +@media screen and (min-width: 600px) { + body { + background: #000000; + font-size: 21px; + } + html.myRule1 td { + color: #000000; + } +} +@media screen and (min-width: 600px) { + body.barContrast, + body .barContrast { + background: #ffffff; + font-size: 20px; + } + + html.myRule1 .barContrast td { + color: #ffffff; + } +} +.myRule2 { + background: #ffffff; +} + +@media screen and (min-width: 600px) { + body { + background: #ffffff; + font-size: 20px; + } + + .myRule2 { + background: #ffffff; + } +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library-RTL.css b/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library-RTL.css new file mode 100644 index 00000000..02704986 --- /dev/null +++ b/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library-RTL.css @@ -0,0 +1,21 @@ +.myRule1 { + color: #ffffff; +} +.myRule2 { + display: block; +} +.myRule5 { + color: #ffffff; +} +.myRule6 { + display: block; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule5, +.fooContrast .myRule5 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.css b/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.css new file mode 100644 index 00000000..02704986 --- /dev/null +++ b/test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.css @@ -0,0 +1,21 @@ +.myRule1 { + color: #ffffff; +} +.myRule2 { + display: block; +} +.myRule5 { + color: #ffffff; +} +.myRule6 { + display: block; +} +.fooContrast.myRule1, +.fooContrast .myRule1 { + color: #ffffff; +} + +.fooContrast.myRule5, +.fooContrast .myRule5 { + color: #ffffff; +} \ No newline at end of file diff --git a/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library-RTL.css b/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library-RTL.css new file mode 100644 index 00000000..67d78d99 --- /dev/null +++ b/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library-RTL.css @@ -0,0 +1,32 @@ +.myRule1 { + color: #ffffff; +} +.myRule2 { + display: block; +} +.myRule5 { + color: #ffffff; +} +.myRule6 { + display: block; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule5, +.barContrast .myRule5 { + color: #ffffff; +} +.myRule3 { + color: red; +} + +.myRule4 { + background-color: transparent !important; +} + +.myRule7 { + color: red; +} diff --git a/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.css b/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.css new file mode 100644 index 00000000..27da524e --- /dev/null +++ b/test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.css @@ -0,0 +1,32 @@ +.myRule1 { + color: #ffffff; +} +.myRule2 { + display: block; +} +.myRule5 { + color: #ffffff; +} +.myRule6 { + display: block; +} +.barContrast.myRule1, +.barContrast .myRule1 { + color: #ffffff; +} + +.barContrast.myRule5, +.barContrast .myRule5 { + color: #ffffff; +} +.myRule3 { + color: red; +} + +.myRule4 { + background-color: transparent !important; +} + +.myRule7 { + color: red; +} \ No newline at end of file diff --git a/test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less b/test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less new file mode 100644 index 00000000..117df66c --- /dev/null +++ b/test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less @@ -0,0 +1,9 @@ +@color1: #fefefe; + +.myUiLibRule1 { + color: @color1; +} + +.myUiLibRule2 { + padding: 1px 2px 3px 4px; +} diff --git a/test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less b/test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less new file mode 100644 index 00000000..14e432dd --- /dev/null +++ b/test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less @@ -0,0 +1,3 @@ +@import "../base/library.source.less"; + +@color1: #ffffff; diff --git a/test/fixtures/libraries/lib1/sap/ui/core/themes/foo/.theming b/test/fixtures/libraries/lib1/sap/ui/core/themes/foo/.theming new file mode 100644 index 00000000..cb723427 --- /dev/null +++ b/test/fixtures/libraries/lib1/sap/ui/core/themes/foo/.theming @@ -0,0 +1,14 @@ +{ + "mCssScopes": { + "library": { + "sBaseFile": "library", + "aScopes": [ + { + "sSelector": "fooContrast", + "sEmbeddedFile": "bar.library", + "sEmbeddedCompareFile": "library" + } + ] + } + } +} diff --git a/test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less b/test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less new file mode 100644 index 00000000..d7e8a29f --- /dev/null +++ b/test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less @@ -0,0 +1,3 @@ +@import "../foo/library.source.less"; + +@color1: #000000; diff --git a/test/fixtures/libraries/lib2/sap/ui/core/themes/bar/.theming b/test/fixtures/libraries/lib2/sap/ui/core/themes/bar/.theming new file mode 100644 index 00000000..bf9a0d20 --- /dev/null +++ b/test/fixtures/libraries/lib2/sap/ui/core/themes/bar/.theming @@ -0,0 +1,14 @@ +{ + "mCssScopes": { + "library": { + "sBaseFile": "foo.library", + "aScopes": [ + { + "sSelector": "barContrast", + "sEmbeddedFile": "library", + "sEmbeddedCompareFile": "foo.library" + } + ] + } + } +} diff --git a/test/fixtures/libraries/scopes/comments/lib1/comments/themes/foo/library.source.less b/test/fixtures/libraries/scopes/comments/lib1/comments/themes/foo/library.source.less new file mode 100644 index 00000000..6ac9fa36 --- /dev/null +++ b/test/fixtures/libraries/scopes/comments/lib1/comments/themes/foo/library.source.less @@ -0,0 +1,10 @@ +/** + * This is a sample comment! + */ + +.myRule1 { + /** + * This is a sample comment inside a rule! + */ + color: #000000; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/comments/lib2/comments/themes/bar/library.source.less b/test/fixtures/libraries/scopes/comments/lib2/comments/themes/bar/library.source.less new file mode 100644 index 00000000..cdb0c713 --- /dev/null +++ b/test/fixtures/libraries/scopes/comments/lib2/comments/themes/bar/library.source.less @@ -0,0 +1,11 @@ +/** + * This is a sample comment! + */ + +.myRule1 { + /** + * This is a sample comment inside a rule + * with a different color value! + */ + color: #ffffff; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.source.less b/test/fixtures/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.source.less new file mode 100644 index 00000000..c0e7ac1a --- /dev/null +++ b/test/fixtures/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.source.less @@ -0,0 +1,7 @@ +.myRule1 { + color: #000000; +} + +.myRule2 { + color: #c3c3c3; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.source.less b/test/fixtures/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.source.less new file mode 100644 index 00000000..1dd36bee --- /dev/null +++ b/test/fixtures/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.source.less @@ -0,0 +1,11 @@ +.myRule1 { + color: #ffffff; +} + +#CSS_SCOPE_ROOT { + color: #aaaaaa; +} + +.myRule2 { + color: #444444; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/default/lib1/default/themes/foo/library.source.less b/test/fixtures/libraries/scopes/default/lib1/default/themes/foo/library.source.less new file mode 100644 index 00000000..75d08bde --- /dev/null +++ b/test/fixtures/libraries/scopes/default/lib1/default/themes/foo/library.source.less @@ -0,0 +1,24 @@ +.myRule1 { + color: #000000; +} + +.myRule2 { + color: #ffffff; + float: left; +} + +.myRule3 { + display: -webkit-flex; + display: flex; +} + +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} + +.myRule5, .myRule6, .myRule7 { + color: #000000; +} diff --git a/test/fixtures/libraries/scopes/default/lib2/default/themes/bar/library.source.less b/test/fixtures/libraries/scopes/default/lib2/default/themes/bar/library.source.less new file mode 100644 index 00000000..e003a5b8 --- /dev/null +++ b/test/fixtures/libraries/scopes/default/lib2/default/themes/bar/library.source.less @@ -0,0 +1,28 @@ +.myRule1 { + color: #ffffff; +} + +.myRule2 { + color: #ffffff; + float: left; +} + +.myRule3 { + display: -webkit-flex; + display: flex; +} + +.myRule4 { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; +} + +.myRule5, .myRule6, .myRule7 { + color: #ffffff; +} + +.myRule8 { + display: block; +} diff --git a/test/fixtures/libraries/scopes/dom/lib1/dom/themes/foo/library.source.less b/test/fixtures/libraries/scopes/dom/lib1/dom/themes/foo/library.source.less new file mode 100644 index 00000000..a548c49a --- /dev/null +++ b/test/fixtures/libraries/scopes/dom/lib1/dom/themes/foo/library.source.less @@ -0,0 +1,7 @@ +div.myRule1 { + color: #000000; +} + +div.myRule1 > .myRule2 { + color: #000000; +} diff --git a/test/fixtures/libraries/scopes/dom/lib2/dom/themes/bar/library.source.less b/test/fixtures/libraries/scopes/dom/lib2/dom/themes/bar/library.source.less new file mode 100644 index 00000000..15a3a0d1 --- /dev/null +++ b/test/fixtures/libraries/scopes/dom/lib2/dom/themes/bar/library.source.less @@ -0,0 +1,7 @@ +div.myRule1 { + color: #ffffff; +} + +div.myRule1 > .myRule2 { + color: #ffffff; +} diff --git a/test/fixtures/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.source.less b/test/fixtures/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.source.less new file mode 100644 index 00000000..f1c2401a --- /dev/null +++ b/test/fixtures/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.source.less @@ -0,0 +1,9 @@ +.myRule1 { + color: #000000; +} + +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.source.less b/test/fixtures/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.source.less new file mode 100644 index 00000000..c1798346 --- /dev/null +++ b/test/fixtures/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.source.less @@ -0,0 +1,9 @@ +.myRule1 { + color: #ffffff; +} + +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + width: 100px; + } +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/html/lib1/html/themes/foo/library.source.less b/test/fixtures/libraries/scopes/html/lib1/html/themes/foo/library.source.less new file mode 100644 index 00000000..62464d95 --- /dev/null +++ b/test/fixtures/libraries/scopes/html/lib1/html/themes/foo/library.source.less @@ -0,0 +1,16 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #000000; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #000000; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #000000; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/html/lib2/html/themes/bar/library.source.less b/test/fixtures/libraries/scopes/html/lib2/html/themes/bar/library.source.less new file mode 100644 index 00000000..6fb338d3 --- /dev/null +++ b/test/fixtures/libraries/scopes/html/lib2/html/themes/bar/library.source.less @@ -0,0 +1,16 @@ +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie] .sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed] .sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ie].sapUiGrid td > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid th > .sapUiFormTitle, +html[dir=rtl][data-sap-ui-browser^=ed].sapUiGrid td > .sapUiFormTitle { + color: #ffffff; +} + +html[dir=rtl][data-sap-ui-browser^=ie].sap-desktop-sapUiClass .testClass { + color: #ffffff; +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.source.less b/test/fixtures/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.source.less new file mode 100644 index 00000000..582b8aa2 --- /dev/null +++ b/test/fixtures/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.source.less @@ -0,0 +1,40 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} + +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #000000; + } + + .myRule2 { + color: #ffffff; + } + + .myRule3 { + display: -webkit-flex; + display: flex; + } + + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + + .sapMALI { + height: 3rem; + } +} diff --git a/test/fixtures/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.source.less b/test/fixtures/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.source.less new file mode 100644 index 00000000..8f65d46c --- /dev/null +++ b/test/fixtures/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.source.less @@ -0,0 +1,40 @@ +@font-face { + font-family: 'myfont'; + src: url('font.eot'); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} + +@media (min-width: 600px) and (max-width: 1023px) { + .myRule1 { + color: #ffffff; + } + + .myRule2 { + color: #ffffff; + } + + .myRule3 { + display: -webkit-flex; + display: flex; + } + + .sapMALI { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + + .sapMALI .sapMLIBUnread, + .sapMALI .sapMLIBSelectS, + .sapMALI .sapMLIBSelectM, + .sapMALI .sapMLIBSelectD { + display: none; + } + + .sapMALI { + height: 3rem; + } +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/mixins/lib1/mixins/themes/foo/library.source.less b/test/fixtures/libraries/scopes/mixins/lib1/mixins/themes/foo/library.source.less new file mode 100644 index 00000000..d6d2e826 --- /dev/null +++ b/test/fixtures/libraries/scopes/mixins/lib1/mixins/themes/foo/library.source.less @@ -0,0 +1,10 @@ +@media screen and (min-width: 600px) { + body { + background: #000000; + font-size: 21px; + } + + html.myRule1 td { + color: #000000; + } +} \ No newline at end of file diff --git a/test/fixtures/libraries/scopes/mixins/lib2/mixins/themes/bar/library.source.less b/test/fixtures/libraries/scopes/mixins/lib2/mixins/themes/bar/library.source.less new file mode 100644 index 00000000..57565ba6 --- /dev/null +++ b/test/fixtures/libraries/scopes/mixins/lib2/mixins/themes/bar/library.source.less @@ -0,0 +1,25 @@ +@media screen and (min-width: 600px) { + body { + background: #ffffff; + font-size: 20px; + } + + html.myRule1 td { + color: #ffffff; + } +} + +.myRule2 { + background: #ffffff; +} + +@media screen and (min-width: 600px) { + body { + background: #ffffff; + font-size: 20px; + } + + .myRule2 { + background: #ffffff; + } +} diff --git a/test/fixtures/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.source.less b/test/fixtures/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.source.less new file mode 100644 index 00000000..ffa60b8e --- /dev/null +++ b/test/fixtures/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.source.less @@ -0,0 +1,15 @@ +.myRule1 { + color: #ffffff; +} + +.myRule2 { + display: block; +} + +.myRule5 { + color: #ffffff; +} + +.myRule6 { + display: block; +} diff --git a/test/fixtures/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.source.less b/test/fixtures/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.source.less new file mode 100644 index 00000000..4ee57010 --- /dev/null +++ b/test/fixtures/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.source.less @@ -0,0 +1,27 @@ +.myRule1 { + color: #ffffff; +} + +.myRule2 { + display: block; +} + +.myRule3 { + color: red; +} + +.myRule4 { + background-color: transparent !important; +} + +.myRule5 { + color: #ffffff; +} + +.myRule6 { + display: block; +} + +.myRule7 { + color: red; +} diff --git a/test/test.js b/test/test.js index 7f6788a8..02ed9c57 100644 --- a/test/test.js +++ b/test/test.js @@ -19,6 +19,8 @@ var assert = require('assert'); var path = require('path'); var fs = require('fs'); +var clone = require('clone'); + var crlfPattern = /\r\n/g; var lastLfPattern = /\n$/; var noLastLfPattern = /([^\n])$/; @@ -34,58 +36,63 @@ function readFile(filename, lastLf) { } // tested module -var lessOpenUI5 = require('../lib'); +var Builder = require('../').Builder; describe('options', function() { - it('should return css, cssRtl, variables and imports with default options', function(done) { + it('should return css, cssRtl, variables and imports with default options (lessInput)', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/simple/test.less') - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/simple/test.css'), 'css should be correctly generated.'); assert.equal(result.cssRtl, readFile('test/expected/simple/test-RTL.css'), 'rtl css should be correctly generated.'); assert.deepEqual(result.variables, JSON.parse(readFile('test/expected/simple/test-variables.json')), 'variables should be correctly collected.'); assert.deepEqual(result.imports, [], 'import list should be empty.'); - done(); + }); + + }); + + it('should return css, cssRtl, variables and imports with default options (lessInputPath)', function() { + + return new Builder().build({ + lessInputPath: 'test/fixtures/simple/test.less' + }).then(function(result) { + + assert.equal(result.css, readFile('test/expected/simple/test.css'), 'css should be correctly generated.'); + assert.equal(result.cssRtl, readFile('test/expected/simple/test-RTL.css'), 'rtl css should be correctly generated.'); + assert.deepEqual(result.variables, JSON.parse(readFile('test/expected/simple/test-variables.json')), 'variables should be correctly collected.'); + assert.deepEqual(result.imports, ['test/fixtures/simple/test.less'], 'import list should be empty.'); }); }); - it('should not return cssRtl with option rtl set to false', function(done) { + it('should not return cssRtl with option rtl set to false', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/simple/test.less'), rtl: false - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/simple/test.css'), 'css should be correctly generated.'); assert.strictEqual(result.cssRtl, undefined, 'rtl css should not be generated.'); assert.deepEqual(result.variables, JSON.parse(readFile('test/expected/simple/test-variables.json')), 'variables should be correctly collected.'); - done(); - }); }); - it('should return minified css and cssRtl with lessOption compress set to true', function(done) { + it('should return minified css and cssRtl with lessOption compress set to true', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/simple/test.less'), compiler: { compress: true } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { // remove the last LF to be able to compare the content correctly assert.equal(result.css, readFile('test/expected/simple/test.min.css', false), 'css should be correctly generated.'); @@ -93,26 +100,22 @@ describe('options', function() { assert.deepEqual(result.variables, JSON.parse(readFile('test/expected/simple/test-variables.min.json')), 'variables should be correctly collected.'); assert.deepEqual(result.imports, [], 'import list should be empty.'); - done(); - }); }); - it('should resolve import directives with rootPaths option', function(done) { + it('should resolve import directives with rootPaths option', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/rootPaths/lib2/my/themes/bar/bar.less'), rootPaths: [ 'test/fixtures/rootPaths/lib1', 'test/fixtures/rootPaths/lib2' ], parser: { - filename: 'test/fixtures/rootPaths/lib2/my/themes/bar/bar.less' + filename: 'my/themes/bar/bar.less' } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, '', 'css should be empty.'); assert.deepEqual(result.variables, {}, 'variables should be empty.'); @@ -123,7 +126,32 @@ describe('options', function() { ) ], 'import list should not correctly filled.'); - done(); + }); + + }); + + it('should read input file with rootPaths option', function() { + + return new Builder().build({ + lessInputPath: 'my/themes/bar/bar.less', + rootPaths: [ + 'test/fixtures/rootPaths/lib1', + 'test/fixtures/rootPaths/lib2' + ] + }).then(function(result) { + + assert.equal(result.css, '', 'css should be empty.'); + assert.deepEqual(result.variables, {}, 'variables should be empty.'); + assert.deepEqual(result.imports, [ + path.join( + 'test', 'fixtures', 'rootPaths', + 'lib2', 'my', 'themes', 'bar', 'bar.less' + ), + path.join( + 'test', 'fixtures', 'rootPaths', + 'lib1', 'my', 'themes', 'foo', 'foo.less' + ) + ], 'import list should not correctly filled.'); }); @@ -131,102 +159,201 @@ describe('options', function() { }); -describe('error handling', function() { +describe('libraries', function() { - it('should have correct error in case of undefined variable usage', function(done) { + it('should create base theme', function() { + + return new Builder().build({ + lessInputPath: 'my/ui/lib/themes/base/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/lib1' + ], + library: { + name: "my.ui.lib" + } + }).then(function(result) { + + assert.equal(result.css, readFile('test/expected/libraries/lib1/my/ui/lib/themes/base/library.css'), 'css should be correctly generated.'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/lib1/my/ui/lib/themes/base/library-RTL.css'), 'rtl css should be correctly generated.'); + assert.deepEqual(result.variables, { color1: '#fefefe' }, 'variables should be correctly collected.'); + assert.deepEqual(result.imports, [ + "test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less" + ], 'import list should be correct.'); - lessOpenUI5.build({ - lessInput: readFile('test/fixtures/error/undefined-var.less') - }, function(err, result) { - assert.ok(err); - done(); }); }); -}); + it('should create foo theme with scope as defined in .theming file', function() { -function assertLessToRtlCssEqual(filename, done) { - var lessFilename = 'test/fixtures/rtl/' + filename + '.less'; - var cssFilename = 'test/expected/rtl/' + filename + '.css'; + return new Builder().build({ + lessInputPath: 'my/ui/lib/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ], + library: { + name: "my.ui.lib" + } + }).then(function(result) { + + var oVariablesExpected = { + "default" : { + "color1": "#ffffff", + + }, + "scopes": { + "fooContrast": { + "color1": "#000000" + } + } + } - lessOpenUI5.build({ - lessInput: readFile(lessFilename), - parser: { - filename: filename + '.less', - paths: 'test/fixtures/rtl' + assert.equal(result.css, readFile('test/expected/libraries/lib1/my/ui/lib/themes/foo/library.css'), 'css should be correctly generated.'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/lib1/my/ui/lib/themes/foo/library-RTL.css'), 'rtl css should be correctly generated.'); + assert.deepEqual(result.variables, oVariablesExpected, 'variables should be correctly collected.'); + assert.deepEqual(result.imports, [ + "test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less", + "test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less", + "test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less", + "test/fixtures/libraries/lib1/sap/ui/core/themes/foo/.theming" + ], 'import list should be correct.'); + + }); + + }); + + it('should create bar theme with scope as defined in .theming file', function() { + + return new Builder().build({ + lessInputPath: 'my/ui/lib/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ], + library: { + name: "my.ui.lib" } - }, function(err, result) { + }).then(function(result) { + + var oVariablesExpected = { + "default" : { + "color1": "#ffffff", + }, + "scopes": { + "barContrast": { + "color1": "#000000" + } + } + } + + assert.equal(result.css, readFile('test/expected/libraries/lib2/my/ui/lib/themes/bar/library.css'), 'css should be correctly generated.'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/lib2/my/ui/lib/themes/bar/library-RTL.css'), 'rtl css should be correctly generated.'); + assert.deepEqual(result.variables, oVariablesExpected, 'variables should be correctly collected.'); + assert.deepEqual(result.imports, [ + "test/fixtures/libraries/lib2/my/ui/lib/themes/bar/library.source.less", + "test/fixtures/libraries/lib1/my/ui/lib/themes/foo/library.source.less", + "test/fixtures/libraries/lib1/my/ui/lib/themes/base/library.source.less", + "test/fixtures/libraries/lib2/sap/ui/core/themes/bar/.theming" + ], 'import list should be correct.'); + + }); + + }); - assert.ifError(err); - assert.equal(result.cssRtl, readFile(cssFilename), 'rtl css should not be generated.'); +}); + +describe('error handling', function() { + + it('should have correct error in case of undefined variable usage', function() { + + return new Builder().build({ + lessInput: readFile('test/fixtures/error/undefined-var.less') + }).then(function(result) { + // no resolve + assert.ok(false); + + }, function(err) { - done(); + assert.ok(err); }); + }); + +}); + +function assertLessToRtlCssEqual(filename) { + var lessFilename = 'test/fixtures/rtl/' + filename + '.less'; + var cssFilename = 'test/expected/rtl/' + filename + '.css'; + + return new Builder().build({ + lessInput: readFile(lessFilename), + parser: { + filename: filename + '.less', + paths: 'test/fixtures/rtl' + } + }).then(function(result) { + assert.equal(result.cssRtl, readFile(cssFilename), 'rtl css should not be generated.'); + }); } describe('rtl', function() { - it('background-position', function(done) { - assertLessToRtlCssEqual('background-position', done); + it('background-position', function() { + return assertLessToRtlCssEqual('background-position'); }); - it('background', function(done) { - assertLessToRtlCssEqual('background', done); + it('background', function() { + return assertLessToRtlCssEqual('background'); }); - it('border', function(done) { - assertLessToRtlCssEqual('border', done); + it('border', function() { + return assertLessToRtlCssEqual('border'); }); - it('cursor', function(done) { - assertLessToRtlCssEqual('cursor', done); + it('cursor', function() { + return assertLessToRtlCssEqual('cursor'); }); - it('gradient', function(done) { - assertLessToRtlCssEqual('gradient', done); + it('gradient', function() { + return assertLessToRtlCssEqual('gradient'); }); - it('image-url', function(done) { - assertLessToRtlCssEqual('image-url', done); + it('image-url', function() { + return assertLessToRtlCssEqual('image-url'); }); - it('misc', function(done) { - assertLessToRtlCssEqual('misc', done); + it('misc', function() { + return assertLessToRtlCssEqual('misc'); }); - it('shadow', function(done) { - assertLessToRtlCssEqual('shadow', done); + it('shadow', function() { + return assertLessToRtlCssEqual('shadow'); }); - it('transform', function(done) { - assertLessToRtlCssEqual('transform', done); + it('transform', function() { + return assertLessToRtlCssEqual('transform'); }); - it('variables', function(done) { - assertLessToRtlCssEqual('variables', done); + it('variables', function() { + return assertLessToRtlCssEqual('variables'); }); }); describe('variables', function() { - it('should return only globally defined variables', function(done) { + it('should return only globally defined variables', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/variables/main.less') - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/variables/main.css'), 'css should be correctly generated.'); assert.deepEqual(result.variables, JSON.parse(readFile('test/expected/variables/variables.json')), 'variables should be correctly collected.'); assert.deepEqual(result.imports, [], 'import list should be empty.'); - done(); - }); }); @@ -235,17 +362,15 @@ describe('variables', function() { describe('imports', function() { - it('should return imported file paths', function(done) { + it('should return imported file paths', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/imports/main.less'), parser: { filename: 'main.less', paths: [ 'test/fixtures/imports' ] } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/imports/main.css'), 'css should be correctly generated.'); assert.deepEqual(result.variables, {}, 'variables should be empty.'); @@ -255,49 +380,39 @@ describe('imports', function() { path.join('test', 'fixtures', 'imports', 'dir3', 'inline.css') ], 'import list should be correctly filled.'); - done(); - }); }); - it('should use "relativeUrls" parser option by default', function(done) { + it('should use "relativeUrls" parser option by default', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/imports/main.less'), parser: { filename: 'main.less', paths: [ 'test/fixtures/imports' ] } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/imports/main.css'), 'css should be correctly generated.'); - done(); - }); }); - it('should not rewrite urls when "relativeUrls" parser option is set to "false"', function(done) { + it('should not rewrite urls when "relativeUrls" parser option is set to "false"', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/imports/main.less'), parser: { filename: 'main.less', paths: [ 'test/fixtures/imports' ], relativeUrls: false } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.equal(result.css, readFile('test/expected/imports/main-no-relativeUrls.css'), 'css should be correctly generated.'); - done(); - }); }); @@ -306,45 +421,456 @@ describe('imports', function() { describe('inline theming parameters', function() { - it('should not include inline parameters when no library name is given', function(done) { + it('should not include inline parameters when no library name is given', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/simple/test.less') - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.ok(!/#sap-ui-theme-/.test(result.css), 'inline parameter rule should not be present.'); assert.ok(!/data:text\/plain/.test(result.css), 'data-uri should not be present.'); assert.equal(result.css, readFile('test/expected/simple/test.css'), 'css should be correctly generated.'); - done(); - }); }); - it('should include inline parameters when library name is given', function(done) { + it('should include inline parameters when library name is given', function() { - lessOpenUI5.build({ + return new Builder().build({ lessInput: readFile('test/fixtures/simple/test.less'), library: { name: 'foo.bar' } - }, function(err, result) { - - assert.ifError(err); + }).then(function(result) { assert.ok(/#sap-ui-theme-foo\\.bar/.test(result.css), 'inline parameter rule for library should be present.'); assert.ok(/data:text\/plain/.test(result.css), 'data-uri should be present.'); assert.equal(result.css, readFile('test/expected/simple/test-inline-parameters.css'), 'css should be correctly generated.'); - done(); + }); + + }); + +}); + +describe('theme caching', function() { + + it('should cache the theme', function() { + + var lessOptions = { + lessInputPath: 'my/ui/lib/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ], + library: { + name: "my.ui.lib" + } + }; + + var builder = new Builder(); + + return builder.build(lessOptions).then(function(res) { + + var cacheFirstRun = clone(builder.themeCacheMapping); + + assert.notDeepEqual(cacheFirstRun, {}, 'themeCache should not be empty.'); + + // second run + return builder.build(lessOptions).then(function(result) { + + var cacheSecondRun = clone(builder.themeCacheMapping); + + assert.deepEqual(res, result, "callback result should be the same"); + + assert.notDeepEqual(cacheSecondRun, {}, 'themeCache should not be empty.'); + + assert.deepEqual(cacheFirstRun, cacheSecondRun, 'cache should be equal after second build run.') + + }); + + }); + + }); + + it('should recompile the theme after clearing the cache', function() { + + var lessOptions = { + lessInputPath: 'my/ui/lib/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ], + library: { + name: "my.ui.lib" + } + }; + + var builder = new Builder(); + + return builder.build(lessOptions).then(function(res) { + + var cacheFirstRun = clone(builder.themeCacheMapping); + + assert.notDeepEqual(cacheFirstRun, {}, 'themeCache should not be empty.'); + + builder.clearCache(); + + assert.deepEqual(builder.themeCacheMapping, {}, 'themeCache should be empty.'); + + // second run + return builder.build(lessOptions).then(function(result) { + + var cacheSecondRun = clone(builder.themeCacheMapping); + + assert.equal(JSON.stringify(res, null, 4), JSON.stringify(result, null, 4), "callback result should be the same"); + + assert.notDeepEqual(cacheSecondRun, {}, 'themeCache should not be empty.'); + + }); + + }); + + }); +}); + +describe('CSS Scoping of', function() { + + describe('comments', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'comments/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/comments/lib1', + 'test/fixtures/libraries/scopes/comments/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/comments/lib1/comments/themes/foo/library-RTL.css'), 'Rtl CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'comments/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/comments/lib1', + 'test/fixtures/libraries/scopes/comments/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/comments/lib2/comments/themes/bar/library-RTL.css'), 'Rtl CSS scoping should be correctly generated'); + }); }); }); + describe('css-scope-root', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'css-scope-root/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/css-scope-root/lib1', + 'test/fixtures/libraries/scopes/css-scope-root/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/css-scope-root/lib1/css-scope-root/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'css-scope-root/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/css-scope-root/lib1', + 'test/fixtures/libraries/scopes/css-scope-root/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/css-scope-root/lib2/css-scope-root/themes/bar/library-RTL.css'), 'Rtl CSS scoping should be correctly generated'); + }); + + }); + + }); + + describe('default', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'default/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/default/lib1', + 'test/fixtures/libraries/scopes/default/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/default/lib1/default/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/default/lib1/default/themes/foo/library-RTL.css'), 'Rtl CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'default/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/default/lib1', + 'test/fixtures/libraries/scopes/default/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/default/lib2/default/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/default/lib2/default/themes/bar/library-RTL.css'), 'Rtl CSS scoping should be correctly generated'); + }); + + }); + + }); + + + describe('dom', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'dom/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/dom/lib1', + 'test/fixtures/libraries/scopes/dom/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/dom/lib1/dom/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'dom/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/dom/lib1', + 'test/fixtures/libraries/scopes/dom/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/dom/lib2/dom/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); + + describe('empty media queries', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'empty-media-queries/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/empty-media-queries/lib1', + 'test/fixtures/libraries/scopes/empty-media-queries/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/empty-media-queries/lib1/empty-media-queries/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'empty-media-queries/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/empty-media-queries/lib1', + 'test/fixtures/libraries/scopes/empty-media-queries/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/empty-media-queries/lib2/empty-media-queries/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); + + describe('HTML', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'html/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/html/lib1', + 'test/fixtures/libraries/scopes/html/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/html/lib1/html/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'html/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/html/lib1', + 'test/fixtures/libraries/scopes/html/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/html/lib2/html/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/html/lib2/html/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); + + + describe('media-queries', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'media-queries/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/media-queries/lib1', + 'test/fixtures/libraries/scopes/media-queries/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/media-queries/lib1/media-queries/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'media-queries/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/media-queries/lib1', + 'test/fixtures/libraries/scopes/media-queries/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/media-queries/lib2/media-queries/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); + + describe('mixins', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'mixins/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/mixins/lib1', + 'test/fixtures/libraries/scopes/mixins/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/mixins/lib1/mixins/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'mixins/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/mixins/lib1', + 'test/fixtures/libraries/scopes/mixins/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/mixins/lib2/mixins/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); + + describe('multiple imports', function() { + + it('should return same CSS for foo', function() { + + return new Builder().build({ + lessInputPath: 'multiple-imports/themes/foo/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/multiple-imports/lib1', + 'test/fixtures/libraries/scopes/multiple-imports/lib2', + 'test/fixtures/libraries/lib1' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/multiple-imports/lib1/multiple-imports/themes/foo/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + it('should return same CSS for bar', function() { + + return new Builder().build({ + lessInputPath: 'multiple-imports/themes/bar/library.source.less', + rootPaths: [ + 'test/fixtures/libraries/scopes/multiple-imports/lib1', + 'test/fixtures/libraries/scopes/multiple-imports/lib2', + 'test/fixtures/libraries/lib1', + 'test/fixtures/libraries/lib2' + ] + }).then(function(result) { + assert.equal(result.css, readFile('test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library.css'), 'CSS scoping should be correctly generated'); + assert.equal(result.cssRtl, readFile('test/expected/libraries/scopes/multiple-imports/lib2/multiple-imports/themes/bar/library-RTL.css'), 'CSS scoping should be correctly generated'); + }); + + }); + + }); });