├── .gitattributes ├── test ├── materiels │ ├── noop.js │ └── noop.html ├── package.json ├── bower.json ├── test.coveralls.js ├── test.mock.html ├── test.mock.mock.js ├── valid.js ├── test.mock.valid.js ├── test.mock.schema.js ├── test.mock.spec.dpd.js ├── test.mock.request.js └── test.mock.random.js ├── src ├── mock │ ├── valid │ │ ├── index.js │ │ └── valid.js │ ├── xhr │ │ ├── index.js │ │ └── xhr.js │ ├── schema │ │ ├── index.js │ │ └── schema.js │ ├── regexp │ │ ├── index.js │ │ └── handler.js │ ├── random │ │ ├── index.js │ │ ├── address.js │ │ ├── color_dict.js │ │ ├── name.js │ │ ├── helper.js │ │ ├── misc.js │ │ ├── web.js │ │ ├── color_convert.js │ │ ├── text.js │ │ ├── basic.js │ │ ├── date.js │ │ ├── color.js │ │ └── image.js │ ├── constant.js │ ├── parser.js │ ├── util.js │ └── RE_KEY.svg ├── dependencies.png └── mock.js ├── .spmignore ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .editorconfig ├── bower.json ├── LICENSE ├── package.json ├── README.md ├── bin └── random ├── CHANGELOG.md └── gulpfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /test/materiels/noop.js: -------------------------------------------------------------------------------- 1 | (function noop() {})(); -------------------------------------------------------------------------------- /src/mock/valid/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./valid') -------------------------------------------------------------------------------- /src/mock/xhr/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./xhr') -------------------------------------------------------------------------------- /.spmignore: -------------------------------------------------------------------------------- 1 | bin 2 | demo 3 | doc 4 | editor 5 | src 6 | test 7 | -------------------------------------------------------------------------------- /src/mock/schema/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./schema') -------------------------------------------------------------------------------- /src/dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/Mock/refactoring/src/dependencies.png -------------------------------------------------------------------------------- /test/materiels/noop.html: -------------------------------------------------------------------------------- 1 | 2 |
noop.html
-------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "chai": "^1.10.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bak 2 | .DS_Store 3 | .*.swp 4 | .idea 5 | node_modules 6 | bower_components 7 | coverage 8 | PLAN.md 9 | npm-debug.log -------------------------------------------------------------------------------- /src/mock/regexp/index.js: -------------------------------------------------------------------------------- 1 | var Parser = require('./parser') 2 | var Handler = require('./handler') 3 | module.exports = { 4 | Parser: Parser, 5 | Handler: Handler 6 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "asi": true, 4 | "strict": false, 5 | "undef": true, 6 | "unused": "strict", 7 | "multistr": true, 8 | "node": true 9 | } -------------------------------------------------------------------------------- /test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": {}, 4 | "devDependencies": { 5 | "requirejs": "*", 6 | "mocha": "*", 7 | "chai": "*", 8 | "underscore": "*", 9 | "jquery": "~2.1.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 |   - npm install -g gulp 6 | - npm install -g bower 7 | before_script: 8 | - bower install 9 | - cd test 10 | - bower install 11 | - cd .. 12 | after_script: 13 | - npm run coveralls 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [package.json] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mockjs", 3 | "title": "Mock.js", 4 | "main": "dist/mock.js", 5 | "license": "MIT", 6 | "ignore": [ 7 | "**/.*", 8 | "node_modules", 9 | "bower_components", 10 | "src", 11 | "test", 12 | "site", 13 | "plugins", 14 | "package.json", 15 | "gulpfile.js", 16 | "AUTHORS.txt", 17 | "CONTRIBUTING.md", 18 | "HISTORY.md", 19 | "CHANGELOG.md", 20 | "README.md", 21 | "LICENSE" 22 | ], 23 | "devDependencies": {} 24 | } -------------------------------------------------------------------------------- /src/mock/random/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## Mock.Random 3 | 4 | 工具类,用于生成各种随机数据。 5 | */ 6 | 7 | var Util = require('../util') 8 | 9 | var Random = { 10 | extend: Util.extend 11 | } 12 | 13 | Random.extend(require('./basic')) 14 | Random.extend(require('./date')) 15 | Random.extend(require('./image')) 16 | Random.extend(require('./color')) 17 | Random.extend(require('./text')) 18 | Random.extend(require('./name')) 19 | Random.extend(require('./web')) 20 | Random.extend(require('./address')) 21 | Random.extend(require('./helper')) 22 | Random.extend(require('./misc')) 23 | 24 | module.exports = Random -------------------------------------------------------------------------------- /src/mock/constant.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## Constant 3 | 4 | 常量集合。 5 | */ 6 | /* 7 | RE_KEY 8 | 'name|min-max': value 9 | 'name|count': value 10 | 'name|min-max.dmin-dmax': value 11 | 'name|min-max.dcount': value 12 | 'name|count.dmin-dmax': value 13 | 'name|count.dcount': value 14 | 'name|+step': value 15 | 16 | 1 name, 2 step, 3 range [ min, max ], 4 drange [ dmin, dmax ] 17 | 18 | RE_PLACEHOLDER 19 | placeholder(*) 20 | 21 | [正则查看工具](http://www.regexper.com/) 22 | 23 | #26 生成规则 支持 负数,例如 number|-100-100 24 | */ 25 | module.exports = { 26 | GUID: 1, 27 | RE_KEY: /(.+)\|(?:\+(\d+)|([\+\-]?\d+-?[\+\-]?\d*)?(?:\.(\d+-?\d*))?)/, 28 | RE_RANGE: /([\+\-]?\d+)-?([\+\-]?\d+)?/, 29 | RE_PLACEHOLDER: /\\*@([^@#%&()\?\s]+)(?:\((.*?)\))?/g 30 | // /\\*@([^@#%&()\?\s\/\.]+)(?:\((.*?)\))?/g 31 | // RE_INDEX: /^index$/, 32 | // RE_KEY: /^key$/ 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 nuysoft 4 | http://mockjs.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mockjs", 3 | "title": "Mock.js", 4 | "description": "生成随机数据 & 拦截 Ajax 请求", 5 | "version": "1.0.1-beta3", 6 | "homepage": "http://mockjs.com/", 7 | "keywords": [ 8 | "mock", 9 | "mockJSON", 10 | "mockAjax" 11 | ], 12 | "author": "nuysoft@gmail.com", 13 | "dependencies": { 14 | "commander": "*" 15 | }, 16 | "devDependencies": { 17 | "gulp": "^3.9.0", 18 | "gulp-connect": "*", 19 | "gulp-coveralls": "^0.1.4", 20 | "gulp-istanbul": "^0.10.3", 21 | "gulp-jshint": "^2.0.0", 22 | "gulp-mocha": "^2.2.0", 23 | "gulp-mocha-phantomjs": "^0.10.1", 24 | "jshint": "^2.8.0", 25 | "jshint-stylish": "^2.1.0", 26 | "webpack": "^1.12.9" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/nuysoft/Mock.git" 31 | }, 32 | "main": "./dist/mock.js", 33 | "scripts": { 34 | "test": "gulp mocha", 35 | "coveralls": "gulp coveralls" 36 | }, 37 | "bin": { 38 | "random": "bin/random" 39 | }, 40 | "licenses": [ 41 | { 42 | "type": "MIT", 43 | "url": "https://github.com/nuysoft/Mock/blob/master/LICENSE" 44 | } 45 | ], 46 | "spm": { 47 | "main": "dist/mock.js" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/test.coveralls.js: -------------------------------------------------------------------------------- 1 | /* global require */ 2 | /* global describe, it */ 3 | var assert = require("assert") 4 | describe('Array', function() { 5 | describe('#indexOf()', function() { 6 | it('should return -1 when the value is not present', function() { 7 | assert.equal(-1, [1, 2, 3].indexOf(5)) 8 | assert.equal(-1, [1, 2, 3].indexOf(0)) 9 | }) 10 | }) 11 | }) 12 | describe('Array', function() { 13 | describe('#indexOf()', function() { 14 | it('should return -1 when the value is not present', function() { 15 | assert.equal(-1, [1, 2, 3].indexOf(5)) 16 | assert.equal(-1, [1, 2, 3].indexOf(0)) 17 | }) 18 | }) 19 | }) 20 | describe('Array', function() { 21 | describe('#indexOf()', function() { 22 | it('should return -1 when the value is not present', function() { 23 | assert.equal(-1, [1, 2, 3].indexOf(5)) 24 | assert.equal(-1, [1, 2, 3].indexOf(0)) 25 | }) 26 | }) 27 | }) 28 | describe('Array', function() { 29 | describe('#indexOf()', function() { 30 | it('should return -1 when the value is not present', function() { 31 | assert.equal(-1, [1, 2, 3].indexOf(5)) 32 | assert.equal(-1, [1, 2, 3].indexOf(0)) 33 | }) 34 | }) 35 | }) -------------------------------------------------------------------------------- /src/mock/random/address.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## Address 3 | */ 4 | 5 | var DICT = require('./address_dict') 6 | var REGION = ['东北', '华北', '华东', '华中', '华南', '西南', '西北'] 7 | 8 | module.exports = { 9 | // 随机生成一个大区。 10 | region: function() { 11 | return this.pick(REGION) 12 | }, 13 | // 随机生成一个(中国)省(或直辖市、自治区、特别行政区)。 14 | province: function() { 15 | return this.pick(DICT).name 16 | }, 17 | // 随机生成一个(中国)市。 18 | city: function(prefix) { 19 | var province = this.pick(DICT) 20 | var city = this.pick(province.children) 21 | return prefix ? [province.name, city.name].join(' ') : city.name 22 | }, 23 | // 随机生成一个(中国)县。 24 | county: function(prefix) { 25 | var province = this.pick(DICT) 26 | var city = this.pick(province.children) 27 | var county = this.pick(city.children) || { 28 | name: '-' 29 | } 30 | return prefix ? [province.name, city.name, county.name].join(' ') : county.name 31 | }, 32 | // 随机生成一个邮政编码(六位数字)。 33 | zip: function(len) { 34 | var zip = '' 35 | for (var i = 0; i < (len || 6); i++) zip += this.natural(0, 9) 36 | return zip 37 | } 38 | 39 | // address: function() {}, 40 | // phone: function() {}, 41 | // areacode: function() {}, 42 | // street: function() {}, 43 | // street_suffixes: function() {}, 44 | // street_suffix: function() {}, 45 | // states: function() {}, 46 | // state: function() {}, 47 | } -------------------------------------------------------------------------------- /src/mock/schema/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## toJSONSchema 3 | 4 | 把 Mock.js 风格的数据模板转换成 JSON Schema。 5 | 6 | > [JSON Schema](http://json-schema.org/) 7 | */ 8 | var Constant = require('../constant') 9 | var Util = require('../util') 10 | var Parser = require('../parser') 11 | 12 | function toJSONSchema(template, name, path /* Internal Use Only */ ) { 13 | // type rule properties items 14 | path = path || [] 15 | var result = { 16 | name: typeof name === 'string' ? name.replace(Constant.RE_KEY, '$1') : name, 17 | template: template, 18 | type: Util.type(template), // 可能不准确,例如 { 'name|1': [{}, {} ...] } 19 | rule: Parser.parse(name) 20 | } 21 | result.path = path.slice(0) 22 | result.path.push(name === undefined ? 'ROOT' : result.name) 23 | 24 | switch (result.type) { 25 | case 'array': 26 | result.items = [] 27 | Util.each(template, function(value, index) { 28 | result.items.push( 29 | toJSONSchema(value, index, result.path) 30 | ) 31 | }) 32 | break 33 | case 'object': 34 | result.properties = [] 35 | Util.each(template, function(value, name) { 36 | result.properties.push( 37 | toJSONSchema(value, name, result.path) 38 | ) 39 | }) 40 | break 41 | } 42 | 43 | return result 44 | 45 | } 46 | 47 | module.exports = toJSONSchema 48 | -------------------------------------------------------------------------------- /src/mock/random/color_dict.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## Color 字典数据 3 | 4 | 字典数据来源 [A nicer color palette for the web](http://clrs.cc/) 5 | */ 6 | module.exports = { 7 | // name value nicer 8 | navy: { 9 | value: '#000080', 10 | nicer: '#001F3F' 11 | }, 12 | blue: { 13 | value: '#0000ff', 14 | nicer: '#0074D9' 15 | }, 16 | aqua: { 17 | value: '#00ffff', 18 | nicer: '#7FDBFF' 19 | }, 20 | teal: { 21 | value: '#008080', 22 | nicer: '#39CCCC' 23 | }, 24 | olive: { 25 | value: '#008000', 26 | nicer: '#3D9970' 27 | }, 28 | green: { 29 | value: '#008000', 30 | nicer: '#2ECC40' 31 | }, 32 | lime: { 33 | value: '#00ff00', 34 | nicer: '#01FF70' 35 | }, 36 | yellow: { 37 | value: '#ffff00', 38 | nicer: '#FFDC00' 39 | }, 40 | orange: { 41 | value: '#ffa500', 42 | nicer: '#FF851B' 43 | }, 44 | red: { 45 | value: '#ff0000', 46 | nicer: '#FF4136' 47 | }, 48 | maroon: { 49 | value: '#800000', 50 | nicer: '#85144B' 51 | }, 52 | fuchsia: { 53 | value: '#ff00ff', 54 | nicer: '#F012BE' 55 | }, 56 | purple: { 57 | value: '#800080', 58 | nicer: '#B10DC9' 59 | }, 60 | silver: { 61 | value: '#c0c0c0', 62 | nicer: '#DDDDDD' 63 | }, 64 | gray: { 65 | value: '#808080', 66 | nicer: '#AAAAAA' 67 | }, 68 | black: { 69 | value: '#000000', 70 | nicer: '#111111' 71 | }, 72 | white: { 73 | value: '#FFFFFF', 74 | nicer: '#FFFFFF' 75 | } 76 | } -------------------------------------------------------------------------------- /src/mock.js: -------------------------------------------------------------------------------- 1 | /* global require, module, window */ 2 | var Handler = require('./mock/handler') 3 | var Util = require('./mock/util') 4 | var Random = require('./mock/random') 5 | var RE = require('./mock/regexp') 6 | var toJSONSchema = require('./mock/schema') 7 | var valid = require('./mock/valid') 8 | 9 | var XHR 10 | if (typeof window !== 'undefined') XHR = require('./mock/xhr') 11 | 12 | /*! 13 | Mock - 模拟请求 & 模拟数据 14 | https://github.com/nuysoft/Mock 15 | 墨智 mozhi.gyy@taobao.com nuysoft@gmail.com 16 | */ 17 | var Mock = { 18 | Handler: Handler, 19 | Random: Random, 20 | Util: Util, 21 | XHR: XHR, 22 | RE: RE, 23 | toJSONSchema: toJSONSchema, 24 | valid: valid, 25 | heredoc: Util.heredoc, 26 | setup: function(settings) { 27 | return XHR.setup(settings) 28 | }, 29 | _mocked: {} 30 | } 31 | 32 | Mock.version = '1.0.1-beta3' 33 | 34 | // 避免循环依赖 35 | if (XHR) XHR.Mock = Mock 36 | 37 | /* 38 | * Mock.mock( template ) 39 | * Mock.mock( function() ) 40 | * Mock.mock( rurl, template ) 41 | * Mock.mock( rurl, function(options) ) 42 | * Mock.mock( rurl, rtype, template ) 43 | * Mock.mock( rurl, rtype, function(options) ) 44 | 45 | 根据数据模板生成模拟数据。 46 | */ 47 | Mock.mock = function(rurl, rtype, template) { 48 | // Mock.mock(template) 49 | if (arguments.length === 1) { 50 | return Handler.gen(rurl) 51 | } 52 | // Mock.mock(rurl, template) 53 | if (arguments.length === 2) { 54 | template = rtype 55 | rtype = undefined 56 | } 57 | // 拦截 XHR 58 | if (XHR) window.XMLHttpRequest = XHR 59 | Mock._mocked[rurl + (rtype || '')] = { 60 | rurl: rurl, 61 | rtype: rtype, 62 | template: template 63 | } 64 | return Mock 65 | } 66 | 67 | module.exports = Mock -------------------------------------------------------------------------------- /src/mock/parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | ## Parser 3 | 4 | 解析数据模板(属性名部分)。 5 | 6 | * Parser.parse( name ) 7 | 8 | ```json 9 | { 10 | parameters: [ name, inc, range, decimal ], 11 | rnage: [ min , max ], 12 | 13 | min: min, 14 | max: max, 15 | count : count, 16 | 17 | decimal: decimal, 18 | dmin: dmin, 19 | dmax: dmax, 20 | dcount: dcount 21 | } 22 | ``` 23 | */ 24 | 25 | var Constant = require('./constant') 26 | var Random = require('./random/') 27 | 28 | /* jshint -W041 */ 29 | module.exports = { 30 | parse: function(name) { 31 | name = name == undefined ? '' : (name + '') 32 | 33 | var parameters = (name || '').match(Constant.RE_KEY) 34 | 35 | var range = parameters && parameters[3] && parameters[3].match(Constant.RE_RANGE) 36 | var min = range && range[1] && parseInt(range[1], 10) // || 1 37 | var max = range && range[2] && parseInt(range[2], 10) // || 1 38 | // repeat || min-max || 1 39 | // var count = range ? !range[2] && parseInt(range[1], 10) || Random.integer(min, max) : 1 40 | var count = range ? !range[2] ? parseInt(range[1], 10) : Random.integer(min, max) : undefined 41 | 42 | var decimal = parameters && parameters[4] && parameters[4].match(Constant.RE_RANGE) 43 | var dmin = decimal && decimal[1] && parseInt(decimal[1], 10) // || 0, 44 | var dmax = decimal && decimal[2] && parseInt(decimal[2], 10) // || 0, 45 | // int || dmin-dmax || 0 46 | var dcount = decimal ? !decimal[2] && parseInt(decimal[1], 10) || Random.integer(dmin, dmax) : undefined 47 | 48 | var result = { 49 | // 1 name, 2 inc, 3 range, 4 decimal 50 | parameters: parameters, 51 | // 1 min, 2 max 52 | range: range, 53 | min: min, 54 | max: max, 55 | // min-max 56 | count: count, 57 | // 是否有 decimal 58 | decimal: decimal, 59 | dmin: dmin, 60 | dmax: dmax, 61 | // dmin-dimax 62 | dcount: dcount 63 | } 64 | 65 | for (var r in result) { 66 | if (result[r] != undefined) return result 67 | } 68 | 69 | return {} 70 | } 71 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mock.js 2 | 3 | [![Build Status](https://travis-ci.org/nuysoft/Mock.svg?branch=refactoring)](https://travis-ci.org/nuysoft/Mock) 4 | 5 | 10 | 11 | Mock.js is a simulation data generator to help the front-end to develop and prototype separate from the back-end progress and reduce some monotony particularly while writing automated tests. 12 | 13 | The official site: 14 | 15 | ## Features 16 | 17 | * Generate simulated data according to the data template 18 | * Provide request/response mocking for ajax requests 19 | * ~~Generate simulated data according to HTML-based templates~~ 20 | 21 | This library is loosely inspired by Elijah Manor's post [Mocking 22 | Introduction](http://www.elijahmanor.com/2013/04/angry-birds-of-javascript-green-bird.html), [mennovanslooten/mockJSON](https://github.com/mennovanslooten/mockJSON), [appendto/jquery-mockjax](https://github.com/appendto/jquery-mockjax) and [victorquinn/chancejs](https://github.com/victorquinn/chancejs/). 23 | 24 | ## Questions? 25 | If you have any questions, please feel free to ask through [New Issue](https://github.com/nuysoft/Mock/issues/new). 26 | 27 | ## Reporting an Issue 28 | Make sure the problem you're addressing is reproducible. Use or to provide a test page. Indicate what browsers the issue can be reproduced in. What version of Mock.js is the issue reproducible in. Is it reproducible after updating to the latest version? 29 | 30 | ## License 31 | Mock.js is available under the terms of the [MIT License](./LICENSE). -------------------------------------------------------------------------------- /test/test.mock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test: Mock 5 | 6 | 7 | 8 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 |
35 |
36 |

37 |     

38 | 
39 |     
40 |     
41 |     
42 |     
43 |     
44 |     
45 |     
46 |     
47 |     
48 |     
49 |     
56 | 
57 | 


--------------------------------------------------------------------------------
/test/test.mock.mock.js:
--------------------------------------------------------------------------------
 1 | /* global require, chai, describe, before, it */
 2 | // 数据占位符定义(Data Placeholder Definition,DPD)
 3 | var expect = chai.expect
 4 | var Mock, $, _
 5 | 
 6 | describe('Mock.mock', function() {
 7 |     before(function(done) {
 8 |         require(['mock', 'underscore', 'jquery'], function() {
 9 |             Mock = arguments[0]
10 |             _ = arguments[1]
11 |             $ = arguments[2]
12 |             expect(Mock).to.not.equal(undefined)
13 |             expect(_).to.not.equal(undefined)
14 |             expect($).to.not.equal(undefined)
15 |             done()
16 |         })
17 |     })
18 | 
19 |     describe('Mock.mock( String )', function() {
20 |         it('@EMAIL', function() {
21 |             var data = Mock.mock(this.test.title)
22 |             expect(data).to.not.equal(this.test.title)
23 |             this.test.title += ' => ' + data
24 |         })
25 |     })
26 |     describe('Mock.mock( {} )', function() {
27 |         it('', function() {
28 |             var tpl = {
29 |                 'list|1-10': [{
30 |                     'id|+1': 1,
31 |                     'email': '@EMAIL'
32 |                 }]
33 |             }
34 |             var data = Mock.mock(tpl)
35 |             this.test.title = JSON.stringify(tpl /*, null, 4*/ ) + ' => ' + JSON.stringify(data /*, null, 4*/ )
36 |             expect(data).to.have.property('list')
37 |                 .that.be.an('array').with.length.within(1, 10)
38 |             _.each(data.list, function(item, index, list) {
39 |                 if (index > 0) expect(item.id).to.equal(list[index - 1].id + 1)
40 |             })
41 |         })
42 |     })
43 |     describe('Mock.mock( function() )', function() {
44 |         it('', function() {
45 |             var fn = function() {
46 |                 return Mock.mock({
47 |                     'list|1-10': [{
48 |                         'id|+1': 1,
49 |                         'email': '@EMAIL'
50 |                     }]
51 |                 })
52 |             }
53 |             var data = Mock.mock(fn)
54 |             this.test.title = fn.toString() + ' => ' + JSON.stringify(data /*, null, 4*/ )
55 |             expect(data).to.have.property('list')
56 |                 .that.be.an('array').with.length.within(1, 10)
57 |             _.each(data.list, function(item, index, list) {
58 |                 if (index > 0) expect(item.id).to.equal(list[index - 1].id + 1)
59 |             })
60 |         })
61 |     })
62 | })


--------------------------------------------------------------------------------
/bin/random:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | 
 3 | "use strict";
 4 | 
 5 | /*
 6 |     https://github.com/visionmedia/commander.js
 7 |     http://visionmedia.github.io/commander.js/
 8 |     https://github.com/visionmedia/commander.js/tree/master/examples
 9 | 
10 |     sudo npm install ./ -g
11 | */
12 | 
13 | var path = require('path')
14 | var program = require('commander')
15 | var pkg = require(path.resolve(__dirname, '../package.json'))
16 | var Random = require('../dist/mock.js').Random
17 | 
18 | program
19 |     .version(pkg.version)
20 |     .on('--help', function() {
21 |         console.log('  Examples:')
22 |         console.log('')
23 |         console.log('    $ random date yyyy-MM-dd')
24 |         console.log('    $ random time HH:mm:ss')
25 |         console.log('')
26 |     })
27 | 
28 | ;
29 | (function() {
30 | 
31 |     var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m
32 |     var FN_ARG_SPLIT = /,/
33 |     var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/
34 |     var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg
35 |     var EXCLUDE = [
36 |         'extend',
37 |         'dataImage', // mock/random/image
38 |         'capitalize', 'upper', 'lower', 'pick', 'shuffle', 'order', // mock/random/helper.js
39 |         'increment', 'inc' // mock/random/misc.js
40 |     ]
41 | 
42 |     function parseArgs(fn) {
43 |         var fnText = fn.toString().replace(STRIP_COMMENTS, '')
44 |         var argDecl = fnText.match(FN_ARGS)
45 |         return argDecl[1].split(FN_ARG_SPLIT).join(', ')
46 |     }
47 | 
48 |     Object.keys(Random).forEach(function(key) {
49 |         if (key[0] === '_') return
50 |         if (EXCLUDE.indexOf(key) !== -1) return
51 | 
52 |         var fn = Random[key]
53 |         if (typeof fn === 'function') {
54 |             var argDecl = parseArgs(fn)
55 |             if (argDecl) argDecl = '( ' + argDecl + ' )'
56 |             else argDecl = '()';
57 | 
58 |             program
59 |                 .command(key)
60 |                 .description('Random.' + key + argDecl)
61 |                 .action(function() {
62 |                     var args = [].slice.call(arguments, 0, -1)
63 |                     var result = fn.apply(Random, args)
64 |                     console.log(result)
65 |                 })
66 |         }
67 |     })
68 | 
69 | })()
70 | 
71 | program.parse(process.argv)
72 | 
73 | ;
74 | (function() {
75 |     var cmd = program.args[0]
76 |     if (!cmd) {
77 |         process.stdout.write(program.helpInformation())
78 |         program.emit('--help')
79 |         process.exit()
80 |     }
81 | })()


--------------------------------------------------------------------------------
/src/mock/random/name.js:
--------------------------------------------------------------------------------
 1 | /*
 2 |     ## Name
 3 | 
 4 |     [Beyond the Top 1000 Names](http://www.ssa.gov/oact/babynames/limits.html)
 5 | */
 6 | module.exports = {
 7 | 	// 随机生成一个常见的英文名。
 8 | 	first: function() {
 9 | 		var names = [
10 | 			// male
11 | 			"James", "John", "Robert", "Michael", "William",
12 | 			"David", "Richard", "Charles", "Joseph", "Thomas",
13 | 			"Christopher", "Daniel", "Paul", "Mark", "Donald",
14 | 			"George", "Kenneth", "Steven", "Edward", "Brian",
15 | 			"Ronald", "Anthony", "Kevin", "Jason", "Matthew",
16 | 			"Gary", "Timothy", "Jose", "Larry", "Jeffrey",
17 | 			"Frank", "Scott", "Eric"
18 | 		].concat([
19 | 			// female
20 | 			"Mary", "Patricia", "Linda", "Barbara", "Elizabeth",
21 | 			"Jennifer", "Maria", "Susan", "Margaret", "Dorothy",
22 | 			"Lisa", "Nancy", "Karen", "Betty", "Helen",
23 | 			"Sandra", "Donna", "Carol", "Ruth", "Sharon",
24 | 			"Michelle", "Laura", "Sarah", "Kimberly", "Deborah",
25 | 			"Jessica", "Shirley", "Cynthia", "Angela", "Melissa",
26 | 			"Brenda", "Amy", "Anna"
27 | 		])
28 | 		return this.pick(names)
29 | 			// or this.capitalize(this.word())
30 | 	},
31 | 	// 随机生成一个常见的英文姓。
32 | 	last: function() {
33 | 		var names = [
34 | 			"Smith", "Johnson", "Williams", "Brown", "Jones",
35 | 			"Miller", "Davis", "Garcia", "Rodriguez", "Wilson",
36 | 			"Martinez", "Anderson", "Taylor", "Thomas", "Hernandez",
37 | 			"Moore", "Martin", "Jackson", "Thompson", "White",
38 | 			"Lopez", "Lee", "Gonzalez", "Harris", "Clark",
39 | 			"Lewis", "Robinson", "Walker", "Perez", "Hall",
40 | 			"Young", "Allen"
41 | 		]
42 | 		return this.pick(names)
43 | 			// or this.capitalize(this.word())
44 | 	},
45 | 	// 随机生成一个常见的英文姓名。
46 | 	name: function(middle) {
47 | 		return this.first() + ' ' +
48 | 			(middle ? this.first() + ' ' : '') +
49 | 			this.last()
50 | 	},
51 | 	/*
52 | 	    随机生成一个常见的中文姓。
53 | 	    [世界常用姓氏排行](http://baike.baidu.com/view/1719115.htm)
54 | 	    [玄派网 - 网络小说创作辅助平台](http://xuanpai.sinaapp.com/)
55 | 	 */
56 | 	cfirst: function() {
57 | 		var names = (
58 | 			'王 李 张 刘 陈 杨 赵 黄 周 吴 ' +
59 | 			'徐 孙 胡 朱 高 林 何 郭 马 罗 ' +
60 | 			'梁 宋 郑 谢 韩 唐 冯 于 董 萧 ' +
61 | 			'程 曹 袁 邓 许 傅 沈 曾 彭 吕 ' +
62 | 			'苏 卢 蒋 蔡 贾 丁 魏 薛 叶 阎 ' +
63 | 			'余 潘 杜 戴 夏 锺 汪 田 任 姜 ' +
64 | 			'范 方 石 姚 谭 廖 邹 熊 金 陆 ' +
65 | 			'郝 孔 白 崔 康 毛 邱 秦 江 史 ' +
66 | 			'顾 侯 邵 孟 龙 万 段 雷 钱 汤 ' +
67 | 			'尹 黎 易 常 武 乔 贺 赖 龚 文'
68 | 		).split(' ')
69 | 		return this.pick(names)
70 | 	},
71 | 	/*
72 | 	    随机生成一个常见的中文名。
73 | 	    [中国最常见名字前50名_三九算命网](http://www.name999.net/xingming/xingshi/20131004/48.html)
74 | 	 */
75 | 	clast: function() {
76 | 		var names = (
77 | 			'伟 芳 娜 秀英 敏 静 丽 强 磊 军 ' +
78 | 			'洋 勇 艳 杰 娟 涛 明 超 秀兰 霞 ' +
79 | 			'平 刚 桂英'
80 | 		).split(' ')
81 | 		return this.pick(names)
82 | 	},
83 | 	// 随机生成一个常见的中文姓名。
84 | 	cname: function() {
85 | 		return this.cfirst() + this.clast()
86 | 	}
87 | }


--------------------------------------------------------------------------------
/src/mock/random/helper.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Helpers
  3 | */
  4 | 
  5 | var Util = require('../util')
  6 | 
  7 | module.exports = {
  8 | 	// 把字符串的第一个字母转换为大写。
  9 | 	capitalize: function(word) {
 10 | 		return (word + '').charAt(0).toUpperCase() + (word + '').substr(1)
 11 | 	},
 12 | 	// 把字符串转换为大写。
 13 | 	upper: function(str) {
 14 | 		return (str + '').toUpperCase()
 15 | 	},
 16 | 	// 把字符串转换为小写。
 17 | 	lower: function(str) {
 18 | 		return (str + '').toLowerCase()
 19 | 	},
 20 | 	// 从数组中随机选取一个元素,并返回。
 21 | 	pick: function pick(arr, min, max) {
 22 | 		// pick( item1, item2 ... )
 23 | 		if (!Util.isArray(arr)) {
 24 | 			arr = [].slice.call(arguments)
 25 | 			min = 1
 26 | 			max = 1
 27 | 		} else {
 28 | 			// pick( [ item1, item2 ... ] )
 29 | 			if (min === undefined) min = 1
 30 | 
 31 | 			// pick( [ item1, item2 ... ], count )
 32 | 			if (max === undefined) max = min
 33 | 		}
 34 | 
 35 | 		if (min === 1 && max === 1) return arr[this.natural(0, arr.length - 1)]
 36 | 
 37 | 		// pick( [ item1, item2 ... ], min, max )
 38 | 		return this.shuffle(arr, min, max)
 39 | 
 40 | 		// 通过参数个数判断方法签名,扩展性太差!#90
 41 | 		// switch (arguments.length) {
 42 | 		// 	case 1:
 43 | 		// 		// pick( [ item1, item2 ... ] )
 44 | 		// 		return arr[this.natural(0, arr.length - 1)]
 45 | 		// 	case 2:
 46 | 		// 		// pick( [ item1, item2 ... ], count )
 47 | 		// 		max = min
 48 | 		// 			/* falls through */
 49 | 		// 	case 3:
 50 | 		// 		// pick( [ item1, item2 ... ], min, max )
 51 | 		// 		return this.shuffle(arr, min, max)
 52 | 		// }
 53 | 	},
 54 | 	/*
 55 | 	    打乱数组中元素的顺序,并返回。
 56 | 	    Given an array, scramble the order and return it.
 57 | 
 58 | 	    其他的实现思路:
 59 | 	        // https://code.google.com/p/jslibs/wiki/JavascriptTips
 60 | 	        result = result.sort(function() {
 61 | 	            return Math.random() - 0.5
 62 | 	        })
 63 | 	*/
 64 | 	shuffle: function shuffle(arr, min, max) {
 65 | 		arr = arr || []
 66 | 		var old = arr.slice(0),
 67 | 			result = [],
 68 | 			index = 0,
 69 | 			length = old.length;
 70 | 		for (var i = 0; i < length; i++) {
 71 | 			index = this.natural(0, old.length - 1)
 72 | 			result.push(old[index])
 73 | 			old.splice(index, 1)
 74 | 		}
 75 | 		switch (arguments.length) {
 76 | 			case 0:
 77 | 			case 1:
 78 | 				return result
 79 | 			case 2:
 80 | 				max = min
 81 | 					/* falls through */
 82 | 			case 3:
 83 | 				min = parseInt(min, 10)
 84 | 				max = parseInt(max, 10)
 85 | 				return result.slice(0, this.natural(min, max))
 86 | 		}
 87 | 	},
 88 | 	/*
 89 | 	    * Random.order(item, item)
 90 | 	    * Random.order([item, item ...])
 91 | 
 92 | 	    顺序获取数组中的元素
 93 | 
 94 | 	    [JSON导入数组支持数组数据录入](https://github.com/thx/RAP/issues/22)
 95 | 
 96 | 	    不支持单独调用!
 97 | 	*/
 98 | 	order: function order(array) {
 99 | 		order.cache = order.cache || {}
100 | 
101 | 		if (arguments.length > 1) array = [].slice.call(arguments, 0)
102 | 
103 | 		// options.context.path/templatePath
104 | 		var options = order.options
105 | 		var templatePath = options.context.templatePath.join('.')
106 | 
107 | 		var cache = (
108 | 			order.cache[templatePath] = order.cache[templatePath] || {
109 | 				index: 0,
110 | 				array: array
111 | 			}
112 | 		)
113 | 
114 | 		return cache.array[cache.index++ % cache.array.length]
115 | 	}
116 | }


--------------------------------------------------------------------------------
/src/mock/random/misc.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Miscellaneous
  3 | */
  4 | var DICT = require('./address_dict')
  5 | module.exports = {
  6 | 	// Dice
  7 | 	d4: function() {
  8 | 		return this.natural(1, 4)
  9 | 	},
 10 | 	d6: function() {
 11 | 		return this.natural(1, 6)
 12 | 	},
 13 | 	d8: function() {
 14 | 		return this.natural(1, 8)
 15 | 	},
 16 | 	d12: function() {
 17 | 		return this.natural(1, 12)
 18 | 	},
 19 | 	d20: function() {
 20 | 		return this.natural(1, 20)
 21 | 	},
 22 | 	d100: function() {
 23 | 		return this.natural(1, 100)
 24 | 	},
 25 | 	/*
 26 | 	    随机生成一个 GUID。
 27 | 
 28 | 	    http://www.broofa.com/2008/09/javascript-uuid-function/
 29 | 	    [UUID 规范](http://www.ietf.org/rfc/rfc4122.txt)
 30 | 	        UUIDs (Universally Unique IDentifier)
 31 | 	        GUIDs (Globally Unique IDentifier)
 32 | 	        The formal definition of the UUID string representation is provided by the following ABNF [7]:
 33 | 	            UUID                   = time-low "-" time-mid "-"
 34 | 	                                   time-high-and-version "-"
 35 | 	                                   clock-seq-and-reserved
 36 | 	                                   clock-seq-low "-" node
 37 | 	            time-low               = 4hexOctet
 38 | 	            time-mid               = 2hexOctet
 39 | 	            time-high-and-version  = 2hexOctet
 40 | 	            clock-seq-and-reserved = hexOctet
 41 | 	            clock-seq-low          = hexOctet
 42 | 	            node                   = 6hexOctet
 43 | 	            hexOctet               = hexDigit hexDigit
 44 | 	            hexDigit =
 45 | 	                "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" /
 46 | 	                "a" / "b" / "c" / "d" / "e" / "f" /
 47 | 	                "A" / "B" / "C" / "D" / "E" / "F"
 48 | 	    
 49 | 	    https://github.com/victorquinn/chancejs/blob/develop/chance.js#L1349
 50 | 	*/
 51 | 	guid: function() {
 52 | 		var pool = "abcdefABCDEF1234567890",
 53 | 			guid = this.string(pool, 8) + '-' +
 54 | 			this.string(pool, 4) + '-' +
 55 | 			this.string(pool, 4) + '-' +
 56 | 			this.string(pool, 4) + '-' +
 57 | 			this.string(pool, 12);
 58 | 		return guid
 59 | 	},
 60 | 	uuid: function() {
 61 | 		return this.guid()
 62 | 	},
 63 | 	/*
 64 | 	    随机生成一个 18 位身份证。
 65 | 
 66 | 	    [身份证](http://baike.baidu.com/view/1697.htm#4)
 67 | 	        地址码 6 + 出生日期码 8 + 顺序码 3 + 校验码 1
 68 | 	    [《中华人民共和国行政区划代码》国家标准(GB/T2260)](http://zhidao.baidu.com/question/1954561.html)
 69 | 	*/
 70 | 	id: function() {
 71 | 		var id,
 72 | 			sum = 0,
 73 | 			rank = [
 74 | 				"7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"
 75 | 			],
 76 | 			last = [
 77 | 				"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"
 78 | 			]
 79 | 
 80 | 		id = this.pick(DICT).id +
 81 | 			this.date('yyyyMMdd') +
 82 | 			this.string('number', 3)
 83 | 
 84 | 		for (var i = 0; i < id.length; i++) {
 85 | 			sum += id[i] * rank[i];
 86 | 		}
 87 | 		id += last[sum % 11];
 88 | 
 89 | 		return id
 90 | 	},
 91 | 
 92 | 	/*
 93 | 	    生成一个全局的自增整数。
 94 | 	    类似自增主键(auto increment primary key)。
 95 | 	*/
 96 | 	increment: function() {
 97 | 		var key = 0
 98 | 		return function(step) {
 99 | 			return key += (+step || 1) // step?
100 | 		}
101 | 	}(),
102 | 	inc: function(step) {
103 | 		return this.increment(step)
104 | 	}
105 | }


--------------------------------------------------------------------------------
/src/mock/random/web.js:
--------------------------------------------------------------------------------
 1 | /*
 2 |     ## Web
 3 | */
 4 | module.exports = {
 5 |     /*
 6 |         随机生成一个 URL。
 7 | 
 8 |         [URL 规范](http://www.w3.org/Addressing/URL/url-spec.txt)
 9 |             http                    Hypertext Transfer Protocol 
10 |             ftp                     File Transfer protocol 
11 |             gopher                  The Gopher protocol 
12 |             mailto                  Electronic mail address 
13 |             mid                     Message identifiers for electronic mail 
14 |             cid                     Content identifiers for MIME body part 
15 |             news                    Usenet news 
16 |             nntp                    Usenet news for local NNTP access only 
17 |             prospero                Access using the prospero protocols 
18 |             telnet rlogin tn3270    Reference to interactive sessions
19 |             wais                    Wide Area Information Servers 
20 |     */
21 |     url: function(protocol, host) {
22 |         return (protocol || this.protocol()) + '://' + // protocol?
23 |             (host || this.domain()) + // host?
24 |             '/' + this.word()
25 |     },
26 |     // 随机生成一个 URL 协议。
27 |     protocol: function() {
28 |         return this.pick(
29 |             // 协议簇
30 |             'http ftp gopher mailto mid cid news nntp prospero telnet rlogin tn3270 wais'.split(' ')
31 |         )
32 |     },
33 |     // 随机生成一个域名。
34 |     domain: function(tld) {
35 |         return this.word() + '.' + (tld || this.tld())
36 |     },
37 |     /*
38 |         随机生成一个顶级域名。
39 |         国际顶级域名 international top-level domain-names, iTLDs
40 |         国家顶级域名 national top-level domainnames, nTLDs
41 |         [域名后缀大全](http://www.163ns.com/zixun/post/4417.html)
42 |     */
43 |     tld: function() { // Top Level Domain
44 |         return this.pick(
45 |             (
46 |                 // 域名后缀
47 |                 'com net org edu gov int mil cn ' +
48 |                 // 国内域名
49 |                 'com.cn net.cn gov.cn org.cn ' +
50 |                 // 中文国内域名
51 |                 '中国 中国互联.公司 中国互联.网络 ' +
52 |                 // 新国际域名
53 |                 'tel biz cc tv info name hk mobi asia cd travel pro museum coop aero ' +
54 |                 // 世界各国域名后缀
55 |                 'ad ae af ag ai al am an ao aq ar as at au aw az ba bb bd be bf bg bh bi bj bm bn bo br bs bt bv bw by bz ca cc cf cg ch ci ck cl cm cn co cq cr cu cv cx cy cz de dj dk dm do dz ec ee eg eh es et ev fi fj fk fm fo fr ga gb gd ge gf gh gi gl gm gn gp gr gt gu gw gy hk hm hn hr ht hu id ie il in io iq ir is it jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md mg mh ml mm mn mo mp mq mr ms mt mv mw mx my mz na nc ne nf ng ni nl no np nr nt nu nz om qa pa pe pf pg ph pk pl pm pn pr pt pw py re ro ru rw sa sb sc sd se sg sh si sj sk sl sm sn so sr st su sy sz tc td tf tg th tj tk tm tn to tp tr tt tv tw tz ua ug uk us uy va vc ve vg vn vu wf ws ye yu za zm zr zw'
56 |             ).split(' ')
57 |         )
58 |     },
59 |     // 随机生成一个邮件地址。
60 |     email: function(domain) {
61 |         return this.character('lower') + '.' + this.word() + '@' +
62 |             (
63 |                 domain ||
64 |                 (this.word() + '.' + this.tld())
65 |             )
66 |             // return this.character('lower') + '.' + this.last().toLowerCase() + '@' + this.last().toLowerCase() + '.' + this.tld()
67 |             // return this.word() + '@' + (domain || this.domain())
68 |     },
69 |     // 随机生成一个 IP 地址。
70 |     ip: function() {
71 |         return this.natural(0, 255) + '.' +
72 |             this.natural(0, 255) + '.' +
73 |             this.natural(0, 255) + '.' +
74 |             this.natural(0, 255)
75 |     }
76 | }


--------------------------------------------------------------------------------
/src/mock/util.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Utilities
  3 | */
  4 | var Util = {}
  5 | 
  6 | Util.extend = function extend() {
  7 |     var target = arguments[0] || {},
  8 |         i = 1,
  9 |         length = arguments.length,
 10 |         options, name, src, copy, clone
 11 | 
 12 |     if (length === 1) {
 13 |         target = this
 14 |         i = 0
 15 |     }
 16 | 
 17 |     for (; i < length; i++) {
 18 |         options = arguments[i]
 19 |         if (!options) continue
 20 | 
 21 |         for (name in options) {
 22 |             src = target[name]
 23 |             copy = options[name]
 24 | 
 25 |             if (target === copy) continue
 26 |             if (copy === undefined) continue
 27 | 
 28 |             if (Util.isArray(copy) || Util.isObject(copy)) {
 29 |                 if (Util.isArray(copy)) clone = src && Util.isArray(src) ? src : []
 30 |                 if (Util.isObject(copy)) clone = src && Util.isObject(src) ? src : {}
 31 | 
 32 |                 target[name] = Util.extend(clone, copy)
 33 |             } else {
 34 |                 target[name] = copy
 35 |             }
 36 |         }
 37 |     }
 38 | 
 39 |     return target
 40 | }
 41 | 
 42 | Util.each = function each(obj, iterator, context) {
 43 |     var i, key
 44 |     if (this.type(obj) === 'number') {
 45 |         for (i = 0; i < obj; i++) {
 46 |             iterator(i, i)
 47 |         }
 48 |     } else if (obj.length === +obj.length) {
 49 |         for (i = 0; i < obj.length; i++) {
 50 |             if (iterator.call(context, obj[i], i, obj) === false) break
 51 |         }
 52 |     } else {
 53 |         for (key in obj) {
 54 |             if (iterator.call(context, obj[key], key, obj) === false) break
 55 |         }
 56 |     }
 57 | }
 58 | 
 59 | Util.type = function type(obj) {
 60 |     return (obj === null || obj === undefined) ? String(obj) : Object.prototype.toString.call(obj).match(/\[object (\w+)\]/)[1].toLowerCase()
 61 | }
 62 | 
 63 | Util.each('String Object Array RegExp Function'.split(' '), function(value) {
 64 |     Util['is' + value] = function(obj) {
 65 |         return Util.type(obj) === value.toLowerCase()
 66 |     }
 67 | })
 68 | 
 69 | Util.isObjectOrArray = function(value) {
 70 |     return Util.isObject(value) || Util.isArray(value)
 71 | }
 72 | 
 73 | Util.isNumeric = function(value) {
 74 |     return !isNaN(parseFloat(value)) && isFinite(value)
 75 | }
 76 | 
 77 | Util.keys = function(obj) {
 78 |     var keys = [];
 79 |     for (var key in obj) {
 80 |         if (obj.hasOwnProperty(key)) keys.push(key)
 81 |     }
 82 |     return keys;
 83 | }
 84 | Util.values = function(obj) {
 85 |     var values = [];
 86 |     for (var key in obj) {
 87 |         if (obj.hasOwnProperty(key)) values.push(obj[key])
 88 |     }
 89 |     return values;
 90 | }
 91 | 
 92 | /*
 93 |     ### Mock.heredoc(fn)
 94 | 
 95 |     * Mock.heredoc(fn)
 96 | 
 97 |     以直观、安全的方式书写(多行)HTML 模板。
 98 | 
 99 |     **使用示例**如下所示:
100 | 
101 |         var tpl = Mock.heredoc(function() {
102 |             /*!
103 |         {{email}}{{age}}
104 |         
108 |             *\/
109 |         })
110 |     
111 |     **相关阅读**
112 |     * [Creating multiline strings in JavaScript](http://stackoverflow.com/questions/805107/creating-multiline-strings-in-javascript)、
113 | */
114 | Util.heredoc = function heredoc(fn) {
115 |     // 1. 移除起始的 function(){ /*!
116 |     // 2. 移除末尾的 */ }
117 |     // 3. 移除起始和末尾的空格
118 |     return fn.toString()
119 |         .replace(/^[^\/]+\/\*!?/, '')
120 |         .replace(/\*\/[^\/]+$/, '')
121 |         .replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '') // .trim()
122 | }
123 | 
124 | Util.noop = function() {}
125 | 
126 | module.exports = Util


--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | # Changelog
  2 | 
  3 | ## 2015.12.03 V0.2.0-alpha2
  4 | 
  5 | 1. 改用 webpack 打包
  6 | 
  7 | ### 2014.3.20 V0.2.0-alpha1
  8 | 
  9 | 1. 增加网站
 10 | 
 11 | ### 2014.12.23 V0.2.0 重构代码
 12 | 
 13 | 1. 改用 gulp 打包
 14 | 2. 改用 mocha 重写测试用例
 15 | 3. 改用 requirejs 重构代码
 16 | 
 17 | ### 2014.6.24 V0.2.0 重构代码
 18 | 
 19 | 1. 支持 UMD,包括:
 20 |     * 未打包前的代码
 21 |     * 打包后的代码
 22 | 2. random CLI
 23 |     * --help 增加方法和参数说明
 24 | 3. 重构文档站 @萝素
 25 |     * 增加《入门》
 26 |     * 单列《文档》
 27 | 4. 测试用例
 28 |     * 重写测试用例
 29 |     * 同时支持 nodeunit 和 qunit
 30 |     * 同时支持 jQuery、KISSY、Zepto
 31 |     * 同时支持 KMD、AMD、CMD
 32 | 5. 复写 XHR @行列 @霍庸
 33 | 6. 废弃的功能
 34 |     * Mock.mockjax()
 35 |     * Mock.tpl()
 36 |     * Mock.xtpl()
 37 | 7. Random.dateImage() 支持 node-canvas
 38 | 8. Mock.valid(tpl, data)
 39 | 9. Mock.toJOSNSchema()
 40 | 10. Mock.mock(regexp) 
 41 | 11. 完善地支持 node,代码中的:
 42 |     * window
 43 |     * document
 44 |     * XHRHttpRequest
 45 | 12. 支持相对路径
 46 | 
 47 | ### 2014.6.23 V0.1.5
 48 | 
 49 | 1. [!] 修复 #28 #29,因为 jQuery 每个版本在 Ajax 实现上有些差异,导致在拦截 Ajax 请求时出现了兼容性问题(例如,方法 `xhr.onload()` 访问不到)。本次[测试](http://jsfiddle.net/8y8Fz/)并通过的 jQuery 版本有:
 50 | 
 51 |     * jQuery 2.1.0
 52 |     * jQuery 2.0.2
 53 |     * jQuery 1.11.0
 54 |     * jQuery 1.10.1
 55 |     * jQuery 1.9.1
 56 |     * jQuery 1.8.3
 57 |     * jQuery 1.7.2
 58 |     * jQuery 1.6.4
 59 | 
 60 | 非常抱歉,这个问题一直困扰着 Mock.js 用户,在后面的版本中,会通过拦截 XMLHttpRequest 的方法“一劳永逸”地解决拦截 Ajax 的兼容和适配问题。
 61 | 
 62 | ### 2014.6.18 V0.1.4
 63 | 
 64 | 1. [!] 修复 #14 0.1.1版本试了好像jq1.10可以,1.11下$.ajax拦截没反应
 65 | 2. [!] 修复 #22 异步加载js文件的时候发现问题
 66 | 3. [!] 修复 #23 Mock.mockjax 导致 $.getScript 不执行回调
 67 | 4. [!] 修复 #24 Window Firefox 30.0 引用 占位符 抛错
 68 | 5. [!] 修复 #25 改变了非函数属性的顺序,查找起来不方便
 69 | 6. [!] 修复 #26 生成规则 支持 负数 number|-100-+100
 70 | 7. [!] 修复 #27 数据模板编辑器 格式化(Tidy) 时会丢掉 函数属性
 71 | 8. [+] 数据模板编辑器 增加了 编辑区 和 生成结果区 的同步滚动
 72 | 9. [!] test/nodeuinit > test/nodeunit
 73 | 
 74 | ### 2014.5.26 V0.1.3
 75 | 
 76 | 1. [!] 修复 #21
 77 | 
 78 | ### 2014.5.26 V0.1.2
 79 | 
 80 | 1. [!] 重构 Mock.mockjax()
 81 | 2. [!] 更新 package.json/devDependencies
 82 | 3. [+] 增加 懒懒交流会 PPT
 83 | 
 84 | ### 2014.5.9 V0.1.2
 85 | 1. [+] 支持 [`Mock.mock(rurl, rtype, template)`](http://mockjs.com/#mock)
 86 | 2. [+] 支持 [`'name|min-max': {}`、`'name|count': {}`](http://mockjs.com/#语法规范)
 87 | 3. [+] 支持 [`'name': function(){}`](http://mockjs.com/#语法规范)
 88 | 4. [+] 新增占位符 [@NOW](http://mockjs.com/#now)
 89 | 5. [+] 更新了 [语法规范](http://mockjs.com/#语法规范)
 90 | 
 91 | ### 2013.9.6
 92 | 1. 增加占位符 @DATAIMAGE
 93 | 2. 解析占位符时**完全**忽略大小写
 94 | 
 95 | ### 2013.9.3
 96 | 1. 文档增加用法示例:Sea.js (CMD)、RequireJS (AMD)
 97 | 2. 增加对 CMD 规范的支持
 98 | 3. 生成 SourceMap 文件 `dist/mock-min.map`
 99 | 
100 | ### 2013.8.21
101 | 1. 100% 基于客户端模板生成模拟数据,支持 KISSY XTemplate。
102 | 1. 调整文件结构。
103 | 
104 | ### 2013.8.11
105 | 1. 80% 基于客户端模板生成模拟数据。
106 | 1. 完善针对 KISSY XTemplate 的测试用例 [test/mock4tpl-xtpl-node.js](test/mock4tpl-xtpl-node.js)。
107 | 1. [Mock4Tpl](src/tpl/mock4tpl.js) 支持 Partials。
108 | 1. Mock 支持转义 @。
109 | 1. 更新 README.md,增加对 Mock4Tpl 的说明。
110 | 1. 完善 [demo](demo/)。
111 | 1. 减少 Mock、Mock4Tpl 暴漏的 API。
112 | 
113 | ### 2013.8.7
114 | 1. 75% 基于客户端模板生成模拟数据。
115 | 1. 完善测试用例 [test/mock4tpl-node.js](test/mock4tpl-node.js)。
116 | 1. 重构文件和目录结构,把代码模块化。
117 | 1. 参考 Handlebars.js,引入 Jison 生成模板解析器。
118 | 
119 | #### 2013.8.2
120 | 1. 60% 基于客户端模板生成模拟数据。
121 | 1. 增加测试用例 [test/mock4tpl-node.js](test/mock4tpl-node.js),参考自 。
122 | 
123 | #### 2013.7.31
124 | 1. 50% 基于客户端模板生成模拟数据。
125 | 
126 | #### 2013.7.18
127 | 1. 增加占位符 @COLOR。
128 | 1. 完善对占位符的解析,过滤掉 `#%&()?/.`。
129 | 1. 对“支持的占位符”分组。
130 | 
131 | #### 2013.7.12
132 | 1. Mock.mock(rurl, template) 的参数 rurl 可以是字符串或正则。
133 | 1. 把产生随机元数据的接口封装到 Mock.Random 中。
134 | 1. 增加对日期的格式化。
135 | 1. 增加占位符 @IMG、@PARAGRAPH、@SENTENCE、@WORD、@FIRST、@LAST、@NAME、@DOMAIN、@EMAIL、@IP、@ID。
136 | 1. 支持嵌套的占位符,例如 `@IMG(@AD_SIZE)`。
137 | 1. 支持把普通属性当作占位符使用,例如 `@IMG(@size)`。


--------------------------------------------------------------------------------
/src/mock/random/color_convert.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Color Convert
  3 | 
  4 |     http://blog.csdn.net/idfaya/article/details/6770414
  5 |         颜色空间RGB与HSV(HSL)的转换
  6 | */
  7 | // https://github.com/harthur/color-convert/blob/master/conversions.js
  8 | module.exports = {
  9 | 	rgb2hsl: function rgb2hsl(rgb) {
 10 | 		var r = rgb[0] / 255,
 11 | 			g = rgb[1] / 255,
 12 | 			b = rgb[2] / 255,
 13 | 			min = Math.min(r, g, b),
 14 | 			max = Math.max(r, g, b),
 15 | 			delta = max - min,
 16 | 			h, s, l;
 17 | 
 18 | 		if (max == min)
 19 | 			h = 0;
 20 | 		else if (r == max)
 21 | 			h = (g - b) / delta;
 22 | 		else if (g == max)
 23 | 			h = 2 + (b - r) / delta;
 24 | 		else if (b == max)
 25 | 			h = 4 + (r - g) / delta;
 26 | 
 27 | 		h = Math.min(h * 60, 360);
 28 | 
 29 | 		if (h < 0)
 30 | 			h += 360;
 31 | 
 32 | 		l = (min + max) / 2;
 33 | 
 34 | 		if (max == min)
 35 | 			s = 0;
 36 | 		else if (l <= 0.5)
 37 | 			s = delta / (max + min);
 38 | 		else
 39 | 			s = delta / (2 - max - min);
 40 | 
 41 | 		return [h, s * 100, l * 100];
 42 | 	},
 43 | 	rgb2hsv: function rgb2hsv(rgb) {
 44 | 		var r = rgb[0],
 45 | 			g = rgb[1],
 46 | 			b = rgb[2],
 47 | 			min = Math.min(r, g, b),
 48 | 			max = Math.max(r, g, b),
 49 | 			delta = max - min,
 50 | 			h, s, v;
 51 | 
 52 | 		if (max === 0)
 53 | 			s = 0;
 54 | 		else
 55 | 			s = (delta / max * 1000) / 10;
 56 | 
 57 | 		if (max == min)
 58 | 			h = 0;
 59 | 		else if (r == max)
 60 | 			h = (g - b) / delta;
 61 | 		else if (g == max)
 62 | 			h = 2 + (b - r) / delta;
 63 | 		else if (b == max)
 64 | 			h = 4 + (r - g) / delta;
 65 | 
 66 | 		h = Math.min(h * 60, 360);
 67 | 
 68 | 		if (h < 0)
 69 | 			h += 360;
 70 | 
 71 | 		v = ((max / 255) * 1000) / 10;
 72 | 
 73 | 		return [h, s, v];
 74 | 	},
 75 | 	hsl2rgb: function hsl2rgb(hsl) {
 76 | 		var h = hsl[0] / 360,
 77 | 			s = hsl[1] / 100,
 78 | 			l = hsl[2] / 100,
 79 | 			t1, t2, t3, rgb, val;
 80 | 
 81 | 		if (s === 0) {
 82 | 			val = l * 255;
 83 | 			return [val, val, val];
 84 | 		}
 85 | 
 86 | 		if (l < 0.5)
 87 | 			t2 = l * (1 + s);
 88 | 		else
 89 | 			t2 = l + s - l * s;
 90 | 		t1 = 2 * l - t2;
 91 | 
 92 | 		rgb = [0, 0, 0];
 93 | 		for (var i = 0; i < 3; i++) {
 94 | 			t3 = h + 1 / 3 * -(i - 1);
 95 | 			if (t3 < 0) t3++;
 96 | 			if (t3 > 1) t3--;
 97 | 
 98 | 			if (6 * t3 < 1)
 99 | 				val = t1 + (t2 - t1) * 6 * t3;
100 | 			else if (2 * t3 < 1)
101 | 				val = t2;
102 | 			else if (3 * t3 < 2)
103 | 				val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
104 | 			else
105 | 				val = t1;
106 | 
107 | 			rgb[i] = val * 255;
108 | 		}
109 | 
110 | 		return rgb;
111 | 	},
112 | 	hsl2hsv: function hsl2hsv(hsl) {
113 | 		var h = hsl[0],
114 | 			s = hsl[1] / 100,
115 | 			l = hsl[2] / 100,
116 | 			sv, v;
117 | 		l *= 2;
118 | 		s *= (l <= 1) ? l : 2 - l;
119 | 		v = (l + s) / 2;
120 | 		sv = (2 * s) / (l + s);
121 | 		return [h, sv * 100, v * 100];
122 | 	},
123 | 	hsv2rgb: function hsv2rgb(hsv) {
124 | 		var h = hsv[0] / 60
125 | 		var s = hsv[1] / 100
126 | 		var v = hsv[2] / 100
127 | 		var hi = Math.floor(h) % 6
128 | 
129 | 		var f = h - Math.floor(h)
130 | 		var p = 255 * v * (1 - s)
131 | 		var q = 255 * v * (1 - (s * f))
132 | 		var t = 255 * v * (1 - (s * (1 - f)))
133 | 
134 | 		v = 255 * v
135 | 
136 | 		switch (hi) {
137 | 			case 0:
138 | 				return [v, t, p]
139 | 			case 1:
140 | 				return [q, v, p]
141 | 			case 2:
142 | 				return [p, v, t]
143 | 			case 3:
144 | 				return [p, q, v]
145 | 			case 4:
146 | 				return [t, p, v]
147 | 			case 5:
148 | 				return [v, p, q]
149 | 		}
150 | 	},
151 | 	hsv2hsl: function hsv2hsl(hsv) {
152 | 		var h = hsv[0],
153 | 			s = hsv[1] / 100,
154 | 			v = hsv[2] / 100,
155 | 			sl, l;
156 | 
157 | 		l = (2 - s) * v;
158 | 		sl = s * v;
159 | 		sl /= (l <= 1) ? l : 2 - l;
160 | 		l /= 2;
161 | 		return [h, sl * 100, l * 100];
162 | 	},
163 | 	// http://www.140byt.es/keywords/color
164 | 	rgb2hex: function(
165 | 		a, // red, as a number from 0 to 255
166 | 		b, // green, as a number from 0 to 255
167 | 		c // blue, as a number from 0 to 255
168 | 	) {
169 | 		return "#" + ((256 + a << 8 | b) << 8 | c).toString(16).slice(1)
170 | 	},
171 | 	hex2rgb: function(
172 | 		a // take a "#xxxxxx" hex string,
173 | 	) {
174 | 		a = '0x' + a.slice(1).replace(a.length > 4 ? a : /./g, '$&$&') | 0;
175 | 		return [a >> 16, a >> 8 & 255, a & 255]
176 | 	}
177 | }


--------------------------------------------------------------------------------
/src/mock/random/text.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Text
  3 | 
  4 |     http://www.lipsum.com/
  5 | */
  6 | var Basic = require('./basic')
  7 | var Helper = require('./helper')
  8 | 
  9 | function range(defaultMin, defaultMax, min, max) {
 10 |     return min === undefined ? Basic.natural(defaultMin, defaultMax) : // ()
 11 |         max === undefined ? min : // ( len )
 12 |         Basic.natural(parseInt(min, 10), parseInt(max, 10)) // ( min, max )
 13 | }
 14 | 
 15 | module.exports = {
 16 |     // 随机生成一段文本。
 17 |     paragraph: function(min, max) {
 18 |         var len = range(3, 7, min, max)
 19 |         var result = []
 20 |         for (var i = 0; i < len; i++) {
 21 |             result.push(this.sentence())
 22 |         }
 23 |         return result.join(' ')
 24 |     },
 25 |     // 
 26 |     cparagraph: function(min, max) {
 27 |         var len = range(3, 7, min, max)
 28 |         var result = []
 29 |         for (var i = 0; i < len; i++) {
 30 |             result.push(this.csentence())
 31 |         }
 32 |         return result.join('')
 33 |     },
 34 |     // 随机生成一个句子,第一个单词的首字母大写。
 35 |     sentence: function(min, max) {
 36 |         var len = range(12, 18, min, max)
 37 |         var result = []
 38 |         for (var i = 0; i < len; i++) {
 39 |             result.push(this.word())
 40 |         }
 41 |         return Helper.capitalize(result.join(' ')) + '.'
 42 |     },
 43 |     // 随机生成一个中文句子。
 44 |     csentence: function(min, max) {
 45 |         var len = range(12, 18, min, max)
 46 |         var result = []
 47 |         for (var i = 0; i < len; i++) {
 48 |             result.push(this.cword())
 49 |         }
 50 | 
 51 |         return result.join('') + '。'
 52 |     },
 53 |     // 随机生成一个单词。
 54 |     word: function(min, max) {
 55 |         var len = range(3, 10, min, max)
 56 |         var result = '';
 57 |         for (var i = 0; i < len; i++) {
 58 |             result += Basic.character('lower')
 59 |         }
 60 |         return result
 61 |     },
 62 |     // 随机生成一个或多个汉字。
 63 |     cword: function(pool, min, max) {
 64 |         // 最常用的 500 个汉字 http://baike.baidu.com/view/568436.htm
 65 |         var DICT_KANZI = '的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞'
 66 | 
 67 |         var len
 68 |         switch (arguments.length) {
 69 |             case 0: // ()
 70 |                 pool = DICT_KANZI
 71 |                 len = 1
 72 |                 break
 73 |             case 1: // ( pool )
 74 |                 if (typeof arguments[0] === 'string') {
 75 |                     len = 1
 76 |                 } else {
 77 |                     // ( length )
 78 |                     len = pool
 79 |                     pool = DICT_KANZI
 80 |                 }
 81 |                 break
 82 |             case 2:
 83 |                 // ( pool, length )
 84 |                 if (typeof arguments[0] === 'string') {
 85 |                     len = min
 86 |                 } else {
 87 |                     // ( min, max )
 88 |                     len = this.natural(pool, min)
 89 |                     pool = DICT_KANZI
 90 |                 }
 91 |                 break
 92 |             case 3:
 93 |                 len = this.natural(min, max)
 94 |                 break
 95 |         }
 96 | 
 97 |         var result = ''
 98 |         for (var i = 0; i < len; i++) {
 99 |             result += pool.charAt(this.natural(0, pool.length - 1))
100 |         }
101 |         return result
102 |     },
103 |     // 随机生成一句标题,其中每个单词的首字母大写。
104 |     title: function(min, max) {
105 |         var len = range(3, 7, min, max)
106 |         var result = []
107 |         for (var i = 0; i < len; i++) {
108 |             result.push(this.capitalize(this.word()))
109 |         }
110 |         return result.join(' ')
111 |     },
112 |     // 随机生成一句中文标题。
113 |     ctitle: function(min, max) {
114 |         var len = range(3, 7, min, max)
115 |         var result = []
116 |         for (var i = 0; i < len; i++) {
117 |             result.push(this.cword())
118 |         }
119 |         return result.join('')
120 |     }
121 | }


--------------------------------------------------------------------------------
/src/mock/random/basic.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Basics
  3 | */
  4 | module.exports = {
  5 |     // 返回一个随机的布尔值。
  6 |     boolean: function(min, max, cur) {
  7 |         if (cur !== undefined) {
  8 |             min = typeof min !== 'undefined' && !isNaN(min) ? parseInt(min, 10) : 1
  9 |             max = typeof max !== 'undefined' && !isNaN(max) ? parseInt(max, 10) : 1
 10 |             return Math.random() > 1.0 / (min + max) * min ? !cur : cur
 11 |         }
 12 | 
 13 |         return Math.random() >= 0.5
 14 |     },
 15 |     bool: function(min, max, cur) {
 16 |         return this.boolean(min, max, cur)
 17 |     },
 18 |     // 返回一个随机的自然数(大于等于 0 的整数)。
 19 |     natural: function(min, max) {
 20 |         min = typeof min !== 'undefined' ? parseInt(min, 10) : 0
 21 |         max = typeof max !== 'undefined' ? parseInt(max, 10) : 9007199254740992 // 2^53
 22 |         return Math.round(Math.random() * (max - min)) + min
 23 |     },
 24 |     // 返回一个随机的整数。
 25 |     integer: function(min, max) {
 26 |         min = typeof min !== 'undefined' ? parseInt(min, 10) : -9007199254740992
 27 |         max = typeof max !== 'undefined' ? parseInt(max, 10) : 9007199254740992 // 2^53
 28 |         return Math.round(Math.random() * (max - min)) + min
 29 |     },
 30 |     int: function(min, max) {
 31 |         return this.integer(min, max)
 32 |     },
 33 |     // 返回一个随机的浮点数。
 34 |     float: function(min, max, dmin, dmax) {
 35 |         dmin = dmin === undefined ? 0 : dmin
 36 |         dmin = Math.max(Math.min(dmin, 17), 0)
 37 |         dmax = dmax === undefined ? 17 : dmax
 38 |         dmax = Math.max(Math.min(dmax, 17), 0)
 39 |         var ret = this.integer(min, max) + '.';
 40 |         for (var i = 0, dcount = this.natural(dmin, dmax); i < dcount; i++) {
 41 |             ret += (
 42 |                 // 最后一位不能为 0:如果最后一位为 0,会被 JS 引擎忽略掉。
 43 |                 (i < dcount - 1) ? this.character('number') : this.character('123456789')
 44 |             )
 45 |         }
 46 |         return parseFloat(ret, 10)
 47 |     },
 48 |     // 返回一个随机字符。
 49 |     character: function(pool) {
 50 |         var pools = {
 51 |             lower: 'abcdefghijklmnopqrstuvwxyz',
 52 |             upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
 53 |             number: '0123456789',
 54 |             symbol: '!@#$%^&*()[]'
 55 |         }
 56 |         pools.alpha = pools.lower + pools.upper
 57 |         pools['undefined'] = pools.lower + pools.upper + pools.number + pools.symbol
 58 | 
 59 |         pool = pools[('' + pool).toLowerCase()] || pool
 60 |         return pool.charAt(this.natural(0, pool.length - 1))
 61 |     },
 62 |     char: function(pool) {
 63 |         return this.character(pool)
 64 |     },
 65 |     // 返回一个随机字符串。
 66 |     string: function(pool, min, max) {
 67 |         var len
 68 |         switch (arguments.length) {
 69 |             case 0: // ()
 70 |                 len = this.natural(3, 7)
 71 |                 break
 72 |             case 1: // ( length )
 73 |                 len = pool
 74 |                 pool = undefined
 75 |                 break
 76 |             case 2:
 77 |                 // ( pool, length )
 78 |                 if (typeof arguments[0] === 'string') {
 79 |                     len = min
 80 |                 } else {
 81 |                     // ( min, max )
 82 |                     len = this.natural(pool, min)
 83 |                     pool = undefined
 84 |                 }
 85 |                 break
 86 |             case 3:
 87 |                 len = this.natural(min, max)
 88 |                 break
 89 |         }
 90 | 
 91 |         var text = ''
 92 |         for (var i = 0; i < len; i++) {
 93 |             text += this.character(pool)
 94 |         }
 95 | 
 96 |         return text
 97 |     },
 98 |     str: function( /*pool, min, max*/ ) {
 99 |         return this.string.apply(this, arguments)
100 |     },
101 |     // 返回一个整型数组。
102 |     range: function(start, stop, step) {
103 |         // range( stop )
104 |         if (arguments.length <= 1) {
105 |             stop = start || 0;
106 |             start = 0;
107 |         }
108 |         // range( start, stop )
109 |         step = arguments[2] || 1;
110 | 
111 |         start = +start
112 |         stop = +stop
113 |         step = +step
114 | 
115 |         var len = Math.max(Math.ceil((stop - start) / step), 0);
116 |         var idx = 0;
117 |         var range = new Array(len);
118 | 
119 |         while (idx < len) {
120 |             range[idx++] = start;
121 |             start += step;
122 |         }
123 | 
124 |         return range;
125 |     }
126 | }


--------------------------------------------------------------------------------
/src/mock/random/date.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Date
  3 | */
  4 | var patternLetters = {
  5 |     yyyy: 'getFullYear',
  6 |     yy: function(date) {
  7 |         return ('' + date.getFullYear()).slice(2)
  8 |     },
  9 |     y: 'yy',
 10 | 
 11 |     MM: function(date) {
 12 |         var m = date.getMonth() + 1
 13 |         return m < 10 ? '0' + m : m
 14 |     },
 15 |     M: function(date) {
 16 |         return date.getMonth() + 1
 17 |     },
 18 | 
 19 |     dd: function(date) {
 20 |         var d = date.getDate()
 21 |         return d < 10 ? '0' + d : d
 22 |     },
 23 |     d: 'getDate',
 24 | 
 25 |     HH: function(date) {
 26 |         var h = date.getHours()
 27 |         return h < 10 ? '0' + h : h
 28 |     },
 29 |     H: 'getHours',
 30 |     hh: function(date) {
 31 |         var h = date.getHours() % 12
 32 |         return h < 10 ? '0' + h : h
 33 |     },
 34 |     h: function(date) {
 35 |         return date.getHours() % 12
 36 |     },
 37 | 
 38 |     mm: function(date) {
 39 |         var m = date.getMinutes()
 40 |         return m < 10 ? '0' + m : m
 41 |     },
 42 |     m: 'getMinutes',
 43 | 
 44 |     ss: function(date) {
 45 |         var s = date.getSeconds()
 46 |         return s < 10 ? '0' + s : s
 47 |     },
 48 |     s: 'getSeconds',
 49 | 
 50 |     SS: function(date) {
 51 |         var ms = date.getMilliseconds()
 52 |         return ms < 10 && '00' + ms || ms < 100 && '0' + ms || ms
 53 |     },
 54 |     S: 'getMilliseconds',
 55 | 
 56 |     A: function(date) {
 57 |         return date.getHours() < 12 ? 'AM' : 'PM'
 58 |     },
 59 |     a: function(date) {
 60 |         return date.getHours() < 12 ? 'am' : 'pm'
 61 |     },
 62 |     T: 'getTime'
 63 | }
 64 | module.exports = {
 65 |     // 日期占位符集合。
 66 |     _patternLetters: patternLetters,
 67 |     // 日期占位符正则。
 68 |     _rformat: new RegExp((function() {
 69 |         var re = []
 70 |         for (var i in patternLetters) re.push(i)
 71 |         return '(' + re.join('|') + ')'
 72 |     })(), 'g'),
 73 |     // 格式化日期。
 74 |     _formatDate: function(date, format) {
 75 |         return format.replace(this._rformat, function creatNewSubString($0, flag) {
 76 |             return typeof patternLetters[flag] === 'function' ? patternLetters[flag](date) :
 77 |                 patternLetters[flag] in patternLetters ? creatNewSubString($0, patternLetters[flag]) :
 78 |                 date[patternLetters[flag]]()
 79 |         })
 80 |     },
 81 |     // 生成一个随机的 Date 对象。
 82 |     _randomDate: function(min, max) { // min, max
 83 |         min = min === undefined ? new Date(0) : min
 84 |         max = max === undefined ? new Date() : max
 85 |         return new Date(Math.random() * (max.getTime() - min.getTime()))
 86 |     },
 87 |     // 返回一个随机的日期字符串。
 88 |     date: function(format) {
 89 |         format = format || 'yyyy-MM-dd'
 90 |         return this._formatDate(this._randomDate(), format)
 91 |     },
 92 |     // 返回一个随机的时间字符串。
 93 |     time: function(format) {
 94 |         format = format || 'HH:mm:ss'
 95 |         return this._formatDate(this._randomDate(), format)
 96 |     },
 97 |     // 返回一个随机的日期和时间字符串。
 98 |     datetime: function(format) {
 99 |         format = format || 'yyyy-MM-dd HH:mm:ss'
100 |         return this._formatDate(this._randomDate(), format)
101 |     },
102 |     // 返回当前的日期和时间字符串。
103 |     now: function(unit, format) {
104 |         // now(unit) now(format)
105 |         if (arguments.length === 1) {
106 |             // now(format)
107 |             if (!/year|month|day|hour|minute|second|week/.test(unit)) {
108 |                 format = unit
109 |                 unit = ''
110 |             }
111 |         }
112 |         unit = (unit || '').toLowerCase()
113 |         format = format || 'yyyy-MM-dd HH:mm:ss'
114 | 
115 |         var date = new Date()
116 | 
117 |         /* jshint -W086 */
118 |         // 参考自 http://momentjs.cn/docs/#/manipulating/start-of/
119 |         switch (unit) {
120 |             case 'year':
121 |                 date.setMonth(0)
122 |             case 'month':
123 |                 date.setDate(1)
124 |             case 'week':
125 |             case 'day':
126 |                 date.setHours(0)
127 |             case 'hour':
128 |                 date.setMinutes(0)
129 |             case 'minute':
130 |                 date.setSeconds(0)
131 |             case 'second':
132 |                 date.setMilliseconds(0)
133 |         }
134 |         switch (unit) {
135 |             case 'week':
136 |                 date.setDate(date.getDate() - date.getDay())
137 |         }
138 | 
139 |         return this._formatDate(date, format)
140 |     }
141 | }


--------------------------------------------------------------------------------
/src/mock/random/color.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## Color
  3 | 
  4 |     http://llllll.li/randomColor/
  5 |         A color generator for JavaScript.
  6 |         randomColor generates attractive colors by default. More specifically, randomColor produces bright colors with a reasonably high saturation. This makes randomColor particularly useful for data visualizations and generative art.
  7 | 
  8 |     http://randomcolour.com/
  9 |         var bg_colour = Math.floor(Math.random() * 16777215).toString(16);
 10 |         bg_colour = "#" + ("000000" + bg_colour).slice(-6);
 11 |         document.bgColor = bg_colour;
 12 |     
 13 |     http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
 14 |         Creating random colors is actually more difficult than it seems. The randomness itself is easy, but aesthetically pleasing randomness is more difficult.
 15 |         https://github.com/devongovett/color-generator
 16 | 
 17 |     http://www.paulirish.com/2009/random-hex-color-code-snippets/
 18 |         Random Hex Color Code Generator in JavaScript
 19 | 
 20 |     http://chancejs.com/#color
 21 |         chance.color()
 22 |         // => '#79c157'
 23 |         chance.color({format: 'hex'})
 24 |         // => '#d67118'
 25 |         chance.color({format: 'shorthex'})
 26 |         // => '#60f'
 27 |         chance.color({format: 'rgb'})
 28 |         // => 'rgb(110,52,164)'
 29 | 
 30 |     http://tool.c7sky.com/webcolor
 31 |         网页设计常用色彩搭配表
 32 |     
 33 |     https://github.com/One-com/one-color
 34 |         An OO-based JavaScript color parser/computation toolkit with support for RGB, HSV, HSL, CMYK, and alpha channels.
 35 |         API 很赞
 36 | 
 37 |     https://github.com/harthur/color
 38 |         JavaScript color conversion and manipulation library
 39 | 
 40 |     https://github.com/leaverou/css-colors
 41 |         Share & convert CSS colors
 42 |     http://leaverou.github.io/css-colors/#slategray
 43 |         Type a CSS color keyword, #hex, hsl(), rgba(), whatever:
 44 | 
 45 |     色调 hue
 46 |         http://baike.baidu.com/view/23368.htm
 47 |         色调指的是一幅画中画面色彩的总体倾向,是大的色彩效果。
 48 |     饱和度 saturation
 49 |         http://baike.baidu.com/view/189644.htm
 50 |         饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。
 51 |     亮度 brightness
 52 |         http://baike.baidu.com/view/34773.htm
 53 |         亮度是指发光体(反光体)表面发光(反光)强弱的物理量。
 54 |     照度 luminosity
 55 |         物体被照亮的程度,采用单位面积所接受的光通量来表示,表示单位为勒[克斯](Lux,lx) ,即 1m / m2 。
 56 | 
 57 |     http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
 58 |         var letters = '0123456789ABCDEF'.split('')
 59 |         var color = '#'
 60 |         for (var i = 0; i < 6; i++) {
 61 |             color += letters[Math.floor(Math.random() * 16)]
 62 |         }
 63 |         return color
 64 |     
 65 |         // 随机生成一个无脑的颜色,格式为 '#RRGGBB'。
 66 |         // _brainlessColor()
 67 |         var color = Math.floor(
 68 |             Math.random() *
 69 |             (16 * 16 * 16 * 16 * 16 * 16 - 1)
 70 |         ).toString(16)
 71 |         color = "#" + ("000000" + color).slice(-6)
 72 |         return color.toUpperCase()
 73 | */
 74 | 
 75 | var Convert = require('./color_convert')
 76 | var DICT = require('./color_dict')
 77 | 
 78 | module.exports = {
 79 |     // 随机生成一个有吸引力的颜色,格式为 '#RRGGBB'。
 80 |     color: function(name) {
 81 |         if (name || DICT[name]) return DICT[name].nicer
 82 |         return this.hex()
 83 |     },
 84 |     // #DAC0DE
 85 |     hex: function() {
 86 |         var hsv = this._goldenRatioColor()
 87 |         var rgb = Convert.hsv2rgb(hsv)
 88 |         var hex = Convert.rgb2hex(rgb[0], rgb[1], rgb[2])
 89 |         return hex
 90 |     },
 91 |     // rgb(128,255,255)
 92 |     rgb: function() {
 93 |         var hsv = this._goldenRatioColor()
 94 |         var rgb = Convert.hsv2rgb(hsv)
 95 |         return 'rgb(' +
 96 |             parseInt(rgb[0], 10) + ', ' +
 97 |             parseInt(rgb[1], 10) + ', ' +
 98 |             parseInt(rgb[2], 10) + ')'
 99 |     },
100 |     // rgba(128,255,255,0.3)
101 |     rgba: function() {
102 |         var hsv = this._goldenRatioColor()
103 |         var rgb = Convert.hsv2rgb(hsv)
104 |         return 'rgba(' +
105 |             parseInt(rgb[0], 10) + ', ' +
106 |             parseInt(rgb[1], 10) + ', ' +
107 |             parseInt(rgb[2], 10) + ', ' +
108 |             Math.random().toFixed(2) + ')'
109 |     },
110 |     // hsl(300,80%,90%)
111 |     hsl: function() {
112 |         var hsv = this._goldenRatioColor()
113 |         var hsl = Convert.hsv2hsl(hsv)
114 |         return 'hsl(' +
115 |             parseInt(hsl[0], 10) + ', ' +
116 |             parseInt(hsl[1], 10) + ', ' +
117 |             parseInt(hsl[2], 10) + ')'
118 |     },
119 |     // http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
120 |     // https://github.com/devongovett/color-generator/blob/master/index.js
121 |     // 随机生成一个有吸引力的颜色。
122 |     _goldenRatioColor: function(saturation, value) {
123 |         this._goldenRatio = 0.618033988749895
124 |         this._hue = this._hue || Math.random()
125 |         this._hue += this._goldenRatio
126 |         this._hue %= 1
127 | 
128 |         if (typeof saturation !== "number") saturation = 0.5;
129 |         if (typeof value !== "number") value = 0.95;
130 | 
131 |         return [
132 |             this._hue * 360,
133 |             saturation * 100,
134 |             value * 100
135 |         ]
136 |     }
137 | }


--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
  1 | var gulp = require('gulp')
  2 | var jshint = require('gulp-jshint')
  3 | var webpack = require("webpack")
  4 | var connect = require('gulp-connect')
  5 | var mochaPhantomJS = require('gulp-mocha-phantomjs')
  6 | var exec = require('child_process').exec
  7 | 
  8 | var istanbul = require('gulp-istanbul')
  9 | var mocha = require('gulp-mocha')
 10 | var coveralls = require('gulp-coveralls')
 11 | 
 12 | // 
 13 | gulp.task('hello', function() {
 14 |     console.log((function() {
 15 |         /*
 16 | ___  ___              _        _      
 17 | |  \/  |             | |      (_)     
 18 | | .  . |  ___    ___ | | __    _  ___ 
 19 | | |\/| | / _ \  / __|| |/ /   | |/ __|
 20 | | |  | || (_) || (__ |   <  _ | |\__ \
 21 | \_|  |_/ \___/  \___||_|\_\(_)| ||___/
 22 |                              _/ |     
 23 |                             |__/    
 24 |         */
 25 |     }).toString().split('\n').slice(2, -2).join('\n') + '\n')
 26 | })
 27 | 
 28 | // https://github.com/AveVlad/gulp-connect
 29 | gulp.task('connect', function() {
 30 |     /* jshint unused:false */
 31 |     connect.server({
 32 |         port: 5050,
 33 |         middleware: function(connect, opt) {
 34 |             return [
 35 |                 // https://github.com/senchalabs/connect/#use-middleware
 36 |                 function cors(req, res, next) {
 37 |                     res.setHeader('Access-Control-Allow-Origin', '*')
 38 |                     res.setHeader('Access-Control-Allow-Methods', '*')
 39 |                     next()
 40 |                 }
 41 |             ]
 42 |         }
 43 |     })
 44 | })
 45 | 
 46 | // https://github.com/spenceralger/gulp-jshint
 47 | gulp.task('jshint', function() {
 48 |     var globs = [
 49 |         'src/**/*.js', 'test/test.*.js', 'gulpfile.js', '!**/regexp/parser.js'
 50 |     ]
 51 |     return gulp.src(globs)
 52 |         .pipe(jshint('.jshintrc'))
 53 |         .pipe(jshint.reporter('jshint-stylish'))
 54 | })
 55 | 
 56 | // https://webpack.github.io/docs/usage-with-gulp.html
 57 | gulp.task("webpack", function( /*callback*/ ) {
 58 |     webpack({
 59 |         entry: './src/mock.js',
 60 |         output: {
 61 |             path: './dist',
 62 |             filename: 'mock.js',
 63 |             library: 'Mock',
 64 |             libraryTarget: 'umd'
 65 |         }
 66 |     }, function(err /*, stats*/ ) {
 67 |         // console.log(err, stats)
 68 |         if (err) throw err
 69 |     })
 70 |     webpack({
 71 |         entry: './src/mock.js',
 72 |         devtool: 'source-map',
 73 |         output: {
 74 |             path: './dist',
 75 |             filename: 'mock-min.js',
 76 |             library: 'Mock',
 77 |             libraryTarget: 'umd'
 78 |         },
 79 |         plugins: [
 80 |             new webpack.optimize.UglifyJsPlugin({
 81 |                 minimize: true
 82 |             })
 83 |         ]
 84 |     }, function(err /*, stats*/ ) {
 85 |         // console.log(err, stats)
 86 |         if (err) throw err
 87 |     })
 88 | })
 89 | 
 90 | // https://github.com/mrhooray/gulp-mocha-phantomjs
 91 | gulp.task('mocha', function() {
 92 |     return gulp.src('test/test.mock.html')
 93 |         .pipe(mochaPhantomJS({
 94 |             reporter: 'spec'
 95 |         }))
 96 | })
 97 | 
 98 | 
 99 | // https://github.com/floatdrop/gulp-watch
100 | var watchTasks = ['hello', 'madge', 'jshint', 'webpack', 'mocha']
101 | gulp.task('watch', function( /*callback*/ ) {
102 |     gulp.watch(['src/**/*.js', 'gulpfile.js', 'test/*'], watchTasks)
103 | })
104 | 
105 | // https://github.com/pahen/madge
106 | gulp.task('madge', function( /*callback*/ ) {
107 |     exec('madge ./src/',
108 |         function(error, stdout /*, stderr*/ ) {
109 |             if (error) console.log('exec error: ' + error)
110 |             console.log('module dependencies:')
111 |             console.log(stdout)
112 |         }
113 |     )
114 |     exec('madge --image ./src/dependencies.png ./src/',
115 |         function(error /*, stdout, stderr*/ ) {
116 |             if (error) console.log('exec error: ' + error)
117 |         }
118 |     )
119 | })
120 | 
121 | // TODO
122 | 
123 | // https://github.com/SBoudrias/gulp-istanbul
124 | gulp.task('istanbul', function(cb) {
125 |     gulp.src(['test/test.coveralls.js'])
126 |         .pipe(istanbul()) // Covering files
127 |         .pipe(istanbul.hookRequire()) // Force `require` to return covered files
128 |         .on('finish', function() {
129 |             gulp.src(['test/test.coveralls.js'])
130 |                 .pipe(mocha({}))
131 |                 .pipe(istanbul.writeReports()) // Creating the reports after tests runned
132 |                 .on('end', cb)
133 |         })
134 | })
135 | gulp.task('istanbulForMochaPhantomJS', function(cb) {
136 |     gulp.src(['dist/mock.js'])
137 |         .pipe(istanbul()) // Covering files
138 |         .pipe(istanbul.hookRequire()) // Force `require` to return covered files
139 |         .on('finish', function() {
140 |             gulp.src(['test/test.mock.html'])
141 |                 .pipe(mochaPhantomJS({
142 |                     reporter: 'spec'
143 |                 }))
144 |                 .pipe(istanbul.writeReports()) // Creating the reports after tests runned
145 |                 .on('end', cb)
146 |         })
147 | })
148 | 
149 | // https://github.com/markdalgleish/gulp-coveralls
150 | gulp.task('coveralls', ['istanbul'], function() {
151 |     return gulp.src('coverage/**/lcov.info')
152 |         .pipe(coveralls())
153 | })
154 | 
155 | // 
156 | gulp.task('publish', function() {
157 |     var child_process = require('child_process')
158 |     child_process.exec('ls', function(error, stdout, stderr) {
159 |         console.log(error, stdout, stderr)
160 |     })
161 | })
162 | 
163 | gulp.task('default', watchTasks.concat(['watch', 'connect']))
164 | gulp.task('build', ['jshint', 'webpack', 'mocha'])


--------------------------------------------------------------------------------
/test/valid.js:
--------------------------------------------------------------------------------
  1 | module('Mck.valid(template, data)')
  2 | 
  3 | if (!window.valid) {
  4 |     window.valid = Mock.valid
  5 | }
  6 | 
  7 | test('Name', function() {
  8 |     console.group('Name')
  9 | 
 10 |     var result;
 11 | 
 12 |     result = valid({
 13 |         name: 1
 14 |     }, {
 15 |         name: 1
 16 |     })
 17 |     equal(result.length, 0, JSON.stringify(result, null, 4))
 18 | 
 19 |     result = valid({
 20 |         name1: 1
 21 |     }, {
 22 |         name2: 1
 23 |     })
 24 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 25 | 
 26 |     console.groupEnd('Name')
 27 | })
 28 | 
 29 | test('Type', function() {
 30 |     console.group('Type')
 31 | 
 32 |     var result;
 33 | 
 34 |     result = valid(
 35 |         1,
 36 |         '1'
 37 |     )
 38 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 39 | 
 40 |     result = valid({}, [])
 41 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 42 | 
 43 |     result = valid({
 44 |         name: 1
 45 |     }, {
 46 |         name: 1
 47 |     })
 48 |     equal(result.length, 0, JSON.stringify(result, null, 4))
 49 | 
 50 |     result = valid({
 51 |         name: 1
 52 |     }, {
 53 |         name: '1'
 54 |     })
 55 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 56 | 
 57 |     console.groupEnd('Type')
 58 | })
 59 | 
 60 | test('Value - Number', function() {
 61 |     console.group('Value - Number')
 62 | 
 63 |     var result;
 64 | 
 65 |     result = valid({
 66 |         name: 1
 67 |     }, {
 68 |         name: 1
 69 |     })
 70 |     equal(result.length, 0, JSON.stringify(result, null, 4))
 71 | 
 72 |     result = valid({
 73 |         name: 1
 74 |     }, {
 75 |         name: 2
 76 |     })
 77 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 78 | 
 79 |     result = valid({
 80 |         name: 1.1
 81 |     }, {
 82 |         name: 2.2
 83 |     })
 84 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 85 | 
 86 |     result = valid({
 87 |         'name|1-10': 1
 88 |     }, {
 89 |         name: 5
 90 |     })
 91 |     equal(result.length, 0, JSON.stringify(result, null, 4))
 92 | 
 93 |     result = valid({
 94 |         'name|1-10': 1
 95 |     }, {
 96 |         name: 0
 97 |     })
 98 |     equal(result.length, 1, JSON.stringify(result, null, 4))
 99 | 
100 |     result = valid({
101 |         'name|1-10': 1
102 |     }, {
103 |         name: 11
104 |     })
105 |     equal(result.length, 1, JSON.stringify(result, null, 4))
106 | 
107 |     console.groupEnd('Value - Number')
108 | })
109 | 
110 | test('Value - String', function() {
111 |     console.group('Value - String')
112 | 
113 |     var result;
114 | 
115 |     result = valid({
116 |         name: 'value'
117 |     }, {
118 |         name: 'value'
119 |     })
120 |     equal(result.length, 0, JSON.stringify(result, null, 4))
121 | 
122 |     result = valid({
123 |         name: 'value1'
124 |     }, {
125 |         name: 'value2'
126 |     })
127 |     equal(result.length, 1, JSON.stringify(result, null, 4))
128 | 
129 |     result = valid({
130 |         'name|1': 'value'
131 |     }, {
132 |         name: 'value'
133 |     })
134 |     equal(result.length, 0, JSON.stringify(result, null, 4))
135 | 
136 |     result = valid({
137 |         'name|2': 'value'
138 |     }, {
139 |         name: 'valuevalue'
140 |     })
141 |     equal(result.length, 0, JSON.stringify(result, null, 4))
142 | 
143 |     result = valid({
144 |         'name|2': 'value'
145 |     }, {
146 |         name: 'value'
147 |     })
148 |     equal(result.length, 1, JSON.stringify(result, null, 4))
149 | 
150 |     result = valid({
151 |         'name|2-3': 'value'
152 |     }, {
153 |         name: 'value'
154 |     })
155 |     equal(result.length, 1, JSON.stringify(result, null, 4))
156 | 
157 |     result = valid({
158 |         'name|2-3': 'value'
159 |     }, {
160 |         name: 'valuevaluevaluevalue'
161 |     })
162 |     equal(result.length, 1, JSON.stringify(result, null, 4))
163 | 
164 |     console.groupEnd('Value - String')
165 | })
166 | 
167 | test('Value - Object', function() {
168 |     console.group('Value - Object')
169 | 
170 |     var result;
171 | 
172 |     result = valid({
173 |         name: 1
174 |     }, {
175 |         name: 1
176 |     })
177 |     equal(result.length, 0, JSON.stringify(result, null, 4))
178 | 
179 |     result = valid({
180 |         name1: 1
181 |     }, {
182 |         name2: 2
183 |     })
184 |     equal(result.length, 1, JSON.stringify(result, null, 4))
185 | 
186 |     result = valid({
187 |         name1: 1,
188 |         name2: 2
189 |     }, {
190 |         name3: 3
191 |     })
192 |     equal(result.length, 1, JSON.stringify(result, null, 4))
193 | 
194 |     result = valid({
195 |         name1: 1,
196 |         name2: 2
197 |     }, {
198 |         name1: '1',
199 |         name2: '2'
200 |     })
201 |     equal(result.length, 2, JSON.stringify(result, null, 4))
202 | 
203 |     console.groupEnd('Value - Object')
204 | })
205 | 
206 | test('Value - Array', function() {
207 |     console.group('Value - Array')
208 | 
209 |     var result;
210 | 
211 |     result = valid(
212 |         [1, 2, 3], [1, 2, 3]
213 |     )
214 |     equal(result.length, 0, JSON.stringify(result, null, 4))
215 | 
216 |     result = valid(
217 |         [1, 2, 3], [1, 2, 3, 4]
218 |     )
219 |     equal(result.length, 1, JSON.stringify(result, null, 4))
220 | 
221 |     result = valid({
222 |         'name|2-3': [1]
223 |     }, {
224 |         'name': [1, 2, 3, 4]
225 |     })
226 |     equal(result.length, 1, JSON.stringify(result, null, 4))
227 | 
228 |     result = valid({
229 |         'name|2-3': [1]
230 |     }, {
231 |         'name': [1]
232 |     })
233 |     equal(result.length, 1, JSON.stringify(result, null, 4))
234 | 
235 |     result = valid({
236 |         'name|2-3': [1, 2, 3]
237 |     }, {
238 |         'name': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
239 |     })
240 |     equal(result.length, 1, JSON.stringify(result, null, 4))
241 | 
242 |     result = valid({
243 |         'name|2-3': [1, 2, 3]
244 |     }, {
245 |         'name': [1, 2, 3]
246 |     })
247 |     equal(result.length, 1, JSON.stringify(result, null, 4))
248 | 
249 |     result = valid({
250 |         'name|2-3': [1]
251 |     }, {
252 |         'name': [1, 1, 1]
253 |     })
254 |     equal(result.length, 0, JSON.stringify(result, null, 4))
255 | 
256 |     result = valid({
257 |         'name|2-3': [1]
258 |     }, {
259 |         'name': [1, 2, 3]
260 |     })
261 |     equal(result.length, 2, JSON.stringify(result, null, 4))
262 | 
263 |     console.groupEnd('Value - Array')
264 | })


--------------------------------------------------------------------------------
/test/test.mock.valid.js:
--------------------------------------------------------------------------------
  1 | /* global require, chai, describe, before, it */
  2 | /* global window */
  3 | var expect = chai.expect
  4 | var Mock, Random, $, _
  5 | 
  6 | describe('Mock.valid', function() {
  7 |     before(function(done) {
  8 |         require(['mock', 'underscore', 'jquery'], function() {
  9 |             Mock = arguments[0]
 10 |             window.Random = Random = Mock.Random
 11 |             _ = arguments[1]
 12 |             $ = arguments[2]
 13 |             expect(Mock).to.not.equal(undefined)
 14 |             expect(_).to.not.equal(undefined)
 15 |             expect($).to.not.equal(undefined)
 16 |             done()
 17 |         })
 18 |     })
 19 | 
 20 |     function stringify(json) {
 21 |         return JSON.stringify(json /*, null, 4*/ )
 22 |     }
 23 | 
 24 |     function title(tpl, data, result, test) {
 25 |         test.title = stringify(tpl) + ' VS ' + stringify(data) + '\n\tresult: ' + stringify(result)
 26 | 
 27 |         // if (result.length) test.title += '\n\tresult: '
 28 |         // for (var i = 0; i < result.length; i++) {
 29 |         //     test.title += '\n\t' + result[i].message // stringify(result)
 30 |         // }
 31 |     }
 32 | 
 33 |     function doit(tpl, data, len) {
 34 |         it('', function() {
 35 |             var result = Mock.valid(tpl, data)
 36 |             title(tpl, data, result, this.test)
 37 |             expect(result).to.be.an('array').with.length(len)
 38 |         })
 39 |     }
 40 | 
 41 |     describe('Name', function() {
 42 |         doit({
 43 |             name: 1
 44 |         }, {
 45 |             name: 1
 46 |         }, 0)
 47 | 
 48 |         doit({
 49 |             name1: 1
 50 |         }, {
 51 |             name2: 1
 52 |         }, 1)
 53 |     })
 54 |     describe('Value - Number', function() {
 55 |         doit({
 56 |             name: 1
 57 |         }, {
 58 |             name: 1
 59 |         }, 0)
 60 | 
 61 |         doit({
 62 |             name: 1
 63 |         }, {
 64 |             name: 2
 65 |         }, 1)
 66 | 
 67 |         doit({
 68 |             name: 1.1
 69 |         }, {
 70 |             name: 2.2
 71 |         }, 1)
 72 | 
 73 |         doit({
 74 |             'name|1-10': 1
 75 |         }, {
 76 |             name: 5
 77 |         }, 0)
 78 | 
 79 |         doit({
 80 |             'name|1-10': 1
 81 |         }, {
 82 |             name: 0
 83 |         }, 1)
 84 | 
 85 |         doit({
 86 |             'name|1-10': 1
 87 |         }, {
 88 |             name: 11
 89 |         }, 1)
 90 |     })
 91 |     describe('Value - String', function() {
 92 |         doit({
 93 |             name: 'value'
 94 |         }, {
 95 |             name: 'value'
 96 |         }, 0)
 97 | 
 98 |         doit({
 99 |             name: 'value1'
100 |         }, {
101 |             name: 'value2'
102 |         }, 1)
103 | 
104 |         doit({
105 |             'name|1': 'value'
106 |         }, {
107 |             name: 'value'
108 |         }, 0)
109 | 
110 |         doit({
111 |             'name|2': 'value'
112 |         }, {
113 |             name: 'valuevalue'
114 |         }, 0)
115 | 
116 |         doit({
117 |             'name|2': 'value'
118 |         }, {
119 |             name: 'value'
120 |         }, 1)
121 | 
122 |         doit({
123 |             'name|2-3': 'value'
124 |         }, {
125 |             name: 'value'
126 |         }, 1)
127 | 
128 |         doit({
129 |             'name|2-3': 'value'
130 |         }, {
131 |             name: 'valuevaluevaluevalue'
132 |         }, 1)
133 |     })
134 |     describe('Value - RgeExp', function() {
135 |         doit({
136 |             name: /value/
137 |         }, {
138 |             name: 'value'
139 |         }, 0)
140 |         doit({
141 |             name: /value/
142 |         }, {
143 |             name: 'vvvvv'
144 |         }, 1)
145 |         doit({
146 |             'name|1-10': /value/
147 |         }, {
148 |             name: 'valuevaluevaluevaluevalue'
149 |         }, 0)
150 |         doit({
151 |             'name|1-10': /value/
152 |         }, {
153 |             name: 'vvvvvvvvvvvvvvvvvvvvvvvvv'
154 |         }, 1)
155 |         doit({
156 |             'name|1-10': /^value$/
157 |         }, {
158 |             name: 'valuevaluevaluevaluevalue'
159 |         }, 0)
160 |         doit({
161 |             name: /[a-z][A-Z][0-9]/
162 |         }, {
163 |             name: 'yL5'
164 |         }, 0)
165 |     })
166 |     describe('Value - Object', function() {
167 |         doit({
168 |             name: 1
169 |         }, {
170 |             name: 1
171 |         }, 0)
172 |         doit({
173 |             name1: 1
174 |         }, {
175 |             name2: 2
176 |         }, 1)
177 |         doit({
178 |             name1: 1,
179 |             name2: 2
180 |         }, {
181 |             name3: 3
182 |         }, 1)
183 |         doit({
184 |             name1: 1,
185 |             name2: 2
186 |         }, {
187 |             name1: '1',
188 |             name2: '2'
189 |         }, 2)
190 |         doit({
191 |             a: {
192 |                 b: {
193 |                     c: {
194 |                         d: 1
195 |                     }
196 |                 }
197 |             }
198 |         }, {
199 |             a: {
200 |                 b: {
201 |                     c: {
202 |                         d: 2
203 |                     }
204 |                 }
205 |             }
206 |         }, 1)
207 |     })
208 |     describe('Value - Array', function() {
209 |         doit([1, 2, 3], [1, 2, 3], 0)
210 | 
211 |         doit([1, 2, 3], [1, 2, 3, 4], 1)
212 | 
213 |         // 'name|1': array
214 |         doit({
215 |             'name|1': [1, 2, 3]
216 |         }, {
217 |             'name': 1
218 |         }, 0)
219 |         doit({
220 |             'name|1': [1, 2, 3]
221 |         }, {
222 |             'name': 2
223 |         }, 0)
224 |         doit({
225 |             'name|1': [1, 2, 3]
226 |         }, {
227 |             'name': 3
228 |         }, 0)
229 |         doit({ // 不检测
230 |             'name|1': [1, 2, 3]
231 |         }, {
232 |             'name': 4
233 |         }, 0)
234 | 
235 |         // 'name|+1': array
236 |         doit({
237 |             'name|+1': [1, 2, 3]
238 |         }, {
239 |             'name': 1
240 |         }, 0)
241 |         doit({
242 |             'name|+1': [1, 2, 3]
243 |         }, {
244 |             'name': 2
245 |         }, 0)
246 |         doit({
247 |             'name|+1': [1, 2, 3]
248 |         }, {
249 |             'name': 3
250 |         }, 0)
251 |         doit({
252 |             'name|+1': [1, 2, 3]
253 |         }, {
254 |             'name': 4
255 |         }, 0)
256 | 
257 |         // 'name|min-max': array
258 |         doit({
259 |             'name|2-3': [1]
260 |         }, {
261 |             'name': [1, 2, 3, 4]
262 |         }, 1)
263 | 
264 |         doit({
265 |             'name|2-3': [1]
266 |         }, {
267 |             'name': [1]
268 |         }, 1)
269 | 
270 |         doit({
271 |             'name|2-3': [1, 2, 3]
272 |         }, {
273 |             'name': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
274 |         }, 1)
275 | 
276 |         doit({
277 |             'name|2-3': [1, 2, 3]
278 |         }, {
279 |             'name': [1, 2, 3]
280 |         }, 1)
281 | 
282 |         doit({
283 |             'name|2-3': [1]
284 |         }, {
285 |             'name': [1, 1, 1]
286 |         }, 0)
287 | 
288 |         doit({
289 |             'name|2-3': [1]
290 |         }, {
291 |             'name': [1, 2, 3]
292 |         }, 2)
293 | 
294 |         // 'name|count': array
295 |     })
296 |     describe('Value - Placeholder', function() {
297 |         doit({
298 |             name: '@email'
299 |         }, {
300 |             name: 'nuysoft@gmail.com'
301 |         }, 0)
302 |         doit({
303 |             name: '@int'
304 |         }, {
305 |             name: 123
306 |         }, 0)
307 |     })
308 | })


--------------------------------------------------------------------------------
/test/test.mock.schema.js:
--------------------------------------------------------------------------------
  1 | /* global require, chai, describe, before, it */
  2 | /* global window */
  3 | // 数据占位符定义(Data Placeholder Definition,DPD)
  4 | var expect = chai.expect
  5 | var Mock, $, _
  6 | 
  7 | describe('Schema', function() {
  8 |     before(function(done) {
  9 |         require(['mock', 'underscore', 'jquery'], function() {
 10 |             Mock = arguments[0]
 11 |             window.XMLHttpRequest = Mock.XHR
 12 |             _ = arguments[1]
 13 |             $ = arguments[2]
 14 |             expect(Mock).to.not.equal(undefined)
 15 |             expect(_).to.not.equal(undefined)
 16 |             expect($).to.not.equal(undefined)
 17 |             done()
 18 |         })
 19 |     })
 20 | 
 21 |     function stringify(json) {
 22 |         return JSON.stringify(json /*, null, 4*/ )
 23 |     }
 24 | 
 25 |     function doit(template, validator) {
 26 |         it('', function() {
 27 |             var schema = Mock.toJSONSchema(template)
 28 |             this.test.title = (stringify(template) || template.toString()) + ' => ' + stringify(schema)
 29 |             validator(schema)
 30 |         })
 31 |     }
 32 | 
 33 |     describe('Type', function() {
 34 |         doit(1, function(schema) {
 35 |             expect(schema.name).to.be.an('undefined')
 36 |                 // expect(schema).to.not.have.property('name')
 37 |             expect(schema).to.have.property('type', 'number')
 38 |             for (var n in schema.rule) {
 39 |                 expect(schema.rule[n]).to.be.null()
 40 |             }
 41 |         })
 42 |         doit(true, function(schema) {
 43 |             expect(schema.name).to.be.an('undefined')
 44 |                 // expect(schema).to.not.have.property('name')
 45 |             expect(schema).to.have.property('type', 'boolean')
 46 |             for (var n in schema.rule) {
 47 |                 expect(schema.rule[n]).to.be.null()
 48 |             }
 49 |         })
 50 |         doit('', function(schema) {
 51 |             expect(schema.name).to.be.an('undefined')
 52 |                 // expect(schema).to.not.have.property('name')
 53 |             expect(schema).to.have.property('type', 'string')
 54 |             for (var n in schema.rule) {
 55 |                 expect(schema.rule[n]).to.be.null()
 56 |             }
 57 |         })
 58 |         doit(function() {}, function(schema) {
 59 |             expect(schema.name).to.be.an('undefined')
 60 |                 // expect(schema).to.not.have.property('name')
 61 |             expect(schema).to.have.property('type', 'function')
 62 |             for (var n in schema.rule) {
 63 |                 expect(schema.rule[n]).to.be.null()
 64 |             }
 65 |         })
 66 |         doit(/\d/, function(schema) {
 67 |             expect(schema.name).to.be.an('undefined')
 68 |                 // expect(schema).to.not.have.property('name')
 69 |             expect(schema).to.have.property('type', 'regexp')
 70 |             for (var n in schema.rule) {
 71 |                 expect(schema.rule[n]).to.be.null()
 72 |             }
 73 |         })
 74 |         doit([], function(schema) {
 75 |             expect(schema.name).to.be.an('undefined')
 76 |                 // expect(schema).to.not.have.property('name')
 77 |             expect(schema).to.have.property('type', 'array')
 78 |             for (var n in schema.rule) {
 79 |                 expect(schema.rule[n]).to.be.null()
 80 |             }
 81 |             expect(schema).to.have.property('items').with.length(0)
 82 |         })
 83 |         doit({}, function(schema) {
 84 |             expect(schema.name).to.be.an('undefined')
 85 |                 // expect(schema).to.not.have.property('name')
 86 |             expect(schema).to.have.property('type', 'object')
 87 |             for (var n in schema.rule) {
 88 |                 expect(schema.rule[n]).to.be.null()
 89 |             }
 90 |             expect(schema).to.have.property('properties').with.length(0)
 91 |         })
 92 | 
 93 |     })
 94 | 
 95 |     describe('Object', function() {
 96 |         doit({
 97 |             a: {
 98 |                 b: {
 99 |                     c: {
100 |                         d: {}
101 |                     }
102 |                 }
103 |             }
104 |         }, function(schema) {
105 |             expect(schema.name).to.be.an('undefined')
106 |                 // expect(schema).to.not.have.property('name')
107 |             expect(schema).to.have.property('type', 'object')
108 | 
109 |             var properties;
110 | 
111 |             // root.properties
112 |             properties = schema.properties
113 |             expect(properties).to.with.length(1)
114 |             expect(properties[0]).to.have.property('name', 'a')
115 |             expect(properties[0]).to.have.property('type', 'object')
116 | 
117 |             // root.a.properties
118 |             properties = properties[0].properties
119 |             expect(properties).to.with.length(1)
120 |             expect(properties[0]).to.have.property('name', 'b')
121 |             expect(properties[0]).to.have.property('type', 'object')
122 | 
123 |             // root.a.b.properties
124 |             properties = properties[0].properties
125 |             expect(properties).to.with.length(1)
126 |             expect(properties[0]).to.have.property('name', 'c')
127 |             expect(properties[0]).to.have.property('type', 'object')
128 | 
129 |             // root.a.b.c.properties
130 |             properties = properties[0].properties
131 |             expect(properties).to.with.length(1)
132 |             expect(properties[0]).to.have.property('name', 'd')
133 |             expect(properties[0]).to.have.property('type', 'object')
134 | 
135 |             // root.a.b.c.d.properties
136 |             properties = properties[0].properties
137 |             expect(properties).to.with.length(0)
138 |         })
139 | 
140 |     })
141 | 
142 |     describe('Array', function() {
143 |         doit([
144 |             [
145 |                 ['foo', 'bar']
146 |             ]
147 |         ], function(schema) {
148 |             expect(schema.name).to.be.an('undefined')
149 |                 // expect(schema).to.not.have.property('name')
150 |             expect(schema).to.have.property('type', 'array')
151 | 
152 |             var items;
153 | 
154 |             // root.items
155 |             items = schema.items
156 |             expect(items).to.with.length(1)
157 |             expect(items[0]).to.have.property('type', 'array')
158 | 
159 |             // root[0].items
160 |             items = items[0].items
161 |             expect(items).to.with.length(1)
162 |             expect(items[0]).to.have.property('type', 'array')
163 | 
164 |             // root[0][0].items
165 |             items = items[0].items
166 |             expect(items).to.with.length(2)
167 |             expect(items[0]).to.have.property('type', 'string')
168 |             expect(items[1]).to.have.property('type', 'string')
169 |         })
170 |     })
171 | 
172 |     describe('String Rule', function() {
173 |         doit({
174 |             'string|1-10': '★'
175 |         }, function(schema) {
176 |             expect(schema.name).to.be.an('undefined')
177 |                 // expect(schema).to.not.have.property('name')
178 |             expect(schema).to.have.property('type', 'object')
179 | 
180 |             var properties;
181 |             // root.properties
182 |             properties = schema.properties
183 |             expect(properties).to.with.length(1)
184 |             expect(properties[0]).to.have.property('type', 'string')
185 |             expect(properties[0].rule).to.have.property('min', 1)
186 |             expect(properties[0].rule).to.have.property('max', 10)
187 |         })
188 |         doit({
189 |             'string|3': 'value',
190 |         }, function(schema) {
191 |             expect(schema.name).to.be.an('undefined')
192 |                 // expect(schema).to.not.have.property('name')
193 |             expect(schema).to.have.property('type', 'object')
194 | 
195 |             var properties;
196 |             // root.properties
197 |             properties = schema.properties
198 |             expect(properties).to.with.length(1)
199 |             expect(properties[0]).to.have.property('type', 'string')
200 |             expect(properties[0].rule).to.have.property('min', 3)
201 |             expect(properties[0].rule.max).to.be.an('undefined')
202 |         })
203 |     })
204 | 
205 | })


--------------------------------------------------------------------------------
/test/test.mock.spec.dpd.js:
--------------------------------------------------------------------------------
  1 | /* global require, chai, describe, before, it */
  2 | // 数据占位符定义(Data Placeholder Definition,DPD)
  3 | var expect = chai.expect
  4 | var Mock, $, _
  5 | 
  6 | describe('DPD', function() {
  7 |     before(function(done) {
  8 |         require(['mock', 'underscore', 'jquery'], function() {
  9 |             Mock = arguments[0]
 10 |             _ = arguments[1]
 11 |             $ = arguments[2]
 12 |             expect(Mock).to.not.equal(undefined)
 13 |             expect(_).to.not.equal(undefined)
 14 |             expect($).to.not.equal(undefined)
 15 |             done()
 16 |         })
 17 |     })
 18 |     describe('Reference', function() {
 19 |         it('@EMAIL', function() {
 20 |             var data = Mock.mock(this.test.title)
 21 |             expect(data).to.not.equal(this.test.title)
 22 |         })
 23 |     })
 24 |     describe('Priority', function() {
 25 |         it('@EMAIL', function() {
 26 |             var data = Mock.mock({
 27 |                 email: 'nuysoft@gmail.com',
 28 |                 name: '@EMAIL'
 29 |             })
 30 |             this.test.title += ' => ' + data.name
 31 |             expect(data.name).to.not.equal(data.email)
 32 |         })
 33 |         it('@email', function() {
 34 |             var data = Mock.mock({
 35 |                 email: 'nuysoft@gmail.com',
 36 |                 name: '@email'
 37 |             })
 38 |             this.test.title += ' => ' + data.name
 39 |             expect(data.name).to.equal(data.email)
 40 |         })
 41 |     })
 42 |     describe('Escape', function() {
 43 |         it('\@EMAIL', function() {
 44 |             var data = Mock.mock(this.test.title)
 45 |             this.test.title += ' => ' + data
 46 |             expect(data).to.not.equal(this.test.title)
 47 |         })
 48 |         it('\\@EMAIL', function() {
 49 |             var data = Mock.mock(this.test.title)
 50 |             this.test.title += ' => ' + data
 51 |             expect(data).to.not.equal(this.test.title)
 52 |         })
 53 |         it('\\\@EMAIL', function() {
 54 |             var data = Mock.mock(this.test.title)
 55 |             this.test.title += ' => ' + data
 56 |             expect(data).to.not.equal(this.test.title)
 57 |         })
 58 |         it('\\\\@EMAIL', function() {
 59 |             var data = Mock.mock(this.test.title)
 60 |             this.test.title += ' => ' + data
 61 |             expect(data).to.not.equal(this.test.title)
 62 |         })
 63 |     })
 64 |     describe('Path', function() {
 65 |         it('Absolute Path', function() {
 66 |             var data = Mock.mock({
 67 |                 id: '@UUID',
 68 |                 children: [{
 69 |                     parentId: '@/id'
 70 |                 }],
 71 |                 child: {
 72 |                     parentId: '@/id'
 73 |                 }
 74 |             })
 75 |             expect(data.children[0]).to.have.property('parentId', data.id)
 76 |             expect(data.child).to.have.property('parentId', data.id)
 77 |         })
 78 |         it('Relative Path', function() {
 79 |             var data = Mock.mock({
 80 |                 id: '@UUID',
 81 |                 children: [{
 82 |                     parentId: '@../../id'
 83 |                 }],
 84 |                 child: {
 85 |                     parentId: '@../id'
 86 |                 }
 87 |             })
 88 |             expect(data.children[0]).to.have.property('parentId', data.id)
 89 |             expect(data.child).to.have.property('parentId', data.id)
 90 |         })
 91 | 
 92 |     })
 93 |     describe('Complex', function() {
 94 |         var tpl = {
 95 |             basics: {
 96 |                 boolean1: '@BOOLEAN',
 97 |                 boolean2: '@BOOLEAN(1, 9, true)',
 98 | 
 99 |                 natural1: '@NATURAL',
100 |                 natural2: '@NATURAL(10000)',
101 |                 natural3: '@NATURAL(60, 100)',
102 | 
103 |                 integer1: '@INTEGER',
104 |                 integer2: '@INTEGER(10000)',
105 |                 integer3: '@INTEGER(60, 100)',
106 | 
107 |                 float1: '@FLOAT',
108 |                 float2: '@FLOAT(0)',
109 |                 float3: '@FLOAT(60, 100)',
110 |                 float4: '@FLOAT(60, 100, 3)',
111 |                 float5: '@FLOAT(60, 100, 3, 5)',
112 | 
113 |                 character1: '@CHARACTER',
114 |                 character2: '@CHARACTER("lower")',
115 |                 character3: '@CHARACTER("upper")',
116 |                 character4: '@CHARACTER("number")',
117 |                 character5: '@CHARACTER("symbol")',
118 |                 character6: '@CHARACTER("aeiou")',
119 | 
120 |                 string1: '@STRING',
121 |                 string2: '@STRING(5)',
122 |                 string3: '@STRING("lower",5)',
123 |                 string4: '@STRING(7, 10)',
124 |                 string5: '@STRING("aeiou", 1, 3)',
125 | 
126 |                 range1: '@RANGE(10)',
127 |                 range2: '@RANGE(3, 7)',
128 |                 range3: '@RANGE(1, 10, 2)',
129 |                 range4: '@RANGE(1, 10, 3)',
130 | 
131 |                 date: '@DATE',
132 |                 time: '@TIME',
133 | 
134 |                 datetime1: '@DATETIME',
135 |                 datetime2: '@DATETIME("yyyy-MM-dd A HH:mm:ss")',
136 |                 datetime3: '@DATETIME("yyyy-MM-dd a HH:mm:ss")',
137 |                 datetime4: '@DATETIME("yy-MM-dd HH:mm:ss")',
138 |                 datetime5: '@DATETIME("y-MM-dd HH:mm:ss")',
139 |                 datetime6: '@DATETIME("y-M-d H:m:s")',
140 | 
141 |                 now: '@NOW',
142 |                 nowYear: '@NOW("year")',
143 |                 nowMonth: '@NOW("month")',
144 |                 nowDay: '@NOW("day")',
145 |                 nowHour: '@NOW("hour")',
146 |                 nowMinute: '@NOW("minute")',
147 |                 nowSecond: '@NOW("second")',
148 |                 nowWeek: '@NOW("week")',
149 |                 nowCustom: '@NOW("yyyy-MM-dd HH:mm:ss SS")'
150 |             },
151 |             image: {
152 |                 image1: '@IMAGE',
153 |                 image2: '@IMAGE("100x200", "#000")',
154 |                 image3: '@IMAGE("100x200", "#000", "hello")',
155 |                 image4: '@IMAGE("100x200", "#000", "#FFF", "hello")',
156 |                 image5: '@IMAGE("100x200", "#000", "#FFF", "png", "hello")',
157 | 
158 |                 dataImage1: '@DATAIMAGE',
159 |                 dataImage2: '@DATAIMAGE("200x100")',
160 |                 dataImage3: '@DATAIMAGE("300x100", "Hello Mock.js!")'
161 |             },
162 |             color: {
163 |                 color: '@COLOR',
164 |                 render: function() {
165 |                     $('.header').css('background', this.color)
166 |                 }
167 |             },
168 |             text: {
169 |                 title1: '@TITLE',
170 |                 title2: '@TITLE(5)',
171 |                 title3: '@TITLE(3, 5)',
172 | 
173 |                 word1: '@WORD',
174 |                 word2: '@WORD(5)',
175 |                 word3: '@WORD(3, 5)',
176 | 
177 |                 sentence1: '@SENTENCE',
178 |                 sentence2: '@SENTENCE(5)',
179 |                 sentence3: '@SENTENCE(3, 5)',
180 | 
181 |                 paragraph1: '@PARAGRAPH',
182 |                 paragraph2: '@PARAGRAPH(2)',
183 |                 paragraph3: '@PARAGRAPH(1, 3)'
184 |             },
185 |             name: {
186 |                 first: '@FIRST',
187 |                 last: '@LAST',
188 |                 name1: '@NAME',
189 |                 name2: '@NAME(true)'
190 |             },
191 |             web: {
192 |                 url: '@URL',
193 |                 domain: '@DOMAIN',
194 |                 email: '@EMAIL',
195 |                 ip: '@IP',
196 |                 tld: '@TLD',
197 |             },
198 |             address: {
199 |                 region: '@REGION',
200 |                 province: '@PROVINCE',
201 |                 city: '@CITY',
202 |                 county: '@COUNTY'
203 |             },
204 |             miscellaneous: {
205 |                 guid: '@GUID',
206 |                 id: '@ID',
207 |                 'increment1|3': [
208 |                     '@INCREMENT'
209 |                 ],
210 |                 'increment2|3': [
211 |                     '@INCREMENT(10)'
212 |                 ]
213 |             },
214 |             helpers: {
215 |                 capitalize1: '@CAPITALIZE()',
216 |                 capitalize2: '@CAPITALIZE("hello")',
217 | 
218 |                 upper1: '@UPPER',
219 |                 upper2: '@UPPER("hello")',
220 | 
221 |                 lower1: '@LOWER',
222 |                 lower2: '@LOWER("HELLO")',
223 | 
224 |                 pick1: '@PICK',
225 |                 pick2: '@PICK("abc")',
226 |                 pick3: '@PICK(["a", "b", "c"])',
227 | 
228 |                 shuffle1: '@SHUFFLE',
229 |                 shuffle2: '@SHUFFLE(["a", "b", "c"])'
230 |             }
231 |         }
232 |         it('', function() {
233 |             var data = Mock.mock(tpl)
234 |             // this.test.title += JSON.stringify(data, null, 4)
235 |             expect(data).to.be.a('object')
236 |         })
237 |     })
238 | })


--------------------------------------------------------------------------------
/src/mock/random/image.js:
--------------------------------------------------------------------------------
  1 | /* global document  */
  2 | /*
  3 |     ## Image
  4 | */
  5 | module.exports = {
  6 |     // 常见的广告宽高
  7 |     _adSize: [
  8 |         '300x250', '250x250', '240x400', '336x280', '180x150',
  9 |         '720x300', '468x60', '234x60', '88x31', '120x90',
 10 |         '120x60', '120x240', '125x125', '728x90', '160x600',
 11 |         '120x600', '300x600'
 12 |     ],
 13 |     // 常见的屏幕宽高
 14 |     _screenSize: [
 15 |         '320x200', '320x240', '640x480', '800x480', '800x480',
 16 |         '1024x600', '1024x768', '1280x800', '1440x900', '1920x1200',
 17 |         '2560x1600'
 18 |     ],
 19 |     // 常见的视频宽高
 20 |     _videoSize: ['720x480', '768x576', '1280x720', '1920x1080'],
 21 |     /*
 22 |         生成一个随机的图片地址。
 23 | 
 24 |         替代图片源
 25 |             http://fpoimg.com/
 26 |         参考自 
 27 |             http://rensanning.iteye.com/blog/1933310
 28 |             http://code.tutsplus.com/articles/the-top-8-placeholders-for-web-designers--net-19485
 29 |     */
 30 |     image: function(size, background, foreground, format, text) {
 31 |         // Random.image( size, background, foreground, text )
 32 |         if (arguments.length === 4) {
 33 |             text = format
 34 |             format = undefined
 35 |         }
 36 |         // Random.image( size, background, text )
 37 |         if (arguments.length === 3) {
 38 |             text = foreground
 39 |             foreground = undefined
 40 |         }
 41 |         // Random.image()
 42 |         if (!size) size = this.pick(this._adSize)
 43 | 
 44 |         if (background && ~background.indexOf('#')) background = background.slice(1)
 45 |         if (foreground && ~foreground.indexOf('#')) foreground = foreground.slice(1)
 46 | 
 47 |         // http://dummyimage.com/600x400/cc00cc/470047.png&text=hello
 48 |         return 'http://dummyimage.com/' + size +
 49 |             (background ? '/' + background : '') +
 50 |             (foreground ? '/' + foreground : '') +
 51 |             (format ? '.' + format : '') +
 52 |             (text ? '&text=' + text : '')
 53 |     },
 54 |     img: function() {
 55 |         return this.image.apply(this, arguments)
 56 |     },
 57 | 
 58 |     /*
 59 |         BrandColors
 60 |         http://brandcolors.net/
 61 |         A collection of major brand color codes curated by Galen Gidman.
 62 |         大牌公司的颜色集合
 63 | 
 64 |         // 获取品牌和颜色
 65 |         $('h2').each(function(index, item){
 66 |             item = $(item)
 67 |             console.log('\'' + item.text() + '\'', ':', '\'' + item.next().text() + '\'', ',')
 68 |         })
 69 |     */
 70 |     _brandColors: {
 71 |         '4ormat': '#fb0a2a',
 72 |         '500px': '#02adea',
 73 |         'About.me (blue)': '#00405d',
 74 |         'About.me (yellow)': '#ffcc33',
 75 |         'Addvocate': '#ff6138',
 76 |         'Adobe': '#ff0000',
 77 |         'Aim': '#fcd20b',
 78 |         'Amazon': '#e47911',
 79 |         'Android': '#a4c639',
 80 |         'Angie\'s List': '#7fbb00',
 81 |         'AOL': '#0060a3',
 82 |         'Atlassian': '#003366',
 83 |         'Behance': '#053eff',
 84 |         'Big Cartel': '#97b538',
 85 |         'bitly': '#ee6123',
 86 |         'Blogger': '#fc4f08',
 87 |         'Boeing': '#0039a6',
 88 |         'Booking.com': '#003580',
 89 |         'Carbonmade': '#613854',
 90 |         'Cheddar': '#ff7243',
 91 |         'Code School': '#3d4944',
 92 |         'Delicious': '#205cc0',
 93 |         'Dell': '#3287c1',
 94 |         'Designmoo': '#e54a4f',
 95 |         'Deviantart': '#4e6252',
 96 |         'Designer News': '#2d72da',
 97 |         'Devour': '#fd0001',
 98 |         'DEWALT': '#febd17',
 99 |         'Disqus (blue)': '#59a3fc',
100 |         'Disqus (orange)': '#db7132',
101 |         'Dribbble': '#ea4c89',
102 |         'Dropbox': '#3d9ae8',
103 |         'Drupal': '#0c76ab',
104 |         'Dunked': '#2a323a',
105 |         'eBay': '#89c507',
106 |         'Ember': '#f05e1b',
107 |         'Engadget': '#00bdf6',
108 |         'Envato': '#528036',
109 |         'Etsy': '#eb6d20',
110 |         'Evernote': '#5ba525',
111 |         'Fab.com': '#dd0017',
112 |         'Facebook': '#3b5998',
113 |         'Firefox': '#e66000',
114 |         'Flickr (blue)': '#0063dc',
115 |         'Flickr (pink)': '#ff0084',
116 |         'Forrst': '#5b9a68',
117 |         'Foursquare': '#25a0ca',
118 |         'Garmin': '#007cc3',
119 |         'GetGlue': '#2d75a2',
120 |         'Gimmebar': '#f70078',
121 |         'GitHub': '#171515',
122 |         'Google Blue': '#0140ca',
123 |         'Google Green': '#16a61e',
124 |         'Google Red': '#dd1812',
125 |         'Google Yellow': '#fcca03',
126 |         'Google+': '#dd4b39',
127 |         'Grooveshark': '#f77f00',
128 |         'Groupon': '#82b548',
129 |         'Hacker News': '#ff6600',
130 |         'HelloWallet': '#0085ca',
131 |         'Heroku (light)': '#c7c5e6',
132 |         'Heroku (dark)': '#6567a5',
133 |         'HootSuite': '#003366',
134 |         'Houzz': '#73ba37',
135 |         'HTML5': '#ec6231',
136 |         'IKEA': '#ffcc33',
137 |         'IMDb': '#f3ce13',
138 |         'Instagram': '#3f729b',
139 |         'Intel': '#0071c5',
140 |         'Intuit': '#365ebf',
141 |         'Kickstarter': '#76cc1e',
142 |         'kippt': '#e03500',
143 |         'Kodery': '#00af81',
144 |         'LastFM': '#c3000d',
145 |         'LinkedIn': '#0e76a8',
146 |         'Livestream': '#cf0005',
147 |         'Lumo': '#576396',
148 |         'Mixpanel': '#a086d3',
149 |         'Meetup': '#e51937',
150 |         'Nokia': '#183693',
151 |         'NVIDIA': '#76b900',
152 |         'Opera': '#cc0f16',
153 |         'Path': '#e41f11',
154 |         'PayPal (dark)': '#1e477a',
155 |         'PayPal (light)': '#3b7bbf',
156 |         'Pinboard': '#0000e6',
157 |         'Pinterest': '#c8232c',
158 |         'PlayStation': '#665cbe',
159 |         'Pocket': '#ee4056',
160 |         'Prezi': '#318bff',
161 |         'Pusha': '#0f71b4',
162 |         'Quora': '#a82400',
163 |         'QUOTE.fm': '#66ceff',
164 |         'Rdio': '#008fd5',
165 |         'Readability': '#9c0000',
166 |         'Red Hat': '#cc0000',
167 |         'Resource': '#7eb400',
168 |         'Rockpack': '#0ba6ab',
169 |         'Roon': '#62b0d9',
170 |         'RSS': '#ee802f',
171 |         'Salesforce': '#1798c1',
172 |         'Samsung': '#0c4da2',
173 |         'Shopify': '#96bf48',
174 |         'Skype': '#00aff0',
175 |         'Snagajob': '#f47a20',
176 |         'Softonic': '#008ace',
177 |         'SoundCloud': '#ff7700',
178 |         'Space Box': '#f86960',
179 |         'Spotify': '#81b71a',
180 |         'Sprint': '#fee100',
181 |         'Squarespace': '#121212',
182 |         'StackOverflow': '#ef8236',
183 |         'Staples': '#cc0000',
184 |         'Status Chart': '#d7584f',
185 |         'Stripe': '#008cdd',
186 |         'StudyBlue': '#00afe1',
187 |         'StumbleUpon': '#f74425',
188 |         'T-Mobile': '#ea0a8e',
189 |         'Technorati': '#40a800',
190 |         'The Next Web': '#ef4423',
191 |         'Treehouse': '#5cb868',
192 |         'Trulia': '#5eab1f',
193 |         'Tumblr': '#34526f',
194 |         'Twitch.tv': '#6441a5',
195 |         'Twitter': '#00acee',
196 |         'TYPO3': '#ff8700',
197 |         'Ubuntu': '#dd4814',
198 |         'Ustream': '#3388ff',
199 |         'Verizon': '#ef1d1d',
200 |         'Vimeo': '#86c9ef',
201 |         'Vine': '#00a478',
202 |         'Virb': '#06afd8',
203 |         'Virgin Media': '#cc0000',
204 |         'Wooga': '#5b009c',
205 |         'WordPress (blue)': '#21759b',
206 |         'WordPress (orange)': '#d54e21',
207 |         'WordPress (grey)': '#464646',
208 |         'Wunderlist': '#2b88d9',
209 |         'XBOX': '#9bc848',
210 |         'XING': '#126567',
211 |         'Yahoo!': '#720e9e',
212 |         'Yandex': '#ffcc00',
213 |         'Yelp': '#c41200',
214 |         'YouTube': '#c4302b',
215 |         'Zalongo': '#5498dc',
216 |         'Zendesk': '#78a300',
217 |         'Zerply': '#9dcc7a',
218 |         'Zootool': '#5e8b1d'
219 |     },
220 |     _brandNames: function() {
221 |         var brands = [];
222 |         for (var b in this._brandColors) {
223 |             brands.push(b)
224 |         }
225 |         return brands
226 |     },
227 |     /*
228 |         生成一段随机的 Base64 图片编码。
229 | 
230 |         https://github.com/imsky/holder
231 |         Holder renders image placeholders entirely on the client side.
232 | 
233 |         dataImageHolder: function(size) {
234 |             return 'holder.js/' + size
235 |         },
236 |     */
237 |     dataImage: function(size, text) {
238 |         var canvas
239 |         if (typeof document !== 'undefined') {
240 |             canvas = document.createElement('canvas')
241 |         } else {
242 |             /*
243 |                 https://github.com/Automattic/node-canvas
244 |                     npm install canvas --save
245 |                 安装问题:
246 |                 * http://stackoverflow.com/questions/22953206/gulp-issues-with-cario-install-command-not-found-when-trying-to-installing-canva
247 |                 * https://github.com/Automattic/node-canvas/issues/415
248 |                 * https://github.com/Automattic/node-canvas/wiki/_pages
249 | 
250 |                 PS:node-canvas 的安装过程实在是太繁琐了,所以不放入 package.json 的 dependencies。
251 |              */
252 |             var Canvas = module.require('canvas')
253 |             canvas = new Canvas()
254 |         }
255 | 
256 |         var ctx = canvas && canvas.getContext && canvas.getContext("2d")
257 |         if (!canvas || !ctx) return ''
258 | 
259 |         if (!size) size = this.pick(this._adSize)
260 |         text = text !== undefined ? text : size
261 | 
262 |         size = size.split('x')
263 | 
264 |         var width = parseInt(size[0], 10),
265 |             height = parseInt(size[1], 10),
266 |             background = this._brandColors[this.pick(this._brandNames())],
267 |             foreground = '#FFF',
268 |             text_height = 14,
269 |             font = 'sans-serif';
270 | 
271 |         canvas.width = width
272 |         canvas.height = height
273 |         ctx.textAlign = 'center'
274 |         ctx.textBaseline = 'middle'
275 |         ctx.fillStyle = background
276 |         ctx.fillRect(0, 0, width, height)
277 |         ctx.fillStyle = foreground
278 |         ctx.font = 'bold ' + text_height + 'px ' + font
279 |         ctx.fillText(text, (width / 2), (height / 2), width)
280 |         return canvas.toDataURL('image/png')
281 |     }
282 | }


--------------------------------------------------------------------------------
/src/mock/RE_KEY.svg:
--------------------------------------------------------------------------------
 1 | 
 2 |           
 3 |             
71 |           
72 |           
73 |             
74 |               
75 |                 
76 |                 
77 |                 
78 |                 
79 |                 
80 |               
81 |             
82 |           
83 |         Created with Snapgroup #1any character|+group #2digitgroup #3One of:+-digit-One of:+-digit.group #4digit-digit


--------------------------------------------------------------------------------
/src/mock/regexp/handler.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## RegExp Handler
  3 | 
  4 |     https://github.com/ForbesLindesay/regexp
  5 |     https://github.com/dmajda/pegjs
  6 |     http://www.regexper.com/
  7 | 
  8 |     每个节点的结构
  9 |         {
 10 |             type: '',
 11 |             offset: number,
 12 |             text: '',
 13 |             body: {},
 14 |             escaped: true/false
 15 |         }
 16 | 
 17 |     type 可选值
 18 |         alternate             |         选择
 19 |         match                 匹配
 20 |         capture-group         ()        捕获组
 21 |         non-capture-group     (?:...)   非捕获组
 22 |         positive-lookahead    (?=p)     零宽正向先行断言
 23 |         negative-lookahead    (?!p)     零宽负向先行断言
 24 |         quantified            a*        重复节点
 25 |         quantifier            *         量词
 26 |         charset               []        字符集
 27 |         range                 {m, n}    范围
 28 |         literal               a         直接量字符
 29 |         unicode               \uxxxx    Unicode
 30 |         hex                   \x        十六进制
 31 |         octal                 八进制
 32 |         back-reference        \n        反向引用
 33 |         control-character     \cX       控制字符
 34 | 
 35 |         // Token
 36 |         start               ^       开头
 37 |         end                 $       结尾
 38 |         any-character       .       任意字符
 39 |         backspace           [\b]    退格直接量
 40 |         word-boundary       \b      单词边界
 41 |         non-word-boundary   \B      非单词边界
 42 |         digit               \d      ASCII 数字,[0-9]
 43 |         non-digit           \D      非 ASCII 数字,[^0-9]
 44 |         form-feed           \f      换页符
 45 |         line-feed           \n      换行符
 46 |         carriage-return     \r      回车符
 47 |         white-space         \s      空白符
 48 |         non-white-space     \S      非空白符
 49 |         tab                 \t      制表符
 50 |         vertical-tab        \v      垂直制表符
 51 |         word                \w      ASCII 字符,[a-zA-Z0-9]
 52 |         non-word            \W      非 ASCII 字符,[^a-zA-Z0-9]
 53 |         null-character      \o      NUL 字符
 54 |  */
 55 | 
 56 | var Util = require('../util')
 57 | var Random = require('../random/')
 58 |     /*
 59 |         
 60 |     */
 61 | var Handler = {
 62 |     extend: Util.extend
 63 | }
 64 | 
 65 | // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_code_chart
 66 | /*var ASCII_CONTROL_CODE_CHART = {
 67 |     '@': ['\u0000'],
 68 |     A: ['\u0001'],
 69 |     B: ['\u0002'],
 70 |     C: ['\u0003'],
 71 |     D: ['\u0004'],
 72 |     E: ['\u0005'],
 73 |     F: ['\u0006'],
 74 |     G: ['\u0007', '\a'],
 75 |     H: ['\u0008', '\b'],
 76 |     I: ['\u0009', '\t'],
 77 |     J: ['\u000A', '\n'],
 78 |     K: ['\u000B', '\v'],
 79 |     L: ['\u000C', '\f'],
 80 |     M: ['\u000D', '\r'],
 81 |     N: ['\u000E'],
 82 |     O: ['\u000F'],
 83 |     P: ['\u0010'],
 84 |     Q: ['\u0011'],
 85 |     R: ['\u0012'],
 86 |     S: ['\u0013'],
 87 |     T: ['\u0014'],
 88 |     U: ['\u0015'],
 89 |     V: ['\u0016'],
 90 |     W: ['\u0017'],
 91 |     X: ['\u0018'],
 92 |     Y: ['\u0019'],
 93 |     Z: ['\u001A'],
 94 |     '[': ['\u001B', '\e'],
 95 |     '\\': ['\u001C'],
 96 |     ']': ['\u001D'],
 97 |     '^': ['\u001E'],
 98 |     '_': ['\u001F']
 99 | }*/
100 | 
101 | // ASCII printable code chart
102 | // var LOWER = 'abcdefghijklmnopqrstuvwxyz'
103 | // var UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
104 | // var NUMBER = '0123456789'
105 | // var SYMBOL = ' !"#$%&\'()*+,-./' + ':;<=>?@' + '[\\]^_`' + '{|}~'
106 | var LOWER = ascii(97, 122)
107 | var UPPER = ascii(65, 90)
108 | var NUMBER = ascii(48, 57)
109 | var OTHER = ascii(32, 47) + ascii(58, 64) + ascii(91, 96) + ascii(123, 126) // 排除 95 _ ascii(91, 94) + ascii(96, 96)
110 | var PRINTABLE = ascii(32, 126)
111 | var SPACE = ' \f\n\r\t\v\u00A0\u2028\u2029'
112 | var CHARACTER_CLASSES = {
113 |     '\\w': LOWER + UPPER + NUMBER + '_', // ascii(95, 95)
114 |     '\\W': OTHER.replace('_', ''),
115 |     '\\s': SPACE,
116 |     '\\S': function() {
117 |         var result = PRINTABLE
118 |         for (var i = 0; i < SPACE.length; i++) {
119 |             result = result.replace(SPACE[i], '')
120 |         }
121 |         return result
122 |     }(),
123 |     '\\d': NUMBER,
124 |     '\\D': LOWER + UPPER + OTHER
125 | }
126 | 
127 | function ascii(from, to) {
128 |     var result = ''
129 |     for (var i = from; i <= to; i++) {
130 |         result += String.fromCharCode(i)
131 |     }
132 |     return result
133 | }
134 | 
135 | // var ast = RegExpParser.parse(regexp.source)
136 | Handler.gen = function(node, result, cache) {
137 |     cache = cache || {
138 |         guid: 1
139 |     }
140 |     return Handler[node.type] ? Handler[node.type](node, result, cache) :
141 |         Handler.token(node, result, cache)
142 | }
143 | 
144 | Handler.extend({
145 |     /* jshint unused:false */
146 |     token: function(node, result, cache) {
147 |         switch (node.type) {
148 |             case 'start':
149 |             case 'end':
150 |                 return ''
151 |             case 'any-character':
152 |                 return Random.character()
153 |             case 'backspace':
154 |                 return ''
155 |             case 'word-boundary': // TODO
156 |                 return ''
157 |             case 'non-word-boundary': // TODO
158 |                 break
159 |             case 'digit':
160 |                 return Random.pick(
161 |                     NUMBER.split('')
162 |                 )
163 |             case 'non-digit':
164 |                 return Random.pick(
165 |                     (LOWER + UPPER + OTHER).split('')
166 |                 )
167 |             case 'form-feed':
168 |                 break
169 |             case 'line-feed':
170 |                 return node.body || node.text
171 |             case 'carriage-return':
172 |                 break
173 |             case 'white-space':
174 |                 return Random.pick(
175 |                     SPACE.split('')
176 |                 )
177 |             case 'non-white-space':
178 |                 return Random.pick(
179 |                     (LOWER + UPPER + NUMBER).split('')
180 |                 )
181 |             case 'tab':
182 |                 break
183 |             case 'vertical-tab':
184 |                 break
185 |             case 'word': // \w [a-zA-Z0-9]
186 |                 return Random.pick(
187 |                     (LOWER + UPPER + NUMBER).split('')
188 |                 )
189 |             case 'non-word': // \W [^a-zA-Z0-9]
190 |                 return Random.pick(
191 |                     OTHER.replace('_', '').split('')
192 |                 )
193 |             case 'null-character':
194 |                 break
195 |         }
196 |         return node.body || node.text
197 |     },
198 |     /*
199 |         {
200 |             type: 'alternate',
201 |             offset: 0,
202 |             text: '',
203 |             left: {
204 |                 boyd: []
205 |             },
206 |             right: {
207 |                 boyd: []
208 |             }
209 |         }
210 |     */
211 |     alternate: function(node, result, cache) {
212 |         // node.left/right {}
213 |         return this.gen(
214 |             Random.boolean() ? node.left : node.right,
215 |             result,
216 |             cache
217 |         )
218 |     },
219 |     /*
220 |         {
221 |             type: 'match',
222 |             offset: 0,
223 |             text: '',
224 |             body: []
225 |         }
226 |     */
227 |     match: function(node, result, cache) {
228 |         result = ''
229 |             // node.body []
230 |         for (var i = 0; i < node.body.length; i++) {
231 |             result += this.gen(node.body[i], result, cache)
232 |         }
233 |         return result
234 |     },
235 |     // ()
236 |     'capture-group': function(node, result, cache) {
237 |         // node.body {}
238 |         result = this.gen(node.body, result, cache)
239 |         cache[cache.guid++] = result
240 |         return result
241 |     },
242 |     // (?:...)
243 |     'non-capture-group': function(node, result, cache) {
244 |         // node.body {}
245 |         return this.gen(node.body, result, cache)
246 |     },
247 |     // (?=p)
248 |     'positive-lookahead': function(node, result, cache) {
249 |         // node.body
250 |         return this.gen(node.body, result, cache)
251 |     },
252 |     // (?!p)
253 |     'negative-lookahead': function(node, result, cache) {
254 |         // node.body
255 |         return ''
256 |     },
257 |     /*
258 |         {
259 |             type: 'quantified',
260 |             offset: 3,
261 |             text: 'c*',
262 |             body: {
263 |                 type: 'literal',
264 |                 offset: 3,
265 |                 text: 'c',
266 |                 body: 'c',
267 |                 escaped: false
268 |             },
269 |             quantifier: {
270 |                 type: 'quantifier',
271 |                 offset: 4,
272 |                 text: '*',
273 |                 min: 0,
274 |                 max: Infinity,
275 |                 greedy: true
276 |             }
277 |         }
278 |     */
279 |     quantified: function(node, result, cache) {
280 |         result = ''
281 |             // node.quantifier {}
282 |         var count = this.quantifier(node.quantifier);
283 |         // node.body {}
284 |         for (var i = 0; i < count; i++) {
285 |             result += this.gen(node.body, result, cache)
286 |         }
287 |         return result
288 |     },
289 |     /*
290 |         quantifier: {
291 |             type: 'quantifier',
292 |             offset: 4,
293 |             text: '*',
294 |             min: 0,
295 |             max: Infinity,
296 |             greedy: true
297 |         }
298 |     */
299 |     quantifier: function(node, result, cache) {
300 |         var min = Math.max(node.min, 0)
301 |         var max = isFinite(node.max) ? node.max :
302 |             min + Random.integer(3, 7)
303 |         return Random.integer(min, max)
304 |     },
305 |     /*
306 |         
307 |     */
308 |     charset: function(node, result, cache) {
309 |         // node.invert
310 |         if (node.invert) return this['invert-charset'](node, result, cache)
311 | 
312 |         // node.body []
313 |         var literal = Random.pick(node.body)
314 |         return this.gen(literal, result, cache)
315 |     },
316 |     'invert-charset': function(node, result, cache) {
317 |         var pool = PRINTABLE
318 |         for (var i = 0, item; i < node.body.length; i++) {
319 |             item = node.body[i]
320 |             switch (item.type) {
321 |                 case 'literal':
322 |                     pool = pool.replace(item.body, '')
323 |                     break
324 |                 case 'range':
325 |                     var min = this.gen(item.start, result, cache).charCodeAt()
326 |                     var max = this.gen(item.end, result, cache).charCodeAt()
327 |                     for (var ii = min; ii <= max; ii++) {
328 |                         pool = pool.replace(String.fromCharCode(ii), '')
329 |                     }
330 |                     /* falls through */
331 |                 default:
332 |                     var characters = CHARACTER_CLASSES[item.text]
333 |                     if (characters) {
334 |                         for (var iii = 0; iii <= characters.length; iii++) {
335 |                             pool = pool.replace(characters[iii], '')
336 |                         }
337 |                     }
338 |             }
339 |         }
340 |         return Random.pick(pool.split(''))
341 |     },
342 |     range: function(node, result, cache) {
343 |         // node.start, node.end
344 |         var min = this.gen(node.start, result, cache).charCodeAt()
345 |         var max = this.gen(node.end, result, cache).charCodeAt()
346 |         return String.fromCharCode(
347 |             Random.integer(min, max)
348 |         )
349 |     },
350 |     literal: function(node, result, cache) {
351 |         return node.escaped ? node.body : node.text
352 |     },
353 |     // Unicode \u
354 |     unicode: function(node, result, cache) {
355 |         return String.fromCharCode(
356 |             parseInt(node.code, 16)
357 |         )
358 |     },
359 |     // 十六进制 \xFF
360 |     hex: function(node, result, cache) {
361 |         return String.fromCharCode(
362 |             parseInt(node.code, 16)
363 |         )
364 |     },
365 |     // 八进制 \0
366 |     octal: function(node, result, cache) {
367 |         return String.fromCharCode(
368 |             parseInt(node.code, 8)
369 |         )
370 |     },
371 |     // 反向引用
372 |     'back-reference': function(node, result, cache) {
373 |         return cache[node.code] || ''
374 |     },
375 |     /*
376 |         http://en.wikipedia.org/wiki/C0_and_C1_control_codes
377 |     */
378 |     CONTROL_CHARACTER_MAP: function() {
379 |         var CONTROL_CHARACTER = '@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \\ ] ^ _'.split(' ')
380 |         var CONTROL_CHARACTER_UNICODE = '\u0000 \u0001 \u0002 \u0003 \u0004 \u0005 \u0006 \u0007 \u0008 \u0009 \u000A \u000B \u000C \u000D \u000E \u000F \u0010 \u0011 \u0012 \u0013 \u0014 \u0015 \u0016 \u0017 \u0018 \u0019 \u001A \u001B \u001C \u001D \u001E \u001F'.split(' ')
381 |         var map = {}
382 |         for (var i = 0; i < CONTROL_CHARACTER.length; i++) {
383 |             map[CONTROL_CHARACTER[i]] = CONTROL_CHARACTER_UNICODE[i]
384 |         }
385 |         return map
386 |     }(),
387 |     'control-character': function(node, result, cache) {
388 |         return this.CONTROL_CHARACTER_MAP[node.code]
389 |     }
390 | })
391 | 
392 | module.exports = Handler


--------------------------------------------------------------------------------
/src/mock/xhr/xhr.js:
--------------------------------------------------------------------------------
  1 | /* global window, document, location, Event, setTimeout */
  2 | /*
  3 |     ## MockXMLHttpRequest
  4 | 
  5 |     期望的功能:
  6 |     1. 完整地覆盖原生 XHR 的行为
  7 |     2. 完整地模拟原生 XHR 的行为
  8 |     3. 在发起请求时,自动检测是否需要拦截
  9 |     4. 如果不必拦截,则执行原生 XHR 的行为
 10 |     5. 如果需要拦截,则执行虚拟 XHR 的行为
 11 |     6. 兼容 XMLHttpRequest 和 ActiveXObject
 12 |         new window.XMLHttpRequest()
 13 |         new window.ActiveXObject("Microsoft.XMLHTTP")
 14 | 
 15 |     关键方法的逻辑:
 16 |     * new   此时尚无法确定是否需要拦截,所以创建原生 XHR 对象是必须的。
 17 |     * open  此时可以取到 URL,可以决定是否进行拦截。
 18 |     * send  此时已经确定了请求方式。
 19 | 
 20 |     规范:
 21 |     http://xhr.spec.whatwg.org/
 22 |     http://www.w3.org/TR/XMLHttpRequest2/
 23 | 
 24 |     参考实现:
 25 |     https://github.com/philikon/MockHttpRequest/blob/master/lib/mock.js
 26 |     https://github.com/trek/FakeXMLHttpRequest/blob/master/fake_xml_http_request.js
 27 |     https://github.com/ilinsky/xmlhttprequest/blob/master/XMLHttpRequest.js
 28 |     https://github.com/firebug/firebug-lite/blob/master/content/lite/xhr.js
 29 |     https://github.com/thx/RAP/blob/master/lab/rap.plugin.xinglie.js
 30 | 
 31 |     **需不需要全面重写 XMLHttpRequest?**
 32 |         http://xhr.spec.whatwg.org/#interface-xmlhttprequest
 33 |         关键属性 readyState、status、statusText、response、responseText、responseXML 是 readonly,所以,试图通过修改这些状态,来模拟响应是不可行的。
 34 |         因此,唯一的办法是模拟整个 XMLHttpRequest,就像 jQuery 对事件模型的封装。
 35 | 
 36 |     // Event handlers
 37 |     onloadstart         loadstart
 38 |     onprogress          progress
 39 |     onabort             abort
 40 |     onerror             error
 41 |     onload              load
 42 |     ontimeout           timeout
 43 |     onloadend           loadend
 44 |     onreadystatechange  readystatechange
 45 |  */
 46 | 
 47 | var Util = require('../util')
 48 | 
 49 | // 备份原生 XMLHttpRequest
 50 | window._XMLHttpRequest = window.XMLHttpRequest
 51 | window._ActiveXObject = window.ActiveXObject
 52 | 
 53 | /*
 54 |     PhantomJS
 55 |     TypeError: '[object EventConstructor]' is not a constructor (evaluating 'new Event("readystatechange")')
 56 | 
 57 |     https://github.com/bluerail/twitter-bootstrap-rails-confirm/issues/18
 58 |     https://github.com/ariya/phantomjs/issues/11289
 59 | */
 60 | try {
 61 |     new window.Event('custom')
 62 | } catch (exception) {
 63 |     window.Event = function(type, bubbles, cancelable, detail) {
 64 |         var event = document.createEvent('CustomEvent') // MUST be 'CustomEvent'
 65 |         event.initCustomEvent(type, bubbles, cancelable, detail)
 66 |         return event
 67 |     }
 68 | }
 69 | 
 70 | var XHR_STATES = {
 71 |     // The object has been constructed.
 72 |     UNSENT: 0,
 73 |     // The open() method has been successfully invoked.
 74 |     OPENED: 1,
 75 |     // All redirects (if any) have been followed and all HTTP headers of the response have been received.
 76 |     HEADERS_RECEIVED: 2,
 77 |     // The response's body is being received.
 78 |     LOADING: 3,
 79 |     // The data transfer has been completed or something went wrong during the transfer (e.g. infinite redirects).
 80 |     DONE: 4
 81 | }
 82 | 
 83 | var XHR_EVENTS = 'readystatechange loadstart progress abort error load timeout loadend'.split(' ')
 84 | var XHR_REQUEST_PROPERTIES = 'timeout withCredentials'.split(' ')
 85 | var XHR_RESPONSE_PROPERTIES = 'readyState responseURL status statusText responseType response responseText responseXML'.split(' ')
 86 | 
 87 | // https://github.com/trek/FakeXMLHttpRequest/blob/master/fake_xml_http_request.js#L32
 88 | var HTTP_STATUS_CODES = {
 89 |     100: "Continue",
 90 |     101: "Switching Protocols",
 91 |     200: "OK",
 92 |     201: "Created",
 93 |     202: "Accepted",
 94 |     203: "Non-Authoritative Information",
 95 |     204: "No Content",
 96 |     205: "Reset Content",
 97 |     206: "Partial Content",
 98 |     300: "Multiple Choice",
 99 |     301: "Moved Permanently",
100 |     302: "Found",
101 |     303: "See Other",
102 |     304: "Not Modified",
103 |     305: "Use Proxy",
104 |     307: "Temporary Redirect",
105 |     400: "Bad Request",
106 |     401: "Unauthorized",
107 |     402: "Payment Required",
108 |     403: "Forbidden",
109 |     404: "Not Found",
110 |     405: "Method Not Allowed",
111 |     406: "Not Acceptable",
112 |     407: "Proxy Authentication Required",
113 |     408: "Request Timeout",
114 |     409: "Conflict",
115 |     410: "Gone",
116 |     411: "Length Required",
117 |     412: "Precondition Failed",
118 |     413: "Request Entity Too Large",
119 |     414: "Request-URI Too Long",
120 |     415: "Unsupported Media Type",
121 |     416: "Requested Range Not Satisfiable",
122 |     417: "Expectation Failed",
123 |     422: "Unprocessable Entity",
124 |     500: "Internal Server Error",
125 |     501: "Not Implemented",
126 |     502: "Bad Gateway",
127 |     503: "Service Unavailable",
128 |     504: "Gateway Timeout",
129 |     505: "HTTP Version Not Supported"
130 | }
131 | 
132 | /*
133 |     MockXMLHttpRequest
134 | */
135 | 
136 | function MockXMLHttpRequest() {
137 |     // 初始化 custom 对象,用于存储自定义属性
138 |     this.custom = {
139 |         events: {},
140 |         requestHeaders: {},
141 |         responseHeaders: {}
142 |     }
143 | }
144 | 
145 | MockXMLHttpRequest._settings = {
146 |     timeout: '10-100',
147 |     /*
148 |         timeout: 50,
149 |         timeout: '10-100',
150 |      */
151 | }
152 | 
153 | MockXMLHttpRequest.setup = function(settings) {
154 |     Util.extend(MockXMLHttpRequest._settings, settings)
155 |     return MockXMLHttpRequest._settings
156 | }
157 | 
158 | Util.extend(MockXMLHttpRequest, XHR_STATES)
159 | Util.extend(MockXMLHttpRequest.prototype, XHR_STATES)
160 | 
161 | // 标记当前对象为 MockXMLHttpRequest
162 | MockXMLHttpRequest.prototype.mock = true
163 | 
164 | // 是否拦截 Ajax 请求
165 | MockXMLHttpRequest.prototype.match = false
166 | 
167 | // 初始化 Request 相关的属性和方法
168 | Util.extend(MockXMLHttpRequest.prototype, {
169 |     // https://xhr.spec.whatwg.org/#the-open()-method
170 |     // Sets the request method, request URL, and synchronous flag.
171 |     open: function(method, url, async, username, password) {
172 |         var that = this
173 | 
174 |         Util.extend(this.custom, {
175 |             method: method,
176 |             url: url,
177 |             async: typeof async === 'boolean' ? async : true,
178 |             username: username,
179 |             password: password,
180 |             options: {
181 |                 url: url,
182 |                 type: method
183 |             }
184 |         })
185 | 
186 |         this.custom.timeout = function(timeout) {
187 |             if (typeof timeout === 'number') return timeout
188 |             if (typeof timeout === 'string' && !~timeout.indexOf('-')) return parseInt(timeout, 10)
189 |             if (typeof timeout === 'string' && ~timeout.indexOf('-')) {
190 |                 var tmp = timeout.split('-')
191 |                 var min = parseInt(tmp[0], 10)
192 |                 var max = parseInt(tmp[1], 10)
193 |                 return Math.round(Math.random() * (max - min)) + min
194 |             }
195 |         }(MockXMLHttpRequest._settings.timeout)
196 | 
197 |         // 查找与请求参数匹配的数据模板
198 |         var item = find(this.custom.options)
199 | 
200 |         function handle(event) {
201 |             // 同步属性 NativeXMLHttpRequest => MockXMLHttpRequest
202 |             for (var i = 0; i < XHR_RESPONSE_PROPERTIES.length; i++) {
203 |                 try {
204 |                     that[XHR_RESPONSE_PROPERTIES[i]] = xhr[XHR_RESPONSE_PROPERTIES[i]]
205 |                 } catch (e) {}
206 |             }
207 |             // 触发 MockXMLHttpRequest 上的同名事件
208 |             that.dispatchEvent(new Event(event.type /*, false, false, that*/ ))
209 |         }
210 | 
211 |         // 如果未找到匹配的数据模板,则采用原生 XHR 发送请求。
212 |         if (!item) {
213 |             // 创建原生 XHR 对象,调用原生 open(),监听所有原生事件
214 |             var xhr = createNativeXMLHttpRequest()
215 |             this.custom.xhr = xhr
216 | 
217 |             // 初始化所有事件,用于监听原生 XHR 对象的事件
218 |             for (var i = 0; i < XHR_EVENTS.length; i++) {
219 |                 xhr.addEventListener(XHR_EVENTS[i], handle)
220 |             }
221 | 
222 |             // xhr.open()
223 |             if (username) xhr.open(method, url, async, username, password)
224 |             else xhr.open(method, url, async)
225 | 
226 |             // 同步属性 MockXMLHttpRequest => NativeXMLHttpRequest
227 |             for (var j = 0; j < XHR_REQUEST_PROPERTIES.length; j++) {
228 |                 try {
229 |                     xhr[XHR_REQUEST_PROPERTIES[j]] = that[XHR_REQUEST_PROPERTIES[j]]
230 |                 } catch (e) {}
231 |             }
232 | 
233 |             return
234 |         }
235 | 
236 |         // 找到了匹配的数据模板,开始拦截 XHR 请求
237 |         this.match = true
238 |         this.custom.template = item
239 |         this.readyState = MockXMLHttpRequest.OPENED
240 |         this.dispatchEvent(new Event('readystatechange' /*, false, false, this*/ ))
241 |     },
242 |     // https://xhr.spec.whatwg.org/#the-setrequestheader()-method
243 |     // Combines a header in author request headers.
244 |     setRequestHeader: function(name, value) {
245 |         // 原生 XHR
246 |         if (!this.match) {
247 |             this.custom.xhr.setRequestHeader(name, value)
248 |             return
249 |         }
250 | 
251 |         // 拦截 XHR
252 |         var requestHeaders = this.custom.requestHeaders
253 |         if (requestHeaders[name]) requestHeaders[name] += ',' + value
254 |         else requestHeaders[name] = value
255 |     },
256 |     timeout: 0,
257 |     withCredentials: false,
258 |     upload: {},
259 |     // https://xhr.spec.whatwg.org/#the-send()-method
260 |     // Initiates the request.
261 |     send: function send(data) {
262 |         var that = this
263 |         this.custom.options.body = data
264 | 
265 |         // 原生 XHR
266 |         if (!this.match) {
267 |             this.custom.xhr.send(data)
268 |             return
269 |         }
270 | 
271 |         // 拦截 XHR
272 | 
273 |         // X-Requested-With header
274 |         this.setRequestHeader('X-Requested-With', 'MockXMLHttpRequest')
275 | 
276 |         // loadstart The fetch initiates.
277 |         this.dispatchEvent(new Event('loadstart' /*, false, false, this*/ ))
278 | 
279 |         if (this.custom.async) setTimeout(done, this.custom.timeout) // 异步
280 |         else done() // 同步
281 | 
282 |         function done() {
283 |             that.readyState = MockXMLHttpRequest.HEADERS_RECEIVED
284 |             that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
285 |             that.readyState = MockXMLHttpRequest.LOADING
286 |             that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
287 | 
288 |             that.status = 200
289 |             that.statusText = HTTP_STATUS_CODES[200]
290 | 
291 |             // fix #92 #93 by @qddegtya
292 |             that.response = that.responseText = JSON.stringify(
293 |                 convert(that.custom.template, that.custom.options),
294 |                 null, 4
295 |             )
296 | 
297 |             that.readyState = MockXMLHttpRequest.DONE
298 |             that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
299 |             that.dispatchEvent(new Event('load' /*, false, false, that*/ ));
300 |             that.dispatchEvent(new Event('loadend' /*, false, false, that*/ ));
301 |         }
302 |     },
303 |     // https://xhr.spec.whatwg.org/#the-abort()-method
304 |     // Cancels any network activity.
305 |     abort: function abort() {
306 |         // 原生 XHR
307 |         if (!this.match) {
308 |             this.custom.xhr.abort()
309 |             return
310 |         }
311 | 
312 |         // 拦截 XHR
313 |         this.readyState = MockXMLHttpRequest.UNSENT
314 |         this.dispatchEvent(new Event('abort', false, false, this))
315 |         this.dispatchEvent(new Event('error', false, false, this))
316 |     }
317 | })
318 | 
319 | // 初始化 Response 相关的属性和方法
320 | Util.extend(MockXMLHttpRequest.prototype, {
321 |     responseURL: '',
322 |     status: MockXMLHttpRequest.UNSENT,
323 |     statusText: '',
324 |     // https://xhr.spec.whatwg.org/#the-getresponseheader()-method
325 |     getResponseHeader: function(name) {
326 |         // 原生 XHR
327 |         if (!this.match) {
328 |             return this.custom.xhr.getResponseHeader(name)
329 |         }
330 | 
331 |         // 拦截 XHR
332 |         return this.custom.responseHeaders[name.toLowerCase()]
333 |     },
334 |     // https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method
335 |     // http://www.utf8-chartable.de/
336 |     getAllResponseHeaders: function() {
337 |         // 原生 XHR
338 |         if (!this.match) {
339 |             return this.custom.xhr.getAllResponseHeaders()
340 |         }
341 | 
342 |         // 拦截 XHR
343 |         var responseHeaders = this.custom.responseHeaders
344 |         var headers = ''
345 |         for (var h in responseHeaders) {
346 |             if (!responseHeaders.hasOwnProperty(h)) continue
347 |             headers += h + ': ' + responseHeaders[h] + '\r\n'
348 |         }
349 |         return headers
350 |     },
351 |     overrideMimeType: function( /*mime*/ ) {},
352 |     responseType: '', // '', 'text', 'arraybuffer', 'blob', 'document', 'json'
353 |     response: null,
354 |     responseText: '',
355 |     responseXML: null
356 | })
357 | 
358 | // EventTarget
359 | Util.extend(MockXMLHttpRequest.prototype, {
360 |     addEventListener: function addEventListener(type, handle) {
361 |         var events = this.custom.events
362 |         if (!events[type]) events[type] = []
363 |         events[type].push(handle)
364 |     },
365 |     removeEventListener: function removeEventListener(type, handle) {
366 |         var handles = this.custom.events[type] || []
367 |         for (var i = 0; i < handles.length; i++) {
368 |             if (handles[i] === handle) {
369 |                 handles.splice(i--, 1)
370 |             }
371 |         }
372 |     },
373 |     dispatchEvent: function dispatchEvent(event) {
374 |         var handles = this.custom.events[event.type] || []
375 |         for (var i = 0; i < handles.length; i++) {
376 |             handles[i].call(this, event)
377 |         }
378 | 
379 |         var ontype = 'on' + event.type
380 |         if (this[ontype]) this[ontype](event)
381 |     }
382 | })
383 | 
384 | // Inspired by jQuery
385 | function createNativeXMLHttpRequest() {
386 |     var isLocal = function() {
387 |         var rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
388 |         var rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/
389 |         var ajaxLocation = location.href
390 |         var ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || []
391 |         return rlocalProtocol.test(ajaxLocParts[1])
392 |     }()
393 | 
394 |     return window.ActiveXObject ?
395 |         (!isLocal && createStandardXHR() || createActiveXHR()) : createStandardXHR()
396 | 
397 |     function createStandardXHR() {
398 |         try {
399 |             return new window._XMLHttpRequest();
400 |         } catch (e) {}
401 |     }
402 | 
403 |     function createActiveXHR() {
404 |         try {
405 |             return new window._ActiveXObject("Microsoft.XMLHTTP");
406 |         } catch (e) {}
407 |     }
408 | }
409 | 
410 | 
411 | // 查找与请求参数匹配的数据模板:URL,Type
412 | function find(options) {
413 | 
414 |     for (var sUrlType in MockXMLHttpRequest.Mock._mocked) {
415 |         var item = MockXMLHttpRequest.Mock._mocked[sUrlType]
416 |         if (
417 |             (!item.rurl || match(item.rurl, options.url)) &&
418 |             (!item.rtype || match(item.rtype, options.type.toLowerCase()))
419 |         ) {
420 |             // console.log('[mock]', options.url, '>', item.rurl)
421 |             return item
422 |         }
423 |     }
424 | 
425 |     function match(expected, actual) {
426 |         if (Util.type(expected) === 'string') {
427 |             return expected === actual
428 |         }
429 |         if (Util.type(expected) === 'regexp') {
430 |             return expected.test(actual)
431 |         }
432 |     }
433 | 
434 | }
435 | 
436 | // 数据模板 => 响应数据
437 | function convert(item, options) {
438 |     return Util.isFunction(item.template) ?
439 |         item.template(options) : MockXMLHttpRequest.Mock.mock(item.template)
440 | }
441 | 
442 | module.exports = MockXMLHttpRequest


--------------------------------------------------------------------------------
/src/mock/valid/valid.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |     ## valid(template, data)
  3 | 
  4 |     校验真实数据 data 是否与数据模板 template 匹配。
  5 |     
  6 |     实现思路:
  7 |     1. 解析规则。
  8 |         先把数据模板 template 解析为更方便机器解析的 JSON-Schame
  9 |         name               属性名 
 10 |         type               属性值类型
 11 |         template           属性值模板
 12 |         properties         对象属性数组
 13 |         items              数组元素数组
 14 |         rule               属性值生成规则
 15 |     2. 递归验证规则。
 16 |         然后用 JSON-Schema 校验真实数据,校验项包括属性名、值类型、值、值生成规则。
 17 | 
 18 |     提示信息 
 19 |     https://github.com/fge/json-schema-validator/blob/master/src/main/resources/com/github/fge/jsonschema/validator/validation.properties
 20 |     [JSON-Schama validator](http://json-schema-validator.herokuapp.com/)
 21 |     [Regexp Demo](http://demos.forbeslindesay.co.uk/regexp/)
 22 | */
 23 | var Constant = require('../constant')
 24 | var Util = require('../util')
 25 | var toJSONSchema = require('../schema')
 26 | 
 27 | function valid(template, data) {
 28 |     var schema = toJSONSchema(template)
 29 |     var result = Diff.diff(schema, data)
 30 |     for (var i = 0; i < result.length; i++) {
 31 |         // console.log(template, data)
 32 |         // console.warn(Assert.message(result[i]))
 33 |     }
 34 |     return result
 35 | }
 36 | 
 37 | /*
 38 |     ## name
 39 |         有生成规则:比较解析后的 name
 40 |         无生成规则:直接比较
 41 |     ## type
 42 |         无类型转换:直接比较
 43 |         有类型转换:先试着解析 template,然后再检查?
 44 |     ## value vs. template
 45 |         基本类型
 46 |             无生成规则:直接比较
 47 |             有生成规则:
 48 |                 number
 49 |                     min-max.dmin-dmax
 50 |                     min-max.dcount
 51 |                     count.dmin-dmax
 52 |                     count.dcount
 53 |                     +step
 54 |                     整数部分
 55 |                     小数部分
 56 |                 boolean 
 57 |                 string  
 58 |                     min-max
 59 |                     count
 60 |     ## properties
 61 |         对象
 62 |             有生成规则:检测期望的属性个数,继续递归
 63 |             无生成规则:检测全部的属性个数,继续递归
 64 |     ## items
 65 |         数组
 66 |             有生成规则:
 67 |                 `'name|1': [{}, {} ...]`            其中之一,继续递归
 68 |                 `'name|+1': [{}, {} ...]`           顺序检测,继续递归
 69 |                 `'name|min-max': [{}, {} ...]`      检测个数,继续递归
 70 |                 `'name|count': [{}, {} ...]`        检测个数,继续递归
 71 |             无生成规则:检测全部的元素个数,继续递归
 72 | */
 73 | var Diff = {
 74 |     diff: function diff(schema, data, name /* Internal Use Only */ ) {
 75 |         var result = []
 76 | 
 77 |         // 先检测名称 name 和类型 type,如果匹配,才有必要继续检测
 78 |         if (
 79 |             this.name(schema, data, name, result) &&
 80 |             this.type(schema, data, name, result)
 81 |         ) {
 82 |             this.value(schema, data, name, result)
 83 |             this.properties(schema, data, name, result)
 84 |             this.items(schema, data, name, result)
 85 |         }
 86 | 
 87 |         return result
 88 |     },
 89 |     /* jshint unused:false */
 90 |     name: function(schema, data, name, result) {
 91 |         var length = result.length
 92 | 
 93 |         Assert.equal('name', schema.path, name + '', schema.name + '', result)
 94 | 
 95 |         return result.length === length
 96 |     },
 97 |     type: function(schema, data, name, result) {
 98 |         var length = result.length
 99 | 
100 |         switch (schema.type) {
101 |             case 'string':
102 |                 // 跳过含有『占位符』的属性值,因为『占位符』返回值的类型可能和模板不一致,例如 '@int' 会返回一个整形值
103 |                 if (schema.template.match(Constant.RE_PLACEHOLDER)) return true
104 |                 break
105 |             case 'array':
106 |                 if (schema.rule.parameters) {
107 |                     // name|count: array
108 |                     if (schema.rule.min !== undefined && schema.rule.max === undefined) {
109 |                         // 跳过 name|1: array,因为最终值的类型(很可能)不是数组,也不一定与 `array` 中的类型一致
110 |                         if (schema.rule.count === 1) return true
111 |                     }
112 |                     // 跳过 name|+inc: array
113 |                     if (schema.rule.parameters[2]) return true
114 |                 }
115 |                 break
116 |             case 'function':
117 |                 // 跳过 `'name': function`,因为函数可以返回任何类型的值。
118 |                 return true
119 |         }
120 | 
121 |         Assert.equal('type', schema.path, Util.type(data), schema.type, result)
122 | 
123 |         return result.length === length
124 |     },
125 |     value: function(schema, data, name, result) {
126 |         var length = result.length
127 | 
128 |         var rule = schema.rule
129 |         var templateType = schema.type
130 |         if (templateType === 'object' || templateType === 'array' || templateType === 'function') return true
131 | 
132 |         // 无生成规则
133 |         if (!rule.parameters) {
134 |             switch (templateType) {
135 |                 case 'regexp':
136 |                     Assert.match('value', schema.path, data, schema.template, result)
137 |                     return result.length === length
138 |                 case 'string':
139 |                     // 同样跳过含有『占位符』的属性值,因为『占位符』的返回值会通常会与模板不一致
140 |                     if (schema.template.match(Constant.RE_PLACEHOLDER)) return result.length === length
141 |                     break
142 |             }
143 |             Assert.equal('value', schema.path, data, schema.template, result)
144 |             return result.length === length
145 |         }
146 | 
147 |         // 有生成规则
148 |         var actualRepeatCount
149 |         switch (templateType) {
150 |             case 'number':
151 |                 var parts = (data + '').split('.')
152 |                 parts[0] = +parts[0]
153 | 
154 |                 // 整数部分
155 |                 // |min-max
156 |                 if (rule.min !== undefined && rule.max !== undefined) {
157 |                     Assert.greaterThanOrEqualTo('value', schema.path, parts[0], Math.min(rule.min, rule.max), result)
158 |                         // , 'numeric instance is lower than the required minimum (minimum: {expected}, found: {actual})')
159 |                     Assert.lessThanOrEqualTo('value', schema.path, parts[0], Math.max(rule.min, rule.max), result)
160 |                 }
161 |                 // |count
162 |                 if (rule.min !== undefined && rule.max === undefined) {
163 |                     Assert.equal('value', schema.path, parts[0], rule.min, result, '[value] ' + name)
164 |                 }
165 | 
166 |                 // 小数部分
167 |                 if (rule.decimal) {
168 |                     // |dmin-dmax
169 |                     if (rule.dmin !== undefined && rule.dmax !== undefined) {
170 |                         Assert.greaterThanOrEqualTo('value', schema.path, parts[1].length, rule.dmin, result)
171 |                         Assert.lessThanOrEqualTo('value', schema.path, parts[1].length, rule.dmax, result)
172 |                     }
173 |                     // |dcount
174 |                     if (rule.dmin !== undefined && rule.dmax === undefined) {
175 |                         Assert.equal('value', schema.path, parts[1].length, rule.dmin, result)
176 |                     }
177 |                 }
178 | 
179 |                 break
180 | 
181 |             case 'boolean':
182 |                 break
183 | 
184 |             case 'string':
185 |                 // 'aaa'.match(/a/g)
186 |                 actualRepeatCount = data.match(new RegExp(schema.template, 'g'))
187 |                 actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0
188 | 
189 |                 // |min-max
190 |                 if (rule.min !== undefined && rule.max !== undefined) {
191 |                     Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.min, result)
192 |                     Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
193 |                 }
194 |                 // |count
195 |                 if (rule.min !== undefined && rule.max === undefined) {
196 |                     Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
197 |                 }
198 | 
199 |                 break
200 | 
201 |             case 'regexp':
202 |                 actualRepeatCount = data.match(new RegExp(schema.template.source.replace(/^\^|\$$/g, ''), 'g'))
203 |                 actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0
204 | 
205 |                 // |min-max
206 |                 if (rule.min !== undefined && rule.max !== undefined) {
207 |                     Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.min, result)
208 |                     Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
209 |                 }
210 |                 // |count
211 |                 if (rule.min !== undefined && rule.max === undefined) {
212 |                     Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
213 |                 }
214 |                 break
215 |         }
216 | 
217 |         return result.length === length
218 |     },
219 |     properties: function(schema, data, name, result) {
220 |         var length = result.length
221 | 
222 |         var rule = schema.rule
223 |         var keys = Util.keys(data)
224 |         if (!schema.properties) return
225 | 
226 |         // 无生成规则
227 |         if (!schema.rule.parameters) {
228 |             Assert.equal('properties length', schema.path, keys.length, schema.properties.length, result)
229 |         } else {
230 |             // 有生成规则
231 |             // |min-max
232 |             if (rule.min !== undefined && rule.max !== undefined) {
233 |                 Assert.greaterThanOrEqualTo('properties length', schema.path, keys.length, Math.min(rule.min, rule.max), result)
234 |                 Assert.lessThanOrEqualTo('properties length', schema.path, keys.length, Math.max(rule.min, rule.max), result)
235 |             }
236 |             // |count
237 |             if (rule.min !== undefined && rule.max === undefined) {
238 |                 // |1, |>1
239 |                 if (rule.count !== 1) Assert.equal('properties length', schema.path, keys.length, rule.min, result)
240 |             }
241 |         }
242 | 
243 |         if (result.length !== length) return false
244 | 
245 |         for (var i = 0; i < keys.length; i++) {
246 |             result.push.apply(
247 |                 result,
248 |                 this.diff(
249 |                     function() {
250 |                         var property
251 |                         Util.each(schema.properties, function(item /*, index*/ ) {
252 |                             if (item.name === keys[i]) property = item
253 |                         })
254 |                         return property || schema.properties[i]
255 |                     }(),
256 |                     data[keys[i]],
257 |                     keys[i]
258 |                 )
259 |             )
260 |         }
261 | 
262 |         return result.length === length
263 |     },
264 |     items: function(schema, data, name, result) {
265 |         var length = result.length
266 | 
267 |         if (!schema.items) return
268 | 
269 |         var rule = schema.rule
270 | 
271 |         // 无生成规则
272 |         if (!schema.rule.parameters) {
273 |             Assert.equal('items length', schema.path, data.length, schema.items.length, result)
274 |         } else {
275 |             // 有生成规则
276 |             // |min-max
277 |             if (rule.min !== undefined && rule.max !== undefined) {
278 |                 Assert.greaterThanOrEqualTo('items', schema.path, data.length, (Math.min(rule.min, rule.max) * schema.items.length), result,
279 |                     '[{utype}] array is too short: {path} must have at least {expected} elements but instance has {actual} elements')
280 |                 Assert.lessThanOrEqualTo('items', schema.path, data.length, (Math.max(rule.min, rule.max) * schema.items.length), result,
281 |                     '[{utype}] array is too long: {path} must have at most {expected} elements but instance has {actual} elements')
282 |             }
283 |             // |count
284 |             if (rule.min !== undefined && rule.max === undefined) {
285 |                 // |1, |>1
286 |                 if (rule.count === 1) return result.length === length
287 |                 else Assert.equal('items length', schema.path, data.length, (rule.min * schema.items.length), result)
288 |             }
289 |             // |+inc
290 |             if (rule.parameters[2]) return result.length === length
291 |         }
292 | 
293 |         if (result.length !== length) return false
294 | 
295 |         for (var i = 0; i < data.length; i++) {
296 |             result.push.apply(
297 |                 result,
298 |                 this.diff(
299 |                     schema.items[i % schema.items.length],
300 |                     data[i],
301 |                     i % schema.items.length
302 |                 )
303 |             )
304 |         }
305 | 
306 |         return result.length === length
307 |     }
308 | }
309 | 
310 | /*
311 |     完善、友好的提示信息
312 |     
313 |     Equal, not equal to, greater than, less than, greater than or equal to, less than or equal to
314 |     路径 验证类型 描述 
315 | 
316 |     Expect path.name is less than or equal to expected, but path.name is actual.
317 | 
318 |     Expect path.name is less than or equal to expected, but path.name is actual.
319 |     Expect path.name is greater than or equal to expected, but path.name is actual.
320 | 
321 | */
322 | var Assert = {
323 |     message: function(item) {
324 |         return (item.message ||
325 |                 '[{utype}] Expect {path}\'{ltype} {action} {expected}, but is {actual}')
326 |             .replace('{utype}', item.type.toUpperCase())
327 |             .replace('{ltype}', item.type.toLowerCase())
328 |             .replace('{path}', Util.isArray(item.path) && item.path.join('.') || item.path)
329 |             .replace('{action}', item.action)
330 |             .replace('{expected}', item.expected)
331 |             .replace('{actual}', item.actual)
332 |     },
333 |     equal: function(type, path, actual, expected, result, message) {
334 |         if (actual === expected) return true
335 |         switch (type) {
336 |             case 'type':
337 |                 // 正则模板 === 字符串最终值
338 |                 if (expected === 'regexp' && actual === 'string') return true
339 |                 break
340 |         }
341 | 
342 |         var item = {
343 |             path: path,
344 |             type: type,
345 |             actual: actual,
346 |             expected: expected,
347 |             action: 'is equal to',
348 |             message: message
349 |         }
350 |         item.message = Assert.message(item)
351 |         result.push(item)
352 |         return false
353 |     },
354 |     // actual matches expected
355 |     match: function(type, path, actual, expected, result, message) {
356 |         if (expected.test(actual)) return true
357 | 
358 |         var item = {
359 |             path: path,
360 |             type: type,
361 |             actual: actual,
362 |             expected: expected,
363 |             action: 'matches',
364 |             message: message
365 |         }
366 |         item.message = Assert.message(item)
367 |         result.push(item)
368 |         return false
369 |     },
370 |     notEqual: function(type, path, actual, expected, result, message) {
371 |         if (actual !== expected) return true
372 |         var item = {
373 |             path: path,
374 |             type: type,
375 |             actual: actual,
376 |             expected: expected,
377 |             action: 'is not equal to',
378 |             message: message
379 |         }
380 |         item.message = Assert.message(item)
381 |         result.push(item)
382 |         return false
383 |     },
384 |     greaterThan: function(type, path, actual, expected, result, message) {
385 |         if (actual > expected) return true
386 |         var item = {
387 |             path: path,
388 |             type: type,
389 |             actual: actual,
390 |             expected: expected,
391 |             action: 'is greater than',
392 |             message: message
393 |         }
394 |         item.message = Assert.message(item)
395 |         result.push(item)
396 |         return false
397 |     },
398 |     lessThan: function(type, path, actual, expected, result, message) {
399 |         if (actual < expected) return true
400 |         var item = {
401 |             path: path,
402 |             type: type,
403 |             actual: actual,
404 |             expected: expected,
405 |             action: 'is less to',
406 |             message: message
407 |         }
408 |         item.message = Assert.message(item)
409 |         result.push(item)
410 |         return false
411 |     },
412 |     greaterThanOrEqualTo: function(type, path, actual, expected, result, message) {
413 |         if (actual >= expected) return true
414 |         var item = {
415 |             path: path,
416 |             type: type,
417 |             actual: actual,
418 |             expected: expected,
419 |             action: 'is greater than or equal to',
420 |             message: message
421 |         }
422 |         item.message = Assert.message(item)
423 |         result.push(item)
424 |         return false
425 |     },
426 |     lessThanOrEqualTo: function(type, path, actual, expected, result, message) {
427 |         if (actual <= expected) return true
428 |         var item = {
429 |             path: path,
430 |             type: type,
431 |             actual: actual,
432 |             expected: expected,
433 |             action: 'is less than or equal to',
434 |             message: message
435 |         }
436 |         item.message = Assert.message(item)
437 |         result.push(item)
438 |         return false
439 |     }
440 | }
441 | 
442 | valid.Diff = Diff
443 | valid.Assert = Assert
444 | 
445 | module.exports = valid


--------------------------------------------------------------------------------
/test/test.mock.request.js:
--------------------------------------------------------------------------------
  1 | /* global console, require, chai, describe, before, it */
  2 | // 数据占位符定义(Data Placeholder Definition,DPD)
  3 | var expect = chai.expect
  4 | var Mock, $, _
  5 | 
  6 | describe('Request', function() {
  7 |     before(function(done) {
  8 |         require(['mock', 'underscore', 'jquery'], function() {
  9 |             Mock = arguments[0]
 10 |             _ = arguments[1]
 11 |             $ = arguments[2]
 12 |             expect(Mock).to.not.equal(undefined)
 13 |             expect(_).to.not.equal(undefined)
 14 |             expect($).to.not.equal(undefined)
 15 |             done()
 16 |         })
 17 |     })
 18 | 
 19 |     function stringify(json) {
 20 |         return JSON.stringify(json /*, null, 4*/ )
 21 |     }
 22 | 
 23 |     describe('jQuery.ajax()', function() {
 24 |         it('', function(done) {
 25 |             var that = this
 26 |             var url = Math.random()
 27 |             $.ajax({
 28 |                 url: url,
 29 |                 dataType: 'json'
 30 |             }).done(function( /*data, textStatus, jqXHR*/ ) {
 31 |                 // 不会进入
 32 |             }).fail(function(jqXHR /*, textStatus, errorThrown*/ ) {
 33 |                 // 浏览器 || PhantomJS
 34 |                 expect([404, 0]).to.include(jqXHR.status)
 35 |                 that.test.title += url + ' => ' + jqXHR.status
 36 |             }).always(function() {
 37 |                 done()
 38 |             })
 39 |         })
 40 |     })
 41 |     describe('jQuery.getScript()', function() {
 42 |         it('', function(done) {
 43 |             var that = this
 44 |             var url = './materiels/noop.js'
 45 |             $.getScript(url, function(script, textStatus, jqXHR) {
 46 |                 expect(script).to.be.ok
 47 |                 that.test.title += url + ' => ' + jqXHR.status + ' ' + textStatus
 48 |                 done()
 49 |             })
 50 |         })
 51 |     })
 52 |     describe('jQuery.load()', function() {
 53 |         it('', function(done) {
 54 |             var that = this
 55 |             var url = './materiels/noop.html'
 56 |             $('
').load(url, function(responseText /*, textStatus, jqXHR*/ ) { 57 | expect(responseText).to.be.ok 58 | that.test.title += url + ' => ' + responseText 59 | done() 60 | }) 61 | }) 62 | }) 63 | describe('jQuery.ajax() XHR Fields', function() { 64 | it('', function(done) { 65 | var that = this 66 | var url = Math.random() 67 | var xhr 68 | $.ajax({ 69 | xhr: function() { 70 | xhr = $.ajaxSettings.xhr() 71 | return xhr 72 | }, 73 | url: url, 74 | dataType: 'json', 75 | xhrFields: { 76 | timeout: 123, 77 | withCredentials: true 78 | } 79 | }).done(function( /*data, textStatus, jqXHR*/ ) { 80 | // 不会进入 81 | }).fail(function(jqXHR /*, textStatus, errorThrown*/ ) { 82 | // 浏览器 || PhantomJS 83 | expect([404, 0]).to.include(jqXHR.status) 84 | that.test.title += url + ' => ' + jqXHR.status 85 | expect(xhr.timeout).to.be.equal(123) 86 | expect(xhr.withCredentials).to.be.equal(true) 87 | }).always(function() { 88 | done() 89 | }) 90 | }) 91 | }) 92 | 93 | describe('Mock.mock( rurl, template )', function() { 94 | it('', function(done) { 95 | var that = this 96 | var url = 'rurl_template.json' 97 | 98 | Mock.mock(/rurl_template.json/, { 99 | 'list|1-10': [{ 100 | 'id|+1': 1, 101 | 'email': '@EMAIL' 102 | }] 103 | }) 104 | 105 | Mock.setup({ 106 | // timeout: 100, 107 | timeout: '10-50', 108 | }) 109 | $.ajax({ 110 | url: url, 111 | dataType: 'json' 112 | }).done(function(data /*, textStatus, jqXHR*/ ) { 113 | that.test.title += url + ' => ' + stringify(data) 114 | expect(data).to.have.property('list') 115 | .that.be.an('array').with.length.within(1, 10) 116 | _.each(data.list, function(item, index, list) { 117 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 118 | }) 119 | }).fail(function(jqXHR, textStatus, errorThrown) { 120 | console.log(jqXHR, textStatus, errorThrown) 121 | }).always(function() { 122 | done() 123 | }) 124 | }) 125 | }) 126 | 127 | describe('Mock.mock( rurl, function(options) )', function() { 128 | it('', function(done) { 129 | var that = this 130 | var url = 'rurl_function.json' 131 | 132 | Mock.mock(/rurl_function\.json/, function(options) { 133 | expect(options).to.not.equal(undefined) 134 | expect(options.url).to.be.equal(url) 135 | expect(options.type).to.be.equal('GET') 136 | expect(options.body).to.be.equal(null) 137 | return Mock.mock({ 138 | 'list|1-10': [{ 139 | 'id|+1': 1, 140 | 'email': '@EMAIL' 141 | }] 142 | }) 143 | }) 144 | 145 | $.ajax({ 146 | url: url, 147 | dataType: 'json' 148 | }).done(function(data /*, status, jqXHR*/ ) { 149 | that.test.title += url + ' => ' + stringify(data) 150 | expect(data).to.have.property('list') 151 | .that.be.an('array').with.length.within(1, 10) 152 | _.each(data.list, function(item, index, list) { 153 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 154 | }) 155 | }).fail(function(jqXHR, textStatus, errorThrown) { 156 | console.log(jqXHR, textStatus, errorThrown) 157 | }).always(function() { 158 | done() 159 | }) 160 | }) 161 | }) 162 | 163 | describe('Mock.mock( rurl, function(options) ) + GET + data', function() { 164 | it('', function(done) { 165 | var that = this 166 | var url = 'rurl_function.json' 167 | 168 | Mock.mock(/rurl_function\.json/, function(options) { 169 | expect(options).to.not.equal(undefined) 170 | expect(options.url).to.be.equal(url + '?foo=1') 171 | expect(options.type).to.be.equal('GET') 172 | expect(options.body).to.be.equal(null) 173 | return Mock.mock({ 174 | 'list|1-10': [{ 175 | 'id|+1': 1, 176 | 'email': '@EMAIL' 177 | }] 178 | }) 179 | }) 180 | 181 | $.ajax({ 182 | url: url, 183 | dataType: 'json', 184 | data: { 185 | foo: 1 186 | } 187 | }).done(function(data /*, status, jqXHR*/ ) { 188 | that.test.title += url + ' => ' + stringify(data) 189 | expect(data).to.have.property('list') 190 | .that.be.an('array').with.length.within(1, 10) 191 | _.each(data.list, function(item, index, list) { 192 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 193 | }) 194 | }).fail(function(jqXHR, textStatus, errorThrown) { 195 | console.log(jqXHR, textStatus, errorThrown) 196 | }).always(function() { 197 | done() 198 | }) 199 | }) 200 | }) 201 | 202 | describe('Mock.mock( rurl, function(options) ) + POST + data', function() { 203 | it('', function(done) { 204 | var that = this 205 | var url = 'rurl_function.json' 206 | 207 | Mock.mock(/rurl_function\.json/, function(options) { 208 | expect(options).to.not.equal(undefined) 209 | expect(options.url).to.be.equal(url) 210 | expect(options.type).to.be.equal('POST') 211 | expect(options.body).to.be.equal('foo=1') 212 | return Mock.mock({ 213 | 'list|1-10': [{ 214 | 'id|+1': 1, 215 | 'email': '@EMAIL' 216 | }] 217 | }) 218 | }) 219 | 220 | $.ajax({ 221 | url: url, 222 | type: 'post', 223 | dataType: 'json', 224 | data: { 225 | foo: 1 226 | } 227 | }).done(function(data /*, status, jqXHR*/ ) { 228 | that.test.title += url + ' => ' + stringify(data) 229 | expect(data).to.have.property('list') 230 | .that.be.an('array').with.length.within(1, 10) 231 | _.each(data.list, function(item, index, list) { 232 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 233 | }) 234 | }).fail(function(jqXHR, textStatus, errorThrown) { 235 | console.log(jqXHR, textStatus, errorThrown) 236 | }).always(function() { 237 | done() 238 | }) 239 | }) 240 | }) 241 | 242 | describe('Mock.mock( rurl, rtype, template )', function() { 243 | it('', function(done) { 244 | var that = this 245 | var url = 'rurl_rtype_template.json' 246 | var count = 0 247 | 248 | Mock.mock(/rurl_rtype_template\.json/, 'get', { 249 | 'list|1-10': [{ 250 | 'id|+1': 1, 251 | 'email': '@EMAIL', 252 | type: 'get' 253 | }] 254 | }) 255 | Mock.mock(/rurl_rtype_template\.json/, 'post', { 256 | 'list|1-10': [{ 257 | 'id|+1': 1, 258 | 'email': '@EMAIL', 259 | type: 'post' 260 | }] 261 | }) 262 | 263 | $.ajax({ 264 | url: url, 265 | type: 'get', 266 | dataType: 'json' 267 | }).done(function(data /*, status, jqXHR*/ ) { 268 | that.test.title += 'GET ' + url + ' => ' + stringify(data) + ' ' 269 | expect(data).to.have.property('list') 270 | .that.be.an('array').with.length.within(1, 10) 271 | _.each(data.list, function(item, index, list) { 272 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 273 | expect(item).to.have.property('type').equal('get') 274 | }) 275 | }).done(success).always(complete) 276 | 277 | $.ajax({ 278 | url: url, 279 | type: 'post', 280 | dataType: 'json' 281 | }).done(function(data /*, status, jqXHR*/ ) { 282 | that.test.title += 'POST ' + url + ' => ' + stringify(data) + ' ' 283 | expect(data).to.have.property('list') 284 | .that.be.an('array').with.length.within(1, 10) 285 | _.each(data.list, function(item, index, list) { 286 | if (index > 0) expect(item.id).to.be.equal(list[index - 1].id + 1) 287 | expect(item).to.have.property('type').equal('post') 288 | }) 289 | }).done(success).always(complete) 290 | 291 | function success( /*data*/ ) { 292 | count++ 293 | } 294 | 295 | function complete() { 296 | if (count === 2) done() 297 | } 298 | 299 | }) 300 | }) 301 | 302 | describe('Mock.mock( rurl, rtype, function(options) )', function() { 303 | it('', function(done) { 304 | var that = this 305 | var url = 'rurl_rtype_function.json' 306 | var count = 0 307 | 308 | Mock.mock(/rurl_rtype_function\.json/, /get/, function(options) { 309 | expect(options).to.not.equal(undefined) 310 | expect(options.url).to.be.equal(url) 311 | expect(options.type).to.be.equal('GET') 312 | expect(options.body).to.be.equal(null) 313 | return { 314 | type: 'get' 315 | } 316 | }) 317 | Mock.mock(/rurl_rtype_function\.json/, /post|put/, function(options) { 318 | expect(options).to.not.equal(undefined) 319 | expect(options.url).to.be.equal(url) 320 | expect(['POST', 'PUT']).to.include(options.type) 321 | expect(options.body).to.be.equal(null) 322 | return { 323 | type: options.type.toLowerCase() 324 | } 325 | }) 326 | 327 | $.ajax({ 328 | url: url, 329 | type: 'get', 330 | dataType: 'json' 331 | }).done(function(data /*, status, jqXHR*/ ) { 332 | that.test.title += 'GET ' + url + ' => ' + stringify(data) 333 | expect(data).to.have.property('type', 'get') 334 | }).done(success).always(complete) 335 | 336 | $.ajax({ 337 | url: url, 338 | type: 'post', 339 | dataType: 'json' 340 | }).done(function(data /*, status, jqXHR*/ ) { 341 | that.test.title += 'POST ' + url + ' => ' + stringify(data) 342 | expect(data).to.have.property('type', 'post') 343 | }).done(success).always(complete) 344 | 345 | $.ajax({ 346 | url: url, 347 | type: 'put', 348 | dataType: 'json' 349 | }).done(function(data /*, status, jqXHR*/ ) { 350 | that.test.title += 'PUT ' + url + ' => ' + stringify(data) 351 | expect(data).to.have.property('type', 'put') 352 | }).done(success).always(complete) 353 | 354 | 355 | function success( /*data*/ ) { 356 | count++ 357 | } 358 | 359 | function complete() { 360 | if (count === 3) done() 361 | } 362 | 363 | }) 364 | }) 365 | describe('Mock.mock( rurl, rtype, function(options) ) + data', function() { 366 | it('', function(done) { 367 | var that = this 368 | var url = 'rurl_rtype_function.json' 369 | var count = 0 370 | 371 | Mock.mock(/rurl_rtype_function\.json/, /get/, function(options) { 372 | expect(options).to.not.equal(undefined) 373 | expect(options.url).to.be.equal(url + '?foo=1') 374 | expect(options.type).to.be.equal('GET') 375 | expect(options.body).to.be.equal(null) 376 | return { 377 | type: 'get' 378 | } 379 | }) 380 | Mock.mock(/rurl_rtype_function\.json/, /post|put/, function(options) { 381 | expect(options).to.not.equal(undefined) 382 | expect(options.url).to.be.equal(url) 383 | expect(['POST', 'PUT']).to.include(options.type) 384 | expect(options.body).to.be.equal('foo=1') 385 | return { 386 | type: options.type.toLowerCase() 387 | } 388 | }) 389 | 390 | $.ajax({ 391 | url: url, 392 | type: 'get', 393 | dataType: 'json', 394 | data: { 395 | foo: 1 396 | } 397 | }).done(function(data /*, status, jqXHR*/ ) { 398 | that.test.title += 'GET ' + url + ' => ' + stringify(data) 399 | expect(data).to.have.property('type', 'get') 400 | }).done(success).always(complete) 401 | 402 | $.ajax({ 403 | url: url, 404 | type: 'post', 405 | dataType: 'json', 406 | data: { 407 | foo: 1 408 | } 409 | }).done(function(data /*, status, jqXHR*/ ) { 410 | that.test.title += 'POST ' + url + ' => ' + stringify(data) 411 | expect(data).to.have.property('type', 'post') 412 | }).done(success).always(complete) 413 | 414 | $.ajax({ 415 | url: url, 416 | type: 'put', 417 | dataType: 'json', 418 | data: { 419 | foo: 1 420 | } 421 | }).done(function(data /*, status, jqXHR*/ ) { 422 | that.test.title += 'PUT ' + url + ' => ' + stringify(data) 423 | expect(data).to.have.property('type', 'put') 424 | }).done(success).always(complete) 425 | 426 | 427 | function success( /*data*/ ) { 428 | count++ 429 | } 430 | 431 | function complete() { 432 | if (count === 3) done() 433 | } 434 | 435 | }) 436 | }) 437 | describe('#105 addEventListener', function() { 438 | it('addEventListene => addEventListener', function(done) { 439 | var xhr = new Mock.XHR() 440 | expect(xhr.addEventListener).to.not.equal(undefined) 441 | expect(xhr.addEventListene).to.equal(undefined) 442 | done() 443 | }) 444 | }) 445 | }) -------------------------------------------------------------------------------- /test/test.mock.random.js: -------------------------------------------------------------------------------- 1 | /* global require, chai, describe, before, it */ 2 | /* global window */ 3 | // 数据占位符定义(Data Placeholder Definition,DPD) 4 | var expect = chai.expect 5 | var Mock, Random, $, _, Random 6 | 7 | /* jshint -W061 */ 8 | describe('Random', function() { 9 | before(function(done) { 10 | require(['mock', 'underscore', 'jquery'], function() { 11 | Mock = arguments[0] 12 | window.Random = Random = Mock.Random 13 | _ = arguments[1] 14 | $ = arguments[2] 15 | expect(Mock).to.not.equal(undefined) 16 | expect(_).to.not.equal(undefined) 17 | expect($).to.not.equal(undefined) 18 | done() 19 | }) 20 | }) 21 | 22 | function stringify(json) { 23 | return JSON.stringify(json /*, null, 4*/ ) 24 | } 25 | 26 | function doit(expression, validator) { 27 | it('', function() { 28 | // for (var i = 0; i < 1; i++) {} 29 | var data = eval(expression) 30 | validator(data) 31 | this.test.title = stringify(expression) + ' => ' + stringify(data) 32 | }) 33 | } 34 | 35 | describe('Basic', function() { 36 | doit('Random.boolean()', function(data) { 37 | expect(data).to.be.a('boolean') 38 | }) 39 | 40 | doit('Random.natural()', function(data) { 41 | expect(data).to.be.a('number').within(0, 9007199254740992) 42 | }) 43 | doit('Random.natural(1, 3)', function(data) { 44 | expect(data).to.be.a('number').within(1, 3) 45 | }) 46 | doit('Random.natural(1)', function(data) { 47 | expect(data).to.be.a('number').least(1) 48 | }) 49 | 50 | doit('Random.integer()', function(data) { 51 | expect(data).to.be.a('number').within(-9007199254740992, 9007199254740992) 52 | }) 53 | doit('Random.integer(-10, 10)', function(data) { 54 | expect(data).to.be.a('number').within(-10, 10) 55 | }) 56 | 57 | // 1 整数部分 2 小数部分 58 | var RE_FLOAT = /(\-?\d+)\.?(\d+)?/ 59 | 60 | function validFloat(float, min, max, dmin, dmax) { 61 | RE_FLOAT.lastIndex = 0 62 | var parts = RE_FLOAT.exec(float + '') 63 | 64 | expect(+parts[1]).to.be.a('number').within(min, max) 65 | 66 | /* jshint -W041 */ 67 | if (parts[2] != undefined) { 68 | expect(parts[2]).to.have.length.within(dmin, dmax) 69 | } 70 | } 71 | 72 | doit('Random.float()', function(data) { 73 | validFloat(data, -9007199254740992, 9007199254740992, 0, 17) 74 | }) 75 | doit('Random.float(0)', function(data) { 76 | validFloat(data, 0, 9007199254740992, 0, 17) 77 | }) 78 | doit('Random.float(60, 100)', function(data) { 79 | validFloat(data, 60, 100, 0, 17) 80 | }) 81 | doit('Random.float(60, 100, 3)', function(data) { 82 | validFloat(data, 60, 100, 3, 17) 83 | }) 84 | doit('Random.float(60, 100, 3, 5)', function(data) { 85 | validFloat(data, 60, 100, 3, 5) 86 | }) 87 | 88 | var CHARACTER_LOWER = 'abcdefghijklmnopqrstuvwxyz' 89 | var CHARACTER_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 90 | var CHARACTER_NUMBER = '0123456789' 91 | var CHARACTER_SYMBOL = '!@#$%^&*()[]' 92 | doit('Random.character()', function(data) { 93 | expect(data).to.be.a('string').with.length(1) 94 | expect( 95 | CHARACTER_LOWER + 96 | CHARACTER_UPPER + 97 | CHARACTER_NUMBER + 98 | CHARACTER_SYMBOL 99 | ).to.include(data) 100 | }) 101 | doit('Random.character("lower")', function(data) { 102 | expect(data).to.be.a('string').with.length(1) 103 | expect(CHARACTER_LOWER).to.include(data) 104 | }) 105 | doit('Random.character("upper")', function(data) { 106 | expect(data).to.be.a('string').with.length(1) 107 | expect(CHARACTER_UPPER).to.include(data) 108 | }) 109 | doit('Random.character("number")', function(data) { 110 | expect(data).to.be.a('string').with.length(1) 111 | expect(CHARACTER_NUMBER).to.include(data) 112 | }) 113 | doit('Random.character("symbol")', function(data) { 114 | expect(data).to.be.a('string').with.length(1) 115 | expect(CHARACTER_SYMBOL).to.include(data) 116 | }) 117 | doit('Random.character("aeiou")', function(data) { 118 | expect(data).to.be.a('string').with.length(1) 119 | expect('aeiou').to.include(data) 120 | }) 121 | 122 | doit('Random.string()', function(data) { 123 | expect(data).to.be.a('string').with.length.within(3, 7) 124 | }) 125 | doit('Random.string(5)', function(data) { 126 | expect(data).to.be.a('string').with.length(5) 127 | }) 128 | doit('Random.string("lower", 5)', function(data) { 129 | expect(data).to.be.a('string').with.length(5) 130 | for (var i = 0; i < data.length; i++) { 131 | expect(CHARACTER_LOWER).to.include(data[i]) 132 | } 133 | }) 134 | doit('Random.string(7, 10)', function(data) { 135 | expect(data).to.be.a('string').with.length.within(7, 10) 136 | }) 137 | doit('Random.string("aeiou", 1, 3)', function(data) { 138 | expect(data).to.be.a('string').with.length.within(1, 3) 139 | for (var i = 0; i < data.length; i++) { 140 | expect('aeiou').to.include(data[i]) 141 | } 142 | }) 143 | 144 | doit('Random.range(10)', function(data) { 145 | expect(data).to.be.an('array').with.length(10) 146 | }) 147 | doit('Random.range(3, 7)', function(data) { 148 | expect(data).to.be.an('array').deep.equal([3, 4, 5, 6]) 149 | }) 150 | doit('Random.range(1, 10, 2)', function(data) { 151 | expect(data).to.be.an('array').deep.equal([1, 3, 5, 7, 9]) 152 | }) 153 | doit('Random.range(1, 10, 3)', function(data) { 154 | expect(data).to.be.an('array').deep.equal([1, 4, 7]) 155 | }) 156 | 157 | var RE_DATE = /\d{4}-\d{2}-\d{2}/ 158 | var RE_TIME = /\d{2}:\d{2}:\d{2}/ 159 | var RE_DATETIME = new RegExp(RE_DATE.source + ' ' + RE_TIME.source) 160 | 161 | doit('Random.date()', function(data) { 162 | expect(RE_DATE.test(data)).to.be.true 163 | }) 164 | 165 | doit('Random.time()', function(data) { 166 | expect(RE_TIME.test(data)).to.be.true 167 | }) 168 | 169 | doit('Random.datetime()', function(data) { 170 | expect(RE_DATETIME.test(data)).to.be.true 171 | }) 172 | doit('Random.datetime("yyyy-MM-dd A HH:mm:ss")', function(data) { 173 | expect(data).to.be.ok 174 | }) 175 | doit('Random.datetime("yyyy-MM-dd a HH:mm:ss")', function(data) { 176 | expect(data).to.be.ok 177 | }) 178 | doit('Random.datetime("yy-MM-dd HH:mm:ss")', function(data) { 179 | expect(data).to.be.ok 180 | }) 181 | doit('Random.datetime("y-MM-dd HH:mm:ss")', function(data) { 182 | expect(data).to.be.ok 183 | }) 184 | doit('Random.datetime("y-M-d H:m:s")', function(data) { 185 | expect(data).to.be.ok 186 | }) 187 | doit('Random.datetime("yyyy yy y MM M dd d HH H hh h mm m ss s SS S A a T")', function(data) { 188 | expect(data).to.be.ok 189 | }) 190 | 191 | doit('Random.now()', function(data) { 192 | expect(data).to.be.ok 193 | }) 194 | doit('Random.now("year")', function(data) { 195 | expect(data).to.be.ok 196 | }) 197 | doit('Random.now("month")', function(data) { 198 | expect(data).to.be.ok 199 | }) 200 | doit('Random.now("day")', function(data) { 201 | expect(data).to.be.ok 202 | }) 203 | doit('Random.now("hour")', function(data) { 204 | expect(data).to.be.ok 205 | }) 206 | doit('Random.now("minute")', function(data) { 207 | expect(data).to.be.ok 208 | }) 209 | doit('Random.now("second")', function(data) { 210 | expect(data).to.be.ok 211 | }) 212 | doit('Random.now("week")', function(data) { 213 | expect(data).to.be.ok 214 | }) 215 | doit('Random.now("yyyy-MM-dd HH:mm:ss SS")', function(data) { 216 | expect(data).to.be.ok 217 | }) 218 | }) 219 | 220 | describe('Image', function() { 221 | doit('Random.image()', function(data) { 222 | expect(data).to.be.ok 223 | }) 224 | it('Random.dataImage()', function() { 225 | var data = eval(this.test.title) 226 | expect(data).to.be.ok 227 | this.test.title = stringify(this.test.title) + ' => ' 228 | }) 229 | it('Random.dataImage("200x100")', function() { 230 | var data = eval(this.test.title) 231 | expect(data).to.be.ok 232 | this.test.title = stringify(this.test.title) + ' => ' 233 | }) 234 | it('Random.dataImage("200x100", "Hello Mock.js!")', function() { 235 | var data = eval(this.test.title) 236 | expect(data).to.be.ok 237 | this.test.title = stringify(this.test.title) + ' => ' 238 | }) 239 | }) 240 | 241 | var RE_COLOR = /^#[0-9a-fA-F]{6}$/ 242 | var RE_COLOR_RGB = /^rgb\(\d{1,3}, \d{1,3}, \d{1,3}\)$/ 243 | var RE_COLOR_RGBA = /^rgba\(\d{1,3}, \d{1,3}, \d{1,3}, 0\.\d{1,2}\)$/ 244 | var RE_COLOR_HSL = /^hsl\(\d{1,3}, \d{1,3}, \d{1,3}\)$/ 245 | describe('Color', function() { 246 | doit('Random.color()', function(data) { 247 | expect(RE_COLOR.test(data)).to.true 248 | }) 249 | doit('Random.hex()', function(data) { 250 | expect(RE_COLOR.test(data)).to.true 251 | }) 252 | doit('Random.rgb()', function(data) { 253 | expect(RE_COLOR_RGB.test(data)).to.true 254 | }) 255 | doit('Random.rgba()', function(data) { 256 | expect(RE_COLOR_RGBA.test(data)).to.true 257 | }) 258 | doit('Random.hsl()', function(data) { 259 | expect(RE_COLOR_HSL.test(data)).to.true 260 | }) 261 | }) 262 | 263 | describe('Text', function() { 264 | doit('Random.paragraph()', function(data) { 265 | expect(data.split('.').length - 1).to.within(3, 7) 266 | }) 267 | doit('Random.paragraph(2)', function(data) { 268 | expect(data.split('.').length - 1).to.equal(2) 269 | }) 270 | doit('Random.paragraph(1, 3)', function(data) { 271 | expect(data.split('.').length - 1).to.within(1, 3) 272 | }) 273 | 274 | doit('Random.sentence()', function(data) { 275 | expect(data[0]).to.equal(data.toUpperCase()[0]) 276 | expect(data.split(' ').length).to.within(12, 18) 277 | }) 278 | doit('Random.sentence(4)', function(data) { 279 | expect(data[0]).to.equal(data.toUpperCase()[0]) 280 | expect(data.split(' ').length).to.equal(4) 281 | }) 282 | doit('Random.sentence(3, 5)', function(data) { 283 | expect(data[0]).to.equal(data.toUpperCase()[0]) 284 | expect(data.split(' ').length).to.within(3, 5) 285 | }) 286 | 287 | doit('Random.word()', function(data) { 288 | expect(data).to.have.length.within(3, 10) 289 | }) 290 | doit('Random.word(4)', function(data) { 291 | expect(data).to.have.length(4) 292 | }) 293 | doit('Random.word(3, 5)', function(data) { 294 | expect(data).to.have.length.within(3, 5) 295 | }) 296 | 297 | doit('Random.title()', function(data) { 298 | var words = data.split(' ') 299 | _.each(words, function(word) { 300 | expect(word[0]).to.equal(word[0].toUpperCase()) 301 | }) 302 | expect(words).to.have.length.within(3, 7) 303 | }) 304 | doit('Random.title(4)', function(data) { 305 | var words = data.split(' ') 306 | _.each(words, function(word) { 307 | expect(word[0]).to.equal(word[0].toUpperCase()) 308 | }) 309 | expect(words).to.have.length(4) 310 | }) 311 | doit('Random.title(3, 5)', function(data) { 312 | var words = data.split(' ') 313 | _.each(words, function(word) { 314 | expect(word[0]).to.equal(word[0].toUpperCase()) 315 | }) 316 | expect(words).to.have.length.within(3, 5) 317 | }) 318 | }) 319 | 320 | describe('Name', function() { 321 | doit('Random.first()', function(data) { 322 | expect(data[0]).to.equal(data[0].toUpperCase()) 323 | }) 324 | doit('Random.last()', function(data) { 325 | expect(data[0]).to.equal(data[0].toUpperCase()) 326 | }) 327 | doit('Random.name()', function(data) { 328 | var words = data.split(' ') 329 | expect(words).to.have.length(2) 330 | expect(words[0][0]).to.equal(words[0][0].toUpperCase()) 331 | expect(words[1][0]).to.equal(words[1][0].toUpperCase()) 332 | }) 333 | doit('Random.name(true)', function(data) { 334 | var words = data.split(' ') 335 | expect(words).to.have.length(3) 336 | expect(words[0][0]).to.equal(words[0][0].toUpperCase()) 337 | expect(words[1][0]).to.equal(words[1][0].toUpperCase()) 338 | expect(words[2][0]).to.equal(words[2][0].toUpperCase()) 339 | }) 340 | 341 | doit('Random.cfirst()', function(data) { 342 | expect(data).to.be.ok 343 | }) 344 | doit('Random.clast()', function(data) { 345 | expect(data).to.be.ok 346 | }) 347 | doit('Random.cname()', function(data) { 348 | expect(data).to.be.ok 349 | }) 350 | }) 351 | 352 | var RE_URL = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/ 353 | var RE_IP = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ 354 | describe('Web', function() { 355 | doit('Random.url()', function(data) { 356 | expect(RE_URL.test(data)).to.be.true 357 | }) 358 | doit('Random.domain()', function(data) { 359 | expect(data).to.be.ok 360 | }) 361 | doit('Random.domain("com")', function(data) { 362 | expect(data).to.include('.com') 363 | }) 364 | doit('Random.tld()', function(data) { 365 | expect(data).to.be.ok 366 | }) 367 | 368 | doit('Random.email()', function(data) { 369 | expect(data).to.be.ok 370 | }) 371 | doit('Random.email("nuysoft.com")', function(data) { 372 | expect(data).to.include('@nuysoft.com') 373 | }) 374 | doit('Random.ip()', function(data) { 375 | expect(RE_IP.test(data)).to.be.true 376 | }) 377 | }) 378 | describe('Address', function() { 379 | doit('Random.region()', function(data) { 380 | expect(data).to.be.ok 381 | }) 382 | doit('Random.province()', function(data) { 383 | expect(data).to.be.ok 384 | }) 385 | doit('Random.city()', function(data) { 386 | expect(data).to.be.ok 387 | }) 388 | doit('Random.city(true)', function(data) { 389 | expect(data).to.be.ok 390 | }) 391 | doit('Random.county()', function(data) { 392 | expect(data).to.be.ok 393 | }) 394 | doit('Random.county(true)', function(data) { 395 | expect(data).to.be.ok 396 | }) 397 | doit('Random.zip()', function(data) { 398 | expect(data).to.be.ok 399 | }) 400 | }) 401 | describe('Helpers', function() { 402 | doit('Random.capitalize()', function(data) { 403 | expect(data).to.equal('Undefined') 404 | }) 405 | doit('Random.capitalize("hello")', function(data) { 406 | expect(data).to.equal('Hello') 407 | }) 408 | 409 | doit('Random.upper()', function(data) { 410 | expect(data).to.equal('UNDEFINED') 411 | }) 412 | doit('Random.upper("hello")', function(data) { 413 | expect(data).to.equal('HELLO') 414 | }) 415 | 416 | doit('Random.lower()', function(data) { 417 | expect(data).to.equal('undefined') 418 | }) 419 | doit('Random.lower("HELLO")', function(data) { 420 | expect(data).to.equal('hello') 421 | }) 422 | 423 | doit('Random.pick()', function(data) { 424 | expect(data).to.be.undefined 425 | }) 426 | doit('Random.pick("a", "e", "i", "o", "u")', function(data) { 427 | expect(["a", "e", "i", "o", "u"]).to.include(data) 428 | }) 429 | doit('Random.pick(["a", "e", "i", "o", "u"])', function(data) { 430 | expect(["a", "e", "i", "o", "u"]).to.include(data) 431 | }) 432 | doit('Random.pick(["a", "e", "i", "o", "u"], 3)', function(data) { 433 | expect(data).to.be.an('array').with.length(3) 434 | }) 435 | doit('Random.pick(["a", "e", "i", "o", "u"], 1, 5)', function(data) { 436 | expect(data).to.be.an('array').with.length.within(1, 5) 437 | }) 438 | 439 | doit('Random.shuffle()', function(data) { 440 | expect(data).to.deep.equal([]) 441 | }) 442 | doit('Random.shuffle(["a", "e", "i", "o", "u"])', function(data) { 443 | expect(data.join('')).to.not.equal('aeiou') 444 | expect(data.sort().join('')).to.equal('aeiou') 445 | }) 446 | doit('Random.shuffle(["a", "e", "i", "o", "u"], 3)', function(data) { 447 | expect(data).to.be.an('array').with.length(3) 448 | }) 449 | doit('Random.shuffle(["a", "e", "i", "o", "u"], 1, 5)', function(data) { 450 | expect(data).to.be.an('array').with.length.within(1, 5) 451 | }) 452 | }) 453 | 454 | var RE_GUID = /[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12}/ 455 | describe('Miscellaneous', function() { 456 | doit('Random.guid()', function(data) { 457 | expect(data).to.be.a('string').with.length(36) 458 | expect(RE_GUID.test(data)).to.be.true 459 | }) 460 | doit('Random.id()', function(data) { 461 | expect(data).to.be.a('string').with.length(18) 462 | }) 463 | }) 464 | }) --------------------------------------------------------------------------------