├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── contrib ├── cakejs.png ├── di.md ├── dist.js ├── loop.md └── namespaces.png ├── examples ├── browser │ └── index.html ├── counter │ ├── .babelrc │ ├── app │ │ ├── index.html │ │ └── index.js │ ├── package.json │ └── webpack.config.js ├── nesting │ ├── .babelrc │ ├── app │ │ ├── index.html │ │ ├── index.js │ │ ├── record.js │ │ └── store.js │ ├── package.json │ └── webpack.config.js ├── routes │ ├── .babelrc │ ├── app │ │ ├── index.html │ │ └── index.js │ ├── package.json │ └── webpack.config.js ├── select │ ├── .babelrc │ ├── app │ │ ├── index.html │ │ └── index.js │ ├── package.json │ └── webpack.config.js └── todo │ ├── .babelrc │ ├── app │ ├── index.css │ ├── index.html │ └── index.js │ ├── package.json │ └── webpack.config.js ├── index.js ├── lib ├── bvd │ ├── examples │ │ ├── countdown │ │ │ ├── .babelrc │ │ │ ├── app │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ │ ├── package.json │ │ │ └── webpack.config.js │ │ └── misc │ │ │ └── inspect.gif │ ├── index.js │ ├── lib │ │ ├── diff.js │ │ ├── h.js │ │ └── patch.js │ └── tests │ │ ├── fixtures │ │ ├── basic.js │ │ ├── nested.js │ │ ├── simple.js │ │ ├── svg.js │ │ └── textnodes.js │ │ └── mocha-tests │ │ ├── attributes.test.js │ │ ├── diff.test.js │ │ ├── events.test.js │ │ ├── h.test.js │ │ ├── patch.test.js │ │ ├── render.test.js │ │ ├── svg.test.js │ │ └── unrefobjects.test.js ├── cake.js ├── caramel.js ├── container.js ├── cream.js ├── dom.js ├── index.js ├── mixer.js ├── recipes │ ├── fn.js │ └── rafcaf.js └── zefir.js ├── package.json └── tests ├── cake.test.js ├── caramel.test.js ├── container.test.js ├── cream-observers.test.js ├── cream.test.js ├── create.test.js ├── mixer.test.js └── zefir.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha" : true 5 | }, 6 | "extends": "eslint:recommended", 7 | "globals" : { 8 | "describe" : true, 9 | "it" : true, 10 | "window" : true, 11 | "document" : true 12 | }, 13 | "rules": { 14 | "indent": [ 15 | "error", 16 | 2, { "SwitchCase" : 0 } 17 | ], 18 | "linebreak-style": [ 19 | "error", 20 | "unix" 21 | ], 22 | "quotes": [ 23 | "error", 24 | "single" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | package-lock.json 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | build 30 | dist 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | node_modules 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | install: 5 | - npm i 6 | script: 7 | - npm run cov 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Svetlana Linuxenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CakeJs](https://notabug.org/hofuku/cakejs2/raw/master/contrib/cakejs.png)](https://notabug.org/hofuku/cakejs2/raw/master/contrib/cakejs.png) 2 | 3 | `CakeJS2` is a lightweight front-end framework which borrows most awesome features from others. 4 | 5 | ### New features 6 | 7 | * Virtual-dom merged into a source tree 8 | * No dependencies 9 | * SVG support (partial) 10 | 11 | ### Features 12 | 13 | * All in one 14 | * [Dependency management](./contrib/di.md) 15 | * [Live rendering](./contrib/loop.md) (+ [virtual dom](https://github.com/linuxenko/basic-virtual-dom)) 16 | * Good [performance](https://15lyfromsaturn.github.io/js-repaint-perfs/cakejs/index.html) 17 | * JSX support 18 | * Small error stack trace (?) 19 | * Small size and codebase ([about 23kb](https://unpkg.com/cakejs2@latest/dist/cake.min.js)) 20 | * ES5 support (Yeah!) 21 | * Extremely easy to learn 22 | 23 | **For example: a candle counter recipe:** 24 | 25 | ```js 26 | create().route('/', 'counter'); 27 | 28 | Cream.extend({ 29 | _namespace : 'counter', 30 | 31 | candles : 0, 32 | 33 | increment : function() { 34 | this.set('candles', this.candles + 1); 35 | }, 36 | 37 | render : function() { 38 | return h('button', { onClick : this.increment }, 'Candles on the Cake: ' + this.candles); 39 | } 40 | }); 41 | ``` 42 | 43 | Hyperscript is an requirement: 44 | 45 | ``` 46 | /** @jsx h */ 47 | ``` 48 | 49 | **More examples** 50 | 51 | Live demos: 52 | 53 | * [todomvc](http://codepen.io/linuxenko/pen/jVRwLL) 54 | * [js-repaint-perf](https://15lyfromsaturn.github.io/js-repaint-perfs/cakejs/index.html) 55 | 56 | Check out [examples](./examples) folder. 57 | 58 | **Installation** 59 | 60 | To install latest version, the repository archive could be used: 61 | 62 | ``` 63 | https://notabug.org/hofuku/cakejs2/archive/master.zip 64 | ``` 65 | 66 | CDN 67 | 68 | None yet 69 | 70 | Deprecated: 71 | ``` 72 | https://unpkg.com/cakejs2@latest/dist/cake.min.js 73 | ``` 74 | 75 | **API** 76 | 77 | * `h` 78 | * [next](/contrib/loop.md) 79 | * [register](/contrib/di.md) 80 | * [unregister](/contrib/di.md) 81 | * [inject](/contrib/di.md) 82 | * `create` 83 | * `Cream` 84 | 85 | `create` options: 86 | 87 | ```js 88 | create({ 89 | element : document.body // by default 90 | elementClass : cake 91 | elementId : cake 92 | createRoot : false // do not create root node, use render's 93 | 94 | ``` 95 | 96 | `route`: 97 | 98 | ```js 99 | create().route( 100 | '/posts/:id/post', // URL pattern, also available "*" pattern 101 | 'home' // Namespace of the component 102 | ); 103 | 104 | ``` 105 | 106 | **Namespaces** 107 | 108 | [![Namespaces](https://raw.githubusercontent.com/linuxenko/cakejs2/master/contrib/namespaces.png)](http://i.imgur.com/USVdVuM.gifv) 109 | 110 | *Cream* is a base component of a cake. 111 | 112 | Functions: 113 | 114 | * `init` 115 | * `willTransition` 116 | * `didTransition` 117 | * `render` 118 | 119 | Options: 120 | 121 | * `_namespsace` - object's namespace 122 | * `_after` - DI after 123 | 124 | Zefir: 125 | 126 | * `props` - routing options ( `/:id/` for an instance became `props.id` ) 127 | * `params` - params eg `?iam=param` bacame `params.iam` 128 | 129 | Sugar: 130 | 131 | * `observes` 132 | * `property` - computed property 133 | 134 | `observes` creates observer function 135 | 136 | ```js 137 | dataWatcher : function() { .... }.observes('posts', /^store/) 138 | ``` 139 | 140 | #### History 141 | 142 | * [2022] Forked to became a general development version for now. [notabug](https://notabug.org/hofuku/cakejs2) 143 | * [2016] `Cakejs2` is a second generation of the `cakejs` framework [origin](https://githubg.com/linuxenko/cakejs2) 144 | * [2012] First version of `cakejs` were made and [published](https://github.com/linuxenko/cakejs2/tree/outdated-v1) in 2014th. 145 | 146 | #### License 147 | 148 | MIT License 149 | 150 | Copyright (c) 2016 Svetlana Linuxenko 151 | 152 | -------------------------------------------------------------------------------- /contrib/cakejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxenko/cakejs2/3d2323a3f7b3737932b95bcb031df1bc966bd1f8/contrib/cakejs.png -------------------------------------------------------------------------------- /contrib/di.md: -------------------------------------------------------------------------------- 1 | **Dependency Management** 2 | 3 | Every registered object has its own `_container` and namespace. 4 | How to place `Cream` into `container`: 5 | 6 | * By specifying `_namespace` property inside of an object. 7 | * Using `register` function, for example: `register('routes.home', homeCream)`. 8 | 9 | To manage dependencies priority, the `_after` option could be used: 10 | 11 | Using an appropriate argument list for `register()` function: 12 | 13 | ```js 14 | var Home = Cream.extend({}); 15 | 16 | register('routes.home', Home, 'mystore'); 17 | 18 | ``` 19 | 20 | or just explicitly specifying using the `_after` keyword inside of a `Cream`: 21 | 22 | ```js 23 | Cream.extend({ 24 | _namespace : 'routes.home', 25 | _after : 'mystore' 26 | }); 27 | 28 | ``` 29 | 30 | **Dependency Injection** 31 | 32 | Any registered object or its property can be easily injected using `inject` function: 33 | 34 | ```js 35 | Cream.extend({ 36 | store : inject('mystore.records') 37 | }); 38 | 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /contrib/dist.js: -------------------------------------------------------------------------------- 1 | window.cake = require('../'); 2 | -------------------------------------------------------------------------------- /contrib/loop.md: -------------------------------------------------------------------------------- 1 | **Run loop** 2 | 3 | `CakeJS2` renders changes using `run loop` iterations in case namesapaces or 4 | properties were changed. 5 | 6 | `Run loop` is a `mixer` component of the `cake`'s internals, it uses brower's implementations 7 | e.g `requestAnimationFrame` or implements its own using plain old `settimeout` function. 8 | 9 | **Explicit actions** 10 | 11 | Any calculations, anything that application do, is hidden from the render loop, 12 | until you call `set`. Only then it will rerender `DOM` when next iteration comes.. 13 | -------------------------------------------------------------------------------- /contrib/namespaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxenko/cakejs2/3d2323a3f7b3737932b95bcb031df1bc966bd1f8/contrib/namespaces.png -------------------------------------------------------------------------------- /examples/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/counter/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /examples/counter/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/counter/app/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import './index.html'; 3 | import {h, Cream, create, _container } from '../../../'; 4 | 5 | create({ 6 | element : document.body, 7 | elementId : 'application', 8 | elementClass : 'cake-application' 9 | }) 10 | .route('/', 'counter'); 11 | 12 | Cream.extend({ 13 | _namespace : 'counter', 14 | 15 | clicked : 0, 16 | 17 | increment() { 18 | this.set('clicked', this.get('clicked') + 1); 19 | }, 20 | 21 | render() { 22 | return ( 23 | 24 | ); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /examples/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/counter/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new webpack.NoErrorsPlugin() 44 | 45 | ], 46 | stats: { 47 | colors: true 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /examples/nesting/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /examples/nesting/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/nesting/app/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import './index.html'; 3 | import {h, Cream, create, inject, _container } from '../../../'; 4 | import Store from './store'; 5 | import Record from './record'; 6 | 7 | var c =create({ 8 | element : document.body, 9 | elementId : 'application', 10 | elementClass : 'cake-application' 11 | }) 12 | .route('/', 'records.index') 13 | 14 | Cream.extend({ 15 | _namespace: 'records.index', 16 | 17 | render: function() { 18 | return ( 19 |
20 |

List of Records

21 | {Store.store.map(function(r, i) { 22 | return ( 23 | 24 | {r.content} 25 | 26 | ); 27 | }) 28 | } 29 |
30 | ); 31 | } 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /examples/nesting/app/record.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import {h, Cream} from '../../../'; 3 | 4 | export default Cream.extend({ 5 | render: function() { 6 | return ( 7 |
8 |

{this.props.title}

9 |
{this.props.children}
10 |
11 | ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/nesting/app/store.js: -------------------------------------------------------------------------------- 1 | import {Cream} from '../../../'; 2 | 3 | export default Cream.extend({ 4 | byId: function(id) { 5 | return this.store[id]; 6 | }, 7 | 8 | store: [ 9 | { title: 'First record', content: 'First record content' }, 10 | { title: 'Second record', content: 'Second record content' }, 11 | { title: 'Third record', content: 'Third record content' } 12 | ] 13 | }); 14 | -------------------------------------------------------------------------------- /examples/nesting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/nesting/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new webpack.NoErrorsPlugin() 44 | 45 | ], 46 | stats: { 47 | colors: true 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /examples/routes/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /examples/routes/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/routes/app/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import './index.html'; 3 | import {h, Cream, create, inject, _container } from '../../../'; 4 | 5 | var c =create({ 6 | element : document.body, 7 | elementId : 'application', 8 | elementClass : 'cake-application' 9 | }) 10 | .route('/', 'records.index') 11 | .route('/:id', 'records.record'); 12 | 13 | Cream.extend({ 14 | _namespace : 'records.data', 15 | store : [ 16 | { title : 'First record', content : 'First record content' }, 17 | { title : 'Second record', content : 'Second record content' } 18 | ] 19 | }); 20 | 21 | function appWrapper(children) { 22 | return (
23 |

Routes example

24 | { children } 25 |
); 26 | } 27 | 28 | Cream.extend({ 29 | _namespace : 'records.record', 30 | 31 | store : inject('records.data.store'), 32 | 33 | record : function() { 34 | return this.get('store.' + this.get('props.id')); 35 | }.property(), 36 | 37 | component() { 38 | return (
39 | { this.get('record').title } 40 |

{ this.get('record').content }

41 | back 42 |
); 43 | }, 44 | 45 | render() { 46 | return appWrapper(this.component()); 47 | } 48 | 49 | }); 50 | 51 | Cream.extend({ 52 | _namespace : 'records.index', 53 | 54 | store : inject('records.data.store'), 55 | 56 | records : function() { 57 | return this.get('store').map(function(record, i) { 58 | record.idx = i; 59 | 60 | return record; 61 | }); 62 | }.property(), 63 | 64 | component() { 65 | return ( 66 | 67 | { this.get('records').map(function(record) { 68 | return ( 69 | 70 | 73 | 76 | 77 | ); 78 | }) 79 | } 80 |
71 | { record.title } 72 | 74 | view 75 |
81 | ); 82 | }, 83 | render() { 84 | return appWrapper(this.component()); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /examples/routes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/routes/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new webpack.NoErrorsPlugin() 44 | 45 | ], 46 | stats: { 47 | colors: true 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /examples/select/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /examples/select/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/select/app/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import './index.html'; 3 | import { h, Cream, create, _container } from '../../../'; 4 | 5 | create({ 6 | element: document.body, 7 | elementId: 'application', 8 | elementClass: 'cake-application' 9 | }) 10 | .route('/', 'counter'); 11 | 12 | Cream.extend({ 13 | _namespace: 'counter', 14 | 15 | selected: 'None', 16 | data: [{ 17 | id: 1, 18 | text: "Bella" 19 | }, { 20 | id: 2, 21 | text: "Kitty" 22 | }, { 23 | id: 3, 24 | text: "Loki" 25 | }, { 26 | id: 4, 27 | text: "Milo" 28 | }, { 29 | id: 5, 30 | text: "Missy" 31 | }], 32 | 33 | changeSelect(event) { 34 | this.set('selected', this.get('data').filter(i => i.id == event.target.value)[0].text); 35 | }, 36 | 37 | render() { 38 | return ( 39 |
40 | {this.selected} 41 |
42 | 53 |
54 | ); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /examples/select/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/select/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new webpack.NoErrorsPlugin() 44 | 45 | ], 46 | stats: { 47 | colors: true 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /examples/todo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /examples/todo/app/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * sindresorhus todomvc-css 4 | */ 5 | 6 | html, 7 | body { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | button { 13 | margin: 0; 14 | padding: 0; 15 | border: 0; 16 | background: none; 17 | font-size: 100%; 18 | vertical-align: baseline; 19 | font-family: inherit; 20 | font-weight: inherit; 21 | color: inherit; 22 | -webkit-appearance: none; 23 | appearance: none; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | body { 29 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 30 | line-height: 1.4em; 31 | background: #f5f5f5; 32 | color: #4d4d4d; 33 | min-width: 230px; 34 | max-width: 550px; 35 | margin: 0 auto; 36 | -webkit-font-smoothing: antialiased; 37 | -moz-osx-font-smoothing: grayscale; 38 | font-weight: 300; 39 | } 40 | 41 | :focus { 42 | outline: 0; 43 | } 44 | 45 | .hidden { 46 | display: none; 47 | } 48 | 49 | .todoapp { 50 | background: #fff; 51 | margin: 130px 0 40px 0; 52 | position: relative; 53 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 54 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 55 | } 56 | 57 | .todoapp input::-webkit-input-placeholder { 58 | font-style: italic; 59 | font-weight: 300; 60 | color: #e6e6e6; 61 | } 62 | 63 | .todoapp input::-moz-placeholder { 64 | font-style: italic; 65 | font-weight: 300; 66 | color: #e6e6e6; 67 | } 68 | 69 | .todoapp input::input-placeholder { 70 | font-style: italic; 71 | font-weight: 300; 72 | color: #e6e6e6; 73 | } 74 | 75 | .todoapp h1 { 76 | position: absolute; 77 | top: -155px; 78 | width: 100%; 79 | font-size: 100px; 80 | font-weight: 100; 81 | text-align: center; 82 | color: rgba(175, 47, 47, 0.15); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | text-rendering: optimizeLegibility; 86 | } 87 | 88 | .new-todo, 89 | .edit { 90 | position: relative; 91 | margin: 0; 92 | width: 100%; 93 | font-size: 24px; 94 | font-family: inherit; 95 | font-weight: inherit; 96 | line-height: 1.4em; 97 | border: 0; 98 | color: inherit; 99 | padding: 6px; 100 | border: 1px solid #999; 101 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 102 | box-sizing: border-box; 103 | -webkit-font-smoothing: antialiased; 104 | -moz-osx-font-smoothing: grayscale; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 12px 16px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | word-break: break-all; 201 | padding: 15px 60px 15px 15px; 202 | margin-left: 45px; 203 | display: block; 204 | line-height: 1.2; 205 | transition: color 0.4s; 206 | } 207 | 208 | .todo-list li.completed label { 209 | color: #d9d9d9; 210 | text-decoration: line-through; 211 | } 212 | 213 | .todo-list li .destroy { 214 | display: none; 215 | position: absolute; 216 | top: 0; 217 | right: 10px; 218 | bottom: 0; 219 | width: 40px; 220 | height: 40px; 221 | margin: auto 0; 222 | font-size: 30px; 223 | color: #cc9a9a; 224 | margin-bottom: 11px; 225 | transition: color 0.2s ease-out; 226 | } 227 | 228 | .todo-list li .destroy:hover { 229 | color: #af5b5e; 230 | } 231 | 232 | .todo-list li .destroy:after { 233 | content: '×'; 234 | } 235 | 236 | .todo-list li:hover .destroy { 237 | display: block; 238 | } 239 | 240 | .todo-list li .edit { 241 | display: none; 242 | } 243 | 244 | .todo-list li.editing:last-child { 245 | margin-bottom: -1px; 246 | } 247 | 248 | .footer { 249 | color: #777; 250 | padding: 10px 15px; 251 | height: 20px; 252 | text-align: center; 253 | border-top: 1px solid #e6e6e6; 254 | } 255 | 256 | .footer:before { 257 | content: ''; 258 | position: absolute; 259 | right: 0; 260 | bottom: 0; 261 | left: 0; 262 | height: 50px; 263 | overflow: hidden; 264 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 265 | 0 8px 0 -3px #f6f6f6, 266 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 267 | 0 16px 0 -6px #f6f6f6, 268 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 269 | } 270 | 271 | .todo-count { 272 | float: left; 273 | text-align: left; 274 | } 275 | 276 | .todo-count strong { 277 | font-weight: 300; 278 | } 279 | 280 | .filters { 281 | margin: 0; 282 | padding: 0; 283 | list-style: none; 284 | position: absolute; 285 | right: 0; 286 | left: 0; 287 | } 288 | 289 | .filters li { 290 | display: inline; 291 | } 292 | 293 | .filters li a { 294 | color: inherit; 295 | margin: 3px; 296 | padding: 3px 7px; 297 | text-decoration: none; 298 | border: 1px solid transparent; 299 | border-radius: 3px; 300 | } 301 | 302 | .filters li a:hover { 303 | border-color: rgba(175, 47, 47, 0.1); 304 | } 305 | 306 | .filters li a.selected { 307 | border-color: rgba(175, 47, 47, 0.2); 308 | } 309 | 310 | .clear-completed, 311 | html .clear-completed:active { 312 | float: right; 313 | position: relative; 314 | line-height: 20px; 315 | text-decoration: none; 316 | cursor: pointer; 317 | } 318 | 319 | .clear-completed:hover { 320 | text-decoration: underline; 321 | } 322 | 323 | .info { 324 | margin: 65px auto 0; 325 | color: #bfbfbf; 326 | font-size: 10px; 327 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 328 | text-align: center; 329 | } 330 | 331 | .info p { 332 | line-height: 1; 333 | } 334 | 335 | .info a { 336 | color: inherit; 337 | text-decoration: none; 338 | font-weight: 400; 339 | } 340 | 341 | .info a:hover { 342 | text-decoration: underline; 343 | } 344 | 345 | /* 346 | Hack to remove background from Mobile Safari. 347 | Can't use it globally since it destroys checkboxes in Firefox 348 | */ 349 | @media screen and (-webkit-min-device-pixel-ratio:0) { 350 | .toggle-all, 351 | .todo-list li .toggle { 352 | background: none; 353 | } 354 | 355 | .todo-list li .toggle { 356 | height: 40px; 357 | } 358 | 359 | .toggle-all { 360 | -webkit-transform: rotate(90deg); 361 | transform: rotate(90deg); 362 | -webkit-appearance: none; 363 | appearance: none; 364 | } 365 | } 366 | 367 | @media (max-width: 430px) { 368 | .footer { 369 | height: 50px; 370 | } 371 | 372 | .filters { 373 | bottom: 10px; 374 | } 375 | } -------------------------------------------------------------------------------- /examples/todo/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CakeJS • TodoMVC 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/todo/app/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import './index.html'; 3 | import './index.css'; 4 | import {h, Cream, create, _container } from '../../../'; 5 | 6 | create({ 7 | element : document.body, 8 | createRoot : false 9 | }) 10 | .route('/', 'todomvc') 11 | .route('/:filter', 'todomvc'); 12 | 13 | Cream.extend({ 14 | _namespace : 'todomvc', 15 | 16 | todosStore : [ 17 | { 18 | title : 'Taste Javascript', 19 | complete : true, 20 | }, 21 | { 22 | title : 'Buy a unicorn', 23 | complete : false 24 | } 25 | ], 26 | 27 | completeAll : function(e) { 28 | this.get('todosStore').map((t, i) => 29 | this.set('todosStore.'+ i +'.complete', e.target.checked)); 30 | }, 31 | 32 | clearCompleted : function() { 33 | this.set('todosStore', this.get('todosStore').filter( todo => todo.complete === false )); 34 | }, 35 | 36 | itemsLeft : function() { 37 | return this.get('todosStore').filter( t => t.complete === false ).length; 38 | }.property(), 39 | 40 | completeTodo : function(idx) { 41 | this.set('todosStore.' + idx + '.complete', 42 | !this.get('todosStore.' + idx + '.complete')); 43 | }, 44 | 45 | removeTodo : function(idx) { 46 | this.splice('todosStore', idx, 1); 47 | }, 48 | 49 | insertTodo : function(e) { 50 | if (e.keyCode !== 13 || e.target.value.length < 1) { 51 | return; 52 | } 53 | 54 | this.unshift('todosStore', { title : e.target.value, complete : false }); 55 | e.target.value = ''; 56 | }, 57 | 58 | untoggleEditings : function(e) { 59 | if (e && e.target.tagName !== 'INPUT') { 60 | this.set('todosStore', this.get('todosStore').map( t => { t.editing = false; return t; })); 61 | } 62 | }, 63 | 64 | toggleEditing : function(idx) { 65 | this.untoggleEditings(); 66 | this.set('todosStore.' + idx + '.editing', !this.get('todosStore.' + idx + '.editing')); 67 | }, 68 | 69 | editTodo : function(idx, e) { 70 | if (e.keyCode !== 13 || e.target.value.length < 1) { 71 | return; 72 | } 73 | 74 | this.set('todosStore.' + idx + '.title', e.target.value); 75 | this.set('todosStore.' + idx + '.editing', false); 76 | }, 77 | 78 | todos : function() { 79 | return this.get('todosStore').map((t, i) => { 80 | t.idx = i; 81 | t.classes = []; 82 | if (t.complete) t.classes.push('completed'); 83 | if (t.editing) t.classes.push('editing'); 84 | return t; 85 | }).filter(todo => { 86 | switch(this.get('props.filter')) { 87 | case 'active' : return todo.complete === false; 88 | case 'completed' : return todo.complete === true; 89 | default: return todo; 90 | } 91 | }); 92 | }.property(), 93 | 94 | render() { 95 | return ( 96 |
97 |
98 |

cakejs

99 | 100 |
101 |
102 | 103 | 104 |
    105 | { this.get('todos').map( todo => { 106 | return ( 107 |
  • 108 |
    109 | 111 | 112 | 113 |
    114 | 115 |
  • 116 | ); 117 | })} 118 |
119 |
120 |
121 | { this.get('itemsLeft') } item left 122 | 133 | 134 |
135 |
136 | ); 137 | } 138 | }); 139 | -------------------------------------------------------------------------------- /examples/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/todo/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | }, 40 | { 41 | test : /\.css$/, 42 | loader : 'file?name=[name].css' 43 | } 44 | ] 45 | }, 46 | plugins: [ 47 | new webpack.NoErrorsPlugin() 48 | 49 | ], 50 | stats: { 51 | colors: true 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/'); 2 | -------------------------------------------------------------------------------- /lib/bvd/examples/countdown/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets' : ['es2015', 'react'] 3 | } 4 | -------------------------------------------------------------------------------- /lib/bvd/examples/countdown/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/bvd/examples/countdown/app/index.js: -------------------------------------------------------------------------------- 1 | import './index.html'; 2 | 3 | /* coundown gist from https://gist.github.com/loremipson/8834955 */ 4 | var date = new Date(), 5 | month = date.getMonth(), 6 | day = date.getDate(), 7 | weekDay = date.getDay(); 8 | 9 | var hours = { 10 | start: new Date(date.getFullYear(), month, day), 11 | end: new Date(date.getFullYear(), month, day) 12 | }; 13 | 14 | // weekDay var [0 = sun, 1 = mon, 2 = tues ... 5 = fri 6 = sat] 15 | 16 | // If it's Monday - Friday 17 | if(weekDay >= 1 && weekDay <= 5){ 18 | 19 | // Start at 7am, end at 8pm 20 | hours.start.setHours(7); 21 | hours.end.setHours(20); 22 | 23 | // If it's Saturday 24 | } else if(weekDay == 6){ 25 | 26 | // Start at 8am, end at 8pm 27 | hours.start.setHours(8); 28 | hours.end.setHours(20); 29 | 30 | // If it's Sunday 31 | } else { 32 | 33 | // Start at 9am, end at 6pm 34 | hours.start.setHours(9); 35 | hours.end.setHours(18); 36 | } 37 | 38 | function countDown(){ 39 | var date = new Date(), 40 | countHours = ('0' + (hours.end.getHours() - date.getHours())).substr(-2), 41 | countMinutes = ('0' + (59 - date.getMinutes())).substr(-2), 42 | countSeconds = ('0' + (59 - date.getSeconds())).substr(-2); 43 | 44 | return { h : countHours, m : countMinutes, s : countSeconds }; 45 | } 46 | 47 | 48 | /** @jsx h */ 49 | 50 | import {h, patch, diff} from '../../../'; 51 | 52 | var initialDom = ( 53 |
54 |

Counter

55 |
56 | ); 57 | 58 | document.getElementById('application') 59 | .appendChild(initialDom.render()); 60 | 61 | setInterval(function() { 62 | var cd = countDown(); 63 | var countDownDom = ( 64 |
65 |

Day Countdown

66 |
67 | {cd.h} :  68 | {cd.m} :  69 | {cd.s} 70 |
71 |
72 | ); 73 | 74 | var diffs = diff(initialDom, countDownDom); 75 | patch(initialDom, diffs); 76 | 77 | }, 1000); -------------------------------------------------------------------------------- /lib/bvd/examples/countdown/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "author": "Svetlana Linuxenko (http://www.linuxenko.pro)", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "babel-core": "^6.18.2", 7 | "babel-loader": "^6.2.8", 8 | "babel-preset-es2015": "^6.18.0", 9 | "babel-preset-react": "^6.16.0", 10 | "file-loader": "^0.9.0", 11 | "webpack": "^1.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/bvd/examples/countdown/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var path = require('path'); 4 | 5 | var webpack = require('webpack'); 6 | var dir_js = path.resolve(__dirname, 'app'); 7 | var dir_build = path.resolve(__dirname, 'build'); 8 | 9 | module.exports = { 10 | entry: { 11 | app : path.resolve(dir_js, 'index.js') 12 | }, 13 | devtool: 'source-map', 14 | output: { 15 | path: dir_build, 16 | filename: 'bundle.js' 17 | }, 18 | resolveLoader: { 19 | fallback: [path.join(__dirname, 'node_modules')] 20 | }, 21 | resolve: { 22 | modulesDirectories: ['node_modules', '../../../lib', dir_js], 23 | fallback: [path.join(__dirname, 'node_modules')] 24 | }, 25 | devServer: { 26 | contentBase: dir_build, 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | presets : ['es2015', 'react'] 35 | }, 36 | { 37 | test : /\.html$/, 38 | loader : 'file?name=[name].html' 39 | }, 40 | { 41 | test : /\.cur$/, 42 | loader : 'file?name=[name].cur' 43 | } 44 | ] 45 | }, 46 | plugins: [ 47 | new webpack.NoErrorsPlugin() 48 | 49 | ], 50 | stats: { 51 | colors: true 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /lib/bvd/examples/misc/inspect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxenko/cakejs2/3d2323a3f7b3737932b95bcb031df1bc966bd1f8/lib/bvd/examples/misc/inspect.gif -------------------------------------------------------------------------------- /lib/bvd/index.js: -------------------------------------------------------------------------------- 1 | exports.h = require('./lib/h').h; 2 | exports.diff = require('./lib/diff').diff; 3 | exports.patch = require('./lib/patch').patch; 4 | 5 | exports.PATCH_CREATE = require('./lib/diff').PATCH_CREATE; 6 | exports.PATCH_REMOVE = require('./lib/diff').PATCH_REMOVE; 7 | exports.PATCH_REPLACE = require('./lib/diff').PATCH_REPLACE; 8 | exports.PATCH_REORDER = require('./lib/diff').PATCH_REORDER; 9 | exports.PATCH_PROPS = require('./lib/diff').PATCH_PROPS; 10 | -------------------------------------------------------------------------------- /lib/bvd/lib/diff.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff 3 | */ 4 | 5 | var PATCH_CREATE = 0; 6 | var PATCH_REMOVE = 1; 7 | var PATCH_REPLACE = 2; 8 | var PATCH_REORDER = 3; 9 | var PATCH_PROPS = 4; 10 | 11 | /** 12 | * Diff two virtual dom trees 13 | * 14 | * @name diff 15 | * @function 16 | * @access public 17 | * @param {Object} oldNode virtual tree to compare 18 | * @param {Object} newNode virtual tree to compare with 19 | */ 20 | var diff = function (oldNode, newNode) { 21 | if (typeof oldNode === 'undefined' || typeof newNode === 'undefined') { 22 | throw new Error('cannot diff undefined nodes'); 23 | } 24 | 25 | if (!_isNodeSame(oldNode, newNode)) { 26 | throw new Error('unable create diff replace for root node'); 27 | } 28 | 29 | return _diffTree(oldNode, newNode, []); 30 | }; 31 | 32 | /** 33 | * Tree walker function 34 | * 35 | * @name _diffTree 36 | * @function 37 | * @access private 38 | * @param {} a 39 | * @param {} b 40 | * @param {} patches 41 | */ 42 | var _diffTree = function (a, b, patches) { 43 | _diffProps(a, b, patches); 44 | 45 | if (b.tag === 'text') { 46 | if (b.children !== a.children) { 47 | patches.push({ t: PATCH_REPLACE, node: a, with: b }); 48 | } 49 | return; 50 | } 51 | 52 | if (Array.isArray(b.children)) { 53 | _diffChild(a.children, b.children, a, patches); 54 | } else if (Array.isArray(a.children)) { 55 | for (var i = 0; i < a.children.length; i++) { 56 | patches.push({ t: PATCH_REMOVE, from: i, node: _nodeId(a), item: _nodeId(a.children[i]) }); 57 | } 58 | } 59 | 60 | return patches; 61 | }; 62 | 63 | /** 64 | * Tree children diffings 65 | * 66 | * @name _diffChild 67 | * @function 68 | * @access private 69 | * @param {} a 70 | * @param {} b 71 | * @param {} pn 72 | * @param {} patches 73 | */ 74 | var _diffChild = function (a, b, pn, patches) { 75 | var reorderMap = []; 76 | var i; 77 | var j; 78 | var found; 79 | 80 | for (i = 0; i < b.length; i++) { 81 | found = false; 82 | 83 | if (!a) { 84 | if (!pn.children) { 85 | pn.children = []; 86 | } 87 | 88 | if (b[i].tag === 'text') { 89 | patches.push({ t: PATCH_CREATE, to: i, node: _nodeId(pn), item: _nodeId(b[i]) }); 90 | } else { 91 | patches.push({ t: PATCH_CREATE, to: i, node: _nodeId(pn), item: _nodeId(b[i].clone()) }); 92 | } 93 | continue; 94 | } 95 | 96 | for (j = 0; j < a.length; j++) { 97 | if (_isNodeSame(a[j], b[i]) && reorderMap.indexOf(a[j]) === -1) { 98 | if (j !== i) { 99 | patches.push({ t: PATCH_REORDER, from: j, to: i, node: _nodeId(pn), item: _nodeId(a[j]) }); 100 | } 101 | reorderMap.push(a[j]); 102 | 103 | _diffTree(a[j], b[i], patches); 104 | found = true; 105 | break; 106 | } 107 | } 108 | 109 | if (found === false) { 110 | reorderMap.push(null); 111 | patches.push({ t: PATCH_CREATE, to: i, node: _nodeId(pn), item: b[i].tag === 'text' ? _nodeId(b[i]) : _nodeId(b[i].clone()) }); 112 | } 113 | } 114 | 115 | if (!a) return; 116 | 117 | for (i = 0; i < a.length; i++) { 118 | if (reorderMap.indexOf(a[i]) === -1) { 119 | patches.push({ t: PATCH_REMOVE, from: i, node: _nodeId(pn), item: _nodeId(a[i]) }); 120 | } 121 | } 122 | }; 123 | 124 | /** 125 | * Props diffings 126 | * 127 | * @name _diffProps 128 | * @function 129 | * @access private 130 | * @param {} a 131 | * @param {} b 132 | * @param {} patches 133 | * @param {} type 134 | */ 135 | var _diffProps = function (a, b, patches) { 136 | if (!a || !b || !a.props && !b.props) { 137 | return; 138 | } 139 | 140 | var toChange = []; 141 | var toRemove = []; 142 | var battrs = Object.keys(b.props); 143 | var aattrs = Object.keys(a.props); 144 | var aattrsLen = aattrs.filter(function(attr) { 145 | return (attr !== 'ref' && !(attr.match(/^on/))); 146 | }).length; 147 | var i; 148 | 149 | if (a.el && a.el.attributes.length !== aattrsLen) { 150 | for (i = 0; i < a.el.attributes.length; i++) { 151 | var attr = a.el.attributes[i]; 152 | var name = attr.name; 153 | 154 | if (name === 'class') { 155 | name = 'className'; 156 | } 157 | 158 | if (!(name in aattrs)) { 159 | a.props[name] = attr.value; 160 | } 161 | 162 | if (attr.value !== a.props[name]) { 163 | a.props[name] = attr.value; 164 | } 165 | } 166 | aattrs = Object.keys(a.props); 167 | } 168 | 169 | for (i = 0; i < battrs.length || i < aattrs.length; i++) { 170 | if (i < battrs.length) { 171 | if (!(battrs[i] in a.props) || b.props[battrs[i]] !== a.props[battrs[i]]) { 172 | toChange.push({ name: battrs[i], value: b.props[battrs[i]] }); 173 | } 174 | } 175 | 176 | if (i < aattrs.length) { 177 | if (!(aattrs[i] in b.props)) { 178 | toRemove.push({ name: aattrs[i] }); 179 | } 180 | } 181 | } 182 | 183 | if (toRemove.length > 0) { 184 | patches.push({ t: PATCH_PROPS, remove: toRemove, node: _nodeId(a) }); 185 | } 186 | 187 | if (toChange.length > 0) { 188 | patches.push({ t: PATCH_PROPS, change: toChange, node: _nodeId(a) }); 189 | } 190 | }; 191 | 192 | /** 193 | * Node identifier 194 | * 195 | * @name _nodeId 196 | * @function 197 | * @access private 198 | * @param {} node 199 | */ 200 | var _nodeId = function (node) { 201 | return node; 202 | }; 203 | 204 | /** 205 | * Nodes comparison 206 | * 207 | * @name _isNodeSame 208 | * @function 209 | * @access private 210 | * @param {} a 211 | * @param {} b 212 | */ 213 | var _isNodeSame = function (a, b) { 214 | return a.tag === b.tag; 215 | }; 216 | 217 | exports.PATCH_CREATE = PATCH_CREATE; 218 | exports.PATCH_REMOVE = PATCH_REMOVE; 219 | exports.PATCH_REPLACE = PATCH_REPLACE; 220 | exports.PATCH_REORDER = PATCH_REORDER; 221 | exports.PATCH_PROPS = PATCH_PROPS; 222 | exports.diff = diff; 223 | -------------------------------------------------------------------------------- /lib/bvd/lib/h.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Element 3 | */ 4 | 5 | /** 6 | * General tree 7 | * 8 | * /** @jsx h * / 9 | * 10 | * @name h 11 | * @function 12 | * @access public 13 | */ 14 | 15 | var xmlns = "http://www.w3.org/2000/svg"; 16 | 17 | var H = function (argv) { 18 | if (!(this instanceof H)) { 19 | if (!(argv instanceof H)) { 20 | if (typeof argv === 'function') { 21 | return argv.apply(argv, [].slice.call(arguments, 1, arguments.length)); 22 | } 23 | 24 | if (typeof argv === 'object' && typeof argv.render === 'function') { 25 | if ('props' in argv) { 26 | for (var i in arguments[1]) { 27 | argv.props[i] = arguments[1][i]; 28 | } 29 | } else { 30 | argv.props = arguments[1] || []; 31 | } 32 | argv.props.children = [].slice.call(arguments, 2, arguments.length); 33 | return argv.render(argv.props); 34 | } 35 | } 36 | return new H(arguments); 37 | } 38 | 39 | if (argv[0] instanceof H) { 40 | return argv[0]; 41 | } 42 | 43 | this.tag = argv[0].toLowerCase(); 44 | this.props = argv[1] || {}; 45 | 46 | if (this.tag === 'text') { 47 | this.tag = 'svgtext'; 48 | } 49 | 50 | if (argv[2] === null || argv[2] === undefined) { 51 | return; 52 | } 53 | 54 | if (argv.length > 2) { 55 | if (typeof argv[2] !== 'object' && argv.length === 3) { 56 | this.children = [_createTextNode(argv[2])]; 57 | } else if (Array.isArray(argv[2])) { 58 | this.children = argv[2]; 59 | } else { 60 | this.children = [].concat.apply([], [].slice.call(argv, 2, argv.length)) 61 | .filter(function (n) { 62 | return n !== null && n !== undefined && n !== false; 63 | }) 64 | .map(function (n) { 65 | if (!(n instanceof H)) { 66 | return _createTextNode(n); 67 | } else { 68 | return n; 69 | } 70 | }); 71 | } 72 | } 73 | }; 74 | 75 | /** 76 | * Tree renderer 77 | * 78 | * @name render 79 | * @function 80 | * @access public 81 | * @param {Boolean} fasle - do not save DOM into tree 82 | */ 83 | H.prototype.render = function (node, parent) { 84 | node = node || this; 85 | 86 | node.el = createElement(node.tag ? node : this, parent); 87 | 88 | var children = node.children; 89 | 90 | if (typeof children === 'object') { 91 | for (var i = 0; i < children.length; i++) { 92 | node.el.appendChild(this.render(children[i], node.el)); 93 | } 94 | } 95 | 96 | return node.el; 97 | }; 98 | 99 | H.prototype.setAttribute = function(name, value) { 100 | try { 101 | if (this.el instanceof window.SVGElement) { 102 | this.el.setAttributeNS(null, name, value); 103 | } else { 104 | this.el.setAttribute(name, value); 105 | } 106 | } catch(e) { 107 | this.el.setAttribute(name, value); 108 | } 109 | }; 110 | 111 | H.prototype.setProp = function (name, value) { 112 | if (typeof this.el !== 'undefined') { 113 | if (name === 'className') { 114 | this.setAttribute('class', value); 115 | } else if (name === 'style' && typeof value !== 'string') { 116 | this.setAttribute('style', _stylePropToString(value)); 117 | } else if (name.match(/^on/)) { 118 | this.addEvent(name, value); 119 | } else if (name === 'ref') { 120 | if (typeof value === 'function') { 121 | value(this.el); 122 | } 123 | } else if (typeof value === 'boolean' || value === 'true') { 124 | this.setAttribute(name, value); 125 | this.el[name] = Boolean(value); 126 | } else { 127 | this.setAttribute(name, value); 128 | } 129 | } 130 | 131 | this.props[name] = value; 132 | }; 133 | 134 | H.prototype.setProps = function (props) { 135 | var propNames = Object.keys(props); 136 | 137 | for (var i = 0; i < propNames.length; i++) { 138 | var prop = propNames[i]; 139 | this.setProp(prop, props[prop]); 140 | } 141 | }; 142 | 143 | H.prototype.rmProp = function (name) { 144 | if (typeof this.el !== 'undefined') { 145 | if (name === 'className') { 146 | this.el.removeAttribute('class'); 147 | } else if (name.match(/^on/)) { 148 | this.removeEvent(name); 149 | } else if (name === 'ref') { 150 | /* Nothing to do */ 151 | } else if (typeof value === 'boolean') { 152 | this.el.removeAttribute(name); 153 | delete this.el[name]; 154 | } else { 155 | this.el.removeAttribute(name); 156 | } 157 | } 158 | 159 | delete this.props[name]; 160 | }; 161 | 162 | H.prototype.addEvent = function (name, listener) { 163 | name = name.slice(2).toLowerCase(); 164 | 165 | this.listeners = this.listeners || {}; 166 | 167 | if (name in this.listeners) { 168 | this.removeEvent(name); 169 | } 170 | 171 | this.listeners[name] = listener; 172 | this.el.addEventListener(name, listener); 173 | }; 174 | 175 | H.prototype.removeEvent = function (name) { 176 | name = name.replace(/^on/, '').toLowerCase(); 177 | if (name in this.listeners) { 178 | this.el.removeEventListener(name, this.listeners[name]); 179 | delete this.listeners[name]; 180 | } 181 | }; 182 | 183 | H.prototype.clone = function () { 184 | var node = { 185 | tag: String(this.tag), 186 | props: _cloneProps(this.props) 187 | }; 188 | 189 | if (typeof this.children !== 'undefined') { 190 | node.children = this.tag === 'text' 191 | ? String(this.children) 192 | : this.children.map(function (child) { 193 | return child.tag === 'text' ? _createTextNode(child.children) : child.clone(); 194 | }); 195 | } 196 | 197 | return H(node.tag, node.props, node.children); 198 | }; 199 | 200 | var _cloneProps = function (props, keepRefs) { 201 | if (typeof keepRefs === 'undefined') { 202 | keepRefs = true; 203 | } 204 | 205 | var attrs = Object.keys(props); 206 | var i; 207 | var name; 208 | var cloned = {}; 209 | 210 | for (i = 0; i < attrs.length; i++) { 211 | name = attrs[i]; 212 | 213 | if (typeof props[name] === 'string') { 214 | cloned[name] = String(props[name]); 215 | } else if (typeof props[name] === 'function' && keepRefs === true) { 216 | cloned[name] = props[name]; 217 | } else if (typeof props[name] === 'boolean') { 218 | cloned[name] = Boolean(props[name]); 219 | } else if (typeof props[name] === 'object') { 220 | cloned[name] = _cloneProps(props[name]); 221 | } 222 | } 223 | 224 | return cloned; 225 | }; 226 | 227 | var _stylePropToString = function (props) { 228 | var out = ''; 229 | var attrs = Object.keys(props); 230 | 231 | for (var i = 0; i < attrs.length; i++) { 232 | out += attrs[i].replace(/([A-Z])/g, '-$1').toLowerCase(); 233 | out += ':'; 234 | out += props[attrs[i]]; 235 | out += ';'; 236 | } 237 | 238 | return out; 239 | }; 240 | 241 | var _createTextNode = function (text) { 242 | return { 243 | tag: 'text', 244 | children: String(text) 245 | }; 246 | }; 247 | 248 | var createElement = function (node, parent) { 249 | // node.el = node.tag === 'text' 250 | // ? document.createTextNode(node.children) 251 | // : document.createElement(node.tag); 252 | 253 | 254 | switch (node.tag) { 255 | case 'text': 256 | node.el = document.createTextNode(node.children); 257 | break; 258 | case 'svgtext': 259 | node.el = document.createElementNS(xmlns, 'text'); 260 | break; 261 | case 'lineargradient': 262 | node.el = document.createElementNS(xmlns, 'linearGradient'); 263 | break; 264 | case 'radialgradient': 265 | node.el = document.createElementNS(xmlns, 'radialGradient'); 266 | break; 267 | case 'fegaussianblur': 268 | node.el = document.createElementNS(xmlns, 'feGaussianBlur'); 269 | break; 270 | case 'feoffset': 271 | node.el = document.createElementNS(xmlns, 'feOffset'); 272 | break; 273 | case 'feblend': 274 | node.el = document.createElementNS(xmlns, 'feBlend'); 275 | break; 276 | case 'svg': 277 | case 'g': 278 | case 'circle': 279 | case 'ellipse': 280 | case 'line': 281 | case 'path': 282 | case 'polygon': 283 | case 'polyline': 284 | case 'rect': 285 | case 'defs': 286 | case 'stop': 287 | case 'filter': 288 | node.el = document.createElementNS(xmlns, node.tag); 289 | break; 290 | default: 291 | node.el = document.createElement(node.tag); 292 | break; 293 | } 294 | 295 | if (typeof node.props !== 'undefined') { 296 | node.setProps(node.props); 297 | } 298 | 299 | if (typeof parent !== 'undefined') { 300 | parent.appendChild(node.el); 301 | } 302 | 303 | return node.el; 304 | }; 305 | 306 | exports.h = H; 307 | exports.createElement = createElement; 308 | -------------------------------------------------------------------------------- /lib/bvd/lib/patch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Patch 3 | */ 4 | 5 | var PATCH_CREATE = require('./diff').PATCH_CREATE; 6 | var PATCH_REMOVE = require('./diff').PATCH_REMOVE; 7 | var PATCH_REPLACE = require('./diff').PATCH_REPLACE; 8 | var PATCH_REORDER = require('./diff').PATCH_REORDER; 9 | var PATCH_PROPS = require('./diff').PATCH_PROPS; 10 | 11 | var createElement = require('./h').createElement; 12 | 13 | /** 14 | * Patch DOM and virtual tree 15 | * 16 | * @name patch 17 | * @function 18 | * @access public 19 | * @param {Object} tree Tree to patch 20 | * @param {Array} patches Array of patches 21 | */ 22 | var patch = function(tree, patches) { 23 | var render = true; 24 | 25 | if (typeof tree.el === 'undefined') { 26 | render = false; 27 | } 28 | 29 | for (var i = 0; i < patches.length; i++) { 30 | var p = patches[i]; 31 | 32 | switch(p.t) { 33 | case PATCH_REORDER: 34 | _patchReorder(p, render); 35 | break; 36 | case PATCH_CREATE: 37 | _patchCreate(p, render); 38 | break; 39 | case PATCH_REMOVE: 40 | _patchRemove(p, render); 41 | break; 42 | case PATCH_REPLACE: 43 | _patchReplace(p, render); 44 | break; 45 | case PATCH_PROPS: 46 | _patchProps(p, render); 47 | break; 48 | } 49 | } 50 | }; 51 | 52 | /** 53 | * Replace existen node content 54 | * 55 | * @name patchReplace 56 | * @function 57 | * @access private 58 | */ 59 | var _patchReplace = function(p, render) { 60 | p.node.children = String(p.with.children); 61 | 62 | if (render === true) { 63 | p.node.el.nodeValue = String(p.with.children); 64 | } 65 | }; 66 | 67 | /** 68 | * Reorder existen node 69 | * 70 | * @name patchReorder 71 | * @function 72 | * @access private 73 | */ 74 | var _patchReorder = function(p, render) { 75 | if (render === true) { 76 | p.node.el.insertBefore(p.item.el, p.node.el.childNodes[p.to]); 77 | } 78 | 79 | p.node.children.splice(p.to, 0, 80 | p.node.children.splice(p.node.children.indexOf(p.item), 1)[0]); 81 | }; 82 | 83 | /** 84 | * Create new tree node 85 | * 86 | * @name patchCreate 87 | * @function 88 | * @access private 89 | */ 90 | var _patchCreate = function(p, render) { 91 | var element; 92 | 93 | if (render === true) { 94 | element = p.item.tag === 'text' ? 95 | createElement(p.item) : p.item.render(); 96 | } 97 | 98 | if (p.node.children.length - 1 < p.to) { 99 | p.node.children.push(p.item); 100 | 101 | if (render === true) { 102 | p.node.el.appendChild(element); 103 | } 104 | } else { 105 | p.node.children.splice(p.to, 0, p.item); 106 | 107 | if (render === true) { 108 | p.node.el.insertBefore(element, p.node.el.childNodes[p.to]); 109 | } 110 | } 111 | }; 112 | 113 | /** 114 | * Remove tree node 115 | * 116 | * @name patchRemove 117 | * @function 118 | * @access private 119 | */ 120 | var _patchRemove = function(p, render) { 121 | if (render === true) { 122 | p.node.el.removeChild(p.item.el); 123 | } 124 | 125 | for (var i = 0; i < p.node.children.length; i++) { 126 | if (p.node.children[i] === p.item) { 127 | p.node.children.splice(i, 1); 128 | } 129 | } 130 | }; 131 | 132 | /** 133 | * Replace props 134 | * 135 | * @name _patchProps 136 | * @function 137 | * @access private 138 | */ 139 | var _patchProps = function(p) { 140 | var i; 141 | 142 | if ('remove' in p) { 143 | for (i = 0; i < p.remove.length; i++) { 144 | p.node.rmProp(p.remove[i].name); 145 | } 146 | return; 147 | } 148 | 149 | if ('change' in p) { 150 | for (i = 0; i < p.change.length; i++) { 151 | p.node.setProp(p.change[i].name, p.change[i].value); 152 | } 153 | return; 154 | } 155 | }; 156 | 157 | exports.patch = patch; 158 | -------------------------------------------------------------------------------- /lib/bvd/tests/fixtures/basic.js: -------------------------------------------------------------------------------- 1 | var h = require('../../').h; 2 | 3 | exports.tree1 = h( 4 | 'div', 5 | { id: 'application', className: 'main-app' }, 6 | h( 7 | 'em', 8 | { className: 'em' }, 9 | 'Item 1' 10 | ), 11 | h( 12 | 'div', 13 | null, 14 | 'ffirett' 15 | ), 16 | h( 17 | 'div', 18 | { className: '2to-remove' }, 19 | '2removable div' 20 | ), 21 | h( 22 | 'span', 23 | { className: 'menu-item' }, 24 | 'Item 1' 25 | ), 26 | h( 27 | 'ul', 28 | null, 29 | h( 30 | 'li', 31 | null, 32 | h( 33 | 'span', 34 | { className: 'menu-item' }, 35 | 'Item 1' 36 | ), 37 | h( 38 | 'p', 39 | { className: 'redundant-item' }, 40 | 'new text Item 2' 41 | ) 42 | ), 43 | h( 44 | 'li', 45 | null, 46 | h( 47 | 'div', 48 | { className: 'changed-menu-item' }, 49 | 'new text Item 2' 50 | ), 51 | h( 52 | 'span', 53 | { className: 'menu-item' }, 54 | 'Item 2' 55 | ) 56 | ) 57 | ) 58 | ); 59 | 60 | exports.tree2 = h( 61 | 'div', 62 | { id: 'app', className: 'changed-class' }, 63 | h( 64 | 'span', 65 | { className: 'menu-item' }, 66 | 'Item 1' 67 | ), 68 | h( 69 | 'strong', 70 | null, 71 | 'sttrong' 72 | ), 73 | h( 74 | 'ul', 75 | { className : 'test test' }, 76 | h( 77 | 'li', 78 | null, 79 | h( 80 | 'span', 81 | { className : 'llll', id : 'kkkkk' }, 82 | 'Item changed text 1' 83 | ) 84 | ), 85 | h( 86 | 'li', 87 | null, 88 | h( 89 | 'span', 90 | { className: 'changed-menu-item' }, 91 | 'new text Item 2' 92 | ), 93 | h( 94 | 'div', 95 | { className: 'changed-menu-item' }, 96 | 'new text Item 2' 97 | ) 98 | ), 99 | h( 100 | 'li', 101 | null, 102 | h( 103 | 'span', 104 | { className: 'menu-item' }, 105 | 'Item 3' 106 | ) 107 | ) 108 | ), 109 | h( 110 | 'div', 111 | { className: 'to-remove' }, 112 | 'removable div' 113 | ) 114 | ); 115 | -------------------------------------------------------------------------------- /lib/bvd/tests/fixtures/nested.js: -------------------------------------------------------------------------------- 1 | var h = require('../../').h; 2 | 3 | exports.a = h( 4 | 'div', 5 | { id: 'app', className: 'changed-class' }, 6 | h( 7 | 'ul', 8 | null, 9 | h( 10 | 'li', 11 | null, 12 | h( 13 | 'span', 14 | { className: 'menu-item' }, 15 | 'Item 1' 16 | ) 17 | ), 18 | h( 19 | 'li', 20 | null, 21 | h( 22 | 'span', 23 | { className: 'changed-menu-item' }, 24 | 'Item 2' 25 | ) 26 | ), 27 | h( 28 | 'li', 29 | null, 30 | h( 31 | 'span', 32 | { className: 'menu-item' }, 33 | 'Item 3' 34 | ) 35 | ) 36 | ) 37 | ); 38 | 39 | exports.b = h( 40 | 'div', 41 | { id: 'app', className: 'changed-class' }, 42 | h( 43 | 'ul', 44 | null, 45 | h( 46 | 'li', 47 | null, 48 | h( 49 | 'span', 50 | { className: 'menu-item' }, 51 | 'Item 1' 52 | ) 53 | ), 54 | h( 55 | 'li', 56 | null, 57 | h( 58 | 'span', 59 | { className: 'changed-menu-item' }, 60 | 'Item 2' 61 | ) 62 | ), 63 | h( 64 | 'li', 65 | null, 66 | h( 67 | 'span', 68 | { className: 'menu-item' }, 69 | 'Item 3 added text' 70 | ) 71 | ) 72 | ) 73 | ); 74 | 75 | 76 | exports.a1 = h( 77 | 'div', 78 | { id: 'app', className: 'changed-class' }, 79 | h( 80 | 'ul', 81 | null, 82 | h( 83 | 'li', 84 | null, 85 | h( 86 | 'span', 87 | { className: 'menu-item' }, 88 | 'Item 1' 89 | ) 90 | ), 91 | h( 92 | 'li', 93 | null, 94 | h( 95 | 'span', 96 | { className: 'changed-menu-item' }, 97 | 'Item 2' 98 | ) 99 | ), 100 | h( 101 | 'li', 102 | null, 103 | h( 104 | 'span', 105 | { className: 'menu-item' }, 106 | 'Item 3' 107 | ) 108 | ) 109 | ) 110 | ); 111 | 112 | exports.b1 = h( 113 | 'div', 114 | { id: 'app', className: 'changed-class' }, 115 | h( 116 | 'ul', 117 | null, 118 | h( 119 | 'li', 120 | null, 121 | h( 122 | 'span', 123 | { className: 'menu-item' }, 124 | 'Item 1' 125 | ) 126 | ), 127 | h( 128 | 'li', 129 | null, 130 | h( 131 | 'span', 132 | { className: 'changed-menu-item' }, 133 | 'Item 2' 134 | ) 135 | ), 136 | h( 137 | 'li', 138 | null, 139 | h( 140 | 'span', 141 | { className: 'menu-item' }, 142 | 'Item 3 added text' 143 | ) 144 | ) 145 | ) 146 | ); 147 | 148 | -------------------------------------------------------------------------------- /lib/bvd/tests/fixtures/simple.js: -------------------------------------------------------------------------------- 1 | var h = require('../../').h; 2 | 3 | exports.a = h( 4 | 'div', 5 | { id: 'application', className: 'test-class test-class2' }, 6 | h( 7 | 'div', 8 | null, 9 | 'text' 10 | ) 11 | ); 12 | 13 | exports.a1 = h( 14 | 'div', 15 | { id: 'application', className: 'test-class test-class2' }, 16 | 'text' 17 | ); 18 | 19 | exports.a2 = h( 20 | 'div', 21 | { id: 'application', className: 'no-class' }, 22 | h( 23 | 'div', 24 | null, 25 | 'changed' 26 | ) 27 | ); 28 | 29 | exports.b = h( 30 | 'div', 31 | { id: 'application', className: 'test-class test-class2' }, 32 | 'text' 33 | ); 34 | 35 | exports.c = h( 36 | 'div', 37 | { id: 'application', className: 'test-class test-class2' }, 38 | h( 39 | 'div', 40 | null, 41 | 'changed' 42 | ) 43 | ); 44 | 45 | exports.d = h( 46 | 'div', 47 | null, 48 | h( 49 | 'span', 50 | { id : 'text-node', className : 'text-node' }, 51 | 'node-text' 52 | ) 53 | ); 54 | 55 | exports.e = h( 56 | 'div', 57 | { 'data-name' : 'to remove', className : 'test-class' }, 58 | h( 59 | 'div', 60 | null, 61 | 'changed' 62 | ) 63 | ); 64 | 65 | exports.f = h( 66 | 'div', 67 | { id: 'app', className: 'changed-class' }, 68 | h( 69 | 'span', 70 | { className: 'changed-menu-item' }, 71 | 'Reorder me' 72 | ), 73 | h( 74 | 'ul', 75 | null, 76 | h( 77 | 'li', 78 | null, 79 | h( 80 | 'span', 81 | { className: 'menu-item' }, 82 | 'Item 1' 83 | ) 84 | ), 85 | h( 86 | 'li', 87 | null, 88 | h( 89 | 'span', 90 | { className: 'changed-menu-item' }, 91 | 'Item 2' 92 | ) 93 | ), 94 | h( 95 | 'li', 96 | null, 97 | h( 98 | 'span', 99 | { className: 'menu-item' }, 100 | 'Item 3' 101 | ) 102 | ) 103 | ) 104 | ); 105 | 106 | exports.f1 = h( 107 | 'div', 108 | { id: 'app', className: 'changed-class' }, 109 | h( 110 | 'ul', 111 | null, 112 | h( 113 | 'li', 114 | null, 115 | h( 116 | 'span', 117 | { className: 'menu-item' }, 118 | 'Item 1' 119 | ) 120 | ), 121 | h( 122 | 'li', 123 | null, 124 | h( 125 | 'span', 126 | { className: 'changed-menu-item' }, 127 | 'Item 2' 128 | ) 129 | ), 130 | h( 131 | 'li', 132 | null, 133 | h( 134 | 'span', 135 | { className: 'menu-item' }, 136 | 'Item 3' 137 | ) 138 | ) 139 | ), 140 | h( 141 | 'span', 142 | { className: 'changed-menu-item' }, 143 | 'Reorder me' 144 | ) 145 | ); 146 | -------------------------------------------------------------------------------- /lib/bvd/tests/fixtures/svg.js: -------------------------------------------------------------------------------- 1 | var h = require('../../').h; 2 | 3 | exports.a = 4 | h('svg', { id: 'app'}, 5 | h( 6 | 'g', 7 | null, 8 | h('rect', { x: '40', y: '80', fill: 'blue' }), 9 | h('rect', { x: '40', y: '80', fill: 'red' }), 10 | h('rect', { x: '40', y: '80', fill: 'green' }) 11 | ) 12 | ); 13 | 14 | exports.a = 15 | h('svg', { id: 'app'}, 16 | h( 17 | 'g', 18 | null, 19 | h('rect', { x: '40', y: '80', fill: 'blue' }), 20 | h('text', { x: '40', y: '80' }, 'text node') 21 | ) 22 | ); 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/bvd/tests/fixtures/textnodes.js: -------------------------------------------------------------------------------- 1 | var h = require('../../').h; 2 | exports.a = h( 3 | 'div', 4 | null, 5 | h( 6 | 'div', 7 | null, 8 | h( 9 | 'h3', 10 | null, 11 | 'Counter' 12 | ) 13 | ) 14 | ); 15 | 16 | exports.b = h( 17 | 'div', 18 | null, 19 | h( 20 | 'div', 21 | null, 22 | h( 23 | 'h3', 24 | null, 25 | 'Day Countdown' 26 | ) 27 | ), 28 | h( 29 | 'div', 30 | { className: 'clock' }, 31 | h( 32 | 'strong', 33 | null, 34 | '1' 35 | ), 36 | ' :', 37 | h( 38 | 'strong', 39 | null, 40 | '2' 41 | ), 42 | ' :', 43 | h( 44 | 'strong', 45 | null, 46 | '3' 47 | ) 48 | ) 49 | ); 50 | 51 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/attributes.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var h = require('../../').h; 6 | var diff = require('../../').diff; 7 | var patch = require('../../').patch; 8 | 9 | describe('Test attributes', function() { 10 | jsdom(); 11 | 12 | it('should setup style attribute', function() { 13 | var a = h('div', { style: { backgroundColor: '#fff', left: '20px' } }, 14 | h('div', { style: 'right:20px;' }) 15 | ); 16 | 17 | var dom = a.render(); 18 | 19 | expect(a.props['style']).to.be.exists; 20 | expect(a.props['style']).to.be.deep.equal({ backgroundColor: '#fff', left: '20px' }); 21 | expect(dom.getAttribute('style')).to.be.a('string'); 22 | expect(dom.getAttribute('style')).to.be.equal('background-color:#fff;left:20px;'); 23 | expect(a.children[0].props['style']).to.be.equal('right:20px;'); 24 | }); 25 | 26 | it('should diff and patch style attribute', function() { 27 | var a = h('div', { style: { left: '21px' }}, ''); 28 | var b = h('div', { style: { backgroundColor: '#fff', left: '20px' } }, 29 | h('div', { style: 'right:20px;' }) 30 | ); 31 | 32 | var dom = a.render(); 33 | patch(a, diff(a, b)); 34 | 35 | expect(a.props['style']).to.be.exists; 36 | expect(a.props['style']).to.be.deep.equal({ backgroundColor: '#fff', left: '20px' }); 37 | expect(dom.getAttribute('style')).to.be.a('string'); 38 | expect(dom.getAttribute('style')).to.be.equal('background-color:#fff;left:20px;'); 39 | expect(a.children[0].props['style']).to.be.equal('right:20px;'); 40 | }); 41 | 42 | it('should patch attrs modified via dom node', function () { 43 | var a = h('div', null, ''); 44 | var b = h('div', null, ''); 45 | 46 | var dom = a.render(); 47 | 48 | dom.classList.add('active'); 49 | patch(a, diff(a, b)); 50 | 51 | expect(dom.attributes.length).to.be.equal(0); 52 | expect(dom.props).to.be.an('undefined'); 53 | }); 54 | 55 | it('should patch attrs created via dom node', function () { 56 | var a = h('div', { onClick: function () {}}, ''); 57 | var b = h('div', { ref: function () {}, onClick: function () {}} , ''); 58 | 59 | var dom = a.render(); 60 | 61 | dom.classList.add('active'); 62 | 63 | expect(dom.attributes.length).to.be.equal(1); 64 | patch(a, diff(a, b)); 65 | 66 | expect(dom.attributes.length).to.be.equal(0); 67 | expect(Object.keys(a.props).length).to.be.equal(2); 68 | }); 69 | 70 | it('should patch attrs created via dom node #2', function () { 71 | var a = h('div', { onClick: function () {}}, ''); 72 | var b = h('div', { onClick: function () {}, tabindex: 0} , ''); 73 | 74 | var dom = a.render(); 75 | 76 | dom.classList.add('active'); 77 | 78 | expect(dom.attributes.length).to.be.equal(1); 79 | patch(a, diff(a, b)); 80 | 81 | expect(dom.attributes.length).to.be.equal(1); 82 | expect(dom.attributes[0].name).to.be.equal('tabindex'); 83 | expect(Object.keys(a.props).length).to.be.equal(2); 84 | }); 85 | 86 | it('should patch listeners #1', function () { 87 | var listener1 = function() { return 1; }; 88 | var a = h('div', { onClick: listener1}, ''); 89 | var b = h('div', { onClick: function () { return 2; }, tabindex: 0} , ''); 90 | 91 | a.render(); 92 | 93 | expect(a.props.onClick()).to.be.equal(1); 94 | patch(a, diff(a, b)); 95 | expect(a.props.onClick()).to.be.equal(2); 96 | }); 97 | 98 | it('should patch replace listeners #2', function () { 99 | var fail = false; 100 | var listener1 = function() { fail = true; }; 101 | var a = h('div', { onClick: listener1}, ''); 102 | var b = h('div', { tabindex: 0} , ''); 103 | 104 | var dom = a.render(); 105 | patch(a, diff(a, b)); 106 | 107 | dom.click(); 108 | expect(fail).to.be.false; 109 | }); 110 | }); -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/diff.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var diff = require('../../').diff; 6 | var h = require('../../').h; 7 | 8 | var a = require('../fixtures/simple').a; 9 | var b = require('../fixtures/simple').b; 10 | var c = require('../fixtures/simple').c; 11 | var e = require('../fixtures/simple').e; 12 | 13 | var na = require('../fixtures/nested').a; 14 | var nb = require('../fixtures/nested').b; 15 | 16 | var PATCH_REPLACE = require('../../').PATCH_REPLACE; 17 | 18 | describe('Test diff()', function() { 19 | jsdom(); 20 | 21 | it('should create trees', function() { 22 | expect(a).to.be.an('object'); 23 | expect(b).to.be.an('object'); 24 | }); 25 | 26 | it('should deal with empty arguments', function() { 27 | expect(function() { diff(a); }).to.throw(); 28 | expect(function() { diff(undefined, a); }).to.throw(); 29 | }); 30 | 31 | it('should diff simple tree', function() { 32 | var diffs; 33 | 34 | a.render(); 35 | 36 | expect(function() { 37 | diffs = diff(a, c); 38 | }).not.throw(); 39 | 40 | expect(diffs).to.be.an('array'); 41 | expect(diffs.length).to.be.equal(1); 42 | expect(diffs[0].t).to.be.equal(PATCH_REPLACE); 43 | }); 44 | 45 | it('should diff nonexisten node', function() { 46 | var dom = b.render(); 47 | var diffs; 48 | 49 | expect(dom.children).not.be.exists; 50 | 51 | diffs = diff(b, a); 52 | 53 | expect(diffs.length).to.be.equal(2); 54 | }); 55 | 56 | it('should diff props', function() { 57 | e.render(); 58 | var diffs = diff(e, c); 59 | 60 | 61 | expect(diffs[0].t).to.be.equal(4); 62 | expect(diffs[1].t).to.be.equal(4); 63 | expect(diffs[1].change.length).to.be.equal(2); 64 | }); 65 | 66 | it('should diff complex tree', function() { 67 | na.render(); 68 | expect(na.children[0].children.length).to.be.equal(3); 69 | var diffs = diff(na, nb); 70 | 71 | expect(diffs).to.be.an('array'); 72 | expect(diffs.length).to.be.equal(1); 73 | expect(diffs[0].t).to.be.equal(2); 74 | }); 75 | 76 | it('should not diff root node', function() { 77 | var src = h('span', null, 'hello world'); 78 | var dst = h('strong', null, 'bye bye'); 79 | 80 | src.render(); 81 | expect(function() { diff(src, dst); }).to.throw(); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/events.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jsdom = require('mocha-jsdom'); 3 | 4 | var h = require('../../').h; 5 | var diff = require('../../').diff; 6 | var patch = require('../../').patch; 7 | 8 | describe('Test events', function() { 9 | jsdom(); 10 | 11 | it('should handle very simple click event', function(done) { 12 | var tree = h('div', { onClick : function() { done(); } }, ''); 13 | 14 | expect(tree.props.onClick).to.be.a('function'); 15 | tree.render(); 16 | tree.el.click(); 17 | }); 18 | 19 | it('should repatch event handlers', function(done) { 20 | var counter = 0; 21 | var a = h('div', { onClick : function() { counter++; } }, ''); 22 | var b = h('div', { onClick : function() { 23 | expect(counter).to.be.equal(1); 24 | done(); 25 | } }, ''); 26 | 27 | a.render(); 28 | a.el.click(); 29 | 30 | expect(counter).to.be.equal(1); 31 | expect(Object.keys(a.listeners).length).to.be.equal(1); 32 | 33 | patch(a, diff(a, b)); 34 | expect(Object.keys(a.listeners).length).to.be.equal(1); 35 | a.el.click(); 36 | }); 37 | 38 | it('should work with refs', function() { 39 | var elRef; 40 | var a = h('div', { ref : function(ref) { elRef = ref; } }, ''); 41 | 42 | expect(elRef).to.be.an('undefined'); 43 | a.render(); 44 | expect(elRef).to.be.equal(a.el); 45 | }); 46 | 47 | it('should replace refs by patch', function() { 48 | var elRef1; 49 | var elRef2; 50 | var a = h('div', { ref : function(ref) { elRef1 = ref; } }, ''); 51 | var b = h('div', { ref : function(ref) { elRef2 = ref; } }, ''); 52 | 53 | a.render(); 54 | var diffs = diff(a, b); 55 | patch(a, diffs); 56 | 57 | expect(elRef1).to.be.equal(a.el); 58 | expect(elRef2).to.be.equal(a.el); 59 | }); 60 | }); 61 | 62 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/h.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var sinon = require('sinon'); 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var a = require('../fixtures/simple').a; 6 | var d = require('../fixtures/simple').d; 7 | 8 | var h = require('../../').h; 9 | 10 | describe('Test h()', function() { 11 | jsdom(); 12 | it('should create object tree', function() { 13 | expect(a).to.be.exists; 14 | expect(a).to.be.an('object'); 15 | expect(a.tag).to.be.equal('div'); 16 | expect(a.children).to.be.an('array'); 17 | }); 18 | 19 | it('should create object with props', function() { 20 | expect(a.props.className).to.be.a('string'); 21 | expect(a.props.className).to.be.equal('test-class test-class2'); 22 | }); 23 | 24 | it('should create object with childs', function() { 25 | expect(a.children[0].props).to.be.an('object'); 26 | expect(a.children[0].tag).to.be.a('string'); 27 | expect(a.children[0].tag).to.be.equal('div'); 28 | expect(a.children[0].children[0].tag).to.be.a('string'); 29 | expect(a.children[0].children[0].tag).to.be.equal('text'); 30 | expect(a.children[0].children[0].children).to.be.a('string'); 31 | }); 32 | 33 | it('should create childrem props', function() { 34 | expect(d.children[0].tag).to.be.equal('span'); 35 | expect(d.children[0].children[0].children).to.be.equal('node-text'); 36 | expect(d.children[0].props.className).to.be.equal('text-node'); 37 | expect(d.children[0].props.id).to.be.equal('text-node'); 38 | }); 39 | 40 | it('should create more compex trees', function() { 41 | var nested; 42 | 43 | expect(function() { 44 | nested = require('../fixtures/nested').a; 45 | }).not.to.throw(); 46 | 47 | expect(nested.tag).to.be.equal('div'); 48 | expect(nested.children).to.be.an('array'); 49 | }); 50 | 51 | it('should create empty text nodes', function() { 52 | var empty = h('div', null, ''); 53 | 54 | expect(empty.tag).to.be.equal('div'); 55 | expect(empty.children[0].tag).to.be.equal('text'); 56 | expect(empty.children[0].children).to.be.equal(''); 57 | expect(empty.children[0].children).to.be.a('string'); 58 | }); 59 | 60 | it('should create nested node text', function() { 61 | var node; 62 | 63 | expect(function() { 64 | node = h('div', null, h('strong', null, ''), 'L', h('strong', null, 'test')); 65 | }).not.throw(); 66 | 67 | expect(node.children.length).to.be.equal(3); 68 | expect(node.children[1].tag).to.be.equal('text'); 69 | expect(node.children[1].children).to.be.equal('L'); 70 | }); 71 | 72 | it('should create nested with text first tree', function() { 73 | var node; 74 | 75 | expect(function() { 76 | node = h('div', null, '', h('strong', null, 'rrr'), h('strong', null, 'test')); 77 | }).not.throw(); 78 | 79 | expect(node.children.length).to.be.equal(3); 80 | expect(node.children[0].tag).to.be.equal('text'); 81 | expect(node.children[0].children).to.be.equal(''); 82 | }); 83 | 84 | it('should handle react like nesting', function() { 85 | var Test = h('div', null, 'text'); 86 | var Result = h('div', null, h(Test, null)); 87 | 88 | expect(Result.children[0].tag).to.be.equal('div'); 89 | expect(Result.children[0].children[0].children).to.be.equal('text'); 90 | }); 91 | 92 | it('should render h with falsy childs', function() { 93 | expect(function() { 94 | h('div', null, null).render(); 95 | h('div', null, 0).render(); 96 | h('div', null, '').render(); 97 | h('div', null, undefined).render(); 98 | }).not.throw(); 99 | 100 | var p = h('div', null, null); 101 | expect(p.children).to.be.an('undefined'); 102 | p = h('div', null, undefined); 103 | expect(p.children).to.be.an('undefined'); 104 | p = h('div', null, ''); 105 | expect(p.children[0].children).to.be.equal(''); 106 | p = h('div', null, 0); 107 | expect(p.children[0].children).to.be.equal('0'); 108 | }); 109 | 110 | it('should render h with nested mixed types', function() { 111 | var p = h('button', null, 'Clicked ', 1, null); 112 | expect(p.children.length).to.be.equal(2); 113 | expect(function() { p.render(); }).not.throw(); 114 | }); 115 | 116 | it('should create boolean props', function() { 117 | var p = h('div', null, h('input', { type: 'checkbox', checked: 'true' })).render(); 118 | expect(p.children[0].getAttribute('checked')).to.be.equal('true'); 119 | var checkbox = p.childNodes[0]; 120 | expect(checkbox.checked).to.be.true; 121 | }); 122 | 123 | it('should create from arrays of childs', function() { 124 | var p = h('div', null , 125 | h('span', null,'child1'), 126 | 'child2', 127 | h('span', null, 'child3'), 128 | [ h('div', null, '1'), h('div', null, '2') ] 129 | ); 130 | 131 | expect(p.children[3].tag).to.be.equal('div'); 132 | }); 133 | 134 | it('should handle nesting functions', function() { 135 | var rendered = sinon.spy(); 136 | var Nested = { 137 | render: function() { 138 | rendered(); 139 | return h('div', null, this.props.name, this.props.children); 140 | } 141 | }; 142 | 143 | var cl = h('div', null, h(Nested, {name: 'hello'}, 'world')); 144 | 145 | expect(rendered.calledOnce).to.be.true; 146 | expect(cl.children[0].children.length).to.be.equal(2); 147 | expect(cl.children[0].children[0].children).to.be.equal('hello'); 148 | expect(cl.children[0].children[1].children).to.be.equal('world'); 149 | }); 150 | 151 | it('should only update props', function() { 152 | var Nested = { 153 | props: { ownProp: 'keepme' }, 154 | render: function() { 155 | return h('div', null, this.props.name, this.props.children, this.props.ownProp); 156 | } 157 | }; 158 | 159 | var cl = h('div', null, h(Nested, {name: 'hello'}, 'world')); 160 | 161 | expect(cl.children[0].children.length).to.be.equal(3); 162 | expect(cl.children[0].children[0].children).to.be.equal('hello'); 163 | expect(cl.children[0].children[1].children).to.be.equal('world'); 164 | expect(cl.children[0].children[2].children).to.be.equal('keepme'); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/patch.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var diff = require('../../').diff; 6 | var patch = require('../../').patch; 7 | var h = require('../../').h; 8 | 9 | var a = require('../fixtures/simple').a; 10 | var b = require('../fixtures/simple').b; 11 | 12 | var a1 = require('../fixtures/simple').a1; 13 | var a2 = require('../fixtures/simple').a2; 14 | 15 | var f = require('../fixtures/simple').f; 16 | var f1 = require('../fixtures/simple').f1; 17 | 18 | var tree1 = require('../fixtures/basic').tree1; 19 | var tree2 = require('../fixtures/basic').tree2; 20 | 21 | describe('Test patch()', function() { 22 | jsdom(); 23 | 24 | it('should patch simple nodes', function() { 25 | var dom = b.render(); 26 | var diffs; 27 | 28 | expect(dom.childNodes[0]).to.be.instanceof(window.Text); 29 | diffs = diff(b, a); 30 | 31 | expect(function() { 32 | patch(b, diffs); 33 | }).not.throw(); 34 | 35 | expect(dom.childNodes[0]).to.be.instanceof(window.HTMLDivElement); 36 | expect(dom.childNodes[0].childNodes[0]).to.be.instanceof(window.Text); 37 | expect(dom.childNodes[0].childNodes[0].textContent) 38 | .to.be.equal('text'); 39 | }); 40 | 41 | it('should patch virtual tree', function() { 42 | a1.render(); 43 | var diffs; 44 | 45 | expect(a1.children).to.be.an('array'); 46 | expect(a1.children[0].children).to.be.a('string'); 47 | expect(a1.children[0].tag).to.be.equal('text'); 48 | 49 | diffs = diff(a1, a2); 50 | patch(a1, diffs); 51 | 52 | expect(a1.children).to.be.an('array'); 53 | expect(a1.children[0].children).to.be.an('array'); 54 | expect(a1.children[0].tag).to.be.equal('div'); 55 | }); 56 | 57 | it('should patch props', function() { 58 | expect(a1.props.className).to.be.equal(a2.props.className); 59 | }); 60 | 61 | it('should reorder nested tree', function() { 62 | f.render(); 63 | 64 | expect(f.children[0].tag).to.be.equal('span'); 65 | expect(f.children[1].tag).to.be.equal('ul'); 66 | 67 | var diffs = diff(f, f1); 68 | patch(f, diffs); 69 | 70 | expect(f.children[1].tag).to.be.equal('span'); 71 | expect(f.children[0].tag).to.be.equal('ul'); 72 | }); 73 | 74 | it('should reorder more complex tree', function() { 75 | tree1.render(); 76 | 77 | expect(tree1.children[0].tag).to.be.equal('em'); 78 | expect(tree1.children[1].tag).to.be.equal('div'); 79 | expect(tree1.children[2].tag).to.be.equal('div'); 80 | expect(tree1.children[3].tag).to.be.equal('span'); 81 | expect(tree1.children[4].tag).to.be.equal('ul'); 82 | 83 | patch(tree1, diff(tree1, tree2)); 84 | 85 | expect(tree1.children[0].tag).to.be.equal('span'); 86 | expect(tree1.children[1].tag).to.be.equal('strong'); 87 | expect(tree1.children[2].tag).to.be.equal('ul'); 88 | expect(tree1.children[3].tag).to.be.equal('div'); 89 | expect(tree1.children[4]).to.be.a('undefined'); 90 | 91 | /* tested reordering */ 92 | expect(tree1.children[2].children[1].children[0].tag).to.be.equal('span'); 93 | expect(tree1.children[2].children[1].children[1].tag).to.be.equal('div'); 94 | expect(tree1.children[2].children[1].children[2]).to.be.a('undefined'); 95 | 96 | }); 97 | 98 | it('should complex tree inners be equal', function() { 99 | expect(tree1.el.innerHTML).to.be.equal(tree2.render().innerHTML); 100 | }); 101 | 102 | it('should replace root node props and text', function() { 103 | var src = h('span', null, 'hello world'); 104 | var dst = h('span', { c : 'e', d : 'z'}, 'bye bye'); 105 | 106 | src.render(); 107 | 108 | expect(src.el.attributes.length).to.be.equal(0); 109 | 110 | patch(src, diff(src, dst)); 111 | 112 | expect(src.children[0].children).to.be.equal('bye bye'); 113 | expect(src.el.attributes['c'].value).to.be.equal('e'); 114 | expect(src.el.attributes['d'].value).to.be.equal('z'); 115 | }); 116 | 117 | it('should remove some props', function() { 118 | var src = h('span', { c : 'e', d : 'z'}, 'hello world'); 119 | var dst = h('span', null, 'bye bye'); 120 | 121 | src.render(); 122 | expect(src.el.attributes.length).to.be.equal(2); 123 | patch(src, diff(src, dst)); 124 | expect(src.el.attributes.length).to.be.equal(0); 125 | }); 126 | 127 | it('should patch boolean props', function() { 128 | var src = h('span', { c : true, d : 'z'}, 'hello world'); 129 | var dst = h('span', { b : true, c : false}, 'bye bye'); 130 | 131 | src.render(); 132 | expect(src.el.attributes.length).to.be.equal(2); 133 | patch(src, diff(src, dst)); 134 | expect(src.el.attributes.length).to.be.equal(2); 135 | expect(src.el.getAttribute('b')).to.be.equal('true'); 136 | expect(src.el.getAttribute('c')).to.be.equal('false'); 137 | }); 138 | 139 | it('should patch empty node', function() { 140 | var t = h('div', null, ''); 141 | var p = h('div', { a : 'b' }, h('span', null, '')); 142 | 143 | t.render(); 144 | patch(t, diff(t, p)); 145 | p.render(); 146 | 147 | expect(t.children.length).to.be.equal(1); 148 | expect(t.children[0].tag).to.be.equal('span'); 149 | expect(t.props.a).to.be.equal('b'); 150 | expect(t.el.getAttribute('a')).to.be.equal('b'); 151 | }); 152 | 153 | it('should patch complex text nodes sequence', function() { 154 | var src = require('../fixtures/textnodes').a; 155 | var dst = require('../fixtures/textnodes').b; 156 | 157 | src.render(); 158 | expect(src.children.length).to.be.equal(1); 159 | patch(src, diff(src, dst)); 160 | expect(src.children.length).to.be.equal(2); 161 | expect(src.children[1].children.length).to.be.equal(5); 162 | }); 163 | 164 | it('should be able patch before render()', function() { 165 | var t = h('div', null, ''); 166 | var p = h('div', { a : 'b' }, h('span', null, 'text')); 167 | 168 | expect(function() { patch(t, diff(t, p)); }).not.throw(); 169 | 170 | expect(t.el).to.be.an('undefined'); 171 | expect(t.children[0].el).to.be.an('undefined'); 172 | expect(t.children[0].children[0].el).to.be.an('undefined'); 173 | expect(t.children[0].children[0].children).to.be.equal('text'); 174 | 175 | t.render(); 176 | 177 | expect(t.el).to.be.instanceof(window.HTMLDivElement); 178 | expect(t.children[0].el).to.be.instanceof(window.HTMLSpanElement); 179 | expect(t.children[0].children[0].el).to.be.instanceof(window.Text); 180 | expect(t.children[0].children[0].children).to.be.equal('text'); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/render.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var a = require('../fixtures/simple').a; 6 | var b = require('../fixtures/simple').b; 7 | var c = require('../fixtures/simple').c; 8 | 9 | var h = require('../../').h; 10 | 11 | describe('Test render()', function() { 12 | jsdom(); 13 | 14 | it('should render simple nodes', function() { 15 | expect(a).to.be.an('object'); 16 | expect(b).to.be.an('object'); 17 | expect(c).to.be.an('object'); 18 | 19 | expect(function() { 20 | a.render(); 21 | b.render(); 22 | c.render(); 23 | }).not.throw(); 24 | }); 25 | 26 | it('shoud render DOM nodes', function() { 27 | var dom = a.render(); 28 | expect(dom).to.be.instanceof(window.HTMLDivElement); 29 | dom = b.render(); 30 | expect(dom).to.be.instanceof(window.HTMLDivElement); 31 | }); 32 | 33 | it('should render text nodes', function() { 34 | var dom = a.render(); 35 | expect(dom.childNodes[0].childNodes[0]).to.be.instanceof(window.Text); 36 | expect(dom.childNodes[0].childNodes[0].textContent).to.be.equal('text'); 37 | }); 38 | 39 | it('should render more complex nodes', function() { 40 | var dom; 41 | 42 | expect(function() { 43 | dom = require('../fixtures/nested').a.render(); 44 | }).not.throw(); 45 | 46 | expect(dom).to.be.instanceof(window.HTMLDivElement); 47 | 48 | expect(dom.childNodes[0]).to.be.instanceof(window.HTMLUListElement); 49 | expect(dom.childNodes[0].childNodes[0]) 50 | .to.be.instanceof(window.HTMLLIElement); 51 | expect(dom.childNodes[0].childNodes[1]) 52 | .to.be.instanceof(window.HTMLLIElement); 53 | expect(dom.childNodes[0].childNodes[2]) 54 | .to.be.instanceof(window.HTMLLIElement); 55 | }); 56 | 57 | it('should render empty text node', function() { 58 | var tree = h('div', { prop1 : 'propval' }, ''); 59 | 60 | expect(function() { tree.render(); }).not.throw(); 61 | }); 62 | 63 | it('should set props', function() { 64 | var tree = h('div', { prop1 : 'propval' }, ''); 65 | expect(tree.props.prop1).to.be.equal('propval'); 66 | tree.render(); 67 | expect(tree.el.getAttribute('prop1')).to.be.equal('propval'); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/svg.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var sinon = require('sinon'); 3 | var jsdom = require('mocha-jsdom'); 4 | 5 | var a = require('../fixtures/svg').a; 6 | var d = require('../fixtures/svg').d; 7 | var h = require('../../').h; 8 | 9 | describe('Test ', function() { 10 | jsdom(); 11 | 12 | it('should create svg tree', function() { 13 | expect(a).to.be.exists; 14 | expect(a).to.be.an('object'); 15 | expect(a.tag).to.be.equal('svg'); 16 | expect(a.children).to.be.an('array'); 17 | }); 18 | 19 | it('should create empty svg node', function() { 20 | var empty = h('svg', null, ''); 21 | expect(empty.tag).to.be.equal('svg'); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /lib/bvd/tests/mocha-tests/unrefobjects.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jsdom = require('mocha-jsdom'); 3 | 4 | var h = require('../../').h; 5 | var diff = require('../../').diff; 6 | var patch = require('../../').patch; 7 | 8 | describe('Test extended h { }', function() { 9 | jsdom(); 10 | 11 | it('should clone h { } object', function() { 12 | var a = h('div', {test : '123'}, 'test'); 13 | var b = a.clone(); 14 | 15 | expect(b).not.equal(a); 16 | expect(b).to.be.instanceof(h); 17 | expect(b.props).not.be.equal(a.props); 18 | expect(b.props).not.be.equal(a.props); 19 | expect(b.children).not.be.equal(a.children); 20 | 21 | b.render(); 22 | 23 | expect(a.el).to.be.an('undefined'); 24 | expect(a.children[0].el).to.be.an('undefined'); 25 | }); 26 | 27 | it('should clone references ->', function() { 28 | var refFn = function() {}; 29 | var a = h('div', { onClick : refFn, test : '123' }, 'test'); 30 | var b = a.clone(); 31 | 32 | expect(a).not.equal(b); 33 | expect(a.props).not.equal(b.props); 34 | expect(a.props.onClick).to.equal(b.props.onClick); 35 | }); 36 | 37 | it('should not affect cloned node', function() { 38 | var a = require('../fixtures/nested').a1; 39 | var b = require('../fixtures/nested').b1; 40 | 41 | var c = a.clone(); 42 | 43 | c.render(); 44 | 45 | patch(c, diff(c, b)); 46 | 47 | expect(function() { 48 | 49 | var nextTree = function(a) { 50 | if (typeof a === 'object' && a.el) throw new Error('Element found'); 51 | 52 | if (Array.isArray(a.children)) 53 | for(var i = 0; i < a.children.length; i++) { 54 | nextTree(a.children[i]); 55 | } 56 | }; 57 | 58 | nextTree(a); 59 | nextTree(b); 60 | nextTree(c.clone()); 61 | }).not.throw(); 62 | 63 | }); 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /lib/cake.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Cake is an application constructor. 4 | */ 5 | 6 | require('./zefir'); 7 | var Cream = require('./cream'); 8 | var Container = require('./container'); 9 | var Mixer = require('./mixer'); 10 | 11 | var bvd = require('./dom'); 12 | 13 | var Cake = Cream.extend({ 14 | _namespace : 'cake', 15 | 16 | zefir : Container.inject('zefir'), 17 | 18 | tree : null, 19 | 20 | /** 21 | * Routes shortcut 22 | * 23 | * @name route 24 | * @function 25 | * @access public 26 | * @param {String} path url pattern 27 | * @param {String} cream Cream namsespace 28 | */ 29 | route : function(path, cream) { 30 | this.get('zefir').route(path, cream); 31 | return this; 32 | }, 33 | 34 | /** 35 | * Cake initializetion function 36 | * 37 | * @name create 38 | * @function 39 | * @access public 40 | * @param {Object} opts Application options 41 | */ 42 | create : function(opts) { 43 | opts = opts || {}; 44 | this.set('opts', opts); 45 | this._createElement(opts.element); 46 | 47 | Mixer.run(); 48 | this._namespaceWatcher(); 49 | this._updateWatcher(); 50 | return this; 51 | }, 52 | 53 | /** 54 | * Application destroyer 55 | * 56 | * @name destroy 57 | * @function 58 | * @access public 59 | */ 60 | destroy : function() { 61 | Mixer.stop(); 62 | this.set('zefir.current', null); 63 | this.set('zefir.routes', []); 64 | this._removeTree(); 65 | }, 66 | 67 | render : function() { 68 | var cream = this.get('loaded'); 69 | 70 | if (cream && typeof cream.render === 'function') { 71 | if (!this.tree && this.opts.createRoot === false) { 72 | this.set('tree', cream.render()); 73 | this.element.appendChild(this.tree.render()); 74 | } else if (this.tree && this.opts.createRoot !== false) { 75 | bvd.patch(this.tree, bvd.diff(this.tree, this._createRoot(cream.render()))); 76 | } else { 77 | bvd.patch(this.tree, bvd.diff(this.tree, cream.render())); 78 | } 79 | cream._didMount(this.tree); 80 | } 81 | }, 82 | 83 | _updateWatcher : function() { 84 | if (this.loaded && this.loaded._updated === true) { 85 | this.render(); 86 | this.set('loaded._updated', false); 87 | } 88 | 89 | Mixer.next(this._updateWatcher); 90 | }, 91 | 92 | _namespaceWatcher : function() { 93 | var self = this; 94 | var zefirRe = new RegExp(/^zefir.*/), cakeRe = new RegExp(/^cake.*/); 95 | 96 | this._emitter.on('setProp', function(name) { 97 | if (self.loaded && self.loaded._updated !== true) { 98 | if (name.match(zefirRe) || name.match(cakeRe)) { 99 | return; 100 | } 101 | self.set('loaded._updated', true); 102 | } 103 | }); 104 | 105 | /** 106 | * Post load initializer 107 | */ 108 | this._emitter.on(/^register/, function() { 109 | if (self.get('zefir')) { 110 | self.get('zefir').deviceWatcher(); 111 | self.get('zefir').locationWatcher(self.get('zefir.location')); 112 | } 113 | }); 114 | }, 115 | 116 | _createElement : function(element) { 117 | if (!element) { 118 | element = document.getElementById('cake'); 119 | 120 | if (element) { 121 | document.body.removeChild(element); 122 | } 123 | 124 | element = document.body; 125 | } 126 | 127 | if (this.get('opts.createRoot') !== false) { 128 | this.set('tree', this._createRoot('')); 129 | element.appendChild(this.tree.render()); 130 | } 131 | 132 | this.set('element', element); 133 | }, 134 | 135 | _createRoot : function(children) { 136 | return bvd.h('div', { 137 | className : this.opts.elementClass || 'cake', 138 | id : this.opts.elementId || 'cake'}, children); 139 | }, 140 | 141 | _removeTree : function() { 142 | if (this.tree) { 143 | this.tree.el.parentNode.removeChild(this.tree.el); 144 | this.set('tree', null); 145 | } 146 | }, 147 | 148 | _unloadComponent : function() { 149 | if (this.get('loaded')) { 150 | var cream = this.get('loaded'); 151 | cream._didTransition(); 152 | 153 | delete cream.props; 154 | delete cream.params; 155 | 156 | this.set('loaded', null); 157 | } 158 | }, 159 | 160 | _loadComponent : function() { 161 | var route = this.get('zefir.current'); 162 | 163 | if (!route) { 164 | this._unloadComponent(); 165 | return; 166 | } 167 | 168 | var cream = Container.inject(route.cream)(); 169 | 170 | /** 171 | * No cream were registered, maybe later 172 | */ 173 | if (!cream) { 174 | this._unloadComponent(); 175 | return; 176 | } 177 | 178 | if (this.loaded === cream && 179 | route.props === this.get('loaded.props') && 180 | route.params === this.get('loaded.params')) { 181 | 182 | this.get('loaded')._didTransition(); 183 | this.set('loaded._updated', true); 184 | return; 185 | } 186 | 187 | this._unloadComponent(); 188 | 189 | this.set('loaded', cream); 190 | 191 | cream.props = Container.inject('zefir.current.props'); 192 | cream.params = Container.inject('zefir.current.params'); 193 | cream._init(); 194 | cream._willTransition(); 195 | 196 | this.set('loaded._updated', true); 197 | 198 | }.observes('zefir.current') 199 | }); 200 | 201 | module.exports = Cake; 202 | -------------------------------------------------------------------------------- /lib/caramel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Caramel (eventemitter) with support of 3 | * state listeners and regular expressions. 4 | * 5 | * Following features are supported: 6 | * 7 | * on 8 | * once 9 | * off 10 | * emit 11 | * has 12 | * 13 | * @name caramel 14 | * @function 15 | * @access public 16 | */ 17 | var caramel = function() { 18 | this.listeners = []; 19 | }; 20 | 21 | caramel.prototype.on = function(eventName, listener) { 22 | this._subscribe(eventName, TYPE_MANY, listener); 23 | }; 24 | 25 | caramel.prototype.once = function(eventName, listener) { 26 | this._subscribe(eventName, TYPE_ONCE, listener); 27 | }; 28 | 29 | caramel.prototype.emit = function(eventName, data) { 30 | this._fireEvent(eventName, data); 31 | }; 32 | 33 | caramel.prototype.has = function(eventName, listener) { 34 | return this._listenerFor(eventName, listener) ? true : false; 35 | }; 36 | 37 | caramel.prototype.off = function(eventName, listener) { 38 | this._unsubscribe(eventName, listener); 39 | }; 40 | 41 | caramel.prototype._fireEvent = function(eventName, data) { 42 | if ((typeof eventName !== 'string' || eventName.length < 1) && 43 | !(eventName instanceof RegExp)) { 44 | throw new Error('Wrong arguments ' + eventName); 45 | } 46 | 47 | for (var i = 0; i < this.listeners.length; i++) { 48 | var event = this.listeners[i]; 49 | 50 | if (event.eventType === TYPE_REGEXP && event.eventName.test(eventName) || 51 | event.eventType === TYPE_STRING && event.eventName === eventName || 52 | event.eventType === TYPE_STRING && (eventName instanceof RegExp) && 53 | eventName.test(event.eventName) 54 | ) { 55 | 56 | event.listener(data); 57 | 58 | if (event.runType === TYPE_ONCE) { 59 | this._unsubscribe(event.eventName, event.listener); 60 | } 61 | } 62 | } 63 | }; 64 | 65 | caramel.prototype._unsubscribe = function(eventName, listener) { 66 | var toRemove = [], i; 67 | 68 | for (i = 0; i < this.listeners.length; i++) { 69 | var l = this.listeners[i], remove = false; 70 | 71 | if (l.eventName === eventName) { 72 | remove = true; 73 | } else if (eventName instanceof RegExp && eventName.test(l.eventName)) { 74 | remove = true; 75 | } 76 | 77 | if (remove === true) { 78 | if (typeof listener === 'undefined') { 79 | toRemove.push(i); 80 | } else if (this.listeners[i].listener === listener) { 81 | toRemove.push(i); 82 | } 83 | } 84 | } 85 | 86 | for (i = toRemove.length -1; i >= 0; i--) { 87 | this.listeners.splice(toRemove[i], 1); 88 | } 89 | }; 90 | 91 | caramel.prototype._subscribe = function(eventName, type, listener) { 92 | if (!isValidProps(eventName, listener)) { 93 | throw new Error('Wrong arguments ' + eventName + ' ' + listener); 94 | } 95 | 96 | if (this.has(eventName, listener)) { 97 | throw new Error('Such listener already exists'); 98 | } 99 | 100 | if (this.listeners.length >= MAX_LISTENERS) { 101 | throw new Error('Limit of the listeners exceed MAX_LISTENERS = ' + MAX_LISTENERS); 102 | } 103 | 104 | var event = { 105 | eventName : eventName, 106 | listener : listener, 107 | runType : type, 108 | eventType : (eventName instanceof RegExp) ? TYPE_REGEXP : TYPE_STRING 109 | }; 110 | 111 | this.listeners.push(event); 112 | }; 113 | 114 | caramel.prototype._listenerFor = function(eventName, listener) { 115 | for (var i = 0; i < this.listeners.length; i++) { 116 | if (this.listeners[i].eventName === eventName && 117 | this.listeners[i].listener === listener) { 118 | return this.listeners[i]; 119 | } 120 | } 121 | }; 122 | 123 | var isValidEventName = function(eventName) { 124 | return (eventName && 125 | (typeof eventName === 'string' || 126 | (eventName instanceof RegExp))); 127 | }; 128 | 129 | var isValidProps = function(eventName, listener) { 130 | return (isValidEventName(eventName) && typeof listener === 'function'); 131 | }; 132 | 133 | var TYPE_ONCE = 0; 134 | var TYPE_MANY = 1; 135 | var TYPE_REGEXP = 2; 136 | var TYPE_STRING = 3; 137 | var MAX_LISTENERS = 256; 138 | 139 | module.exports = caramel; -------------------------------------------------------------------------------- /lib/container.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Container of the different ingredients to make a cake 3 | * 4 | */ 5 | var Caramel = require('./caramel'), 6 | caramel = new Caramel(), 7 | container = {}, 8 | observers = []; 9 | 10 | /** 11 | * Resolve property change observers 12 | */ 13 | caramel.on('setProp', function(name) { 14 | for (var i = 0; i < observers.length; i++) { 15 | var observer = observers[i]; 16 | 17 | for (var j = 0; j < observer.prop.length; j++) { 18 | var prop = observer.prop[j]; 19 | var longName = observer.cream._namespace + '.' + prop; 20 | 21 | if (longName === name || prop === name || 22 | (prop instanceof RegExp && prop.test(name))) { 23 | observer.fn(); 24 | } 25 | } 26 | } 27 | }); 28 | 29 | /** 30 | * Newly created Cream initializer inside of the container 31 | */ 32 | var createCream = function(name, obj) { 33 | obj._namespace = name; 34 | observers = observers.concat(obj._observers); 35 | obj._emitter = caramel; 36 | }; 37 | 38 | /** 39 | * Deinitializer 40 | */ 41 | var removeCream = function(name, obj) { 42 | if (obj._observers.length > 0) { 43 | observers = observers.filter(function(o) { 44 | return obj._observers.indexOf(o) === -1; 45 | }); 46 | } 47 | obj._destroy(); 48 | }; 49 | 50 | /** 51 | * DI register method 52 | * 53 | * @name register 54 | * @function 55 | * @access public 56 | * @param {String} name namespace register object into 57 | * @param {Cream} obj 58 | * @param {String} after namespace register object after 59 | */ 60 | var register = function(name, obj, after) { 61 | if (typeof after === 'string' && !inject(after)()) { 62 | caramel.once('register:' + after, function() { 63 | register(name, obj); 64 | }); 65 | return; 66 | } 67 | 68 | var path = name.split('.'); 69 | var ref = container; 70 | 71 | for (var i = 0; i < path.length - 1; i++) { 72 | if (!ref[path[i]]) { 73 | ref = ref[path[i]] = {}; 74 | } else { 75 | ref = ref[path[i]]; 76 | } 77 | } 78 | 79 | if (!ref[path[path.length - 1]]) { 80 | ref[path[path.length - 1]] = { cream : obj }; 81 | } else { 82 | ref[path[path.length - 1]].cream = obj; 83 | } 84 | 85 | createCream(name, obj); 86 | caramel.emit('register:' + name); 87 | }; 88 | 89 | /** 90 | * DI remove method 91 | * 92 | * @name unregister 93 | * @function 94 | * @access public 95 | * @param {String} name namespace remove from 96 | */ 97 | var unregister = function(name) { 98 | var path = name.split('.'); 99 | var ref = container; 100 | 101 | for (var i = 0; i < path.length - 1; i++) { 102 | if (!(ref = ref[path[i]])) { 103 | return; 104 | } 105 | } 106 | 107 | if (typeof ref[path[path.length - 1]] === 'object') { 108 | /** 109 | * Destroy registered Cream 110 | */ 111 | if (ref[path[path.length - 1]]._type === 'Cream') { 112 | removeCream(name, ref[path[path.length - 1]].cream); 113 | 114 | } else if (ref[path[path.length - 1]].cream && 115 | ref[path[path.length - 1]].cream._type === 'Cream') { 116 | 117 | removeCream(name, ref[path[path.length - 1]].cream); 118 | 119 | delete ref[path[path.length - 1]].cream; 120 | } 121 | 122 | delete ref[path[path.length - 1]]; 123 | } 124 | }; 125 | 126 | /** 127 | * DI inject 128 | * 129 | * @name inject 130 | * @function 131 | * @access public 132 | * @param {String} name namespace inject object from 133 | * @returns {function} injection resolver function 134 | */ 135 | var inject = function(name) { 136 | 137 | var injection = function() { 138 | var path = name.split('.'); 139 | var ref = container; 140 | 141 | for (var i = 0; i < path.length; i++) { 142 | 143 | if (!ref[path[i]]) { 144 | if (ref.cream) { 145 | ref = ref.cream; 146 | i--; 147 | continue; 148 | } 149 | 150 | return; 151 | } else { 152 | ref = ref[path[i]]; 153 | } 154 | } 155 | 156 | return ref.cream || ref; 157 | }; 158 | 159 | injection.isInjection = true; 160 | injection.namespace = name; 161 | 162 | // if (!skipCache) { 163 | // caramel.once('register:' + name, function() { 164 | // injection(); 165 | // }); 166 | // } 167 | 168 | return injection; 169 | }; 170 | 171 | module.exports = { 172 | _container : container, 173 | register : register, 174 | unregister : unregister, 175 | inject : inject 176 | }; 177 | -------------------------------------------------------------------------------- /lib/cream.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Cream is a base ingredient 4 | */ 5 | 6 | /** 7 | * Sugar function prototypes (observes, property) 8 | */ 9 | require('./recipes/fn'); 10 | var Container = require('./container'); 11 | 12 | /** 13 | * Base ingredient of any cake 14 | * 15 | * @name Cream 16 | * @function 17 | * @access public 18 | */ 19 | var Cream = function() { 20 | this._type = 'Cream'; 21 | this._observers = []; 22 | this._namespace = null; 23 | this._emitter; 24 | }; 25 | 26 | Cream.prototype.get = function(name) { 27 | var ref = this; 28 | var path = name.split('.'); 29 | for (var i = 0; i < path.length; i++) { 30 | /** 31 | * Nor propery injected 32 | */ 33 | if (typeof ref[path[i]] === 'function' && ref[path[i]].isInjection) { 34 | ref = ref[path[i]](); 35 | continue; 36 | } 37 | 38 | if ((ref = ref[path[i]]) === undefined) { 39 | return; 40 | } 41 | } 42 | 43 | /** 44 | * It is property function 45 | */ 46 | if (ref && typeof ref === 'object' && ref.isProperty === true) { 47 | return ref.fn.call(ref.parent); 48 | } 49 | 50 | return ref; 51 | }; 52 | 53 | Cream.prototype.set = function(name, value) { 54 | return this._setProp(name, 'set', value); 55 | }; 56 | 57 | Cream.prototype.push = function(name) { 58 | return this._setProp(name, 'push', [].slice.call(arguments, 1)); 59 | }; 60 | 61 | Cream.prototype.unshift = function(name) { 62 | return this._setProp(name, 'unshift', [].slice.call(arguments, 1)); 63 | }; 64 | 65 | Cream.prototype.splice = function(name) { 66 | return this._setProp(name, 'splice', [].slice.call(arguments, 1)); 67 | }; 68 | 69 | Cream.prototype.pop = function(name) { 70 | return this._setProp(name, 'pop', [].slice.call(arguments, 1)); 71 | }; 72 | 73 | Cream.prototype.shift = function(name) { 74 | return this._setProp(name, 'shift', [].slice.call(arguments, 1)); 75 | }; 76 | 77 | Cream.prototype._setProp = function(name, fnName, args) { 78 | 79 | var result; 80 | 81 | if (this._isValidLocalProp(name)) { 82 | /** 83 | * Setup local property 84 | */ 85 | if (fnName === 'set') { 86 | this._getPropParent(name)[this._getPropName(name)] = args; 87 | result = true; 88 | } else { 89 | result = this.get(name)[fnName].apply(this.get(name), args); 90 | } 91 | this.notifyPropertyChange(name); 92 | } else { 93 | /** 94 | * Setup injected reference 95 | */ 96 | var nsRef = this._getPropertyRef(name); 97 | 98 | if (nsRef) { 99 | result = nsRef.ref._setProp(nsRef.name , fnName, args); 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | Cream.prototype._init = function() { 107 | ('init' in this) && this.init(); 108 | }; 109 | 110 | Cream.prototype._destroy = function() { 111 | ('destroy' in this) && this.destroy(); 112 | }; 113 | 114 | Cream.prototype._didTransition = function() { 115 | ('didTransition' in this) && this.didTransition(); 116 | }; 117 | 118 | Cream.prototype._willTransition = function() { 119 | ('willTransition' in this) && this.willTransition(); 120 | }; 121 | 122 | Cream.prototype._didMount = function(h) { 123 | ('didMount' in this) && this.didMount(h); 124 | }; 125 | 126 | Cream.prototype.notifyPropertyChange = function(name) { 127 | this._emitter.emit('setProp', this._namespace + '.' + name); 128 | }; 129 | 130 | Cream.prototype._isValidLocalProp = function(name) { 131 | if (!this._namespace) { 132 | throw new Error('Cream should be registered before you set something'); 133 | } 134 | 135 | if (typeof name !== 'string' || name.length < 1) { 136 | throw new Error('Tying to set value with wrong name'); 137 | } 138 | 139 | var prns = this._propNamespace(name); 140 | 141 | if (prns === undefined || prns === this._namespace) { 142 | return true; 143 | } 144 | }; 145 | 146 | /* 147 | * Reference of the namespace (injectable props) 148 | */ 149 | Cream.prototype._getPropertyRef = function(name) { 150 | var path = this._propAbsolutePath(name).split('.'); 151 | var nsRef; 152 | for (var i = path.length; i >= 0; i--) { 153 | nsRef = Container.inject(path.slice(0, i).join('.'))(); 154 | 155 | /* 156 | * Resolve parent of the namespace 157 | */ 158 | if (nsRef instanceof Cream) { 159 | return { 160 | ref : nsRef, 161 | name : path.slice(i).join('.') 162 | }; 163 | } 164 | } 165 | }; 166 | 167 | Cream.prototype._propAbsolutePath = function(name) { 168 | var ns = this._propNamespace(name); 169 | 170 | if (ns) { 171 | if (ns !== this._namespace) { 172 | var nsName = name.split('.').slice(1).join('.'); 173 | /** 174 | * Directly injected props (aka alias) 175 | */ 176 | if (nsName) { 177 | return ns + '.' + name.split('.').slice(1).join('.'); 178 | } else { 179 | return ns; 180 | } 181 | } 182 | return ns + '.' + name; 183 | } 184 | 185 | return name; 186 | }; 187 | 188 | Cream.prototype._getPropName = function(name) { 189 | return name.split('.').pop(); 190 | }; 191 | 192 | Cream.prototype._getPropParent = function(name) { 193 | var ref = this; 194 | var path = name.split('.'); 195 | 196 | for (var i = 0; i < path.length - 1; i++) { 197 | if ((ref = ref[path[i]]) === undefined) { 198 | return; 199 | } 200 | } 201 | 202 | return ref; 203 | }; 204 | 205 | Cream.prototype._propNamespace = function(name) { 206 | var ref = this, i; 207 | var path = name.split('.'); 208 | 209 | for (i = 0; i < path.length; i++) { 210 | if ((ref = ref[path[i]]) === undefined) { 211 | return; 212 | } 213 | 214 | if (typeof ref === 'function' && ref.isInjection) { 215 | return ref.namespace; 216 | } 217 | } 218 | 219 | return this._namespace; 220 | }; 221 | 222 | Cream.prototype._addObserver = function(observer) { 223 | if (observer.prop.length < 1) { return; } 224 | 225 | var self = this; 226 | 227 | this._observers.push({ 228 | cream : this, 229 | prop : observer.prop.map(function(p) { 230 | if (typeof p === 'string') { 231 | return self._propAbsolutePath(p); 232 | } 233 | return p; 234 | }), 235 | fn : observer.fn.bind(this) 236 | }); 237 | }; 238 | 239 | Cream.prototype.extend = function(obj) { 240 | var newCream = Cream.extend(obj); 241 | 242 | for (var i in this) { 243 | if (!newCream[i]) { 244 | newCream[i] = this[i]; 245 | } 246 | } 247 | 248 | return newCream; 249 | }; 250 | 251 | Cream.extend = function(obj) { 252 | var F = function() {}; 253 | F.prototype = new Cream(); 254 | F = new F(); 255 | 256 | for (var i in obj) { 257 | var descr = Object.getOwnPropertyDescriptor(obj, i); 258 | 259 | if (descr !== undefined) { 260 | F[i] = typeof obj[i] === 'function' && 261 | !obj[i].isInjection ? obj[i].bind(F) : obj[i]; 262 | 263 | 264 | if (F[i] && typeof F[i] === 'object' && F[i].isObserver === true) { 265 | F._addObserver(F[i]); 266 | } 267 | 268 | if (obj[i] && typeof obj[i] === 'object' && obj[i].isProperty) { 269 | obj[i].parent = F; 270 | } 271 | } 272 | } 273 | 274 | if ('_namespace' in obj) { 275 | if ('_after' in obj) { 276 | Container.register(obj._namespace, F, obj._after); 277 | } else { 278 | Container.register(obj._namespace, F); 279 | } 280 | } 281 | 282 | return F; 283 | }; 284 | 285 | module.exports = Cream; 286 | -------------------------------------------------------------------------------- /lib/dom.js: -------------------------------------------------------------------------------- 1 | var dom = require('./bvd'); 2 | 3 | module.exports = dom; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Container = require('./container'); 2 | var Cream = require('./cream'); 3 | var Cake = require('./cake'); 4 | 5 | module.exports = { 6 | _container : Container._container, 7 | _mixer : require('./mixer'), 8 | h : require('./dom').h, 9 | next : require('./mixer').next, 10 | register : Container.register, 11 | unregister : Container.unregister, 12 | inject : Container.inject, 13 | Cream : Cream, 14 | create : Cake.create, 15 | destroy : Cake.destroy 16 | }; -------------------------------------------------------------------------------- /lib/mixer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Mixer 4 | */ 5 | 6 | var raf = require('./recipes/rafcaf').raf, 7 | caf = require('./recipes/rafcaf').caf; 8 | 9 | var frame = null; 10 | var nextQueue = []; 11 | var isRunning = false; 12 | 13 | /** 14 | * next tick queue handler 15 | */ 16 | var nextTick = function() { 17 | var queue = nextQueue.splice(0); 18 | 19 | for (var i = 0; i < queue.length; i++) { 20 | var task = queue[i]; 21 | 22 | if (task && typeof task === 'function') { 23 | task(); 24 | } 25 | } 26 | }; 27 | 28 | /** 29 | * Mixer loop runner 30 | * 31 | * @name run 32 | * @function 33 | * @access public 34 | */ 35 | var run = function() { 36 | if (isRunning === true) { 37 | throw new Error('Mixer already running'); 38 | } 39 | 40 | isRunning = true; 41 | 42 | var loop = function() { 43 | nextTick(); 44 | frame = raf(loop); 45 | }; 46 | 47 | loop(); 48 | }; 49 | 50 | /** 51 | * Stop mixer 52 | * 53 | * @name stop 54 | * @function 55 | * @access public 56 | */ 57 | var stop = function() { 58 | caf(frame); 59 | nextTick(); 60 | isRunning = false; 61 | }; 62 | 63 | /** 64 | * Next tick runner 65 | * 66 | * @name next 67 | * @function 68 | * @access public 69 | * @param {Function} fn function to run 70 | */ 71 | var next = function(fn) { 72 | nextQueue.push(fn); 73 | }; 74 | 75 | module.exports = { 76 | run : run, 77 | stop : stop, 78 | next : next 79 | }; 80 | -------------------------------------------------------------------------------- /lib/recipes/fn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Function sugar 3 | */ 4 | 5 | Function.prototype.property = function() { 6 | return { 7 | isProperty : true, 8 | fn : this 9 | }; 10 | }; 11 | 12 | Function.prototype.observes = function() { 13 | return { 14 | isProperty : true, 15 | isObserver : true, 16 | prop : [].slice.call(arguments), 17 | fn : this 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/recipes/rafcaf.js: -------------------------------------------------------------------------------- 1 | /* eslint no-undef : 0 */ 2 | var win; 3 | 4 | try { 5 | win = global.window || window; 6 | } catch (e) { 7 | win = {}; 8 | } 9 | 10 | var request = 11 | win.requestAnimationFrame || 12 | win.webkitRequestAnimationFrame || 13 | win.mozRequestAnimationFrame || 14 | win.oRequestAnimationFrame || 15 | win.msRequestAnimationFrame || 16 | function(callback){ 17 | return setTimeout(function(){ 18 | callback(); 19 | }, 1e3 / 60); 20 | }; 21 | 22 | var cancel = 23 | win.cancelAnimationFrame || 24 | win.webkitCancelAnimationFrame || 25 | win.webkitCancelRequestAnimationFrame || 26 | win.mozCancelAnimationFrame || 27 | win.oCancelAnimationFrame || 28 | win.msCancelAnimationFrame || 29 | function(timeout){ 30 | clearTimeout(timeout); 31 | }; 32 | 33 | var raf = function(){ 34 | return request.apply(win, arguments); 35 | }; 36 | 37 | var caf = function(){ 38 | return cancel.apply(win, arguments); 39 | }; 40 | 41 | module.exports = { 42 | raf : raf, 43 | caf : caf 44 | }; 45 | -------------------------------------------------------------------------------- /lib/zefir.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * A little bit of zefir to make cake nicer 4 | */ 5 | 6 | var Cream = require('./cream'); 7 | var next = require('./mixer').next; 8 | 9 | var Zefir = Cream.extend({ 10 | /** 11 | * Yeah, zefir is a most top thing on the cake 12 | */ 13 | _namespace : 'zefir', 14 | 15 | /** 16 | * 17 | * Routes container. 18 | * 19 | * Format: 20 | * { 21 | * filter : /path/:id/name 22 | * cream : path.cream.location 23 | * } 24 | * 25 | */ 26 | routes : [], 27 | 28 | 29 | /** 30 | * Default location 31 | */ 32 | location : '/', 33 | 34 | _createOrigin : function(url) { 35 | var origin; 36 | 37 | if (url.match(/\#/)) { 38 | url = '-//-/#' + url.split('#')[1]; 39 | } 40 | 41 | origin = url 42 | .replace(/^.+?\/\/+[^\/]+/, '') 43 | .split(/[/#]/).join('/').replace('//', '') 44 | .split(/[?]/); 45 | 46 | return { 47 | path : origin[0].replace(/\/$/g, '') || '/', 48 | params : decodeURIComponent(origin[1] || '') 49 | }; 50 | }, 51 | 52 | _pathProps : function(filter, path) { 53 | if (path.match(new RegExp('^' + filter + '$'))) { 54 | return {}; 55 | } 56 | 57 | var f = new RegExp('^' + 58 | filter.replace(/:\w+/g, '\(\\w+\)') 59 | .replace(/\//gi, '\\/') + '$'); 60 | 61 | var props = path.match(f); 62 | 63 | if (!props) { 64 | return; 65 | } 66 | 67 | var propNames = {}; 68 | 69 | filter.match(/:\w+/gi).slice().map(function(p, i) { 70 | p = p.replace(':', ''); 71 | propNames[p] = props[i + 1]; 72 | }); 73 | 74 | return propNames; 75 | }, 76 | 77 | _queryProps : function(origin) { 78 | var params = {}; 79 | var tmp = origin.split(/[=&]/); 80 | 81 | for (var i = 0; i < tmp.length; i++) { 82 | params[tmp[i]] = tmp[++i]; 83 | } 84 | 85 | return params; 86 | }, 87 | 88 | /** 89 | * Insert routes 90 | * 91 | * @name addRoute 92 | * @function 93 | * @access public 94 | * @param {String} filter route filter string 95 | * @param {String} cream registered cream namespace 96 | */ 97 | 98 | route : function(filter, cream) { 99 | var r = { 100 | filter : filter, 101 | cream : cream 102 | }; 103 | 104 | if (filter === '*') { 105 | this.push('routes', r); 106 | } else { 107 | this.unshift('routes', r); 108 | } 109 | 110 | this.locationWatcher(this.location); 111 | }, 112 | 113 | locationWatcher : function(location) { 114 | var self = this; 115 | var origin = this._createOrigin(location); 116 | 117 | var route = this.get('routes').map(function(r) { 118 | if (r.filter === '*') { 119 | return { 120 | cream : r.cream, 121 | props : {}, 122 | params : {} 123 | }; 124 | } 125 | 126 | var props = self._pathProps(r.filter, origin.path); 127 | 128 | if (!props) return false; 129 | 130 | return { 131 | cream : r.cream, 132 | props : props, 133 | params : {} 134 | }; 135 | }).filter(function(r) { return r; }).shift(); 136 | 137 | if (route) { 138 | if (origin.params) { 139 | route.params = this._queryProps(origin.params); 140 | } 141 | } 142 | 143 | this.set('current', route); 144 | }, 145 | 146 | deviceWatcher : function() { 147 | if (window && window.location) { 148 | if (this.location !== window.location.href) { 149 | this.location = String(window.location.href); 150 | this.locationWatcher(this.location); 151 | } 152 | } 153 | 154 | next(this.deviceWatcher.bind(this)); 155 | } 156 | }); 157 | 158 | module.exports = Zefir; 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cakejs2", 3 | "version": "0.2.1", 4 | "description": "Lightweight front-end framework with only best parts and features of most awesome frameworks.", 5 | "main": "index.js", 6 | "files": [ 7 | "dist", 8 | "contrib", 9 | "lib", 10 | "index.js", 11 | "README.md", 12 | "LICENSE" 13 | ], 14 | "devDependencies": { 15 | "browserify": "^13.1.1", 16 | "chai": "^3.5.0", 17 | "coveralls": "^2.11.15", 18 | "eslint": "^3.11.1", 19 | "jsdom": "^9.8.3", 20 | "mocha": "^3.2.0", 21 | "nyc": "^10.0.0", 22 | "sinon": "^1.17.6", 23 | "uglify-js": "^2.7.5" 24 | }, 25 | "scripts": { 26 | "test": "npm run ci", 27 | "mocha": "mocha tests/**", 28 | "lint": "eslint lib/** tests/**", 29 | "ci": "npm run lint && npm run mocha", 30 | "cov": "nyc npm run ci && nyc report --reporter=text-lcov | coveralls", 31 | "prepublish": "mkdir -p dist && browserify --ignore-missing contrib/dist.js > dist/cake.js && browserify --ignore-missing contrib/dist.js | uglifyjs -c > dist/cake.min.js" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/linuxenko/cakejs2.git" 36 | }, 37 | "keywords": [], 38 | "author": "Svetlana Linuxenko", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/linuxenko/cakejs2/issues" 42 | }, 43 | "homepage": "https://github.com/linuxenko/cakejs2#readme" 44 | } 45 | -------------------------------------------------------------------------------- /tests/cake.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var sinon = require('sinon'); 3 | var h = require('../').h; 4 | var cake = require('../'); 5 | var c; 6 | 7 | var jsdom = require('jsdom'); 8 | var doc = jsdom.jsdom('',{ url: 'http://test/'}); 9 | var win = doc.defaultView; 10 | global.document = doc; 11 | global.window = win; 12 | propagateToGlobal(win); 13 | 14 | function propagateToGlobal (window) { 15 | for (var key in window) { 16 | if (!window.hasOwnProperty(key)) continue; 17 | if (key in global) continue; 18 | 19 | global[key] = window[key]; 20 | } 21 | } 22 | 23 | describe('Cake', function() { 24 | before(function() { 25 | c = cake.create(); 26 | }); 27 | 28 | after(function() { 29 | c.get('zefir').set('routes', []); 30 | c.destroy(); 31 | cake.unregister('routes'); 32 | }); 33 | 34 | beforeEach(function() { 35 | c.get('zefir').set('routes', []); 36 | }); 37 | 38 | afterEach(function() { 39 | cake.unregister('routes'); 40 | }); 41 | 42 | it('should bake some cake', function() { 43 | var r = cake.Cream.extend({ 44 | _namespace : 'routes.hello', 45 | }); 46 | 47 | c.route('/home/:id', 'routes.hello'); 48 | c.route('/posts/:name/post/', 'routes.hello'); 49 | jsdom.changeURL(window, 'http://localhost/home/123?test=321'); 50 | c.get('zefir').deviceWatcher(); 51 | 52 | expect(c.get('loaded._namespace')).to.be.equal(r._namespace); 53 | 54 | jsdom.changeURL(window, 'http://localhost/ll'); 55 | c.get('zefir').deviceWatcher(); 56 | 57 | expect(c.get('loaded')).to.be.null; 58 | }); 59 | 60 | it('should call transition events', function() { 61 | var didTransition = sinon.spy(); 62 | var willTransition = sinon.spy(); 63 | 64 | cake.Cream.extend({ 65 | _namespace : 'routes.hello', 66 | didTransition : didTransition, 67 | willTransition : willTransition 68 | }); 69 | 70 | c.route('/home/:id', 'routes.hello'); 71 | jsdom.changeURL(window, 'http://localhost/home/123?test=321'); 72 | c.get('zefir').deviceWatcher(); 73 | 74 | expect(willTransition.calledOnce).to.be.true; 75 | expect(didTransition.calledOnce).to.be.false; 76 | 77 | jsdom.changeURL(window, 'http://localhost/home/12?test=321'); 78 | c.get('zefir').deviceWatcher(); 79 | 80 | expect(didTransition.calledOnce).to.be.true; 81 | expect(willTransition.calledOnce).to.be.true; 82 | }); 83 | 84 | it('should provide props and params', function() { 85 | var r = cake.Cream.extend({ 86 | _namespace : 'routes.hello' 87 | }); 88 | 89 | c.route('/home/:id', 'routes.hello'); 90 | jsdom.changeURL(window, 'http://localhost/home/123?test=321'); 91 | c.get('zefir').deviceWatcher(); 92 | 93 | expect(r.get('props.id')).to.be.equal('123'); 94 | expect(r.get('params.test')).to.be.equal('321'); 95 | 96 | jsdom.changeURL(window, 'http://localhost/home/2?test=21'); 97 | c.get('zefir').deviceWatcher(); 98 | 99 | expect(r.get('props.id')).to.be.equal('2'); 100 | expect(r.get('params.test')).to.be.equal('21'); 101 | }); 102 | 103 | it('should render from provided namespace', function() { 104 | var rndrSpy = sinon.spy(); 105 | jsdom.changeURL(window, 'http://localhost/ho/3?test=321'); 106 | c.get('zefir').deviceWatcher(); 107 | 108 | var r = cake.Cream.extend({ 109 | _namespace : 'routes.hello', 110 | helloText : 'hello', 111 | 112 | render : function() { 113 | rndrSpy(); 114 | return h('div', null, this.get('helloText')); 115 | } 116 | }); 117 | 118 | c.route('/home/:id', 'routes.hello'); 119 | jsdom.changeURL(window, 'http://localhost/home/123?test=321'); 120 | c.get('zefir').deviceWatcher(); 121 | c.get('_loadComponent'); 122 | c._updateWatcher(); 123 | 124 | expect(rndrSpy.calledOnce).to.be.true; 125 | expect(document.body.innerHTML) 126 | .to.be.equal('
hello
'); 127 | r.set('helloText', 'world'); 128 | c._updateWatcher(); 129 | expect(document.body.innerHTML) 130 | .to.be.equal('
world
'); 131 | }); 132 | 133 | it('should render nested namespace', function() { 134 | jsdom.changeURL(window, 'http://localhost/home/44?test=321'); 135 | 136 | var renderSpy = sinon.spy(); 137 | var b = cake.Cream.extend({ 138 | _namespace : 'routes.hello.nested', 139 | helloText : 'hello' 140 | }); 141 | 142 | var r = cake.Cream.extend({ 143 | _namespace : 'routes.hello', 144 | hello : cake.inject('routes.hello.nested.helloText'), 145 | 146 | render : function() { 147 | renderSpy(); 148 | return h('div', { id : 'rtest'}, this.get('hello')); 149 | } 150 | }); 151 | 152 | c.route('/home/:id', 'routes.hello'); 153 | jsdom.changeURL(window, 'http://localhost/home/3?test=321'); 154 | c.get('zefir').deviceWatcher(); 155 | c._updateWatcher(); 156 | 157 | var element = document.getElementById('rtest'); 158 | 159 | expect(element.textContent).to.be.equal('hello'); 160 | 161 | r.set('hello', 'world'); 162 | c._updateWatcher(); 163 | expect(element.textContent).to.be.equal('world'); 164 | 165 | b.set('helloText', 'hello123'); 166 | c._updateWatcher(); 167 | c.get('zefir').deviceWatcher(); 168 | expect(element.textContent).to.be.equal('hello123'); 169 | expect(renderSpy.callCount).to.be.equal(3); 170 | }); 171 | 172 | it('should handle before initializers', function() { 173 | jsdom.changeURL(window, 'http://localhost:80'); 174 | 175 | c.route('/', 'routes.home'); 176 | var initSpy = sinon.spy(); 177 | cake.Cream.extend({ 178 | _namespace : 'routes.home', 179 | init : initSpy 180 | }); 181 | 182 | expect(initSpy.calledOnce).to.be.true; 183 | }); 184 | 185 | it('should handle after initializers', function() { 186 | jsdom.changeURL(window, 'http://localhost:80'); 187 | 188 | var initSpy = sinon.spy(); 189 | cake.Cream.extend({ 190 | _namespace : 'routes.home', 191 | init : initSpy 192 | }); 193 | 194 | c.route('/', 'routes.home'); 195 | 196 | expect(initSpy.calledOnce).to.be.true; 197 | }); 198 | 199 | it('should handle any-route pattern', function() { 200 | jsdom.changeURL(window, 'http://localhost:80/not-foun/d/123'); 201 | 202 | var initSpy = sinon.spy(); 203 | cake.Cream.extend({ 204 | _namespace : 'routes.home', 205 | init : initSpy 206 | }); 207 | 208 | c.route('*', 'routes.home'); 209 | 210 | expect(initSpy.calledOnce).to.be.true; 211 | }); 212 | 213 | it('should not override routes by any-route', function() { 214 | jsdom.changeURL(window, 'http://localhost:80/'); 215 | 216 | var anySpy = sinon.spy(); 217 | var homeSpy = sinon.spy(); 218 | 219 | c.route('/', 'routes.home'); 220 | c.route('*', 'routes.anyroute'); 221 | 222 | cake.Cream.extend({ 223 | _namespace : 'routes.home', 224 | init : homeSpy 225 | }); 226 | 227 | cake.Cream.extend({ 228 | _namespace : 'routes.anyroute', 229 | init : anySpy 230 | }); 231 | 232 | expect(anySpy.callCount).to.be.equal(0); 233 | expect(homeSpy.calledOnce).to.be.true; 234 | }); 235 | 236 | it('should ignore empty render', function() { 237 | c.route('*', 'routes.home'); 238 | 239 | var initSpy = sinon.spy(); 240 | 241 | cake.Cream.extend({ 242 | _namespace : 'routes.home', 243 | init : initSpy, 244 | render : function() { } 245 | }); 246 | 247 | expect(initSpy.calledOnce).to.be.true; 248 | }); 249 | 250 | it('should handle injected props', function() { 251 | cake.Cream.extend({ 252 | _namespace: 'routes.store', 253 | getNum: function() { 254 | return this.get('data')[1] * 2; 255 | }.property(), 256 | data: [ 1, 2, 3, 4] 257 | }); 258 | 259 | var Component = cake.Cream.extend({ 260 | store: cake.inject('routes.store'), 261 | test: function() { 262 | return this.get('store.getNum'); 263 | } 264 | }); 265 | 266 | expect(Component.test()).to.be.equal(4); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /tests/caramel.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Caramel = require('../lib/caramel'); 3 | 4 | describe('Caramel for the cake', function() { 5 | var caramel; 6 | 7 | afterEach(function() { 8 | caramel.listeners = []; 9 | }); 10 | 11 | it('should get some caramel', function() { 12 | expect(function() { 13 | caramel = new Caramel(); 14 | }).not.to.throw(); 15 | 16 | expect(caramel).to.be.exists; 17 | expect(caramel).to.be.an('object'); 18 | expect(caramel).to.be.instanceof(Caramel); 19 | }); 20 | 21 | it('should subscribe some listeners', function() { 22 | var listener = function() {}; 23 | 24 | expect(function() { 25 | caramel.on('some-event', listener); 26 | caramel.on(/^some.*event$/, listener); 27 | caramel.on(new RegExp(/^some.*event$/), listener); 28 | }).not.throw(); 29 | 30 | expect(function() { 31 | caramel.on([], listener); 32 | caramel.on({}, listener); 33 | }).to.throw(); 34 | }); 35 | 36 | it('should handle duplicate listeners', function() { 37 | var listener = function() { }; 38 | 39 | caramel.on('test', listener); 40 | 41 | expect(function() { 42 | caramel.on('test', listener); 43 | }).to.throw(); 44 | }); 45 | 46 | it('should limit listeners number', function() { 47 | expect(function() { 48 | for (var i = 0; i < 257; i++) { 49 | caramel.once('test', function() {}); 50 | } 51 | }).to.throw(); 52 | 53 | }); 54 | 55 | it('should emit events simple events', function() { 56 | var emitted = 0; 57 | 58 | caramel.on('test', function() { emitted++; }); 59 | 60 | expect(function() { caramel.emit('test'); }).not.throw(); 61 | expect(emitted).to.be.equal(1); 62 | }); 63 | 64 | it('should handle wrong emitters', function() { 65 | expect(function() { 66 | caramel.emit(''); 67 | }).to.throw(); 68 | 69 | expect(function() { 70 | caramel.emit([]); 71 | }).to.throw(); 72 | 73 | expect(function() { 74 | caramel.emit({}); 75 | }).to.throw(); 76 | }); 77 | 78 | it('should remove "once" listeners', function() { 79 | var emitted = 0; 80 | 81 | caramel.once('test', function() { emitted++; }); 82 | 83 | expect(function() { caramel.emit('test'); }).not.throw(); 84 | expect(emitted).to.be.equal(1); 85 | expect(caramel.listeners.length).to.be.equal(0); 86 | }); 87 | 88 | it('should unsubscribe listeners', function() { 89 | var listener = function() {}; 90 | 91 | caramel.on('test', listener); 92 | 93 | expect(caramel.listeners.length).to.be.equal(1); 94 | expect(caramel.has('test', listener)).to.be.true; 95 | 96 | caramel.off('test', listener); 97 | 98 | expect(caramel.listeners.length).to.be.equal(0); 99 | expect(caramel.has('test', listener)).to.be.false; 100 | }); 101 | 102 | it('should support regexp unsubscribers', function() { 103 | caramel.on('model:changed', function() {}); 104 | caramel.on('model:inserted', function() {}); 105 | caramel.on('model:removed', function() {}); 106 | 107 | expect(caramel.listeners.length).to.be.equal(3); 108 | 109 | caramel.off(/^model/); 110 | 111 | expect(caramel.listeners.length).to.be.equal(0); 112 | }); 113 | 114 | it('should emit regexp event', function() { 115 | var emitted = 0; 116 | caramel.on('model:changed', function() { emitted++; }); 117 | caramel.on('model:inserted', function() { emitted++; }); 118 | caramel.on('model:removed', function() { emitted++; }); 119 | 120 | expect(caramel.listeners.length).to.be.equal(3); 121 | caramel.emit(/^model/); 122 | expect(emitted).to.be.equal(3); 123 | caramel.emit(/changed$/); 124 | expect(emitted).to.be.equal(4); 125 | }); 126 | 127 | it('should emit with some data', function() { 128 | var emitted = 0; 129 | caramel.on('model:changed', function(e) { emitted = e; }); 130 | caramel.emit('model:changed', 7); 131 | expect(emitted).to.be.equal(7); 132 | caramel.emit(/changed$/, 5); 133 | expect(emitted).to.be.equal(5); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /tests/container.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Bakery = require('../'); 3 | 4 | describe('Container for ingredients', function() { 5 | afterEach(function() { 6 | try { 7 | var creams = Object.keys(Bakery._container.creams); 8 | 9 | for (var i = 0; i < creams.length; i++) { 10 | Bakery.unregister('creams.' + creams[i]); 11 | } 12 | 13 | Bakery.unregister('creams'); 14 | } catch(e) { /* not found */ } 15 | }); 16 | 17 | it('should be empty container', function() { 18 | expect(Bakery._container).to.be.exists; 19 | }); 20 | 21 | it('should register objects in container', function() { 22 | var c1 = new Bakery.Cream.extend({}); 23 | var c2 = new Bakery.Cream.extend({}); 24 | var c3 = new Bakery.Cream.extend({}); 25 | var c4 = new Bakery.Cream.extend({}); 26 | 27 | Bakery.register('creams.cream_0', c1); 28 | Bakery.register('creams.cream_1', c2); 29 | Bakery.register('creams.cream_2', c3); 30 | Bakery.register('creams.cream_3', c4); 31 | 32 | expect(Object.keys(Bakery._container.creams).length).to.be.equal(4); 33 | expect(Bakery._container.creams.cream_0.cream).to.be.equal(c1); 34 | expect(Bakery._container.creams.cream_1.cream).to.be.equal(c2); 35 | expect(Bakery._container.creams.cream_2.cream).to.be.equal(c3); 36 | expect(Bakery._container.creams.cream_3.cream).to.be.equal(c4); 37 | }); 38 | 39 | it('should unregister container objects', function() { 40 | var c1 = new Bakery.Cream.extend({}); 41 | var c2 = new Bakery.Cream.extend({}); 42 | 43 | expect(Bakery._container.creams).to.be.an('undefined'); 44 | 45 | Bakery.register('creams.cream_0', c1); 46 | Bakery.register('creams.cream_1', c2); 47 | 48 | expect(Bakery._container.creams.cream_0.cream).to.be.equal(c1); 49 | expect(Bakery._container.creams.cream_1.cream).to.be.equal(c2); 50 | 51 | Bakery.unregister('creams.cream_0'); 52 | Bakery.unregister('creams.cream_1'); 53 | 54 | expect(Bakery._container.creams.cream_0).to.be.an('undefined'); 55 | expect(Bakery._container.creams.cream_1).to.be.an('undefined'); 56 | }); 57 | 58 | it('should hanlde unregistering invalid objects', function() { 59 | expect(Bakery.unregister('crms.ctrst')).to.be.an('undefined'); 60 | expect(Bakery.unregister('crms')).to.be.an('undefined'); 61 | }); 62 | 63 | it('should inject objects', function() { 64 | var c1 = Bakery.Cream.extend({ hello : 'world' }); 65 | Bakery.register('creams.injectable', c1); 66 | 67 | var c2 = Bakery.Cream.extend({ 68 | c1 : Bakery.inject('creams.injectable'), 69 | hello : Bakery.inject('creams.injectable.hello') 70 | }); 71 | 72 | expect(c2.get('c1')).to.be.equal(c1); 73 | expect(c2.get('hello')).to.be.equal('world'); 74 | }); 75 | 76 | it('should register object after', function() { 77 | var c1 = Bakery.Cream.extend({ hello : 'world' }); 78 | var c2 = Bakery.Cream.extend({ world : 'hello' }); 79 | 80 | Bakery.register('creams.cream_1', c2, 'creams.cream_0'); 81 | expect(Bakery._container.creams).to.be.an('undefined'); 82 | Bakery.register('creams.cream_0', c1); 83 | expect(Bakery._container.creams.cream_1.cream).to.be.equal(c2); 84 | }); 85 | 86 | it('should register after with any order', function() { 87 | var c1 = Bakery.Cream.extend({ hello : 'world' }); 88 | var c2 = Bakery.Cream.extend({ world : 'hello' }); 89 | 90 | Bakery.register('creams.cream_0', c1); 91 | Bakery.register('creams.cream_1', c2, 'creams.cream_0'); 92 | 93 | expect(Bakery.inject('creams.cream_1')()).to.be.equal(c2); 94 | }); 95 | 96 | it('should inject even before object registered', function() { 97 | var c1 = Bakery.Cream.extend({ hello : Bakery.inject('creams.injectable2'), 98 | test: 'test', 99 | testFn : function() { } 100 | }); 101 | var c2 = Bakery.Cream.extend({ world : 'texthello' }); 102 | 103 | Bakery.register('creams.cream_0', c1); 104 | Bakery.register('creams.injectable2', c2); 105 | 106 | expect(c1.get('hello.world')).to.be.equal('texthello'); 107 | expect(c1.get('testFn')).to.be.a('function'); 108 | expect(c1.get('testFn.test')).to.be.an('undefined'); 109 | }); 110 | 111 | it('should register object\'s namespace', function() { 112 | var c = Bakery.Cream.extend({}); 113 | 114 | Bakery.register('creams.cream_0', c); 115 | expect(c._namespace).to.be.equal('creams.cream_0'); 116 | }); 117 | 118 | it('should register selfnamespaced creams', function() { 119 | var c = Bakery.Cream.extend({ 120 | _namespace : 'creams.selfreg' 121 | }); 122 | 123 | expect(Bakery._container.creams.selfreg.cream).to.be.equal(c); 124 | expect(c._namespace).to.be.equal(c.get('_namespace')); 125 | }); 126 | 127 | it('should register/unregister observers', function() { 128 | var c = Bakery.Cream.extend({ 129 | _namespace : 'creams.observable_cream', 130 | hello : 'world', 131 | test : function() { return this.get('hello'); }.observes('hello') 132 | }); 133 | 134 | expect(c).to.be.instanceof(Bakery.Cream); 135 | expect(c.get('test')).to.be.equal('world'); 136 | 137 | Bakery.unregister('creams.observable_creams'); 138 | }); 139 | 140 | it('should register component with builtin _after prop', function() { 141 | var b = Bakery.Cream.extend({ 142 | _namespace : 'creams.a', 143 | _after : 'creams.c', 144 | init : function() { 145 | expect(Bakery._container.creams.b).to.be.exists; 146 | expect(Bakery._container.creams.c).to.be.exists; 147 | } 148 | }); 149 | var c = Bakery.Cream.extend({ 150 | _namespace : 'creams.c', 151 | _after : 'creams.b', 152 | init : function() { 153 | expect(Bakery._container.creams.b).to.be.exists; 154 | expect(Bakery._container.creams.a).not.to.be.exists; 155 | } 156 | }); 157 | Bakery.Cream.extend({ 158 | _namespace : 'creams.b' 159 | }); 160 | 161 | c.init(); 162 | b.init(); 163 | }); 164 | 165 | it('shoud make nested creams nested', function() { 166 | Bakery.Cream.extend({ 167 | _namespace : 'creams.a.b', 168 | text : 'hello' 169 | }); 170 | 171 | var a = Bakery.Cream.extend({ 172 | _namespace : 'creams.a', 173 | nested : Bakery.inject('creams.a.b') 174 | }); 175 | 176 | expect(a.get('nested._namespace')).to.be.equal('creams.a.b'); 177 | expect(a.get('nested.text')).to.be.equal('hello'); 178 | }); 179 | 180 | it('should inject nonobject props', function() { 181 | var b = Bakery.Cream.extend({ 182 | _namespace : 'creams.hello.nested', 183 | helloText : 'hello' 184 | }); 185 | 186 | var r = Bakery.Cream.extend({ 187 | _namespace : 'creams.hello', 188 | hello : Bakery.inject('creams.hello.nested.helloText') 189 | }); 190 | 191 | expect(b.get('helloText')).to.be.equal(r.get('hello')); 192 | r.set('hello', 'world'); 193 | expect(b.get('helloText')).to.be.equal('world'); 194 | expect(r.get('hello')).to.be.equal('world'); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /tests/cream-observers.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Bakery = require('../'); 3 | var Cream = Bakery.Cream; 4 | 5 | describe('Cream observers', function() { 6 | afterEach(function() { 7 | try { 8 | var creams = Object.keys(Bakery._container.creams); 9 | 10 | for (var i = 0; i < creams.length; i++) { 11 | Bakery.unregister('creams.' + creams[i]); 12 | } 13 | 14 | Bakery.unregister('creams'); 15 | } catch(e) { /* not found */ } 16 | }); 17 | 18 | it('should observe multiple props', function() { 19 | var runcount = 0; 20 | var b = Cream.extend({ 21 | _namespace : 'creams.c', 22 | test : { 23 | bool : false, 24 | undef : undefined, 25 | nil : null, 26 | reg : /^$/ 27 | }, 28 | watch : function() { runcount++; }.observes('test.bool', 'test.undef', 'test.nil', 'test.reg') 29 | }); 30 | 31 | b.set('test.bool', true); 32 | b.set('test.undef', new Object()); 33 | b.set('test.nil', false); 34 | b.set('test.reg', /^test/); 35 | 36 | expect(runcount).to.be.equal(4); 37 | }); 38 | 39 | it('should observe multiple props by regex', function() { 40 | var runcount = 0; 41 | var b = Cream.extend({ 42 | _namespace : 'creams.c', 43 | test : { 44 | bool : false, 45 | undef : undefined, 46 | nil : null, 47 | reg : /^$/ 48 | }, 49 | complete : false, 50 | watch : function() { runcount++; }.observes(/^creams.c.test/, 'complete') 51 | }); 52 | 53 | b.set('test.bool', true); 54 | b.set('test.undef', new Object()); 55 | b.set('test.nil', false); 56 | b.set('test.reg', /^test/); 57 | b.set('complete', true); 58 | 59 | expect(runcount).to.be.equal(5); 60 | }); 61 | 62 | it('should handle deep nesting', function() { 63 | var runcount = 0; 64 | var b = Cream.extend({ 65 | _namespace : 'creams.c', 66 | test : { 67 | nested : [ 0, { a : 'b' } ] 68 | }, 69 | watch : function() { runcount++; }.observes('test.nested.1.a') 70 | }); 71 | 72 | b.set('test.nested.1.a', 'c'); 73 | expect(runcount).to.be.equal(1); 74 | }); 75 | 76 | it('should handle injectable deep nesting', function() { 77 | var b = Cream.extend({ 78 | _namespace : 'creams.b', 79 | test : { 80 | nested : [ 0, { a : 'b' } ] 81 | } 82 | }); 83 | 84 | var c = Cream.extend({ 85 | _namespace : 'creams.c', 86 | test : Bakery.inject('creams.b.test'), 87 | watch : function() { this.set('deeprop', this.get('test.nested.1.a')); }.observes('test.nested.1.a') 88 | }); 89 | 90 | b.set('test.nested.1.a', 'c'); 91 | expect(c.get('deeprop')).to.be.equal('c'); 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /tests/cream.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Bakery = require('../'); 3 | var Cream = Bakery.Cream; 4 | var h = require('../').h; 5 | 6 | describe('Cream for the cake', function() { 7 | afterEach(function() { 8 | try { 9 | var creams = Object.keys(Bakery._container.creams); 10 | 11 | for (var i = 0; i < creams.length; i++) { 12 | Bakery.unregister('creams.' + creams[i]); 13 | } 14 | 15 | Bakery.unregister('creams'); 16 | } catch(e) { /* not found */ } 17 | }); 18 | 19 | it('should create some cream', function() { 20 | var c = Cream.extend({ str : 'str', init : true }); 21 | 22 | expect(c).to.be.instanceof(Cream); 23 | expect(c.init).to.be.true; 24 | expect(c.str).to.be.a('string'); 25 | expect(c.str).to.be.equal('str'); 26 | expect(c.get).to.be.a('function'); 27 | expect(c.set).to.be.a('function'); 28 | 29 | var d = Cream.extend({ opt : false }); 30 | 31 | expect(c.opt).to.be.an('undefined'); 32 | expect(Cream.opt).to.be.an('undefined'); 33 | expect(Cream.str).to.be.an('undefined'); 34 | expect(d.str).to.be.an('undefined'); 35 | expect(d.opt).to.be.false; 36 | }); 37 | 38 | it('should handle different getters', function() { 39 | var c = Cream.extend({ 40 | hello : 'world', 41 | nested : { 42 | test : 'text' 43 | } 44 | }); 45 | 46 | expect(c.get('hello')).to.be.equal('world'); 47 | expect(c.get('nested.test')).to.be.equal('text'); 48 | }); 49 | 50 | it('should handle embedded function bindings', function() { 51 | var c = Cream.extend({ 52 | hname : 'hello', 53 | test : function() { return this.get('hname'); } 54 | }); 55 | 56 | expect(c.get('test')()).to.be.equal('hello'); 57 | expect(c.test()).to.be.equal('hello'); 58 | }); 59 | 60 | it('should create property functions', function() { 61 | var c = Cream.extend({ 62 | hello : 'hello', 63 | test : function() { return this.get('hello') + 'world'; }.property() 64 | }); 65 | 66 | expect(c.get('test')).to.be.equal('helloworld'); 67 | }); 68 | 69 | it('should create observer function', function() { 70 | var up = null; 71 | var c = Cream.extend({ 72 | _namespace : 'creams.observable_cream', 73 | hello : 'hello', 74 | test : function() { up = this.get('hello'); }.observes('hello') 75 | }); 76 | 77 | expect(c.get('hello')).to.be.equal('hello'); 78 | expect(up).to.be.null; 79 | 80 | c.set('hello', 'world'); 81 | expect(c.get('hello')).to.be.equal('world'); 82 | expect(up).to.be.equal('world'); 83 | }); 84 | 85 | it('should cream objects communicate with observers', function() { 86 | var up = null; 87 | var b = Cream.extend({ 88 | _namespace : 'creams.b', 89 | test : 'hello' 90 | }); 91 | 92 | Cream.extend({ 93 | _namespace : 'creams.c', 94 | btest : Bakery.inject('creams.b'), 95 | watch : function() { up = this.get('btest.test'); }.observes('btest.test') 96 | }); 97 | 98 | expect(up).to.be.null; 99 | b.set('test', 'world'); 100 | 101 | expect(up).to.be.equal('world'); 102 | 103 | }); 104 | 105 | it('should change injectable props', function() { 106 | var up; 107 | var b = Cream.extend({ 108 | _namespace : 'creams.b', 109 | test : 'hello' 110 | }); 111 | 112 | var c = Cream.extend({ 113 | _namespace : 'creams.c', 114 | btest : Bakery.inject('creams.b'), 115 | watch : function() { up = this.get('btest.test'); }.observes('btest.test') 116 | }); 117 | 118 | expect(b.get('test')).to.be.equal('hello'); 119 | c.set('btest.test', 'world'); 120 | expect(b.get('test')).to.be.equal('world'); 121 | expect(up).to.be.equal('world'); 122 | }); 123 | 124 | it('should change nested props', function() { 125 | var b = Cream.extend({ 126 | _namespace : 'creams.b', 127 | test : { a : 'b'} 128 | }); 129 | 130 | var c = Cream.extend({ 131 | _namespace : 'creams.c', 132 | btest : Bakery.inject('creams.b.test'), 133 | }); 134 | 135 | expect(b.get('test.a')).to.be.equal('b'); 136 | expect(c.get('btest.a')).to.be.equal('b'); 137 | c.set('btest.a', 'c'); 138 | expect(b.get('test.a')).to.be.equal('c'); 139 | expect(c.get('btest.a')).to.be.equal('c'); 140 | }); 141 | 142 | it('should observer nested props', function(done) { 143 | var up; 144 | var b = Cream.extend({ 145 | _namespace : 'creams.b', 146 | test : { a : 'hello' }, 147 | complete : false, 148 | v : Bakery.inject('creams.c.vvar') 149 | }); 150 | 151 | var c = Cream.extend({ 152 | _namespace : 'creams.c', 153 | vvar : {sss : 1}, 154 | btest : Bakery.inject('creams.b.test'), 155 | watch : function() { up = this.get('btest.a'); }.observes('btest.a'), 156 | done : function() { done(); }.observes('creams.b.complete') 157 | }); 158 | 159 | b.set('test.a', 'world'); 160 | expect(up).to.be.equal('world'); 161 | c.set('btest.a', 'kkk'); 162 | expect(up).to.be.equal('kkk'); 163 | expect(b.get('v.sss')).to.be.equal(1); 164 | expect(b.set('v.sss', 2)).to.be.exists; 165 | expect(c.get('vvar.sss')).to.be.equal(2); 166 | b.set('complete', true); 167 | }); 168 | 169 | it('should set/get different types of properties', function() { 170 | var b = Cream.extend({ 171 | _namespace : 'creams.b', 172 | test : { a : 'hello' }, 173 | bool : false, 174 | undef : undefined, 175 | nil : null, 176 | reg : /^$/ 177 | }); 178 | 179 | expect(b.get('bool')).to.be.equal(false); 180 | expect(b.get('undef')).to.be.equal(undefined); 181 | expect(b.get('nil')).to.be.equal(null); 182 | expect(b.get('reg')).to.be.instanceof(RegExp); 183 | 184 | b.set('bool', true); 185 | b.set('undef', new Object()); 186 | b.set('nil', false); 187 | b.set('reg', /^test/); 188 | 189 | expect(b.get('bool')).to.be.equal(true); 190 | expect(b.get('undef')).to.be.instanceof(Object); 191 | expect(b.get('nil')).to.be.equal(false); 192 | expect(b.get('reg')).to.be.instanceof(RegExp); 193 | 194 | b.set('bool', false); 195 | b.set('undef', undefined); 196 | b.set('nil', null); 197 | b.set('reg', /^/); 198 | 199 | expect(b.get('bool')).to.be.equal(false); 200 | expect(b.get('undef')).to.be.equal(undefined); 201 | expect(b.get('nil')).to.be.equal(null); 202 | expect(b.get('reg')).to.be.instanceof(RegExp); 203 | }); 204 | 205 | it('should hanlde deep set/get different types', function() { 206 | var b = Cream.extend({ 207 | _namespace : 'creams.b', 208 | test : { 209 | bool : false, 210 | undef : undefined, 211 | nil : null, 212 | reg : /^$/ 213 | } 214 | }); 215 | 216 | expect(b.get('test.bool')).to.be.equal(false); 217 | expect(b.get('test.undef')).to.be.equal(undefined); 218 | expect(b.get('test.nil')).to.be.equal(null); 219 | expect(b.get('test.reg')).to.be.instanceof(RegExp); 220 | 221 | b.set('test.bool', true); 222 | b.set('test.undef', new Object()); 223 | b.set('test.nil', false); 224 | b.set('test.reg', /^test/); 225 | 226 | expect(b.get('test.bool')).to.be.equal(true); 227 | expect(b.get('test.undef')).to.be.instanceof(Object); 228 | expect(b.get('test.nil')).to.be.equal(false); 229 | expect(b.get('test.reg')).to.be.instanceof(RegExp); 230 | }); 231 | 232 | it('should handle deep set/get injected with diff types', function() { 233 | Cream.extend({ 234 | _namespace : 'creams.c', 235 | test : { 236 | bool : false, 237 | undef : undefined, 238 | nil : null, 239 | reg : /^$/ 240 | } 241 | }); 242 | 243 | var b = Cream.extend({ 244 | _namespace : 'creams.b', 245 | test : Bakery.inject('creams.c.test') 246 | }); 247 | 248 | expect(b.get('test.bool')).to.be.equal(false); 249 | expect(b.get('test.undef')).to.be.equal(undefined); 250 | expect(b.get('test.nil')).to.be.equal(null); 251 | expect(b.get('test.reg')).to.be.instanceof(RegExp); 252 | 253 | b.set('test.bool', true); 254 | b.set('test.undef', new Object()); 255 | b.set('test.nil', false); 256 | b.set('test.reg', /^test/); 257 | 258 | expect(b.get('test.bool')).to.be.equal(true); 259 | expect(b.get('test.undef')).to.be.instanceof(Object); 260 | expect(b.get('test.nil')).to.be.equal(false); 261 | expect(b.get('test.reg')).to.be.instanceof(RegExp); 262 | }); 263 | 264 | it('should deal with broken setters', function() { 265 | var c = Cream.extend({ 266 | _namespace : 'creams.c', 267 | 268 | }); 269 | 270 | expect(function() { 271 | c.set('test.ccc', 'hello'); 272 | }).to.throw(); 273 | }); 274 | 275 | // it('should be initialized/deinitialized by di', function(done) { 276 | // var c = Cream.extend({ 277 | // initialized : false, 278 | // 279 | // init: function() { 280 | // this.set('initialized', true); 281 | // }, 282 | // 283 | // destroy : function() { done(); } 284 | // }); 285 | // 286 | // expect(c.get('initialized')).to.be.false; 287 | // Bakery.register('creams.c', c); 288 | // expect(c.get('initialized')).to.be.true; 289 | // Bakery.unregister('creams.c'); 290 | // }); 291 | 292 | it('should prevent sets without namespace', function() { 293 | var c = Cream.extend({}); 294 | expect(function() { 295 | c.set('test', 123); 296 | }).to.throw(); 297 | }); 298 | 299 | it('should prevent setup invalid props', function() { 300 | var c = Cream.extend({ _namespace : 'creams.c' }); 301 | 302 | expect(function() { c.set(null, true); }).to.throw(); 303 | expect(function() { c.set(undefined, true); }).to.throw(); 304 | expect(function() { c.set(/^/, true); }).to.throw(); 305 | expect(function() { c.set(true, true); }).to.throw(); 306 | expect(function() { c.set('', true); }).to.throw(); 307 | expect(function() { c.set('.', true); }).to.throw(); 308 | expect(function() { c.set('t.', true); }).to.throw(); 309 | }); 310 | 311 | it('should handle minimal amount of array\'s fns', function() { 312 | var numpushes = 0; 313 | var c = Cream.extend({ 314 | _namespace : 'creams.c', 315 | arr : [1,2,4,5], 316 | watch : function() { numpushes++; }.observes('arr') 317 | }); 318 | 319 | var b = Cream.extend({ 320 | _namespace : 'creams.b', 321 | aa : Bakery.inject('creams.c'), 322 | watch : function() { numpushes++; }.observes('aa.arr') 323 | }); 324 | 325 | b.push('aa.arr', 6); 326 | expect(c.arr.length).to.be.equal(5); 327 | expect(b.pop('aa.arr')).to.be.equal(6); 328 | expect(c.shift('arr')).to.be.equal(1); 329 | expect(b.splice('aa.arr', 1, 1)).to.be.deep.equal([4]); 330 | expect(b.unshift('aa.arr', 8)).to.be.equal(3); 331 | expect(b.get('aa.arr').length).to.be.equal(3); 332 | expect(numpushes).to.be.equal(10); 333 | }); 334 | 335 | it('should extend himselfs', function() { 336 | var c = Cream.extend({ 337 | _namespace : 'creams.c', 338 | hello : function() {} 339 | }); 340 | 341 | var b = c.extend({ 342 | _namespace : 'creams.b' 343 | }); 344 | 345 | expect(b.hello).to.be.a('function'); 346 | }); 347 | 348 | it('should handle recursive setters', function() { 349 | var c = Cream.extend({ 350 | _namespace : 'creams.c', 351 | hello : 'hello' 352 | }); 353 | 354 | var b = Cream.extend({ 355 | _namespace : 'creams.b', 356 | text : Bakery.inject('creams.c.hello') 357 | }); 358 | 359 | expect(b.text).to.be.a('function'); 360 | b.set('text', 'world'); 361 | expect(b.text).to.be.a('function'); 362 | c.set('hello', '123'); 363 | expect(b.text).to.be.a('function'); 364 | expect(b.get('text')).to.be.equal('123'); 365 | }); 366 | 367 | it('should handle nested creams', function() { 368 | var Nested = Cream.extend({ 369 | render: function() { 370 | return h('div', null, this.props.name); 371 | } 372 | }); 373 | 374 | var Wrapper = Cream.extend({ 375 | render: function() { 376 | return h(Nested, {name: 'prop-from-root'}); 377 | } 378 | }); 379 | 380 | expect(Wrapper.render().children[0].children).to.be.equal('prop-from-root'); 381 | }); 382 | }); 383 | -------------------------------------------------------------------------------- /tests/create.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var cake = require('../'); 3 | 4 | var jsdom = require('jsdom'); 5 | var doc = jsdom.jsdom('',{ url: 'http://test/'}); 6 | var win = doc.defaultView; 7 | global.document = doc; 8 | global.window = win; 9 | propagateToGlobal(win); 10 | 11 | function propagateToGlobal (window) { 12 | for (var key in window) { 13 | if (!window.hasOwnProperty(key)) continue; 14 | if (key in global) continue; 15 | 16 | global[key] = window[key]; 17 | } 18 | } 19 | 20 | describe('Some kitchen tests', function() { 21 | after(function() { 22 | cake.destroy(); 23 | }); 24 | it('should replace existen element', function() { 25 | var el = document.createElement('div'); 26 | el.setAttribute('id', 'cake'); 27 | document.body.appendChild(el); 28 | 29 | cake.create(); 30 | 31 | expect(document.getElementById('cake')).not.be.equal(el); 32 | 33 | cake.destroy(); 34 | }); 35 | 36 | it('should start clean after destroying', function() { 37 | expect(document.getElementById('cake')).not.exists; 38 | cake.create(); 39 | expect(document.getElementById('cake')).to.be.exists; 40 | cake.destroy(); 41 | expect(document.getElementById('cake')).not.exists; 42 | }); 43 | 44 | it('should handle createRoot opts', function() { 45 | expect(document.getElementById('cake')).not.exists; 46 | cake.create({ createRoot : false }); 47 | expect(document.getElementById('cake')).not.exists; 48 | cake.destroy(); 49 | expect(document.getElementById('cake')).not.exists; 50 | 51 | var c = cake.create({ createRoot: false }); 52 | 53 | c.route('/', 'home'); 54 | 55 | cake.Cream.extend({ 56 | _namespace : 'home', 57 | render : function() { 58 | return cake.h('div', { id : 'test' }, ''); 59 | } 60 | }); 61 | 62 | expect(document.getElementById('cake')).not.be.exists; 63 | expect(document.getElementById('test')).to.be.exists; 64 | cake.destroy(); 65 | }); 66 | 67 | it('should handle createRoot opts with element', function() { 68 | var el = document.createElement('div'); 69 | document.body.appendChild(el); 70 | 71 | var c = cake.create({ createRoot: false, element : el }); 72 | 73 | c.route('/', 'home'); 74 | 75 | cake.Cream.extend({ 76 | _namespace : 'home', 77 | render : function() { 78 | return cake.h('div', { id : 'test' }, ''); 79 | } 80 | }); 81 | 82 | c._updateWatcher(); 83 | 84 | expect(document.getElementById('cake')).not.be.exists; 85 | expect(document.getElementById('test')).to.be.exists; 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /tests/mixer.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var mixer = require('../')._mixer; 3 | 4 | describe('Mixer for mixing it all together', function() { 5 | before(function() { 6 | mixer.stop(); 7 | }); 8 | 9 | it('should choose most appropriate mixer mode', function(done) { 10 | expect(mixer).to.be.exists; 11 | mixer.next(function() { done(); }); 12 | mixer.run(); 13 | mixer.stop(); 14 | }); 15 | 16 | it('should run next tick only once', function(done) { 17 | var ticks = 0; 18 | 19 | mixer.next(function() { ticks++; }); 20 | mixer.run(); 21 | 22 | setTimeout(function() { 23 | mixer.stop(); 24 | expect(ticks).to.be.equal(1); 25 | done(); 26 | }, 16.7 * 2); 27 | }); 28 | 29 | it('should run tasks multiple times', function(done) { 30 | var ticks = 0; 31 | 32 | mixer.run(); 33 | 34 | var runner = function() { 35 | ticks++; 36 | mixer.next(runner); 37 | }; 38 | 39 | runner(); 40 | 41 | setTimeout(function() { 42 | expect(ticks).to.be.least(2); 43 | mixer.stop(); 44 | done(); 45 | }, 16.7 * 2); 46 | 47 | }); 48 | 49 | it('should prevent run mixer multiple times', function() { 50 | mixer.run(); 51 | expect(function() { mixer.run(); }).to.throw(); 52 | mixer.stop(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/zefir.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | 3 | var zefir = require('../lib/zefir'); 4 | 5 | describe('Zefir on top of the cake', function() { 6 | after(function() { 7 | zefir.set('routes', []); 8 | }); 9 | it('should hanlde route props', function() { 10 | expect(zefir).to.be.exists; 11 | zefir.route('/', 'routes.home'); 12 | zefir.route('/posts/:tt/post', 'routes.post'); 13 | zefir.locationWatcher('http://a.b/posts/123/post'); 14 | 15 | expect(zefir.current.cream).to.be.equal('routes.post'); 16 | expect(zefir.current.props.tt).to.be.equal('123'); 17 | 18 | zefir.locationWatcher('https://n.a/'); 19 | expect(zefir.current.cream).to.be.equal('routes.home'); 20 | zefir.locationWatcher('https://f.x/s/1/post'); 21 | expect(zefir.current).to.be.an('undefined'); 22 | 23 | zefir.locationWatcher('/dfds'); 24 | expect(zefir.current).to.be.an('undefined'); 25 | zefir.locationWatcher( '/'); 26 | expect(zefir.current.cream).to.be.equal('routes.home'); 27 | zefir.locationWatcher('/posts/123/post'); 28 | expect(zefir.current.cream).to.be.equal('routes.post'); 29 | expect(zefir.current.props.tt).to.be.equal('123'); 30 | }); 31 | 32 | it('should hanlde provided query params', function() { 33 | expect(zefir).to.be.exists; 34 | zefir.route('/', 'routes.home'); 35 | zefir.route('/posts/:tt/post', 'routes.post'); 36 | zefir.locationWatcher('http://a.b/posts/123/post?test=123'); 37 | 38 | expect(zefir.current.params).to.be.deep.equal({ test : '123' }); 39 | 40 | zefir.locationWatcher('/?t=a&b='); 41 | expect(zefir.current.params.t).to.be.equal('a'); 42 | expect(zefir.current.params.b).to.be.equal(''); 43 | zefir.locationWatcher('/?t=a&b'); 44 | expect(zefir.current.params.b).to.be.an('undefined'); 45 | }); 46 | 47 | it('should handle non http hashbased location', function() { 48 | expect(zefir).to.be.exists; 49 | zefir.route('/', 'routes.home'); 50 | zefir.route('/posts/:tt/post', 'routes.post'); 51 | zefir.locationWatcher('file:///a.b/folder/index.html#/posts/123/post?test=123'); 52 | 53 | 54 | expect(zefir.current.params).to.be.deep.equal({ test : '123' }); 55 | 56 | zefir.locationWatcher('/?t=a&b='); 57 | expect(zefir.current.params.t).to.be.equal('a'); 58 | expect(zefir.current.params.b).to.be.equal(''); 59 | zefir.locationWatcher('/?t=a&b'); 60 | expect(zefir.current.params.b).to.be.an('undefined'); 61 | }); 62 | 63 | it('should handle hashlocation without trailing hash', function() { 64 | expect(zefir).to.be.exists; 65 | zefir.route('/', 'routes.home'); 66 | zefir.route('/posts/:tt/post', 'routes.post'); 67 | zefir.locationWatcher('file:///a.b/folder/index.html/#/posts/123/post?test=123'); 68 | 69 | 70 | expect(zefir.current.params).to.be.deep.equal({ test : '123' }); 71 | 72 | zefir.locationWatcher('/?t=a&b='); 73 | expect(zefir.current.params.t).to.be.equal('a'); 74 | expect(zefir.current.params.b).to.be.equal(''); 75 | zefir.locationWatcher('/?t=a&b'); 76 | expect(zefir.current.params.b).to.be.an('undefined'); 77 | }); 78 | 79 | it('should handle http nested path with hash', function() { 80 | expect(zefir).to.be.exists; 81 | zefir.route('/', 'routes.home'); 82 | zefir.route('/posts/:tt/post', 'routes.post'); 83 | zefir.locationWatcher('https://hello-world.com/folder-nestd/index/#/posts/123/post?test=123'); 84 | 85 | 86 | expect(zefir.current.params).to.be.deep.equal({ test : '123' }); 87 | 88 | zefir.locationWatcher('/?t=a&b='); 89 | expect(zefir.current.params.t).to.be.equal('a'); 90 | expect(zefir.current.params.b).to.be.equal(''); 91 | zefir.locationWatcher('/?t=a&b'); 92 | expect(zefir.current.params.b).to.be.an('undefined'); 93 | }); 94 | }); 95 | --------------------------------------------------------------------------------