├── .travis.yml ├── .gitignore ├── test ├── test-run.js ├── test-util.js ├── react-bem.html ├── test-setup.js └── react-bem.test.js ├── example ├── index.html └── jsx │ └── test.jsx ├── bower.json ├── package.json ├── Gruntfile.js ├── license.txt ├── README.md └── src └── react-bem.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | example/components 4 | -------------------------------------------------------------------------------- /test/test-run.js: -------------------------------------------------------------------------------- 1 | if (!window.PHANTOMJS) { 2 | mocha.run(); 3 | } 4 | -------------------------------------------------------------------------------- /test/test-util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clones / deep copies an object. 3 | * 4 | * @param Object obj 5 | * Any object. 6 | * 7 | * @return Object 8 | * obj--cloned. 9 | */ 10 | function clone(obj) { 11 | if (obj === null || typeof(obj) !== 'object') return obj; 12 | var temp = new Object(); 13 | for (var key in obj) { 14 | temp[key] = clone(obj[key]); 15 | } 16 | return temp; 17 | } 18 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React BEM Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/jsx/test.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var Test = React.createClass({ 3 | mixins: [ReactBEM], 4 | 5 | bem_blocks: ["widget"], 6 | bem_block_modifiers: ["christmas"], 7 | 8 | bem_render: function() { 9 | return ( 10 |
11 |

HEADER: This is the Header

12 |
13 | ); 14 | } 15 | }); 16 | 17 | document.addEventListener("DOMContentLoaded", function() { 18 | 19 | ReactDOM.render( 20 | , 21 | document.getElementsByTagName("body")[0] 22 | ); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bem", 3 | "version": "0.0.2", 4 | "homepage": "https://github.com/cuzzo/react-bem", 5 | "authors": [ 6 | "Cuzzo Yahn " 7 | ], 8 | "description": "React automatic BEM class naming.", 9 | "main": "main.js", 10 | "keywords": [ 11 | "react", 12 | "BEM" 13 | ], 14 | "license": "BSD", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "dist", 21 | "Gruntfile.js", 22 | "example" 23 | ], 24 | "dependencies": { 25 | "react": "~0.14.3" 26 | }, 27 | "devDependencies": { 28 | "mocha": "1.21.4", 29 | "chai": "1.9.1", 30 | "sinon": "http://sinonjs.org/releases/sinon-1.9.0.js" 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-auto-bem", 3 | "description": "React automatic BEM class naming.", 4 | "version": "0.0.2", 5 | "homepage": "https://github.com/cuzzo/react-bem", 6 | "devDependencies": { 7 | "bower": "~1.3.2", 8 | "grunt": "~0.4.1", 9 | "grunt-cli": "~0.1.7", 10 | "grunt-contrib-watch": "~0.6.1", 11 | "grunt-react": "~0.12.3", 12 | "grunt-lib-phantomjs": "~0.6", 13 | "grunt-mocha": "~0.4.7" 14 | }, 15 | "scripts": { 16 | "postinstall": "bower install", 17 | "test": "grunt test" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "BSD", 22 | "url": "http://opensource.org/licences/BSD-2-Clause" 23 | } 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/cuzzo/react-bem.git" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/react-bem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React BEM Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | react: { 4 | dynamic_mappings: { 5 | files: [ 6 | { 7 | expand: true, 8 | cwd: "example/jsx", 9 | src: ["**/*.jsx"], 10 | dest: "example/components", 11 | ext: ".js" 12 | } 13 | ] 14 | } 15 | }, 16 | 17 | mocha: { 18 | options: { 19 | reporter: 'Nyan', // Duh! 20 | run: true 21 | } 22 | }, 23 | 24 | watch: { 25 | react: { 26 | files: ["example/jsx/**/*.jsx"], 27 | tasks: ["react"] 28 | } 29 | } 30 | }); 31 | 32 | grunt.loadNpmTasks("grunt-react"); 33 | grunt.loadNpmTasks("grunt-mocha"); 34 | grunt.registerTask("test", "Run Mocha tests.", function() { 35 | // If not --test option is specified, run all tests. 36 | var test_case = grunt.option("test") || "**/*"; 37 | 38 | grunt.config.set("mocha.browser", ["test/" + test_case + ".html"]); 39 | grunt.task.run("mocha"); 40 | }); 41 | grunt.loadNpmTasks("grunt-contrib-watch"); 42 | }; 43 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Cuzzo Yahn 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /test/test-setup.js: -------------------------------------------------------------------------------- 1 | window._TEST_MODE = true; 2 | mocha.setup('bdd'); 3 | 4 | // PhantomJS React Shim 5 | 6 | (function() { 7 | 8 | var Ap = Array.prototype; 9 | var slice = Ap.slice; 10 | var Fp = Function.prototype; 11 | 12 | if (!Fp.bind) { 13 | // PhantomJS doesn't support Function.prototype.bind natively, so 14 | // polyfill it whenever this module is required. 15 | Fp.bind = function(context) { 16 | var func = this; 17 | var args = slice.call(arguments, 1); 18 | 19 | function bound() { 20 | var invokedAsConstructor = func.prototype && (this instanceof func); 21 | return func.apply( 22 | // Ignore the context parameter when invoking the bound function 23 | // as a constructor. Note that this includes not only constructor 24 | // invocations using the new keyword but also calls to base class 25 | // constructors such as BaseClass.call(this, ...) or super(...). 26 | !invokedAsConstructor && context || this, 27 | args.concat(slice.call(arguments)) 28 | ); 29 | } 30 | 31 | // The bound function must share the .prototype of the unbound 32 | // function so that any object created by one constructor will count 33 | // as an instance of both constructors. 34 | bound.prototype = func.prototype; 35 | 36 | return bound; 37 | }; 38 | } 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React BEM [![Build Status](https://travis-ci.org/cuzzo/react-bem.svg?branch=master)](https://travis-ci.org/cuzzo/react-bem) 2 | 3 | BEM class names are systematic. So why write them yourself? 4 | 5 | # Usage 6 | 7 | React BEM automatically generates BEM style classes on React components... So you don't have to. 8 | 9 | # Installation 10 | 11 | ```bash 12 | bower install react-bem 13 | ``` 14 | 15 | # Example 16 | 17 | JSX Component: 18 | 19 | ```javascript 20 | var Header = React.createClass({ 21 | mixins: [ReactBEM], 22 | 23 | bem_blocks: ["widget"], 24 | bem_block_modifiers: ["christmas"], 25 | 26 | bem_render: function() { 27 | return ( 28 |
29 |

HEADER: This is the Header

30 |
31 | ); 32 | } 33 | }); 34 | ``` 35 | 36 | Translates to: 37 | 38 | ```html 39 |
40 |

41 | 42 | 43 | This is the Header 44 | 45 |

46 |
47 | ``` 48 | 49 | You can see it live, how it attaches the BEM classes, [here](http://cuzzo.github.io/react-bem/example/ "React autogenerate BEM class names example"). 50 | 51 | # Custom E 52 | 53 | Don't want the E in BEM to be the HTML element name? Don't blame you. 54 | 55 | ```javascript 56 | var Header = React.createClass({ 57 | mixins: [ReactBEM], 58 | 59 | bem_blocks: ["widget"], 60 | bem_block_modifiers: ["christmas"], 61 | 62 | bem_render: function() { 63 | return ( 64 |
65 |

HEADER: This is the Header

66 |
67 | ); 68 | } 69 | }); 70 | ``` 71 | 72 | Translates to: 73 | 74 | ```html 75 |
76 |

77 | 78 | 79 | This is the Header 80 | 81 |

82 |
83 | ``` 84 | 85 | There's a lot of reasons why you should be styling with **more** than just the class attribute. But say you want to define a role and use something different for your BEM element. Or say you just have a religion about not using the role attribute. Fine. 86 | 87 | ```javascript 88 | var Header = React.createClass({ 89 | mixins: [ReactBEM], 90 | 91 | bem_blocks: ["widget"], 92 | bem_block_modifiers: ["christmas"], 93 | 94 | bem_render: function() { 95 | return ( 96 |
97 |

HEADER: This is the Header

98 |
99 | ); 100 | } 101 | }); 102 | ``` 103 | 104 | Translates to: 105 | 106 | ```html 107 |
108 |

109 | 110 | 111 | This is the Header 112 | 113 |

114 |
115 | ``` 116 | 117 | Other Resources 118 | --------------- 119 | * [react-bem-helper](https://github.com/marcohamersma/react-bem-helper "React BEM Helper") - A React BEM Helper. 120 | * [bem-classnames](https://github.com/pocotan001/bem-classnames "React BEM class name manager") - A React BEM class name manager. 121 | * [bem-cn](https://github.com/albburtsev/bem-cn "React BEM class name generator") - A React BEM class name generator. 122 | * [b_](https://github.com/azproduction/b_ "React BEM class name formatter") - A React class name formatter. 123 | 124 | 125 | # License 126 | 127 | React BEM is free--as in BSD. Hack your heart out, hackers. 128 | -------------------------------------------------------------------------------- /src/react-bem.js: -------------------------------------------------------------------------------- 1 | var BEMTransformer = function() { 2 | this.get_child_modifiers = function(child) { 3 | if (typeof child === "string" || !child.props.modifiers) return []; 4 | return child.props.modifiers.split(" "); 5 | }; 6 | 7 | this.get_child_bem_element = function(child) { 8 | if (typeof child.props.bem_element !== "string") return null; 9 | return child.props.bem_element; 10 | }; 11 | 12 | this.get_child_tag_name = function(child) { 13 | var name = (typeof child.type === "string") 14 | ? child.type 15 | : child.type.displayName; 16 | 17 | return name.toLowerCase().replace("reactdom", ""); 18 | }; 19 | 20 | this.get_child_bem_role = function(child) { 21 | if (typeof child.props.role !== "string") return null; 22 | return child.props.role; 23 | }; 24 | 25 | this.get_child_element = function(child) { 26 | if (typeof child === "string") return child; 27 | return this.get_child_bem_element(child) 28 | || this.get_child_bem_role(child) 29 | || this.get_child_tag_name(child) 30 | || ""; 31 | }; 32 | 33 | this.build_bem_class = function(child, blocks, block_modifiers, translate) { 34 | var B = blocks, 35 | BM = block_modifiers, 36 | E = this.get_child_element(child), 37 | EM = this.get_child_modifiers(child); 38 | 39 | var classes = []; 40 | for (var b in B) { 41 | b = B[b]; 42 | 43 | classes.push(b + "__" + E); 44 | 45 | for (var em in EM) { 46 | em = EM[em]; 47 | classes.push(b + "__" + E + "--" + em); 48 | } 49 | 50 | for (var bm in BM) { 51 | bm = BM[bm]; 52 | 53 | classes.push(b + "--" + bm + "__" + E); 54 | for (var em in EM) { 55 | em = EM[em]; 56 | classes.push(b + "--" + bm + "__" + E + "--" + em); 57 | } 58 | } 59 | } 60 | 61 | var bem_classes = classes.join(" "); 62 | return (typeof translate === "function") 63 | ? translate(bem_classes) 64 | : bem_classes; 65 | }; 66 | 67 | this.transform_props = function(props, blocks, block_modifiers, translate) { 68 | var changes = {}; 69 | 70 | if (typeof props.children === "object") { 71 | var children = React.Children.toArray(props.children), 72 | 73 | transformed_children = children.map(function (child) { 74 | return this.transform(child, blocks, block_modifiers, translate); 75 | }.bind(this)), 76 | 77 | is_untransformed = function (transformed, i) { 78 | return transformed !== children[i]; 79 | }; 80 | 81 | if (transformed_children.some(is_untransformed)) { 82 | changes.children = transformed_children; 83 | } 84 | } 85 | 86 | for (var key in Object.keys(props)) { 87 | if (key === "children") continue; 88 | 89 | var child = props[key]; 90 | if (!React.isValidElement(child)) continue; 91 | 92 | var new_child = this.transform(child, blocks, block_modifiers, translate); 93 | if (new_child === child) continue; 94 | changes[key] = new_child; 95 | } 96 | 97 | return changes; 98 | }; 99 | 100 | this.transform = function(element, blocks, block_modifiers, translate) { 101 | if (typeof element !== "object") return element; 102 | 103 | var changes = 104 | this.transform_props(element.props, blocks, block_modifiers, translate); 105 | 106 | var suffix_classes = element.props.className 107 | ? element.props.className 108 | : ""; 109 | 110 | changes.className = "" 111 | + this.build_bem_class(element, blocks, block_modifiers, translate) 112 | + " " 113 | + suffix_classes; 114 | 115 | var children = changes.children || element.props.children; 116 | return (Object.keys(changes).length === 0) 117 | ? element 118 | : React.cloneElement(element, changes, children); 119 | }.bind(this); 120 | }; 121 | 122 | var transformer = new BEMTransformer(); 123 | 124 | ReactBEM = { 125 | getInitialState: function() { 126 | this.bem_blocks = this.bem_blocks || []; 127 | this.bem_block_modifiers = this.bem_block_modifiers || []; 128 | return null; 129 | }, 130 | 131 | render: function() { 132 | return transformer.transform( 133 | this.bem_render(), 134 | this.bem_blocks, 135 | this.bem_block_modifiers, 136 | this.bem_translate_class 137 | ); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /test/react-bem.test.js: -------------------------------------------------------------------------------- 1 | describe("Completer Integration Tests", function() { 2 | 3 | var TestUtils = React.addons.TestUtils; 4 | 5 | var connect = function(Component) { 6 | return TestUtils.renderIntoDocument(React.createElement(Component)); 7 | }; 8 | 9 | var unmount_component = function(component) { 10 | React.unmountComponentAtNode(component.getDOMNode().parentNode); 11 | }; 12 | 13 | var getTypeOfDomNode = function (el) { 14 | var reactComponent = el._reactInternalComponent._currentElement; 15 | var name = ''; 16 | if (typeof reactComponent.type === "string") { 17 | name = reactComponent.type 18 | } else { 19 | name = reactComponent.type.displayName 20 | } 21 | 22 | return name; 23 | } 24 | 25 | chai.assert.bem_exists = function(component, bem_class_name, tag_name) { 26 | var el = TestUtils.findRenderedDOMComponentWithClass( 27 | component, 28 | bem_class_name 29 | ); 30 | 31 | chai.assert.equal(getTypeOfDomNode(el), tag_name); 32 | }; 33 | 34 | chai.assert.bem_not_exists = function(component, bem_class_name) { 35 | var els = TestUtils.scryRenderedDOMComponentsWithClass( 36 | component, 37 | bem_class_name 38 | ); 39 | chai.assert.equal(els.length, 0); 40 | }; 41 | 42 | describe("Single Element BEM Formation Tests", function() { 43 | it("Should formulate (B)lock and (E)lement", function() { 44 | var SimpleComponent = React.createClass({ 45 | mixins: [ReactBEM], 46 | 47 | bem_blocks: ["widget"], 48 | 49 | bem_render: function() { 50 | return React.DOM.header(null); 51 | } 52 | }); 53 | 54 | var component = connect(SimpleComponent); 55 | chai.assert.bem_exists(component, "widget__header", "header"); 56 | unmount_component(component); 57 | }); 58 | 59 | it("Should not modify existing classes", function() { 60 | var SimpleComponent = React.createClass({ 61 | mixins: [ReactBEM], 62 | 63 | bem_blocks: ["widget"], 64 | 65 | bem_render: function() { 66 | return React.DOM.header({className: "no-overwrite"}); 67 | } 68 | }); 69 | 70 | var component = connect(SimpleComponent); 71 | chai.assert.bem_exists(component, "widget__header", "header"); 72 | chai.assert.bem_exists(component, "no-overwrite", "header"); 73 | unmount_component(component); 74 | }); 75 | 76 | it("Should overwrite tagname with role", function() { 77 | var SimpleComponent = React.createClass({ 78 | mixins: [ReactBEM], 79 | 80 | bem_blocks: ["widget"], 81 | 82 | bem_render: function() { 83 | return React.DOM.header({ 84 | className: "no-overwrite", 85 | role: "title" 86 | }); 87 | } 88 | }); 89 | 90 | var component = connect(SimpleComponent); 91 | chai.assert.bem_exists(component, "widget__title", "header"); 92 | chai.assert.bem_not_exists(component, "widget__header"); 93 | chai.assert.bem_exists(component, "no-overwrite", "header"); 94 | unmount_component(component); 95 | }); 96 | 97 | it("should overwrite tagname with bem_element", function() { 98 | var SimpleComponent = React.createClass({ 99 | mixins: [ReactBEM], 100 | 101 | bem_blocks: ["widget"], 102 | 103 | bem_render: function() { 104 | return React.DOM.header({ 105 | className: "no-overwrite", 106 | bem_element: "caption" 107 | }); 108 | } 109 | }); 110 | 111 | var component = connect(SimpleComponent); 112 | chai.assert.bem_exists(component, "widget__caption", "header"); 113 | chai.assert.bem_not_exists(component, "widget__header"); 114 | chai.assert.bem_exists(component, "no-overwrite", "header"); 115 | unmount_component(component); 116 | }); 117 | 118 | it("Should overwrite tagname and role with bem_element", function() { 119 | var SimpleComponent = React.createClass({ 120 | mixins: [ReactBEM], 121 | 122 | bem_blocks: ["widget"], 123 | 124 | bem_render: function() { 125 | return React.DOM.header({ 126 | className: "no-overwrite", 127 | role: "title", 128 | bem_element: "caption" 129 | }); 130 | } 131 | }); 132 | 133 | var component = connect(SimpleComponent); 134 | chai.assert.bem_exists(component, "widget__caption", "header"); 135 | chai.assert.bem_not_exists(component, "widget__header"); 136 | chai.assert.bem_not_exists(component, "widget__title"); 137 | chai.assert.bem_exists(component, "no-overwrite", "header"); 138 | unmount_component(component); 139 | }); 140 | 141 | it("Should formulate (B)lock, Block (M)odifier, and (E)lement", function() { 142 | var SimpleComponent = React.createClass({ 143 | mixins: [ReactBEM], 144 | 145 | bem_blocks: ["widget"], 146 | bem_block_modifiers: ["christmas"], 147 | 148 | bem_render: function() { 149 | return React.DOM.header({className: "no-overwrite"}); 150 | } 151 | }); 152 | 153 | var component = connect(SimpleComponent); 154 | chai.assert.bem_exists(component, "widget__header", "header"); 155 | chai.assert.bem_exists(component, "widget--christmas__header", "header"); 156 | chai.assert.bem_exists(component, "no-overwrite", "header"); 157 | unmount_component(component); 158 | }); 159 | 160 | it("Should formulate (B)lock, (E)lement, and Element (M)odifier", function() { 161 | var SimpleComponent = React.createClass({ 162 | mixins: [ReactBEM], 163 | 164 | bem_blocks: ["widget"], 165 | 166 | bem_render: function() { 167 | return React.DOM.header({ 168 | className: "no-overwrite", 169 | modifiers: "blinking" 170 | }); 171 | } 172 | }); 173 | 174 | var component = connect(SimpleComponent); 175 | chai.assert.bem_exists(component, "widget__header", "header"); 176 | chai.assert.bem_exists(component, "widget__header--blinking", "header"); 177 | chai.assert.bem_exists(component, "no-overwrite", "header"); 178 | unmount_component(component); 179 | }); 180 | 181 | it("Should formulate (B)lock, Block (M)odifier, (E)lement, and Element (M)odifier", function() { 182 | var SimpleComponent = React.createClass({ 183 | mixins: [ReactBEM], 184 | 185 | bem_blocks: ["widget"], 186 | bem_block_modifiers: ["christmas"], 187 | 188 | bem_render: function() { 189 | return React.DOM.header({ 190 | className: "no-overwrite", 191 | modifiers: "blinking" 192 | }); 193 | } 194 | }); 195 | 196 | var component = connect(SimpleComponent); 197 | chai.assert.bem_exists(component, "widget__header", "header"); 198 | chai.assert.bem_exists(component, "widget--christmas__header", "header"); 199 | chai.assert.bem_exists(component, "widget__header--blinking", "header"); 200 | chai.assert.bem_exists(component, "widget--christmas__header--blinking", "header"); 201 | chai.assert.bem_exists(component, "no-overwrite", "header"); 202 | unmount_component(component); 203 | }); 204 | 205 | it("Should formulate multiple Blocks", function() { 206 | var SimpleComponent = React.createClass({ 207 | mixins: [ReactBEM], 208 | 209 | bem_blocks: ["widget", "slider"], 210 | 211 | bem_render: function() { 212 | return React.DOM.header({className: "no-overwrite"}); 213 | } 214 | }); 215 | 216 | var component = connect(SimpleComponent); 217 | chai.assert.bem_exists(component, "widget__header", "header"); 218 | chai.assert.bem_exists(component, "slider__header", "header"); 219 | chai.assert.bem_exists(component, "no-overwrite", "header"); 220 | unmount_component(component); 221 | }); 222 | 223 | it("Should formulate multiple Block Modifiers", function() { 224 | var SimpleComponent = React.createClass({ 225 | mixins: [ReactBEM], 226 | 227 | bem_blocks: ["widget"], 228 | bem_block_modifiers: ["christmas", "thanksgiving"], 229 | 230 | bem_render: function() { 231 | return React.DOM.header({className: "no-overwrite"}); 232 | } 233 | }); 234 | 235 | var component = connect(SimpleComponent); 236 | chai.assert.bem_exists(component, "widget__header", "header"); 237 | chai.assert.bem_exists(component, "widget--christmas__header", "header"); 238 | chai.assert.bem_exists(component, "widget--thanksgiving__header", "header"); 239 | chai.assert.bem_exists(component, "no-overwrite", "header"); 240 | unmount_component(component); 241 | }); 242 | 243 | it("Should formulate multiple Element Modifiers", function() { 244 | var SimpleComponent = React.createClass({ 245 | mixins: [ReactBEM], 246 | 247 | bem_blocks: ["widget"], 248 | 249 | bem_render: function() { 250 | return React.DOM.header({ 251 | className: "no-overwrite", 252 | modifiers: "blinking highlight" 253 | }); 254 | } 255 | }); 256 | 257 | var component = connect(SimpleComponent); 258 | chai.assert.bem_exists(component, "widget__header", "header"); 259 | chai.assert.bem_exists(component, "widget__header--blinking", "header"); 260 | chai.assert.bem_exists(component, "widget__header--highlight", "header"); 261 | chai.assert.bem_exists(component, "no-overwrite", "header"); 262 | unmount_component(component); 263 | }); 264 | 265 | it("Should formulate multiple Blocks, Block Modifiers, and Element Modifiers", function() { 266 | var SimpleComponent = React.createClass({ 267 | mixins: [ReactBEM], 268 | 269 | bem_blocks: ["widget", "slider"], 270 | bem_block_modifiers: ["christmas", "thanksgiving"], 271 | 272 | bem_render: function() { 273 | return React.DOM.header({ 274 | className: "no-overwrite", 275 | modifiers: "blinking highlight" 276 | }); 277 | } 278 | }); 279 | 280 | var component = connect(SimpleComponent); 281 | 282 | chai.assert.bem_exists(component, "widget__header", "header"); 283 | chai.assert.bem_exists(component, "widget--christmas__header", "header"); 284 | chai.assert.bem_exists(component, "widget--thanksgiving__header", "header"); 285 | chai.assert.bem_exists(component, "widget__header--blinking", "header"); 286 | chai.assert.bem_exists(component, "widget__header--highlight", "header"); 287 | chai.assert.bem_exists(component, "widget--christmas__header--blinking", "header"); 288 | chai.assert.bem_exists(component, "widget--christmas__header--highlight", "header"); 289 | chai.assert.bem_exists(component, "widget--thanksgiving__header--blinking", "header"); 290 | chai.assert.bem_exists(component, "widget--thanksgiving__header--highlight", "header"); 291 | chai.assert.bem_exists(component, "widget__header", "header"); 292 | 293 | chai.assert.bem_exists(component, "slider--christmas__header", "header"); 294 | chai.assert.bem_exists(component, "slider--thanksgiving__header", "header"); 295 | chai.assert.bem_exists(component, "slider__header--blinking", "header"); 296 | chai.assert.bem_exists(component, "slider__header--highlight", "header"); 297 | chai.assert.bem_exists(component, "slider--christmas__header--blinking", "header"); 298 | chai.assert.bem_exists(component, "slider--christmas__header--highlight", "header"); 299 | chai.assert.bem_exists(component, "slider--thanksgiving__header--blinking", "header"); 300 | chai.assert.bem_exists(component, "slider--thanksgiving__header--highlight", "header"); 301 | 302 | chai.assert.bem_exists(component, "no-overwrite", "header"); 303 | 304 | unmount_component(component); 305 | }); 306 | 307 | it("Should formulate multiple Blocks, Block Modifiers, and Element Modifiers with proper element name", function() { 308 | var SimpleComponent = React.createClass({ 309 | mixins: [ReactBEM], 310 | 311 | bem_blocks: ["widget", "slider"], 312 | bem_block_modifiers: ["christmas", "thanksgiving"], 313 | 314 | bem_render: function() { 315 | return React.DOM.header({ 316 | className: "no-overwrite", 317 | modifiers: "blinking highlight", 318 | role: "title", 319 | bem_element: "caption" 320 | }); 321 | } 322 | }); 323 | 324 | var component = connect(SimpleComponent); 325 | 326 | chai.assert.bem_exists(component, "widget__caption", "header"); 327 | chai.assert.bem_exists(component, "widget--christmas__caption", "header"); 328 | chai.assert.bem_exists(component, "widget--thanksgiving__caption", "header"); 329 | chai.assert.bem_exists(component, "widget__caption--blinking", "header"); 330 | chai.assert.bem_exists(component, "widget__caption--highlight", "header"); 331 | chai.assert.bem_exists(component, "widget--christmas__caption--blinking", "header"); 332 | chai.assert.bem_exists(component, "widget--christmas__caption--highlight", "header"); 333 | chai.assert.bem_exists(component, "widget--thanksgiving__caption--blinking", "header"); 334 | chai.assert.bem_exists(component, "widget--thanksgiving__caption--highlight", "header"); 335 | chai.assert.bem_exists(component, "widget__caption", "header"); 336 | 337 | chai.assert.bem_exists(component, "slider--christmas__caption", "header"); 338 | chai.assert.bem_exists(component, "slider--thanksgiving__caption", "header"); 339 | chai.assert.bem_exists(component, "slider__caption--blinking", "header"); 340 | chai.assert.bem_exists(component, "slider__caption--highlight", "header"); 341 | chai.assert.bem_exists(component, "slider--christmas__caption--blinking", "header"); 342 | chai.assert.bem_exists(component, "slider--christmas__caption--highlight", "header"); 343 | chai.assert.bem_exists(component, "slider--thanksgiving__caption--blinking", "header"); 344 | chai.assert.bem_exists(component, "slider--thanksgiving__caption--highlight", "header"); 345 | 346 | chai.assert.bem_exists(component, "no-overwrite", "header"); 347 | 348 | unmount_component(component); 349 | }); 350 | }); 351 | 352 | describe("Nested Element BEM Formation Tests", function() { 353 | it("Should nest (B)lock and (E)lement", function() { 354 | var SimpleComponent = React.createClass({ 355 | mixins: [ReactBEM], 356 | 357 | bem_blocks: ["widget"], 358 | 359 | bem_render: function() { 360 | return React.DOM.header(null, 361 | React.DOM.h1(null, 362 | React.DOM.span(null, 363 | "HEADER:" 364 | ), 365 | " This is the Header" 366 | ) 367 | ); 368 | } 369 | }); 370 | 371 | var component = connect(SimpleComponent); 372 | chai.assert.bem_exists(component, "widget__header", "header"); 373 | chai.assert.bem_exists(component, "widget__h1", "h1"); 374 | chai.assert.bem_exists(component, "widget__span", "span"); 375 | unmount_component(component); 376 | }); 377 | 378 | it("Should properly nest element name", function() { 379 | var SimpleComponent = React.createClass({ 380 | mixins: [ReactBEM], 381 | 382 | bem_blocks: ["widget"], 383 | 384 | bem_render: function() { 385 | return React.DOM.header({role: "leader", bem_element: "caption"}, 386 | React.DOM.h1({role: "title"}, 387 | React.DOM.span(null, 388 | "HEADER:" 389 | ), 390 | " This is the Header" 391 | ) 392 | ); 393 | } 394 | }); 395 | 396 | var component = connect(SimpleComponent); 397 | chai.assert.bem_exists(component, "widget__caption", "header"); 398 | chai.assert.bem_exists(component, "widget__title", "h1"); 399 | chai.assert.bem_exists(component, "widget__span", "span"); 400 | unmount_component(component); 401 | }); 402 | 403 | it("Should nest multiple blocks, block modifiers, and element modifiers", function() { 404 | var SimpleComponent = React.createClass({ 405 | mixins: [ReactBEM], 406 | 407 | bem_blocks: ["widget", "slider"], 408 | 409 | bem_render: function() { 410 | return React.DOM.header({role: "leader", bem_element: "caption"}, 411 | React.DOM.h1({role: "title", modifiers: "blinking highlight"}, 412 | React.DOM.span(null, 413 | "HEADER:" 414 | ), 415 | " This is the Header" 416 | ) 417 | ); 418 | } 419 | }); 420 | 421 | var component = connect(SimpleComponent); 422 | chai.assert.bem_exists(component, "widget__caption", "header"); 423 | chai.assert.bem_exists(component, "slider__caption", "header"); 424 | chai.assert.bem_exists(component, "widget__title", "h1"); 425 | chai.assert.bem_exists(component, "widget__title--blinking", "h1"); 426 | chai.assert.bem_exists(component, "slider__title", "h1"); 427 | chai.assert.bem_exists(component, "slider__title--blinking", "h1"); 428 | chai.assert.bem_exists(component, "widget__span", "span"); 429 | chai.assert.bem_exists(component, "slider__span", "span"); 430 | unmount_component(component); 431 | }); 432 | }); 433 | 434 | describe("Translation Tests", function() { 435 | it("Should translate class name", function() { 436 | var SimpleComponent = React.createClass({ 437 | mixins: [ReactBEM], 438 | 439 | bem_blocks: ["widget", "slider"], 440 | 441 | bem_translate_class: function(bem_classes) { 442 | return bem_classes.split(" ").map(function(class_name) { 443 | return "translated-" + class_name; 444 | }).join(" "); 445 | }, 446 | 447 | bem_render: function() { 448 | return React.DOM.header({className: "no-overwrite"}); 449 | } 450 | }); 451 | 452 | var component = connect(SimpleComponent); 453 | chai.assert.bem_not_exists(component, "widget__header", "header"); 454 | chai.assert.bem_not_exists(component, "slider__header", "header"); 455 | chai.assert.bem_exists(component, "translated-widget__header", "header"); 456 | chai.assert.bem_exists(component, "translated-slider__header", "header"); 457 | chai.assert.bem_exists(component, "no-overwrite", "header"); 458 | unmount_component(component); 459 | }); 460 | }); 461 | 462 | describe("Order Tests", function() { 463 | it("Should order classes properly for overrides to work", function() { 464 | var SimpleComponent = React.createClass({ 465 | mixins: [ReactBEM], 466 | 467 | bem_blocks: ["widget"], 468 | bem_block_modifiers: ["christmas"], 469 | 470 | bem_render: function() { 471 | return React.DOM.header({ 472 | modifiers: "blinking" 473 | }); 474 | } 475 | }); 476 | 477 | var component = connect(SimpleComponent), 478 | els = TestUtils.scryRenderedDOMComponentsWithTag(component, "header"), 479 | header = ReactDOM.findDOMNode(els[0]), 480 | classList = header.classList; 481 | 482 | chai.assert.equal(header.classList.length, 4); 483 | chai.assert.equal(header.classList[0], "widget__header"); 484 | chai.assert.equal(header.classList[1], "widget__header--blinking"); 485 | chai.assert.equal(header.classList[2], "widget--christmas__header"); 486 | chai.assert.equal(header.classList[3], "widget--christmas__header--blinking"); 487 | 488 | unmount_component(component); 489 | }); 490 | }); 491 | }); 492 | 493 | --------------------------------------------------------------------------------