├── .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 |
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 |
--------------------------------------------------------------------------------
/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 | })
--------------------------------------------------------------------------------