├── .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 | [![Build Status](https://travis-ci.org/lavyun/better-mock.svg?branch=master)](https://travis-ci.org/lavyun/better-mock) 6 | [![Coverage Status](https://coveralls.io/repos/github/lavyun/better-mock/badge.svg?branch=master)](https://coveralls.io/github/lavyun/better-mock?branch=master) 7 | ![npm](https://img.shields.io/npm/v/better-mock) 8 | ![NPM](https://img.shields.io/npm/l/better-mock) 9 | ![npm](https://img.shields.io/npm/dw/better-mock) 10 | ![type-coverage](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=type-coverage&suffix=%&query=%24.typeCoverage.atLeast&url=https%3A%2F%2Fraw.githubusercontent.com%2Flavyun%2Fbetter-mock%2Fmaster%2Fpackage.json) 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 | 6 | 7 | -------------------------------------------------------------------------------- /doc/.vuepress/components/page-playground.vue: -------------------------------------------------------------------------------- 1 | 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 | ``, 115 | ``, 116 | text ? `${text}` : '', 117 | `` 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 | --------------------------------------------------------------------------------