diff --git a/__tests__/formats/__snapshots__/all.test.js.snap b/__tests__/formats/__snapshots__/all.test.js.snap index 6016b5661..de6b8e471 100644 --- a/__tests__/formats/__snapshots__/all.test.js.snap +++ b/__tests__/formats/__snapshots__/all.test.js.snap @@ -514,6 +514,37 @@ exports[`formats all should return less/variables as a string 1`] = ` @color_red: #FF0000; /* comment */" `; +exports[`formats all should return sass/map-deep as a string 1`] = ` +" +/* + Do not edit directly + Generated on Sat, 01 Jan 2000 00:00:00 GMT +*/ + +$color_red: #FF0000 !default; // comment + +$tokens: ( + 'color': ( + 'red': $color_red + ) +); +" +`; + +exports[`formats all should return sass/map-flat as a string 1`] = ` +" +/* + Do not edit directly + Generated on Sat, 01 Jan 2000 00:00:00 GMT +*/ + +$tokens: ( + // comment + 'color_red': #FF0000 +); +" +`; + exports[`formats all should return scss/icons as a string 1`] = ` "/** * Do not edit directly diff --git a/__tests__/formats/__snapshots__/scssMaps.test.js.snap b/__tests__/formats/__snapshots__/scssMaps.test.js.snap new file mode 100644 index 000000000..af61b5e61 --- /dev/null +++ b/__tests__/formats/__snapshots__/scssMaps.test.js.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`formats sass/map-deep sass/map-deep snapshot 1`] = ` +" +/* + Do not edit directly + Generated on Sat, 01 Jan 2000 00:00:00 GMT +*/ + +$size-font-small: 12rem !default; +$size-font-large: 18rem !default; +$color-base-red: #ff0000 !default; // comment +$color-white: #ffffff !default; + +$tokens: ( + 'size': ( + 'font': ( + 'small': $size-font-small, + 'large': $size-font-large + ) + ), + 'color': ( + 'base': ( + 'red': $color-base-red + ), + 'white': $color-white + ) +); +" +`; + +exports[`formats sass/map-flat sass/map-flat snapshot 1`] = ` +" +/* + Do not edit directly + Generated on Sat, 01 Jan 2000 00:00:00 GMT +*/ + +$tokens: ( + 'size-font-small': 12rem, + 'size-font-large': 18rem, + // comment + 'color-base-red': #ff0000, + 'color-white': #ffffff +); +" +`; diff --git a/__tests__/formats/all.test.js b/__tests__/formats/all.test.js index 07d28f179..bce206e63 100644 --- a/__tests__/formats/all.test.js +++ b/__tests__/formats/all.test.js @@ -12,7 +12,6 @@ */ var formats = require('../../lib/common/formats'); -var helpers = require('../__helpers'); var _ = require('lodash'); var file = { diff --git a/__tests__/formats/scssMaps.test.js b/__tests__/formats/scssMaps.test.js new file mode 100644 index 000000000..684ca8d6a --- /dev/null +++ b/__tests__/formats/scssMaps.test.js @@ -0,0 +1,213 @@ +/* + * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with + * the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +var formats = require('../../lib/common/formats'); +var scss = require('node-sass'); +var _ = require('lodash'); + +var dictionary = { + "properties": { + "size": { + "font": { + "small": { + "value": "12rem", + "original": { + "value": "12px" + }, + "name": "size-font-small", + "attributes": { + "category": "size", + "type": "font", + "item": "small" + }, + "path": [ + "size", + "font", + "small" + ] + }, + "large": { + "value": "18rem", + "original": { + "value": "18px" + }, + "name": "size-font-large", + "attributes": { + "category": "size", + "type": "font", + "item": "large" + }, + "path": [ + "size", + "font", + "large" + ] + } + } + }, + "color": { + "base": { + "red": { + "value": "#ff0000", + "comment": "comment", + "original": { + "value": "#FF0000", + "comment": "comment" + }, + "name": "color-base-red", + "attributes": { + "category": "color", + "type": "base", + "item": "red" + }, + "path": [ + "color", + "base", + "red" + ] + } + }, + "white": { + "value": "#ffffff", + "original": { + "value": "#ffffff" + }, + "name": "color-white", + "attributes": { + "category": "color", + "type": "white" + }, + "path": [ + "color", + "white" + ] + } + } + }, + "allProperties": [ + { + "value": "12rem", + "original": { + "value": "12px" + }, + "name": "size-font-small", + "attributes": { + "category": "size", + "type": "font", + "item": "small" + }, + "path": [ + "size", + "font", + "small" + ] + }, + { + "value": "18rem", + "original": { + "value": "18px" + }, + "name": "size-font-large", + "attributes": { + "category": "size", + "type": "font", + "item": "large" + }, + "path": [ + "size", + "font", + "large" + ] + }, + { + "value": "#ff0000", + "comment": "comment", + "original": { + "value": "#FF0000", + "comment": "comment" + }, + "name": "color-base-red", + "attributes": { + "category": "color", + "type": "base", + "item": "red" + }, + "path": [ + "color", + "base", + "red" + ] + }, + { + "value": "#ffffff", + "original": { + "value": "#ffffff" + }, + "name": "color-white", + "attributes": { + "category": "color", + "type": "white" + }, + "path": [ + "color", + "white" + ] + } + ] +}; + +describe('formats', () => { + _.each(['sass/map-flat', 'sass/map-deep'], function(key) { + + describe(key, () => { + + var file = { + "destination": "__output/", + "format": key + }; + + // mock the Date.now() call to a fixed value + const constantDate = new Date('2000-01-01'); + const globalDate = global.Date; + global.Date = function() { return constantDate }; + + var formatter = formats[key].bind(file); + var output = formatter(dictionary, file); + + // reset the global Date object (or node-sass will complain!) + global.Date = globalDate; + + it('should return ' + key + ' as a string', () => { + expect(typeof output).toBe('string'); + }); + + it('should have a valid scss syntax', done => { + scss.render({ + data: output, + }, function(err, result) { + if(err) { + return done(new Error(err)); + } + expect(result.css).toBeDefined(); + return done(); + }); + }); + + it(key + ' snapshot', () => { + expect(output).toMatchSnapshot(); + }); + + }); + + }); +}); diff --git a/lib/common/formats.js b/lib/common/formats.js index a9218db0a..d9f5ad3b2 100644 --- a/lib/common/formats.js +++ b/lib/common/formats.js @@ -92,6 +92,47 @@ module.exports = { '\n}\n'; }, + /** + * Creates a Sass file with a flat map based on the style dictionary + * + * @memberof Formats + * @kind member + * @example + * ```scss + * $tokens: ( + * $color-background-base: #f0f0f0; + * $color-background-alt: #eeeeee; + * ) + * ``` + */ + 'sass/map-flat': _.template( + fs.readFileSync(__dirname + '/templates/sass/map-flat.template') + ), + + /** + * Creates a Sass file with a deep map based on the style dictionary + * + * @memberof Formats + * @kind member + * @example + * ```scss + * $color-background-base: #f0f0f0 !default; + * $color-background-alt: #eeeeee !default; + * + * $tokens: { + * 'color': ( + * 'background': ( + * 'base': $color-background-base, + * 'alt': $color-background-alt + * ) + * ) + * ) + * ``` + */ + 'sass/map-deep': _.template( + fs.readFileSync(__dirname + '/templates/sass/map-deep.template') + ), + /** * Creates a SCSS file with variable definitions based on the style dictionary * diff --git a/lib/common/templates/sass/map-deep.template b/lib/common/templates/sass/map-deep.template new file mode 100644 index 000000000..bb6cf7a27 --- /dev/null +++ b/lib/common/templates/sass/map-deep.template @@ -0,0 +1,68 @@ +<% +// +// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// or in the "license" file accompanying this file. This file 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. + +%> +<% + // for backward compatibility we need to have the user explicitly hide it + var showFileHeader = (this.options && this.options.hasOwnProperty('showFileHeader')) ? this.options.showFileHeader : true; + if(showFileHeader) { + var header = ''; + header += "/*\n Do not edit directly"; + header += "\n Generated on " + new Date().toUTCString(); + header += "\n*/\n"; + print(header); + } + + print("\n"); + + // output the list of tokens as Sass variables + // + _.each(allProperties, function(prop) { + var output = ''; + output += '$' + prop.name + ': ' + prop.value + ' !default;' + if(prop.comment) { + output += ' // ' + prop.comment; + } + output += '\n'; + print(output); + }); + + print('\n'); + + // output the list of tokens as a Sass nested map + // (the values are pointing to the variables) + // + print(`$tokens: ${processJsonNode(properties, 0)};\n`); + + // recursive function to process a properties JSON node + // + function processJsonNode(obj, depth) { + var output = ''; + if (obj.hasOwnProperty('value')) { + // if we have found a leaf (a property with a value) append the value + output += `$${obj.name}`; + } else { + // if we have found a group of properties, use the Sass group "(...)" syntax and loop -recursively- on the children + output += '(\n' + output += Object.keys(obj).map(function(newKey) { + var newProp = obj[newKey]; + var indent = ' '.repeat(depth+1); + return `${indent}'${newKey}': ${processJsonNode(newProp, depth + 1)}`; + }).join(',\n'); + output += '\n' + ' '.repeat(depth) + ')'; + } + return output; + } +%> \ No newline at end of file diff --git a/lib/common/templates/sass/map-flat.template b/lib/common/templates/sass/map-flat.template new file mode 100644 index 000000000..91913ad4f --- /dev/null +++ b/lib/common/templates/sass/map-flat.template @@ -0,0 +1,42 @@ +<% +// +// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// or in the "license" file accompanying this file. This file 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. + +%> +<% + // for backward compatibility we need to have the user explicitly hide it + var showFileHeader = (this.options && this.options.hasOwnProperty('showFileHeader')) ? this.options.showFileHeader : true; + if(showFileHeader) { + var header = ''; + header += "/*\n Do not edit directly"; + header += "\n Generated on " + new Date().toUTCString(); + header += "\n*/\n"; + print(header); + } + + print('\n'); + + var output = ''; + output += '$tokens: (\n'; + output += allProperties.map(function(prop){ + var line = ''; + if(prop.comment) { + line += ' // ' + prop.comment + '\n'; + } + line += ' \'' + prop.name + '\': ' + prop.value + return line; + }).join(',\n'); + output += '\n);'; + print(output); +%>