diff --git a/README.md b/README.md index 6712dff3..cc1c2c91 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Tool for working with BEM import strings. [dependency-img]: http://img.shields.io/david/bem-sdk/bem-import-notation.svg -Extract [BEM entities](https://en.bem.info/methodology/key-concepts/#bem-entity) from import strings. +Extract [BEM entities] from import strings. Installation ------------ @@ -51,6 +51,7 @@ API --- * [parse](#parsestr-ctx) +* [stringify](#stringify) ### parse(str, ctx) @@ -84,6 +85,15 @@ Note that, using context exludes `{ block: 'button'}` from result. So `parse('m:theme=normal', { block: 'button' })` is not same as `parse('b:button m:theme=normal')` +### stringify + +Parameter | Type | Description +----------|----------|------------------------------------------------------------------------------ +`entities`| `array` | Array of [BEM entities] to merge into import string [notation](#notation) + +Forms a string from [BEM entities]. Be aware to merge only one type of entities. +The array should contains one block or one elem and optionally it's modifiers. + Notation -------- @@ -211,3 +221,5 @@ License ------- Code and documentation copyright 2017 YANDEX LLC. Code released under the [Mozilla Public License 2.0](LICENSE.txt). + +[BEM entities]: https://en.bem.info/methodology/key-concepts/#bem-entity diff --git a/index.js b/index.js index a1170974..2ce3091e 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,6 @@ * `import 'm:modOfThisBlock` * @returns {BemCell[]} */ - function parse(importString, ctx) { const main = {}; ctx || (ctx = {}); @@ -60,6 +59,41 @@ function parse(importString, ctx) { }, []); } +/** + * Create import string notation of passed bem-cells. + * + * Example: + * ```js + * stringify([{ block : 'button' }, { block : 'button', mod : { name : 'theme', val : 'normal' } }]) + * // 'b:button m:theme=normal' + * ``` + * @public + * @param {BemCell[]} - Set of BEM entites to merge into import string notation + * @returns {String} + */ +function stringify(cells) { + const merged = [].concat(cells).reduce((acc, cell) => { + cell.block && (acc.b = cell.block); + cell.elem && (acc.e = cell.elem); + cell.mod && (acc.m[cell.mod.name] || (acc.m[cell.mod.name] = [])) + && cell.mod.val && acc.m[cell.mod.name].push(cell.mod.val); + cell.tech && (acc.t = cell.tech); + return acc; + }, { m : {} }); + + return ['b', 'e', 'm', 't'].map(k => tmpl[k](merged[k])).join(''); +} + +const tmpl = { + b : b => `b:${b}`, + e : e => e ? ` e:${e}` : '', + m : m => Object.keys(m).map(name => `${tmpl.mn(name)}${tmpl.mv(m[name])}`).join(''), + mn : m => ` m:${m}`, + mv : v => v.length ? `=${v.join('|')}` : '', + t : t => t ? ` t:${t}` : '' +}; + module.exports = { - parse + parse, + stringify }; diff --git a/test.js b/test/parse.js similarity index 99% rename from test.js rename to test/parse.js index 216a3d53..c9149c25 100644 --- a/test.js +++ b/test/parse.js @@ -1,5 +1,5 @@ var expect = require('chai').expect, - p = require('.').parse; + p = require('..').parse; it('should return an array', () => { expect(p('b:button')).to.be.an('Array'); diff --git a/test/stringify.js b/test/stringify.js new file mode 100644 index 00000000..f4e7af18 --- /dev/null +++ b/test/stringify.js @@ -0,0 +1,118 @@ +var expect = require('chai').expect, + s = require('..').stringify; + +it('should return a string', () => { + expect(s([{ block : 'button' }])).to.be.an('String'); +}); + +describe('block', () => { + it('should stringify block', () => { + expect(s({ block : 'button' })).to.be.equal('b:button'); + }); + + it('should stringify block with simple modifier', () => { + expect(s([ + { block : 'popup' }, + { block : 'popup', mod : { name : 'autoclosable' } } + ])).to.be.equal('b:popup m:autoclosable'); + }); + + it('should stringify block with modifier', () => { + expect(s([ + { block : 'popup' }, + { block : 'popup', mod : { name : 'autoclosable' } }, + { block : 'popup', mod : { name : 'autoclosable', val : 'yes' } } + ])).to.equal('b:popup m:autoclosable=yes'); + }); + + it('should stringify block with modifier and several values', () => { + expect(s([ + { block : 'popup' }, + { block : 'popup', mod : { name : 'theme' } }, + { block : 'popup', mod : { name : 'theme', val : 'normal' } }, + { block : 'popup', mod : { name : 'theme', val : 'action' } } + ])).to.equal('b:popup m:theme=normal|action'); + }); + + it('should stringify block with several modifiers', () => { + expect(s([ + { block : 'popup' }, + { block : 'popup', mod : { name : 'theme' } }, + { block : 'popup', mod : { name : 'autoclosable' } } + ])).to.equal('b:popup m:theme m:autoclosable'); + }); + + it('should stringify block with several modifiers and several values', () => { + expect(s([ + { block : 'popup' }, + { block : 'popup', mod : { name : 'theme' } }, + { block : 'popup', mod : { name : 'theme', val : 'normal' } }, + { block : 'popup', mod : { name : 'theme', val : 'action' } }, + { block : 'popup', mod : { name : 'autoclosable' } }, + { block : 'popup', mod : { name : 'autoclosable', val : 'yes' } } + ])).to.equal('b:popup m:theme=normal|action m:autoclosable=yes'); + }); +}); + +describe('elem', () => { + it('should stringify elem', () => { + expect(s([{ block : 'button', elem : 'text' }])).to.be.equal('b:button e:text'); + }); + + it('should stringify elem with simple modifier', () => { + expect(s([ + { block : 'button2', elem : 'text' }, + { block : 'button2', elem : 'text', mod : { name : 'pseudo' } } + ])).to.equal('b:button2 e:text m:pseudo'); + }); + + it('should stringify elem with modifier', () => { + expect(s([ + { block : 'button2', elem : 'text' }, + { block : 'button2', elem : 'text', mod : { name : 'pseudo' } }, + { block : 'button2', elem : 'text', mod : { name : 'pseudo', val : 'yes' } } + ])).to.equal('b:button2 e:text m:pseudo=yes'); + }); + + it('should stringify elem with modifier and several values', () => { + expect(s([ + { block : 'button2', elem : 'text' }, + { block : 'button2', elem : 'text', mod : { name : 'theme' } }, + { block : 'button2', elem : 'text', mod : { name : 'theme', val : 'normal' } }, + { block : 'button2', elem : 'text', mod : { name : 'theme', val : 'action' } } + ])).to.equal('b:button2 e:text m:theme=normal|action'); + }); + + it('should stringify elem with several modifiers', () => { + expect(s([ + { block : 'popup', elem : 'tail' }, + { block : 'popup', elem : 'tail', mod : { name : 'theme' } }, + { block : 'popup', elem : 'tail', mod : { name : 'autoclosable' } } + ])).to.equal('b:popup e:tail m:theme m:autoclosable'); + }); + + it('should stringify elem with several modifiers and several values', () => { + expect(s([ + { block : 'popup', elem : 'tail' }, + { block : 'popup', elem : 'tail', mod : { name : 'theme' } }, + { block : 'popup', elem : 'tail', mod : { name : 'theme', val : 'normal' } }, + { block : 'popup', elem : 'tail', mod : { name : 'theme', val : 'action' } }, + { block : 'popup', elem : 'tail', mod : { name : 'autoclosable' } }, + { block : 'popup', elem : 'tail', mod : { name : 'autoclosable', val : 'yes' } } + ])).to.equal('b:popup e:tail m:theme=normal|action m:autoclosable=yes'); + }); +}); + +describe('tech', () => { + it('should stringify block with tech', () => { + expect(s({ block : 'button', tech : 'css' })).to.be.equal('b:button t:css'); + }); + + it('should stringify block with mod and tech', () => { + expect(s([ + { block : 'popup', tech : 'js' }, + { block : 'popup', mod : { name : 'autoclosable' }, tech : 'js' }, + { block : 'popup', mod : { name : 'autoclosable', val : 'yes' }, tech : 'js' } + ])).to.be.equal('b:popup m:autoclosable=yes t:js'); + }); +});