├── .coveralls.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── CONTRIBUTING.md
└── ISSUE_TEMPLATE
│ ├── bug-report-bug----.md
│ ├── custom-issue-template---------.md
│ └── feature-request-------.md
├── .gitignore
├── .huskyrc.js
├── .lintstagedrc
├── .travis.yml
├── LICENSE
├── README.md
├── bin
└── random
├── build
├── build.js
├── config.js
└── dev.js
├── dist
├── mock.browser.esm.js
├── mock.browser.js
├── mock.browser.min.js
├── mock.mp.d.ts
├── mock.mp.esm.d.ts
├── mock.mp.esm.js
├── mock.mp.js
└── mock.node.js
├── doc
├── .vuepress
│ ├── components
│ │ ├── code-link.vue
│ │ └── page-playground.vue
│ ├── config.js
│ ├── enhanceApp.js
│ ├── public
│ │ ├── dts
│ │ │ └── axios.d.ts
│ │ └── images
│ │ │ ├── logo-hor.png
│ │ │ └── logo-ver.png
│ └── styles
│ │ └── palette.styl
├── README.md
├── changelog
│ └── README.md
├── deploy.sh
├── document
│ ├── README.md
│ ├── miniprogram.md
│ ├── mock
│ │ └── README.md
│ ├── random
│ │ ├── address.md
│ │ ├── basic.md
│ │ ├── color.md
│ │ ├── date.md
│ │ ├── extend.md
│ │ ├── helper.md
│ │ ├── image.md
│ │ ├── miscellaneous.md
│ │ ├── name.md
│ │ ├── text.md
│ │ └── web.md
│ ├── setup
│ │ └── README.md
│ ├── syntax-specification.md
│ ├── toJSONSchema
│ │ └── README.md
│ └── valid
│ │ └── README.md
└── playground
│ └── README.md
├── package.json
├── src
├── core
│ ├── handler.ts
│ ├── mocked.ts
│ ├── parser.ts
│ ├── regexp
│ │ ├── handler.ts
│ │ ├── index.ts
│ │ └── parser.js
│ ├── schema.ts
│ ├── setting.ts
│ └── valid.ts
├── platform
│ ├── browser
│ │ ├── fetch.ts
│ │ ├── index.ts
│ │ └── xhr.ts
│ ├── mp
│ │ ├── index.ts
│ │ ├── request.ts
│ │ └── types.ts
│ └── node
│ │ └── index.ts
├── random
│ ├── address.ts
│ ├── basic.ts
│ ├── color-convert.ts
│ ├── color.ts
│ ├── date.ts
│ ├── helper.ts
│ ├── image.ts
│ ├── index.ts
│ ├── misc.ts
│ ├── name.ts
│ ├── text.ts
│ └── web.ts
├── transfer
│ └── index.ts
├── types
│ └── index.ts
└── utils
│ ├── constant.ts
│ ├── index.ts
│ └── string-to-array.ts
├── test
├── browser
│ ├── index.html
│ ├── test.coverage.js
│ ├── test.dpd.js
│ ├── test.dtd.js
│ ├── test.fetch.js
│ ├── test.mock.js
│ ├── test.random.js
│ ├── test.schema.js
│ ├── test.valid.js
│ └── test.xhr.js
├── fixtures
│ ├── files
│ │ └── logo.png
│ └── server.js
├── mp
│ ├── test.dpd.js
│ ├── test.dtd.js
│ ├── test.index.js
│ ├── test.mock.js
│ ├── test.random.js
│ ├── test.request.js
│ ├── test.schema.js
│ └── test.valid.js
└── node
│ └── test.random.js
├── tsconfig.json
└── typings
└── index.d.ts
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: hYC80YUc8T4pzzZPwmFHU9btx6kHfFNqQ
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/random/color-convert.ts
2 | src/core/regexp/parser.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | parserOptions: {
5 | ecmaVersion: 7,
6 | sourceType: 'module'
7 | },
8 | env: {
9 | es6: true,
10 | node: true,
11 | browser: true,
12 | mocha: true
13 | },
14 | extends: [
15 | 'plugin:@typescript-eslint/recommended',
16 | ],
17 | plugins: [
18 | '@typescript-eslint'
19 | ],
20 | rules: {
21 | 'key-spacing': [2, {
22 | 'beforeColon': false,
23 | 'afterColon': true
24 | }],
25 | 'keyword-spacing': [2, {
26 | 'before': true,
27 | 'after': true
28 | }],
29 | 'space-before-function-paren': 'error',
30 | 'prefer-rest-params': 'off',
31 | '@typescript-eslint/no-use-before-define': 'off',
32 | "@typescript-eslint/indent": ["error", 2],
33 | '@typescript-eslint/no-explicit-any': 'off',
34 | '@typescript-eslint/no-non-null-assertion': 'off',
35 | '@typescript-eslint/explicit-function-return-type': 'off',
36 | '@typescript-eslint/no-empty-function': 'off',
37 | '@typescript-eslint/no-inferrable-types': 'off',
38 | '@typescript-eslint/no-var-requires': 'off',
39 | '@typescript-eslint/camelcase': 'off'
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | dist/* linguist-vendored
2 | build/* linguist-vendored
3 | doc/* linguist-vendored
4 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Submitting a Pull Request
2 |
3 | 非常感谢您为 `better-mock` 做出贡献,在提交 pull request 前请花几分钟时间阅读一下这篇指南:
4 |
5 | - 在进行巨大更改之前,先开一个 issue,与维护人员讨论。
6 |
7 | - 请保持你的 pr 尽可能的小,不要将多个特性或者多个 bug 修复写到一个 pr 里。
8 |
9 | - 尽可能多的为你的 pr 填写描述信息。
10 |
11 | ## Getting started
12 |
13 | 1. Fork `better-mock`, 然后克隆仓库到本地。
14 |
15 | 2. 基于 `develop` 分支拉取新的分支,在做完更改之后,可以执行 `npm run dev` 进行调试。
16 |
17 | 3. 请确保你的更改都通过了 `test/browser/*.js` 下的单元测试。
18 |
19 | 4. 提交代码,注意不要将 `dist` 目录下的更改提交上去。
20 |
21 | 4. 提交 pr 到 `better-mock` 的 `develop` 分支。
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report-bug----.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report(Bug 模板)
3 | about: Create a report to help us improve
4 | title: "[BUG] xxxxxx"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **问题简述**
11 | 请简述你遇到的问题。
12 |
13 | **复现步骤**
14 | 请详细介绍如何复现该问题,有利于我们定位,例如:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **期望结果**
21 | 请描述您期望的结果。
22 |
23 | **截图**
24 | 如果有截图,可以贴一下截图。
25 |
26 | **环境**
27 | 请描述一下你的代码运行环境。
28 | - Env: 运行环境(浏览器、小程序还是Node.js)
29 | - OS: 客户端OS(IOS、安卓还是PC)
30 | - better-mock version: 库版本
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom-issue-template---------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template(自定义问题模板)
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request-------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request(新特性模板)
3 | about: Suggest an idea for this project
4 | title: "[Feature] xxx"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **描述一下你期望的新特性**
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .*.swp
3 | .idea
4 | .vscode
5 | node_modules
6 | bower_components
7 | npm-debug.log
8 | old-mock
9 | package-lock.json
10 | ts-dist
11 | .nyc_output
12 | coverage
13 | .dev-doc
14 | .tmp
15 | .type-coverage
16 | example
17 | doc/.vuepress/dist
18 | publish.sh
19 | test/index.html
--------------------------------------------------------------------------------
/.huskyrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | hooks: {
3 | 'pre-commit': 'lint-staged',
4 | }
5 | }
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "src/**/*.{js,ts}": ["eslint", "git add"]
3 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10.15.0
4 | branches:
5 | only:
6 | - master
7 | install:
8 | - npm install
9 |
10 | before-script:
11 | - node ./test/fixtures/server.js &
12 | script:
13 | - npm run build
14 | - npm run cover -- --allow-chrome-as-root
15 | after_success:
16 | - npm run coveralls
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 http://github.com/lavyun
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://travis-ci.org/lavyun/better-mock)
6 | [](https://coveralls.io/github/lavyun/better-mock?branch=master)
7 | 
8 | 
9 | 
10 | 
11 |
12 | ## 介绍
13 |
14 | **better-mock** fork 自 [Mock.js](https://github.com/nuysoft/Mock),使用方法和 Mock.js 一致,用于 javascript mock 数据生成,它可以拦截 `XHR` 和 `fetch` 请求,并返回自定义的数据类型。并且还支持主流小程序(微信、支付宝、头条、百度)。
15 |
16 | [文档介绍](http://lavyun.gitee.io/better-mock/)
17 |
18 | [更新日志](http://lavyun.gitee.io/better-mock/changelog/)
19 |
20 | ## 为什么有 Better-Mock ?
21 |
22 | Mock.js 是一个很好的库,国内使用者众多,虽然该库几乎已经停止维护了,但是还是有很多使用者在提 issue 和 PR,这些问题都得不到有效的解决。而且在当前时代下,Mock.js 的构建工具、代码质量都显得很陈旧,所以 `better-mock` 将会在 Mock.js 的基础上进行迭代,持续修复 Mock.js 的众多issue,支持更多的新特性。
23 |
24 | ## 特点
25 |
26 | * 100% 兼容 [Mock.js](https://github.com/nuysoft/Mock)。
27 | * 使用 `typescript` 进行重构,更好的代码提示。
28 | * 更加现代化的构建打包方案。
29 | * 更加完善的单元测试。
30 | * 支持对 `fetch` 的拦截。
31 | * 支持主流小程序(微信、支付宝、头条、百度)。
32 |
33 |
34 | ## 谁在使用
35 | - [react-admin](https://github.com/pansyjs/react-admin)
36 | 基于 react/ts/antd 的前端模板.
37 | - [mockm](https://github.com/wll8/mockm)
38 | 帮助前端快速生成或拦截基于 server 的接口和数据.
39 | - [查看更多...](https://github.com/lavyun/better-mock/network/dependents)
40 |
41 | ## 安装
42 |
43 | ```shell
44 | npm install better-mock
45 | ```
46 |
47 | ## 使用
48 |
49 | 使用 `better-mock` 代替 `mockjs`。
50 |
51 | ```js
52 | const Mock = require('better-mock')
53 | Mock.mock({
54 | 'list|1-10': [{
55 | 'id|+1': 1
56 | }]
57 | })
58 | ```
59 |
60 | ## 贡献指南
61 |
62 | 如果你想贡献自己的代码到 `better-mock`,请先仔细阅读这份[贡献指南](https://github.com/lavyun/better-mock/blob/master/.github/CONTRIBUTING.md)。
63 |
64 | ## License
65 | Mock.js is available under the terms of the [MIT License](./LICENSE).
--------------------------------------------------------------------------------
/bin/random:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | "use strict";
4 |
5 | // https://github.com/visionmedia/commander.js
6 | // http://visionmedia.github.io/commander.js/
7 | // https://github.com/visionmedia/commander.js/tree/master/examples
8 | //
9 | // sudo npm install ./ -g
10 |
11 | var path = require('path')
12 | var program = require('commander')
13 | var pkg = require(path.resolve(__dirname, '../package.json'))
14 | var Random = require('../dist/mock.node.js').Random
15 |
16 | var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m
17 | var FN_ARG_SPLIT = /,/
18 | var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg
19 | var EXCLUDE = [
20 | 'extend',
21 | 'dataImage', // mock/random/image
22 | 'capitalize', 'upper', 'lower', 'pick', 'shuffle', 'order', // mock/random/helper.js
23 | 'increment', 'inc' // mock/random/misc.js
24 | ]
25 |
26 | function parseArgs(fn) {
27 | var fnText = fn.toString().replace(STRIP_COMMENTS, '')
28 | var argDecl = fnText.match(FN_ARGS)
29 | return argDecl[1].split(FN_ARG_SPLIT).join(', ')
30 | }
31 |
32 | Object.keys(Random).forEach(function(key) {
33 | if (key[0] === '_') return
34 | if (EXCLUDE.indexOf(key) !== -1) return
35 |
36 | var fn = Random[key]
37 | if (typeof fn === 'function') {
38 | var argDecl = parseArgs(fn)
39 | if (argDecl) argDecl = '( ' + argDecl + ' )'
40 | else argDecl = '()';
41 |
42 | program
43 | .command(key)
44 | .description('Random.' + key + argDecl)
45 | .action(function() {
46 | var args = [].slice.call(arguments, 0, -1)
47 | var result = fn.apply(Random, args)
48 | console.log(result)
49 | })
50 | }
51 | })
52 |
53 | program
54 | .version(pkg.version)
55 | .on('--help', function() {
56 | console.log(' Examples:')
57 | console.log('')
58 | console.log(' $ random date yyyy-MM-dd')
59 | console.log(' $ random time HH:mm:ss')
60 | console.log('')
61 | })
62 |
63 | program.parse(process.argv)
64 |
65 | var cmd = program.args[0]
66 | if (!cmd) {
67 | process.stdout.write(program.helpInformation())
68 | program.emit('--help')
69 | process.exit()
70 | }
71 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const zlib = require('zlib')
4 | const rollup = require('rollup')
5 | const uglify = require('uglify-js')
6 |
7 | if (!fs.existsSync('dist')) {
8 | fs.mkdirSync('dist')
9 | }
10 |
11 | const builds = require('./config').getAllBuilds()
12 |
13 | build(builds)
14 |
15 | function build (builds) {
16 | let built = 0
17 | const total = builds.length
18 | const next = () => {
19 | buildEntry(builds[built]).then(() => {
20 | built++
21 | if (built < total) {
22 | next()
23 | }
24 | }).catch(logError)
25 | }
26 |
27 | next()
28 | }
29 |
30 | function buildEntry (config) {
31 | const output = config.output
32 | const { file, banner } = output
33 | const isProd = /min\.js$/.test(file)
34 | return rollup.rollup(config)
35 | .then(bundle => bundle.generate(output))
36 | .then(({ output }) => {
37 | for (let chunkOrAssert of output) {
38 | if (!chunkOrAssert.isAsset) {
39 | const code = chunkOrAssert.code
40 | if (isProd) {
41 | var minified = (banner ? banner + '\n' : '') + uglify.minify(code).code
42 | return write(file, minified, true)
43 | } else {
44 | return write(file, code)
45 | }
46 | }
47 | }
48 | })
49 | }
50 |
51 | function write (dest, code, zip) {
52 | return new Promise((resolve, reject) => {
53 | function report (extra) {
54 | console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
55 | resolve()
56 | }
57 |
58 | fs.writeFile(dest, code, err => {
59 | if (err) return reject(err)
60 | if (zip) {
61 | zlib.gzip(code, (err, zipped) => {
62 | if (err) return reject(err)
63 | report(' (gzipped: ' + getSize(zipped) + ')')
64 | })
65 | } else {
66 | report()
67 | }
68 | })
69 | })
70 | }
71 |
72 | function getSize (code) {
73 | return (code.length / 1024).toFixed(2) + 'kb'
74 | }
75 |
76 | function logError (e) {
77 | console.log(e)
78 | }
79 |
80 | function blue (str) {
81 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
82 | }
83 |
--------------------------------------------------------------------------------
/build/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const version = require('../package.json').version
3 | const replace = require('rollup-plugin-replace')
4 | const nodeResolve = require('rollup-plugin-node-resolve')
5 | const json = require('rollup-plugin-json')
6 | const typescript = require('rollup-plugin-typescript2')
7 |
8 | const resolve = p => {
9 | return path.resolve(__dirname, p)
10 | }
11 |
12 | const platform = process.argv[2]
13 |
14 | const PLATFORM_TYPES = {
15 | BROWSER: 'BROWSER',
16 | NODE: 'NODE',
17 | MP: 'MP'
18 | }
19 |
20 | const createBanner = (fileName) => {
21 | return `/*!
22 | * better-mock v${version} (${fileName})
23 | * (c) 2019-${new Date().getFullYear()} lavyun@163.com
24 | * Released under the MIT License.
25 | */
26 | `
27 | }
28 |
29 | // 浏览器 build
30 | const browserBuilds = [
31 | {
32 | platform: PLATFORM_TYPES.BROWSER,
33 | entry: resolve('../src/platform/browser/index.ts'),
34 | dest: resolve('../dist/mock.browser.js'),
35 | format: 'umd',
36 | banner: createBanner('mock.browser.js')
37 | },
38 | {
39 | platform: PLATFORM_TYPES.BROWSER,
40 | entry: resolve('../src/platform/browser/index.ts'),
41 | dest: resolve('../dist/mock.browser.min.js'),
42 | format: 'umd',
43 | banner: createBanner('mock.browser.min.js')
44 | },
45 | {
46 | platform: PLATFORM_TYPES.BROWSER,
47 | entry: resolve('../src/platform/browser/index.ts'),
48 | dest: resolve('../dist/mock.browser.esm.js'),
49 | format: 'es',
50 | banner: createBanner('mock.browser.esm.js')
51 | }
52 | ]
53 |
54 | // node build
55 | const nodeBuilds = [
56 | {
57 | platform: PLATFORM_TYPES.NODE,
58 | entry: resolve('../src/platform/node/index.ts'),
59 | dest: resolve('../dist/mock.node.js'),
60 | format: 'cjs',
61 | banner: createBanner('mock.node.js')
62 | }
63 | ]
64 |
65 | // 小程序 build
66 | const mpBuilds = [
67 | {
68 | platform: PLATFORM_TYPES.MP,
69 | entry: resolve('../src/platform/mp/index.ts'),
70 | dest: resolve('../dist/mock.mp.js'),
71 | format: 'umd',
72 | banner: createBanner('mock.mp.js')
73 | },
74 | {
75 | platform: PLATFORM_TYPES.MP,
76 | entry: resolve('../src/platform/mp/index.ts'),
77 | dest: resolve('../dist/mock.mp.esm.js'),
78 | format: 'es',
79 | banner: createBanner('mock.mp.esm.js')
80 | }
81 | ]
82 |
83 | const platformMap = {
84 | browser: browserBuilds,
85 | node: nodeBuilds,
86 | mp: mpBuilds
87 | }
88 |
89 | const builds = platform
90 | ? platformMap[platform] || browserBuilds
91 | : [
92 | ...browserBuilds,
93 | ...nodeBuilds,
94 | ...mpBuilds
95 | ]
96 |
97 | const genConfig = (opts) => {
98 | return {
99 | input: opts.entry,
100 | plugins: [
101 | typescript(),
102 | nodeResolve(),
103 | replace({
104 | '__VERSION__': version,
105 | 'process.env.PLATFORM_BROWSER': opts.platform === PLATFORM_TYPES.BROWSER,
106 | 'process.env.PLATFORM_NODE': opts.platform === PLATFORM_TYPES.NODE,
107 | 'process.env.PLATFORM_MP': opts.platform === PLATFORM_TYPES.MP,
108 | }),
109 | json()
110 | ],
111 | output: {
112 | name: 'Mock',
113 | file: opts.dest,
114 | format: opts.format,
115 | banner: opts.banner
116 | },
117 | onwarn: function (error) {
118 | if (error.code === 'THIS_IS_UNDEFINED') {
119 | return
120 | }
121 | }
122 | }
123 | }
124 |
125 | const getAllBuilds = () => builds.map(genConfig)
126 |
127 | exports.getAllBuilds = getAllBuilds
128 |
--------------------------------------------------------------------------------
/build/dev.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const rollup = require('rollup')
3 | const spinner = require('ora')()
4 |
5 | if (!fs.existsSync('dist')) {
6 | fs.mkdirSync('dist')
7 | }
8 |
9 | const builds = require('./config').getAllBuilds()
10 |
11 | const watcher = rollup.watch(builds[0])
12 |
13 | watcher.on('event', event => {
14 | if (event.code === 'BUNDLE_START') {
15 | spinner.start('Building!')
16 | }
17 | if (event.code === 'BUNDLE_END') {
18 | spinner.succeed('Build complete!')
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/dist/mock.mp.d.ts:
--------------------------------------------------------------------------------
1 | import Mock from '../typings'
2 | export = Mock
--------------------------------------------------------------------------------
/dist/mock.mp.esm.d.ts:
--------------------------------------------------------------------------------
1 | import Mock from '../typings'
2 | export = Mock
--------------------------------------------------------------------------------
/doc/.vuepress/components/code-link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 在线示例
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/doc/.vuepress/components/page-playground.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
clear
8 |
9 | 可以在编辑器中使用 better-mock 和 axios 的所有方法, console.log 后会在下方打印结果, 也可以F12打开控制台查看
10 |
11 |
12 |
13 |
{{ message.value }}
14 |
15 |
16 |
17 |
18 |
117 |
118 |
--------------------------------------------------------------------------------
/doc/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
2 |
3 | module.exports = {
4 | title: 'Better-Mock',
5 | description: 'Mock.js plus',
6 | base: '/better-mock/',
7 | themeConfig: {
8 | nav: [
9 | { text: '文档', link: '/document/' },
10 | { text: '练习', link: '/playground/' },
11 | { text: '更新日志', link: '/changelog/' },
12 | { text: 'Github', link: 'http://github.com/lavyun/better-mock' },
13 | ],
14 | sidebar: {
15 | '/document/': [
16 | ['/document/', '介绍'],
17 | ['/document/syntax-specification', '语法规范'],
18 | ['/document/mock/', 'Mock.mock()'],
19 | ['/document/setup/', 'Mock.setup()'],
20 | {
21 | title: 'Mock.Random',
22 | children: [
23 | ['/document/random/basic', 'basic'],
24 | ['/document/random/date', 'date'],
25 | ['/document/random/image', 'image'],
26 | ['/document/random/color', 'color'],
27 | ['/document/random/text', 'text'],
28 | ['/document/random/name', 'name'],
29 | ['/document/random/web', 'web'],
30 | ['/document/random/address', 'address'],
31 | ['/document/random/helper', 'helper'],
32 | ['/document/random/miscellaneous', 'miscellaneous'],
33 | ['/document/random/extend', '自定义扩展']
34 | ]
35 | },
36 | ['/document/valid/', 'Mock.valid()'],
37 | ['/document/toJSONSchema/', 'Mock.toJSONSchema()'],
38 | ['/document/miniprogram', '在小程序中使用']
39 | ],
40 | '/changelog/': [
41 | ''
42 | ]
43 | }
44 | },
45 | configureWebpack: (config, isServer) => {
46 | if (!isServer) {
47 | config.plugins.push(new MonacoWebpackPlugin({
48 | languages: ["typescript", "javascript", "css"],
49 | }))
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/doc/.vuepress/enhanceApp.js:
--------------------------------------------------------------------------------
1 | export default ({
2 | Vue, // VuePress 正在使用的 Vue 构造函数
3 | options, // 附加到根实例的一些选项
4 | router, // 当前应用的路由实例
5 | siteData, // 站点元数据
6 | isServer // 当前应用配置是处于 服务端渲染 或 客户端
7 | }) => {
8 | if (!isServer) {
9 | require('../../dist/mock.browser')
10 | }
11 | }
--------------------------------------------------------------------------------
/doc/.vuepress/public/dts/axios.d.ts:
--------------------------------------------------------------------------------
1 | interface AxiosTransformer {
2 | (data: any, headers?: any): any;
3 | }
4 |
5 | interface AxiosAdapter {
6 | (config: AxiosRequestConfig): AxiosPromise;
7 | }
8 |
9 | interface AxiosBasicCredentials {
10 | username: string;
11 | password: string;
12 | }
13 |
14 | interface AxiosProxyConfig {
15 | host: string;
16 | port: number;
17 | auth?: {
18 | username: string;
19 | password:string;
20 | };
21 | protocol?: string;
22 | }
23 |
24 | type Method =
25 | | 'get' | 'GET'
26 | | 'delete' | 'DELETE'
27 | | 'head' | 'HEAD'
28 | | 'options' | 'OPTIONS'
29 | | 'post' | 'POST'
30 | | 'put' | 'PUT'
31 | | 'patch' | 'PATCH'
32 | | 'link' | 'LINK'
33 | | 'unlink' | 'UNLINK'
34 |
35 | type ResponseType =
36 | | 'arraybuffer'
37 | | 'blob'
38 | | 'document'
39 | | 'json'
40 | | 'text'
41 | | 'stream'
42 |
43 | interface AxiosRequestConfig {
44 | url?: string;
45 | method?: Method;
46 | baseURL?: string;
47 | transformRequest?: AxiosTransformer | AxiosTransformer[];
48 | transformResponse?: AxiosTransformer | AxiosTransformer[];
49 | headers?: any;
50 | params?: any;
51 | paramsSerializer?: (params: any) => string;
52 | data?: any;
53 | timeout?: number;
54 | timeoutErrorMessage?: string;
55 | withCredentials?: boolean;
56 | adapter?: AxiosAdapter;
57 | auth?: AxiosBasicCredentials;
58 | responseType?: ResponseType;
59 | xsrfCookieName?: string;
60 | xsrfHeaderName?: string;
61 | onUploadProgress?: (progressEvent: any) => void;
62 | onDownloadProgress?: (progressEvent: any) => void;
63 | maxContentLength?: number;
64 | validateStatus?: (status: number) => boolean;
65 | maxRedirects?: number;
66 | socketPath?: string | null;
67 | httpAgent?: any;
68 | httpsAgent?: any;
69 | proxy?: AxiosProxyConfig | false;
70 | cancelToken?: CancelToken;
71 | }
72 |
73 | interface AxiosResponse {
74 | data: T;
75 | status: number;
76 | statusText: string;
77 | headers: any;
78 | config: AxiosRequestConfig;
79 | request?: any;
80 | }
81 |
82 | interface AxiosError extends Error {
83 | config: AxiosRequestConfig;
84 | code?: string;
85 | request?: any;
86 | response?: AxiosResponse;
87 | isAxiosError: boolean;
88 | toJSON: () => object;
89 | }
90 |
91 | interface AxiosPromise extends Promise> {
92 | }
93 |
94 | interface CancelStatic {
95 | new (message?: string): Cancel;
96 | }
97 |
98 | interface Cancel {
99 | message: string;
100 | }
101 |
102 | interface Canceler {
103 | (message?: string): void;
104 | }
105 |
106 | interface CancelTokenStatic {
107 | new (executor: (cancel: Canceler) => void): CancelToken;
108 | source(): CancelTokenSource;
109 | }
110 |
111 | interface CancelToken {
112 | promise: Promise;
113 | reason?: Cancel;
114 | throwIfRequested(): void;
115 | }
116 |
117 | interface CancelTokenSource {
118 | token: CancelToken;
119 | cancel: Canceler;
120 | }
121 |
122 | interface AxiosInterceptorManager {
123 | use(onFulfilled?: (value: V) => V | Promise, onRejected?: (error: any) => any): number;
124 | eject(id: number): void;
125 | }
126 |
127 | interface AxiosInstance {
128 | (config: AxiosRequestConfig): AxiosPromise;
129 | (url: string, config?: AxiosRequestConfig): AxiosPromise;
130 | defaults: AxiosRequestConfig;
131 | interceptors: {
132 | request: AxiosInterceptorManager;
133 | response: AxiosInterceptorManager;
134 | };
135 | getUri(config?: AxiosRequestConfig): string;
136 | request> (config: AxiosRequestConfig): Promise;
137 | get>(url: string, config?: AxiosRequestConfig): Promise;
138 | delete>(url: string, config?: AxiosRequestConfig): Promise;
139 | head>(url: string, config?: AxiosRequestConfig): Promise;
140 | options>(url: string, config?: AxiosRequestConfig): Promise;
141 | post>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
142 | put>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
143 | patch>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
144 | }
145 |
146 | interface AxiosStatic extends AxiosInstance {
147 | create(config?: AxiosRequestConfig): AxiosInstance;
148 | Cancel: CancelStatic;
149 | CancelToken: CancelTokenStatic;
150 | isCancel(value: any): boolean;
151 | all(values: (T | Promise)[]): Promise;
152 | spread(callback: (...args: T[]) => R): (array: T[]) => R;
153 | }
154 |
155 | declare const axios: AxiosStatic
156 |
157 |
--------------------------------------------------------------------------------
/doc/.vuepress/public/images/logo-hor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavyun/better-mock/5359ed68c02c7ef7d1f8a5bca4cd33160be1001c/doc/.vuepress/public/images/logo-hor.png
--------------------------------------------------------------------------------
/doc/.vuepress/public/images/logo-ver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavyun/better-mock/5359ed68c02c7ef7d1f8a5bca4cd33160be1001c/doc/.vuepress/public/images/logo-ver.png
--------------------------------------------------------------------------------
/doc/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 | .home
2 | .hero img
3 | max-height: 150px;
4 |
5 | #main-title
6 | display: none;
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroImage: /images/logo-hor.png
4 | tagline: 已全局引入 better-mock, 打开控制台进行调试!
5 | actionText: 快速上手 →
6 | actionLink: /document/
7 | features:
8 | - title: 100% 兼容 Mock.js
9 | details: 基于 Mock.js,使用 typescript 重构成更加现代化的 javascript 库。
10 | - title: 支持小程序
11 | details: 支持主流小程序:微信、支付宝、百度、头条。
12 | - title: 拦截 ajax 和 fetch 请求,开发无侵入
13 | details: 不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。
14 | - title: 持续兼容 Mock.js
15 | details: 持续解决 Mock.js 沉积的 issue。
16 | footer: MIT Licensed | Copyright © 2019-present lavyun
17 | ---
--------------------------------------------------------------------------------
/doc/changelog/README.md:
--------------------------------------------------------------------------------
1 | # 更新日志
2 |
3 | ## v0.2.0
4 |
5 | 发布日期:2020-01-03
6 |
7 | ### Features
8 |
9 | * 支持在小程序中使用。
10 |
11 | ## v0.1.3
12 |
13 | 发布日期:2019-11-29
14 |
15 | ### Features
16 |
17 | * rurl 支持通配符。[Mock.js#177](https://github.com/nuysoft/Mock/issues/177)
18 | * mock 方法的回调函数增加 headers 字段。 [Mock.js#312](https://github.com/nuysoft/Mock/issues/312)、[Mock.js#358](https://github.com/nuysoft/Mock/issues/358)
19 |
20 |
21 | ## v0.1.0
22 |
23 | 发布日期:2019-11-21
24 |
25 | ### Features
26 |
27 | * 支持对 [fetch](https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch) 的拦截。
28 |
29 | ### Optimize
30 |
31 | * 优化 `Random.address` 和 `Random.id` 对 [直筒子市](https://baike.baidu.com/item/%E7%9B%B4%E7%AD%92%E5%AD%90%E5%B8%82/7744993?fr=aladdin) 的处理。
32 | * 优化 `Random.dataImage` 在 Node 端的处理。
33 |
34 | ### Fix
35 |
36 | * 修复 `Random.image` 方法不支持带空格的文字。[Mock.js#291](https://github.com/nuysoft/Mock/issues/291)
37 |
38 |
39 | ## v0.0.3
40 |
41 | 发布日期:2019-11-17
42 |
43 | ### Features
44 |
45 | * 增加 `d.ts` 声明文件。
46 | * 新增 `Random.phone` 方法,生成一个随机的手机号。
47 |
48 | ### Optimize
49 |
50 | * 优化 `Random.dataImage` 方法,移除对 node-canvas 的依赖,改为获取远端图片的 buffer,在转为base64。
51 |
52 | ### Fix
53 |
54 | * 修复无法拦截带参数的 GET,[Mock.js#109](https://github.com/nuysoft/Mock/issues/109),
55 | * 修复 `Mock.mock` 方法中, `rtype` 无需区分大小写。即:`get`、`GET` 都可以被成功匹配到。
56 |
57 | ## v0.0.2
58 |
59 | 发布日期:2019-11-13
60 |
61 | ### Features
62 |
63 | * 新增 `Random.timestamp` 方法 [Mock.js#372](https://github.com/nuysoft/Mock/issues/372),随机生成一个时间戳。
64 | * 使用 [china-location](https://github.com/JasonBoy/china-location) 库作为省市区数据源。
65 | * 新增 `Random.version` 方法,随机生成一个版本号。
66 |
--------------------------------------------------------------------------------
/doc/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # 确保脚本抛出遇到的错误
4 | set -e
5 |
6 | # 生成静态文件
7 | npm run doc:build
8 |
9 | # 进入生成的文件夹
10 | cd doc/.vuepress/dist
11 |
12 | git init
13 | git add -A
14 | git commit -m 'deploy'
15 |
16 | git push -f git@github.com:lavyun/better-mock.git master:gh-pages
17 |
18 | cd -
--------------------------------------------------------------------------------
/doc/document/README.md:
--------------------------------------------------------------------------------
1 | # 介绍
2 |
3 | ## better-mock 是什么
4 |
5 | `better-mock` fork 了 [Mock.js](https://github.com/nuysoft/Mock),在代码实现、构建脚本、单元测试上都选择了更加现代化的技术方案进行重构,所以使用者无需更改代码,可以 `100%` 兼容Mock.js。
6 |
7 | ## 为什么会有这个库
8 |
9 | 虽然 `Mock.js` 已经很长时间已经没有维护了,但是还是会一些使用者在提 `issue`,提 `PR`,所以 `better-mock` 的规划是:重构`Mock.js`,在不改变 `Mock.js` API 的基础上进行长期迭代,并且解决一些 `Mock.js` 的 `issue` 和 `PR`。
10 |
11 | ## 一些说明
12 |
13 | `better-mock` 的定位是**开发**环境下和 **Node.js** 中的数据 mock,不会着重考虑浏览器兼容性,所以在重构 Mock.js 的过程中,移除了一些浏览器兼容性代码,但是得益于 `babel`,构建后的代码会兼容 `IE9+`。
14 |
15 | ## 安装
16 |
17 | ```shell
18 | npm install better-mock
19 | ```
20 |
21 | ## 使用
22 |
23 | ### 使用 Webpack
24 |
25 | ```js
26 | import Mock from 'better-mock'
27 | const data = Mock.mock({
28 | // 属性 list 的值是一个数组,其中含有 1 到 10 个元素
29 | 'list|1-10': [
30 | {
31 | // 属性 id 是一个自增数,起始值为 1,每次增 1
32 | 'id|+1': 1
33 | }
34 | ]
35 | })
36 | console.log(data)
37 | ```
38 |
39 | ### 使用 Node.js
40 |
41 | ```js
42 | const Mock = require('better-mock')
43 | const data = Mock.mock({
44 | 'list|1-10': [
45 | {
46 | 'id|+1': 1
47 | }
48 | ]
49 | })
50 | console.log(data)
51 | ```
52 |
53 | ### 在浏览器中直接引用
54 |
55 | ```html
56 |
57 | ```
58 |
59 | ### RequireJS (AMD)
60 |
61 | ```js
62 | require.config({
63 | paths: {
64 | mock: 'path/to/better-mock'
65 | }
66 | })
67 |
68 | require(['mock'], function(Mock) {
69 | var data = Mock.mock({
70 | 'list|1-10': [
71 | {
72 | 'id|+1': 1
73 | }
74 | ]
75 | })
76 | // 输出结果
77 | document.body.innerHTML += '' + JSON.stringify(data, null, 4) + '
'
78 | })
79 | ```
80 |
81 | ### Random CLI
82 |
83 | ```shell
84 | # 全局安装
85 | $ npm install better-mock -g
86 |
87 | # 执行
88 | $ random url
89 | # => http://rmcpx.org/funzwc
90 |
91 | # 帮助
92 | random -h
93 | ```
94 |
95 |
--------------------------------------------------------------------------------
/doc/document/miniprogram.md:
--------------------------------------------------------------------------------
1 | ## 介绍
2 |
3 | `better-mock` 会针对各个小程序平台,对其 `request` 方法进行拦截,返回自定义的 mock 数据。
4 |
5 | ## 如何使用
6 |
7 | ### 可以使用 npm 的情况
8 |
9 | 安装 `better-mock` 后直接引入 `dist/mock.mp.js` 文件:
10 |
11 | ```js
12 | const Mock = require('better-mock/dist/mock.mp.js')
13 |
14 | Mock.mock('http://example.com/path/to', {
15 | phone: '@PHONE'
16 | })
17 |
18 | wx.request({
19 | url: 'http://example.com/path/to',
20 | success (res) {
21 | console.log(res.data.phone) // 13687529123
22 | }
23 | })
24 | ```
25 |
26 | ### 不能使用 npm 的情况
27 |
28 | 如果不能使用 npm,可以下载 release 文件到本地,然后引入。
29 |
30 | [https://unpkg.com/better-mock/dist/mock.mp.js](https://unpkg.com/better-mock/dist/mock.mp.js)
--------------------------------------------------------------------------------
/doc/document/mock/README.md:
--------------------------------------------------------------------------------
1 | ## mock
2 |
3 | 根据数据模板生成模拟数据,共有 5 种参数格式。
4 |
5 | ### Mock.mock( template )
6 |
7 | 根据数据模板生成模拟数据。
8 |
9 | ```js
10 | Mock.mock({
11 | 'list|1-10': [{
12 | 'id|+1': 1,
13 | 'email': '@EMAIL'
14 | }]
15 | })
16 | ```
17 |
18 |
19 |
20 | const ret = Mock.mock({
21 | 'list|1-10': [{
22 | 'id|+1': 1,
23 | 'email': '@EMAIL'
24 | }]
25 | })
26 | console.log(ret)
27 |
28 |
29 |
30 | ### Mock.mock( url, template )
31 |
32 | 记录数据模板。当拦截到匹配 `url` 的 Ajax 请求时,将根据数据模板 `template` 生成模拟数据,并作为响应数据返回。
33 |
34 | ```js
35 | Mock.mock('/mock_url', {
36 | 'list|1-10': [{
37 | 'id|+1': 1,
38 | 'email': '@EMAIL'
39 | }]
40 | })
41 | ```
42 |
43 |
44 |
45 | Mock.mock('/mock_url', {
46 | 'list|1-10': [{
47 | 'id|+1': 1,
48 | 'email': '@EMAIL'
49 | }]
50 | })
51 | axios.get('/mock_url').then(res => {
52 | console.log(res.data)
53 | })
54 |
55 |
56 |
57 | ### Mock.mock( url, function( options ) )
58 |
59 | 记录用于生成响应数据的函数。当拦截到匹配 `url` 的 Ajax 请求时,函数 `function(options)` 将被执行,并把执行结果作为响应数据返回。
60 |
61 | ```js
62 | Mock.mock('/mock_url', function (options) {
63 | return options
64 | })
65 | ```
66 |
67 |
68 |
69 | Mock.mock('/mock_url', function (options) {
70 | return options
71 | })
72 | axios.get('/mock_url').then(res => {
73 | console.log(res.data)
74 | })
75 |
76 |
77 |
78 | ### Mock.mock( url, type, template )
79 |
80 | 记录数据模板。当拦截到匹配 `url` 和 `type` 的 Ajax 请求时,将根据数据模板 `template` 生成模拟数据,并作为响应数据返回。
81 |
82 | ```js
83 | Mock.mock('/mock_url', 'post', {
84 | 'list|1-10': [{
85 | 'id|+1': 1,
86 | 'email': '@EMAIL'
87 | }]
88 | })
89 | ```
90 |
91 |
92 |
93 | Mock.mock('/mock_url', 'post', {
94 | 'list|1-10': [{
95 | 'id|+1': 1,
96 | 'email': '@EMAIL'
97 | }]
98 | })
99 | axios.post('/mock_url').then(res => {
100 | console.log(res.data)
101 | })
102 |
103 |
104 |
105 |
106 | ### Mock.mock( url, type, function( options ) )
107 |
108 | 记录用于生成响应数据的函数。当拦截到匹配 `url` 和 `type` 的 Ajax 请求时,函数 `function(options)` 将被执行,并把执行结果作为响应数据返回。
109 |
110 | ```js
111 | Mock.mock('/mock_url', 'post', function (options) {
112 | return options
113 | })
114 | ```
115 |
116 |
117 |
118 | Mock.mock('/mock_url', 'post', function (options) {
119 | return options
120 | })
121 | axios.post('/mock_url').then(res => {
122 | console.log(res.data)
123 | })
124 |
125 |
126 |
127 |
128 | 函数可以返回一个 promise,用于异步生成数据。
129 |
130 | ```js
131 | Mock.mock('/mock_url', 'post', function (options) {
132 | return new Promise(resolve => {
133 | setTimeout(() => {
134 | resolve({
135 | value: Math.random()
136 | })
137 | }, 1000)
138 | })
139 | })
140 | ```
141 |
142 |
143 |
144 | Mock.mock('/mock_url', 'post', function (options) {
145 | return new Promise(resolve => {
146 | setTimeout(() => {
147 | resolve({
148 | value: Math.random()
149 | })
150 | }, 1000)
151 | })
152 | })
153 | axios.post('/mock_url').then(res => {
154 | console.log(res.data)
155 | })
156 |
157 |
158 |
159 | ### 参数的含义和默认值如下所示:
160 |
161 | #### url
162 |
163 | 表示需要拦截的 URL,可以是 URL 字符串、URL 通配符 或者 URL 正则。例如 `/\/domain\/list\.json/`、`'/domain/list/:id'`、`'/domain/list/*'`、`'/domain/list.json'`。
164 |
165 | #### type
166 |
167 | 表示需要拦截的 Ajax 请求类型。例如 `GET`、`POST`、`PUT`、`DELETE` 等,支持小写。
168 |
169 | #### template
170 |
171 | 表示数据模板,可以是对象或字符串。例如 `{ 'data|1-10':[{}] }`、`'@EMAIL'`。
172 |
173 | #### function(options)
174 |
175 | 表示用于生成响应数据的函数。
176 |
177 | ##### options
178 |
179 | 指向本次请求的 Ajax 选项集,含有 `url`、`type`、`body`、`headers` 四个属性。
180 |
--------------------------------------------------------------------------------
/doc/document/random/address.md:
--------------------------------------------------------------------------------
1 | ## Random.region
2 |
3 | * Random.region()
4 |
5 | 随机生成一个(中国)大区。
6 |
7 | ```js
8 | Random.region()
9 | // => "华北"
10 | ```
11 |
12 | ## Random.province
13 |
14 | * Random.province()
15 |
16 | 随机生成一个(中国)省(或直辖市、自治区、特别行政区)。
17 |
18 | ```js
19 | Random.province()
20 | // => "黑龙江省"
21 | ```
22 |
23 | ## Random.city
24 |
25 | * Random.city()
26 | * Random.city( prefix )
27 |
28 | 随机生成一个(中国)市。
29 |
30 | ### prefix
31 |
32 | 布尔值。指示是否生成所属的省。
33 |
34 | ```js
35 | Random.city()
36 | // => "唐山市"
37 | Random.city(true)
38 | // => "福建省 漳州市"
39 | ```
40 |
41 | ## Random.county
42 |
43 | * Random.county()
44 | * Random.county( prefix )
45 |
46 | 随机生成一个(中国)县。
47 |
48 | ### prefix
49 |
50 | 布尔值。指示是否生成所属的省、市。
51 |
52 | ```js
53 | Random.county()
54 | // => "上杭县"
55 | Random.county(true)
56 | // => "甘肃省 白银市 会宁县"
57 | ```
58 |
59 | ## Random.zip
60 |
61 | * Random.zip()
62 |
63 | 随机生成一个邮政编码(六位数字)。
64 |
65 | ```js
66 | Random.zip()
67 | // => "908812"
68 | ```
69 |
--------------------------------------------------------------------------------
/doc/document/random/basic.md:
--------------------------------------------------------------------------------
1 | ## Random.boolean
2 |
3 | - Random.boolean()
4 | - Random.boolean( min, max, current )
5 |
6 | 返回一个随机的布尔值。
7 |
8 | ### min
9 |
10 | 指示参数 current 出现的概率。概率计算公式为 `min / (min + max)`。该参数的默认值为 1,即有 50% 的概率返回参数 current。
11 |
12 | ### max
13 |
14 | 指示参数 current 的相反值 `!current` 出现的概率。概率计算公式为 `max / (min + max)`。该参数的默认值为 `1`,即有 50% 的概率返回参数 `!current`。
15 |
16 | ### current
17 |
18 | 可选值为布尔值 `true` 或 `false`。如果未传入任何参数,则返回 `true` 和 `false` 的概率各为 50%。该参数没有默认值。在该方法的内部,依据原生方法 Math.random() 返回的(浮点)数来计算和返回布尔值,例如在最简单的情况下,返回值是表达式 `Math.random() >= 0.5` 的执行结果。
19 |
20 | ```js
21 | Random.boolean()
22 | // => true
23 | Random.boolean(1, 9, true)
24 | // => false
25 | Random.bool()
26 | // => false
27 | Random.bool(1, 9, false)
28 | // => true
29 | ```
30 |
31 | ## Random.natural
32 |
33 | - Random.natural()
34 | - Random.natural( min )
35 | - Random.natural( min, max )
36 |
37 | 返回一个随机的自然数(大于等于 0 的整数)。
38 |
39 | ### min
40 |
41 | 指示随机自然数的最小值。默认值为 0。
42 |
43 | ### max
44 |
45 | 指示随机自然数的最大值。默认值为 9007199254740992。
46 |
47 | ```js
48 | Random.natural()
49 | // => 1002794054057984
50 | Random.natural(10000)
51 | // => 71529071126209
52 | Random.natural(60, 100)
53 | // => 77
54 | ```
55 |
56 | ## Random.integer
57 |
58 | - Random.integer()
59 | - Random.integer( min )
60 | - Random.integer( min, max )
61 |
62 | 返回一个随机的整数。
63 |
64 | ### min
65 |
66 | 指示随机整数的最小值。默认值为 -9007199254740992。
67 |
68 | ### max
69 |
70 | 指示随机整数的最大值。默认值为 9007199254740992。
71 |
72 | ```js
73 | Random.integer()
74 | // => -3815311811805184
75 | Random.integer(10000)
76 | // => 4303764511003750
77 | Random.integer(60, 100)
78 | // => 96
79 | ```
80 |
81 | ## Random.float
82 |
83 | - Random.float()
84 | - Random.float( min )
85 | - Random.float( min, max )
86 | - Random.float( min, max, dmin )
87 | - Random.float( min, max, dmin, dmax )
88 |
89 | 返回一个随机的浮点数。
90 |
91 | ### min
92 |
93 | 整数部分的最小值。默认值为 -9007199254740992。
94 |
95 | ### max
96 |
97 | 整数部分的最大值。默认值为 9007199254740992。
98 |
99 | ### dmin
100 |
101 | 小数部分位数的最小值。默认值为 0。
102 |
103 | ### dmax
104 |
105 | 小数部分位数的最大值。默认值为 17。
106 |
107 | ```js
108 | Random.float()
109 | // => -1766114241544192.8
110 | Random.float(0)
111 | // => 556530504040448.25
112 | Random.float(60, 100)
113 | // => 82.56779679549358
114 | Random.float(60, 100, 3)
115 | // => 61.718533677927894
116 | Random.float(60, 100, 3, 5)
117 | // => 70.6849
118 | ```
119 |
120 | ## Random.character
121 |
122 | - Random.character()
123 | - Random.character('lower' | 'upper' | 'number' | 'symbol' )
124 | - Random.character( pool )
125 |
126 | 返回一个随机字符。
127 |
128 | ### pool
129 |
130 | 字符串。表示字符池,将从中选择一个字符返回。
131 |
132 | 如果传入了 `'lower'` 或 `'upper'`、`'number'`、`'symbol'`,表示从内置的字符池从选取:
133 |
134 | ```js
135 | {
136 | lower: "abcdefghijklmnopqrstuvwxyz",
137 | upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
138 | number: "0123456789",
139 | symbol: "!@#$%^&*()[]"
140 | }
141 | ```
142 |
143 | 如果未传入该参数,则从 `lower + upper + number + symbol` 中随机选取一个字符返回。
144 |
145 | ```js
146 | Random.character()
147 | // => "P"
148 | Random.character('lower')
149 | // => "y"
150 | Random.character('upper')
151 | // => "X"
152 | Random.character('number')
153 | // => "1"
154 | Random.character('symbol')
155 | // => "&"
156 | ```
157 |
158 | ## Random.string
159 |
160 | - Random.string()
161 | - Random.string( length )
162 | - Random.string( pool, length )
163 | - Random.string( min, max )
164 | - Random.string( pool, min, max )
165 |
166 | 返回一个随机字符串。
167 |
168 | ### pool
169 |
170 | 字符串。表示字符池,将从中选择一个字符返回。
171 |
172 | 如果传入 `'lower'` 或 `'upper'`、`'number'`、`'symbol'`,表示从内置的字符池从选取:
173 |
174 | ```js
175 | {
176 | lower: "abcdefghijklmnopqrstuvwxyz",
177 | upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
178 | number: "0123456789",
179 | symbol: "!@#$%^&*()[]"
180 | }
181 | ```
182 |
183 | 如果未传入该参数,则从 `lower + upper + number + symbol` 中随机选取一个字符返回。
184 |
185 | ### min
186 |
187 | 随机字符串的最小长度。默认值为 3。
188 |
189 | ### max
190 |
191 | 随机字符串的最大长度。默认值为 7。
192 |
193 | ```js
194 | Random.string()
195 | // => "pJjDUe"
196 | Random.string(5)
197 | // => "GaadY"
198 | Random.string('lower', 5)
199 | // => "jseqj"
200 | Random.string(7, 10)
201 | // => "UuGQgSYk"
202 | Random.string('aeiou', 1, 3)
203 | // => "ea"
204 | Random.string('壹贰叁肆伍陆柒捌玖拾', 3, 5)
205 | // => "肆捌伍叁"
206 | ```
207 |
208 | ## Random.range
209 |
210 | - Random.range( stop )
211 | - Random.range( start, stop )
212 | - Random.range( start, stop, step )
213 |
214 | 返回一个整型数组。
215 |
216 | ### start `必选`
217 |
218 | 数组中整数的起始值。
219 |
220 | ### stop
221 |
222 | 数组中整数的结束值(不包含在返回值中)。
223 |
224 | ### step
225 |
226 | 数组中整数之间的步长。默认值为 1。
227 |
228 | ```js
229 | Random.range(10)
230 | // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
231 | Random.range(3, 7)
232 | // => [3, 4, 5, 6]
233 | Random.range(1, 10, 2)
234 | // => [1, 3, 5, 7, 9]
235 | Random.range(1, 10, 3)
236 | // => [1, 4, 7]
237 | ```
238 |
--------------------------------------------------------------------------------
/doc/document/random/color.md:
--------------------------------------------------------------------------------
1 | ## Random.color
2 |
3 | * Random.color()
4 |
5 | 随机生成一个有吸引力的颜色,格式为 '#RRGGBB'。
6 |
7 | ```js
8 | Random.color()
9 | // => "#3538B2"
10 | ```
11 |
12 | ## Random.hex
13 |
14 | * Random.hex()
15 |
16 | 随机生成一个有吸引力的颜色,格式为 '#RRGGBB'。
17 |
18 | ```js
19 | Random.hex()
20 | // => "#3538B2"
21 | ```
22 |
23 | ## Random.rgb
24 |
25 | * Random.rgb()
26 |
27 | 随机生成一个有吸引力的颜色,格式为 'rgb(r, g, b)'。
28 |
29 | ```js
30 | Random.rgb()
31 | // => "rgb(242, 198, 121)"
32 | ```
33 |
34 | ## Random.rgba
35 |
36 | * Random.rgba()
37 |
38 | 随机生成一个有吸引力的颜色,格式为 'rgba(r, g, b, a)'。
39 |
40 | ```js
41 | Random.rgba()
42 | // => "rgba(242, 198, 121, 0.13)"
43 | ```
44 |
45 | ## Random.hsl
46 |
47 | * Random.hsl()
48 |
49 | 随机生成一个有吸引力的颜色,格式为 'hsl(h, s, l)'。
50 |
51 | ```js
52 | Random.hsl()
53 | // => "hsl(345, 82, 71)"
54 | ```
55 |
--------------------------------------------------------------------------------
/doc/document/random/date.md:
--------------------------------------------------------------------------------
1 | ## Random.date
2 |
3 | * Random.date()
4 | * Random.date(format)
5 |
6 | 返回一个随机的日期字符串。
7 |
8 | ### format
9 |
10 | 指示生成的日期字符串的格式。默认值为 `yyyy-MM-dd`。
11 |
12 | 可选的占位符参考自 [Ext.Date](http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Date),如下所示:
13 |
14 | | Format | Description | Example
15 | | ------- | ----------------------------------------------------------- | --------------
16 | | yyyy | A full numeric representation of a year, 4 digits | 1999 or 2003
17 | | yy | A two digit representation of a year | 99 or 03
18 | | y | A two digit representation of a year | 99 or 03
19 | | MM | Numeric representation of a month, with leading zeros | 01 to 12
20 | | M | Numeric representation of a month, without leading zeros | 1 to 12
21 | | dd | Day of the month, 2 digits with leading zeros | 01 to 31
22 | | d | Day of the month without leading zeros | 1 to 31
23 | | HH | 24-hour format of an hour with leading zeros | 00 to 23
24 | | H | 24-hour format of an hour without leading zeros | 0 to 23
25 | | hh | 12-hour format of an hour without leading zeros | 1 to 12
26 | | h | 12-hour format of an hour with leading zeros | 01 to 12
27 | | mm | Minutes, with leading zeros | 00 to 59
28 | | m | Minutes, without leading zeros | 0 to 59
29 | | ss | Seconds, with leading zeros | 00 to 59
30 | | s | Seconds, without leading zeros | 0 to 59
31 | | SS | Milliseconds, with leading zeros | 000 to 999
32 | | S | Milliseconds, without leading zeros | 0 to 999
33 | | A | Uppercase Ante meridiem and Post meridiem | AM or PM
34 | | a | Lowercase Ante meridiem and Post meridiem | am or pm
35 | | T | Milliseconds, since 1970-1-1 00:00:00 UTC | 759883437303
36 |
37 | ```js
38 | Random.date()
39 | // => "2002-10-23"
40 | Random.date('yyyy-MM-dd')
41 | // => "1983-01-29"
42 | Random.date('yy-MM-dd')
43 | // => "79-02-14"
44 | Random.date('y-MM-dd')
45 | // => "81-05-17"
46 | Random.date('y-M-d')
47 | // => "84-6-5"
48 | ```
49 |
50 | ## Random.time
51 |
52 | * Random.time()
53 | * Random.time( format )
54 |
55 | 返回一个随机的时间字符串。
56 |
57 | ### format
58 |
59 | 指示生成的时间字符串的格式。默认值为 `HH:mm:ss`。
60 |
61 | 可选的占位符参考自 [Ext.Date](http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Date),请参见 Random.date( format? )。
62 |
63 | ```js
64 | Random.time()
65 | // => "00:14:47"
66 | Random.time('A HH:mm:ss')
67 | // => "PM 20:47:37"
68 | Random.time('a HH:mm:ss')
69 | // => "pm 17:40:00"
70 | Random.time('HH:mm:ss')
71 | // => "03:57:53"
72 | Random.time('H:m:s')
73 | // => "3:5:13"
74 | ```
75 |
76 | ## Random.datetime
77 |
78 | * Random.datetime()
79 | * Random.datetime( format )
80 |
81 | 返回一个随机的日期和时间字符串。
82 |
83 | ### format
84 |
85 | 指示生成的日期和时间字符串的格式。默认值为 `yyyy-MM-dd HH:mm:ss`。
86 |
87 | 可选的占位符参考自 [Ext.Date](http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Date),请参见 Random.date( format? )。
88 |
89 | ```js
90 | Random.datetime()
91 | // => "1977-11-17 03:50:15"
92 | Random.datetime('yyyy-MM-dd A HH:mm:ss')
93 | // => "1976-04-24 AM 03:48:25"
94 | Random.datetime('yy-MM-dd a HH:mm:ss')
95 | // => "73-01-18 pm 22:12:32"
96 | Random.datetime('y-MM-dd HH:mm:ss')
97 | // => "79-06-24 04:45:16"
98 | Random.datetime('y-M-d H:m:s')
99 | // => "02-4-23 2:49:40"
100 | ```
101 |
102 | ## Random.timestamp
103 |
104 | * Random.timestamp()
105 |
106 | 返回一个随机的时间戳。
107 |
108 | ## Random.now
109 |
110 | * Ranndom.now()
111 | * Ranndom.now( unit, format )
112 | * Ranndom.now( format )
113 | * Ranndom.now( unit )
114 |
115 | 返回当前的日期和时间字符串。
116 |
117 | ### unit
118 |
119 | 表示时间单位,用于对当前日期和时间进行格式化。可选值有:`year`、`month`、`week`、`day`、`hour`、`minute`、`second`、`week`,默认不会格式化。
120 |
121 | ### format
122 |
123 | 指示生成的日期和时间字符串的格式。默认值为 `yyyy-MM-dd HH:mm:ss`。可选的占位符参考自 [Ext.Date](http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Date),请参见 [Random.date(format)](#date)。
124 |
125 | ```js
126 | Random.now()
127 | // => "2014-04-29 20:08:38 "
128 | Random.now('day', 'yyyy-MM-dd HH:mm:ss SS')
129 | // => "2014-04-29 00:00:00 000"
130 | Random.now('day')
131 | // => "2014-04-29 00:00:00 "
132 | Random.now('yyyy-MM-dd HH:mm:ss SS')
133 | // => "2014-04-29 20:08:38 157"
134 | Random.now('year')
135 | // => "2014-01-01 00:00:00"
136 | Random.now('month')
137 | // => "2014-04-01 00:00:00"
138 | Random.now('week')
139 | // => "2014-04-27 00:00:00"
140 | Random.now('day')
141 | // => "2014-04-29 00:00:00"
142 | Random.now('hour')
143 | // => "2014-04-29 20:00:00"
144 | Random.now('minute')
145 | // => "2014-04-29 20:08:00"
146 | Random.now('second')
147 | // => "2014-04-29 20:08:38"
148 | ```
--------------------------------------------------------------------------------
/doc/document/random/extend.md:
--------------------------------------------------------------------------------
1 | ## Random.extend
2 |
3 | * Random.extend( source )
4 |
5 | Mock.Random 中的方法与数据模板的 `@占位符` 一一对应,在需要时还可以为 Mock.Random 扩展方法,然后在数据模板中通过 `@扩展方法` 引用。例如:
6 |
7 |
8 | ```js
9 | Random.extend({
10 | constellation: function(date) {
11 | const constellations = ['白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座']
12 | return this.pick(constellations)
13 | }
14 | })
15 | Random.constellation()
16 | // => "水瓶座"
17 | Mock.mock('@CONSTELLATION')
18 | // => "天蝎座"
19 | Mock.mock({
20 | constellation: '@CONSTELLATION'
21 | })
22 | // => { constellation: "射手座" }
23 | ```
--------------------------------------------------------------------------------
/doc/document/random/helper.md:
--------------------------------------------------------------------------------
1 | ## Random.capitalize
2 |
3 | * Random.capitalize(word)
4 |
5 | 把字符串的第一个字母转换为大写。
6 |
7 | ```js
8 | Random.capitalize('hello')
9 | // => "Hello"
10 | ```
11 |
12 | ## Random.upper
13 |
14 | * Random.upper( str )
15 |
16 | 把字符串转换为大写。
17 |
18 | ```js
19 | Random.upper('hello')
20 | // => "HELLO"
21 | ```
22 |
23 | ## Random.lower
24 |
25 | * Random.lower( str )
26 |
27 | 把字符串转换为小写。
28 |
29 | ```js
30 | Random.lower('HELLO')
31 | // => "hello"
32 | ```
33 |
34 | ## Random.pick
35 |
36 | * Random.pick( arr )
37 |
38 | 从数组中随机选取一个元素,并返回。
39 |
40 | ```js
41 | Random.pick(['a', 'e', 'i', 'o', 'u'])
42 | // => "o"
43 | ```
44 |
45 | ## Random.shuffle
46 |
47 | * Random.shuffle( arr )
48 | * Random.shuffle( arr, length )
49 | * Random.shuffle( arr, min, max )
50 |
51 | 打乱数组中元素的顺序,并返回。
52 |
53 | ### length
54 |
55 | 返回后的数组长度。
56 |
57 | ### min
58 |
59 | 返回后的数组最小长度。
60 |
61 | ### max
62 |
63 | 返回后的数组最大长度。
64 |
65 | ```js
66 | Random.shuffle(['a', 'e', 'i', 'o', 'u'])
67 | // => ["o", "u", "e", "i", "a"]
68 |
69 | Random.shuffle(['a', 'e', 'i', 'o', 'u'], 3)
70 | // => ["o", "u", "i"]
71 |
72 | Random.shuffle(['a', 'e', 'i', 'o', 'u'], 2, 4)
73 | // => ["o", "u"]
74 | ```
75 |
--------------------------------------------------------------------------------
/doc/document/random/miscellaneous.md:
--------------------------------------------------------------------------------
1 | ## Random.guid
2 |
3 | * Random.guid()
4 |
5 | 随机生成一个 GUID。
6 |
7 | ```js
8 | Random.guid()
9 | // => "662C63B4-FD43-66F4-3328-C54E3FF0D56E"
10 | ```
11 |
12 | `Random.guid()` 的实现参考了 [UUID 规范](http://www.ietf.org/rfc/rfc4122.txt)。
13 |
14 | ## Random.id
15 |
16 | * Random.id()
17 |
18 | 随机生成一个 18 位身份证。
19 |
20 | ```js
21 | Random.id()
22 | // => "420000200710091854"
23 | ```
24 |
25 | ## Random.version
26 |
27 | * Random.version()
28 | * Random.version(depth)
29 |
30 | 随机生成一个版本号,每一位的最大值不超过10
31 |
32 | ### depth
33 |
34 | 版本号的层级,默认为 3
35 |
36 | ```js
37 | Random.version()
38 | // => 3.6.1
39 | Random.version(4)
40 | // => 4.9.1.8
41 | ```
42 |
43 | ## Random.increment
44 |
45 | * Random.increment()
46 | * Random.increment( step )
47 |
48 | 生成一个全局的自增整数。
49 |
50 | ### step
51 |
52 | 整数自增的步长。默认值为 1。
53 |
54 | ```js
55 | Random.increment()
56 | // => 1
57 | Random.increment(100)
58 | // => 101
59 | Random.increment(1000)
60 | // => 1101
61 | ```
62 |
63 | ## Random.phone
64 |
65 | * Random.phone()
66 |
67 | 生成一个中国的手机号。
68 |
69 | ```js
70 | Random.phone()
71 | // => 13088757656
72 | ```
73 |
--------------------------------------------------------------------------------
/doc/document/random/name.md:
--------------------------------------------------------------------------------
1 | ## Random.first
2 |
3 | * Random.first()
4 |
5 | 随机生成一个常见的英文名。
6 |
7 | ```js
8 | Random.first()
9 | // => "Nancy"
10 | ```
11 |
12 | ## Random.last
13 |
14 | * Random.last()
15 |
16 | 随机生成一个常见的英文姓。
17 |
18 | ```js
19 | Random.last()
20 | // => "Martinez"
21 | ```
22 |
23 | ## Random.name
24 |
25 | * Random.name()
26 | * Random.name( middle )
27 |
28 | 随机生成一个常见的英文姓名。
29 |
30 | ### middle
31 |
32 | 布尔值。指示是否生成中间名。
33 |
34 | ```js
35 | Random.name()
36 | // => "Larry Wilson"
37 | Random.name(true)
38 | // => "Helen Carol Martinez"
39 | ```
40 |
41 | ## Random.cfirst
42 |
43 | * Random.cfirst()
44 |
45 | 随机生成一个常见的中文名。
46 |
47 | ```js
48 | Random.cfirst()
49 | // => "曹"
50 | ```
51 |
52 | ## Random.clast
53 |
54 | * Random.clast()
55 |
56 | 随机生成一个常见的中文姓。
57 |
58 | ```js
59 | Random.clast()
60 | // => "艳"
61 | ```
62 |
63 | ## Random.cname
64 |
65 | * Random.cname()
66 |
67 | 随机生成一个常见的中文姓名。
68 |
69 | ```js
70 | Random.cname()
71 | // => "袁军"
72 | ```
73 |
--------------------------------------------------------------------------------
/doc/document/random/text.md:
--------------------------------------------------------------------------------
1 | ## Random.paragraph
2 |
3 | * Random.paragraph()
4 | * Random.paragraph( len )
5 | * Random.paragraph( min, max )
6 |
7 | 随机生成一段文本。
8 |
9 | ### len
10 |
11 | 指示文本中句子的个数。默认值为 3 到 7 之间的随机数。
12 |
13 | ### min
14 |
15 | 指示文本中句子的最小个数。默认值为 3。
16 |
17 | ### max
18 |
19 | 指示文本中句子的最大个数。默认值为 7。
20 |
21 |
22 | ```js
23 | Random.paragraph()
24 | // => "Yohbjjz psxwibxd jijiccj kvemj eidnus disnrst rcconm bcjrof tpzhdo ncxc yjws jnmdmty. Dkmiwza ibudbufrnh ndmcpz tomdyh oqoonsn jhoy rueieihtt vsrjpudcm sotfqsfyv mjeat shnqmslfo oirnzu cru qmpt ggvgxwv jbu kjde. Kzegfq kigj dtzdd ngtytgm comwwoox fgtee ywdrnbam utu nyvlyiv tubouw lezpkmyq fkoa jlygdgf pgv gyerges wbykcxhwe bcpmt beqtkq. Mfxcqyh vhvpovktvl hrmsgfxnt jmnhyndk qohnlmgc sicmlnsq nwku dxtbmwrta omikpmajv qda qrn cwoyfaykxa xqnbv bwbnyov hbrskzt. Pdfqwzpb hypvtknt bovxx noramu xhzam kfb ympmebhqxw gbtaszonqo zmsdgcku mjkjc widrymjzj nytudruhfr uudsitbst cgmwewxpi bye. Eyseox wyef ikdnws weoyof dqecfwokkv svyjdyulk glusauosnu achmrakky kdcfp kujrqcq xojqbxrp mpfv vmw tahxtnw fhe lcitj."
25 |
26 | Random.paragraph(2)
27 | // => "Dlpec hnwvovvnq slfehkf zimy qpxqgy vwrbi mok wozddpol umkek nffjcmk gnqhhvm ztqkvjm kvukg dqubvqn xqbmoda. Vdkceijr fhhyemx hgkruvxuvr kuez wmkfv lusfksuj oewvvf cyw tfpo jswpseupm ypybap kwbofwg uuwn rvoxti ydpeeerf."
28 |
29 | Random.paragraph(1, 3)
30 | // => "Qdgfqm puhxle twi lbeqjqfi bcxeeecu pqeqr srsx tjlnew oqtqx zhxhkvq pnjns eblxhzzta hifj csvndh ylechtyu."
31 | ```
32 |
33 | ## Random.cparagraph
34 |
35 | * Random.cparagraph()
36 | * Random.cparagraph( len )
37 | * Random.cparagraph( min, max )
38 |
39 | 随机生成一段中文文本。
40 |
41 | 参数的含义和默认值同 **Random.paragraph**
42 |
43 | ```js
44 | Random.cparagraph()
45 | // => "给日数时化周作少情者美制论。到先争劳今已美变江以好较正新深。族国般建难出就金感基酸转。任部四那响成族利标铁导术一或已于。省元切世权往着路积会其区素白思断。加把他位间存定国工取除许热规先法方。"
46 |
47 | Random.cparagraph(2)
48 | // => "去话起时为无子议气根复即传月广。题林里油步不约认山形两标命导社干。"
49 |
50 | Random.cparagraph(1, 3)
51 | // => "候无部社心性有构员其深例矿取民为。须被亲需报每完认支这明复几下在铁需连。省备可离展五斗器就石正队除解动。"
52 | ```
53 |
54 | ## Random.sentence
55 |
56 | * Random.sentence()
57 | * Random.sentence( len )
58 | * Random.sentence( min, max )
59 |
60 | 随机生成一个句子,第一个单词的首字母大写。
61 |
62 | ### len
63 |
64 | 指示句子中单词的个数。默认值为 12 到 18 之间的随机数。
65 |
66 | ### min
67 |
68 | 指示句子中单词的最小个数。默认值为 12。
69 |
70 | ### max
71 |
72 | 指示句子中单词的最大个数。默认值为 18。
73 |
74 | ```js
75 | Random.sentence()
76 | // => "Jovasojt qopupwh plciewh dryir zsqsvlkga yeam."
77 | Random.sentence(5)
78 | // => "Fwlymyyw htccsrgdk rgemfpyt cffydvvpc ycgvno."
79 | Random.sentence(3, 5)
80 | // => "Mgl qhrprwkhb etvwfbixm jbqmg."
81 | ```
82 |
83 | ## Random.csentence
84 |
85 | * Random.csentence()
86 | * Random.csentence( len )
87 | * Random.csentence( min, max )
88 |
89 | 随机生成一段中文文本。
90 |
91 | 参数的含义和默认值同 **Random.sentence**
92 |
93 | ```js
94 | Random.csentence()
95 | // => "第任人九同段形位第律认得。"
96 |
97 | Random.csentence(2)
98 | // => "维总。"
99 |
100 | Random.csentence(1, 3)
101 | // => "厂存。"
102 | ```
103 |
104 | ## Random.word
105 |
106 | * Random.word()
107 | * Random.word( len )
108 | * Random.word( min, max )
109 |
110 | 随机生成一个单词。
111 |
112 | ### len
113 |
114 | 指示单词中字符的个数。默认值为 3 到 10 之间的随机数。
115 |
116 | ### min
117 |
118 | 指示单词中字符的最小个数。默认值为 3。
119 |
120 | ### max
121 |
122 | 指示单词中字符的最大个数。默认值为 10。
123 |
124 | ```js
125 | Random.word()
126 | // => "fxpocl"
127 | Random.word(5)
128 | // => "xfqjb"
129 | Random.word(3, 5)
130 | // => "kemh"
131 | ```
132 |
133 | ## Random.cword
134 |
135 | * Random.cword()
136 | * Random.cword( pool )
137 | * Random.cword( length )
138 | * Random.cword( pool, length )
139 | * Random.cword( min, max )
140 | * Random.cword( pool, min, max )
141 |
142 | 随机生成一个汉字。
143 |
144 | ### pool
145 |
146 | 汉字字符串。表示汉字字符池,将从中选择一个汉字字符返回。
147 |
148 | ### min
149 |
150 | 随机汉字字符串的最小长度。默认值为 1。
151 |
152 | ### max
153 |
154 | 随机汉字字符串的最大长度。默认值为 1。
155 |
156 | ```js
157 | Random.cword()
158 | // => "干"
159 | Random.cword('零一二三四五六七八九十')
160 | // => "六"
161 | Random.cword(3)
162 | // => "别金提"
163 | Random.cword('零一二三四五六七八九十', 3)
164 | // => ""七七七""
165 | Random.cword(5, 7)
166 | // => "设过证全争听"
167 | Random.cword('零一二三四五六七八九十', 5, 7)
168 | // => "九七七零四"
169 | ```
170 |
171 | ## Random.emoji
172 |
173 | * Random.emoji()
174 | * Random.emoji( pool )
175 | * Random.emoji( length )
176 | * Random.emoji( pool, length )
177 | * Random.emoji( min, max )
178 | * Random.emoji( pool, min, max )
179 |
180 | 随机生成一个或多个 emoji 表情字符。
181 |
182 | ### pool
183 |
184 | 可以是任意字符串,如常用字符、特殊字符、emoji等,将从中选择一个或多个字符返回。
185 |
186 | ### min
187 |
188 | 随机 emoji 字符串的最小长度。默认值为 1。
189 |
190 | ### max
191 |
192 | 随机 emoji 字符串的最大长度。默认值为 1。
193 |
194 | ```js
195 | Random.emoji()
196 | // => "😷"
197 | Random.emoji('😀😁😂😃😄')
198 | // => "😁"
199 | Random.emoji(3)
200 | // => "😂😃😄"
201 | Random.emoji('😀😁😂😃😄', 2)
202 | // => ""😃😀""
203 | Random.emoji(3, 6)
204 | // => "🐥🐄🍪🌘😷🙊"
205 | Random.emoji('123🌘😷🙊★♠♫', 3, 6)
206 | // => "★2🌘😷"
207 | ```
208 |
209 | ## Random.title
210 |
211 | * Random.title()
212 | * Random.title( len )
213 | * Random.title( min, max )
214 |
215 | 随机生成一句标题,其中每个单词的首字母大写。
216 |
217 | ### len
218 |
219 | 指示单词中字符的个数。默认值为 3 到 7 之间的随机数。
220 |
221 | ### min
222 |
223 | 指示单词中字符的最小个数。默认值为 3。
224 |
225 | ### max
226 |
227 | 指示单词中字符的最大个数。默认值为 7。
228 |
229 | ```js
230 | Random.title()
231 | // => "Rduqzr Muwlmmlg Siekwvo Ktn Nkl Orn"
232 | Random.title(5)
233 | // => "Ahknzf Btpehy Xmpc Gonehbnsm Mecfec"
234 | Random.title(3, 5)
235 | // => "Hvjexiondr Pyickubll Owlorjvzys Xfnfwbfk"
236 | ```
237 |
238 | ## Random.ctitle
239 |
240 | * Random.ctitle()
241 | * Random.ctitle( len )
242 | * Random.ctitle( min, max )
243 |
244 | 随机生成一句中文标题。
245 |
246 | 参数的含义和默认值同 **Random.title**
247 |
248 | ### len
249 |
250 | 指示单词中字符的个数。默认值为 3 到 7 之间的随机数。
251 |
252 | ### min
253 |
254 | 指示单词中字符的最小个数。默认值为 3。
255 |
256 | ### max
257 |
258 | 指示单词中字符的最大个数。默认值为 7。
259 |
260 | ```js
261 | Random.ctitle()
262 | // => "证构动必作"
263 | Random.ctitle(5)
264 | // => "应青次影育"
265 | Random.ctitle(3, 5)
266 | // => "出料阶相"
267 | ```
268 |
--------------------------------------------------------------------------------
/doc/document/random/web.md:
--------------------------------------------------------------------------------
1 | ## Random.url
2 |
3 | * Random.url()
4 | * Random.url( protocol, host )
5 |
6 | 随机生成一个 URL。
7 |
8 | ### protocol
9 |
10 | 指定 URL 协议。例如 `http`。
11 |
12 | ### host
13 |
14 | 指定 URL 域名和端口号。例如 `nuysoft.com`。
15 |
16 | ```js
17 | Random.url()
18 | // => "mid://axmg.bg/bhyq"
19 | Random.url('http')
20 | // => "http://splap.yu/qxzkyoubp"
21 | Random.url('http', 'nuysoft.com')
22 | // => "http://nuysoft.com/ewacecjhe"
23 | ```
24 |
25 | ## Random.protocol
26 |
27 | * Random.protocol()
28 |
29 | 随机生成一个 URL 协议。返回以下值之一:`'http'`、`'ftp'`、`'gopher'`、`'mailto'`、`'mid'`、`'cid'`、`'news'`、`'nntp'`、`'prospero'`、`'telnet'`、`'rlogin'`、`'tn3270'`、`'wais'`。
30 |
31 | ```js
32 | Random.protocol()
33 | // => "ftp"
34 | ```
35 |
36 | ## Random.domain
37 |
38 | * Random.domain()
39 |
40 | 随机生成一个域名。
41 |
42 | ```js
43 | Random.domain()
44 | // => "kozfnb.org"
45 | ```
46 |
47 | ## Random.tld
48 |
49 | * Random.tld()
50 |
51 | 随机生成一个顶级域名(Top Level Domain)。
52 |
53 | ```js
54 | Random.tld()
55 | // => "net"
56 | ```
57 |
58 | ## Random.email
59 |
60 | * Random.email()
61 | * Random.email( domain )
62 |
63 | 随机生成一个邮件地址。
64 |
65 | ### domain
66 |
67 | 指定邮件地址的域名。例如 `nuysoft.com`。
68 |
69 | ```js
70 | Random.email()
71 | // => "x.davis@jackson.edu"
72 | Random.email('nuysoft.com')
73 | // => "h.pqpneix@nuysoft.com"
74 | ```
75 |
76 | ## Random.ip
77 |
78 | * Random.ip()
79 |
80 | 随机生成一个 IP 地址。
81 |
82 | ```js
83 | Random.ip()
84 | // => "34.206.109.169"
85 | ```
86 |
--------------------------------------------------------------------------------
/doc/document/setup/README.md:
--------------------------------------------------------------------------------
1 | ## setup
2 |
3 | ### Mock.setup( settings )
4 |
5 | 配置拦截 Ajax 请求时的行为。支持的配置项有:`timeout`。
6 |
7 | #### timeout
8 |
9 | 指定被拦截的 Ajax 请求的响应时间,单位是毫秒。值可以是正整数,例如 `400`,表示 400 毫秒 后才会返回响应内容;也可以是横杠 `'-'` 风格的字符串,例如 `'200-600'`,表示响应时间介于 200 和 600 毫秒之间。默认值是`'10-100'`。
10 |
11 | ```js
12 | Mock.setup({
13 | timeout: 400
14 | })
15 | Mock.setup({
16 | timeout: '200-600'
17 | })
18 | ```
19 |
20 | ::: tip
21 | 目前,接口 `Mock.setup( settings )` 仅用于配置 Ajax 请求,将来可能用于配置 Mock 的其他行为。
22 | :::
--------------------------------------------------------------------------------
/doc/document/syntax-specification.md:
--------------------------------------------------------------------------------
1 | # 语法规范
2 |
3 | Mock.js 的语法规范包括两部分:
4 |
5 | 1. 数据模板定义规范(Data Template Definition,DTD)
6 | 2. 数据占位符定义规范(Data Placeholder Definition,DPD)
7 |
8 | ## 数据模板定义规范 DTD
9 |
10 | **数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:**
11 |
12 | ```js
13 | // 属性名 name
14 | // 生成规则 rule
15 | // 属性值 value
16 | 'name|rule': value
17 | ```
18 |
19 | **注意:**
20 |
21 | - **属性名** 和 **生成规则** 之间用竖线 `|` 分隔。
22 | - **生成规则** 是可选的。
23 | - **生成规则** 有以下几种格式:
24 | 1. `'name|min-max': value`
25 | 1. `'name|count': value`
26 | 1. `'name|min-max.dmin-dmax': value`
27 | 1. `'name|min-max.dcount': value`
28 | 1. `'name|count.dmin-dmax': value`
29 | 1. `'name|count.dcount': value`
30 | 1. `'name|+step': value`
31 | - **生成规则** 的含义需要依赖**属性值**的类型才能确定。
32 | - **属性值** 中可以含有 `@占位符`。
33 | - **属性值** 还指定了最终值的初始值和类型。
34 |
35 | **生成规则和示例:**
36 |
37 | ### 1. 属性值是字符串 **String**
38 |
39 | 1. `'name|min-max': string`
40 |
41 | 通过重复 `string` 生成一个字符串,重复次数大于等于 `min`,小于等于 `max`。
42 |
43 | 2. `'name|count': string`
44 |
45 | 通过重复 `string` 生成一个字符串,重复次数等于 `count`。
46 |
47 | ### 2. 属性值是数字 **Number**
48 |
49 | 1. `'name|+1': number`
50 |
51 | 属性值自动加 1,初始值为 `number`。
52 |
53 | 2. `'name|min-max': number`
54 |
55 | 生成一个大于等于 `min`、小于等于 `max` 的整数,属性值 `number` 只是用来确定类型。
56 |
57 | 3. `'name|min-max.dmin-dmax': number`
58 |
59 | 生成一个浮点数,整数部分大于等于 `min`、小于等于 `max`,小数部分保留 `dmin` 到 `dmax` 位。
60 |
61 | ```js
62 | Mock.mock({
63 | 'number1|1-100.1-10': 1,
64 | 'number2|123.1-10': 1,
65 | 'number3|123.3': 1,
66 | 'number4|123.10': 1.123
67 | })
68 | // =>
69 | {
70 | "number1": 12.92,
71 | "number2": 123.51,
72 | "number3": 123.777,
73 | "number4": 123.1231091814
74 | }
75 | ```
76 |
77 | ### 3. 属性值是布尔型 **Boolean**
78 |
79 | 1. `'name|1': boolean`
80 |
81 | 随机生成一个布尔值,值为 true 的概率是 1/2,值为 false 的概率同样是 1/2。
82 |
83 | 2. `'name|min-max': value`
84 |
85 | 随机生成一个布尔值,值为 `value` 的概率是 `min / (min + max)`,值为 `!value` 的概率是 `max / (min + max)`。
86 |
87 | ### 4. 属性值是对象 **Object**
88 |
89 | 1. `'name|count': object`
90 |
91 | 从属性值 `object` 中随机选取 `count` 个属性。
92 |
93 | 2. `'name|min-max': object`
94 |
95 | 从属性值 `object` 中随机选取 `min` 到 `max` 个属性。
96 |
97 | ### 5. 属性值是数组 **Array**
98 |
99 | 1. `'name|1': array`
100 |
101 | 从属性值 `array` 中随机选取 1 个元素,作为最终值。
102 |
103 | 2. `'name|+1': array`
104 |
105 | 从属性值 `array` 中顺序选取 1 个元素,作为最终值。
106 |
107 | 3. `'name|min-max': array`
108 |
109 | 通过重复属性值 `array` 生成一个新数组,重复次数大于等于 `min`,小于等于 `max`。
110 |
111 | 4. `'name|count': array`
112 |
113 | 通过重复属性值 `array` 生成一个新数组,重复次数为 `count`。
114 |
115 | ### 6. 属性值是函数 **Function**
116 |
117 | 1. `'name': function`
118 |
119 | 执行函数 `function`,取其返回值作为最终的属性值,函数的上下文为属性 `'name'` 所在的对象。
120 |
121 | ### 7. 属性值是正则表达式 **RegExp**
122 |
123 | 1. `'name': regexp`
124 |
125 | 根据正则表达式 `regexp` 反向生成可以匹配它的字符串。用于生成自定义格式的字符串。
126 |
127 | ```js
128 | Mock.mock({
129 | 'regexp1': /[a-z][A-Z][0-9]/,
130 | 'regexp2': /\w\W\s\S\d\D/,
131 | 'regexp3': /\d{5,10}/
132 | })
133 | // =>
134 | {
135 | "regexp1": "pJ7",
136 | "regexp2": "F)\fp1G",
137 | "regexp3": "561659409"
138 | }
139 | ```
140 |
141 | ## 数据占位符定义规范 DPD
142 |
143 | **占位符** 只是在属性值字符串中占个位置,并不出现在最终的属性值中。
144 |
145 | **占位符** 的格式为:
146 |
147 | ```
148 | @占位符
149 | @占位符(参数 [, 参数])
150 | ```
151 |
152 | **注意:**
153 |
154 | - 用 `@` 来标识其后的字符串是 **占位符**。
155 | - **占位符** 引用的是 `Mock.Random` 中的方法。
156 | - **占位符** 也可以引用 **数据模板** 中的属性。
157 | - **占位符** 会优先引用 **数据模板** 中的属性。
158 | - **占位符** 支持 **相对路径** 和 **绝对路径**。
159 | - 可以使用 `\\` 来转义 `@` 符号。
160 |
161 | ```javascript
162 | Mock.mock({
163 | name: {
164 | first: '@FIRST',
165 | middle: '@FIRST',
166 | last: '@LAST',
167 | email: 'example\\@gmail.com',
168 | full: '@first @middle @last'
169 | }
170 | })
171 | // =>
172 | {
173 | "name": {
174 | "first": "Charles",
175 | "middle": "Brenda",
176 | "last": "Lopez",
177 | "email": "example@gmail.com",
178 | "full": "Charles Brenda Lopez"
179 | }
180 | }
181 | ```
182 |
183 | ## 类型转换器 Transfer
184 |
185 | ### 支持在 生成规则 后面使用转换器语法:
186 |
187 | ```js
188 | Mock.mock({
189 | 'name1': '1',
190 | 'name2#number': '1',
191 | 'name3#boolean': '1',
192 | 'name4|3#number': '1',
193 | 'name5|1-3#number': '1',
194 | 'name6|1-3.2-4#string': 1,
195 | 'name7#number': '@PHONE'
196 | })
197 | // =>
198 | {
199 | "name1": "1",
200 | "name2": 1,
201 | "name3": true,
202 | "name4": 111,
203 | "name5": 11,
204 | "name6": "2.112",
205 | "name7": 14996533237
206 | }
207 | ```
208 |
209 | **注意:**
210 |
211 | - 使用 `#` 符号来定义转换器。
212 | - 内置了以下几个转换器:`boolean`、`string`、`number`。
213 | - 可以使用 `Mock.Transfer.extend` 方法自定义转换器。
214 |
215 | ### 自定义转换器:
216 |
217 | ```js
218 | Mock.Transfer.extend({
219 | json (value) {
220 | return JSON.parse(value)
221 | }
222 | })
223 | Mock.mock({
224 | 'name#json': '{}'
225 | })
226 | // =>
227 | {
228 | name: {}
229 | }
230 | ```
--------------------------------------------------------------------------------
/doc/document/toJSONSchema/README.md:
--------------------------------------------------------------------------------
1 | ## Mock.toJSONSchema
2 |
3 | - Mock.toJSONSchema( template )
4 |
5 | 把数据模板 `template` 转换成 [JSON Schema](http://json-schema.org/)。
6 |
7 | ### template `必选`
8 |
9 | 表示数据模板,可以是对象或字符串。例如 `{ 'list|1-10':[{}] }`、`'@EMAIL'`。
10 |
11 | ```js
12 | var template = {
13 | 'key|1-10': '★'
14 | }
15 | Mock.toJSONSchema(template)
16 | // =>
17 | {
18 | "name": undefined,
19 | "path": [
20 | "ROOT"
21 | ],
22 | "type": "object",
23 | "template": {
24 | "key|1-10": "★"
25 | },
26 | "rule": {},
27 | "properties": [{
28 | "name": "key",
29 | "path": [
30 | "ROOT",
31 | "key"
32 | ],
33 | "type": "string",
34 | "template": "★",
35 | "rule": {
36 | "parameters": ["key|1-10", "key", null, "1-10", null],
37 | "range": ["1-10", "1", "10"],
38 | "min": 1,
39 | "max": 10,
40 | "count": 3,
41 | "decimal": undefined,
42 | "dmin": undefined,
43 | "dmax": undefined,
44 | "dcount": undefined
45 | }
46 | }]
47 | }
48 | ```
49 |
50 | ```js
51 | var template = {
52 | 'list|1-10': [{}]
53 | }
54 | Mock.toJSONSchema(template)
55 | // =>
56 | {
57 | "name": undefined,
58 | "path": ["ROOT"],
59 | "type": "object",
60 | "template": {
61 | "list|1-10": [{}]
62 | },
63 | "rule": {},
64 | "properties": [{
65 | "name": "list",
66 | "path": ["ROOT", "list"],
67 | "type": "array",
68 | "template": [{}],
69 | "rule": {
70 | "parameters": ["list|1-10", "list", null, "1-10", null],
71 | "range": ["1-10", "1", "10"],
72 | "min": 1,
73 | "max": 10,
74 | "count": 6,
75 | "decimal": undefined,
76 | "dmin": undefined,
77 | "dmax": undefined,
78 | "dcount": undefined
79 | },
80 | "items": [{
81 | "name": 0,
82 | "path": ["data", "list", 0],
83 | "type": "object",
84 | "template": {},
85 | "rule": {},
86 | "properties": []
87 | }]
88 | }]
89 | }
90 | ```
91 |
--------------------------------------------------------------------------------
/doc/document/valid/README.md:
--------------------------------------------------------------------------------
1 | ## Mock.valid
2 |
3 | * Mock.valid( template, data )
4 |
5 | 校验真实数据 `data` 是否与数据模板 `template` 匹配。
6 |
7 | ### template `必选`
8 |
9 | 表示数据模板,可以是对象或字符串。例如 `{ 'list|1-10':[{}] }`、`'@EMAIL'`。
10 |
11 | ### data `必选`
12 |
13 | 表示真实数据。
14 |
15 | ```js
16 | const template = {
17 | name: 'value1'
18 | }
19 | const data = {
20 | name: 'value2'
21 | }
22 | Mock.valid(template, data)
23 | // =>
24 | [
25 | {
26 | "path": [
27 | "data",
28 | "name"
29 | ],
30 | "type": "value",
31 | "actual": "value2",
32 | "expected": "value1",
33 | "action": "equal to",
34 | "message": "[VALUE] Expect ROOT.name'value is equal to value1, but is value2"
35 | }
36 | ]
37 | ```
38 |
--------------------------------------------------------------------------------
/doc/playground/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | pageClass: page-playground
3 | ---
4 |
5 |
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "better-mock",
3 | "description": "Forked from Mockjs. Generate random data & Intercept ajax request. Support miniprogram.",
4 | "author": "lavyun@163.com",
5 | "version": "0.3.7",
6 | "main": "./dist/mock.node.js",
7 | "module": "./dist/mock.browser.esm.js",
8 | "browser": "./dist/mock.browser.js",
9 | "scripts": {
10 | "test": "mochify test/browser/**/*.js --timeout=20000",
11 | "test:node": "mocha test/node/**/*.js --timeout=20000",
12 | "cover": "nyc --reporter=lcov --instrument false mochify test/browser/**/*.js --timeout=20000 --transform [ babelify --ignore [ test ] --plugins [ babel-plugin-istanbul ] ]",
13 | "coveralls": "cat ./coverage/lcov.info | coveralls",
14 | "cover:type": "type-coverage --strict --cache --ignore-catch --ignore-files '**/parser.js' --ignore-files '**/color-convert.ts'",
15 | "dev": "npm run dev:browser & npm run dev:node",
16 | "dev:browser": "node build/dev.js browser",
17 | "dev:node": "node build/dev.js node",
18 | "build": "rimraf dist/*.js && npm run lint && node build/build.js",
19 | "doc:dev": "vuepress dev doc",
20 | "doc:build": "vuepress build doc",
21 | "doc:deploy": "sh doc/deploy.sh",
22 | "lint": "eslint 'src/**/*.{js,ts}'"
23 | },
24 | "keywords": [
25 | "mock",
26 | "mockJSON",
27 | "mockAjax",
28 | "mockjs",
29 | "javascript mocking",
30 | "intercept request",
31 | "ajax",
32 | "xhr",
33 | "XMLHttpRequest",
34 | "fetch",
35 | "fetch mocking",
36 | "miniProgram",
37 | "mp"
38 | ],
39 | "files": [
40 | "bin",
41 | "dist",
42 | "src",
43 | "test",
44 | "typings",
45 | "LICENCE",
46 | "package.json",
47 | "README.md",
48 | "tsconfig.json"
49 | ],
50 | "bin": {
51 | "random": "bin/random"
52 | },
53 | "types": "./typings/index.d.ts",
54 | "typeCoverage": {
55 | "atLeast": 84
56 | },
57 | "dependencies": {
58 | "china-location": "^2.0.0",
59 | "regexparam": "^1.3.0"
60 | },
61 | "devDependencies": {
62 | "@babel/core": "^7.4.5",
63 | "@babel/preset-env": "^7.4.5",
64 | "@types/node": "^12.7.2",
65 | "@typescript-eslint/eslint-plugin": "^2.15.0",
66 | "@typescript-eslint/parser": "^2.15.0",
67 | "axios": "^0.19.2",
68 | "babel-plugin-istanbul": "^5.2.0",
69 | "babelify": "^10.0.0",
70 | "chai": "^4.2.0",
71 | "commander": "^3.0.0",
72 | "coveralls": "^3.0.6",
73 | "eslint": "^6.8.0",
74 | "husky": "^4.0.0",
75 | "jquery": "^3.4.1",
76 | "lint-staged": "^9.5.0",
77 | "mocha": "^6.2.0",
78 | "mochify": "^6.4.1",
79 | "monaco-editor": "^0.19.2",
80 | "monaco-editor-webpack-plugin": "^1.8.2",
81 | "nyc": "^14.1.1",
82 | "ora": "^5.1.0",
83 | "raw-loader": "^4.0.0",
84 | "rimraf": "^2.6.3",
85 | "rollup": "^1.14.4",
86 | "rollup-plugin-babel": "^4.3.2",
87 | "rollup-plugin-json": "^4.0.0",
88 | "rollup-plugin-node-resolve": "^5.0.1",
89 | "rollup-plugin-replace": "^2.2.0",
90 | "rollup-plugin-typescript2": "^0.25.2",
91 | "serve-handler": "^6.1.3",
92 | "type-coverage": "^2.3.1",
93 | "typescript": "^3.7.2",
94 | "uglify-js": "^3.6.0",
95 | "vuepress": "^1.0.3",
96 | "zlib": "^1.0.5"
97 | },
98 | "repository": {
99 | "type": "git",
100 | "url": "https://github.com/lavyun/better-mock"
101 | },
102 | "engines": {
103 | "node": ">=6"
104 | },
105 | "license": "MIT"
106 | }
107 |
--------------------------------------------------------------------------------
/src/core/mocked.ts:
--------------------------------------------------------------------------------
1 | import { Mocked, MockedItem, XHRCustomOptions } from '../types'
2 | import { isString, isRegExp, isFunction } from '../utils'
3 | import handler from './handler'
4 | import rgx from 'regexparam'
5 |
6 | class IMocked {
7 | private _mocked: Mocked = {}
8 |
9 | public set (key: string, value: MockedItem) {
10 | this._mocked[key] = value
11 | }
12 |
13 | public getMocked () {
14 | return this._mocked
15 | }
16 |
17 | // 查找与请求参数匹配的数据模板:URL,Type
18 | public find (url: string, type: string): MockedItem | undefined {
19 | const mockedItems: MockedItem[] = Object.values(this._mocked)
20 | for (let i = 0; i < mockedItems.length; i++) {
21 | const item = mockedItems[i]
22 | const urlMatched = this._matchUrl(item.rurl, url)
23 | const typeMatched = this._matchType(item.rtype, type)
24 | if (!item.rtype && urlMatched) {
25 | return item
26 | }
27 | if (urlMatched && typeMatched) {
28 | return item
29 | }
30 | }
31 | }
32 |
33 | /**
34 | * 数据模板转换成 mock 数据
35 | * @param item 发请求时匹配到的 mock 数据源
36 | * @param options 包含请求头,请求体,请求方法等
37 | */
38 | public convert (item: MockedItem, options: Partial) {
39 | return isFunction(item.template) ? item.template(options) : handler.gen(item.template)
40 | }
41 |
42 | private _matchUrl (expected: string | RegExp | undefined, actual: string): boolean {
43 | if (isString(expected)) {
44 | if (expected === actual) {
45 | return true
46 | }
47 |
48 | // expected: /hello/world
49 | // actual: /hello/world?type=1
50 | if (actual.indexOf(expected) === 0 && actual[expected.length] === '?') {
51 | return true
52 | }
53 |
54 | if (expected.indexOf('/') === 0) {
55 | return rgx(expected).pattern.test(actual)
56 | }
57 | }
58 | if (isRegExp(expected)) {
59 | return expected.test(actual)
60 | }
61 | return false
62 | }
63 |
64 | private _matchType (expected: string | RegExp | undefined, actual: string): boolean {
65 | if (isString(expected) || isRegExp(expected)) {
66 | return new RegExp(expected, 'i').test(actual)
67 | }
68 | return false
69 | }
70 | }
71 |
72 | export default new IMocked()
73 |
--------------------------------------------------------------------------------
/src/core/parser.ts:
--------------------------------------------------------------------------------
1 | // 解析数据模板(属性名部分)。
2 | // Parser.parse( name ):
3 | // {
4 | // parameters: [ name, inc, range, decimal ],
5 | // range: [ min-max, min , max ],
6 | // min: min,
7 | // max: max,
8 | // count : count,
9 | // decimal: [dmin-dmax, dmin, dmax],
10 | // dmin: dmin,
11 | // dmax: dmax,
12 | // dcount: dcount
13 | // }
14 | import constant from '../utils/constant'
15 | import random from '../random'
16 |
17 | export const parse = function (name: string | undefined | number) {
18 | name = name === undefined ? '' : (name + '')
19 |
20 | const parameters = name.match(constant.RE_KEY)
21 |
22 | // name|min-max, name|count
23 | const range = parameters && parameters[3] && parameters[3].match(constant.RE_RANGE)
24 | const min = range && range[1] && parseInt(range[1], 10)
25 | const max = range && range[2] && parseInt(range[2], 10)
26 | // 如果是 min-max, 返回 min-max 之间的一个数
27 | // 如果是 count, 返回 count
28 | const count = range
29 | ? range[2]
30 | ? random.integer(Number(min), Number(max))
31 | : parseInt(range[1], 10)
32 | : undefined
33 |
34 | const decimal = parameters && parameters[4] && parameters[4].match(constant.RE_RANGE)
35 | const dmin = decimal && decimal[1] && parseInt(decimal[1], 10)
36 | const dmax = decimal && decimal[2] && parseInt(decimal[2], 10)
37 | // int || dmin-dmax
38 | const dcount = decimal
39 | ? decimal[2]
40 | ? random.integer(Number(dmin), Number(dmax))
41 | : parseInt(decimal[1], 10)
42 | : undefined
43 |
44 | const result = {
45 | // 1 name, 2 inc, 3 range, 4 decimal
46 | parameters,
47 | // 1 min, 2 max
48 | range,
49 | min,
50 | max,
51 | count,
52 | decimal,
53 | dmin,
54 | dmax,
55 | dcount
56 | }
57 |
58 | for (const r in result) {
59 | if (result[r] != undefined) {
60 | return result
61 | }
62 | }
63 |
64 | return {} as typeof result
65 | }
66 |
--------------------------------------------------------------------------------
/src/core/regexp/handler.ts:
--------------------------------------------------------------------------------
1 | // ## RegExp Handler
2 | //
3 | // https://github.com/ForbesLindesay/regexp
4 | // https://github.com/dmajda/pegjs
5 | // http://www.regexper.com/
6 | //
7 | // 每个节点的结构
8 | // {
9 | // type: '',
10 | // offset: number,
11 | // text: '',
12 | // body: {},
13 | // escaped: true/false
14 | // }
15 | //
16 | // type 可选值
17 | // alternate | 选择
18 | // match 匹配
19 | // capture-group () 捕获组
20 | // non-capture-group (?:...) 非捕获组
21 | // positive-lookahead (?=p) 零宽正向先行断言
22 | // negative-lookahead (?!p) 零宽负向先行断言
23 | // quantified a* 重复节点
24 | // quantifier * 量词
25 | // charset [] 字符集
26 | // range {m, n} 范围
27 | // literal a 直接量字符
28 | // unicode \uxxxx Unicode
29 | // hex \x 十六进制
30 | // octal 八进制
31 | // back-reference \n 反向引用
32 | // control-character \cX 控制字符
33 | //
34 | // // Token
35 | // start ^ 开头
36 | // end $ 结尾
37 | // any-character . 任意字符
38 | // backspace [\b] 退格直接量
39 | // word-boundary \b 单词边界
40 | // non-word-boundary \B 非单词边界
41 | // digit \d ASCII 数字,[0-9]
42 | // non-digit \D 非 ASCII 数字,[^0-9]
43 | // form-feed \f 换页符
44 | // line-feed \n 换行符
45 | // carriage-return \r 回车符
46 | // white-space \s 空白符
47 | // non-white-space \S 非空白符
48 | // tab \t 制表符
49 | // vertical-tab \v 垂直制表符
50 | // word \w ASCII 字符,[a-zA-Z0-9]
51 | // non-word \W 非 ASCII 字符,[^a-zA-Z0-9]
52 | // null-character \o NUL 字符
53 | import random from '../../random'
54 |
55 | // ASCII printable code chart
56 | const LOWER = ascii(97, 122)
57 | const UPPER = ascii(65, 90)
58 | const NUMBER = ascii(48, 57)
59 | const OTHER = ascii(32, 47) + ascii(58, 64) + ascii(91, 96) + ascii(123, 126) // 排除 95 _ ascii(91, 94) + ascii(96, 96)
60 | const PRINTABLE = ascii(32, 126)
61 | const SPACE = ' \f\n\r\t\v\u00A0\u2028\u2029'
62 | const CHARACTER_CLASSES = {
63 | '\\w': LOWER + UPPER + NUMBER + '_', // ascii(95, 95)
64 | '\\W': OTHER.replace('_', ''), '\\s': SPACE, '\\S': function () {
65 | let result = PRINTABLE
66 | for (let i = 0; i < SPACE.length; i++) {
67 | result = result.replace(SPACE[i], '')
68 | }
69 | return result
70 | }(), '\\d': NUMBER, '\\D': LOWER + UPPER + OTHER
71 | }
72 |
73 | function ascii (from, to) {
74 | let result = ''
75 | for (let i = from; i <= to; i++) {
76 | result += String.fromCharCode(i)
77 | }
78 | return result
79 | }
80 |
81 | const handler = {
82 | // var ast = RegExpParser.parse(regexp.source)
83 | gen: function (node, result?, cache?) {
84 | cache = cache || {
85 | guid: 1
86 | }
87 | return handler[node.type] ? handler[node.type](node, result, cache) : handler.token(node)
88 | },
89 | token: /* istanbul ignore next */ function (node) {
90 | switch (node.type) {
91 | case 'start':
92 | case 'end':
93 | return ''
94 | case 'any-character':
95 | return random.character()
96 | case 'backspace':
97 | return ''
98 | case 'word-boundary': // TODO
99 | return ''
100 | case 'non-word-boundary': // TODO
101 | break
102 | case 'digit':
103 | return random.pick(NUMBER.split(''))
104 | case 'non-digit':
105 | return random.pick((LOWER + UPPER + OTHER).split(''))
106 | case 'form-feed':
107 | break
108 | case 'line-feed':
109 | return node.body || node.text
110 | case 'carriage-return':
111 | break
112 | case 'white-space':
113 | return random.pick(SPACE.split(''))
114 | case 'non-white-space':
115 | return random.pick((LOWER + UPPER + NUMBER).split(''))
116 | case 'tab':
117 | break
118 | case 'vertical-tab':
119 | break
120 | case 'word': // \w [a-zA-Z0-9]
121 | return random.pick((LOWER + UPPER + NUMBER).split(''))
122 | case 'non-word': // \W [^a-zA-Z0-9]
123 | return random.pick(OTHER.replace('_', '').split(''))
124 | case 'null-character':
125 | break
126 | }
127 | return node.body || node.text
128 | },
129 | // {
130 | // type: 'alternate',
131 | // offset: 0,
132 | // text: '',
133 | // left: {
134 | // boyd: []
135 | // },
136 | // right: {
137 | // boyd: []
138 | // }
139 | // }
140 | alternate: function (node, result, cache) {
141 | // node.left/right {}
142 | return handler.gen(random.boolean() ? node.left : node.right, result, cache)
143 | },
144 |
145 | // {
146 | // type: 'match',
147 | // offset: 0,
148 | // text: '',
149 | // body: []
150 | // }
151 | match: function (node, result, cache) {
152 | result = ''
153 | // node.body []
154 | for (let i = 0; i < node.body.length; i++) {
155 | result += handler.gen(node.body[i], result, cache)
156 | }
157 | return result
158 | },
159 |
160 | // ()
161 | 'capture-group': function (node, result, cache) {
162 | // node.body {}
163 | result = handler.gen(node.body, result, cache)
164 | cache[cache.guid++] = result
165 | return result
166 | },
167 |
168 | // (?:...)
169 | 'non-capture-group': function (node, result, cache) {
170 | // node.body {}
171 | return handler.gen(node.body, result, cache)
172 | },
173 |
174 | // (?=p)
175 | 'positive-lookahead': function (node, result, cache) {
176 | // node.body
177 | return handler.gen(node.body, result, cache)
178 | },
179 |
180 | // (?!p)
181 | 'negative-lookahead': function () {
182 | // node.body
183 | return ''
184 | },
185 |
186 | // {
187 | // type: 'quantified',
188 | // offset: 3,
189 | // text: 'c*',
190 | // body: {
191 | // type: 'literal',
192 | // offset: 3,
193 | // text: 'c',
194 | // body: 'c',
195 | // escaped: false
196 | // },
197 | // quantifier: {
198 | // type: 'quantifier',
199 | // offset: 4,
200 | // text: '*',
201 | // min: 0,
202 | // max: Infinity,
203 | // greedy: true
204 | // }
205 | // }
206 | quantified: function (node, result, cache) {
207 | result = ''
208 | // node.quantifier {}
209 | const count = handler.quantifier(node.quantifier)
210 | // node.body {}
211 | for (let i = 0; i < count; i++) {
212 | result += handler.gen(node.body, result, cache)
213 | }
214 | return result
215 | },
216 |
217 | // quantifier: {
218 | // type: 'quantifier',
219 | // offset: 4,
220 | // text: '*',
221 | // min: 0,
222 | // max: Infinity,
223 | // greedy: true
224 | // }
225 | quantifier: function (node) {
226 | const min = Math.max(node.min, 0)
227 | const max = isFinite(node.max) ? node.max : min + random.integer(3, 7)
228 | return random.integer(min, max)
229 | },
230 |
231 | charset: function (node, result, cache) {
232 | // node.invert
233 | if (node.invert) {
234 | return handler['invert-charset'](node, result, cache)
235 | }
236 |
237 | // node.body []
238 | const literal = random.pick(node.body)
239 | return handler.gen(literal, result, cache)
240 | },
241 |
242 | 'invert-charset': function (node, result, cache) {
243 | let pool = PRINTABLE
244 | let item
245 | for (let i = 0; i < node.body.length; i++) {
246 | item = node.body[i]
247 | switch (item.type) {
248 | case 'literal':
249 | pool = pool.replace(item.body, '')
250 | break
251 | case 'range':
252 | const min = handler.gen(item.start, result, cache).charCodeAt()
253 | const max = handler.gen(item.end, result, cache).charCodeAt()
254 | for (let ii = min; ii <= max; ii++) {
255 | pool = pool.replace(String.fromCharCode(ii), '')
256 | }
257 | /* falls through */
258 | default:
259 | const characters = CHARACTER_CLASSES[item.text]
260 | if (characters) {
261 | for (let iii = 0; iii <= characters.length; iii++) {
262 | pool = pool.replace(characters[iii], '')
263 | }
264 | }
265 | }
266 | }
267 | return random.pick(pool.split(''))
268 | },
269 |
270 | range: function (node, result, cache) {
271 | // node.start, node.end
272 | const min = handler.gen(node.start, result, cache).charCodeAt()
273 | const max = handler.gen(node.end, result, cache).charCodeAt()
274 | return String.fromCharCode(random.integer(min, max))
275 | },
276 |
277 | literal: function (node) {
278 | return node.escaped ? node.body : node.text
279 | },
280 |
281 | // Unicode \u
282 | unicode: function (node) {
283 | return String.fromCharCode(parseInt(node.code, 16))
284 | },
285 |
286 | // 十六进制 \xFF
287 | hex: function (node) {
288 | return String.fromCharCode(parseInt(node.code, 16))
289 | }
290 |
291 | , // 八进制 \0
292 | octal: function (node) {
293 | return String.fromCharCode(parseInt(node.code, 8))
294 | },
295 |
296 | // 反向引用
297 | 'back-reference': function (node, result, cache) {
298 | return cache[node.code] || ''
299 | },
300 |
301 | // http://en.wikipedia.org/wiki/C0_and_C1_control_codes
302 | CONTROL_CHARACTER_MAP: function () {
303 | const 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(' ')
304 | const 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(' ')
305 | const map = {}
306 | for (let i = 0; i < CONTROL_CHARACTER.length; i++) {
307 | map[CONTROL_CHARACTER[i]] = CONTROL_CHARACTER_UNICODE[i]
308 | }
309 | return map
310 | }(),
311 |
312 | 'control-character': function (node) {
313 | return this.CONTROL_CHARACTER_MAP[node.code]
314 | }
315 | }
316 |
317 | export default handler
318 |
--------------------------------------------------------------------------------
/src/core/regexp/index.ts:
--------------------------------------------------------------------------------
1 | import Handler from './handler'
2 | import Parser from './parser'
3 |
4 | export default {
5 | Parser,
6 | Handler
7 | }
8 |
--------------------------------------------------------------------------------
/src/core/schema.ts:
--------------------------------------------------------------------------------
1 | // 把 Mock.js 风格的数据模板转换成 JSON Schema。
2 | import constant from '../utils/constant'
3 | import { type, isArray, isObject } from '../utils'
4 | import { parse } from './parser'
5 | import { SchemaResult } from '../types'
6 |
7 | function toJSONSchema (template: object | string | (string | object)[], name?: string | number, path?: string[]) {
8 | path = path || []
9 | const result: SchemaResult = {
10 | name: typeof name === 'string' ? name.replace(constant.RE_KEY, '$1') : name,
11 | template: template,
12 | type: type(template), // 可能不准确,例如 { 'name|1': [{}, {} ...] }
13 | rule: parse(name),
14 | path: path.slice(0)
15 | }
16 | result.path!.push(name === undefined ? 'ROOT' : result.name as string)
17 |
18 | if (isArray(template)) {
19 | result.items = [];
20 | template.forEach((item, index) => {
21 | result.items!.push(
22 | toJSONSchema(item, index, result.path)
23 | )
24 | })
25 | } else if (isObject(template)) {
26 | result.properties = [];
27 | for (const key in template) {
28 | result.properties.push(
29 | toJSONSchema(template[key], key, result.path)
30 | )
31 | }
32 | }
33 |
34 | return result
35 |
36 | }
37 |
38 | export default toJSONSchema
39 |
--------------------------------------------------------------------------------
/src/core/setting.ts:
--------------------------------------------------------------------------------
1 | import { Settings } from '../types/index'
2 |
3 | class Setting {
4 | private _setting: Settings = {
5 | timeout: '10-100'
6 | }
7 |
8 | setup (setting: Partial) {
9 | Object.assign(this._setting, setting)
10 | }
11 |
12 | parseTimeout (timeout: Settings['timeout'] = this._setting.timeout): number {
13 | if (typeof timeout === 'number') {
14 | return timeout
15 | }
16 | if (typeof timeout === 'string' && timeout.indexOf('-') === -1) {
17 | return parseInt(timeout, 10)
18 | }
19 | if (typeof timeout === 'string' && timeout.indexOf('-') !== -1) {
20 | const tmp = timeout.split('-')
21 | const min = parseInt(tmp[0], 10)
22 | const max = parseInt(tmp[1], 10)
23 | return Math.round(Math.random() * (max - min)) + min
24 | }
25 | return 0
26 | }
27 | }
28 |
29 | export default new Setting()
--------------------------------------------------------------------------------
/src/platform/browser/fetch.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../utils'
2 | import { XHRCustomOptions, StringObject } from '../../types'
3 | import mocked from '../../core/mocked'
4 | import setting from '../../core/setting'
5 |
6 | const _nativeFetch = fetch
7 | const _nativeRequest = Request
8 |
9 | function extendRequest (request: Request, input: RequestInfo, init?: RequestInit | undefined): Request {
10 | if (isString(input)) {
11 | request['_actualUrl'] = input
12 | }
13 | if (init && init.body) {
14 | request['_actualBody'] = init.body
15 | }
16 | if (input instanceof _nativeRequest && !init) {
17 | request['_actualUrl'] = input['_actualUrl']
18 | request['_actualBody'] = input['_actualBody']
19 | }
20 | return request
21 | }
22 |
23 | type RequestCtor = typeof window.Request
24 |
25 | let MockRequest: (RequestCtor | Function) & { __MOCK__?: boolean }
26 | /**
27 | * 拦截 window.Request 实例化
28 | * 原生 Request 对象被实例化后,对 request.url 取值得到的是拼接后的 url:
29 | * const request = new Request('/path/to')
30 | * console.log(request.url) => 'http://example.com/path/to'
31 | * 原生 Request 对象被实例化后,对 request.body 取值得到的是 undefined:
32 | * const request = new Request('/path/to', { method: 'POST', body: 'foo=1' })
33 | * console.log(request.body) => undefined
34 | */
35 | if (window.Proxy) {
36 | MockRequest = new Proxy(_nativeRequest, {
37 | construct (target, [input, init]: [RequestInfo, RequestInit | undefined]): Request {
38 | const request = new target(input, init)
39 | return extendRequest(request, input, init)
40 | }
41 | })
42 | } /* istanbul ignore next */ else {
43 | MockRequest = function MockRequest (input: RequestInfo, init?: RequestInit | undefined): Request {
44 | const request = new _nativeRequest(input, init)
45 | return extendRequest(request, input, init)
46 | }
47 | MockRequest.prototype = _nativeRequest.prototype
48 | }
49 |
50 | // 拦截 fetch 方法
51 | // https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch
52 | async function MockFetch (input: RequestInfo, init?: RequestInit | undefined): Promise {
53 | let request: Request
54 | if (input instanceof Request && !init) {
55 | request = input
56 | } else {
57 | request = new Request(input, init)
58 | }
59 |
60 | // 收集请求头
61 | const headers: StringObject = {}
62 | request.headers.forEach((value, key) => {
63 | headers[key] = value
64 | })
65 |
66 | // 优先获取自己扩展的 _actualUrl 和 _actualBody
67 | const options: XHRCustomOptions = {
68 | url: request['_actualUrl'] || request.url,
69 | type: request.method,
70 | body: request['_actualBody'] || request.body || null,
71 | headers: headers
72 | }
73 |
74 | // 查找与请求参数匹配的数据模板
75 | const item = mocked.find(options.url, options.type)
76 |
77 | // 如果未找到匹配的数据模板,则采用原生 fetch 发送请求。
78 | if (!item) {
79 | return _nativeFetch(input, init)
80 | }
81 |
82 | // 找到了匹配的数据模板,拦截 fetch 请求
83 | const result = await mocked.convert(item, options)
84 | const body = JSON.stringify(result)
85 | const response = new Response(body, {
86 | status: 200,
87 | statusText: 'ok',
88 | headers: request.headers
89 | })
90 |
91 | // 异步返回数据
92 | return new Promise((resolve) => {
93 | setTimeout(() => {
94 | resolve(response)
95 | }, setting.parseTimeout())
96 | })
97 | }
98 |
99 | function overrideFetchAndRequest () {
100 | if (window.fetch && !MockRequest.__MOCK__) {
101 | MockRequest.__MOCK__ = true
102 | window.Request = MockRequest as RequestCtor
103 | window.fetch = MockFetch
104 | }
105 | }
106 |
107 | export {
108 | overrideFetchAndRequest
109 | }
110 |
--------------------------------------------------------------------------------
/src/platform/browser/index.ts:
--------------------------------------------------------------------------------
1 | // For browser
2 | import Handler from '../../core/handler'
3 | import RE from '../../core/regexp'
4 | import toJSONSchema from '../../core/schema'
5 | import valid from '../../core/valid'
6 | import mocked from '../../core/mocked'
7 | import setting from '../../core/setting'
8 | import * as Util from '../../utils'
9 | import Random from '../../random'
10 | import Transfer from '../../transfer'
11 | import { overrideXHR, MockXMLHttpRequest } from './xhr'
12 | import { overrideFetchAndRequest } from './fetch'
13 |
14 | const Mock = {
15 | Handler,
16 | Random,
17 | Transfer,
18 | Util,
19 | XHR: MockXMLHttpRequest,
20 | RE,
21 | toJSONSchema,
22 | valid,
23 | mock,
24 | heredoc: Util.heredoc,
25 | setup: setting.setup.bind(setting),
26 | _mocked: mocked.getMocked(),
27 | version: '__VERSION__'
28 | }
29 |
30 | // 根据数据模板生成模拟数据。
31 | function mock (rurl: string | RegExp, rtype?: string | RegExp, template?: object | Function) {
32 | Util.assert(arguments.length, 'The mock function needs to pass at least one parameter!')
33 | // Mock.mock(template)
34 | if (arguments.length === 1) {
35 | return Handler.gen(rurl)
36 | }
37 | // Mock.mock(url, template)
38 | if (arguments.length === 2) {
39 | template = rtype as object | Function
40 | rtype = undefined
41 | }
42 | // 拦截 XHR
43 | overrideXHR()
44 | // 拦截fetch
45 | overrideFetchAndRequest()
46 |
47 | const key = String(rurl) + String(rtype)
48 | mocked.set(key, { rurl, rtype, template })
49 |
50 | return Mock
51 | }
52 |
53 | export default Mock
54 |
--------------------------------------------------------------------------------
/src/platform/browser/xhr.ts:
--------------------------------------------------------------------------------
1 | import { createCustomEvent } from '../../utils'
2 | import { XHRCustom, XHRBody } from '../../types'
3 | import mocked from '../../core/mocked'
4 | import setting from '../../core/setting'
5 |
6 | // 备份原生 XMLHttpRequest
7 | const _XMLHttpRequest = XMLHttpRequest
8 |
9 | enum XHR_STATES {
10 | // The object has been constructed.
11 | UNSENT = 0,
12 | // The open() method has been successfully invoked.
13 | OPENED = 1,
14 | // All redirects (if any) have been followed and all HTTP headers of the response have been received.
15 | HEADERS_RECEIVED = 2,
16 | // The response's body is being received.
17 | LOADING = 3,
18 | // The data transfer has been completed or something went wrong during the transfer (e.g. infinite redirects).
19 | DONE = 4,
20 | }
21 |
22 | const XHR_EVENTS = ['readystatechange', 'loadstart', 'progress', 'abort', 'error', 'load', 'timeout', 'loadend']
23 | const XHR_REQUEST_PROPERTIES = ['timeout', 'withCredentials', 'responseType']
24 | const XHR_RESPONSE_PROPERTIES = [
25 | 'readyState',
26 | 'responseURL',
27 | 'status',
28 | 'statusText',
29 | 'response',
30 | 'responseText',
31 | 'responseXML'
32 | ]
33 |
34 | class MockXMLHttpRequest {
35 | custom: XHRCustom
36 |
37 | // 标记当前对象为 MockXMLHttpRequest
38 | mock: boolean = true
39 |
40 | // 是否拦截 Ajax 请求
41 | match: boolean = false
42 |
43 | timeout: number = 0
44 |
45 | readyState: number = XHR_STATES.UNSENT
46 |
47 | withCredentials: boolean = false
48 |
49 | // https://xhr.spec.whatwg.org/#the-send()-method
50 | upload: XMLHttpRequestUpload
51 |
52 | responseURL: string = ''
53 |
54 | status: number = XHR_STATES.UNSENT
55 |
56 | statusText: string = ''
57 |
58 | // '', 'text', 'arraybuffer', 'blob', 'document', 'json'
59 | responseType: string = ''
60 |
61 | response: any = null
62 |
63 | responseText: string = ''
64 |
65 | responseXML: string = ''
66 |
67 | UNSENT: number = XHR_STATES.UNSENT
68 | OPENED: number = XHR_STATES.OPENED
69 | HEADERS_RECEIVED: number = XHR_STATES.HEADERS_RECEIVED
70 | LOADING: number = XHR_STATES.LOADING
71 | DONE: number = XHR_STATES.DONE
72 |
73 | constructor () {
74 | // 初始化 custom 对象,用于存储自定义属性
75 | this.custom = {
76 | events: {},
77 | requestHeaders: {},
78 | responseHeaders: {},
79 | timeout: 0,
80 | options: {},
81 | xhr: createNativeXHR()!,
82 | template: null,
83 | async: true
84 | }
85 | this.upload = this.custom.xhr.upload
86 | }
87 |
88 | open (method: string, url: string, async: boolean = true, username?: string, password?: string) {
89 | Object.assign(this.custom, {
90 | method: method,
91 | url: url,
92 | async: typeof async === 'boolean' ? async : true,
93 | username: username,
94 | password: password,
95 | options: {
96 | url: url,
97 | type: method
98 | }
99 | })
100 |
101 | this.custom.timeout = setting.parseTimeout()
102 |
103 | // 查找与请求参数匹配的数据模板
104 | const options = this.custom.options
105 | const item = mocked.find(options.url!, options.type!)
106 |
107 | // 如果未找到匹配的数据模板,则采用原生 XHR 发送请求。
108 | if (!item) {
109 | const xhr = this.custom.xhr
110 |
111 | // 初始化所有事件,用于监听原生 XHR 对象的事件
112 | for (let i = 0; i < XHR_EVENTS.length; i++) {
113 | xhr.addEventListener(XHR_EVENTS[i], (event) => {
114 | // 同步属性 NativeXMLHttpRequest => MockXMLHttpRequest
115 | XHR_RESPONSE_PROPERTIES.forEach(prop => {
116 | try {
117 | this[prop] = xhr[prop]
118 | } catch (e) {}
119 | })
120 | // 触发 MockXMLHttpRequest 上的同名事件
121 | this.dispatchEvent(event)
122 | })
123 | }
124 |
125 | // xhr.open()
126 | if (username) {
127 | xhr.open(method, url, async, username, password)
128 | } else {
129 | xhr.open(method, url, async)
130 | }
131 |
132 | return
133 | }
134 |
135 | // 找到了匹配的数据模板,开始拦截 XHR 请求
136 | this.match = true
137 | this.custom.template = item
138 | this.readyState = XHR_STATES.OPENED
139 | this.dispatchEvent(createCustomEvent('readystatechange'))
140 | }
141 |
142 | // Combines a header in author request headers.
143 | setRequestHeader (name: string, value: string): void {
144 | // 原生 XHR
145 | if (!this.match) {
146 | this.custom.xhr!.setRequestHeader(name, value)
147 | return
148 | }
149 |
150 | // 拦截 XHR
151 | const requestHeaders = this.custom.requestHeaders
152 | if (requestHeaders[name]) {
153 | requestHeaders[name] += ',' + value
154 | } else {
155 | requestHeaders[name] = value
156 | }
157 | }
158 |
159 | // Initiates the request.
160 | send (data: XHRBody): void {
161 | this.custom.options.body = data
162 | this.custom.options.headers = this.custom.requestHeaders
163 |
164 | // 原生 XHR
165 | if (!this.match) {
166 | // 同步属性 MockXMLHttpRequest => NativeXMLHttpRequest
167 | XHR_REQUEST_PROPERTIES.forEach(prop => {
168 | try {
169 | this.custom.xhr[prop] = this[prop]
170 | } catch (e) {}
171 | })
172 | this.custom.xhr!.send(data)
173 | return
174 | }
175 |
176 | // 拦截 XHR
177 | // X-Requested-With header
178 | this.setRequestHeader('X-Requested-With', 'MockXMLHttpRequest')
179 |
180 | // loadstart The fetch initiates.
181 | this.dispatchEvent(createCustomEvent('loadstart'))
182 |
183 | const done = async () => {
184 | this.readyState = XHR_STATES.HEADERS_RECEIVED
185 | this.dispatchEvent(createCustomEvent('readystatechange'))
186 | this.readyState = XHR_STATES.LOADING
187 | this.dispatchEvent(createCustomEvent('readystatechange'))
188 |
189 | this.status = 200
190 | this.statusText = 'OK'
191 |
192 | // fix #92 #93 by @qddegtya
193 | const mockResponse = await mocked.convert(this.custom.template!, this.custom.options)
194 | this.response = this.responseText = JSON.stringify(mockResponse)
195 |
196 | this.readyState = XHR_STATES.DONE
197 | this.dispatchEvent(createCustomEvent('readystatechange'))
198 | this.dispatchEvent(createCustomEvent('load'))
199 | this.dispatchEvent(createCustomEvent('loadend'))
200 | }
201 |
202 | if (this.custom.async) {
203 | // 异步
204 | setTimeout(done, this.custom.timeout)
205 | } else {
206 | // 同步
207 | done()
208 | }
209 | }
210 | // https://xhr.spec.whatwg.org/#the-abort()-method
211 | // Cancels any network activity.
212 | abort (): void {
213 | // 原生 XHR
214 | if (!this.match) {
215 | this.custom.xhr!.abort()
216 | return
217 | }
218 |
219 | // 拦截 XHR
220 | this.readyState = XHR_STATES.UNSENT
221 | this.dispatchEvent(createCustomEvent('abort', false, false, this))
222 | this.dispatchEvent(createCustomEvent('error', false, false, this))
223 | }
224 |
225 | // https://xhr.spec.whatwg.org/#the-getresponseheader()-method
226 | getResponseHeader (name: string): string | null {
227 | // 原生 XHR
228 | if (!this.match) {
229 | return this.custom.xhr!.getResponseHeader(name)
230 | }
231 |
232 | // 拦截 XHR
233 | return this.custom.responseHeaders[name.toLowerCase()]
234 | }
235 |
236 | // https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method
237 | // http://www.utf8-chartable.de/
238 | getAllResponseHeaders (): string {
239 | // 原生 XHR
240 | if (!this.match) {
241 | return this.custom.xhr!.getAllResponseHeaders()
242 | }
243 |
244 | // 拦截 XHR
245 | const responseHeaders = this.custom.responseHeaders
246 | let headers = ''
247 | for (const h in responseHeaders) {
248 | if (!responseHeaders.hasOwnProperty(h)) {
249 | continue
250 | }
251 | headers += h + ': ' + responseHeaders[h] + '\r\n'
252 | }
253 | return headers
254 | }
255 |
256 | overrideMimeType () {}
257 |
258 | addEventListener (type: string, handle: Function): void {
259 | const events = this.custom.events
260 | if (!events[type]) {
261 | events[type] = []
262 | }
263 | events[type].push(handle)
264 | }
265 |
266 | removeEventListener (type: string, handle: Function): void {
267 | const handles = this.custom.events[type] || []
268 | for (let i = 0; i < handles.length; i++) {
269 | if (handles[i] === handle) {
270 | handles.splice(i--, 1)
271 | }
272 | }
273 | }
274 |
275 | dispatchEvent (event: Event): void {
276 | const handles = this.custom.events[event.type] || []
277 | for (let i = 0; i < handles.length; i++) {
278 | handles[i].call(this, event)
279 | }
280 |
281 | const onType = 'on' + event.type
282 | if (this[onType]) {
283 | this[onType](event)
284 | }
285 | }
286 |
287 | static UNSENT: number = XHR_STATES.UNSENT
288 | static OPENED: number = XHR_STATES.OPENED
289 | static HEADERS_RECEIVED: number = XHR_STATES.HEADERS_RECEIVED
290 | static LOADING: number = XHR_STATES.LOADING
291 | static DONE: number = XHR_STATES.DONE
292 |
293 | static __MOCK__: boolean = false
294 | }
295 |
296 | // Inspired by jQuery
297 | function createNativeXHR () {
298 | const localProtocolRE = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
299 | const isLocal = localProtocolRE.test(location.protocol)
300 |
301 | return window.ActiveXObject
302 | ? (!isLocal && createStandardXHR()) || createActiveXHR()
303 | : createStandardXHR()
304 |
305 | function createStandardXHR () {
306 | try {
307 | return new _XMLHttpRequest()
308 | } catch (e) {}
309 | }
310 |
311 | function createActiveXHR () {
312 | try {
313 | return new window.ActiveXObject('Microsoft.XMLHTTP')
314 | } catch (e) {}
315 | }
316 | }
317 |
318 | function overrideXHR () {
319 | if (!MockXMLHttpRequest.__MOCK__) {
320 | MockXMLHttpRequest.__MOCK__ = true
321 | window.XMLHttpRequest = MockXMLHttpRequest as any
322 | }
323 | }
324 |
325 | export {
326 | MockXMLHttpRequest,
327 | overrideXHR
328 | }
329 |
330 | declare global {
331 | interface Window {
332 | ActiveXObject: { new(type: string): XMLHttpRequest };
333 | XMLHttpRequest: XMLHttpRequest;
334 | }
335 | }
336 |
--------------------------------------------------------------------------------
/src/platform/mp/index.ts:
--------------------------------------------------------------------------------
1 | // For mini-program
2 | import Handler from '../../core/handler'
3 | import RE from '../../core/regexp'
4 | import toJSONSchema from '../../core/schema'
5 | import valid from '../../core/valid'
6 | import mocked from '../../core/mocked'
7 | import setting from '../../core/setting'
8 | import * as Util from '../../utils'
9 | import Random from '../../random'
10 | import Transfer from '../../transfer'
11 | import { overrideRequest } from './request'
12 |
13 | const Mock = {
14 | Handler,
15 | Random,
16 | Transfer,
17 | Util,
18 | RE,
19 | toJSONSchema,
20 | valid,
21 | mock,
22 | setup: setting.setup.bind(setting),
23 | _mocked: mocked.getMocked(),
24 | version: '__VERSION__'
25 | }
26 |
27 | // 根据数据模板生成模拟数据。
28 | function mock (rurl: string | RegExp, rtype?: string | RegExp, template?: object | Function) {
29 | Util.assert(arguments.length, 'The mock function needs to pass at least one parameter!')
30 | // Mock.mock(template)
31 | if (arguments.length === 1) {
32 | return Handler.gen(rurl)
33 | }
34 | // Mock.mock(url, template)
35 | if (arguments.length === 2) {
36 | template = rtype as object | Function
37 | rtype = undefined
38 | }
39 |
40 | overrideRequest()
41 |
42 | const key = String(rurl) + String(rtype)
43 | mocked.set(key, { rurl, rtype, template })
44 |
45 | return Mock
46 | }
47 |
48 | export default Mock
49 |
--------------------------------------------------------------------------------
/src/platform/mp/request.ts:
--------------------------------------------------------------------------------
1 | import mocked from '../../core/mocked'
2 | import setting from '../../core/setting'
3 | import { PlatformName, MpGlobal, WxSuccessCallback, MySuccessCallback, MpRequestOptions } from './types'
4 | import { XHRCustomOptions } from '../../types'
5 | import { isFunction, assert } from '../../utils'
6 |
7 | // 获取小程序平台标识
8 | function getMpPlatform (): {
9 | name: PlatformName;
10 | global: MpGlobal;
11 | } {
12 | let global
13 | let name
14 | if (typeof wx !== 'undefined') {
15 | global = wx
16 | name = 'wx'
17 | } else if (typeof my !== 'undefined') {
18 | global = my
19 | name = 'my'
20 | } else if (typeof tt !== 'undefined') {
21 | global = tt
22 | name = 'tt'
23 | } else if (typeof swan !== 'undefined') {
24 | global = swan
25 | name = 'swan'
26 | }
27 |
28 | assert(global && name, 'Invalid mini-program platform, just work in "wx", "my", "tt" or "swan"!')
29 |
30 | return { global, name }
31 | }
32 |
33 | const platform = getMpPlatform()
34 | const platformName = platform.name
35 | const platformRequest = platform.global.request
36 |
37 | function MockRequest (opts: MpRequestOptions) {
38 | const options: XHRCustomOptions = {
39 | url: opts.url,
40 | type: opts.method || 'GET',
41 | body: opts.data as any || null,
42 | headers: opts.header || opts.headers || {}
43 | }
44 |
45 | // 查找与请求参数匹配的数据模板
46 | const item = mocked.find(options.url, options.type)
47 |
48 | // 如果未找到匹配的数据模板,则采用原生 request 发送请求。
49 | if (!item) {
50 | return platformRequest(opts)
51 | }
52 |
53 | // 找到了匹配的数据模板,拦截 fetch 请求
54 | const responseData = mocked.convert(item, options)
55 |
56 | let successOptions: WxSuccessCallback | MySuccessCallback
57 |
58 | if (platformName === 'my') {
59 | successOptions = {
60 | status: 200,
61 | data: responseData,
62 | headers: {}
63 | } as MySuccessCallback
64 | } else {
65 | successOptions = {
66 | statusCode: 200,
67 | data: responseData,
68 | header: {}
69 | } as WxSuccessCallback
70 | }
71 |
72 | if (isFunction(opts.success) || isFunction(opts.complete)) {
73 | setTimeout(() => {
74 | isFunction(opts.success) && opts.success(successOptions)
75 | isFunction(opts.complete) && opts.complete(successOptions)
76 | }, setting.parseTimeout())
77 |
78 | }
79 | }
80 |
81 | // 覆盖原生的 request 方法
82 | function overrideRequest () {
83 | if (!platform.global.request.__MOCK__) {
84 | // 小程序 API 做了 setter 限制,不能直接复制
85 | Object.defineProperty(platform.global, 'request', {
86 | configurable: true,
87 | enumerable: true,
88 | writable: true,
89 | value: MockRequest
90 | })
91 | platform.global.request.__MOCK__ = true
92 | }
93 | }
94 |
95 | export {
96 | overrideRequest,
97 | getMpPlatform
98 | }
99 |
100 | declare global {
101 | let wx: any
102 | let my: any
103 | let tt: any
104 | let swan: any
105 | }
106 |
--------------------------------------------------------------------------------
/src/platform/mp/types.ts:
--------------------------------------------------------------------------------
1 |
2 | export type PlatformName = 'wx' | 'my' | 'tt' | 'swan'
3 |
4 | export type MpRequest = { __MOCK__?: boolean } & ((options: MpRequestOptions) => any)
5 |
6 | export interface MpRequestOptions {
7 | url: string;
8 | data: object;
9 | method: string;
10 | header?: object;
11 | headers?: object;
12 | dataType: string;
13 | responseType: string;
14 | success: (res: WxSuccessCallback | MySuccessCallback) => any;
15 | fail: (err: any) => any;
16 | complete: (res: WxSuccessCallback | MySuccessCallback) => any;
17 | }
18 |
19 | export interface MpGlobal {
20 | request: MpRequest;
21 | httpRequest?: MpRequest;
22 | [prop: string]: any;
23 | }
24 |
25 | // 微信成功时回调函数参数列表
26 | export interface WxSuccessCallback {
27 | data: object;
28 | statusCode: number;
29 | header: object;
30 | }
31 |
32 | // 支付宝成功时回调函数参数列表
33 | export interface MySuccessCallback {
34 | data: object;
35 | status: number;
36 | headers: object;
37 | }
--------------------------------------------------------------------------------
/src/platform/node/index.ts:
--------------------------------------------------------------------------------
1 | // For Node.js
2 | import Handler from '../../core/handler'
3 | import toJSONSchema from '../../core/schema'
4 | import RE from '../../core/regexp'
5 | import valid from '../../core/valid'
6 | import setting from '../../core/setting'
7 | import * as Util from '../../utils'
8 | import Random from '../../random'
9 | import Transfer from '../../transfer'
10 |
11 | const Mock = {
12 | Handler,
13 | Random,
14 | Transfer,
15 | Util,
16 | RE,
17 | toJSONSchema,
18 | valid,
19 | mock,
20 | heredoc: Util.heredoc,
21 | setup: setting.setup.bind(setting),
22 | version: '__VERSION__'
23 | }
24 |
25 |
26 | // Mock.mock( template )
27 | // 根据数据模板生成模拟数据。
28 | function mock (template: string) {
29 | return Handler.gen(template)
30 | }
31 |
32 | export default Mock
33 |
--------------------------------------------------------------------------------
/src/random/address.ts:
--------------------------------------------------------------------------------
1 | import * as helper from './helper'
2 | import * as basic from './basic'
3 | import * as location from 'china-location/dist/location.json'
4 |
5 | const REGION = ['东北', '华北', '华东', '华中', '华南', '西南', '西北']
6 | const areas = location['default']
7 |
8 | // 随机生成一个大区。
9 | export const region = function (): string {
10 | return helper.pick(REGION)
11 | }
12 |
13 | // 随机生成一个(中国)省(或直辖市、自治区、特别行政区)。
14 | export const province = function (): string {
15 | return helper.pickMap(areas).name
16 | }
17 |
18 | /**
19 | * 随机生成一个(中国)市。
20 | * @param prefix 是否有省前缀
21 | */
22 | export const city = function (prefix: boolean = false): string {
23 | const province = helper.pickMap(areas)
24 | const city = helper.pickMap(province.cities)
25 | return prefix ? [province.name, city.name].join(' ') : city.name
26 | }
27 |
28 | /**
29 | * 随机生成一个(中国)县。
30 | * @param prefix 是否有省/市前缀
31 | */
32 | export const county = function (prefix: boolean = false): string {
33 | // 直筒子市,无区县
34 | // https://baike.baidu.com/item/%E7%9B%B4%E7%AD%92%E5%AD%90%E5%B8%82
35 | const specialCity = ['460400', '441900', '442000', '620200']
36 | const province = helper.pickMap(areas)
37 | const city = helper.pickMap(province.cities)
38 | /* istanbul ignore next */
39 | if (specialCity.indexOf(city.code) !== -1) {
40 | return county(prefix)
41 | }
42 | const district = helper.pickMap(city.districts) || '-'
43 | return prefix ? [province.name, city.name, district].join(' ') : district
44 | }
45 |
46 | /**
47 | * 随机生成一个邮政编码(默认6位数字)。
48 | * @param len
49 | */
50 | export const zip = function (len: number = 6): string {
51 | let zip = ''
52 | for (let i = 0; i < len; i++) {
53 | zip += basic.natural(0, 9)
54 | }
55 | return zip
56 | }
57 |
--------------------------------------------------------------------------------
/src/random/basic.ts:
--------------------------------------------------------------------------------
1 | import { isDef } from '../utils'
2 |
3 | const MAX_NATURE_NUMBER = 9007199254740992
4 | const MIN_NATURE_NUMBER = -9007199254740992
5 |
6 | // 返回一个随机的布尔值。
7 | export const boolean = function (min: number = 1, max: number = 1, current?: boolean): boolean {
8 | if (isDef(current)) {
9 | if (isDef(min)) {
10 | min = !isNaN(min) ? parseInt(min.toString(), 10) : 1
11 | }
12 | if (isDef(max)) {
13 | max = !isNaN(max) ? parseInt(max.toString(), 10) : 1
14 | }
15 | return Math.random() > 1.0 / (min + max) * min ? !current : current!
16 | }
17 |
18 | return Math.random() >= 0.5
19 | }
20 |
21 | export const bool = boolean
22 |
23 | // 返回一个随机的自然数(大于等于 0 的整数)。
24 | export const natural = function (min: number = 0, max: number = MAX_NATURE_NUMBER): number {
25 | min = parseInt(min.toString(), 10)
26 | max = parseInt(max.toString(), 10)
27 | return Math.round(Math.random() * (max - min)) + min
28 | }
29 |
30 | // 返回一个随机的整数。
31 | export const integer = function (min: number = MIN_NATURE_NUMBER, max: number = MAX_NATURE_NUMBER): number {
32 | min = parseInt(min.toString(), 10)
33 | max = parseInt(max.toString(), 10)
34 | return Math.round(Math.random() * (max - min)) + min
35 | }
36 |
37 | export const int = integer
38 |
39 | // 返回一个随机的浮点数。
40 | export const float = function (min: number, max: number, dmin: number, dmax: number): number {
41 | dmin = isDef(dmin) ? dmin : 0
42 | dmin = Math.max(Math.min(dmin, 17), 0)
43 | dmax = isDef(dmax) ? dmax : 17
44 | dmax = Math.max(Math.min(dmax, 17), 0)
45 | let ret = integer(min, max) + '.'
46 | for (let i = 0, dcount = natural(dmin, dmax); i < dcount; i++) {
47 | // 最后一位不能为 0:如果最后一位为 0,会被 JS 引擎忽略掉。
48 | const num = i < dcount - 1 ? character('number') : character('123456789')
49 | ret += num
50 | }
51 | return parseFloat(ret)
52 | }
53 |
54 | // 返回一个随机字符。
55 | export const character = function (pool: string = ''): string {
56 | const lower = 'abcdefghijklmnopqrstuvwxyz'
57 | const upper = lower.toUpperCase()
58 | const number = '0123456789'
59 | const symbol = '!@#$%^&*()[]'
60 | const pools = {
61 | lower,
62 | upper,
63 | number,
64 | symbol,
65 | alpha: lower + upper
66 | }
67 | if (!pool) {
68 | pool = lower + upper + number + symbol
69 | } else {
70 | pool = pools[pool.toLowerCase()] || pool
71 | }
72 | return pool.charAt(natural(0, pool.length - 1))
73 | }
74 |
75 | export const char = character
76 |
77 | // 返回一个随机字符串。
78 | export const string = function (pool: any, min?: number, max?: number): string {
79 | let len
80 | switch (arguments.length) {
81 | case 0: // ()
82 | len = natural(3, 7)
83 | break
84 | case 1: // ( length )
85 | len = pool
86 | pool = undefined
87 | break
88 | case 2:
89 | // ( pool, length )
90 | if (typeof arguments[0] === 'string') {
91 | len = min
92 | } else {
93 | // ( min, max )
94 | len = natural(pool, min)
95 | pool = undefined
96 | }
97 | break
98 | case 3:
99 | len = natural(min, max)
100 | break
101 | }
102 |
103 | let text = ''
104 | for (let i = 0; i < len; i++) {
105 | text += character(pool)
106 | }
107 |
108 | return text
109 | }
110 |
111 | export const str = string
112 |
113 | // 返回一个整型数组。
114 | export const range = function (start: number, stop: number, step: number = 1): number[] {
115 | // range( stop )
116 | if (arguments.length <= 1) {
117 | stop = start || 0
118 | start = 0
119 | }
120 |
121 | start = +start
122 | stop = +stop
123 | step = +step
124 |
125 | let idx = 0
126 | const len = Math.max(Math.ceil((stop - start) / step), 0)
127 | const range = new Array(len)
128 |
129 | while (idx < len) {
130 | range[idx++] = start
131 | start += step
132 | }
133 |
134 | return range
135 | }
136 |
--------------------------------------------------------------------------------
/src/random/color-convert.ts:
--------------------------------------------------------------------------------
1 | // 颜色空间RGB与HSV(HSL)的转换
2 | // http://blog.csdn.net/idfaya/article/details/6770414
3 | // https://github.com/harthur/color-convert/blob/master/conversions.js
4 | export const rgb2hsl = function rgb2hsl (rgb) {
5 | let r = rgb[0] / 255,
6 | g = rgb[1] / 255,
7 | b = rgb[2] / 255,
8 | min = Math.min(r, g, b),
9 | max = Math.max(r, g, b),
10 | delta = max - min,
11 | h, s, l;
12 |
13 | if (max == min)
14 | h = 0;
15 | else if (r == max)
16 | h = (g - b) / delta;
17 | else if (g == max)
18 | h = 2 + (b - r) / delta;
19 | else if (b == max)
20 | h = 4 + (r - g) / delta;
21 |
22 | h = Math.min(h * 60, 360);
23 |
24 | if (h < 0)
25 | h += 360;
26 |
27 | l = (min + max) / 2;
28 |
29 | if (max == min)
30 | s = 0;
31 | else if (l <= 0.5)
32 | s = delta / (max + min);
33 | else
34 | s = delta / (2 - max - min);
35 |
36 | return [h, s * 100, l * 100];
37 | }
38 |
39 | export const rgb2hsv = function rgb2hsv (rgb) {
40 | let r = rgb[0],
41 | g = rgb[1],
42 | b = rgb[2],
43 | min = Math.min(r, g, b),
44 | max = Math.max(r, g, b),
45 | delta = max - min,
46 | h, s, v;
47 |
48 | if (max === 0)
49 | s = 0;
50 | else
51 | s = (delta / max * 1000) / 10;
52 |
53 | if (max == min)
54 | h = 0;
55 | else if (r == max)
56 | h = (g - b) / delta;
57 | else if (g == max)
58 | h = 2 + (b - r) / delta;
59 | else if (b == max)
60 | h = 4 + (r - g) / delta;
61 |
62 | h = Math.min(h * 60, 360);
63 |
64 | if (h < 0)
65 | h += 360;
66 |
67 | v = ((max / 255) * 1000) / 10;
68 |
69 | return [h, s, v];
70 | }
71 |
72 | export const hsl2rgb = function hsl2rgb (hsl) {
73 | let h = hsl[0] / 360,
74 | s = hsl[1] / 100,
75 | l = hsl[2] / 100,
76 | t1, t2, t3, rgb, val;
77 |
78 | if (s === 0) {
79 | val = l * 255;
80 | return [val, val, val];
81 | }
82 |
83 | if (l < 0.5)
84 | t2 = l * (1 + s);
85 | else
86 | t2 = l + s - l * s;
87 | t1 = 2 * l - t2;
88 |
89 | rgb = [0, 0, 0];
90 | for (let i = 0; i < 3; i++) {
91 | t3 = h + 1 / 3 * -(i - 1);
92 | if (t3 < 0) t3++;
93 | if (t3 > 1) t3--;
94 |
95 | if (6 * t3 < 1)
96 | val = t1 + (t2 - t1) * 6 * t3;
97 | else if (2 * t3 < 1)
98 | val = t2;
99 | else if (3 * t3 < 2)
100 | val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
101 | else
102 | val = t1;
103 |
104 | rgb[i] = val * 255;
105 | }
106 |
107 | return rgb;
108 | }
109 |
110 |
111 | export const hsl2hsv = function hsl2hsv (hsl) {
112 | let h = hsl[0],
113 | s = hsl[1] / 100,
114 | l = hsl[2] / 100,
115 | sv, v;
116 | l *= 2;
117 | s *= (l <= 1) ? l : 2 - l;
118 | v = (l + s) / 2;
119 | sv = (2 * s) / (l + s);
120 | return [h, sv * 100, v * 100];
121 | }
122 |
123 | export const hsv2rgb = function hsv2rgb(hsv) {
124 | let h = hsv[0] / 60
125 | let s = hsv[1] / 100
126 | let v = hsv[2] / 100
127 | let hi = Math.floor(h) % 6
128 |
129 | let f = h - Math.floor(h)
130 | let p = 255 * v * (1 - s)
131 | let q = 255 * v * (1 - (s * f))
132 | let 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 |
152 | export const hsv2hsl = function hsv2hsl (hsv) {
153 | let h = hsv[0],
154 | s = hsv[1] / 100,
155 | v = hsv[2] / 100,
156 | sl, l;
157 |
158 | l = (2 - s) * v;
159 | sl = s * v;
160 | sl /= (l <= 1) ? l : 2 - l;
161 | l /= 2;
162 | return [h, sl * 100, l * 100];
163 | }
164 |
165 | // http://www.140byt.es/keywords/color
166 | export const rgb2hex = function(
167 | a, // red, as a number from 0 to 255
168 | b, // green, as a number from 0 to 255
169 | c // blue, as a number from 0 to 255
170 | ): string {
171 | return "#" + ((256 + a << 8 | b) << 8 | c).toString(16).slice(1)
172 | }
173 |
174 | export const hex2rgb = function (a) {
175 | a = '0x' + a.slice(1).replace(a.length > 4 ? a : /./g, '$&$&') as any | 0;
176 | return [a >> 16, a >> 8 & 255, a & 255]
177 | }
178 |
--------------------------------------------------------------------------------
/src/random/color.ts:
--------------------------------------------------------------------------------
1 | // 颜色相关
2 | import * as convert from './color-convert'
3 |
4 | const colorMap = {
5 | navy: '#001F3F',
6 | blue: '#0074D9',
7 | aqua: '#7FDBFF',
8 | teal: '#39CCCC',
9 | olive: '#3D9970',
10 | green: '#2ECC40',
11 | lime: '#01FF70',
12 | yellow: '#FFDC00',
13 | orange: '#FF851B',
14 | red: '#FF4136',
15 | maroon: '#85144B',
16 | fuchsia: '#F012BE',
17 | purple: '#B10DC9',
18 | silver: '#DDDDDD',
19 | gray: '#AAAAAA',
20 | black: '#111111',
21 | white: '#FFFFFF'
22 | }
23 |
24 | // 随机生成一个有吸引力的颜色,格式为 '#RRGGBB'。
25 | export const color = function (name: string = '') {
26 | if (name && colorMap[name]) {
27 | return colorMap[name]
28 | }
29 | return hex()
30 | }
31 |
32 | // #DAC0DE
33 | export const hex = function (): string {
34 | const hsv = _goldenRatioColor()
35 | const rgb = convert.hsv2rgb(hsv)!
36 | return convert.rgb2hex(rgb[0], rgb[1], rgb[2])
37 | }
38 |
39 | // rgb(128,255,255)
40 | export const rgb = function (): string {
41 | const hsv = _goldenRatioColor()
42 | const rgb = convert.hsv2rgb(hsv)!
43 | return 'rgb(' +
44 | parseInt(rgb[0].toString(), 10) + ', ' +
45 | parseInt(rgb[1].toString(), 10) + ', ' +
46 | parseInt(rgb[2].toString(), 10) + ')'
47 | }
48 |
49 | // rgba(128,255,255,0.3)
50 | export const rgba = function (): string {
51 | const hsv = _goldenRatioColor()
52 | const rgb = convert.hsv2rgb(hsv)!
53 | return 'rgba(' +
54 | parseInt(rgb[0].toString(), 10) + ', ' +
55 | parseInt(rgb[1].toString(), 10) + ', ' +
56 | parseInt(rgb[2].toString(), 10) + ', ' +
57 | Math.random().toFixed(2) + ')'
58 | }
59 |
60 | // hsl(300,80%,90%)
61 | export const hsl = function (): string {
62 | const hsv = _goldenRatioColor()
63 | const hsl = convert.hsv2hsl(hsv)
64 | return 'hsl(' +
65 | parseInt(hsl[0], 10) + ', ' +
66 | parseInt(hsl[1], 10) + ', ' +
67 | parseInt(hsl[2], 10) + ')'
68 | }
69 |
70 | // http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
71 | // https://github.com/devongovett/color-generator/blob/master/index.js
72 | // 随机生成一个有吸引力的颜色。
73 | let _hue: number = 0
74 | const _goldenRatioColor = function (saturation?, value?) {
75 | const _goldenRatio = 0.618033988749895
76 | _hue = _hue || Math.random()
77 | _hue += _goldenRatio
78 | _hue %= 1
79 |
80 | if (typeof saturation !== "number") saturation = 0.5;
81 | if (typeof value !== "number") value = 0.95;
82 |
83 | return [
84 | _hue * 360,
85 | saturation * 100,
86 | value * 100
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/src/random/date.ts:
--------------------------------------------------------------------------------
1 | // Date
2 | import { keys } from '../utils'
3 |
4 | const _padZero = function (value: number): string {
5 | return value < 10 ? '0' + value : value.toString()
6 | }
7 |
8 | const patternLetters = {
9 | yyyy: 'getFullYear',
10 |
11 | yy: function (date: Date): string {
12 | return date.getFullYear().toString().slice(2)
13 | },
14 |
15 | y: 'yy',
16 |
17 | MM: function (date: Date): string {
18 | return _padZero(date.getMonth() + 1)
19 | },
20 |
21 | M: function (date: Date): string {
22 | return (date.getMonth() + 1).toString()
23 | },
24 |
25 | dd: function (date: Date): string {
26 | return _padZero(date.getDate())
27 | },
28 |
29 | d: 'getDate',
30 |
31 | HH: function (date: Date): string {
32 | return _padZero(date.getHours())
33 | },
34 |
35 | H: 'getHours',
36 |
37 | hh: function (date: Date): string {
38 | return _padZero(date.getHours() % 12)
39 | },
40 |
41 | h: function (date: Date): string {
42 | return (date.getHours() % 12).toString()
43 | },
44 |
45 | mm: function (date: Date): string {
46 | return _padZero(date.getMinutes())
47 | },
48 |
49 | m: 'getMinutes',
50 |
51 | ss: function (date: Date): string {
52 | return _padZero(date.getSeconds())
53 | },
54 |
55 | s: 'getSeconds',
56 |
57 | SS: function (date: Date): string {
58 | const ms = date.getMilliseconds()
59 | return ms < 10 && '00' + ms || ms < 100 && '0' + ms || ms.toString()
60 | },
61 |
62 | S: 'getMilliseconds',
63 |
64 | A: function (date: Date): string {
65 | return date.getHours() < 12 ? 'AM' : 'PM'
66 | },
67 |
68 | a: function (date: Date): string {
69 | return date.getHours() < 12 ? 'am' : 'pm'
70 | },
71 |
72 | T: 'getTime'
73 | }
74 |
75 | const _createFormatRE = function (): string {
76 | const re: string[] = keys(patternLetters)
77 | return '(' + re.join('|') + ')'
78 | }
79 |
80 | const _formatDate = function (date: Date, format: string): string {
81 | const formatRE = new RegExp(_createFormatRE(), 'g')
82 | return format.replace(formatRE, function createNewSubString ($0, flag) {
83 | return typeof patternLetters[flag] === 'function'
84 | ? patternLetters[flag](date)
85 | : patternLetters[flag] in patternLetters
86 | ? createNewSubString($0, patternLetters[flag])
87 | : date[patternLetters[flag]]()
88 | })
89 | }
90 |
91 | // 生成一个随机的 Date 对象。
92 | const _randomDate = function (min: Date = new Date(0), max: Date = new Date()): Date {
93 | const randomTS = Math.random() * (max.getTime() - min.getTime())
94 | return new Date(randomTS)
95 | }
96 |
97 | // 返回一个随机的日期字符串。
98 | export const date = function (format: string = 'yyyy-MM-dd'): string {
99 | return _formatDate(_randomDate(), format)
100 | }
101 |
102 | // 返回一个随机的时间字符串。
103 | export const time = function (format: string = 'HH:mm:ss'): string {
104 | return _formatDate(_randomDate(), format)
105 | }
106 |
107 | // 返回一个随机的日期和时间字符串。
108 | export const datetime = function (format: string = 'yyyy-MM-dd HH:mm:ss') {
109 | return _formatDate(_randomDate(), format)
110 | }
111 |
112 | // 返回一个随机的时间戳
113 | export const timestamp = function (): number {
114 | return Number(_formatDate(_randomDate(), 'T'))
115 | }
116 |
117 | // 返回当前的日期和时间字符串。
118 | export const now = function (unit: string, format: string): string {
119 | // now(unit) now(format)
120 | if (arguments.length === 1) {
121 | // now(format)
122 | if (!/year|month|day|hour|minute|second|week/.test(unit)) {
123 | format = unit
124 | unit = ''
125 | }
126 | }
127 | unit = (unit || '').toLowerCase()
128 | format = format || 'yyyy-MM-dd HH:mm:ss'
129 |
130 | const date = new Date()
131 |
132 | // 参考自 http://momentjs.cn/docs/#/manipulating/start-of/
133 | switch (unit) {
134 | case 'year':
135 | date.setMonth(0)
136 | break
137 | case 'month':
138 | date.setDate(1)
139 | break
140 | case 'week':
141 | date.setDate(date.getDate() - date.getDay())
142 | break
143 | case 'day':
144 | date.setHours(0)
145 | break
146 | case 'hour':
147 | date.setMinutes(0)
148 | break
149 | case 'minute':
150 | date.setSeconds(0)
151 | break
152 | case 'second':
153 | date.setMilliseconds(0)
154 | }
155 |
156 | return _formatDate(date, format)
157 | }
158 |
--------------------------------------------------------------------------------
/src/random/helper.ts:
--------------------------------------------------------------------------------
1 | import { isArray, isDef, values } from '../utils'
2 | import * as basic from './basic'
3 |
4 | // 把字符串的第一个字母转换为大写。
5 | export const capitalize = function (word: string): string {
6 | word = word + ''
7 | return word.charAt(0).toUpperCase() + word.substr(1)
8 | }
9 |
10 | // 把字符串转换为大写。
11 | export const upper = function (str: string): string {
12 | return (str + '').toUpperCase()
13 | }
14 |
15 | // 把字符串转换为小写。
16 | export const lower = function (str: string): string {
17 | return (str + '').toLowerCase()
18 | }
19 |
20 | // 从数组中随机选择一个
21 | export const pickOne = function (arr: T[]): T {
22 | return arr[basic.natural(0, arr.length - 1)]
23 | }
24 |
25 | // 从源数组中随机选取一个或多个元素。当传入 min、max 时会选择多个元素并组成数组
26 | export function pick(arr: T[]): T;
27 | export function pick(arr: T[], min: number): T[];
28 | export function pick(arr: T[], min: number, max?: number): T[];
29 | export function pick (arr: T[], min: number = 1, max?: number): T | T[] {
30 | // pick( item1, item2 ... )
31 | if (!isArray(arr)) {
32 | return pickOne(Array.from(arguments))
33 | }
34 |
35 | // pick( [ item1, item2 ... ], count )
36 | if (!isDef(max)) {
37 | max = min
38 | }
39 |
40 | if (min === 1 && max === 1) {
41 | return pickOne(arr)
42 | }
43 |
44 | // pick( [ item1, item2 ... ], min, max )
45 | return shuffle(arr, min, max)
46 | }
47 |
48 | // 从map中随机选择一个
49 | export const pickMap = function (map: object) {
50 | return pick(values(map))
51 | }
52 |
53 | // 打乱数组中元素的顺序,并按照 min - max 返回。
54 | export const shuffle = function (arr: T[], min?: number, max?: number): T[] {
55 | if (!Array.isArray(arr)) {
56 | return []
57 | }
58 | const copy = arr.slice()
59 | const length = arr.length
60 | for (let i = 0; i < length; i++) {
61 | const swapIndex = basic.natural(0, length - 1)
62 | const swapValue = copy[swapIndex]
63 | copy[swapIndex] = copy[i]
64 | copy[i] = swapValue
65 | }
66 | if (min && max) {
67 | return copy.slice(0, basic.natural(min, max))
68 | }
69 | if (min) {
70 | return copy.slice(0, min)
71 | }
72 | return copy
73 | }
74 |
--------------------------------------------------------------------------------
/src/random/image.ts:
--------------------------------------------------------------------------------
1 | // image
2 | import { pick } from './helper'
3 | import { isNumber, assert } from '../utils'
4 |
5 | // 常见图片尺寸
6 | const imageSize: string[] = [
7 | '150x100', '300x200', '400x300', '600x450', '800x600',
8 | '100x150', '200x300', '300x400', '450x600', '600x800',
9 | '100x100', '200x200', '300x300', '450x450', '600x600'
10 | ]
11 |
12 | /**
13 | * 随机生成一个图片,使用:http://iph.href.lu,例如:
14 | * https://iph.href.lu/600x400?fg=cc00cc&bg=470047&text=hello
15 | * @param size 图片大小
16 | * @param background 背景色
17 | * @param foreground 文字颜色
18 | * @param format 图片格式
19 | * @param text 文字
20 | */
21 | export const image = function (size = '', background = '', foreground = '', format = '', text = ''): string {
22 | // Random.image( size, background, foreground, text )
23 | if (arguments.length === 4) {
24 | text = format
25 | format = ''
26 | }
27 | // Random.image( size, background, text )
28 | if (arguments.length === 3) {
29 | text = foreground
30 | foreground = ''
31 | }
32 | // Random.image( size, text )
33 | if (arguments.length === 2) {
34 | text = background
35 | background = ''
36 | }
37 | // Random.image()
38 | size = size || pick(imageSize)
39 |
40 | if (background && ~background.indexOf('#')) {
41 | background = background.slice(1)
42 | }
43 |
44 | if (foreground && ~foreground.indexOf('#')) {
45 | foreground = foreground.slice(1)
46 | }
47 |
48 | return format
49 | ? (
50 | 'https://dummyimage.com/' +
51 | size +
52 | (background ? '/' + background : '') +
53 | (foreground ? '/' + foreground : '') +
54 | (format ? '.' + format : '') +
55 | (text ? '?text=' + encodeURIComponent(text) : '')
56 | )
57 | : `https://iph.href.lu/${size}?bg=${background}&fg=${foreground}&text=${text}`
58 | }
59 |
60 | export const img = image
61 |
62 | /**
63 | * 生成一个随机的base64图片
64 | * @param size 图片宽高
65 | * @param text 图片上的文字
66 | */
67 | export const dataImage = function (size?: string, text?: string): string {
68 | size = size || pick(imageSize)
69 | text = text || size
70 | const background: string = pick([
71 | '#171515', '#e47911', '#183693', '#720e9e', '#c4302b', '#dd4814',
72 | '#00acee', '#0071c5', '#3d9ae8', '#ec6231', '#003580', '#e51937'
73 | ])
74 | const sizes = size!.split('x')
75 | const width = parseInt(sizes[0], 10)
76 | const height = parseInt(sizes[1], 10)
77 |
78 | assert(isNumber(width) && isNumber(height), 'Invalid size, expected INTxINT, e.g. 300x400')
79 |
80 | if (process.env.PLATFORM_BROWSER) {
81 | return createBrowserDataImage(width, height, background, text!)
82 | } else if (process.env.PLATFORM_NODE) {
83 | return createNodeDataImage(width, height, background, text!)
84 | } else {
85 | // 小程序无法直接生成 base64 图片,返回空字符串
86 | return ''
87 | }
88 | }
89 |
90 | // browser 端生成 base64 图片
91 | function createBrowserDataImage (width: number, height: number, background: string, text: string) {
92 | const canvas = document.createElement('canvas')
93 | const ctx = canvas && canvas.getContext && canvas.getContext('2d')
94 | if (!canvas || !ctx) {
95 | return ''
96 | }
97 |
98 | canvas.width = width
99 | canvas.height = height
100 | ctx.textAlign = 'center'
101 | ctx.textBaseline = 'middle'
102 | ctx.fillStyle = background
103 | ctx.fillRect(0, 0, width, height)
104 | ctx.fillStyle = '#FFFFFF'
105 | ctx.font = 'bold 14px sans-serif'
106 | ctx.fillText(text!, width / 2, height / 2, width)
107 | return canvas.toDataURL('image/png')
108 | }
109 |
110 | // node 端生成 base64 图片
111 | // 从 faker.js 借鉴
112 | function createNodeDataImage (width: number, height: number, background: string, text: string) {
113 | const svgString = [
114 | ``
118 | ].join('');
119 | return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svgString);
120 | }
121 |
--------------------------------------------------------------------------------
/src/random/index.ts:
--------------------------------------------------------------------------------
1 | // Mock.Random
2 | import * as basic from './basic'
3 | import * as date from './date'
4 | import * as image from './image'
5 | import * as color from './color'
6 | import * as text from './text'
7 | import * as name from './name'
8 | import * as web from './web'
9 | import * as address from './address'
10 | import * as helper from './helper'
11 | import * as misc from './misc'
12 | import { isObject } from '../utils'
13 |
14 | const random = {
15 | extend: extendFunc,
16 | ...basic,
17 | ...date,
18 | ...image,
19 | ...color,
20 | ...text,
21 | ...name,
22 | ...web,
23 | ...address,
24 | ...helper,
25 | ...misc
26 | }
27 |
28 | function extendFunc (source: object) {
29 | if (isObject(source)) {
30 | for (const key in source) {
31 | random[key] = source[key]
32 | }
33 | }
34 | }
35 |
36 | export default random
37 |
--------------------------------------------------------------------------------
/src/random/misc.ts:
--------------------------------------------------------------------------------
1 | // Miscellaneous
2 | import * as basic from './basic'
3 | import * as helper from './helper'
4 | import * as date from './date'
5 | import * as location from 'china-location/dist/location.json'
6 | import * as util from '../utils'
7 |
8 | const areas = location['default']
9 |
10 | // 随机生成一个 guid
11 | // http://www.broofa.com/2008/09/javascript-uuid-function/
12 | export const guid = function (): string {
13 | const pool = 'abcdefABCDEF1234567890'
14 | return basic.string(pool, 8) + '-' + basic.string(pool, 4) + '-' + basic.string(pool, 4) + '-' + basic.string(pool, 4) + '-' + basic.string(pool, 12)
15 | }
16 |
17 | export const uuid = guid
18 |
19 | // 随机生成一个 18 位身份证。
20 | // http://baike.baidu.com/view/1697.htm#4
21 | // [身份证](http://baike.baidu.com/view/1697.htm#4)
22 | // 地址码 6 + 出生日期码 8 + 顺序码 3 + 校验码 1
23 | // [《中华人民共和国行政区划代码》国家标准(GB/T2260)](http://zhidao.baidu.com/question/1954561.html)
24 | export const id = function (): string {
25 | let _id
26 | let _sum = 0
27 | const rank: string[] = ['7', '9', '10', '5', '8', '4', '2', '1', '6', '3', '7', '9', '10', '5', '8', '4', '2']
28 | const last: string[] = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
29 | // 直筒子市,无区县
30 | // https://baike.baidu.com/item/%E7%9B%B4%E7%AD%92%E5%AD%90%E5%B8%82
31 | const specialCity = ['460400', '441900', '442000', '620200']
32 |
33 | const province = helper.pickMap(areas)
34 | const city = helper.pickMap(province.cities)
35 | /* istanbul ignore next */
36 | if (specialCity.indexOf(city.code) !== -1) {
37 | return id()
38 | }
39 | const districts = city.districts
40 | const district = helper.pick(util.keys(districts))
41 |
42 | _id = district + date.date('yyyyMMdd') + basic.string('number', 3)
43 |
44 | for (let i = 0; i < _id.length; i++) {
45 | _sum += _id[i] * Number(rank[i])
46 | }
47 | _id += last[_sum % 11]
48 |
49 | return _id
50 | }
51 |
52 | // 生成一个全局的自增整数。
53 | // 类似自增主键(auto increment primary key)。
54 | let key = 0
55 | export const increment = function (step: number | string) {
56 | return key += (Number(step) || 1) // step?
57 | }
58 |
59 | export const inc = increment
60 |
61 | /**
62 | * 随机生成一个版本号
63 | * @param depth 版本号的层级,默认为3
64 | */
65 | export const version = function (depth: number = 3): string {
66 | const numbers: number[] = []
67 | for (let i = 0; i < depth; i++) {
68 | numbers.push(basic.natural(0, 10))
69 | }
70 | return numbers.join('.')
71 | }
72 |
73 | // 随机生成一个中国手机号
74 | export const phone = function (): string {
75 | const segments: string[] = [
76 | // 移动号段
77 | '134', '135', '136', '137', '138', '139', '147', '150', '151', '152', '157', '158', '159', '165', '172', '178', '182', '183', '184', '187', '188',
78 | // 联通号段
79 | '130', '131', '132', '145', '155', '156', '171', '175', '176', '185', '186',
80 | // 电信号段
81 | '133', '149', '153', '173', '174', '177', '180', '181', '189', '191'
82 | ]
83 | return helper.pick(segments) + basic.string('number', 8)
84 | }
85 |
--------------------------------------------------------------------------------
/src/random/name.ts:
--------------------------------------------------------------------------------
1 | import * as helper from './helper'
2 |
3 | // 随机生成一个常见的英文名。
4 | export const first = function (): string {
5 | const male = [
6 | "James", "John", "Robert", "Michael", "William",
7 | "David", "Richard", "Charles", "Joseph", "Thomas",
8 | "Christopher", "Daniel", "Paul", "Mark", "Donald",
9 | "George", "Kenneth", "Steven", "Edward", "Brian",
10 | "Ronald", "Anthony", "Kevin", "Jason", "Matthew",
11 | "Gary", "Timothy", "Jose", "Larry", "Jeffrey",
12 | "Frank", "Scott", "Eric"
13 | ]
14 | const female = [
15 | "Mary", "Patricia", "Linda", "Barbara", "Elizabeth",
16 | "Jennifer", "Maria", "Susan", "Margaret", "Dorothy",
17 | "Lisa", "Nancy", "Karen", "Betty", "Helen",
18 | "Sandra", "Donna", "Carol", "Ruth", "Sharon",
19 | "Michelle", "Laura", "Sarah", "Kimberly", "Deborah",
20 | "Jessica", "Shirley", "Cynthia", "Angela", "Melissa",
21 | "Brenda", "Amy", "Anna"
22 | ]
23 | return helper.pick([...male, ...female])
24 | }
25 |
26 | // 随机生成一个常见的英文姓。
27 | export const last = function (): string {
28 | const names = [
29 | "Smith", "Johnson", "Williams", "Brown", "Jones",
30 | "Miller", "Davis", "Garcia", "Rodriguez", "Wilson",
31 | "Martinez", "Anderson", "Taylor", "Thomas", "Hernandez",
32 | "Moore", "Martin", "Jackson", "Thompson", "White",
33 | "Lopez", "Lee", "Gonzalez", "Harris", "Clark",
34 | "Lewis", "Robinson", "Walker", "Perez", "Hall",
35 | "Young", "Allen"
36 | ]
37 | return helper.pick(names)
38 | }
39 |
40 | // 随机生成一个常见的英文姓名。
41 | export const name = function (middle: boolean= false): string {
42 | return first() + ' ' + (middle ? first() + ' ' : '') + last()
43 | }
44 |
45 | // 随机生成一个常见的中文姓。
46 | // [世界常用姓氏排行](http://baike.baidu.com/view/1719115.htm)
47 | // [玄派网 - 网络小说创作辅助平台](http://xuanpai.sinaapp.com/)
48 | export const cfirst = function (): string {
49 | const names = [
50 | "王", "李", "张", "刘", "陈", "杨", "赵", "黄",
51 | "周", "吴", "徐", "孙", "胡", "朱", "高", "林",
52 | "何", "郭", "马", "罗", "梁", "宋", "郑", "谢",
53 | "韩", "唐", "冯", "于", "董", "萧", "程", "曹",
54 | "袁", "邓", "许", "傅", "沈", "曾", "彭", "吕",
55 | "苏", "卢", "蒋", "蔡", "贾", "丁", "魏", "薛",
56 | "叶", "阎", "余", "潘", "杜", "戴", "夏", "锺",
57 | "汪", "田", "任", "姜", "范", "方", "石", "姚",
58 | "谭", "廖", "邹", "熊", "金", "陆", "郝", "孔",
59 | "白", "崔", "康", "毛", "邱", "秦", "江", "史",
60 | "顾", "侯", "邵", "孟", "龙", "万", "段", "雷",
61 | "钱", "汤", "尹", "黎", "易", "常", "武", "乔",
62 | "贺", "赖", "龚", "文"
63 | ]
64 | return helper.pick(names)
65 | }
66 |
67 | // 随机生成一个常见的中文名。
68 | // [中国最常见名字前50名_三九算命网](http://www.name999.net/xingming/xingshi/20131004/48.html)
69 | export const clast = function (): string {
70 | const names = [
71 | "伟", "芳", "娜", "秀英", "敏", "静", "丽", "强",
72 | "磊", "军", "洋", "勇", "艳", "杰", "娟", "涛",
73 | "明", "超", "秀兰", "霞", "平", "刚", "桂英"
74 | ]
75 | return helper.pick(names)
76 | }
77 |
78 | // 随机生成一个常见的中文姓名。
79 | export const cname = function (): string {
80 | return cfirst() + clast()
81 | }
82 |
--------------------------------------------------------------------------------
/src/random/text.ts:
--------------------------------------------------------------------------------
1 | import { isDef } from '../utils'
2 | import * as basic from './basic'
3 | import * as helper from './helper'
4 | import stringToArray from "../utils/string-to-array";
5 |
6 | const _range = function (defaultMin: number, defaultMax: number, min?: number, max?: number): number {
7 | return !isDef(min)
8 | ? basic.natural(defaultMin, defaultMax)
9 | : !isDef(max)
10 | ? min!
11 | : basic.natural(parseInt(min!.toString(), 10), parseInt(max!.toString(), 10)) // ( min, max )
12 | }
13 |
14 | // 随机生成一段文本。
15 | export const paragraph = function (min?: number, max?: number): string {
16 | const len = _range(3, 7, min, max)
17 | const result: string[] = []
18 | for (let i = 0; i < len; i++) {
19 | result.push(sentence())
20 | }
21 | return result.join(' ')
22 | }
23 |
24 | export const cparagraph = function (min?: number, max?: number): string {
25 | const len = _range(3, 7, min, max)
26 | const result: string[] = []
27 | for (let i = 0; i < len; i++) {
28 | result.push(csentence())
29 | }
30 | return result.join('')
31 | }
32 |
33 | // 随机生成一个句子,第一个单词的首字母大写。
34 | export const sentence = function (min?: number, max?: number): string {
35 | const len = _range(12, 18, min, max)
36 | const result: string[] = []
37 | for (let i = 0; i < len; i++) {
38 | result.push(word())
39 | }
40 | return helper.capitalize(result.join(' ')) + '.'
41 | }
42 |
43 | // 随机生成一个中文句子。
44 | export const csentence = function (min?: number, max?: number): string {
45 | const len = _range(12, 18, min, max)
46 | const result: string[] = []
47 | for (let i = 0; i < len; i++) {
48 | result.push(cword())
49 | }
50 |
51 | return result.join('') + '。'
52 | }
53 |
54 | // 随机生成一个单词。
55 | export const word = function (min?: number, max?: number): string {
56 | const len = _range(3, 10, min, max)
57 | let result = ''
58 | for (let i = 0; i < len; i++) {
59 | result += basic.character('lower')
60 | }
61 | return result
62 | }
63 |
64 | // 随机生成一个或多个汉字。
65 | export const cword = function (pool: string = '', min?: number, max?: number): string {
66 | // 最常用的 500 个汉字 http://baike.baidu.com/view/568436.htm
67 | const cnWords = '的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞'
68 |
69 | let len
70 | switch (arguments.length) {
71 | case 0: // ()
72 | pool = cnWords
73 | len = 1
74 | break
75 | case 1: // ( pool )
76 | if (typeof arguments[0] === 'string') {
77 | len = 1
78 | } else {
79 | // ( length )
80 | len = pool
81 | pool = cnWords
82 | }
83 | break
84 | case 2:
85 | // ( pool, length )
86 | if (typeof arguments[0] === 'string') {
87 | len = min
88 | } else {
89 | // ( min, max )
90 | len = basic.natural(parseInt(pool, 10), min)
91 | pool = cnWords
92 | }
93 | break
94 | case 3:
95 | len = basic.natural(min, max)
96 | break
97 | }
98 |
99 | let result = ''
100 | for (let i = 0; i < len; i++) {
101 | result += pool.charAt(basic.natural(0, pool.length - 1))
102 | }
103 | return result
104 | }
105 |
106 | // 随机生成一个或多个 emoji 符号
107 | export const emoji = function (pool?: string | number, min?: number, max?: number) {
108 | if (!['string', 'number', 'undefined'].includes(typeof pool)){
109 | return ''
110 | }
111 | // 常用的 338 个emoji符号 http://www.fhdq.net/emoji.html
112 | const emojis = '😀😁😂😃😄😅😆😉😊😋😎😍😘😗😙😚☺😇😐😑😶😏😣😥😮😯😪😫😴😌😛😜😝😒😓😔😕😲😷😖😞😟😤😢😭😦😧😨😬😰😱😳😵😡😠😈👿👹👺💀👻👽👦👧👨👩👴👵👶👱👮👲👳👷👸💂🎅👰👼💆💇🙍🙎🙅🙆💁🙋🙇🙌🙏👤👥🚶🏃👯💃👫👬👭💏💑👪💪👈👉☝👆👇✌✋👌👍👎✊👊👋👏👐✍👣👀👂👃👅👄💋👓👔👕👖👗👘👙👚👛👜👝🎒💼👞👟👠👡👢👑👒🎩🎓💄💅💍🌂🙈🙉🙊🐵🐒🐶🐕🐩🐺🐱😺😸😹😻😼😽🙀😿😾🐈🐯🐅🐆🐴🐎🐮🐂🐃🐄🐷🐖🐗🐽🐏🐑🐐🐪🐫🐘🐭🐁🐀🐹🐰🐇🐻🐨🐼🐾🐔🐓🐣🐤🐥🐦🐧🐸🐊🐢🐍🐲🐉🐳🐋🐬🐟🐠🐡🐙🐚🐌🐛🐜🐝🐞💐🌸💮🌹🌺🌻🌼🌷🌱🌲🌳🌴🌵🌾🌿🍀🍁🍂🍃🌍🌎🌏🌐🌑🌒🌓🌔🌕🌖🌗🌘🌙🌚🌛🌜☀🌝🌞⭐🌟🌠☁⛅☔⚡❄🔥💧🌊💩🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍅🍆🌽🍄🌰🍞🍖🍗🍔🍟🍕🍳🍲🍱🍘🍙🍚🍛🍜🍝🍠🍢🍣🍤🍥🍡🍦🍧🍨🍩🍪🎂🍰🍫🍬🍭🍮🍯🍼☕🍵🍶🍷🍸🍹🍺🍻🍴'
113 | let array = stringToArray(emojis)
114 | if (typeof pool === 'string') { // emoji("😀😁😂"), emoji("😀😂", 2), emoji("😀😂", 2, 3)
115 | array = stringToArray(pool)
116 | } else if (typeof pool === 'number') { // emoji(2), emoji(2, 3)
117 | max = min
118 | min = pool
119 | }
120 | if (min === undefined || min < 2){ // emoji("😀😁😂"), emoji()
121 | return helper.pick(array) // pick(['1', '2']) => "2", pick(['1', '2'], 1) => "2"
122 | }
123 | return helper.pick(array, min, max).join('')
124 | }
125 |
126 | // 随机生成一句标题,其中每个单词的首字母大写。
127 | export const title = function (min?: number, max?: number): string {
128 | const len = _range(3, 7, min, max)
129 | const result: string[] = []
130 | for (let i = 0; i < len; i++) {
131 | result.push(helper.capitalize(word()))
132 | }
133 | return result.join(' ')
134 | }
135 |
136 | // 随机生成一句中文标题。
137 | export const ctitle = function (min?: number, max?: number): string {
138 | const len = _range(3, 7, min, max)
139 | const result: string[] = []
140 | for (let i = 0; i < len; i++) {
141 | result.push(cword())
142 | }
143 | return result.join('')
144 | }
145 |
--------------------------------------------------------------------------------
/src/random/web.ts:
--------------------------------------------------------------------------------
1 | import * as helper from './helper'
2 | import * as text from './text'
3 | import * as basic from './basic'
4 |
5 | // 随机生成一个 URL。
6 | export const url = function (_protocol: string = protocol(), host: string = domain()): string {
7 | return `${_protocol}://${host}/${text.word()}`
8 | }
9 |
10 | // 随机生成一个 URL 协议。
11 | export const protocol = function (): string {
12 | // 协议簇
13 | const protocols = [
14 | 'http', 'ftp', 'gopher', 'mailto', 'mid', 'cid', 'news', 'nntp',
15 | 'prospero', 'telnet', 'rlogin', 'tn3270', 'wais'
16 | ]
17 | return helper.pick(protocols)
18 | }
19 |
20 | // 随机生成一个域名。
21 | export const domain = function (_tld: string = tld()): string {
22 | return text.word() + '.' + _tld
23 | }
24 |
25 | // 随机生成一个顶级域名。
26 | // [域名后缀大全](http://www.163ns.com/zixun/post/4417.html)
27 | export const tld = function (): string {
28 | const tlds = (
29 | // 域名后缀
30 | 'com net org edu gov int mil cn ' +
31 | // 国内域名
32 | 'com.cn net.cn gov.cn org.cn ' +
33 | // 中文国内域名
34 | '中国 中国互联.公司 中国互联.网络 ' +
35 | // 新国际域名
36 | 'tel biz cc tv info name hk mobi asia cd travel pro museum coop aero ' +
37 | // 世界各国域名后缀
38 | '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'
39 | ).split(' ')
40 | return helper.pick(tlds)
41 | }
42 |
43 | // 随机生成一个邮件地址。
44 | export const email = function (_domain: string = domain()): string {
45 | return basic.character('lower') + '.' + text.word() + '@' + _domain
46 | }
47 |
48 | // 随机生成一个 IP 地址。
49 | export const ip = function (): string {
50 | return basic.natural(0, 255) + '.' +
51 | basic.natural(0, 255) + '.' +
52 | basic.natural(0, 255) + '.' +
53 | basic.natural(0, 255)
54 | }
55 |
--------------------------------------------------------------------------------
/src/transfer/index.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '../utils'
2 |
3 | const number = Number
4 |
5 | const boolean = Boolean
6 |
7 | const string = String
8 |
9 | const transfer = {
10 | number,
11 | boolean,
12 | string,
13 | extend
14 | }
15 |
16 | function extend (source: object) {
17 | if (isObject(source)) {
18 | for (const key in source) {
19 | transfer[key] = source[key]
20 | }
21 | }
22 | }
23 |
24 | export default transfer
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { parse } from '../core/parser'
2 |
3 | export type StringObject = {
4 | [key: string]: string;
5 | } | {}
6 |
7 | export interface MockedItem {
8 | rurl: string | RegExp | undefined;
9 | rtype: string | RegExp | undefined;
10 | template: object | Function | undefined;
11 | }
12 |
13 | export interface Mocked {
14 | [key: string]: MockedItem;
15 | }
16 |
17 | export interface XHRCustomOptions {
18 | url: string;
19 | type: string;
20 | body: XHRBody;
21 | headers: StringObject;
22 | }
23 |
24 | export type XHRBody = Document | BodyInit | null
25 |
26 | export interface XHRCustom {
27 | events: {
28 | [event: string]: Function[];
29 | };
30 | requestHeaders: StringObject;
31 | responseHeaders: StringObject;
32 | timeout: number;
33 | options: Partial;
34 | xhr: XMLHttpRequest;
35 | template: MockedItem | null;
36 | async: boolean;
37 | }
38 |
39 | export interface Settings {
40 | timeout: string | number;
41 | }
42 |
43 | export interface SchemaResult {
44 | name: string | number | undefined;
45 | template: object | string | (string | object)[];
46 | type: string;
47 | rule: ReturnType;
48 | path: string[];
49 | items?: SchemaResult[];
50 | properties?: SchemaResult[];
51 | }
52 |
53 | export interface DiffResult {
54 | path: string[];
55 | type: string;
56 | actual: any;
57 | expected: any;
58 | action: string;
59 | message: string | undefined;
60 | }
61 |
--------------------------------------------------------------------------------
/src/utils/constant.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | GUID: 1,
3 | RE_KEY: /(.+)\|(?:\+(\d+)|([\+\-]?\d+-?[\+\-]?\d*)?(?:\.(\d+-?\d*))?)/,
4 | RE_TRANSFER_TYPE: /#(.*)$/,
5 | RE_RANGE: /([\+\-]?\d+)-?([\+\-]?\d+)?/,
6 | RE_PLACEHOLDER: /\\*@([^@#%&()\?\s]+)(?:\((.*?)\))?/g
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /* type-coverage:ignore-next-line */
2 | export const type = function (value: any): string {
3 | return isDef(value)
4 | ? Object.prototype.toString.call(value).match(/\[object (\w+)\]/)![1].toLowerCase()
5 | : String(value)
6 | }
7 |
8 | export const isDef = function (value: unknown): boolean {
9 | return value !== undefined && value !== null
10 | }
11 |
12 | export const isString = function (value: unknown): value is string {
13 | return type(value) === 'string'
14 | }
15 |
16 | export const isNumber = function (value: unknown): value is number {
17 | return type(value) === 'number'
18 | }
19 |
20 | export const isObject = function (value: unknown): value is object {
21 | return type(value) === 'object'
22 | }
23 |
24 | export const isArray = function (value: unknown): value is Array {
25 | return type(value) === 'array'
26 | }
27 |
28 | export const isRegExp = function (value: unknown): value is RegExp {
29 | return type(value) === 'regexp'
30 | }
31 |
32 | export const isFunction = function (value: unknown): value is Function {
33 | return type(value) === 'function'
34 | }
35 |
36 | export const keys = function (obj: object): string[] {
37 | const keys: string[] = []
38 | for (const key in obj) {
39 | if (obj.hasOwnProperty(key)) {
40 | keys.push(key)
41 | }
42 | }
43 | return keys
44 | }
45 |
46 | export const values = function (obj: object) {
47 | const values: any[] = []
48 | for (const key in obj) {
49 | if (obj.hasOwnProperty(key)) {
50 | values.push(obj[key])
51 | }
52 | }
53 | return values
54 | }
55 |
56 | /**
57 | * Mock.heredoc(fn)
58 | * 以直观、安全的方式书写(多行)HTML 模板。
59 | * http://stackoverflow.com/questions/805107/creating-multiline-strings-in-javascript
60 | */
61 | export const heredoc = function (fn: Function) {
62 | // 1. 移除起始的 function(){ /*!
63 | // 2. 移除末尾的 */ }
64 | // 3. 移除起始和末尾的空格
65 | return fn
66 | .toString()
67 | .replace(/^[^\/]+\/\*!?/, '')
68 | .replace(/\*\/[^\/]+$/, '')
69 | .replace(/^[\s\xA0]+/, '')
70 | .replace(/[\s\xA0]+$/, '') // .trim()
71 | }
72 |
73 | export const noop = function () {}
74 |
75 | export const assert = function (condition: any, error: string) {
76 | if (!condition) {
77 | throw new Error('[better-mock] ' + error)
78 | }
79 | }
80 |
81 | /**
82 | * 创建一个自定义事件,兼容 IE
83 | * @param type 一个字符串,表示事件名称。
84 | * @param bubbles 一个布尔值,表示该事件能否冒泡。
85 | * @param cancelable 一个布尔值,表示该事件是否可以取消。
86 | * @param detail 一个任意类型,传递给事件的自定义数据。
87 | */
88 | export const createCustomEvent = function (type: string, bubbles = false, cancelable = false, detail?: T): CustomEvent {
89 | try {
90 | return new CustomEvent(type, { bubbles, cancelable, detail })
91 | } catch (e) {
92 | const event: CustomEvent = document.createEvent('CustomEvent')
93 | event.initCustomEvent(type, bubbles, cancelable, detail!)
94 | return event
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/utils/string-to-array.ts:
--------------------------------------------------------------------------------
1 | /** Used to compose unicode character classes. */
2 | const rsAstralRange = '\\ud800-\\udfff'
3 | const rsComboMarksRange = '\\u0300-\\u036f'
4 | const reComboHalfMarksRange = '\\ufe20-\\ufe2f'
5 | const rsComboSymbolsRange = '\\u20d0-\\u20ff'
6 | const rsComboMarksExtendedRange = '\\u1ab0-\\u1aff'
7 | const rsComboMarksSupplementRange = '\\u1dc0-\\u1dff'
8 | const rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange
9 | const rsVarRange = '\\ufe0e\\ufe0f'
10 |
11 | /** Used to compose unicode capture groups. */
12 | const rsZWJ = '\\u200d'
13 | const rsAstral = `[${rsAstralRange}]`
14 | const rsCombo = `[${rsComboRange}]`
15 | const rsFitz = '\\ud83c[\\udffb-\\udfff]'
16 | const rsModifier = `(?:${rsCombo}|${rsFitz})`
17 | const rsNonAstral = `[^${rsAstralRange}]`
18 | const rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}'
19 | const rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]'
20 |
21 | /** Used to compose unicode regexes. */
22 | const reOptMod = `${rsModifier}?`
23 | const rsOptVar = `[${rsVarRange}]?`
24 | const rsOptJoin = `(?:${rsZWJ}(?:${[rsNonAstral, rsRegional, rsSurrPair].join('|')})${rsOptVar + reOptMod})*`
25 | const rsSeq = rsOptVar + reOptMod + rsOptJoin
26 | const rsNonAstralCombo = `${rsNonAstral}${rsCombo}?`
27 | const rsSymbol = `(?:${[rsNonAstralCombo, rsCombo, rsRegional, rsSurrPair, rsAstral].join('|')})`
28 |
29 | /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
30 | const reUnicode = RegExp(`${rsFitz}(?=${rsFitz})|${rsSymbol + rsSeq}`, 'g')
31 |
32 | /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
33 | const reHasUnicode = RegExp(`[${rsZWJ + rsAstralRange + rsComboRange + rsVarRange}]`)
34 |
35 | /**
36 | * Checks if `string` contains Unicode symbols.
37 | *
38 | * @private
39 | * @param {string} string The string to inspect.
40 | * @returns {boolean} Returns `true` if a symbol is found, else `false`.
41 | */
42 | function hasUnicode (string) {
43 | return reHasUnicode.test(string)
44 | }
45 |
46 | /**
47 | * Converts an ASCII `string` to an array.
48 | *
49 | * @private
50 | * @param {string} string The string to convert.
51 | * @returns {Array} Returns the converted array.
52 | */
53 | function asciiToArray (string) {
54 | return string.split('')
55 | }
56 |
57 | /**
58 | * Converts a Unicode `string` to an array.
59 | *
60 | * @private
61 | * @param {string} string The string to convert.
62 | * @returns {Array} Returns the converted array.
63 | */
64 | function unicodeToArray (string) {
65 | return string.match(reUnicode) || []
66 | }
67 |
68 | /**
69 | * Converts `string` to an array.
70 | *
71 | * @private
72 | * @param {string} string The string to convert.
73 | * @returns {Array} Returns the converted array.
74 | */
75 | /* istanbul ignore next */
76 | export default function stringToArray (string) {
77 | return hasUnicode(string)
78 | ? unicodeToArray(string)
79 | : asciiToArray(string)
80 | }
81 |
--------------------------------------------------------------------------------
/test/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mocha
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/browser/test.coverage.js:
--------------------------------------------------------------------------------
1 | const originDesign = Object.assign
2 | Object.assign = undefined
3 |
4 | const Mock = require('../../dist/mock.browser')
5 | const Random = Mock.Random
6 | const expect = require('chai').expect
7 |
8 | describe('Coverage', () => {
9 | it('object.assign is not defined', () => {
10 | const data = Random.boolean()
11 | expect(data).to.be.an('boolean')
12 | Object.assign = originDesign
13 | })
14 |
15 | it('Util.keys', () => {
16 | const obj = { name: 1 }
17 | const obj1 = { age: 2 }
18 | Object.setPrototypeOf(obj, obj1)
19 | const keys = Mock.Util.keys(obj)
20 | expect(keys.length).to.equal(1)
21 | })
22 |
23 | it('Util.values', () => {
24 | const obj = { name: 1 }
25 | const obj1 = { age: 2 }
26 | Object.setPrototypeOf(obj, obj1)
27 | const keys = Mock.Util.values(obj)
28 | expect(keys.length).to.equal(1)
29 | })
30 |
31 | it('Util.heredoc', () => {
32 | const data = Mock.Util.heredoc(function () {
33 | /*
34 | This is a comment.
35 | This is second line.
36 | */
37 | })
38 | expect(data).to.equal(`This is a comment.
39 | This is second line.`)
40 | })
41 |
42 | it('Util.noop', () => {
43 | const data = Mock.Util.noop()
44 | expect(data).to.equal(undefined)
45 | })
46 |
47 | it('Util.createCustomEvent', () => {
48 | const originCustomEvent = window.CustomEvent
49 | window.CustomEvent = null
50 | expect(() => {
51 | new CustomEvent('test-event')
52 | }).to.throw(TypeError)
53 | const event = Mock.Util.createCustomEvent('test-event')
54 | expect(event).to.instanceof(originCustomEvent)
55 | window.CustomEvent = originCustomEvent
56 | })
57 |
58 | it('Random.boolean', () => {
59 | const data = Random.boolean('hello', 'world', true)
60 | expect(data).to.be.an('boolean')
61 |
62 | const data1 = Random.boolean(null, null, true)
63 | expect(data1).to.be.an('boolean')
64 | })
65 |
66 | it('Random.dataImage', () => {
67 | const originGetContext = HTMLCanvasElement.prototype.getContext
68 | HTMLCanvasElement.prototype.getContext = () => null
69 | const data = Random.dataImage()
70 | expect(data).to.equal('')
71 | HTMLCanvasElement.prototype.getContext = originGetContext
72 | })
73 |
74 | it('Handler.placeholder', () => {
75 | const originEval = window.eval
76 | window.eval = null
77 | const data = Mock.mock("@boolean(['hello'],)")
78 | expect(data).to.be.oneOf([true, false])
79 | window.eval = originEval
80 | })
81 |
82 | it('Handler.placeholder - Random.xx is not function', () => {
83 | Random.testFn = 1
84 | const data = Mock.mock('@testFn')
85 | expect(data).to.equal('')
86 | })
87 |
88 | it('Mock.setting.parseTimeout', () => {
89 | Mock.setup({ timeout: {} })
90 | Mock.mock('http://Mock.setting.parseTimeout', {})
91 | const xhr = new XMLHttpRequest()
92 | xhr.open('get', 'http://Mock.setting.parseTimeout')
93 | expect(xhr.custom.timeout).to.equal(0)
94 | Mock.setup({ timeout: '10-50' })
95 | })
96 | })
97 |
98 |
--------------------------------------------------------------------------------
/test/browser/test.dpd.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.browser')
2 | const expect = require('chai').expect
3 | const $ = require('jquery')
4 |
5 | describe('DPD', function () {
6 | describe('Reference', function () {
7 | it('@EMAIL', function () {
8 | var data = Mock.mock(this.test.title)
9 | expect(data).to.not.equal(this.test.title)
10 | })
11 | })
12 | describe('Priority', function () {
13 | it('@EMAIL', function () {
14 | var data = Mock.mock({
15 | email: 'nuysoft@gmail.com',
16 | name: '@EMAIL'
17 | })
18 | this.test.title += ' => ' + data.name
19 | expect(data.name).to.not.equal(data.email)
20 | })
21 | it('@email', function () {
22 | var data = Mock.mock({
23 | email: 'nuysoft@gmail.com',
24 | name: '@email'
25 | })
26 | this.test.title += ' => ' + data.name
27 | expect(data.name).to.equal(data.email)
28 | })
29 | })
30 | describe('Escape', function () {
31 | it('\@EMAIL', function () {
32 | var data = Mock.mock(this.test.title)
33 | this.test.title += ' => ' + data
34 | expect(data).to.not.equal(this.test.title)
35 | })
36 | it('\\@EMAIL', function () {
37 | var data = Mock.mock(this.test.title)
38 | this.test.title += ' => ' + data
39 | expect(data).to.equal('@EMAIL')
40 | })
41 | it('\\\@EMAIL', function () {
42 | var data = Mock.mock(this.test.title)
43 | this.test.title += ' => ' + data
44 | expect(data).to.equal('@EMAIL')
45 | })
46 | it('\\\\@EMAIL', function () {
47 | var data = Mock.mock(this.test.title)
48 | this.test.title += ' => ' + data
49 | expect(data).to.equal('\\@EMAIL')
50 | })
51 |
52 | it('\\@EMAIL @EMAIL', function () {
53 | var data = Mock.mock(this.test.title)
54 | this.test.title += ' => ' + data
55 | expect(data.startsWith('@EMAIL ')).to.be.ok
56 | })
57 |
58 | it('\\@EMAIL @EMAIL \\@EMAIL', function () {
59 | var data = Mock.mock(this.test.title)
60 | this.test.title += ' => ' + data
61 | expect(data.startsWith('@EMAIL ')).to.be.ok
62 | expect(data.endsWith(' @EMAIL')).to.be.ok
63 | })
64 | })
65 | describe('Path', function () {
66 | it('Absolute Path', function () {
67 | var data = Mock.mock({
68 | id: '@UUID',
69 | children: [{
70 | parentId: '@/id'
71 | }],
72 | child: {
73 | parentId: '@/id'
74 | }
75 | })
76 | expect(data.children[0]).to.have.property('parentId', data.id)
77 | expect(data.child).to.have.property('parentId', data.id)
78 | })
79 | it('Relative Path', function () {
80 | var data = Mock.mock({
81 | id: '@UUID',
82 | children: [{
83 | parentId: '@../../id'
84 | }],
85 | child: {
86 | parentId: '@../id'
87 | },
88 | myId: '@./id'
89 | })
90 | expect(data.children[0]).to.have.property('parentId', data.id)
91 | expect(data.child).to.have.property('parentId', data.id)
92 | expect(data.myId).to.equal(data.id)
93 | })
94 |
95 | })
96 | describe('Complex', function () {
97 | var tpl = {
98 | basics: {
99 | boolean: '@boolean',
100 | boolean1: '@BOOLEAN',
101 | boolean2: '@BOOLEAN(1, 9, true)',
102 |
103 | natural1: '@NATURAL',
104 | natural2: '@NATURAL(10000)',
105 | natural3: '@NATURAL(60, 100)',
106 |
107 | integer1: '@INTEGER',
108 | integer2: '@INTEGER(10000)',
109 | integer3: '@INTEGER(60, 100)',
110 |
111 | float1: '@FLOAT',
112 | float2: '@FLOAT(0)',
113 | float3: '@FLOAT(60, 100)',
114 | float4: '@FLOAT(60, 100, 3)',
115 | float5: '@FLOAT(60, 100, 3, 5)',
116 | float6: '@FLOAT(@NATURAL)',
117 |
118 | character1: '@CHARACTER',
119 | character2: '@CHARACTER("lower")',
120 | character3: '@CHARACTER("upper")',
121 | character4: '@CHARACTER("number")',
122 | character5: '@CHARACTER("symbol")',
123 | character6: '@CHARACTER("aeiou")',
124 |
125 | string1: '@STRING',
126 | string2: '@STRING(5)',
127 | string3: '@STRING("lower",5)',
128 | string4: '@STRING(7, 10)',
129 | string5: '@STRING("aeiou", 1, 3)',
130 |
131 | range1: '@RANGE(10)',
132 | range2: '@RANGE(3, 7)',
133 | range3: '@RANGE(1, 10, 2)',
134 | range4: '@RANGE(1, 10, 3)'
135 | },
136 | data: {
137 | date: '@DATE',
138 | time: '@TIME',
139 |
140 | datetime1: '@DATETIME',
141 | datetime2: '@DATETIME("yyyy-MM-dd A HH:mm:ss")',
142 | datetime3: '@DATETIME("yyyy-MM-dd a HH:mm:ss")',
143 | datetime4: '@DATETIME("yy-MM-dd HH:mm:ss")',
144 | datetime5: '@DATETIME("y-MM-dd HH:mm:ss")',
145 | datetime6: '@DATETIME("y-M-d H:m:s")',
146 |
147 | timestamp: '@TIMESTAMP',
148 |
149 | now: '@NOW',
150 | nowYear: '@NOW("year")',
151 | nowMonth: '@NOW("month")',
152 | nowDay: '@NOW("day")',
153 | nowHour: '@NOW("hour")',
154 | nowMinute: '@NOW("minute")',
155 | nowSecond: '@NOW("second")',
156 | nowWeek: '@NOW("week")',
157 | nowCustom: '@NOW("yyyy-MM-dd HH:mm:ss SS")'
158 | },
159 | image: {
160 | image1: '@IMAGE',
161 | image2: '@IMAGE("100x200", "#000")',
162 | image3: '@IMAGE("100x200", "#000", "hello")',
163 | image4: '@IMAGE("100x200", "#000", "#FFF", "hello")',
164 | image5: '@IMAGE("100x200", "#000", "#FFF", "png", "hello")',
165 |
166 | dataImage1: '@DATAIMAGE',
167 | dataImage2: '@DATAIMAGE("200x100")',
168 | dataImage3: '@DATAIMAGE("300x100", "Hello Mock.js!")'
169 | },
170 | color: {
171 | color: '@COLOR',
172 | render: function () {
173 | $('.header').css('background', this.color)
174 | }
175 | },
176 | text: {
177 | title1: '@TITLE',
178 | title2: '@TITLE(5)',
179 | title3: '@TITLE(3, 5)',
180 |
181 | word1: '@WORD',
182 | word2: '@WORD(5)',
183 | word3: '@WORD(3, 5)',
184 |
185 | sentence1: '@SENTENCE',
186 | sentence2: '@SENTENCE(5)',
187 | sentence3: '@SENTENCE(3, 5)',
188 |
189 | paragraph1: '@PARAGRAPH',
190 | paragraph2: '@PARAGRAPH(2)',
191 | paragraph3: '@PARAGRAPH(1, 3)'
192 | },
193 | name: {
194 | first: '@FIRST',
195 | last: '@LAST',
196 | name1: '@NAME',
197 | name2: '@NAME(true)'
198 | },
199 | web: {
200 | url: '@URL',
201 | domain: '@DOMAIN',
202 | email: '@EMAIL',
203 | ip: '@IP',
204 | tld: '@TLD'
205 | },
206 | address: {
207 | region: '@REGION',
208 | province: '@PROVINCE',
209 | city: '@CITY',
210 | county: '@COUNTY'
211 | },
212 | miscellaneous: {
213 | guid: '@GUID',
214 | id: '@ID',
215 | version1: '@VERSION',
216 | version2: '@VERSION(4)',
217 | phone: '@PHONE',
218 | 'increment1|3': [
219 | '@INCREMENT'
220 | ],
221 | 'increment2|3': [
222 | '@INCREMENT(10)'
223 | ]
224 | },
225 | helpers: {
226 | capitalize1: '@CAPITALIZE()',
227 | capitalize2: '@CAPITALIZE("hello")',
228 |
229 | upper1: '@UPPER',
230 | upper2: '@UPPER("hello")',
231 |
232 | lower1: '@LOWER',
233 | lower2: '@LOWER("HELLO")',
234 |
235 | pick1: '@PICK',
236 | pick2: '@PICK("abc")',
237 | pick3: '@PICK(["a", "b", "c"])',
238 |
239 | shuffle1: '@SHUFFLE',
240 | shuffle2: '@SHUFFLE(["a", "b", "c"])'
241 | }
242 | }
243 | it('Complex all', function () {
244 | var data = Mock.mock(tpl)
245 | this.test.title += ' => ' + JSON.stringify(data, null, 2)
246 | expect(data).to.be.a('object')
247 | })
248 | })
249 | })
250 |
--------------------------------------------------------------------------------
/test/browser/test.mock.js:
--------------------------------------------------------------------------------
1 | // 数据占位符定义(Data Placeholder Definition,DPD)
2 | const Mock = require('../../dist/mock.browser')
3 | const expect = require('chai').expect
4 |
5 | describe('Mock.mock', function () {
6 | describe('Mock.mock()', () => {
7 | it('Mock.mock() should throw an error', () => {
8 | expect(() => Mock.mock()).to.throw()
9 | })
10 | })
11 | describe('Mock.mock( String )', function () {
12 | it('@EMAIL', function () {
13 | var data = Mock.mock(this.test.title)
14 | expect(data).to.not.equal(this.test.title)
15 | this.test.title += ' => ' + data
16 | })
17 | })
18 | describe('Mock.mock( {} )', function () {
19 | it('', function () {
20 | var tpl = {
21 | 'list|1-10': [{
22 | 'id|+1': 1,
23 | 'email': '@EMAIL'
24 | }]
25 | }
26 | var data = Mock.mock(tpl)
27 | this.test.title = JSON.stringify(tpl /*, null, 4*/) + ' => ' + JSON.stringify(data /*, null, 4*/)
28 | expect(data).to.have.property('list').that.be.an('array').with.length.within(1, 10)
29 | data.list.forEach(function (item, index) {
30 | if (index > 0) expect(item.id).to.equal(data.list[index - 1].id + 1)
31 | })
32 | })
33 | })
34 | describe('Mock.mock( function() )', function () {
35 | it('', function () {
36 | var fn = function () {
37 | return Mock.mock({
38 | 'list|1-10': [{
39 | 'id|+1': 1,
40 | 'email': '@EMAIL'
41 | }]
42 | })
43 | }
44 | var data = Mock.mock(fn)
45 | this.test.title = fn.toString() + ' => ' + JSON.stringify(data /*, null, 4*/)
46 | expect(data).to.have.property('list').that.be.an('array').with.length.within(1, 10)
47 | data.list.forEach(function (item, index) {
48 | if (index > 0) expect(item.id).to.equal(data.list[index - 1].id + 1)
49 | })
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/test/browser/test.schema.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.browser')
2 | const expect = require('chai').expect
3 |
4 | describe('Schema', function () {
5 | function stringify (json) {
6 | return JSON.stringify(json /*, null, 4*/)
7 | }
8 |
9 | function doit (template, validator) {
10 | it('', function () {
11 | var schema = Mock.toJSONSchema(template)
12 | this.test.title = (stringify(template) || template.toString()) + ' => ' + stringify(schema)
13 | validator(schema)
14 | })
15 | }
16 |
17 | describe('Type', function () {
18 | doit(1, function (schema) {
19 | expect(schema.name).to.be.an('undefined')
20 | // expect(schema).to.not.have.property('name')
21 | expect(schema).to.have.property('type', 'number')
22 | for (var n in schema.rule) {
23 | expect(schema.rule[n]).to.be.null()
24 | }
25 | })
26 | doit(true, function (schema) {
27 | expect(schema.name).to.be.an('undefined')
28 | // expect(schema).to.not.have.property('name')
29 | expect(schema).to.have.property('type', 'boolean')
30 | for (var n in schema.rule) {
31 | expect(schema.rule[n]).to.be.null()
32 | }
33 | })
34 | doit('', 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', 'string')
38 | for (var n in schema.rule) {
39 | expect(schema.rule[n]).to.be.null()
40 | }
41 | })
42 | doit(function () {}, 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', 'function')
46 | for (var n in schema.rule) {
47 | expect(schema.rule[n]).to.be.null()
48 | }
49 | })
50 | doit(/\d/, 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', 'regexp')
54 | for (var n in schema.rule) {
55 | expect(schema.rule[n]).to.be.null()
56 | }
57 | })
58 | doit([], 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', 'array')
62 | for (var n in schema.rule) {
63 | expect(schema.rule[n]).to.be.null()
64 | }
65 | expect(schema).to.have.property('items').with.length(0)
66 | })
67 | doit({}, function (schema) {
68 | expect(schema.name).to.be.an('undefined')
69 | // expect(schema).to.not.have.property('name')
70 | expect(schema).to.have.property('type', 'object')
71 | for (var n in schema.rule) {
72 | expect(schema.rule[n]).to.be.null()
73 | }
74 | expect(schema).to.have.property('properties').with.length(0)
75 | })
76 |
77 | })
78 |
79 | describe('Object', function () {
80 | doit({
81 | a: {
82 | b: {
83 | c: {
84 | d: {}
85 | }
86 | }
87 | }
88 | }, function (schema) {
89 | expect(schema.name).to.be.an('undefined')
90 | // expect(schema).to.not.have.property('name')
91 | expect(schema).to.have.property('type', 'object')
92 |
93 | var properties
94 |
95 | // root.properties
96 | properties = schema.properties
97 | expect(properties).to.with.length(1)
98 | expect(properties[0]).to.have.property('name', 'a')
99 | expect(properties[0]).to.have.property('type', 'object')
100 |
101 | // root.a.properties
102 | properties = properties[0].properties
103 | expect(properties).to.with.length(1)
104 | expect(properties[0]).to.have.property('name', 'b')
105 | expect(properties[0]).to.have.property('type', 'object')
106 |
107 | // root.a.b.properties
108 | properties = properties[0].properties
109 | expect(properties).to.with.length(1)
110 | expect(properties[0]).to.have.property('name', 'c')
111 | expect(properties[0]).to.have.property('type', 'object')
112 |
113 | // root.a.b.c.properties
114 | properties = properties[0].properties
115 | expect(properties).to.with.length(1)
116 | expect(properties[0]).to.have.property('name', 'd')
117 | expect(properties[0]).to.have.property('type', 'object')
118 |
119 | // root.a.b.c.d.properties
120 | properties = properties[0].properties
121 | expect(properties).to.with.length(0)
122 | })
123 |
124 | })
125 |
126 | describe('Array', function () {
127 | doit([
128 | [
129 | ['foo', 'bar']
130 | ]
131 | ], function (schema) {
132 | expect(schema.name).to.be.an('undefined')
133 | // expect(schema).to.not.have.property('name')
134 | expect(schema).to.have.property('type', 'array')
135 |
136 | var items
137 |
138 | // root.items
139 | items = schema.items
140 | expect(items).to.with.length(1)
141 | expect(items[0]).to.have.property('type', 'array')
142 |
143 | // root[0].items
144 | items = items[0].items
145 | expect(items).to.with.length(1)
146 | expect(items[0]).to.have.property('type', 'array')
147 |
148 | // root[0][0].items
149 | items = items[0].items
150 | expect(items).to.with.length(2)
151 | expect(items[0]).to.have.property('type', 'string')
152 | expect(items[1]).to.have.property('type', 'string')
153 | })
154 | })
155 |
156 | describe('String Rule', function () {
157 | doit({
158 | 'string|1-10': '★'
159 | }, function (schema) {
160 | expect(schema.name).to.be.an('undefined')
161 | // expect(schema).to.not.have.property('name')
162 | expect(schema).to.have.property('type', 'object')
163 |
164 | var properties
165 | // root.properties
166 | properties = schema.properties
167 | expect(properties).to.with.length(1)
168 | expect(properties[0]).to.have.property('type', 'string')
169 | expect(properties[0].rule).to.have.property('min', 1)
170 | expect(properties[0].rule).to.have.property('max', 10)
171 | })
172 | doit({
173 | 'string|3': 'value'
174 | }, function (schema) {
175 | expect(schema.name).to.be.an('undefined')
176 | // expect(schema).to.not.have.property('name')
177 | expect(schema).to.have.property('type', 'object')
178 |
179 | var properties
180 | // root.properties
181 | properties = schema.properties
182 | expect(properties).to.with.length(1)
183 | expect(properties[0]).to.have.property('type', 'string')
184 | expect(properties[0].rule).to.have.property('min', 3)
185 | expect(properties[0].rule.max).to.be.an('undefined')
186 | })
187 | })
188 |
189 | })
190 |
--------------------------------------------------------------------------------
/test/browser/test.valid.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.browser')
2 | const expect = require('chai').expect
3 |
4 | describe('Mock.valid', function () {
5 | function stringify (json) {
6 | return JSON.stringify(json /*, null, 4*/)
7 | }
8 |
9 | function title (tpl, data, result, test) {
10 | test.title = stringify(tpl) + ' VS ' + stringify(data) + '\n\tresult: ' + stringify(result)
11 | }
12 |
13 | function doit (tpl, data, len) {
14 | it('', function () {
15 | var result = Mock.valid(tpl, data)
16 | title(tpl, data, result, this.test)
17 | expect(result).to.be.an('array').with.length(len)
18 | })
19 | }
20 |
21 | describe('Name', function () {
22 | doit({
23 | name: 1
24 | }, {
25 | name: 1
26 | }, 0)
27 |
28 | doit({
29 | name1: 1
30 | }, {
31 | name2: 1
32 | }, 1)
33 | })
34 | describe('Value - Number', function () {
35 | doit({
36 | name: 1
37 | }, {
38 | name: 1
39 | }, 0)
40 |
41 | doit({
42 | name: 1
43 | }, {
44 | name: 2
45 | }, 1)
46 |
47 | doit({
48 | name: 1.1
49 | }, {
50 | name: 2.2
51 | }, 1)
52 |
53 | doit({
54 | 'name|1-10': 1
55 | }, {
56 | name: 5
57 | }, 0)
58 |
59 | doit({
60 | 'name|1-10': 1
61 | }, {
62 | name: 0
63 | }, 1)
64 |
65 | doit({
66 | 'name|1-10': 1
67 | }, {
68 | name: 11
69 | }, 1)
70 |
71 | doit({ 'name|5': 1 }, { name: 5 }, 0)
72 |
73 | doit({ 'name|5': 1 }, { name: 6 }, 1)
74 |
75 | doit({ 'name|.5': 1 }, { name: 1.34567 }, 0)
76 |
77 | doit({ 'name|.5': 1 }, { name: 1.345678 }, 1)
78 |
79 | doit({ 'name|.5-7': 1 }, { name: 1.345678 }, 0)
80 |
81 | doit({ 'name|.5-7': 1 }, { name: 1.3456 }, 1)
82 | })
83 |
84 | describe('Value - String', function () {
85 | doit({
86 | name: 'value'
87 | }, {
88 | name: 'value'
89 | }, 0)
90 |
91 | doit({
92 | name: 'value1'
93 | }, {
94 | name: 'value2'
95 | }, 1)
96 |
97 | doit({
98 | 'name|1': 'value'
99 | }, {
100 | name: 'value'
101 | }, 0)
102 |
103 | doit({
104 | 'name|2': 'value'
105 | }, {
106 | name: 'valuevalue'
107 | }, 0)
108 |
109 | doit({
110 | 'name|2': 'value'
111 | }, {
112 | name: 'value'
113 | }, 1)
114 |
115 | doit({
116 | 'name|2-3': 'value'
117 | }, {
118 | name: 'value'
119 | }, 1)
120 |
121 | doit({
122 | 'name|2-3': 'value'
123 | }, {
124 | name: 'valuevaluevaluevalue'
125 | }, 1)
126 | })
127 |
128 | describe('Value - RgeExp', function () {
129 | doit({
130 | name: /value/
131 | }, {
132 | name: 'value'
133 | }, 0)
134 | doit({
135 | name: /value/
136 | }, {
137 | name: 'vvvvv'
138 | }, 1)
139 | doit({
140 | 'name|1-10': /value/
141 | }, {
142 | name: 'valuevaluevaluevaluevalue'
143 | }, 0)
144 | doit({
145 | 'name|1-10': /value/
146 | }, {
147 | name: 'vvvvvvvvvvvvvvvvvvvvvvvvv'
148 | }, 1)
149 | doit({
150 | 'name|1-10': /^value$/
151 | }, {
152 | name: 'valuevaluevaluevaluevalue'
153 | }, 0)
154 |
155 | doit({ 'name|2': /^value$/ }, { name: 'valuevalue' }, 0)
156 |
157 | doit({
158 | name: /[a-z][A-Z][0-9]/
159 | }, {
160 | name: 'yL5'
161 | }, 0)
162 | })
163 | describe('Value - Object', function () {
164 | doit({
165 | name: 1
166 | }, {
167 | name: 1
168 | }, 0)
169 | doit({
170 | name1: 1
171 | }, {
172 | name2: 2
173 | }, 1)
174 | doit({
175 | name1: 1,
176 | name2: 2
177 | }, {
178 | name3: 3
179 | }, 1)
180 | doit({
181 | name1: 1,
182 | name2: 2
183 | }, {
184 | name1: '1',
185 | name2: '2'
186 | }, 2)
187 | doit({
188 | a: {
189 | b: {
190 | c: {
191 | d: 1
192 | }
193 | }
194 | }
195 | }, {
196 | a: {
197 | b: {
198 | c: {
199 | d: 2
200 | }
201 | }
202 | }
203 | }, 1)
204 |
205 | doit({
206 | name: {
207 | 'age|1-3': {
208 | hello: 1
209 | }
210 | }
211 | }, {
212 | name: {
213 | age: {
214 | hello: 1
215 | }
216 | }
217 | }, 0)
218 |
219 | doit({
220 | name: {
221 | 'age|2': {
222 | hello: 1,
223 | world: 1
224 | }
225 | }
226 | }, {
227 | name: {
228 | age: {
229 | hello: 1,
230 | world: 1
231 | }
232 | }
233 | }, 0)
234 | })
235 | describe('Value - Array', function () {
236 | doit([1, 2, 3], [1, 2, 3], 0)
237 |
238 | doit([1, 2, 3], [1, 2, 3, 4], 1)
239 |
240 | // 'name|1': array
241 | doit({
242 | 'name|1': [1, 2, 3]
243 | }, {
244 | 'name': 1
245 | }, 0)
246 | doit({
247 | 'name|1': [1, 2, 3]
248 | }, {
249 | 'name': 2
250 | }, 0)
251 | doit({
252 | 'name|1': [1, 2, 3]
253 | }, {
254 | 'name': 3
255 | }, 0)
256 | doit({ // 不检测
257 | 'name|1': [1, 2, 3]
258 | }, {
259 | 'name': 4
260 | }, 0)
261 |
262 | // 'name|+1': array
263 | doit({
264 | 'name|+1': [1, 2, 3]
265 | }, {
266 | 'name': 1
267 | }, 0)
268 | doit({
269 | 'name|+1': [1, 2, 3]
270 | }, {
271 | 'name': 2
272 | }, 0)
273 | doit({
274 | 'name|+1': [1, 2, 3]
275 | }, {
276 | 'name': 3
277 | }, 0)
278 | doit({
279 | 'name|+1': [1, 2, 3]
280 | }, {
281 | 'name': 4
282 | }, 0)
283 |
284 | // 'name|min-max': array
285 | doit({
286 | 'name|2-3': [1]
287 | }, {
288 | 'name': [1, 2, 3, 4]
289 | }, 1)
290 |
291 | doit({
292 | 'name|2-3': [1]
293 | }, {
294 | 'name': [1]
295 | }, 1)
296 |
297 | doit({
298 | 'name|2-3': [1, 2, 3]
299 | }, {
300 | 'name': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
301 | }, 1)
302 |
303 | doit({
304 | 'name|2-3': [1, 2, 3]
305 | }, {
306 | 'name': [1, 2, 3]
307 | }, 1)
308 |
309 | doit({
310 | 'name|2-3': [1]
311 | }, {
312 | 'name': [1, 1, 1]
313 | }, 0)
314 |
315 | doit({
316 | 'name|2-3': [1]
317 | }, {
318 | 'name': [1, 2, 3]
319 | }, 2)
320 |
321 | // 'name|count': array
322 | })
323 | describe('Value - Placeholder', function () {
324 | doit({
325 | name: '@email'
326 | }, {
327 | name: 'nuysoft@gmail.com'
328 | }, 0)
329 | doit({
330 | name: '@int'
331 | }, {
332 | name: 123
333 | }, 0)
334 | doit({
335 | name: '@int'
336 | }, {
337 | name: '123'
338 | }, 1)
339 | })
340 |
341 | describe('Value - Function', function () {
342 | doit({
343 | name: function () {}
344 | }, {
345 | name: 'nuysoft@gmail.com'
346 | }, 0)
347 | })
348 | })
349 |
--------------------------------------------------------------------------------
/test/fixtures/files/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavyun/better-mock/5359ed68c02c7ef7d1f8a5bca4cd33160be1001c/test/fixtures/files/logo.png
--------------------------------------------------------------------------------
/test/fixtures/server.js:
--------------------------------------------------------------------------------
1 | const serveHandler = require('serve-handler');
2 | const http = require('http');
3 | const path = require('path');
4 |
5 | const server = http.createServer((request, response) => {
6 | return serveHandler(request, response, {
7 | public: path.resolve(__dirname, './files'),
8 | headers: [
9 | {
10 | source: '*',
11 | headers: [
12 | {
13 | key: 'Access-Control-Allow-Origin',
14 | value: '*'
15 | }
16 | ]
17 | }
18 | ]
19 | });
20 | })
21 |
22 | server.listen(14000, () => {
23 | console.log('Running at http://localhost:14000');
24 | });
25 |
--------------------------------------------------------------------------------
/test/mp/test.dpd.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.mp')
2 | const expect = require('chai').expect
3 |
4 | const { describe, it } = global
5 |
6 | describe('DPD', function () {
7 | describe('Reference', function () {
8 | it('@EMAIL', function () {
9 | var data = Mock.mock(this.test.title)
10 | expect(data).to.not.equal(this.test.title)
11 | })
12 | })
13 | describe('Priority', function () {
14 | it('@EMAIL', function () {
15 | var data = Mock.mock({
16 | email: 'nuysoft@gmail.com',
17 | name: '@EMAIL'
18 | })
19 | this.test.title += ' => ' + data.name
20 | expect(data.name).to.not.equal(data.email)
21 | })
22 | it('@email', function () {
23 | var data = Mock.mock({
24 | email: 'nuysoft@gmail.com',
25 | name: '@email'
26 | })
27 | this.test.title += ' => ' + data.name
28 | expect(data.name).to.equal(data.email)
29 | })
30 | })
31 | describe('Escape', function () {
32 | it('\@EMAIL', function () {
33 | var data = Mock.mock(this.test.title)
34 | this.test.title += ' => ' + data
35 | expect(data).to.not.equal(this.test.title)
36 | })
37 | it('\\@EMAIL', function () {
38 | var data = Mock.mock(this.test.title)
39 | this.test.title += ' => ' + data
40 | expect(data).to.not.equal(this.test.title)
41 | })
42 | it('\\\@EMAIL', function () {
43 | var data = Mock.mock(this.test.title)
44 | this.test.title += ' => ' + data
45 | expect(data).to.not.equal(this.test.title)
46 | })
47 | it('\\\\@EMAIL', function () {
48 | var data = Mock.mock(this.test.title)
49 | this.test.title += ' => ' + data
50 | expect(data).to.not.equal(this.test.title)
51 | })
52 | })
53 | describe('Path', function () {
54 | it('Absolute Path', function () {
55 | var data = Mock.mock({
56 | id: '@UUID',
57 | children: [{
58 | parentId: '@/id'
59 | }],
60 | child: {
61 | parentId: '@/id'
62 | }
63 | })
64 | expect(data.children[0]).to.have.property('parentId', data.id)
65 | expect(data.child).to.have.property('parentId', data.id)
66 | })
67 | it('Relative Path', function () {
68 | var data = Mock.mock({
69 | id: '@UUID',
70 | children: [{
71 | parentId: '@../../id'
72 | }],
73 | child: {
74 | parentId: '@../id'
75 | },
76 | myId: '@./id'
77 | })
78 | expect(data.children[0]).to.have.property('parentId', data.id)
79 | expect(data.child).to.have.property('parentId', data.id)
80 | expect(data.myId).to.equal(data.id)
81 | })
82 |
83 | })
84 | describe('Complex', function () {
85 | var tpl = {
86 | basics: {
87 | boolean1: '@BOOLEAN',
88 | boolean2: '@BOOLEAN(1, 9, true)',
89 |
90 | natural1: '@NATURAL',
91 | natural2: '@NATURAL(10000)',
92 | natural3: '@NATURAL(60, 100)',
93 |
94 | integer1: '@INTEGER',
95 | integer2: '@INTEGER(10000)',
96 | integer3: '@INTEGER(60, 100)',
97 |
98 | float1: '@FLOAT',
99 | float2: '@FLOAT(0)',
100 | float3: '@FLOAT(60, 100)',
101 | float4: '@FLOAT(60, 100, 3)',
102 | float5: '@FLOAT(60, 100, 3, 5)',
103 |
104 | character1: '@CHARACTER',
105 | character2: '@CHARACTER("lower")',
106 | character3: '@CHARACTER("upper")',
107 | character4: '@CHARACTER("number")',
108 | character5: '@CHARACTER("symbol")',
109 | character6: '@CHARACTER("aeiou")',
110 |
111 | string1: '@STRING',
112 | string2: '@STRING(5)',
113 | string3: '@STRING("lower",5)',
114 | string4: '@STRING(7, 10)',
115 | string5: '@STRING("aeiou", 1, 3)',
116 |
117 | range1: '@RANGE(10)',
118 | range2: '@RANGE(3, 7)',
119 | range3: '@RANGE(1, 10, 2)',
120 | range4: '@RANGE(1, 10, 3)'
121 | },
122 | data: {
123 | date: '@DATE',
124 | time: '@TIME',
125 |
126 | datetime1: '@DATETIME',
127 | datetime2: '@DATETIME("yyyy-MM-dd A HH:mm:ss")',
128 | datetime3: '@DATETIME("yyyy-MM-dd a HH:mm:ss")',
129 | datetime4: '@DATETIME("yy-MM-dd HH:mm:ss")',
130 | datetime5: '@DATETIME("y-MM-dd HH:mm:ss")',
131 | datetime6: '@DATETIME("y-M-d H:m:s")',
132 |
133 | timestamp: '@TIMESTAMP',
134 |
135 | now: '@NOW',
136 | nowYear: '@NOW("year")',
137 | nowMonth: '@NOW("month")',
138 | nowDay: '@NOW("day")',
139 | nowHour: '@NOW("hour")',
140 | nowMinute: '@NOW("minute")',
141 | nowSecond: '@NOW("second")',
142 | nowWeek: '@NOW("week")',
143 | nowCustom: '@NOW("yyyy-MM-dd HH:mm:ss SS")'
144 | },
145 | image: {
146 | image1: '@IMAGE',
147 | image2: '@IMAGE("100x200", "#000")',
148 | image3: '@IMAGE("100x200", "#000", "hello")',
149 | image4: '@IMAGE("100x200", "#000", "#FFF", "hello")',
150 | image5: '@IMAGE("100x200", "#000", "#FFF", "png", "hello")'
151 | },
152 | color: {
153 | color: '@COLOR'
154 | },
155 | text: {
156 | title1: '@TITLE',
157 | title2: '@TITLE(5)',
158 | title3: '@TITLE(3, 5)',
159 |
160 | word1: '@WORD',
161 | word2: '@WORD(5)',
162 | word3: '@WORD(3, 5)',
163 |
164 | sentence1: '@SENTENCE',
165 | sentence2: '@SENTENCE(5)',
166 | sentence3: '@SENTENCE(3, 5)',
167 |
168 | paragraph1: '@PARAGRAPH',
169 | paragraph2: '@PARAGRAPH(2)',
170 | paragraph3: '@PARAGRAPH(1, 3)'
171 | },
172 | name: {
173 | first: '@FIRST',
174 | last: '@LAST',
175 | name1: '@NAME',
176 | name2: '@NAME(true)'
177 | },
178 | web: {
179 | url: '@URL',
180 | domain: '@DOMAIN',
181 | email: '@EMAIL',
182 | ip: '@IP',
183 | tld: '@TLD'
184 | },
185 | address: {
186 | region: '@REGION',
187 | province: '@PROVINCE',
188 | city: '@CITY',
189 | county: '@COUNTY'
190 | },
191 | miscellaneous: {
192 | guid: '@GUID',
193 | id: '@ID',
194 | version1: '@VERSION',
195 | version2: '@VERSION(4)',
196 | phone: '@PHONE',
197 | 'increment1|3': [
198 | '@INCREMENT'
199 | ],
200 | 'increment2|3': [
201 | '@INCREMENT(10)'
202 | ]
203 | },
204 | helpers: {
205 | capitalize1: '@CAPITALIZE()',
206 | capitalize2: '@CAPITALIZE("hello")',
207 |
208 | upper1: '@UPPER',
209 | upper2: '@UPPER("hello")',
210 |
211 | lower1: '@LOWER',
212 | lower2: '@LOWER("HELLO")',
213 |
214 | pick1: '@PICK',
215 | pick2: '@PICK("abc")',
216 | pick3: '@PICK(["a", "b", "c"])',
217 |
218 | shuffle1: '@SHUFFLE',
219 | shuffle2: '@SHUFFLE(["a", "b", "c"])'
220 | }
221 | }
222 | it('', function () {
223 | var data = Mock.mock(tpl)
224 | // this.test.title += JSON.stringify(data, null, 4)
225 | expect(data).to.be.a('object')
226 | })
227 | })
228 | })
229 |
--------------------------------------------------------------------------------
/test/mp/test.index.js:
--------------------------------------------------------------------------------
1 | // 打包后直接在 mp 环境引入
2 | let count = 0
3 | let success = 0
4 | const promiseList = []
5 |
6 | function describe(title, cb) {
7 | console.log(title + '\n')
8 | cb()
9 | }
10 |
11 | function it(title, cb) {
12 | count++
13 | let status
14 | const context = {
15 | test: {
16 | title,
17 | }
18 | }
19 | try {
20 | const thenable = cb.call(context)
21 | title = context.test.title || title
22 | if (thenable) {
23 | let ready
24 | let readyPromise = new Promise(r => { ready = r })
25 | promiseList.push(readyPromise)
26 | thenable.then(() => {
27 | status = true
28 | success++
29 | }).catch(() => {
30 | status = false
31 | }).then(() => {
32 | console.log(`${status ? '✅' : '❌'}${title}`)
33 | ready()
34 | })
35 | } else {
36 | status = true
37 | success++
38 | console.log(`✅${title}`)
39 | }
40 | } catch (err) {
41 | console.error(err)
42 | status = false
43 | console.log(`❌${title}`)
44 | }
45 | }
46 |
47 | global.describe = describe
48 | global.it = it
49 |
50 | require('./test.random')
51 | require('./test.dpd')
52 | require('./test.dtd')
53 | require('./test.mock')
54 | require('./test.schema')
55 | require('./test.valid')
56 | require('./test.request')
57 |
58 | Promise.all(promiseList).then(() => {
59 | console.log(`${success} passing, ${count - success} error`)
60 | })
61 |
--------------------------------------------------------------------------------
/test/mp/test.mock.js:
--------------------------------------------------------------------------------
1 | // 数据占位符定义(Data Placeholder Definition,DPD)
2 | const Mock = require('../../dist/mock.mp')
3 | const expect = require('chai').expect
4 | const { describe, it } = global
5 |
6 | describe('Mock.mock()', () => {
7 | it('Mock.mock() should throw an error', () => {
8 | expect(() => Mock.mock()).to.throw()
9 | })
10 | })
11 | describe('Mock.mock( String )', function () {
12 | it('@EMAIL', function () {
13 | var data = Mock.mock(this.test.title)
14 | expect(data).to.not.equal(this.test.title)
15 | this.test.title += ' => ' + data
16 | })
17 | })
18 | describe('Mock.mock( {} )', function () {
19 | it('', function () {
20 | var tpl = {
21 | 'list|1-10': [{
22 | 'id|+1': 1,
23 | 'email': '@EMAIL'
24 | }]
25 | }
26 | var data = Mock.mock(tpl)
27 | this.test.title = JSON.stringify(tpl /*, null, 4*/) + ' => ' + JSON.stringify(data /*, null, 4*/)
28 | expect(data).to.have.property('list').that.be.an('array').with.length.within(1, 10)
29 | data.list.forEach(function (item, index) {
30 | if (index > 0) expect(item.id).to.equal(data.list[index - 1].id + 1)
31 | })
32 | })
33 | })
34 | describe('Mock.mock( function() )', function () {
35 | it('', function () {
36 | var fn = function () {
37 | return Mock.mock({
38 | 'list|1-10': [{
39 | 'id|+1': 1,
40 | 'email': '@EMAIL'
41 | }]
42 | })
43 | }
44 | var data = Mock.mock(fn)
45 | this.test.title = fn.toString() + ' => ' + JSON.stringify(data /*, null, 4*/)
46 | expect(data).to.have.property('list').that.be.an('array').with.length.within(1, 10)
47 | data.list.forEach(function (item, index) {
48 | if (index > 0) expect(item.id).to.equal(data.list[index - 1].id + 1)
49 | })
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/mp/test.request.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.mp')
2 | const expect = require('chai').expect
3 | const { describe, it } = global
4 |
5 | // override request
6 | Mock.mock('http://example.com', { example: 1 })
7 |
8 | const request = (function () {
9 | if (typeof wx !== 'undefined') {
10 | return promisify(wx.request)
11 | }
12 |
13 | if (typeof my !== 'undefined') {
14 | return promisify(my.request)
15 | }
16 |
17 | if (typeof tt !== 'undefined') {
18 | return promisify(tt.request)
19 | }
20 |
21 | if (typeof swan !== 'undefined') {
22 | return promisify(swan.request)
23 | }
24 | })()
25 |
26 | function dataAssert(data) {
27 | expect(data)
28 | .to.have.property('list')
29 | .that.be.an('array')
30 | .with.length.within(1, 10)
31 |
32 | data.list.forEach(function(item, index) {
33 | if (index > 0) expect(item.id).to.be.equal(data.list[index - 1].id + 1)
34 | })
35 | }
36 |
37 | function promisify(fn) {
38 | return opts => {
39 | return new Promise((resolve, reject) => {
40 | fn(Object.assign(opts || {}, {
41 | success: resolve,
42 | fail: err => {
43 | console.log(err)
44 | reject(err)
45 | }
46 | }))
47 | })
48 | }
49 | }
50 |
51 | describe('request', () => {
52 | it('request - success', function () {
53 | return request({
54 | url: 'https://cnodejs.org/api/v1/topics',
55 | method: 'get'
56 | }).then(res => {
57 | expect(res.data.success).to.be.ok
58 | expect(res.data.data).to.be.an('array')
59 | })
60 | })
61 |
62 | it('request - fail', function () {
63 | return request({
64 | url: Math.random()
65 | }).catch(err => {
66 | expect(err.errMsg)
67 | })
68 | })
69 |
70 | it('Mock.setup', function () {
71 | Mock.setup({ timeout: 2000 })
72 | const url = 'http://example.com/mock_setup'
73 |
74 | Mock.mock(url, {
75 | 'list|1-10': [{
76 | 'id|+1': 1,
77 | 'email': '@EMAIL'
78 | }]
79 | })
80 |
81 | const timeStart = Date.now()
82 |
83 | return request({
84 | url
85 | }).then(({data}) => {
86 | expect(Date.now() - timeStart >= 2000).to.ok
87 | dataAssert(data)
88 | })
89 | })
90 |
91 | Mock.setup({ timeout: '10-50' })
92 |
93 | it('Mock.mock( rurl, template )', () => {
94 | const url = 'http://example.com/rurl_template'
95 |
96 | Mock.mock(url, {
97 | 'list|1-10': [
98 | {
99 | 'id|+1': 1,
100 | email: '@EMAIL'
101 | }
102 | ]
103 | })
104 |
105 | return request({
106 | url
107 | }).then(({ data }) => {
108 | dataAssert(data)
109 | })
110 | })
111 |
112 | it('Mock.mock( rurl, function(options) )', () => {
113 | const url = 'http://example.com/rurl_function'
114 |
115 | Mock.mock(url, function(options) {
116 | expect(options).to.not.equal(undefined)
117 | expect(options.url).to.be.equal(url)
118 | expect(options.type).to.be.equal('GET')
119 | expect(options.body).to.be.equal(null)
120 | expect(options.headers['test-request-header']).to.be.equal('better-mock')
121 | return Mock.mock({
122 | 'list|1-10': [
123 | {
124 | 'id|+1': 1,
125 | email: '@EMAIL'
126 | }
127 | ]
128 | })
129 | })
130 |
131 | return request({
132 | url,
133 | header: {
134 | 'test-request-header': 'better-mock'
135 | }
136 | }).then(({ data }) => {
137 | dataAssert(data)
138 | })
139 | })
140 |
141 | it('Mock.mock( rurl, function(options) ) + GET + data', () => {
142 | const url = 'http://example.com/rurl_function_get_data'
143 |
144 | Mock.mock(url, function(options) {
145 | expect(options).to.not.equal(undefined)
146 | expect(options.url).to.be.equal(url + '?foo=1')
147 | expect(options.type).to.be.equal('GET')
148 | expect(options.body).to.be.equal(null)
149 | return Mock.mock({
150 | 'list|1-10': [
151 | {
152 | 'id|+1': 1,
153 | email: '@EMAIL'
154 | }
155 | ]
156 | })
157 | })
158 |
159 | const requestUrl = url + '?foo=1'
160 | return request({
161 | url: requestUrl
162 | }).then(({ data }) => {
163 | dataAssert(data)
164 | })
165 | })
166 |
167 | it('Mock.mock( rurl, function(options) ) + POST + data', () => {
168 | const url = 'http://example.com/rurl_function_post_data'
169 |
170 | Mock.mock(url, function(options) {
171 | expect(options).to.not.equal(undefined)
172 | expect(options.url).to.be.equal(url)
173 | expect(options.type).to.be.equal('POST')
174 | expect(JSON.stringify(options.body)).to.be.equal('{"foo":1}')
175 | return Mock.mock({
176 | 'list|1-10': [
177 | {
178 | 'id|+1': 1,
179 | email: '@EMAIL'
180 | }
181 | ]
182 | })
183 | })
184 |
185 | return request({
186 | url,
187 | method: 'POST',
188 | data: {
189 | foo: 1
190 | }
191 | }).then(({ data }) => {
192 | dataAssert(data)
193 | })
194 | })
195 |
196 | it('Mock.mock( rurl, rtype, template ) - GET', () => {
197 | const url = 'http://example.com/rurl_rtype_temp_get'
198 |
199 | Mock.mock(url, 'get', {
200 | 'list|1-10': [
201 | {
202 | 'id|+1': 1,
203 | email: '@EMAIL',
204 | type: 'get'
205 | }
206 | ]
207 | })
208 |
209 | return request({
210 | url
211 | }).then(({ data }) => {
212 | dataAssert(data)
213 | data.list.forEach(function(item) {
214 | expect(item).to.have.property('type').equal('get')
215 | })
216 | })
217 | })
218 |
219 | it('Mock.mock( rurl, rtype, template ) - POST', () => {
220 | const url = 'http://example.com/rurl_rtype_temp_post'
221 | Mock.mock(url, 'post', {
222 | 'list|1-10': [
223 | {
224 | 'id|+1': 1,
225 | email: '@EMAIL',
226 | type: 'post'
227 | }
228 | ]
229 | })
230 |
231 | return request({
232 | url,
233 | method: 'POST'
234 | }).then(({ data }) => {
235 | dataAssert(data)
236 | data.list.forEach(function(item) {
237 | expect(item).to.have.property('type').equal('post')
238 | })
239 | })
240 | })
241 |
242 | it('Mock.mock( rurl, rtype, function(options) ) - GET', () => {
243 | const url = 'http://example.com/rurl_rtype_function_get'
244 |
245 | Mock.mock(url, /get/, function(options) {
246 | expect(options).to.not.equal(undefined)
247 | expect(options.url).to.be.equal(url)
248 | expect(options.type).to.be.equal('GET')
249 | expect(options.body).to.be.equal(null)
250 | return {
251 | type: 'get'
252 | }
253 | })
254 |
255 | return request({
256 | url
257 | }).then(({ data }) => {
258 | expect(data).to.have.property('type', 'get')
259 | })
260 | })
261 |
262 | it('Mock.mock( rurl, rtype, function(options) ) - POST|PUT', () => {
263 | const url = 'http://example.com/rurl_rtype_function_post_put'
264 | Mock.mock(url, /post|put/, function(options) {
265 | expect(options).to.not.equal(undefined)
266 | expect(options.url).to.be.equal(url)
267 | expect(['POST', 'PUT']).to.include(options.type)
268 | expect(options.body).to.be.equal(null)
269 | return {
270 | type: options.type.toLowerCase()
271 | }
272 | })
273 |
274 | return Promise.all([
275 | request({
276 | url,
277 | method: 'POST'
278 | }),
279 | request({
280 | url,
281 | method: 'PUT'
282 | })
283 | ]).then(([res1, res2]) => {
284 | expect(res1.data).to.have.property('type', 'post')
285 | expect(res2.data).to.have.property('type', 'put')
286 | })
287 | })
288 |
289 | it('Mock.mock( rurl, rtype, function(options) ) + data - GET', () => {
290 | const url = 'http://example.com/rurl_rtype_function_get_data'
291 |
292 | Mock.mock(url, /get/, function(options) {
293 | expect(options).to.not.equal(undefined)
294 | expect(options.url).to.be.equal(url + '?foo=1')
295 | expect(options.type).to.be.equal('GET')
296 | expect(options.body).to.be.equal(null)
297 | return {
298 | type: 'get'
299 | }
300 | })
301 |
302 | return request({
303 | url: url + '?foo=1'
304 | }).then(({ data }) => {
305 | expect(data).to.have.property('type', 'get')
306 | })
307 | })
308 |
309 | it('Mock.mock( rurl, rtype, function(options) ) + data - POST|PUT', () => {
310 | const url = 'http://example.com/rurl_rtype_function_post_put_data'
311 |
312 | Mock.mock(url, /post|put/, function(options) {
313 | expect(options).to.not.equal(undefined)
314 | expect(options.url).to.be.equal(url)
315 | expect(['POST', 'PUT']).to.include(options.type)
316 | expect(JSON.stringify(options.body)).to.be.equal('{"foo":1}')
317 | return {
318 | type: options.type.toLowerCase()
319 | }
320 | })
321 |
322 | return Promise.all([
323 | request({
324 | url,
325 | method: 'POST',
326 | data: { foo: 1 }
327 | }),
328 | request({
329 | url,
330 | method: 'PUT',
331 | data: { foo: 1 }
332 | })
333 | ]).then(([res1, res2]) => {
334 | expect(res1.data).to.have.property('type', 'post')
335 | expect(res2.data).to.have.property('type', 'put')
336 | })
337 | })
338 |
339 | it('Mock.mock( rurl, rtype, function(options) ) + get + params', () => {
340 | const url = 'http://example.com/rurl_rtype_function_get_params'
341 |
342 | Mock.mock(url, 'get', function(options) {
343 | expect(options).to.not.equal(undefined)
344 | expect(options.url).to.be.equal(url + '?foo=1')
345 | expect(options.type).to.be.equal('GET')
346 | expect(options.body).to.be.equal(null)
347 | return {
348 | type: 'get'
349 | }
350 | })
351 |
352 | return request({
353 | url: url + '?foo=1'
354 | }).then(({ data }) => {
355 | expect(data).to.have.property('type', 'get')
356 | })
357 | })
358 |
359 | it('Mock.mock( rurl, rtype, function(options) ) + method not case sensitive', () => {
360 | const url = 'http://example.com/rurl_rtype_function_not_case_sensitive'
361 |
362 | Mock.mock(url, 'GET', function(options) {
363 | expect(options).to.not.equal(undefined)
364 | expect(options.url).to.be.equal(url + '?foo=1')
365 | expect(options.type).to.be.equal('GET')
366 | expect(options.body).to.be.equal(null)
367 | return {
368 | type: 'get'
369 | }
370 | })
371 |
372 | return request({
373 | url: url + '?foo=1'
374 | }).then(({ data }) => {
375 | expect(data).to.have.property('type', 'get')
376 | })
377 | })
378 | })
379 |
380 |
--------------------------------------------------------------------------------
/test/mp/test.schema.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.mp')
2 | const expect = require('chai').expect
3 | const { describe, it } = global
4 |
5 | describe('Schema', function () {
6 | function stringify (json) {
7 | return JSON.stringify(json /*, null, 4*/)
8 | }
9 |
10 | function doit (template, validator) {
11 | it('', function () {
12 | var schema = Mock.toJSONSchema(template)
13 | this.test.title = (stringify(template) || template.toString()) + ' => ' + stringify(schema)
14 | validator(schema)
15 | })
16 | }
17 |
18 | describe('Type', function () {
19 | doit(1, function (schema) {
20 | expect(schema.name).to.be.an('undefined')
21 | // expect(schema).to.not.have.property('name')
22 | expect(schema).to.have.property('type', 'number')
23 | for (var n in schema.rule) {
24 | expect(schema.rule[n]).to.be.null()
25 | }
26 | })
27 | doit(true, function (schema) {
28 | expect(schema.name).to.be.an('undefined')
29 | // expect(schema).to.not.have.property('name')
30 | expect(schema).to.have.property('type', 'boolean')
31 | for (var n in schema.rule) {
32 | expect(schema.rule[n]).to.be.null()
33 | }
34 | })
35 | doit('', function (schema) {
36 | expect(schema.name).to.be.an('undefined')
37 | // expect(schema).to.not.have.property('name')
38 | expect(schema).to.have.property('type', 'string')
39 | for (var n in schema.rule) {
40 | expect(schema.rule[n]).to.be.null()
41 | }
42 | })
43 | doit(function () {}, function (schema) {
44 | expect(schema.name).to.be.an('undefined')
45 | // expect(schema).to.not.have.property('name')
46 | expect(schema).to.have.property('type', 'function')
47 | for (var n in schema.rule) {
48 | expect(schema.rule[n]).to.be.null()
49 | }
50 | })
51 | doit(/\d/, function (schema) {
52 | expect(schema.name).to.be.an('undefined')
53 | // expect(schema).to.not.have.property('name')
54 | expect(schema).to.have.property('type', 'regexp')
55 | for (var n in schema.rule) {
56 | expect(schema.rule[n]).to.be.null()
57 | }
58 | })
59 | doit([], function (schema) {
60 | expect(schema.name).to.be.an('undefined')
61 | // expect(schema).to.not.have.property('name')
62 | expect(schema).to.have.property('type', 'array')
63 | for (var n in schema.rule) {
64 | expect(schema.rule[n]).to.be.null()
65 | }
66 | expect(schema).to.have.property('items').with.length(0)
67 | })
68 | doit({}, function (schema) {
69 | expect(schema.name).to.be.an('undefined')
70 | // expect(schema).to.not.have.property('name')
71 | expect(schema).to.have.property('type', 'object')
72 | for (var n in schema.rule) {
73 | expect(schema.rule[n]).to.be.null()
74 | }
75 | expect(schema).to.have.property('properties').with.length(0)
76 | })
77 |
78 | })
79 |
80 | describe('Object', function () {
81 | doit({
82 | a: {
83 | b: {
84 | c: {
85 | d: {}
86 | }
87 | }
88 | }
89 | }, function (schema) {
90 | expect(schema.name).to.be.an('undefined')
91 | // expect(schema).to.not.have.property('name')
92 | expect(schema).to.have.property('type', 'object')
93 |
94 | var properties
95 |
96 | // root.properties
97 | properties = schema.properties
98 | expect(properties).to.with.length(1)
99 | expect(properties[0]).to.have.property('name', 'a')
100 | expect(properties[0]).to.have.property('type', 'object')
101 |
102 | // root.a.properties
103 | properties = properties[0].properties
104 | expect(properties).to.with.length(1)
105 | expect(properties[0]).to.have.property('name', 'b')
106 | expect(properties[0]).to.have.property('type', 'object')
107 |
108 | // root.a.b.properties
109 | properties = properties[0].properties
110 | expect(properties).to.with.length(1)
111 | expect(properties[0]).to.have.property('name', 'c')
112 | expect(properties[0]).to.have.property('type', 'object')
113 |
114 | // root.a.b.c.properties
115 | properties = properties[0].properties
116 | expect(properties).to.with.length(1)
117 | expect(properties[0]).to.have.property('name', 'd')
118 | expect(properties[0]).to.have.property('type', 'object')
119 |
120 | // root.a.b.c.d.properties
121 | properties = properties[0].properties
122 | expect(properties).to.with.length(0)
123 | })
124 |
125 | })
126 |
127 | describe('Array', function () {
128 | doit([
129 | [
130 | ['foo', 'bar']
131 | ]
132 | ], function (schema) {
133 | expect(schema.name).to.be.an('undefined')
134 | // expect(schema).to.not.have.property('name')
135 | expect(schema).to.have.property('type', 'array')
136 |
137 | var items
138 |
139 | // root.items
140 | items = schema.items
141 | expect(items).to.with.length(1)
142 | expect(items[0]).to.have.property('type', 'array')
143 |
144 | // root[0].items
145 | items = items[0].items
146 | expect(items).to.with.length(1)
147 | expect(items[0]).to.have.property('type', 'array')
148 |
149 | // root[0][0].items
150 | items = items[0].items
151 | expect(items).to.with.length(2)
152 | expect(items[0]).to.have.property('type', 'string')
153 | expect(items[1]).to.have.property('type', 'string')
154 | })
155 | })
156 |
157 | describe('String Rule', function () {
158 | doit({
159 | 'string|1-10': '★'
160 | }, function (schema) {
161 | expect(schema.name).to.be.an('undefined')
162 | // expect(schema).to.not.have.property('name')
163 | expect(schema).to.have.property('type', 'object')
164 |
165 | var properties
166 | // root.properties
167 | properties = schema.properties
168 | expect(properties).to.with.length(1)
169 | expect(properties[0]).to.have.property('type', 'string')
170 | expect(properties[0].rule).to.have.property('min', 1)
171 | expect(properties[0].rule).to.have.property('max', 10)
172 | })
173 | doit({
174 | 'string|3': 'value'
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', 3)
186 | expect(properties[0].rule.max).to.be.an('undefined')
187 | })
188 | })
189 |
190 | })
191 |
--------------------------------------------------------------------------------
/test/mp/test.valid.js:
--------------------------------------------------------------------------------
1 | const Mock = require('../../dist/mock.mp')
2 | const expect = require('chai').expect
3 | const { describe, it } = global
4 |
5 | describe('Mock.valid', function () {
6 | function stringify (json) {
7 | return JSON.stringify(json /*, null, 4*/)
8 | }
9 |
10 | function title (tpl, data, result, test) {
11 | test.title = stringify(tpl) + ' VS ' + stringify(data) + '\n\tresult: ' + stringify(result)
12 | }
13 |
14 | function doit (tpl, data, len) {
15 | it('', function () {
16 | var result = Mock.valid(tpl, data)
17 | title(tpl, data, result, this.test)
18 | expect(result).to.be.an('array').with.length(len)
19 | })
20 | }
21 |
22 | describe('Name', function () {
23 | doit({
24 | name: 1
25 | }, {
26 | name: 1
27 | }, 0)
28 |
29 | doit({
30 | name1: 1
31 | }, {
32 | name2: 1
33 | }, 1)
34 | })
35 | describe('Value - Number', function () {
36 | doit({
37 | name: 1
38 | }, {
39 | name: 1
40 | }, 0)
41 |
42 | doit({
43 | name: 1
44 | }, {
45 | name: 2
46 | }, 1)
47 |
48 | doit({
49 | name: 1.1
50 | }, {
51 | name: 2.2
52 | }, 1)
53 |
54 | doit({
55 | 'name|1-10': 1
56 | }, {
57 | name: 5
58 | }, 0)
59 |
60 | doit({
61 | 'name|1-10': 1
62 | }, {
63 | name: 0
64 | }, 1)
65 |
66 | doit({
67 | 'name|1-10': 1
68 | }, {
69 | name: 11
70 | }, 1)
71 |
72 | doit({ 'name|5': 1 }, { name: 5 }, 0)
73 |
74 | doit({ 'name|5': 1 }, { name: 6 }, 1)
75 |
76 | doit({ 'name|.5': 1 }, { name: 1.34567 }, 0)
77 |
78 | doit({ 'name|.5': 1 }, { name: 1.345678 }, 1)
79 |
80 | doit({ 'name|.5-7': 1 }, { name: 1.345678 }, 0)
81 |
82 | doit({ 'name|.5-7': 1 }, { name: 1.3456 }, 1)
83 | })
84 |
85 | describe('Value - String', function () {
86 | doit({
87 | name: 'value'
88 | }, {
89 | name: 'value'
90 | }, 0)
91 |
92 | doit({
93 | name: 'value1'
94 | }, {
95 | name: 'value2'
96 | }, 1)
97 |
98 | doit({
99 | 'name|1': 'value'
100 | }, {
101 | name: 'value'
102 | }, 0)
103 |
104 | doit({
105 | 'name|2': 'value'
106 | }, {
107 | name: 'valuevalue'
108 | }, 0)
109 |
110 | doit({
111 | 'name|2': 'value'
112 | }, {
113 | name: 'value'
114 | }, 1)
115 |
116 | doit({
117 | 'name|2-3': 'value'
118 | }, {
119 | name: 'value'
120 | }, 1)
121 |
122 | doit({
123 | 'name|2-3': 'value'
124 | }, {
125 | name: 'valuevaluevaluevalue'
126 | }, 1)
127 | })
128 |
129 | describe('Value - RgeExp', function () {
130 | doit({
131 | name: /value/
132 | }, {
133 | name: 'value'
134 | }, 0)
135 | doit({
136 | name: /value/
137 | }, {
138 | name: 'vvvvv'
139 | }, 1)
140 | doit({
141 | 'name|1-10': /value/
142 | }, {
143 | name: 'valuevaluevaluevaluevalue'
144 | }, 0)
145 | doit({
146 | 'name|1-10': /value/
147 | }, {
148 | name: 'vvvvvvvvvvvvvvvvvvvvvvvvv'
149 | }, 1)
150 | doit({
151 | 'name|1-10': /^value$/
152 | }, {
153 | name: 'valuevaluevaluevaluevalue'
154 | }, 0)
155 |
156 | doit({ 'name|2': /^value$/ }, { name: 'valuevalue' }, 0)
157 |
158 | doit({
159 | name: /[a-z][A-Z][0-9]/
160 | }, {
161 | name: 'yL5'
162 | }, 0)
163 | })
164 | describe('Value - Object', function () {
165 | doit({
166 | name: 1
167 | }, {
168 | name: 1
169 | }, 0)
170 | doit({
171 | name1: 1
172 | }, {
173 | name2: 2
174 | }, 1)
175 | doit({
176 | name1: 1,
177 | name2: 2
178 | }, {
179 | name3: 3
180 | }, 1)
181 | doit({
182 | name1: 1,
183 | name2: 2
184 | }, {
185 | name1: '1',
186 | name2: '2'
187 | }, 2)
188 | doit({
189 | a: {
190 | b: {
191 | c: {
192 | d: 1
193 | }
194 | }
195 | }
196 | }, {
197 | a: {
198 | b: {
199 | c: {
200 | d: 2
201 | }
202 | }
203 | }
204 | }, 1)
205 |
206 | doit({
207 | name: {
208 | 'age|1-3': 1
209 | }
210 | }, {
211 | name: {
212 | age: 2
213 | }
214 | }, 0)
215 | })
216 | describe('Value - Array', function () {
217 | doit([1, 2, 3], [1, 2, 3], 0)
218 |
219 | doit([1, 2, 3], [1, 2, 3, 4], 1)
220 |
221 | // 'name|1': array
222 | doit({
223 | 'name|1': [1, 2, 3]
224 | }, {
225 | 'name': 1
226 | }, 0)
227 | doit({
228 | 'name|1': [1, 2, 3]
229 | }, {
230 | 'name': 2
231 | }, 0)
232 | doit({
233 | 'name|1': [1, 2, 3]
234 | }, {
235 | 'name': 3
236 | }, 0)
237 | doit({ // 不检测
238 | 'name|1': [1, 2, 3]
239 | }, {
240 | 'name': 4
241 | }, 0)
242 |
243 | // 'name|+1': array
244 | doit({
245 | 'name|+1': [1, 2, 3]
246 | }, {
247 | 'name': 1
248 | }, 0)
249 | doit({
250 | 'name|+1': [1, 2, 3]
251 | }, {
252 | 'name': 2
253 | }, 0)
254 | doit({
255 | 'name|+1': [1, 2, 3]
256 | }, {
257 | 'name': 3
258 | }, 0)
259 | doit({
260 | 'name|+1': [1, 2, 3]
261 | }, {
262 | 'name': 4
263 | }, 0)
264 |
265 | // 'name|min-max': array
266 | doit({
267 | 'name|2-3': [1]
268 | }, {
269 | 'name': [1, 2, 3, 4]
270 | }, 1)
271 |
272 | doit({
273 | 'name|2-3': [1]
274 | }, {
275 | 'name': [1]
276 | }, 1)
277 |
278 | doit({
279 | 'name|2-3': [1, 2, 3]
280 | }, {
281 | 'name': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
282 | }, 1)
283 |
284 | doit({
285 | 'name|2-3': [1, 2, 3]
286 | }, {
287 | 'name': [1, 2, 3]
288 | }, 1)
289 |
290 | doit({
291 | 'name|2-3': [1]
292 | }, {
293 | 'name': [1, 1, 1]
294 | }, 0)
295 |
296 | doit({
297 | 'name|2-3': [1]
298 | }, {
299 | 'name': [1, 2, 3]
300 | }, 2)
301 |
302 | // 'name|count': array
303 | })
304 | describe('Value - Placeholder', function () {
305 | doit({
306 | name: '@email'
307 | }, {
308 | name: 'nuysoft@gmail.com'
309 | }, 0)
310 | doit({
311 | name: '@int'
312 | }, {
313 | name: 123
314 | }, 0)
315 | })
316 | })
317 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "es5",
5 | "module": "es2015",
6 | "moduleResolution": "node",
7 | "noImplicitAny": false,
8 | "outDir": ".tmp",
9 | "strict": true,
10 | "resolveJsonModule": true,
11 | "lib": [
12 | "es5",
13 | "es6",
14 | "es7",
15 | "dom"
16 | ]
17 | },
18 | "include": [
19 | "src/**/*.*"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------