├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── dom_parser.js ├── gulpfile.js ├── index.js ├── package.json └── test ├── condition.test.js ├── databinding.test.js ├── list.test.js ├── test.html └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4" 5 | before_script: 6 | - npm install -g gulp 7 | script: 8 | - gulp 9 | - npm test -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright(c) Long Xiang (seanlong@outlook.com) 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wxml-parser 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/seanlong/wxml-parser.svg?branch=master)](https://travis-ci.org/seanlong/wxml-parser) 5 | 6 | ## What 7 | 这是一个微信小程序[WXML](http://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/)文件的JavaScript parser实现。输出微信官方提供的native可执行文件类似的数据结构。 8 | 该数据可以被后续微信小程序的Virtual DOM generator生成真实DOM。 9 | 10 | 目前版本支持除了模板和模板引用外的基本语法。 11 | 12 | ## Run 13 | ``` 14 | var parser = require('wxml-parser); 15 | console.log(JSON.stringify(parser(' {{ message }} ', {message: 'Hello MINA!'}), null, 2)); 16 | ``` 17 | output: 18 | ``` 19 | { 20 | "tag": "wx-body", 21 | "attr": {}, 22 | "children": [{ 23 | "tag": "wx-view", 24 | "attr": {}, 25 | "children": ["Hello MINA!"] 26 | }] 27 | } 28 | ``` 29 | ## License 30 | 31 | [MIT](LICENSE.txt) 32 | -------------------------------------------------------------------------------- /dom_parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * DOMParser HTML extension 3 | * 2012-09-04 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * Public domain. 7 | * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | */ 9 | 10 | /*! @source https://gist.github.com/1129031 */ 11 | /*global document, DOMParser*/ 12 | 13 | (function(DOMParser) { 14 | "use strict"; 15 | 16 | var 17 | DOMParser_proto = DOMParser.prototype 18 | , real_parseFromString = DOMParser_proto.parseFromString 19 | ; 20 | 21 | // Firefox/Opera/IE throw errors on unsupported types 22 | try { 23 | // WebKit returns null on unsupported types 24 | if ((new DOMParser).parseFromString("", "text/html")) { 25 | // text/html parsing is natively supported 26 | return; 27 | } 28 | } catch (ex) {} 29 | 30 | DOMParser_proto.parseFromString = function(markup, type) { 31 | if (/^\s*text\/html\s*(?:;|$)/i.test(type)) { 32 | var 33 | doc = document.implementation.createHTMLDocument("") 34 | ; 35 | if (markup.toLowerCase().indexOf(' -1) { 36 | doc.documentElement.innerHTML = markup; 37 | } 38 | else { 39 | doc.body.innerHTML = markup; 40 | } 41 | return doc; 42 | } else { 43 | return real_parseFromString.apply(this, arguments); 44 | } 45 | }; 46 | }(DOMParser)); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var babelify = require('babelify'); 2 | var browserify = require('browserify'); 3 | var uglify = require('gulp-uglify'); 4 | var watchify = require('watchify'); 5 | 6 | var gulp = require('gulp'); 7 | var source = require('vinyl-source-stream'); 8 | var buffer = require('vinyl-buffer'); 9 | var gutil = require('gulp-util'); 10 | var sourcemaps = require('gulp-sourcemaps'); 11 | var assign = require('lodash.assign'); 12 | var plugins = require('gulp-load-plugins')(); 13 | 14 | var options = { 15 | debug: false, 16 | fullPaths: false, 17 | standalone: 'wxmlparser' 18 | }; 19 | 20 | function createBundler(entry) { 21 | options = plugins.util.env.production ? options : 22 | assign(options, { debug: true, fullPaths: true, cache: {}, packageCache: {} }); 23 | options.entries = [entry]; 24 | var b = browserify(options); 25 | b.transform('babelify', { presets: ['es2015'] }); 26 | return b; 27 | } 28 | 29 | function bundle(b, out) { 30 | return b.bundle() 31 | .on('error', plugins.util.log.bind(plugins.util, 'Browserify Error')) 32 | .pipe(source(out)) 33 | .pipe(buffer()) 34 | .pipe(plugins.util.env.production ? uglify() : gutil.noop()) 35 | .pipe(sourcemaps.init({ loadMaps: true })) 36 | .pipe(sourcemaps.write('./')) 37 | .pipe(gulp.dest('./dist')); 38 | } 39 | 40 | gulp.task('build', function() { 41 | var bundler = createBundler('index.js'); 42 | return bundle(bundler, 'wxml-parser.js') 43 | }); 44 | 45 | gulp.task('test', function() { 46 | var bundler = createBundler('test/test.js'); 47 | bundler.plugin(watchify); 48 | bundler.on('update', function() { 49 | bundle(bundler, 'test.js'); 50 | gutil.log('Rebundle...'); 51 | }); 52 | bundle(bundler, 'test.js'); 53 | }); 54 | 55 | gulp.task('default', function() { 56 | var bundler = createBundler('test/test.js'); 57 | return bundle(bundler, 'test.js') 58 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./dom_parser.js') 4 | 5 | function XML2DataTree(root, context, newRoot, layerOps, layerIdx) { 6 | if (!newRoot) 7 | newRoot = { tag: 'body', children: [] }; 8 | else if (!newRoot.children) 9 | throw new Error('cannot add child to leaf node'); 10 | 11 | if (!root.attributes && root.nodeType == 3) { 12 | var newNode = parseTextNode(root, context); 13 | } else { 14 | var newNode = parseElementNode(root, context, layerOps, layerIdx); 15 | } 16 | if (newNode == null) 17 | return; 18 | 19 | if (newNode.tag !== 'wx-block') { 20 | if (newNode.items && newNode.array && newNode.array.length) 21 | newNode.array.forEach(n => newRoot.children.push(n)); 22 | else 23 | newRoot.children.push(newNode); 24 | } 25 | var newLayerOps = []; 26 | var newLayerIdx = 0; 27 | 28 | if (newNode.items && newNode.array && newNode.array.length) { 29 | newNode.array.forEach((n, idx) => { 30 | n = n.tag === 'wx-block' ? newRoot : n; 31 | var newContext = JSON.parse(JSON.stringify(context)); 32 | newContext[newNode.itemName] = newNode.items[idx]; 33 | newContext[newNode.indexName] = idx; 34 | for (var i = 0; i < root.childNodes.length; ++i) { 35 | var child = root.childNodes[i]; 36 | if (child.nodeType != 1 && child.nodeType != 3) 37 | continue; 38 | if (child.nodeType == 3 && !child.nodeValue.trim().length) 39 | continue; 40 | XML2DataTree(child, newContext, n, newLayerOps, newLayerIdx++); 41 | } 42 | }); 43 | } else { 44 | newNode = newNode.tag === 'wx-block' ? newRoot : newNode; 45 | for (var i = 0; i < root.childNodes.length; ++i) { 46 | var child = root.childNodes[i]; 47 | if (child.nodeType != 1 && child.nodeType != 3) 48 | continue; 49 | if (child.nodeType == 3 && !child.nodeValue.trim().length) 50 | continue; 51 | XML2DataTree(child, context, newNode, newLayerOps, newLayerIdx++); 52 | } 53 | } 54 | 55 | return newRoot; 56 | } 57 | 58 | function parseTextNode(node, data) { 59 | return stringEvaluate(node.nodeValue.trim(), data, false); 60 | } 61 | 62 | function parseElementNode(node, data, layerOps, layerIdx) { 63 | var ret = {}; 64 | ret.tag = 'wx-' + (node.tagName == 'IMG' ? 'image' : node.tagName.toLowerCase()); 65 | ret.attr = {}; 66 | ret.children = []; 67 | for (var i = 0; i < node.attributes.length; ++i) { 68 | var attr = node.attributes[i]; 69 | var value = stringEvaluate(attr.value.trim(), data, attr.name === 'data'); 70 | var name = attr.name; 71 | if (name.split('-').length > 1) 72 | name = name.split('-') 73 | .map((p, i) => i != 0 ? p[0].toUpperCase() + p.substring(1) : p) 74 | .join(''); 75 | if (ret.items && ret.array) { 76 | ret.array.forEach(c => c.attr[name] = value); 77 | } else { 78 | ret.attr[name] = value; 79 | } 80 | //if (name.startsWith('wx:')) { 81 | if (name.substring(0, 3) === 'wx:') { 82 | ret = processWxAttribute(name, value, ret, layerOps, layerIdx); 83 | if (!ret) 84 | break; 85 | } 86 | } 87 | return ret; 88 | } 89 | 90 | function processWxAttribute(name, value, node, operations, idx) { 91 | switch (name) { 92 | case 'wx:if': 93 | var condition = !!value; 94 | operations[idx] = ['if', condition]; 95 | if (condition == false) 96 | return; 97 | break; 98 | case 'wx:elif': 99 | var condition = !!value; 100 | operations[idx] = ['elif', condition]; 101 | if (operations[idx-1] == undefined || operations[idx-1][0] != 'if') 102 | throw new Error('no corresponding wx:if found'); 103 | if (operations[idx-1][1] == true || condition == false) 104 | return; 105 | break; 106 | case 'wx:else': 107 | if (operations[idx-1] == undefined || (operations[idx-1][0] != 'if' && operations[idx-1][0] != 'elif')) 108 | throw new Error('no corresponding wx:if/elif found'); 109 | if (operations[idx-1][1] == true || (operations[idx-1][0] == 'elif' && operations[idx-2][1] == true)) 110 | return; 111 | break; 112 | case 'wx:for': 113 | var items = Array.isArray(value) ? value : []; 114 | delete node.attr[name]; 115 | node = { 116 | tag: node.tag, 117 | indexName: node.indexName ? node.indexName : 'index', 118 | itemName: node.itemName ? node.itemName : 'item', 119 | items: items, 120 | array: items.map(i => { return { tag: node.tag, attr: node.attr, children: [] } }) 121 | }; 122 | break; 123 | case 'wx:forIndex': 124 | node.indexName = value; 125 | if (node.items && node.array) 126 | node.array.forEach(c => delete c.attr[name]); 127 | else if (node.attr) 128 | node.attr[name]; 129 | break; 130 | case 'wx:forItem': 131 | node.itemName = value; 132 | if (node.items && node.array) 133 | node.array.forEach(c => delete c.attr[name]); 134 | else if (node.attr) 135 | node.attr[name]; 136 | break; 137 | } 138 | return node; 139 | } 140 | 141 | function stringEvaluate(str, data, toObject) { 142 | if (str.match(/^{{[^{}]+}}$/)) { 143 | return mustacheEvaluate(str.replace(/^{{/, '').replace(/}}$/, ''), data, toObject); 144 | } else { 145 | return str.replace( 146 | /({{[^{}]+}})/g, 147 | v => mustacheEvaluate(v.replace(/^{{/, '').replace(/}}$/, ''), data, toObject)); 148 | } 149 | } 150 | 151 | function mustacheEvaluate(str, data, toObject) { 152 | var s = '(function() { '; 153 | for (var k in data) { 154 | s += 'var ' + k + ' = JSON.parse(\'' + JSON.stringify(data[k]) + '\');'; 155 | } 156 | s += 'return '; 157 | var exp = toObject ? s + '{' + str + '}})()' : s + str + '})()'; 158 | try { 159 | var ret = eval(exp); 160 | } catch (e) { 161 | console.log(exp) 162 | console.error(e) 163 | } 164 | return ret; 165 | } 166 | 167 | function parser(input, data) { 168 | var doc = new DOMParser().parseFromString(input, 'text/html'); 169 | var tree = XML2DataTree(doc.body, data, null, []); 170 | return tree.children[0]; 171 | } 172 | 173 | module.exports = parser; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxml-parser", 3 | "version": "0.0.3", 4 | "description": "wxapp WXML(HTML like layout file) parser", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/mocha-phantomjs/bin/mocha-phantomjs ./test/test.html" 8 | }, 9 | "keywords": [ 10 | "wx", 11 | "wx-app", 12 | "wxml", 13 | "virtual-dom" 14 | ], 15 | "author": { 16 | "name": "Long Xiang", 17 | "email": "seanlong@outlook.com" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/seanlong/wxml-parser.git" 22 | }, 23 | "license": "MIT", 24 | "devDependencies": { 25 | "babel-preset-es2015": "^6.18.0", 26 | "babelify": "^7.3.0", 27 | "browserify": "^13.1.1", 28 | "gulp": "^3.9.1", 29 | "gulp-load-plugins": "^1.3.0", 30 | "gulp-sourcemaps": "^2.2.0", 31 | "gulp-uglify": "^2.0.0", 32 | "gulp-util": "^3.0.7", 33 | "lodash.assign": "^4.2.0", 34 | "mocha": "^3.1.2", 35 | "mocha-phantomjs": "^4.1.0", 36 | "phantomjs": "^2.1.7", 37 | "vinyl-buffer": "^1.0.0", 38 | "vinyl-source-stream": "^1.1.0", 39 | "watchify": "^3.7.0" 40 | }, 41 | "dependencies": {} 42 | } 43 | -------------------------------------------------------------------------------- /test/condition.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var parser = require('../index.js') 3 | 4 | function assertObject(result, expect) { 5 | return assert.equal(JSON.stringify(result), JSON.stringify(expect)) 6 | } 7 | 8 | describe('Conditional rendering', function() { 9 | 10 | it('wx:if', function() { 11 | var data = { 12 | condition: 'true' 13 | } 14 | var input = ` 15 | True 16 | ` 17 | var expect = { 18 | "tag": "wx-body", 19 | "attr": {}, 20 | "children": [ 21 | { 22 | "tag": "wx-view", 23 | "attr": { 24 | "wx:if": "true" 25 | }, 26 | "children": [ 27 | "True" 28 | ] 29 | } 30 | ] 31 | } 32 | assertObject(parser(input, data), expect) 33 | }); 34 | 35 | it('wx:if/else', function() { 36 | var data = { 37 | length: 7 38 | } 39 | var input = ` 40 | 1 41 | 3 42 | ` 43 | var expect = { 44 | "tag": "wx-body", 45 | "attr": {}, 46 | "children": [ 47 | { 48 | "tag": "wx-view", 49 | "attr": { 50 | "wx:if": true 51 | }, 52 | "children": [ 53 | "1" 54 | ] 55 | } 56 | ] 57 | } 58 | assertObject(parser(input, data), expect) 59 | }); 60 | 61 | it('wx:if/else embedding', function() { 62 | var data = { 63 | length: 7 64 | } 65 | var input = ` 66 | 1 67 | 68 | 3 69 | 4 70 | 5 71 | 72 | ` 73 | var expect = { 74 | "tag": "wx-body", 75 | "attr": {}, 76 | "children": [ 77 | { 78 | "tag": "wx-view", 79 | "attr": { 80 | "wx:else": "" 81 | }, 82 | "children": [ 83 | "3", 84 | { 85 | "tag": "wx-view", 86 | "attr": { 87 | "wx:if": true 88 | }, 89 | "children": [ 90 | "4" 91 | ] 92 | } 93 | ] 94 | } 95 | ] 96 | } 97 | assertObject(parser(input, data), expect) 98 | }); 99 | 100 | it('wx:if/elif/else', function() { 101 | var data = { 102 | length: 3 103 | } 104 | var input = ` 105 | 1 106 | 2 107 | 3 108 | ` 109 | var expect = { 110 | "tag": "wx-body", 111 | "attr": {}, 112 | "children": [ 113 | { 114 | "tag": "wx-view", 115 | "attr": { 116 | "wx:elif": true 117 | }, 118 | "children": [ 119 | "2" 120 | ] 121 | } 122 | ] 123 | } 124 | assertObject(parser(input, data), expect) 125 | }); 126 | 127 | it('block wx:if', function() { 128 | var data = {} 129 | var input = ` 130 | 131 | view1 132 | view2 133 | 134 | ` 135 | var expect = { 136 | "tag": "wx-body", 137 | "attr": {}, 138 | "children": [ 139 | { 140 | "tag": "wx-view", 141 | "attr": {}, 142 | "children": [ 143 | "view1" 144 | ] 145 | }, 146 | { 147 | "tag": "wx-view", 148 | "attr": {}, 149 | "children": [ 150 | "view2" 151 | ] 152 | } 153 | ] 154 | } 155 | assertObject(parser(input, data), expect) 156 | }); 157 | 158 | 159 | it('block wx:else', function() { 160 | var data = {} 161 | var input = ` 162 | 163 | view1 164 | view2 165 | 166 | 167 | view3 168 | view4 169 | 170 | ` 171 | var expect = { 172 | "tag": "wx-body", 173 | "attr": {}, 174 | "children": [ 175 | { 176 | "tag": "wx-view", 177 | "attr": {}, 178 | "children": [ 179 | "view3" 180 | ] 181 | }, 182 | { 183 | "tag": "wx-view", 184 | "attr": {}, 185 | "children": [ 186 | "view4" 187 | ] 188 | } 189 | ] 190 | } 191 | assertObject(parser(input, data), expect) 192 | }); 193 | 194 | }) -------------------------------------------------------------------------------- /test/databinding.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var parser = require('../index.js') 3 | 4 | function assertObject(result, expect) { 5 | return assert.equal(JSON.stringify(result), JSON.stringify(expect)) 6 | } 7 | 8 | describe('Data binding', function() { 9 | 10 | describe('Simple binding', function() { 11 | 12 | it('data content', function() { 13 | var data = { 14 | message: 'Hello MINA!' 15 | } 16 | var input = ` 17 | {{ message }} 18 | ` 19 | var expect = { 20 | "tag": "wx-body", 21 | "attr": {}, 22 | "children": [ 23 | { 24 | "tag": "wx-view", 25 | "attr": {}, 26 | "children": [ 27 | "Hello MINA!" 28 | ] 29 | } 30 | ] 31 | } 32 | assertObject(parser(input, data), expect) 33 | }); 34 | 35 | it('data content no single root', function() { 36 | var data = { 37 | message: 'Hello MINA!' 38 | } 39 | var input = ` 40 | {{ message }} 41 | {{ message }} 42 | ` 43 | var expect = { 44 | "tag": "wx-body", 45 | "attr": {}, 46 | "children": [ 47 | { 48 | "tag": "wx-view", 49 | "attr": {}, 50 | "children": [ 51 | "Hello MINA!" 52 | ] 53 | }, 54 | { 55 | "tag": "wx-view", 56 | "attr": {}, 57 | "children": [ 58 | "Hello MINA!" 59 | ] 60 | } 61 | ] 62 | } 63 | assertObject(parser(input, data), expect) 64 | }); 65 | 66 | it('widget attribute', function() { 67 | var data = { 68 | id: 0 69 | } 70 | var input = ` 71 | 72 | ` 73 | var expect = { 74 | "tag": "wx-body", 75 | "attr": {}, 76 | "children": [ 77 | { 78 | "tag": "wx-view", 79 | "attr": { 80 | "id": "item-0" 81 | }, 82 | "children": [] 83 | } 84 | ] 85 | } 86 | assertObject(parser(input, data), expect) 87 | }); 88 | 89 | it('control attribute: true', function() { 90 | var data = { 91 | condition: true 92 | } 93 | var input = ` 94 | 95 | ` 96 | var expect = { 97 | "tag": "wx-body", 98 | "attr": {}, 99 | "children": [ 100 | { 101 | "tag": "wx-view", 102 | "attr": { 103 | "wx:if": true 104 | }, 105 | "children": [] 106 | } 107 | ] 108 | } 109 | assertObject(parser(input, data), expect) 110 | }); 111 | 112 | it('control attribute: false', function() { 113 | var data = { 114 | condition: false 115 | } 116 | var input = ` 117 | 118 | ` 119 | var expect = { 120 | "tag": "wx-body", 121 | "attr": {}, 122 | "children": [] 123 | } 124 | assertObject(parser(input, data), expect) 125 | }); 126 | 127 | }) 128 | 129 | 130 | describe('Computation', function() { 131 | 132 | it('ternary operation', function() { 133 | var data = { 134 | flag: 1 135 | } 136 | var input = ` 137 | 138 | ` 139 | var expect = { 140 | "tag": "wx-body", 141 | "attr": {}, 142 | "children": [ 143 | { 144 | "tag": "wx-view", 145 | "attr": { 146 | "hidden": true 147 | }, 148 | "children": [ 149 | "Hidden" 150 | ] 151 | } 152 | ] 153 | } 154 | assertObject(parser(input, data), expect) 155 | }); 156 | 157 | it('arithmetic operation', function() { 158 | var data = { 159 | a: 1, 160 | b: 2, 161 | c: 3 162 | } 163 | var input = ` 164 | {{a + b}} + {{c}} + d 165 | ` 166 | var expect = { 167 | "tag": "wx-body", 168 | "attr": {}, 169 | "children": [ 170 | { 171 | "tag": "wx-view", 172 | "attr": {}, 173 | "children": [ 174 | "3 + 3 + d" 175 | ] 176 | } 177 | ] 178 | } 179 | assertObject(parser(input, data), expect) 180 | }); 181 | 182 | it('logical operation', function() { 183 | var data = { 184 | length: 4 185 | } 186 | var input = ` 187 | 188 | ` 189 | var expect = { 190 | "tag": "wx-body", 191 | "attr": {}, 192 | "children": [] 193 | } 194 | assertObject(parser(input, data), expect) 195 | }); 196 | 197 | it('string operation', function() { 198 | var data = { 199 | name: 'MINA' 200 | } 201 | var input = ` 202 | {{"hello" + name}} 203 | ` 204 | var expect = { 205 | "tag": "wx-body", 206 | "attr": {}, 207 | "children": [ 208 | { 209 | "tag": "wx-view", 210 | "attr": {}, 211 | "children": [ 212 | "helloMINA" 213 | ] 214 | } 215 | ] 216 | } 217 | assertObject(parser(input, data), expect) 218 | }); 219 | 220 | }) 221 | 222 | describe('Compositing', function() { 223 | 224 | it('array', function() { 225 | var data = { 226 | zero: 0 227 | } 228 | var input = ` 229 | {{item}} 230 | ` 231 | var expect = { 232 | "tag": "wx-body", 233 | "attr": {}, 234 | "children": [ 235 | { 236 | "tag": "wx-view", 237 | "attr": {}, 238 | "children": [ 239 | 0 240 | ] 241 | }, 242 | { 243 | "tag": "wx-view", 244 | "attr": {}, 245 | "children": [ 246 | 1 247 | ] 248 | }, 249 | { 250 | "tag": "wx-view", 251 | "attr": {}, 252 | "children": [ 253 | 2 254 | ] 255 | }, 256 | { 257 | "tag": "wx-view", 258 | "attr": {}, 259 | "children": [ 260 | 3 261 | ] 262 | }, 263 | { 264 | "tag": "wx-view", 265 | "attr": {}, 266 | "children": [ 267 | 4 268 | ] 269 | } 270 | ] 271 | } 272 | assertObject(parser(input, data), expect) 273 | }); 274 | 275 | it('object', function() { 276 | var data = { 277 | a: 1, 278 | b: 2 279 | } 280 | var input = ` 281 | 282 | ` 283 | var expect = { 284 | "tag": "wx-body", 285 | "attr": {}, 286 | "children": [ 287 | { 288 | "tag": "wx-xxx", 289 | "attr": { 290 | "is": "objectCombine", 291 | "data": { 292 | "foo": 1, 293 | "bar": 2 294 | } 295 | }, 296 | "children": [] 297 | } 298 | ] 299 | } 300 | assertObject(parser(input, data), expect) 301 | }); 302 | 303 | // TODO add babel eval transform support 304 | // it('object expand', function() { 305 | // var data = { 306 | // obj1: { 307 | // a: 1, 308 | // b: 2 309 | // } 310 | // } 311 | // var input = ` 312 | // 313 | // ` 314 | // var expect = { 315 | // } 316 | // console.log(JSON.stringify(parser(input, data))) 317 | // assertObject(parser(input, data), expect) 318 | // }); 319 | 320 | }) 321 | 322 | }); -------------------------------------------------------------------------------- /test/list.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var parser = require('../index.js') 3 | 4 | function assertObject(result, expect) { 5 | return assert.equal(JSON.stringify(result), JSON.stringify(expect)) 6 | } 7 | 8 | describe('List rendering', function() { 9 | 10 | it('wx:for', function() { 11 | var data = { 12 | items: [{ 13 | message: 'foo', 14 | }, { 15 | message: 'bar' 16 | }] 17 | } 18 | var input = ` 19 | 20 | {{index}}: {{item.message}} 21 | 22 | ` 23 | var expect = { 24 | "tag": "wx-body", 25 | "attr": {}, 26 | "children": [ 27 | { 28 | "tag": "wx-view", 29 | "attr": {}, 30 | "children": [ 31 | "0: foo" 32 | ] 33 | }, 34 | { 35 | "tag": "wx-view", 36 | "attr": {}, 37 | "children": [ 38 | "1: bar" 39 | ] 40 | } 41 | ] 42 | } 43 | assertObject(parser(input, data), expect) 44 | }); 45 | 46 | it('wx:for-item wx:for-index', function() { 47 | var data = { 48 | array: [ 49 | { message: 'a' }, 50 | { message: 'b' } 51 | ] 52 | } 53 | var input = ` 54 | 55 | {{idx}}: {{itemName.message}} 56 | 57 | ` 58 | var expect = { 59 | "tag": "wx-body", 60 | "attr": {}, 61 | "children": [ 62 | { 63 | "tag": "wx-view", 64 | "attr": {}, 65 | "children": [ 66 | "0: a" 67 | ] 68 | }, 69 | { 70 | "tag": "wx-view", 71 | "attr": {}, 72 | "children": [ 73 | "1: b" 74 | ] 75 | } 76 | ] 77 | } 78 | assertObject(parser(input, data), expect) 79 | }); 80 | 81 | it('wx:for embedding', function() { 82 | var data = {} 83 | var input = ` 84 | 85 | 86 | 87 | {{i}} * {{j}} = {{i * j}} 88 | 89 | 90 | 91 | ` 92 | var expect = { 93 | "tag": "wx-body", 94 | "attr": {}, 95 | "children": [ 96 | { 97 | "tag": "wx-view", 98 | "attr": {}, 99 | "children": [ 100 | { 101 | "tag": "wx-view", 102 | "attr": {}, 103 | "children": [ 104 | { 105 | "tag": "wx-view", 106 | "attr": { 107 | "wx:if": true 108 | }, 109 | "children": [ 110 | "1 * 1 = 1" 111 | ] 112 | } 113 | ] 114 | }, 115 | { 116 | "tag": "wx-view", 117 | "attr": {}, 118 | "children": [ 119 | { 120 | "tag": "wx-view", 121 | "attr": { 122 | "wx:if": true 123 | }, 124 | "children": [ 125 | "1 * 2 = 2" 126 | ] 127 | } 128 | ] 129 | }, 130 | { 131 | "tag": "wx-view", 132 | "attr": {}, 133 | "children": [ 134 | { 135 | "tag": "wx-view", 136 | "attr": { 137 | "wx:if": true 138 | }, 139 | "children": [ 140 | "1 * 3 = 3" 141 | ] 142 | } 143 | ] 144 | } 145 | ] 146 | }, 147 | { 148 | "tag": "wx-view", 149 | "attr": {}, 150 | "children": [ 151 | { 152 | "tag": "wx-view", 153 | "attr": {}, 154 | "children": [] 155 | }, 156 | { 157 | "tag": "wx-view", 158 | "attr": {}, 159 | "children": [ 160 | { 161 | "tag": "wx-view", 162 | "attr": { 163 | "wx:if": true 164 | }, 165 | "children": [ 166 | "2 * 2 = 4" 167 | ] 168 | } 169 | ] 170 | }, 171 | { 172 | "tag": "wx-view", 173 | "attr": {}, 174 | "children": [ 175 | { 176 | "tag": "wx-view", 177 | "attr": { 178 | "wx:if": true 179 | }, 180 | "children": [ 181 | "2 * 3 = 6" 182 | ] 183 | } 184 | ] 185 | } 186 | ] 187 | }, 188 | { 189 | "tag": "wx-view", 190 | "attr": {}, 191 | "children": [ 192 | { 193 | "tag": "wx-view", 194 | "attr": {}, 195 | "children": [] 196 | }, 197 | { 198 | "tag": "wx-view", 199 | "attr": {}, 200 | "children": [] 201 | }, 202 | { 203 | "tag": "wx-view", 204 | "attr": {}, 205 | "children": [ 206 | { 207 | "tag": "wx-view", 208 | "attr": { 209 | "wx:if": true 210 | }, 211 | "children": [ 212 | "3 * 3 = 9" 213 | ] 214 | } 215 | ] 216 | } 217 | ] 218 | } 219 | ] 220 | } 221 | assertObject(parser(input, data), expect) 222 | }); 223 | 224 | it('block wx:for', function() { 225 | var data = {} 226 | var input = ` 227 | 228 | {{index}}: 229 | {{item}} 230 | 231 | ` 232 | var expect = { 233 | "tag": "wx-body", 234 | "attr": {}, 235 | "children": [ 236 | { 237 | "tag": "wx-view", 238 | "attr": {}, 239 | "children": [ 240 | "0:" 241 | ] 242 | }, 243 | { 244 | "tag": "wx-view", 245 | "attr": {}, 246 | "children": [ 247 | 1 248 | ] 249 | }, 250 | { 251 | "tag": "wx-view", 252 | "attr": {}, 253 | "children": [ 254 | "1:" 255 | ] 256 | }, 257 | { 258 | "tag": "wx-view", 259 | "attr": {}, 260 | "children": [ 261 | 2 262 | ] 263 | }, 264 | { 265 | "tag": "wx-view", 266 | "attr": {}, 267 | "children": [ 268 | "2:" 269 | ] 270 | }, 271 | { 272 | "tag": "wx-view", 273 | "attr": {}, 274 | "children": [ 275 | 3 276 | ] 277 | } 278 | ] 279 | } 280 | //console.log(JSON.stringify(parser(input, data), null, 2)); 281 | assertObject(parser(input, data), expect) 282 | }); 283 | 284 | }) -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | require('./databinding.test.js') 2 | require('./condition.test.js') 3 | require('./list.test.js') --------------------------------------------------------------------------------