├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── ci.yml
│ ├── docs-build.yml
│ └── packages-ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── .vuepress
│ ├── config.js
│ ├── public
│ │ └── assets
│ │ │ ├── 6d308bd9gw1f6wsibnfldg20nk0gr7kg.gif
│ │ │ └── 6d308bd9gw1f6wsic5dmxj20rl0qqtbi.jpg
│ └── styles
│ │ └── palette.styl
├── README.md
├── guide
│ ├── README.md
│ ├── advanced.md
│ ├── install.md
│ └── usage.md
└── zh
│ ├── README.md
│ └── guide
│ ├── README.md
│ ├── advanced.md
│ ├── install.md
│ └── usage.md
├── index.js
├── lib
├── commands
│ ├── browser-events.js
│ ├── index.js
│ ├── macaca-datahub.js
│ └── page-manager.js
├── mocha
│ ├── browser
│ │ ├── progress.js
│ │ └── tty.js
│ ├── context.js
│ ├── hook.js
│ ├── interfaces
│ │ ├── bdd.js
│ │ ├── common.js
│ │ ├── exports.js
│ │ ├── index.js
│ │ ├── qunit.js
│ │ └── tdd.js
│ ├── mocha.js
│ ├── ms.js
│ ├── pending.js
│ ├── reporters
│ │ ├── base.js
│ │ ├── doc.js
│ │ ├── dot.js
│ │ ├── html.js
│ │ ├── index.js
│ │ ├── json-stream.js
│ │ ├── json.js
│ │ ├── landing.js
│ │ ├── list.js
│ │ ├── markdown.js
│ │ ├── min.js
│ │ ├── nyan.js
│ │ ├── progress.js
│ │ ├── spec.js
│ │ ├── tap.js
│ │ └── xunit.js
│ ├── runnable.js
│ ├── runner.js
│ ├── suite.js
│ ├── template.html
│ ├── test.js
│ └── utils.js
├── playwright.js
├── uitest.js
└── utils
│ └── index.js
├── mocha.entry.js
├── package.json
├── packages
└── gulp-uitest
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── README.md
│ ├── index.js
│ ├── lib
│ └── gulp-uitest.js
│ ├── package.json
│ └── test
│ ├── gulp-uitest.test.js
│ └── mocha.opts
├── rollup.config.js
├── test
├── case-sample
│ ├── .eslintrc.js
│ ├── fileChoose.js
│ ├── keyboard.js
│ ├── mouse.js
│ ├── page.js
│ ├── retries.js
│ ├── sample1.js
│ └── sample2.js
├── ci.sh
├── index.html
├── mocha.opts
├── uitest.test.js
└── uitls.test.js
└── uitest-mocha-shim.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | docs_dist
4 | mocha.js
5 | uitest-mocha-shim.js
6 | lib/mocha
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | extends: 'eslint-config-egg',
6 | parserOptions: {
7 | ecmaVersion: 2020,
8 | },
9 | plugins: [],
10 | rules: {
11 | 'valid-jsdoc': 0,
12 | 'no-script-url': 0,
13 | 'no-multi-spaces': 0,
14 | 'default-case': 0,
15 | 'no-case-declarations': 0,
16 | 'one-var-declaration-per-line': 0,
17 | 'no-restricted-syntax': 0,
18 | 'jsdoc/require-param': 0,
19 | 'jsdoc/check-param-names': 0,
20 | 'jsdoc/require-param-description': 0,
21 | 'arrow-parens': 0,
22 | 'prefer-promise-reject-errors': 0,
23 | 'no-control-regex': 0,
24 | 'no-use-before-define': 0,
25 | 'array-callback-return': 0,
26 | 'no-bitwise': 0,
27 | 'no-self-compare': 0,
28 | 'one-var': 0,
29 | 'no-trailing-spaces': [ 'warn', { skipBlankLines: true }],
30 | 'no-return-await': 0,
31 | },
32 | globals: {
33 | window: true,
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | push:
7 | branches:
8 | - '**'
9 |
10 | jobs:
11 | Runner:
12 | timeout-minutes: 10
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout git source
16 | uses: actions/checkout@v3
17 |
18 | - name: Setup docker
19 | uses: docker-practice/actions-setup-docker@1.0.9
20 |
21 | - name: Setup node.js
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: '16'
25 |
26 | - name: Install dependencies
27 | run: |
28 | npm i --force
29 |
30 | - name: Continuous integration
31 | run: |
32 | npm run lint
33 | docker run -i --entrypoint=bash -v `pwd`:/root/tmp --rm mcr.microsoft.com/playwright:v1.26.0-focal -c "cd /root/tmp && ./test/ci.sh"
34 |
35 | - name: Code coverage
36 | uses: codecov/codecov-action@v3.0.0
37 | with:
38 | token: ${{ secrets.CODECOV_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.github/workflows/docs-build.yml:
--------------------------------------------------------------------------------
1 | name: Docs Build
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | push:
7 | branches:
8 | - master
9 |
10 | jobs:
11 | docs-build:
12 | timeout-minutes: 10
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout git source
17 | uses: actions/checkout@v3
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Setup node.js
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: '16'
25 |
26 | - name: Install dependencies
27 | run: |
28 | npm i npm@6 -g
29 | npm i vuepress macaca-ecosystem -D
30 |
31 | - name: Build docs
32 | run: npm run docs:build
33 |
34 | - name: Deploy to GitHub Pages
35 | if: success()
36 | uses: peaceiris/actions-gh-pages@v3
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: ./docs_dist
40 |
--------------------------------------------------------------------------------
/.github/workflows/packages-ci.yml:
--------------------------------------------------------------------------------
1 | name: Packages CI
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | push:
7 | branches:
8 | - '**'
9 |
10 | jobs:
11 | Runner:
12 | timeout-minutes: 10
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout git source
16 | uses: actions/checkout@v3
17 |
18 | - name: Setup node.js
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: '16'
22 |
23 | - name: Install dependencies
24 | run: |
25 | cd ./packages/gulp-uitest/
26 | npm i npm@6 -g
27 | npm i
28 |
29 | - name: Continuous integration
30 | run: |
31 | cd ./packages/gulp-uitest/
32 | npm run lint
33 | npm run test
34 |
35 | - name: Code coverage
36 | uses: codecov/codecov-action@v3.0.0
37 | with:
38 | token: ${{ secrets.CODECOV_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .nyc_output
3 | docs_dist/
4 | coverage/
5 | reports/
6 | *.sw*
7 | *.un~
8 | mocha.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT LICENSE
2 |
3 | Copyright (c) 2017 Alibaba Group Holding Limited and other contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # uitest
2 |
3 | ---
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![CI][CI-image]][CI-url]
7 | [![Test coverage][coveralls-image]][coveralls-url]
8 | [![npm download][download-image]][download-url]
9 |
10 | [npm-image]: https://img.shields.io/npm/v/uitest.svg
11 | [npm-url]: https://npmjs.org/package/uitest
12 | [CI-image]: https://github.com/macacajs/uitest/actions/workflows/ci.yml/badge.svg
13 | [CI-url]: https://github.com/macacajs/uitest/actions/workflows/ci.yml
14 | [coveralls-image]: https://img.shields.io/coveralls/macacajs/uitest.svg
15 | [coveralls-url]: https://coveralls.io/r/macacajs/uitest?branch=master
16 | [download-image]: https://img.shields.io/npm/dm/uitest.svg
17 | [download-url]: https://npmjs.org/package/uitest
18 |
19 | > Run mocha in a browser environment.
20 |
21 |
22 |
23 | ## Contributors
24 |
25 | |[
xudafeng](https://github.com/xudafeng)
|[
zivyangll](https://github.com/zivyangll)
|[
meowtec](https://github.com/meowtec)
|[
ilimei](https://github.com/ilimei)
|[
paradite](https://github.com/paradite)
|[
snapre](https://github.com/snapre)
|
26 | | :---: | :---: | :---: | :---: | :---: | :---: |
27 |
28 |
29 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed Feb 15 2023 14:41:53 GMT+0800`.
30 |
31 |
32 |
33 | ## Installation
34 |
35 | ```bash
36 | $ npm i uitest --save-dev
37 | ```
38 |
39 | For more help, please visite: [macacajs.github.io/uitest](//macacajs.github.io/uitest)
40 |
41 | ## License
42 |
43 | The MIT License (MIT)
44 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const macacaEcosystem = require('macaca-ecosystem');
4 | const traceFragment = require('macaca-ecosystem/lib/trace-fragment');
5 |
6 | const name = 'uitest';
7 |
8 | const title = 'Macaca UITest';
9 |
10 | module.exports = {
11 | dest: 'docs_dist',
12 | base: `/${name}/`,
13 |
14 | locales: {
15 | '/': {
16 | lang: 'en-US',
17 | title,
18 | description: 'Run mocha in a browser environment.',
19 | },
20 | '/zh/': {
21 | lang: 'zh-CN',
22 | title,
23 | description: '在浏览器环境中运行测试。',
24 | },
25 | },
26 | head: [
27 | ['link', {
28 | rel: 'icon',
29 | href: 'https://macacajs.github.io/assets/favicon.ico'
30 | }],
31 | ['script', {
32 | async: true,
33 | src: 'https://www.googletagmanager.com/gtag/js?id=UA-49226133-2',
34 | }, ''],
35 | ['script', {}, `
36 | window.dataLayer = window.dataLayer || [];
37 | function gtag(){dataLayer.push(arguments);}
38 | gtag('js', new Date());
39 | gtag('config', 'UA-49226133-2');
40 | `],
41 | ['script', {}, traceFragment],
42 | ['style', {}, `
43 | img {
44 | width: 100%;
45 | }
46 | `]
47 | ],
48 | serviceWorker: true,
49 | themeConfig: {
50 | repo: `macacajs/${name}`,
51 | editLinks: true,
52 | docsDir: 'docs',
53 | locales: {
54 | '/': {
55 | label: 'English',
56 | selectText: 'Languages',
57 | editLinkText: 'Edit this page on GitHub',
58 | lastUpdated: 'Last Updated',
59 | serviceWorker: {
60 | updatePopup: {
61 | message: 'New content is available.',
62 | buttonText: 'Refresh',
63 | },
64 | },
65 | nav: [
66 | {
67 | text: 'Guide',
68 | link: '/guide/'
69 | },
70 | macacaEcosystem.en,
71 | ],
72 | sidebar: {
73 | '/guide/': genSidebarConfig('Guide', 'Usage', 'Advanced'),
74 | },
75 | },
76 | '/zh/': {
77 | label: '简体中文',
78 | selectText: '选择语言',
79 | editLinkText: '在 GitHub 上编辑此页',
80 | lastUpdated: '上次更新',
81 | serviceWorker: {
82 | updatePopup: {
83 | message: '发现新内容可用',
84 | buttonText: '刷新',
85 | },
86 | },
87 | nav: [
88 | {
89 | text: '指南',
90 | link: '/zh/guide/'
91 | },
92 | macacaEcosystem.zh,
93 | ],
94 | sidebar: {
95 | '/zh/guide/': genSidebarConfig('指南'),
96 | },
97 | },
98 | },
99 | },
100 | };
101 |
102 | function genSidebarConfig(guide) {
103 | return [
104 | {
105 | title: guide,
106 | collapsable: false,
107 | children: [
108 | '',
109 | 'install',
110 | 'usage',
111 | 'advanced',
112 | ],
113 | },
114 | ];
115 | }
116 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/6d308bd9gw1f6wsibnfldg20nk0gr7kg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macacajs/uitest/01372ad0ae75077b1a50baacd92706085a9ed6b4/docs/.vuepress/public/assets/6d308bd9gw1f6wsibnfldg20nk0gr7kg.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/6d308bd9gw1f6wsic5dmxj20rl0qqtbi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macacajs/uitest/01372ad0ae75077b1a50baacd92706085a9ed6b4/docs/.vuepress/public/assets/6d308bd9gw1f6wsic5dmxj20rl0qqtbi.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 | $textColor = #2c3e50
2 | $borderColor = #eaecef
3 | $accentColor = #ee6723
4 | $codeBgColor = #282c34
5 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | home: true
4 | heroImage: https://macacajs.github.io/logo/macaca.svg
5 | actionText: Try it Out →
6 | actionLink: /guide/
7 | footer: MIT Licensed | Copyright © 2015-present Macaca
8 |
9 | ---
10 |
11 | ## Quick Start
12 |
13 | ```bash
14 | # install UITest
15 | $ npm i uitest --save-dev
16 | ```
17 |
18 | ---
19 |
20 | ::: tip Browser
21 | UITest support running in the browser.
22 | :::
23 |
24 | 
25 |
26 | ::: tip Headless
27 | Of course, UITest also supports running in the command-line environment.
28 | :::
29 |
30 | 
31 |
--------------------------------------------------------------------------------
/docs/guide/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | UITest is a test framework that make your code runs in the browser. It is mainly for browsers and is a member of the [Macaca](//macacajs.github.io) ecosystem.
4 |
5 | UITest mainly solves unit test scenarios that require the real environment of the Chromium, such as running JavaScript in V8, verifying whether the rendering is successful in the rendering engine, providing screenshots, coverage, and the corresponding server CI solution. You can now easily test any JavaScript app in a real Chromium without hassling solution.
6 |
--------------------------------------------------------------------------------
/docs/guide/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced
2 |
3 | ## Configuration downgrade
4 |
5 | If you do not want the page to display in retina mode, set `hidpi` to false.
6 |
7 | For more options, see [Electron BrowserWindow options](http://electron.atom.io/docs/api/browser-window/#new-browserwindowoptions).
8 |
9 | ## Run with Travis
10 |
11 | Your `.travis.yml` need the configuration below to run UITest on Travis:
12 |
13 | ```yaml
14 | addons:
15 | apt:
16 | packages:
17 | - xvfb
18 | install:
19 | - export DISPLAY=':99.0'
20 | - Xvfb :99 -screen 0 1366x768x24 > /dev/null 2>&1 &
21 | ```
22 |
23 | ## Run with Docker
24 |
25 | You can use Macaca Electron [Docker Image](//github.com/macacajs/macaca-electron-docker).
26 |
--------------------------------------------------------------------------------
/docs/guide/install.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Requirements
4 |
5 | To install the UITest, [Node.js](https://nodejs.org) environment is required.
6 |
7 | ## Install from NPM
8 |
9 | ```bash
10 | $ npm i uitest --save-dev
11 | ```
12 |
13 | ## Sample
14 |
15 | ```bash
16 | $ git clone https://github.com/macaca-sample/uitest-sample.git --depth=1
17 | $ cd uitest-sample
18 | $ npm i
19 | $ npm run test
20 | ```
21 |
22 | ## More
23 |
24 | - [Game framework Hilo test sample](https://github.com/hiloteam/Hilo)
25 | - [Canvas framework monitor.js test sample](https://github.com/pillowjs/monitor.js)
26 |
--------------------------------------------------------------------------------
/docs/guide/usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | You should configure your entry HTML by including `uitest-mocha-shim.js`.
4 |
5 | Here is an example `test.html`
6 |
7 | ```html
8 |
9 |
10 |
11 | macaca mocha test
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
39 |
42 |
43 |
44 | ```
45 |
46 | ## Start with Node.js
47 |
48 | Your can start uitest using Node API:
49 |
50 | ```javascript
51 | const uitest = require('uitest');
52 |
53 | uitest({
54 | url: 'file:///Users/name/path/index.html',
55 | width: 600,
56 | height: 480,
57 | hidpi: false,
58 | useContentSize: true,
59 | show: false,
60 | }).then(() => {
61 | console.log('uitest success')
62 | }).catch(() => {
63 | console.log('uitest error')
64 | });
65 | ```
66 |
67 | 
68 |
69 | 
70 |
71 | ## Use with Gulp
72 |
73 | ```bash
74 | $ npm i gulp-uitest --save-dev
75 | ```
76 |
77 | ```javascript
78 | const uitest = require('gulp-uitest');
79 | //test
80 | gulp.task('test', function() {
81 | return gulp
82 | .src('test/html/index.html')
83 | .pipe(uitest({
84 | width: 600,
85 | height: 480,
86 | hidpi: false,
87 | useContentSize: true,
88 | show: false,
89 | }));
90 | });
91 |
92 | ```
93 |
94 | ## Use Screenshots
95 |
96 | ```javascript
97 | _macaca_uitest.screenshot(name[String], cb[Function]);
98 | ```
99 |
100 | ## Coverage
101 |
102 | UITest will generate the coverage file if `window.__coverage__` is existed.
103 |
104 | process.env.MACACA_COVERAGE_IGNORE_REG support coverage ignore rule, for example:
105 |
106 | MACACA_COVERAGE_IGNORE_REG='test/' means all files in the `./test` directory are ignored.
107 |
--------------------------------------------------------------------------------
/docs/zh/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | home: true
4 | heroImage: https://macacajs.github.io/logo/macaca.svg
5 | actionText: 使用文档
6 | actionLink: /zh/guide/
7 | footer: MIT Licensed | Copyright © 2015-present Macaca
8 |
9 | ---
10 |
11 | ## 安装简单
12 |
13 | ```bash
14 | $ npm i uitest --save-dev
15 | ```
16 |
17 | ---
18 |
19 | ::: tip 浏览器运行
20 | UITest 支持在浏览器中运行。
21 | :::
22 |
23 | 
24 |
25 | ::: tip 命令行运行
26 | 当然,UITest 也支持在命令行环境运行。
27 | :::
28 |
29 | 
30 |
--------------------------------------------------------------------------------
/docs/zh/guide/README.md:
--------------------------------------------------------------------------------
1 | # 简单介绍
2 |
3 | UITest 是一款运行在浏览器中的测试框架,主要面向场景是浏览器单测,是 [Macaca](//macacajs.github.io) 生态成员之一。
4 |
5 | UITest 主要解决需要浏览器真实环境的单测场景,比如在 V8 中运行 JavaScript,在渲染引擎中验证渲染是否成功,提供了截图、覆盖率等功能和对应的服务器 CI 方案。
6 |
--------------------------------------------------------------------------------
/docs/zh/guide/advanced.md:
--------------------------------------------------------------------------------
1 | # 进阶
2 |
3 | ## 设置降级
4 |
5 | 如果你不想在 Retina 模式中展示页面,可以设置 `hidpi` 为 false。
6 |
7 | 更多配置可以参考 [Electron BrowserWindow 配置](http://electron.atom.io/docs/api/browser-window/#new-browserwindowoptions)。
8 |
9 | ## 持续集成
10 |
11 | 如果你使用 Travis CI 运行测试,你需要在 `.travis.yml` 文件中添加如下配置:
12 |
13 | ```yaml
14 | addons:
15 | apt:
16 | packages:
17 | - xvfb
18 | install:
19 | - export DISPLAY=':99.0'
20 | - Xvfb :99 -screen 0 1366x768x24 > /dev/null 2>&1 &
21 | ```
22 |
23 | ## Docker 运行
24 |
25 | 你也可以使用 Macaca Electron [Docker 镜像](//github.com/macacajs/macaca-electron-docker) 来运行。
26 |
--------------------------------------------------------------------------------
/docs/zh/guide/install.md:
--------------------------------------------------------------------------------
1 | # 安装
2 |
3 | ## 环境需要
4 |
5 | 要安装 UITest, 你需要首先安装 [Node.js](https://nodejs.org),中国用户可以安装 [cnpm](https://npm.taobao.org/) 加快 NPM 模块安装速度。
6 |
7 | ## 命令行安装
8 |
9 | ```bash
10 | $ npm i uitest --save-dev
11 | ```
12 |
13 | ## 运行示例
14 |
15 | ```bash
16 | $ git clone https://github.com/macaca-sample/uitest-sample.git --depth=1
17 | $ cd uitest-sample
18 | $ npm i
19 | $ npm run test
20 | ```
21 |
22 | ## 更多示例
23 |
24 | - [在大型游戏引擎框架 Hilo 中使用](https://github.com/hiloteam/Hilo)
25 | - [在 Canvas 框架 monitor.js 中使用](https://github.com/pillowjs/monitor.js)
26 |
--------------------------------------------------------------------------------
/docs/zh/guide/usage.md:
--------------------------------------------------------------------------------
1 | # 如何使用
2 |
3 | 安装之后可以通过在 html runner 中添加 `uitest-mocha-shim.js` 来进行 UITest 配置。
4 |
5 | 下面是一个 `test.html` 示例:
6 |
7 | ```html
8 |
9 |
10 |
11 | macaca mocha test
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
39 |
43 |
44 |
45 | ```
46 |
47 | ## 通过 Node.js 运行
48 |
49 | 可以通过 Node.js 来启动 UITest:
50 |
51 | ```javascript
52 | uitest({
53 | url: 'file:///Users/name/path/index.html',
54 | width: 600,
55 | height: 480,
56 | hidpi: false,
57 | useContentSize: true,
58 | show: false,
59 | }).then(() => {
60 | console.log('uitest success')
61 | }).catch(() => {
62 | console.log('uitest error')
63 | });
64 | ```
65 |
66 | 效果如下:
67 |
68 | 
69 |
70 | 
71 |
72 | ## 使用 Gulp
73 |
74 | 在 Gulp 中使用 UITest:
75 |
76 | ```bash
77 | $ npm i gulp-uitest --save-dev
78 | ```
79 |
80 | ```javascript
81 | const uitest = require('gulp-uitest');
82 | //test
83 | gulp.task('test', function() {
84 | return gulp
85 | .src('test/html/index.html')
86 | .pipe(uitest({
87 | width: 600,
88 | height: 480,
89 | hidpi: false,
90 | useContentSize: true,
91 | show: false,
92 | }));
93 | });
94 |
95 | 同样的,UITest 运行触发比较灵活,你可以与其他脚本和 pipeline 集成。
96 |
97 | ```
98 |
99 | ## 使用截图
100 |
101 | ```javascript
102 | _macaca_uitest.screenshot(name[String], cb[Function]);
103 | ```
104 |
105 | ## 覆盖率
106 |
107 | 当浏览器上下文中有 `window.__coverage__` 将自动生成覆盖率报告。
108 |
109 | process.env.MACACA_COVERAGE_IGNORE_REG 可以传入 coverage 忽略规则,例如:
110 |
111 | MACACA_COVERAGE_IGNORE_REG='test/' 会忽略所有 `./test` 目录下的文件。
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./lib/uitest');
4 |
--------------------------------------------------------------------------------
/lib/commands/browser-events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | keyboard: async (context, type, key, opt) => {
5 | const { page } = context;
6 | if (page.keyboard[type]) {
7 | await page.keyboard[type](key, opt);
8 | }
9 | return true;
10 | },
11 | mouse: async (context, type, x, y, opt) => {
12 | const { page } = context;
13 | if (page.mouse[type]) {
14 | await page.mouse[type](x, y, opt);
15 | }
16 | return true;
17 | },
18 | fileChooser: async (context, filePath) => {
19 | const { page } = context;
20 | const fileChooser = await page.waitForEvent('filechooser');
21 | await fileChooser.setFiles(filePath);
22 | return true;
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/lib/commands/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const { createThenableFunction } = require('../utils');
7 |
8 | const exitForWait = createThenableFunction();
9 |
10 | const commands = Object.assign({},
11 | require('./macaca-datahub'),
12 | require('./browser-events'),
13 | require('./page-manager'),
14 | {
15 | screenshot: async ({ context }, dir) => {
16 | return await context.getScreenshot(context, { dir });
17 | },
18 | getVideoName: async ({ context }) => {
19 | const filePath = await context.getScreenshot(context, { video: true });
20 | return filePath && path.basename(filePath);
21 | },
22 | exit: async ({ context }, failData) => {
23 | await context.stopDevice();
24 | exitForWait(failData.failedCount || 0);
25 | },
26 | saveCoverage: async (_, data) => {
27 | if (data) {
28 | const coverageDir = path.join(process.cwd(), 'coverage');
29 | try {
30 | if (!fs.existsSync(path.join(coverageDir))) {
31 | fs.mkdirSync(path.join(coverageDir));
32 | }
33 | if (!fs.existsSync(path.join(coverageDir, '.temp'))) {
34 | fs.mkdirSync(path.join(coverageDir, '.temp'));
35 | }
36 | } catch (e) {
37 | return false;
38 | }
39 | const file = path.join(coverageDir, '.temp', `${+new Date()}_coverage.json`);
40 | // ignore tests
41 | const coverageIgnore = process.env.MACACA_COVERAGE_IGNORE_REG;
42 | if (coverageIgnore) {
43 | const ignoreReg = new RegExp(coverageIgnore, 'i');
44 | for (const k in data) {
45 | if (ignoreReg.test(k)) {
46 | delete data[k];
47 | }
48 | }
49 | }
50 | fs.writeFileSync(file, JSON.stringify(data, null, 2));
51 | console.log(`coverage file created at: ${file}`);
52 | }
53 | return true;
54 | },
55 | saveReport: async (_, output) => {
56 | try {
57 | const reportsDir = path.join(process.cwd(), 'reports');
58 |
59 | if (!(fs.existsSync(reportsDir) && fs.statSync(reportsDir).isDirectory())) {
60 | fs.mkdirSync(reportsDir);
61 | }
62 |
63 | const reportsFile = path.join(reportsDir, 'json-final.json');
64 | fs.writeFileSync(reportsFile, JSON.stringify(output, null, 2), 'utf8');
65 | console.log(`reports file created at: ${reportsFile}`);
66 | } catch (e) {
67 | console.error(e);
68 | }
69 | },
70 | }
71 | );
72 |
73 | async function setupCommands(context) {
74 | const { browserContext } = context;
75 |
76 | await browserContext.exposeBinding('__execCommand', async (ctx, name, ...args) => {
77 | if (typeof name !== 'string') throw new Error(`invalid command name ${name}`);
78 | if (!commands[name]) throw new Error(`unknown command name ${name}`);
79 | return await commands[name]({ ...ctx, context }, ...args);
80 | }, { handle: false });
81 | }
82 |
83 | module.exports = {
84 | setupCommands,
85 | exitForWait,
86 | };
87 |
--------------------------------------------------------------------------------
/lib/commands/macaca-datahub.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const DataHubSDK = require('datahub-nodejs-sdk');
4 |
5 | const datahubClient = new DataHubSDK();
6 |
7 | module.exports = {
8 | switchScene: async (_, argData) => {
9 | try {
10 | await datahubClient.switchScene(argData);
11 | } catch (e) {
12 | return false;
13 | }
14 | return true;
15 | },
16 | switchAllScenes: async (_, argData) => {
17 | try {
18 | await datahubClient.switchAllScenes(argData);
19 | } catch (e) {
20 | return false;
21 | }
22 | return true;
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/lib/commands/page-manager.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const fs = require('fs');
3 |
4 | const shimContent = fs.readFileSync(__dirname + '/../../uitest-mocha-shim.js').toString();
5 | const pages = [];
6 |
7 | module.exports = {
8 | newPage: async ({ context }, url) => {
9 | const { browserContext } = context;
10 | const page = await browserContext.newPage();
11 | await page.goto(url, {
12 | waitUntil: 'load' || 'networkidle',
13 | });
14 | await page.addScriptTag({
15 | content: shimContent,
16 | });
17 | const redirectConsole = require('macaca-playwright/dist/lib/redirect-console');
18 | await redirectConsole({ page });
19 | return pages.push(page) - 1;
20 | },
21 | closePage: async (_, id) => {
22 | const page = pages[id];
23 | if (page) {
24 | page.close({ runBeforeUnload: true });
25 | pages[id] = null;
26 | }
27 | return false;
28 | },
29 | runInPage: async (_, id, funcString) => {
30 | const page = pages[id];
31 | if (page) {
32 | return await page.evaluate(funcString);
33 | }
34 | return false;
35 | },
36 | waitForSelector: async (_, id, selector) => {
37 | const page = pages[id];
38 | if (page) {
39 | return await page.waitForSelector(selector);
40 | }
41 | return false;
42 | },
43 | waitForEvent: async (_, id, eventName) => {
44 | const page = pages[id];
45 | if (page) {
46 | return await new Promise(resolve => {
47 | const loaded = () => {
48 | page.removeListener(eventName, loaded);
49 | resolve(true);
50 | };
51 | page.on(eventName, loaded);
52 | });
53 | }
54 | return false;
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/lib/mocha/browser/progress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Expose `Progress`.
5 | */
6 |
7 | module.exports = Progress;
8 |
9 | /**
10 | * Initialize a new `Progress` indicator.
11 | */
12 | function Progress() {
13 | this.percent = 0;
14 | this.size(0);
15 | this.fontSize(11);
16 | this.font('helvetica, arial, sans-serif');
17 | }
18 |
19 | /**
20 | * Set progress size to `size`.
21 | *
22 | * @api public
23 | * @param {number} size
24 | * @return {Progress} Progress instance.
25 | */
26 | Progress.prototype.size = function(size) {
27 | this._size = size;
28 | return this;
29 | };
30 |
31 | /**
32 | * Set text to `text`.
33 | *
34 | * @api public
35 | * @param {string} text
36 | * @return {Progress} Progress instance.
37 | */
38 | Progress.prototype.text = function(text) {
39 | this._text = text;
40 | return this;
41 | };
42 |
43 | /**
44 | * Set font size to `size`.
45 | *
46 | * @api public
47 | * @param {number} size
48 | * @return {Progress} Progress instance.
49 | */
50 | Progress.prototype.fontSize = function(size) {
51 | this._fontSize = size;
52 | return this;
53 | };
54 |
55 | /**
56 | * Set font to `family`.
57 | *
58 | * @param {string} family
59 | * @return {Progress} Progress instance.
60 | */
61 | Progress.prototype.font = function(family) {
62 | this._font = family;
63 | return this;
64 | };
65 |
66 | /**
67 | * Update percentage to `n`.
68 | *
69 | * @param {number} n
70 | * @return {Progress} Progress instance.
71 | */
72 | Progress.prototype.update = function(n) {
73 | this.percent = n;
74 | return this;
75 | };
76 |
77 | /**
78 | * Draw on `ctx`.
79 | *
80 | * @param {CanvasRenderingContext2d} ctx
81 | * @return {Progress} Progress instance.
82 | */
83 | Progress.prototype.draw = function(ctx) {
84 | try {
85 | const percent = Math.min(this.percent, 100);
86 | const size = this._size;
87 | const half = size / 2;
88 | const x = half;
89 | const y = half;
90 | const rad = half - 1;
91 | const fontSize = this._fontSize;
92 |
93 | ctx.font = fontSize + 'px ' + this._font;
94 |
95 | const angle = Math.PI * 2 * (percent / 100);
96 | ctx.clearRect(0, 0, size, size);
97 |
98 | // outer circle
99 | ctx.strokeStyle = '#9f9f9f';
100 | ctx.beginPath();
101 | ctx.arc(x, y, rad, 0, angle, false);
102 | ctx.stroke();
103 |
104 | // inner circle
105 | ctx.strokeStyle = '#eee';
106 | ctx.beginPath();
107 | ctx.arc(x, y, rad - 1, 0, angle, true);
108 | ctx.stroke();
109 |
110 | // text
111 | // eslint-disable-next-line no-bitwise
112 | const text = this._text || (percent | 0) + '%';
113 | const w = ctx.measureText(text).width;
114 |
115 | ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1);
116 | } catch (ignore) {
117 | // don't fail if we can't render progress
118 | }
119 | return this;
120 | };
121 |
--------------------------------------------------------------------------------
/lib/mocha/browser/tty.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.isatty = function isatty() {
4 | return true;
5 | };
6 |
7 | exports.getWindowSize = function getWindowSize() {
8 | if ('innerHeight' in global) {
9 | return [global.innerHeight, global.innerWidth];
10 | }
11 | // In a Web Worker, the DOM Window is not available.
12 | return [640, 480];
13 | };
14 |
--------------------------------------------------------------------------------
/lib/mocha/context.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module Context
4 | */
5 | /**
6 | * Expose `Context`.
7 | */
8 |
9 | module.exports = Context;
10 |
11 | /**
12 | * Initialize a new `Context`.
13 | *
14 | * @api private
15 | */
16 | function Context() {}
17 |
18 | /**
19 | * Set or get the context `Runnable` to `runnable`.
20 | *
21 | * @api private
22 | * @param {Runnable} runnable
23 | * @return {Context} context
24 | */
25 | Context.prototype.runnable = function(runnable) {
26 | if (!arguments.length) {
27 | return this._runnable;
28 | }
29 | this.test = this._runnable = runnable;
30 | return this;
31 | };
32 |
33 | /**
34 | * Set or get test timeout `ms`.
35 | *
36 | * @api private
37 | * @param {number} ms
38 | * @return {Context} self
39 | */
40 | Context.prototype.timeout = function(ms) {
41 | if (!arguments.length) {
42 | return this.runnable().timeout();
43 | }
44 | this.runnable().timeout(ms);
45 | return this;
46 | };
47 |
48 | /**
49 | * Set test timeout `enabled`.
50 | *
51 | * @api private
52 | * @param {boolean} enabled
53 | * @return {Context} self
54 | */
55 | Context.prototype.enableTimeouts = function(enabled) {
56 | if (!arguments.length) {
57 | return this.runnable().enableTimeouts();
58 | }
59 | this.runnable().enableTimeouts(enabled);
60 | return this;
61 | };
62 |
63 | /**
64 | * Set or get test slowness threshold `ms`.
65 | *
66 | * @api private
67 | * @param {number} ms
68 | * @return {Context} self
69 | */
70 | Context.prototype.slow = function(ms) {
71 | if (!arguments.length) {
72 | return this.runnable().slow();
73 | }
74 | this.runnable().slow(ms);
75 | return this;
76 | };
77 |
78 | /**
79 | * Mark a test as skipped.
80 | *
81 | * @api private
82 | * @throws Pending
83 | */
84 | Context.prototype.skip = function() {
85 | this.runnable().skip();
86 | };
87 |
88 | /**
89 | * Set or get a number of allowed retries on failed tests
90 | *
91 | * @api private
92 | * @param {number} n
93 | * @return {Context} self
94 | */
95 | Context.prototype.retries = function(n) {
96 | if (!arguments.length) {
97 | return this.runnable().retries();
98 | }
99 | this.runnable().retries(n);
100 | return this;
101 | };
102 |
--------------------------------------------------------------------------------
/lib/mocha/hook.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Runnable = require('./runnable');
4 | const inherits = require('./utils').inherits;
5 |
6 | /**
7 | * Expose `Hook`.
8 | */
9 |
10 | module.exports = Hook;
11 |
12 | /**
13 | * Initialize a new `Hook` with the given `title` and callback `fn`
14 | *
15 | * @class
16 | * @augments Runnable
17 | * @param {String} title
18 | * @param {Function} fn
19 | */
20 | function Hook(title, fn) {
21 | Runnable.call(this, title, fn);
22 | this.type = 'hook';
23 | }
24 |
25 | /**
26 | * Inherit from `Runnable.prototype`.
27 | */
28 | inherits(Hook, Runnable);
29 |
30 | /**
31 | * Get or set the test `err`.
32 | *
33 | * @memberof Hook
34 | * @public
35 | * @param {Error} err
36 | * @return {Error}
37 | */
38 | Hook.prototype.error = function(err) {
39 | if (!arguments.length) {
40 | err = this._error;
41 | this._error = null;
42 | return err;
43 | }
44 |
45 | this._error = err;
46 | };
47 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/bdd.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Test = require('../test');
4 |
5 | /**
6 | * BDD-style interface:
7 | *
8 | * describe('Array', function() {
9 | * describe('#indexOf()', function() {
10 | * it('should return -1 when not present', function() {
11 | * // ...
12 | * });
13 | *
14 | * it('should return the index when present', function() {
15 | * // ...
16 | * });
17 | * });
18 | * });
19 | *
20 | * @param {Suite} suite Root suite.
21 | */
22 | module.exports = function bddInterface(suite) {
23 | const suites = [suite];
24 |
25 | suite.on('pre-require', function(context, file, mocha) {
26 | const common = require('./common')(suites, context, mocha);
27 |
28 | context.before = common.before;
29 | context.after = common.after;
30 | context.beforeEach = common.beforeEach;
31 | context.afterEach = common.afterEach;
32 | context.run = mocha.options.delay && common.runWithSuite(suite);
33 | /**
34 | * Describe a "suite" with the given `title`
35 | * and callback `fn` containing nested suites
36 | * and/or tests.
37 | */
38 |
39 | context.describe = context.context = function(title, fn) {
40 | return common.suite.create({
41 | title,
42 | file,
43 | fn
44 | });
45 | };
46 |
47 | /**
48 | * Pending describe.
49 | */
50 |
51 | context.xdescribe = context.xcontext = context.describe.skip = function(
52 | title,
53 | fn
54 | ) {
55 | return common.suite.skip({
56 | title,
57 | file,
58 | fn
59 | });
60 | };
61 |
62 | /**
63 | * Exclusive suite.
64 | */
65 |
66 | context.describe.only = function(title, fn) {
67 | return common.suite.only({
68 | title,
69 | file,
70 | fn
71 | });
72 | };
73 |
74 | /**
75 | * Describe a specification or test-case
76 | * with the given `title` and callback `fn`
77 | * acting as a thunk.
78 | */
79 |
80 | context.it = context.specify = function(title, fn) {
81 | const suite = suites[0];
82 | if (suite.isPending()) {
83 | fn = null;
84 | }
85 | const test = new Test(title, fn);
86 | test.file = file;
87 | suite.addTest(test);
88 | return test;
89 | };
90 |
91 | /**
92 | * Exclusive test-case.
93 | */
94 |
95 | context.it.only = function(title, fn) {
96 | return common.test.only(mocha, context.it(title, fn));
97 | };
98 |
99 | /**
100 | * Pending test case.
101 | */
102 |
103 | context.xit = context.xspecify = context.it.skip = function(title) {
104 | return context.it(title);
105 | };
106 |
107 | /**
108 | * times of attempts to retry.
109 | */
110 | context.it.retries = function(times, title, fn) {
111 | const suite = suites[0];
112 | if (suite.isPending()) {
113 | fn = null;
114 | }
115 | const test = new Test(title, fn);
116 | test.file = file;
117 | suite.addTest(test);
118 | test.retries(times);
119 | return test;
120 | };
121 | });
122 | };
123 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Suite = require('../suite');
4 |
5 | /**
6 | * Functions common to more than one interface.
7 | *
8 | * @param {Suite[]} suites
9 | * @param {Context} context
10 | * @return {Object} An object containing common functions.
11 | */
12 | module.exports = function(suites, context) {
13 | return {
14 | /**
15 | * This is only present if flag --delay is passed into Mocha. It triggers
16 | * root suite execution.
17 | *
18 | * @param {Suite} suite The root suite.
19 | * @return {Function} A function which runs the root suite
20 | */
21 | runWithSuite: function runWithSuite(suite) {
22 | return function run() {
23 | suite.run();
24 | };
25 | },
26 |
27 | /**
28 | * Execute before running tests.
29 | *
30 | * @param {string} name
31 | * @param {Function} fn
32 | */
33 | before(name, fn) {
34 | suites[0].beforeAll(name, fn);
35 | },
36 |
37 | /**
38 | * Execute after running tests.
39 | *
40 | * @param {string} name
41 | * @param {Function} fn
42 | */
43 | after(name, fn) {
44 | suites[0].afterAll(name, fn);
45 | },
46 |
47 | /**
48 | * Execute before each test case.
49 | *
50 | * @param {string} name
51 | * @param {Function} fn
52 | */
53 | beforeEach(name, fn) {
54 | suites[0].beforeEach(name, fn);
55 | },
56 |
57 | /**
58 | * Execute after each test case.
59 | *
60 | * @param {string} name
61 | * @param {Function} fn
62 | */
63 | afterEach(name, fn) {
64 | suites[0].afterEach(name, fn);
65 | },
66 |
67 | suite: {
68 | /**
69 | * Create an exclusive Suite; convenience function
70 | * See docstring for create() below.
71 | *
72 | * @param {Object} opts
73 | * @return {Suite}
74 | */
75 | only: function only(opts) {
76 | opts.isOnly = true;
77 | return this.create(opts);
78 | },
79 |
80 | /**
81 | * Create a Suite, but skip it; convenience function
82 | * See docstring for create() below.
83 | *
84 | * @param {Object} opts
85 | * @return {Suite}
86 | */
87 | skip: function skip(opts) {
88 | opts.pending = true;
89 | return this.create(opts);
90 | },
91 |
92 | /**
93 | * Creates a suite.
94 | * @param {Object} opts Options
95 | * @param {string} opts.title Title of Suite
96 | * @param {Function} [opts.fn] Suite Function (not always applicable)
97 | * @param {boolean} [opts.pending] Is Suite pending?
98 | * @param {string} [opts.file] Filepath where this Suite resides
99 | * @param {boolean} [opts.isOnly] Is Suite exclusive?
100 | * @return {Suite}
101 | */
102 | create: function create(opts) {
103 | const suite = Suite.create(suites[0], opts.title);
104 | suite.pending = Boolean(opts.pending);
105 | suite.file = opts.file;
106 | suites.unshift(suite);
107 | if (opts.isOnly) {
108 | suite.parent._onlySuites = suite.parent._onlySuites.concat(suite);
109 | }
110 | if (typeof opts.fn === 'function') {
111 | opts.fn.call(suite);
112 | suites.shift();
113 | } else if (typeof opts.fn === 'undefined' && !suite.pending) {
114 | throw new Error(
115 | 'Suite "' +
116 | suite.fullTitle() +
117 | '" was defined but no callback was supplied. Supply a callback or explicitly skip the suite.'
118 | );
119 | } else if (!opts.fn && suite.pending) {
120 | suites.shift();
121 | }
122 |
123 | return suite;
124 | }
125 | },
126 |
127 | test: {
128 | /**
129 | * Exclusive test-case.
130 | *
131 | * @param {Object} mocha
132 | * @param {Function} test
133 | * @return {*}
134 | */
135 | only(mocha, test) {
136 | test.parent._onlyTests = test.parent._onlyTests.concat(test);
137 | return test;
138 | },
139 |
140 | /**
141 | * Pending test case.
142 | *
143 | * @param {string} title
144 | */
145 | skip(title) {
146 | context.test(title);
147 | },
148 |
149 | /**
150 | * Number of retry attempts
151 | *
152 | * @param {number} n
153 | */
154 | retries(n) {
155 | context.retries(n);
156 | }
157 | }
158 | };
159 | };
160 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/exports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Suite = require('../suite');
3 | const Test = require('../test');
4 |
5 | /**
6 | * Exports-style (as Node.js module) interface:
7 | *
8 | * exports.Array = {
9 | * '#indexOf()': {
10 | * 'should return -1 when the value is not present': function() {
11 | *
12 | * },
13 | *
14 | * 'should return the correct index when the value is present': function() {
15 | *
16 | * }
17 | * }
18 | * };
19 | *
20 | * @param {Suite} suite Root suite.
21 | */
22 | module.exports = function(suite) {
23 | const suites = [suite];
24 |
25 | suite.on('require', visit);
26 |
27 | function visit(obj, file) {
28 | let suite;
29 | for (const key in obj) {
30 | if (typeof obj[key] === 'function') {
31 | const fn = obj[key];
32 | switch (key) {
33 | case 'before':
34 | suites[0].beforeAll(fn);
35 | break;
36 | case 'after':
37 | suites[0].afterAll(fn);
38 | break;
39 | case 'beforeEach':
40 | suites[0].beforeEach(fn);
41 | break;
42 | case 'afterEach':
43 | suites[0].afterEach(fn);
44 | break;
45 | default:
46 | // eslint-disable-next-line no-var
47 | var test = new Test(key, fn);
48 | test.file = file;
49 | suites[0].addTest(test);
50 | }
51 | } else {
52 | suite = Suite.create(suites[0], key);
53 | suites.unshift(suite);
54 | visit(obj[key], file);
55 | suites.shift();
56 | }
57 | }
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.bdd = require('./bdd');
4 | exports.tdd = require('./tdd');
5 | exports.qunit = require('./qunit');
6 | exports.exports = require('./exports');
7 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/qunit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Test = require('../test');
4 |
5 | /**
6 | * QUnit-style interface:
7 | *
8 | * suite('Array');
9 | *
10 | * test('#length', function() {
11 | * var arr = [1,2,3];
12 | * ok(arr.length == 3);
13 | * });
14 | *
15 | * test('#indexOf()', function() {
16 | * var arr = [1,2,3];
17 | * ok(arr.indexOf(1) == 0);
18 | * ok(arr.indexOf(2) == 1);
19 | * ok(arr.indexOf(3) == 2);
20 | * });
21 | *
22 | * suite('String');
23 | *
24 | * test('#length', function() {
25 | * ok('foo'.length == 3);
26 | * });
27 | *
28 | * @param {Suite} suite Root suite.
29 | */
30 | module.exports = function qUnitInterface(suite) {
31 | const suites = [suite];
32 |
33 | suite.on('pre-require', function(context, file, mocha) {
34 | const common = require('./common')(suites, context, mocha);
35 |
36 | context.before = common.before;
37 | context.after = common.after;
38 | context.beforeEach = common.beforeEach;
39 | context.afterEach = common.afterEach;
40 | context.run = mocha.options.delay && common.runWithSuite(suite);
41 | /**
42 | * Describe a "suite" with the given `title`.
43 | */
44 |
45 | context.suite = function(title) {
46 | if (suites.length > 1) {
47 | suites.shift();
48 | }
49 | return common.suite.create({
50 | title,
51 | file,
52 | fn: false
53 | });
54 | };
55 |
56 | /**
57 | * Exclusive Suite.
58 | */
59 |
60 | context.suite.only = function(title) {
61 | if (suites.length > 1) {
62 | suites.shift();
63 | }
64 | return common.suite.only({
65 | title,
66 | file,
67 | fn: false
68 | });
69 | };
70 |
71 | /**
72 | * Describe a specification or test-case
73 | * with the given `title` and callback `fn`
74 | * acting as a thunk.
75 | */
76 |
77 | context.test = function(title, fn) {
78 | const test = new Test(title, fn);
79 | test.file = file;
80 | suites[0].addTest(test);
81 | return test;
82 | };
83 |
84 | /**
85 | * Exclusive test-case.
86 | */
87 |
88 | context.test.only = function(title, fn) {
89 | return common.test.only(mocha, context.test(title, fn));
90 | };
91 |
92 | context.test.skip = common.test.skip;
93 | context.test.retries = common.test.retries;
94 | });
95 | };
96 |
--------------------------------------------------------------------------------
/lib/mocha/interfaces/tdd.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Test = require('../test');
4 |
5 | /**
6 | * TDD-style interface:
7 | *
8 | * suite('Array', function() {
9 | * suite('#indexOf()', function() {
10 | * suiteSetup(function() {
11 | *
12 | * });
13 | *
14 | * test('should return -1 when not present', function() {
15 | *
16 | * });
17 | *
18 | * test('should return the index when present', function() {
19 | *
20 | * });
21 | *
22 | * suiteTeardown(function() {
23 | *
24 | * });
25 | * });
26 | * });
27 | *
28 | * @param {Suite} suite Root suite.
29 | */
30 | module.exports = function(suite) {
31 | const suites = [suite];
32 |
33 | suite.on('pre-require', function(context, file, mocha) {
34 | const common = require('./common')(suites, context, mocha);
35 |
36 | context.setup = common.beforeEach;
37 | context.teardown = common.afterEach;
38 | context.suiteSetup = common.before;
39 | context.suiteTeardown = common.after;
40 | context.run = mocha.options.delay && common.runWithSuite(suite);
41 |
42 | /**
43 | * Describe a "suite" with the given `title` and callback `fn` containing
44 | * nested suites and/or tests.
45 | */
46 | context.suite = function(title, fn) {
47 | return common.suite.create({
48 | title,
49 | file,
50 | fn
51 | });
52 | };
53 |
54 | /**
55 | * Pending suite.
56 | */
57 | context.suite.skip = function(title, fn) {
58 | return common.suite.skip({
59 | title,
60 | file,
61 | fn
62 | });
63 | };
64 |
65 | /**
66 | * Exclusive test-case.
67 | */
68 | context.suite.only = function(title, fn) {
69 | return common.suite.only({
70 | title,
71 | file,
72 | fn
73 | });
74 | };
75 |
76 | /**
77 | * Describe a specification or test-case with the given `title` and
78 | * callback `fn` acting as a thunk.
79 | */
80 | context.test = function(title, fn) {
81 | const suite = suites[0];
82 | if (suite.isPending()) {
83 | fn = null;
84 | }
85 | const test = new Test(title, fn);
86 | test.file = file;
87 | suite.addTest(test);
88 | return test;
89 | };
90 |
91 | /**
92 | * Exclusive test-case.
93 | */
94 |
95 | context.test.only = function(title, fn) {
96 | return common.test.only(mocha, context.test(title, fn));
97 | };
98 |
99 | context.test.skip = common.test.skip;
100 | context.test.retries = common.test.retries;
101 | });
102 | };
103 |
--------------------------------------------------------------------------------
/lib/mocha/mocha.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* !
4 | * mocha
5 | * Copyright(c) 2011 TJ Holowaychuk
6 | * MIT Licensed
7 | */
8 |
9 | const escapeRe = require('escape-string-regexp');
10 | const path = require('path');
11 | const reporters = require('./reporters');
12 | const utils = require('./utils');
13 |
14 | exports = module.exports = Mocha;
15 |
16 | /**
17 | * To require local UIs and reporters when running in node.
18 | */
19 |
20 | if (!process.browser) {
21 | const cwd = process.cwd();
22 | module.paths.push(cwd, path.join(cwd, 'node_modules'));
23 | }
24 |
25 | /**
26 | * Expose internals.
27 | */
28 |
29 | /**
30 | * @public
31 | * @class utils
32 | * @memberof Mocha
33 | */
34 | exports.utils = utils;
35 | exports.interfaces = require('./interfaces');
36 | /**
37 | *
38 | * @memberof Mocha
39 | * @public
40 | */
41 | exports.reporters = reporters;
42 | exports.Runnable = require('./runnable');
43 | exports.Context = require('./context');
44 | /**
45 | *
46 | * @memberof Mocha
47 | */
48 | exports.Runner = require('./runner');
49 | exports.Suite = require('./suite');
50 | exports.Hook = require('./hook');
51 | exports.Test = require('./test');
52 |
53 | /**
54 | * Set up mocha with `options`.
55 | *
56 | * Options:
57 | *
58 | * - `ui` name "bdd", "tdd", "exports" etc
59 | * - `reporter` reporter instance, defaults to `mocha.reporters.spec`
60 | * - `globals` array of accepted globals
61 | * - `timeout` timeout in milliseconds
62 | * - `retries` number of times to retry failed tests
63 | * - `bail` bail on the first test failure
64 | * - `slow` milliseconds to wait before considering a test slow
65 | * - `ignoreLeaks` ignore global leaks
66 | * - `fullTrace` display the full stack-trace on failing
67 | * - `grep` string or regexp to filter tests with
68 | *
69 | * @class Mocha
70 | * @param {Object} options
71 | */
72 | function Mocha(options) {
73 | options = options || {};
74 | this.files = [];
75 | this.options = options;
76 | if (options.grep) {
77 | this.grep(new RegExp(options.grep));
78 | }
79 | if (options.fgrep) {
80 | this.fgrep(options.fgrep);
81 | }
82 | this.suite = new exports.Suite('', new exports.Context());
83 | this.ui(options.ui);
84 | this.bail(options.bail);
85 | this.reporter(options.reporter, options.reporterOptions);
86 | if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
87 | this.timeout(options.timeout);
88 | }
89 | if (typeof options.retries !== 'undefined' && options.retries !== null) {
90 | this.retries(options.retries);
91 | }
92 | this.useColors(options.useColors);
93 | if (options.enableTimeouts !== null) {
94 | this.enableTimeouts(options.enableTimeouts);
95 | }
96 | if (options.slow) {
97 | this.slow(options.slow);
98 | }
99 | }
100 |
101 | /**
102 | * Enable or disable bailing on the first failure.
103 | *
104 | * @public
105 | * @api public
106 | * @param {boolean} [bail]
107 | */
108 | Mocha.prototype.bail = function(bail) {
109 | if (!arguments.length) {
110 | bail = true;
111 | }
112 | this.suite.bail(bail);
113 | return this;
114 | };
115 |
116 | /**
117 | * Add test `file`.
118 | *
119 | * @public
120 | * @api public
121 | * @param {string} file
122 | */
123 | Mocha.prototype.addFile = function(file) {
124 | this.files.push(file);
125 | return this;
126 | };
127 |
128 | /**
129 | * Set reporter to `reporter`, defaults to "spec".
130 | *
131 | * @public
132 | * @param {String|Function} reporter name or constructor
133 | * @param {Object} reporterOptions optional options
134 | * @api public
135 | * @param {string|Function} reporter name or constructor
136 | * @param {Object} reporterOptions optional options
137 | */
138 | Mocha.prototype.reporter = function(reporter, reporterOptions) {
139 | if (typeof reporter === 'function') {
140 | this._reporter = reporter;
141 | } else {
142 | reporter = reporter || 'spec';
143 | let _reporter;
144 | // Try to load a built-in reporter.
145 | if (reporters[reporter]) {
146 | _reporter = reporters[reporter];
147 | }
148 | // Try to load reporters from process.cwd() and node_modules
149 | if (!_reporter) {
150 | try {
151 | _reporter = require(reporter);
152 | } catch (err) {
153 | if (err.message.indexOf('Cannot find module') !== -1) {
154 | // Try to load reporters from a path (absolute or relative)
155 | try {
156 | _reporter = require(path.resolve(process.cwd(), reporter));
157 | } catch (_err) {
158 | err.message.indexOf('Cannot find module') !== -1
159 | ? console.warn('"' + reporter + '" reporter not found')
160 | : console.warn(
161 | '"' +
162 | reporter +
163 | '" reporter blew up with error:\n' +
164 | err.stack
165 | );
166 | }
167 | } else {
168 | console.warn(
169 | '"' + reporter + '" reporter blew up with error:\n' + err.stack
170 | );
171 | }
172 | }
173 | }
174 | if (!_reporter && reporter === 'teamcity') {
175 | console.warn(
176 | 'The Teamcity reporter was moved to a package named ' +
177 | 'mocha-teamcity-reporter ' +
178 | '(https://npmjs.org/package/mocha-teamcity-reporter).'
179 | );
180 | }
181 | if (!_reporter) {
182 | throw new Error('invalid reporter "' + reporter + '"');
183 | }
184 | this._reporter = _reporter;
185 | }
186 | this.options.reporterOptions = reporterOptions;
187 | return this;
188 | };
189 |
190 | /**
191 | * Set test UI `name`, defaults to "bdd".
192 | * @public
193 | * @api public
194 | * @param {string} bdd
195 | */
196 | Mocha.prototype.ui = function(name) {
197 | name = name || 'bdd';
198 | this._ui = exports.interfaces[name];
199 | if (!this._ui) {
200 | try {
201 | this._ui = require(name);
202 | } catch (err) {
203 | throw new Error('invalid interface "' + name + '"');
204 | }
205 | }
206 | this._ui = this._ui(this.suite);
207 |
208 | this.suite.on('pre-require', function(context) {
209 | exports.afterEach = context.afterEach || context.teardown;
210 | exports.after = context.after || context.suiteTeardown;
211 | exports.beforeEach = context.beforeEach || context.setup;
212 | exports.before = context.before || context.suiteSetup;
213 | exports.describe = context.describe || context.suite;
214 | exports.it = context.it || context.test;
215 | exports.xit = context.xit || context.test.skip;
216 | exports.setup = context.setup || context.beforeEach;
217 | exports.suiteSetup = context.suiteSetup || context.before;
218 | exports.suiteTeardown = context.suiteTeardown || context.after;
219 | exports.suite = context.suite || context.describe;
220 | exports.teardown = context.teardown || context.afterEach;
221 | exports.test = context.test || context.it;
222 | exports.run = context.run;
223 | });
224 |
225 | return this;
226 | };
227 |
228 | /**
229 | * Load registered files.
230 | *
231 | * @api private
232 | */
233 | Mocha.prototype.loadFiles = function(fn) {
234 | const self = this;
235 | const suite = this.suite;
236 | this.files.forEach(function(file) {
237 | file = path.resolve(file);
238 | suite.emit('pre-require', global, file, self);
239 | suite.emit('require', require(file), file, self);
240 | suite.emit('post-require', global, file, self);
241 | });
242 | fn && fn();
243 | };
244 |
245 | /**
246 | * Escape string and add it to grep as a regexp.
247 | *
248 | * @public
249 | * @api public
250 | * @param str
251 | * @return {Mocha}
252 | */
253 | Mocha.prototype.fgrep = function(str) {
254 | return this.grep(new RegExp(escapeRe(str)));
255 | };
256 |
257 | /**
258 | * Add regexp to grep, if `re` is a string it is escaped.
259 | *
260 | * @public
261 | * @param {RegExp|String} re
262 | * @return {Mocha}
263 | * @api public
264 | * @param {RegExp|string} re
265 | * @return {Mocha}
266 | */
267 | Mocha.prototype.grep = function(re) {
268 | if (utils.isString(re)) {
269 | // extract args if it's regex-like, i.e: [string, pattern, flag]
270 | const arg = re.match(/^\/(.*)\/(g|i|)$|.*/);
271 | this.options.grep = new RegExp(arg[1] || arg[0], arg[2]);
272 | } else {
273 | this.options.grep = re;
274 | }
275 | return this;
276 | };
277 | /**
278 | * Invert `.grep()` matches.
279 | *
280 | * @public
281 | * @return {Mocha}
282 | * @api public
283 | */
284 | Mocha.prototype.invert = function() {
285 | this.options.invert = true;
286 | return this;
287 | };
288 |
289 | /**
290 | * Ignore global leaks.
291 | *
292 | * @public
293 | * @param {Boolean} ignore
294 | * @return {Mocha}
295 | * @api public
296 | * @param {boolean} ignore
297 | * @return {Mocha}
298 | */
299 | Mocha.prototype.ignoreLeaks = function(ignore) {
300 | this.options.ignoreLeaks = Boolean(ignore);
301 | return this;
302 | };
303 |
304 | /**
305 | * Enable global leak checking.
306 | *
307 | * @return {Mocha}
308 | * @api public
309 | * @public
310 | */
311 | Mocha.prototype.checkLeaks = function() {
312 | this.options.ignoreLeaks = false;
313 | return this;
314 | };
315 |
316 | /**
317 | * Display long stack-trace on failing
318 | *
319 | * @return {Mocha}
320 | * @api public
321 | * @public
322 | */
323 | Mocha.prototype.fullTrace = function() {
324 | this.options.fullStackTrace = true;
325 | return this;
326 | };
327 |
328 | /**
329 | * Enable growl support.
330 | *
331 | * @return {Mocha}
332 | * @api public
333 | * @public
334 | */
335 | Mocha.prototype.growl = function() {
336 | this.options.growl = true;
337 | return this;
338 | };
339 |
340 | /**
341 | * Ignore `globals` array or string.
342 | *
343 | * @param {Array|String} globals
344 | * @return {Mocha}
345 | * @api public
346 | * @public
347 | * @param {Array|string} globals
348 | * @return {Mocha}
349 | */
350 | Mocha.prototype.globals = function(globals) {
351 | this.options.globals = (this.options.globals || []).concat(globals);
352 | return this;
353 | };
354 |
355 | /**
356 | * Emit color output.
357 | *
358 | * @param {Boolean} colors
359 | * @return {Mocha}
360 | * @api public
361 | * @public
362 | * @param {boolean} colors
363 | * @return {Mocha}
364 | */
365 | Mocha.prototype.useColors = function(colors) {
366 | if (colors !== undefined) {
367 | this.options.useColors = colors;
368 | }
369 | return this;
370 | };
371 |
372 | /**
373 | * Use inline diffs rather than +/-.
374 | *
375 | * @param {Boolean} inlineDiffs
376 | * @return {Mocha}
377 | * @api public
378 | * @public
379 | * @param {boolean} inlineDiffs
380 | * @return {Mocha}
381 | */
382 | Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
383 | this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs;
384 | return this;
385 | };
386 |
387 | /**
388 | * Do not show diffs at all.
389 | *
390 | * @param {Boolean} hideDiff
391 | * @return {Mocha}
392 | * @api public
393 | * @public
394 | * @param {boolean} hideDiff
395 | * @return {Mocha}
396 | */
397 | Mocha.prototype.hideDiff = function(hideDiff) {
398 | this.options.hideDiff = hideDiff !== undefined && hideDiff;
399 | return this;
400 | };
401 |
402 | /**
403 | * Set the timeout in milliseconds.
404 | *
405 | * @param {Number} timeout
406 | * @return {Mocha}
407 | * @api public
408 | * @public
409 | * @param {number} timeout
410 | * @return {Mocha}
411 | */
412 | Mocha.prototype.timeout = function(timeout) {
413 | this.suite.timeout(timeout);
414 | return this;
415 | };
416 |
417 | /**
418 | * Set the number of times to retry failed tests.
419 | *
420 | * @param {Number} retry times
421 | * @return {Mocha}
422 | * @api public
423 | * @public
424 | */
425 | Mocha.prototype.retries = function(n) {
426 | this.suite.retries(n);
427 | return this;
428 | };
429 |
430 | /**
431 | * Set slowness threshold in milliseconds.
432 | *
433 | * @param {Number} slow
434 | * @return {Mocha}
435 | * @api public
436 | * @public
437 | * @param {number} slow
438 | * @return {Mocha}
439 | */
440 | Mocha.prototype.slow = function(slow) {
441 | this.suite.slow(slow);
442 | return this;
443 | };
444 |
445 | /**
446 | * Enable timeouts.
447 | *
448 | * @param {Boolean} enabled
449 | * @return {Mocha}
450 | * @api public
451 | * @public
452 | * @param {boolean} enabled
453 | * @return {Mocha}
454 | */
455 | Mocha.prototype.enableTimeouts = function(enabled) {
456 | this.suite.enableTimeouts(
457 | arguments.length && enabled !== undefined ? enabled : true
458 | );
459 | return this;
460 | };
461 |
462 | /**
463 | * Makes all tests async (accepting a callback)
464 | *
465 | * @return {Mocha}
466 | * @api public
467 | * @public
468 | */
469 | Mocha.prototype.asyncOnly = function() {
470 | this.options.asyncOnly = true;
471 | return this;
472 | };
473 |
474 | /**
475 | * Disable syntax highlighting (in browser).
476 | *
477 | * @api public
478 | * @public
479 | */
480 | Mocha.prototype.noHighlighting = function() {
481 | this.options.noHighlighting = true;
482 | return this;
483 | };
484 |
485 | /**
486 | * Enable uncaught errors to propagate (in browser).
487 | *
488 | * @return {Mocha}
489 | * @api public
490 | * @public
491 | */
492 | Mocha.prototype.allowUncaught = function() {
493 | this.options.allowUncaught = true;
494 | return this;
495 | };
496 |
497 | /**
498 | * Delay root suite execution.
499 | * @return {Mocha}
500 | */
501 | Mocha.prototype.delay = function delay() {
502 | this.options.delay = true;
503 | return this;
504 | };
505 |
506 | /**
507 | * Tests marked only fail the suite
508 | * @return {Mocha}
509 | */
510 | Mocha.prototype.forbidOnly = function() {
511 | this.options.forbidOnly = true;
512 | return this;
513 | };
514 |
515 | /**
516 | * Pending tests and tests marked skip fail the suite
517 | * @return {Mocha}
518 | */
519 | Mocha.prototype.forbidPending = function() {
520 | this.options.forbidPending = true;
521 | return this;
522 | };
523 |
524 | /**
525 | * Run tests and invoke `fn()` when complete.
526 | *
527 | * Note that `loadFiles` relies on Node's `require` to execute
528 | * the test interface functions and will be subject to the
529 | * cache - if the files are already in the `require` cache,
530 | * they will effectively be skipped. Therefore, to run tests
531 | * multiple times or to run tests in files that are already
532 | * in the `require` cache, make sure to clear them from the
533 | * cache first in whichever manner best suits your needs.
534 | *
535 | * @api public
536 | * @public
537 | * @param {Function} fn
538 | * @return {Runner}
539 | */
540 | Mocha.prototype.run = function(fn) {
541 | if (this.files.length) {
542 | this.loadFiles();
543 | }
544 | const suite = this.suite;
545 | const options = this.options;
546 | options.files = this.files;
547 | const runner = new exports.Runner(suite, options.delay);
548 | const reporter = new this._reporter(runner, options);
549 | runner.ignoreLeaks = options.ignoreLeaks !== false;
550 | runner.fullStackTrace = options.fullStackTrace;
551 | runner.asyncOnly = options.asyncOnly;
552 | runner.allowUncaught = options.allowUncaught;
553 | runner.forbidOnly = options.forbidOnly;
554 | runner.forbidPending = options.forbidPending;
555 | if (options.grep) {
556 | runner.grep(options.grep, options.invert);
557 | }
558 | if (options.globals) {
559 | runner.globals(options.globals);
560 | }
561 | if (options.growl) {
562 | this._growl(runner, reporter);
563 | }
564 | if (options.useColors !== undefined) {
565 | exports.reporters.Base.useColors = options.useColors;
566 | }
567 | exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
568 | exports.reporters.Base.hideDiff = options.hideDiff;
569 |
570 | function done(failures) {
571 | if (reporter.done) {
572 | reporter.done(failures, fn);
573 | } else {
574 | fn && fn(failures);
575 | }
576 | }
577 |
578 | return runner.run(done);
579 | };
580 |
--------------------------------------------------------------------------------
/lib/mocha/ms.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module milliseconds
4 | */
5 | /**
6 | * Helpers.
7 | */
8 |
9 | const s = 1000;
10 | const m = s * 60;
11 | const h = m * 60;
12 | const d = h * 24;
13 | const y = d * 365.25;
14 |
15 | /**
16 | * Parse or format the given `val`.
17 | *
18 | * @memberof Mocha
19 | * @public
20 | * @api public
21 | * @param {string|number} val
22 | * @return {string|number}
23 | */
24 | module.exports = function(val) {
25 | if (typeof val === 'string') {
26 | return parse(val);
27 | }
28 | return format(val);
29 | };
30 |
31 | /**
32 | * Parse the given `str` and return milliseconds.
33 | *
34 | * @api private
35 | * @param {string} str
36 | * @return {number}
37 | */
38 | function parse(str) {
39 | const match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(
40 | str
41 | );
42 | if (!match) {
43 | return;
44 | }
45 | const n = parseFloat(match[1]);
46 | const type = (match[2] || 'ms').toLowerCase();
47 | switch (type) {
48 | case 'years':
49 | case 'year':
50 | case 'y':
51 | return n * y;
52 | case 'days':
53 | case 'day':
54 | case 'd':
55 | return n * d;
56 | case 'hours':
57 | case 'hour':
58 | case 'h':
59 | return n * h;
60 | case 'minutes':
61 | case 'minute':
62 | case 'm':
63 | return n * m;
64 | case 'seconds':
65 | case 'second':
66 | case 's':
67 | return n * s;
68 | case 'ms':
69 | return n;
70 | default:
71 | // No default case
72 | }
73 | }
74 |
75 | /**
76 | * Format for `ms`.
77 | *
78 | * @api private
79 | * @param {number} ms
80 | * @return {string}
81 | */
82 | function format(ms) {
83 | if (ms >= d) {
84 | return Math.round(ms / d) + 'd';
85 | }
86 | if (ms >= h) {
87 | return Math.round(ms / h) + 'h';
88 | }
89 | if (ms >= m) {
90 | return Math.round(ms / m) + 'm';
91 | }
92 | if (ms >= s) {
93 | return Math.round(ms / s) + 's';
94 | }
95 | return ms + 'ms';
96 | }
97 |
--------------------------------------------------------------------------------
/lib/mocha/pending.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = Pending;
4 |
5 | /**
6 | * Initialize a new `Pending` error with the given message.
7 | *
8 | * @param {string} message
9 | */
10 | function Pending(message) {
11 | this.message = message;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/base.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-useless-concat */
2 | 'use strict';
3 | /**
4 | * @module Base
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const tty = require('tty');
11 | const diff = require('diff');
12 | const ms = require('../ms');
13 | const utils = require('../utils');
14 | const supportsColor = process.browser ? null : require('supports-color');
15 |
16 | /**
17 | * Expose `Base`.
18 | */
19 |
20 | exports = module.exports = Base;
21 |
22 | /**
23 | * Save timer references to avoid Sinon interfering.
24 | * See: https://github.com/mochajs/mocha/issues/237
25 | */
26 |
27 | /* eslint-disable no-unused-vars, no-native-reassign */
28 | const Date = global.Date;
29 | const setTimeout = global.setTimeout;
30 | const setInterval = global.setInterval;
31 | const clearTimeout = global.clearTimeout;
32 | const clearInterval = global.clearInterval;
33 | /* eslint-enable no-unused-vars, no-native-reassign */
34 |
35 | /**
36 | * Check if both stdio streams are associated with a tty.
37 | */
38 |
39 | const isatty = tty.isatty(1) && tty.isatty(2);
40 |
41 | /**
42 | * Enable coloring by default, except in the browser interface.
43 | */
44 |
45 | exports.useColors =
46 | !process.browser &&
47 | (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined);
48 |
49 | /**
50 | * Inline diffs instead of +/-
51 | */
52 |
53 | exports.inlineDiffs = false;
54 |
55 | /**
56 | * Default color map.
57 | */
58 |
59 | exports.colors = {
60 | pass: 90,
61 | fail: 31,
62 | 'bright pass': 92,
63 | 'bright fail': 91,
64 | 'bright yellow': 93,
65 | pending: 36,
66 | suite: 0,
67 | 'error title': 0,
68 | 'error message': 31,
69 | 'error stack': 90,
70 | checkmark: 32,
71 | fast: 90,
72 | medium: 33,
73 | slow: 31,
74 | green: 32,
75 | light: 90,
76 | 'diff gutter': 90,
77 | 'diff added': 32,
78 | 'diff removed': 31
79 | };
80 |
81 | /**
82 | * Default symbol map.
83 | */
84 |
85 | exports.symbols = {
86 | ok: '✓',
87 | err: '✖',
88 | dot: '․',
89 | comma: ',',
90 | bang: '!'
91 | };
92 |
93 | // With node.js on Windows: use symbols available in terminal default fonts
94 | if (process.platform === 'win32') {
95 | exports.symbols.ok = '\u221A';
96 | exports.symbols.err = '\u00D7';
97 | exports.symbols.dot = '.';
98 | }
99 |
100 | /**
101 | * Color `str` with the given `type`,
102 | * allowing colors to be disabled,
103 | * as well as user-defined color
104 | * schemes.
105 | *
106 | * @param {string} type
107 | * @param {string} str
108 | * @return {string}
109 | * @api private
110 | */
111 | const color = (exports.color = function(type, str) {
112 | if (!exports.useColors) {
113 | return String(str);
114 | }
115 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
116 | });
117 |
118 | /**
119 | * Expose term window size, with some defaults for when stderr is not a tty.
120 | */
121 |
122 | exports.window = {
123 | width: 75
124 | };
125 |
126 | if (isatty) {
127 | exports.window.width = process.stdout.getWindowSize
128 | ? process.stdout.getWindowSize(1)[0]
129 | : tty.getWindowSize()[1];
130 | }
131 |
132 | /**
133 | * Expose some basic cursor interactions that are common among reporters.
134 | */
135 |
136 | exports.cursor = {
137 | hide() {
138 | isatty && process.stdout.write('\u001b[?25l');
139 | },
140 |
141 | show() {
142 | isatty && process.stdout.write('\u001b[?25h');
143 | },
144 |
145 | deleteLine() {
146 | isatty && process.stdout.write('\u001b[2K');
147 | },
148 |
149 | beginningOfLine() {
150 | isatty && process.stdout.write('\u001b[0G');
151 | },
152 |
153 | CR() {
154 | if (isatty) {
155 | exports.cursor.deleteLine();
156 | exports.cursor.beginningOfLine();
157 | } else {
158 | process.stdout.write('\r');
159 | }
160 | }
161 | };
162 |
163 | function showDiff(err) {
164 | return (
165 | err &&
166 | err.showDiff !== false &&
167 | sameType(err.actual, err.expected) &&
168 | err.expected !== undefined
169 | );
170 | }
171 |
172 | function stringifyDiffObjs(err) {
173 | if (!utils.isString(err.actual) || !utils.isString(err.expected)) {
174 | err.actual = utils.stringify(err.actual);
175 | err.expected = utils.stringify(err.expected);
176 | }
177 | }
178 |
179 | /**
180 | * Returns a diff between 2 strings with coloured ANSI output.
181 | *
182 | * The diff will be either inline or unified dependant on the value
183 | * of `Base.inlineDiff`.
184 | *
185 | * @param {string} actual
186 | * @param {string} expected
187 | * @return {string} Diff
188 | */
189 | const generateDiff = (exports.generateDiff = function(actual, expected) {
190 | return exports.inlineDiffs
191 | ? inlineDiff(actual, expected)
192 | : unifiedDiff(actual, expected);
193 | });
194 |
195 | /**
196 | * Output the given `failures` as a list.
197 | *
198 | * @public
199 | * @memberof Mocha.reporters.Base
200 | * @variation 1
201 | * @param {Array} failures
202 | * @api public
203 | */
204 |
205 | exports.list = function(failures) {
206 | console.log();
207 | failures.forEach(function(test, i) {
208 | // format
209 | let fmt =
210 | color('error title', ' %s) %s:\n') +
211 | color('error message', ' %s') +
212 | color('error stack', '\n%s\n');
213 |
214 | // msg
215 | let msg;
216 | const err = test.err;
217 | let message;
218 | if (err.message && typeof err.message.toString === 'function') {
219 | message = err.message + '';
220 | } else if (typeof err.inspect === 'function') {
221 | message = err.inspect() + '';
222 | } else {
223 | message = '';
224 | }
225 | let stack = err.stack || message;
226 | let index = message ? stack.indexOf(message) : -1;
227 |
228 | if (index === -1) {
229 | msg = message;
230 | } else {
231 | index += message.length;
232 | msg = stack.slice(0, index);
233 | // remove msg from stack
234 | stack = stack.slice(index + 1);
235 | }
236 |
237 | // uncaught
238 | if (err.uncaught) {
239 | msg = 'Uncaught ' + msg;
240 | }
241 | // explicitly show diff
242 | if (!exports.hideDiff && showDiff(err)) {
243 | stringifyDiffObjs(err);
244 | fmt =
245 | color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
246 | const match = message.match(/^([^:]+): expected/);
247 | msg = '\n ' + color('error message', match ? match[1] : msg);
248 |
249 | msg += generateDiff(err.actual, err.expected);
250 | }
251 |
252 | // indent stack trace
253 | stack = stack.replace(/^/gm, ' ');
254 |
255 | // indented test title
256 | let testTitle = '';
257 | test.titlePath().forEach(function(str, index) {
258 | if (index !== 0) {
259 | testTitle += '\n ';
260 | }
261 | for (let i = 0; i < index; i++) {
262 | testTitle += ' ';
263 | }
264 | testTitle += str;
265 | });
266 |
267 | console.log(fmt, i + 1, testTitle, msg, stack);
268 | });
269 | };
270 |
271 | /**
272 | * Initialize a new `Base` reporter.
273 | *
274 | * All other reporters generally
275 | * inherit from this reporter, providing
276 | * stats such as test duration, number
277 | * of tests passed / failed etc.
278 | *
279 | * @memberof Mocha.reporters
280 | * @public
281 | * @class
282 | * @param {Runner} runner
283 | * @api public
284 | */
285 |
286 | function Base(runner) {
287 | const stats = (this.stats = {
288 | suites: 0,
289 | tests: 0,
290 | passes: 0,
291 | pending: 0,
292 | failures: 0
293 | });
294 | const failures = (this.failures = []);
295 |
296 | if (!runner) {
297 | return;
298 | }
299 | this.runner = runner;
300 |
301 | runner.stats = stats;
302 |
303 | runner.on('start', function() {
304 | stats.start = new Date();
305 | });
306 |
307 | runner.on('suite', function(suite) {
308 | stats.suites = stats.suites || 0;
309 | suite.root || stats.suites++;
310 | });
311 |
312 | runner.on('test end', function() {
313 | stats.tests = stats.tests || 0;
314 | stats.tests++;
315 | });
316 |
317 | runner.on('pass', function(test) {
318 | stats.passes = stats.passes || 0;
319 |
320 | if (test.duration > test.slow()) {
321 | test.speed = 'slow';
322 | } else if (test.duration > test.slow() / 2) {
323 | test.speed = 'medium';
324 | } else {
325 | test.speed = 'fast';
326 | }
327 |
328 | stats.passes++;
329 | });
330 |
331 | runner.on('fail', function(test, err) {
332 | stats.failures = stats.failures || 0;
333 | stats.failures++;
334 | if (showDiff(err)) {
335 | stringifyDiffObjs(err);
336 | }
337 | test.err = err;
338 | failures.push(test);
339 | });
340 |
341 | runner.once('end', function() {
342 | stats.end = new Date();
343 | stats.duration = stats.end - stats.start;
344 | });
345 |
346 | runner.on('pending', function() {
347 | stats.pending++;
348 | });
349 | }
350 |
351 | /**
352 | * Output common epilogue used by many of
353 | * the bundled reporters.
354 | *
355 | * @memberof Mocha.reporters.Base
356 | * @public
357 | * @api public
358 | */
359 | Base.prototype.epilogue = function() {
360 | const stats = this.stats;
361 | let fmt;
362 |
363 | console.log();
364 |
365 | // passes
366 | fmt =
367 | color('bright pass', ' ') +
368 | color('green', ' %d passing') +
369 | color('light', ' (%s)');
370 |
371 | console.log(fmt, stats.passes || 0, ms(stats.duration));
372 |
373 | // pending
374 | if (stats.pending) {
375 | fmt = color('pending', ' ') + color('pending', ' %d pending');
376 |
377 | console.log(fmt, stats.pending);
378 | }
379 |
380 | // failures
381 | if (stats.failures) {
382 | fmt = color('fail', ' %d failing');
383 |
384 | console.log(fmt, stats.failures);
385 |
386 | Base.list(this.failures);
387 | console.log();
388 | }
389 |
390 | console.log();
391 | };
392 |
393 | /**
394 | * Pad the given `str` to `len`.
395 | *
396 | * @api private
397 | * @param {string} str
398 | * @param {string} len
399 | * @return {string}
400 | */
401 | function pad(str, len) {
402 | str = String(str);
403 | return Array(len - str.length + 1).join(' ') + str;
404 | }
405 |
406 | /**
407 | * Returns an inline diff between 2 strings with coloured ANSI output.
408 | *
409 | * @api private
410 | * @param {String} actual
411 | * @param {String} expected
412 | * @return {string} Diff
413 | */
414 | function inlineDiff(actual, expected) {
415 | let msg = errorDiff(actual, expected);
416 |
417 | // linenos
418 | const lines = msg.split('\n');
419 | if (lines.length > 4) {
420 | const width = String(lines.length).length;
421 | msg = lines
422 | .map(function(str, i) {
423 | return pad(++i, width) + ' |' + ' ' + str;
424 | })
425 | .join('\n');
426 | }
427 |
428 | // legend
429 | msg = [
430 | '\n',
431 | color('diff removed', 'actual'),
432 | ' ',
433 | color('diff added', 'expected'),
434 | '\n\n',
435 | msg,
436 | '\n'].join('');
437 |
438 | // indent
439 | msg = msg.replace(/^/gm, ' ');
440 | return msg;
441 | }
442 |
443 | /**
444 | * Returns a unified diff between two strings with coloured ANSI output.
445 | *
446 | * @api private
447 | * @param {String} actual
448 | * @param {String} expected
449 | * @return {string} The diff.
450 | */
451 | function unifiedDiff(actual, expected) {
452 | const indent = ' ';
453 | function cleanUp(line) {
454 | if (line[0] === '+') {
455 | return indent + colorLines('diff added', line);
456 | }
457 | if (line[0] === '-') {
458 | return indent + colorLines('diff removed', line);
459 | }
460 | if (line.match(/@@/)) {
461 | return '--';
462 | }
463 | if (line.match(/\\ No newline/)) {
464 | return null;
465 | }
466 | return indent + line;
467 | }
468 | function notBlank(line) {
469 | return typeof line !== 'undefined' && line !== null;
470 | }
471 | const msg = diff.createPatch('string', actual, expected);
472 | const lines = msg.split('\n').splice(5);
473 | return (
474 | '\n ' +
475 | colorLines('diff added', '+ expected') +
476 | ' ' +
477 | colorLines('diff removed', '- actual') +
478 | '\n\n' +
479 | lines
480 | .map(cleanUp)
481 | .filter(notBlank)
482 | .join('\n')
483 | );
484 | }
485 |
486 | /**
487 | * Return a character diff for `err`.
488 | *
489 | * @api private
490 | * @param {String} actual
491 | * @param {String} expected
492 | * @return {string} the diff
493 | */
494 | function errorDiff(actual, expected) {
495 | return diff
496 | .diffWordsWithSpace(actual, expected)
497 | .map(function(str) {
498 | if (str.added) {
499 | return colorLines('diff added', str.value);
500 | }
501 | if (str.removed) {
502 | return colorLines('diff removed', str.value);
503 | }
504 | return str.value;
505 | })
506 | .join('');
507 | }
508 |
509 | /**
510 | * Color lines for `str`, using the color `name`.
511 | *
512 | * @api private
513 | * @param {string} name
514 | * @param {string} str
515 | * @return {string}
516 | */
517 | function colorLines(name, str) {
518 | return str
519 | .split('\n')
520 | .map(function(str) {
521 | return color(name, str);
522 | })
523 | .join('\n');
524 | }
525 |
526 | /**
527 | * Object#toString reference.
528 | */
529 | const objToString = Object.prototype.toString;
530 |
531 | /**
532 | * Check that a / b have the same type.
533 | *
534 | * @api private
535 | * @param {Object} a
536 | * @param {Object} b
537 | * @return {boolean}
538 | */
539 | function sameType(a, b) {
540 | return objToString.call(a) === objToString.call(b);
541 | }
542 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/doc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module Doc
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 | const utils = require('../utils');
11 |
12 | /**
13 | * Expose `Doc`.
14 | */
15 |
16 | exports = module.exports = Doc;
17 |
18 | /**
19 | * Initialize a new `Doc` reporter.
20 | *
21 | * @class
22 | * @memberof Mocha.reporters
23 | * @augments {Base}
24 | * @public
25 | * @param {Runner} runner
26 | * @api public
27 | */
28 | function Doc(runner) {
29 | Base.call(this, runner);
30 |
31 | let indents = 2;
32 |
33 | function indent() {
34 | return Array(indents).join(' ');
35 | }
36 |
37 | runner.on('suite', function(suite) {
38 | if (suite.root) {
39 | return;
40 | }
41 | ++indents;
42 | console.log('%s', indent());
43 | ++indents;
44 | console.log('%s%s
', indent(), utils.escape(suite.title));
45 | console.log('%s', indent());
46 | });
47 |
48 | runner.on('suite end', function(suite) {
49 | if (suite.root) {
50 | return;
51 | }
52 | console.log('%s
', indent());
53 | --indents;
54 | console.log('%s', indent());
55 | --indents;
56 | });
57 |
58 | runner.on('pass', function(test) {
59 | console.log('%s %s', indent(), utils.escape(test.title));
60 | const code = utils.escape(utils.clean(test.body));
61 | console.log('%s %s
', indent(), code);
62 | });
63 |
64 | runner.on('fail', function(test, err) {
65 | console.log(
66 | '%s %s',
67 | indent(),
68 | utils.escape(test.title)
69 | );
70 | const code = utils.escape(utils.clean(test.body));
71 | console.log(
72 | '%s %s
',
73 | indent(),
74 | code
75 | );
76 | console.log('%s %s', indent(), utils.escape(err));
77 | });
78 | }
79 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/dot.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | 'use strict';
3 | /**
4 | * @module Dot
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const Base = require('./base');
11 | const inherits = require('../utils').inherits;
12 | const color = Base.color;
13 |
14 | /**
15 | * Expose `Dot`.
16 | */
17 |
18 | exports = module.exports = Dot;
19 |
20 | /**
21 | * Initialize a new `Dot` matrix test reporter.
22 | *
23 | * @class
24 | * @memberof Mocha.reporters
25 | * @augments Mocha.reporters.Base
26 | * @public
27 | * @api public
28 | * @param {Runner} runner
29 | */
30 | function Dot(runner) {
31 | Base.call(this, runner);
32 |
33 | const self = this;
34 | const width = (Base.window.width * 0.75) | 0;
35 | let n = -1;
36 |
37 | runner.on('start', function() {
38 | process.stdout.write('\n');
39 | });
40 |
41 | runner.on('pending', function() {
42 | if (++n % width === 0) {
43 | process.stdout.write('\n ');
44 | }
45 | process.stdout.write(color('pending', Base.symbols.comma));
46 | });
47 |
48 | runner.on('pass', function(test) {
49 | if (++n % width === 0) {
50 | process.stdout.write('\n ');
51 | }
52 | if (test.speed === 'slow') {
53 | process.stdout.write(color('bright yellow', Base.symbols.dot));
54 | } else {
55 | process.stdout.write(color(test.speed, Base.symbols.dot));
56 | }
57 | });
58 |
59 | runner.on('fail', function() {
60 | if (++n % width === 0) {
61 | process.stdout.write('\n ');
62 | }
63 | process.stdout.write(color('fail', Base.symbols.bang));
64 | });
65 |
66 | runner.once('end', function() {
67 | console.log();
68 | self.epilogue();
69 | });
70 | }
71 |
72 | /**
73 | * Inherit from `Base.prototype`.
74 | */
75 | inherits(Dot, Base);
76 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/html.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | 'use strict';
3 |
4 | /* eslint-env browser */
5 | /**
6 | * @module HTML
7 | */
8 | /**
9 | * Module dependencies.
10 | */
11 |
12 | const Base = require('./base');
13 | const utils = require('../utils');
14 | const Progress = require('../browser/progress');
15 | const escapeRe = require('escape-string-regexp');
16 | const escape = utils.escape;
17 |
18 | /**
19 | * Save timer references to avoid Sinon interfering (see GH-237).
20 | */
21 |
22 | /* eslint-disable no-unused-vars, no-native-reassign */
23 | const Date = global.Date;
24 | const setTimeout = global.setTimeout;
25 | const setInterval = global.setInterval;
26 | const clearTimeout = global.clearTimeout;
27 | const clearInterval = global.clearInterval;
28 | /* eslint-enable no-unused-vars, no-native-reassign */
29 |
30 | /**
31 | * Expose `HTML`.
32 | */
33 |
34 | exports = module.exports = HTML;
35 |
36 | /**
37 | * Stats template.
38 | */
39 |
40 | const statsTemplate =
41 | '' +
42 | '' +
43 | '- passes: 0
' +
44 | '- failures: 0
' +
45 | '- duration: 0s
' +
46 | '
';
47 |
48 | const playIcon = '‣';
49 |
50 | /**
51 | * Initialize a new `HTML` reporter.
52 | *
53 | * @public
54 | * @class
55 | * @memberof Mocha.reporters
56 | * @augments Mocha.reporters.Base
57 | * @api public
58 | * @param {Runner} runner
59 | */
60 | function HTML(runner) {
61 | Base.call(this, runner);
62 |
63 | const self = this;
64 | const stats = this.stats;
65 | const stat = fragment(statsTemplate);
66 | const items = stat.getElementsByTagName('li');
67 | const passes = items[1].getElementsByTagName('em')[0];
68 | const passesLink = items[1].getElementsByTagName('a')[0];
69 | const failures = items[2].getElementsByTagName('em')[0];
70 | const failuresLink = items[2].getElementsByTagName('a')[0];
71 | const duration = items[3].getElementsByTagName('em')[0];
72 | const canvas = stat.getElementsByTagName('canvas')[0];
73 | const report = fragment('');
74 | const stack = [report];
75 | let progress;
76 | let ctx;
77 | const root = document.getElementById('mocha');
78 |
79 | if (canvas.getContext) {
80 | const ratio = window.devicePixelRatio || 1;
81 | canvas.style.width = canvas.width;
82 | canvas.style.height = canvas.height;
83 | canvas.width *= ratio;
84 | canvas.height *= ratio;
85 | ctx = canvas.getContext('2d');
86 | ctx.scale(ratio, ratio);
87 | progress = new Progress();
88 | }
89 |
90 | if (!root) {
91 | return error('#mocha div missing, add it to your document');
92 | }
93 |
94 | // pass toggle
95 | on(passesLink, 'click', function(evt) {
96 | evt.preventDefault();
97 | unhide();
98 | const name = /pass/.test(report.className) ? '' : ' pass';
99 | report.className = report.className.replace(/fail|pass/g, '') + name;
100 | if (report.className.trim()) {
101 | hideSuitesWithout('test pass');
102 | }
103 | });
104 |
105 | // failure toggle
106 | on(failuresLink, 'click', function(evt) {
107 | evt.preventDefault();
108 | unhide();
109 | const name = /fail/.test(report.className) ? '' : ' fail';
110 | report.className = report.className.replace(/fail|pass/g, '') + name;
111 | if (report.className.trim()) {
112 | hideSuitesWithout('test fail');
113 | }
114 | });
115 |
116 | root.appendChild(stat);
117 | root.appendChild(report);
118 |
119 | if (progress) {
120 | progress.size(40);
121 | }
122 |
123 | runner.on('suite', function(suite) {
124 | if (suite.root) {
125 | return;
126 | }
127 |
128 | // suite
129 | const url = self.suiteURL(suite);
130 | const el = fragment(
131 | '',
132 | url,
133 | escape(suite.title)
134 | );
135 |
136 | // container
137 | stack[0].appendChild(el);
138 | stack.unshift(document.createElement('ul'));
139 | el.appendChild(stack[0]);
140 | });
141 |
142 | runner.on('suite end', function(suite) {
143 | if (suite.root) {
144 | updateStats();
145 | return;
146 | }
147 | stack.shift();
148 | });
149 |
150 | runner.on('pass', function(test) {
151 | const url = self.testURL(test);
152 | const markup =
153 | '';
157 | const el = fragment(markup, test.speed, test.title, test.duration, url);
158 | self.addCodeToggle(el, test.body);
159 | appendToStack(el);
160 | updateStats();
161 | });
162 |
163 | runner.on('fail', function(test) {
164 | const el = fragment(
165 | '',
168 | test.title,
169 | self.testURL(test)
170 | );
171 | let stackString; // Note: Includes leading newline
172 | let message = test.err.toString();
173 |
174 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
175 | // check for the result of the stringifying.
176 | if (message === '[object Error]') {
177 | message = test.err.message;
178 | }
179 |
180 | if (test.err.stack) {
181 | const indexOfMessage = test.err.stack.indexOf(test.err.message);
182 | if (indexOfMessage === -1) {
183 | stackString = test.err.stack;
184 | } else {
185 | stackString = test.err.stack.substr(
186 | test.err.message.length + indexOfMessage
187 | );
188 | }
189 | } else if (test.err.sourceURL && test.err.line !== undefined) {
190 | // Safari doesn't give you a stack. Let's at least provide a source line.
191 | stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
192 | }
193 |
194 | stackString = stackString || '';
195 |
196 | if (test.err.htmlMessage && stackString) {
197 | el.appendChild(
198 | fragment(
199 | '',
200 | test.err.htmlMessage,
201 | stackString
202 | )
203 | );
204 | } else if (test.err.htmlMessage) {
205 | el.appendChild(
206 | fragment('%s
', test.err.htmlMessage)
207 | );
208 | } else {
209 | el.appendChild(
210 | fragment('%e%e
', message, stackString)
211 | );
212 | }
213 |
214 | self.addCodeToggle(el, test.body);
215 | appendToStack(el);
216 | updateStats();
217 | });
218 |
219 | runner.on('pending', function(test) {
220 | const el = fragment(
221 | '%e
',
222 | test.title
223 | );
224 | appendToStack(el);
225 | updateStats();
226 | });
227 |
228 | function appendToStack(el) {
229 | // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
230 | if (stack[0]) {
231 | stack[0].appendChild(el);
232 | }
233 | }
234 |
235 | function updateStats() {
236 | // TODO: add to stats
237 | const percent = (stats.tests / runner.total * 100) | 0;
238 | if (progress) {
239 | progress.update(percent).draw(ctx);
240 | }
241 |
242 | // update stats
243 | const ms = new Date() - stats.start;
244 | text(passes, stats.passes);
245 | text(failures, stats.failures);
246 | text(duration, (ms / 1000).toFixed(2));
247 | }
248 | }
249 |
250 | /**
251 | * Makes a URL, preserving querystring ("search") parameters.
252 | *
253 | * @param {string} s
254 | * @return {string} A new URL.
255 | */
256 | function makeUrl(s) {
257 | let search = window.location.search;
258 |
259 | // Remove previous grep query parameter if present
260 | if (search) {
261 | search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
262 | }
263 |
264 | return (
265 | window.location.pathname +
266 | (search ? search + '&' : '?') +
267 | 'grep=' +
268 | encodeURIComponent(escapeRe(s))
269 | );
270 | }
271 |
272 | /**
273 | * Provide suite URL.
274 | *
275 | * @param {Object} [suite]
276 | */
277 | HTML.prototype.suiteURL = function(suite) {
278 | return makeUrl(suite.fullTitle());
279 | };
280 |
281 | /**
282 | * Provide test URL.
283 | *
284 | * @param {Object} [test]
285 | */
286 | HTML.prototype.testURL = function(test) {
287 | return makeUrl(test.fullTitle());
288 | };
289 |
290 | /**
291 | * Adds code toggle functionality for the provided test's list element.
292 | *
293 | * @param {HTMLLIElement} el
294 | * @param {string} contents
295 | */
296 | HTML.prototype.addCodeToggle = function(el, contents) {
297 | const h2 = el.getElementsByTagName('h2')[0];
298 | let pre;
299 |
300 | on(h2, 'click', function() {
301 | pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
302 | });
303 |
304 | pre = fragment('%e
', utils.clean(contents));
305 | el.appendChild(pre);
306 | pre.style.display = 'none';
307 | };
308 |
309 | /**
310 | * Display error `msg`.
311 | *
312 | * @param {string} msg
313 | */
314 | function error(msg) {
315 | document.body.appendChild(fragment('%s
', msg));
316 | }
317 |
318 | /**
319 | * Return a DOM fragment from `html`.
320 | *
321 | * @param {string} html
322 | */
323 | function fragment(html) {
324 | const args = arguments;
325 | const div = document.createElement('div');
326 | let i = 1;
327 |
328 | div.innerHTML = html.replace(/%([se])/g, function(_, type) {
329 | switch (type) {
330 | case 's':
331 | return String(args[i++]);
332 | case 'e':
333 | return escape(args[i++]);
334 | // no default
335 | }
336 | });
337 |
338 | return div.firstChild;
339 | }
340 |
341 | /**
342 | * Check for suites that do not have elements
343 | * with `classname`, and hide them.
344 | *
345 | * @param {text} classname
346 | */
347 | function hideSuitesWithout(classname) {
348 | const suites = document.getElementsByClassName('suite');
349 | for (let i = 0; i < suites.length; i++) {
350 | const els = suites[i].getElementsByClassName(classname);
351 | if (!els.length) {
352 | suites[i].className += ' hidden';
353 | }
354 | }
355 | }
356 |
357 | /**
358 | * Unhide .hidden suites.
359 | */
360 | function unhide() {
361 | const els = document.getElementsByClassName('suite hidden');
362 | for (let i = 0; i < els.length; ++i) {
363 | els[i].className = els[i].className.replace('suite hidden', 'suite');
364 | }
365 | }
366 |
367 | /**
368 | * Set an element's text contents.
369 | *
370 | * @param {HTMLElement} el
371 | * @param {string} contents
372 | */
373 | function text(el, contents) {
374 | if (el.textContent) {
375 | el.textContent = contents;
376 | } else {
377 | el.innerText = contents;
378 | }
379 | }
380 |
381 | /**
382 | * Listen on `event` with callback `fn`.
383 | */
384 | function on(el, event, fn) {
385 | if (el.addEventListener) {
386 | el.addEventListener(event, fn, false);
387 | } else {
388 | el.attachEvent('on' + event, fn);
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Alias exports to a their normalized format Mocha#reporter to prevent a need
4 | // for dynamic (try/catch) requires, which Browserify doesn't handle.
5 | exports.Base = exports.base = require('./base');
6 | exports.Dot = exports.dot = require('./dot');
7 | exports.Doc = exports.doc = require('./doc');
8 | exports.TAP = exports.tap = require('./tap');
9 | exports.JSON = exports.json = require('./json');
10 | exports.HTML = exports.html = require('./html');
11 | exports.List = exports.list = require('./list');
12 | exports.Min = exports.min = require('./min');
13 | exports.Spec = exports.spec = require('./spec');
14 | exports.Nyan = exports.nyan = require('./nyan');
15 | exports.XUnit = exports.xunit = require('./xunit');
16 | exports.Markdown = exports.markdown = require('./markdown');
17 | exports.Progress = exports.progress = require('./progress');
18 | exports.Landing = exports.landing = require('./landing');
19 | exports.JSONStream = exports['json-stream'] = require('./json-stream');
20 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/json-stream.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module JSONStream
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 |
11 | /**
12 | * Expose `List`.
13 | */
14 |
15 | exports = module.exports = List;
16 |
17 | /**
18 | * Initialize a new `JSONStream` test reporter.
19 | *
20 | * @public
21 | * @name JSONStream
22 | * @class JSONStream
23 | * @memberof Mocha.reporters
24 | * @augments Mocha.reporters.Base
25 | * @api public
26 | * @param {Runner} runner
27 | */
28 | function List(runner) {
29 | Base.call(this, runner);
30 |
31 | const self = this;
32 | const total = runner.total;
33 |
34 | runner.on('start', function() {
35 | console.log(JSON.stringify(['start', {total}]));
36 | });
37 |
38 | runner.on('pass', function(test) {
39 | console.log(JSON.stringify(['pass', clean(test)]));
40 | });
41 |
42 | runner.on('fail', function(test, err) {
43 | test = clean(test);
44 | test.err = err.message;
45 | test.stack = err.stack || null;
46 | console.log(JSON.stringify(['fail', test]));
47 | });
48 |
49 | runner.once('end', function() {
50 | process.stdout.write(JSON.stringify(['end', self.stats]));
51 | });
52 | }
53 |
54 | /**
55 | * Return a plain-object representation of `test`
56 | * free of cyclic properties etc.
57 | *
58 | * @api private
59 | * @param {Object} test
60 | * @return {Object}
61 | */
62 | function clean(test) {
63 | return {
64 | title: test.title,
65 | fullTitle: test.fullTitle(),
66 | duration: test.duration,
67 | currentRetry: test.currentRetry()
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/json.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module JSON
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 |
11 | /**
12 | * Expose `JSON`.
13 | */
14 |
15 | exports = module.exports = JSONReporter;
16 |
17 | /**
18 | * Initialize a new `JSON` reporter.
19 | *
20 | * @public
21 | * @class JSON
22 | * @memberof Mocha.reporters
23 | * @augments Mocha.reporters.Base
24 | * @api public
25 | * @param {Runner} runner
26 | */
27 | function JSONReporter(runner) {
28 | Base.call(this, runner);
29 |
30 | const self = this;
31 | const tests = [];
32 | const pending = [];
33 | const failures = [];
34 | const passes = [];
35 |
36 | runner.on('test end', function(test) {
37 | tests.push(test);
38 | });
39 |
40 | runner.on('pass', function(test) {
41 | passes.push(test);
42 | });
43 |
44 | runner.on('fail', function(test) {
45 | failures.push(test);
46 | });
47 |
48 | runner.on('pending', function(test) {
49 | pending.push(test);
50 | });
51 |
52 | runner.once('end', function() {
53 | const obj = {
54 | stats: self.stats,
55 | tests: tests.map(clean),
56 | pending: pending.map(clean),
57 | failures: failures.map(clean),
58 | passes: passes.map(clean)
59 | };
60 |
61 | runner.testResults = obj;
62 |
63 | process.stdout.write(JSON.stringify(obj, null, 2));
64 | });
65 | }
66 |
67 | /**
68 | * Return a plain-object representation of `test`
69 | * free of cyclic properties etc.
70 | *
71 | * @api private
72 | * @param {Object} test
73 | * @return {Object}
74 | */
75 | function clean(test) {
76 | let err = test.err || {};
77 | if (err instanceof Error) {
78 | err = errorJSON(err);
79 | }
80 |
81 | return {
82 | title: test.title,
83 | fullTitle: test.fullTitle(),
84 | duration: test.duration,
85 | currentRetry: test.currentRetry(),
86 | err: cleanCycles(err)
87 | };
88 | }
89 |
90 | /**
91 | * Replaces any circular references inside `obj` with '[object Object]'
92 | *
93 | * @api private
94 | * @param {Object} obj
95 | * @return {Object}
96 | */
97 | function cleanCycles(obj) {
98 | const cache = [];
99 | return JSON.parse(
100 | JSON.stringify(obj, function(key, value) {
101 | if (typeof value === 'object' && value !== null) {
102 | if (cache.indexOf(value) !== -1) {
103 | // Instead of going in a circle, we'll print [object Object]
104 | return '' + value;
105 | }
106 | cache.push(value);
107 | }
108 |
109 | return value;
110 | })
111 | );
112 | }
113 |
114 | /**
115 | * Transform an Error object into a JSON object.
116 | *
117 | * @api private
118 | * @param {Error} err
119 | * @return {Object}
120 | */
121 | function errorJSON(err) {
122 | const res = {};
123 | Object.getOwnPropertyNames(err).forEach(function(key) {
124 | res[key] = err[key];
125 | }, err);
126 | return res;
127 | }
128 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/landing.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | 'use strict';
3 | /**
4 | * @module Landing
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const Base = require('./base');
11 | const inherits = require('../utils').inherits;
12 | const cursor = Base.cursor;
13 | const color = Base.color;
14 |
15 | /**
16 | * Expose `Landing`.
17 | */
18 |
19 | exports = module.exports = Landing;
20 |
21 | /**
22 | * Airplane color.
23 | */
24 |
25 | Base.colors.plane = 0;
26 |
27 | /**
28 | * Airplane crash color.
29 | */
30 |
31 | Base.colors['plane crash'] = 31;
32 |
33 | /**
34 | * Runway color.
35 | */
36 |
37 | Base.colors.runway = 90;
38 |
39 | /**
40 | * Initialize a new `Landing` reporter.
41 | *
42 | * @public
43 | * @class
44 | * @memberof Mocha.reporters
45 | * @augments Mocha.reporters.Base
46 | * @api public
47 | * @param {Runner} runner
48 | */
49 | function Landing(runner) {
50 | Base.call(this, runner);
51 |
52 | const self = this;
53 | const width = (Base.window.width * 0.75) | 0;
54 | const total = runner.total;
55 | const stream = process.stdout;
56 | let plane = color('plane', '✈');
57 | let crashed = -1;
58 | let n = 0;
59 |
60 | function runway() {
61 | const buf = Array(width).join('-');
62 | return ' ' + color('runway', buf);
63 | }
64 |
65 | runner.on('start', function() {
66 | stream.write('\n\n\n ');
67 | cursor.hide();
68 | });
69 |
70 | runner.on('test end', function(test) {
71 | // check if the plane crashed
72 | const col = crashed === -1 ? (width * ++n / total) | 0 : crashed;
73 |
74 | // show the crash
75 | if (test.state === 'failed') {
76 | plane = color('plane crash', '✈');
77 | crashed = col;
78 | }
79 |
80 | // render landing strip
81 | stream.write('\u001b[' + (width + 1) + 'D\u001b[2A');
82 | stream.write(runway());
83 | stream.write('\n ');
84 | stream.write(color('runway', Array(col).join('⋅')));
85 | stream.write(plane);
86 | stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
87 | stream.write(runway());
88 | stream.write('\u001b[0m');
89 | });
90 |
91 | runner.once('end', function() {
92 | cursor.show();
93 | console.log();
94 | self.epilogue();
95 | });
96 | }
97 |
98 | /**
99 | * Inherit from `Base.prototype`.
100 | */
101 | inherits(Landing, Base);
102 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module List
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 | const inherits = require('../utils').inherits;
11 | const color = Base.color;
12 | const cursor = Base.cursor;
13 |
14 | /**
15 | * Expose `List`.
16 | */
17 |
18 | exports = module.exports = List;
19 |
20 | /**
21 | * Initialize a new `List` test reporter.
22 | *
23 | * @public
24 | * @class
25 | * @memberof Mocha.reporters
26 | * @augments Mocha.reporters.Base
27 | * @api public
28 | * @param {Runner} runner
29 | */
30 | function List(runner) {
31 | Base.call(this, runner);
32 |
33 | const self = this;
34 | let n = 0;
35 |
36 | runner.on('start', function() {
37 | console.log();
38 | });
39 |
40 | runner.on('test', function(test) {
41 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': '));
42 | });
43 |
44 | runner.on('pending', function(test) {
45 | const fmt = color('checkmark', ' -') + color('pending', ' %s');
46 | console.log(fmt, test.fullTitle());
47 | });
48 |
49 | runner.on('pass', function(test) {
50 | const fmt =
51 | color('checkmark', ' ' + Base.symbols.ok) +
52 | color('pass', ' %s: ') +
53 | color(test.speed, '%dms');
54 | cursor.CR();
55 | console.log(fmt, test.fullTitle(), test.duration);
56 | });
57 |
58 | runner.on('fail', function(test) {
59 | cursor.CR();
60 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle());
61 | });
62 |
63 | runner.once('end', self.epilogue.bind(self));
64 | }
65 |
66 | /**
67 | * Inherit from `Base.prototype`.
68 | */
69 | inherits(List, Base);
70 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/markdown.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module Markdown
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 | const utils = require('../utils');
11 |
12 | /**
13 | * Constants
14 | */
15 |
16 | const SUITE_PREFIX = '$';
17 |
18 | /**
19 | * Expose `Markdown`.
20 | */
21 |
22 | exports = module.exports = Markdown;
23 |
24 | /**
25 | * Initialize a new `Markdown` reporter.
26 | *
27 | * @public
28 | * @class
29 | * @memberof Mocha.reporters
30 | * @augments Mocha.reporters.Base
31 | * @api public
32 | * @param {Runner} runner
33 | */
34 | function Markdown(runner) {
35 | Base.call(this, runner);
36 |
37 | let level = 0;
38 | let buf = '';
39 |
40 | function title(str) {
41 | return Array(level).join('#') + ' ' + str;
42 | }
43 |
44 | function mapTOC(suite, obj) {
45 | const ret = obj;
46 | const key = SUITE_PREFIX + suite.title;
47 |
48 | obj = obj[key] = obj[key] || {suite};
49 | suite.suites.forEach(function(suite) {
50 | mapTOC(suite, obj);
51 | });
52 |
53 | return ret;
54 | }
55 |
56 | function stringifyTOC(obj, level) {
57 | ++level;
58 | let buf = '';
59 | let link;
60 | for (const key in obj) {
61 | if (key === 'suite') {
62 | continue;
63 | }
64 | if (key !== SUITE_PREFIX) {
65 | link = ' - [' + key.substring(1) + ']';
66 | link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
67 | buf += Array(level).join(' ') + link;
68 | }
69 | buf += stringifyTOC(obj[key], level);
70 | }
71 | return buf;
72 | }
73 |
74 | function generateTOC(suite) {
75 | const obj = mapTOC(suite, {});
76 | return stringifyTOC(obj, 0);
77 | }
78 |
79 | generateTOC(runner.suite);
80 |
81 | runner.on('suite', function(suite) {
82 | ++level;
83 | const slug = utils.slug(suite.fullTitle());
84 | // eslint-disable-next-line no-useless-concat
85 | buf += '' + '\n';
86 | buf += title(suite.title) + '\n';
87 | });
88 |
89 | runner.on('suite end', function() {
90 | --level;
91 | });
92 |
93 | runner.on('pass', function(test) {
94 | const code = utils.clean(test.body);
95 | buf += test.title + '.\n';
96 | buf += '\n```js\n';
97 | buf += code + '\n';
98 | buf += '```\n\n';
99 | });
100 |
101 | runner.once('end', function() {
102 | process.stdout.write('# TOC\n');
103 | process.stdout.write(generateTOC(runner.suite));
104 | process.stdout.write(buf);
105 | });
106 | }
107 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/min.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module Min
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 | const inherits = require('../utils').inherits;
11 |
12 | /**
13 | * Expose `Min`.
14 | */
15 |
16 | exports = module.exports = Min;
17 |
18 | /**
19 | * Initialize a new `Min` minimal test reporter (best used with --watch).
20 | *
21 | * @public
22 | * @class
23 | * @memberof Mocha.reporters
24 | * @augments Mocha.reporters.Base
25 | * @api public
26 | * @param {Runner} runner
27 | */
28 | function Min(runner) {
29 | Base.call(this, runner);
30 |
31 | runner.on('start', function() {
32 | // clear screen
33 | process.stdout.write('\u001b[2J');
34 | // set cursor position
35 | process.stdout.write('\u001b[1;3H');
36 | });
37 |
38 | runner.once('end', this.epilogue.bind(this));
39 | }
40 |
41 | /**
42 | * Inherit from `Base.prototype`.
43 | */
44 | inherits(Min, Base);
45 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/nyan.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | 'use strict';
3 | /**
4 | * @module Nyan
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const Base = require('./base');
11 | const inherits = require('../utils').inherits;
12 |
13 | /**
14 | * Expose `Dot`.
15 | */
16 |
17 | exports = module.exports = NyanCat;
18 |
19 | /**
20 | * Initialize a new `Dot` matrix test reporter.
21 | *
22 | * @param {Runner} runner
23 | * @api public
24 | * @public
25 | * @class Nyan
26 | * @memberof Mocha.reporters
27 | * @extends Mocha.reporters.Base
28 | */
29 |
30 | function NyanCat(runner) {
31 | Base.call(this, runner);
32 |
33 | const self = this;
34 | const width = (Base.window.width * 0.75) | 0;
35 | const nyanCatWidth = (this.nyanCatWidth = 11);
36 |
37 | this.colorIndex = 0;
38 | this.numberOfLines = 4;
39 | this.rainbowColors = self.generateColors();
40 | this.scoreboardWidth = 5;
41 | this.tick = 0;
42 | this.trajectories = [[], [], [], []];
43 | this.trajectoryWidthMax = width - nyanCatWidth;
44 |
45 | runner.on('start', function() {
46 | Base.cursor.hide();
47 | self.draw();
48 | });
49 |
50 | runner.on('pending', function() {
51 | self.draw();
52 | });
53 |
54 | runner.on('pass', function() {
55 | self.draw();
56 | });
57 |
58 | runner.on('fail', function() {
59 | self.draw();
60 | });
61 |
62 | runner.once('end', function() {
63 | Base.cursor.show();
64 | for (let i = 0; i < self.numberOfLines; i++) {
65 | write('\n');
66 | }
67 | self.epilogue();
68 | });
69 | }
70 |
71 | /**
72 | * Inherit from `Base.prototype`.
73 | */
74 | inherits(NyanCat, Base);
75 |
76 | /**
77 | * Draw the nyan cat
78 | *
79 | * @api private
80 | */
81 |
82 | NyanCat.prototype.draw = function() {
83 | this.appendRainbow();
84 | this.drawScoreboard();
85 | this.drawRainbow();
86 | this.drawNyanCat();
87 | this.tick = !this.tick;
88 | };
89 |
90 | /**
91 | * Draw the "scoreboard" showing the number
92 | * of passes, failures and pending tests.
93 | *
94 | * @api private
95 | */
96 |
97 | NyanCat.prototype.drawScoreboard = function() {
98 | const stats = this.stats;
99 |
100 | function draw(type, n) {
101 | write(' ');
102 | write(Base.color(type, n));
103 | write('\n');
104 | }
105 |
106 | draw('green', stats.passes);
107 | draw('fail', stats.failures);
108 | draw('pending', stats.pending);
109 | write('\n');
110 |
111 | this.cursorUp(this.numberOfLines);
112 | };
113 |
114 | /**
115 | * Append the rainbow.
116 | *
117 | * @api private
118 | */
119 |
120 | NyanCat.prototype.appendRainbow = function() {
121 | const segment = this.tick ? '_' : '-';
122 | const rainbowified = this.rainbowify(segment);
123 |
124 | for (let index = 0; index < this.numberOfLines; index++) {
125 | const trajectory = this.trajectories[index];
126 | if (trajectory.length >= this.trajectoryWidthMax) {
127 | trajectory.shift();
128 | }
129 | trajectory.push(rainbowified);
130 | }
131 | };
132 |
133 | /**
134 | * Draw the rainbow.
135 | *
136 | * @api private
137 | */
138 |
139 | NyanCat.prototype.drawRainbow = function() {
140 | const self = this;
141 |
142 | this.trajectories.forEach(function(line) {
143 | write('\u001b[' + self.scoreboardWidth + 'C');
144 | write(line.join(''));
145 | write('\n');
146 | });
147 |
148 | this.cursorUp(this.numberOfLines);
149 | };
150 |
151 | /**
152 | * Draw the nyan cat
153 | *
154 | * @api private
155 | */
156 | NyanCat.prototype.drawNyanCat = function() {
157 | const self = this;
158 | const startWidth = this.scoreboardWidth + this.trajectories[0].length;
159 | const dist = '\u001b[' + startWidth + 'C';
160 | let padding = '';
161 |
162 | write(dist);
163 | write('_,------,');
164 | write('\n');
165 |
166 | write(dist);
167 | padding = self.tick ? ' ' : ' ';
168 | write('_|' + padding + '/\\_/\\ ');
169 | write('\n');
170 |
171 | write(dist);
172 | padding = self.tick ? '_' : '__';
173 | const tail = self.tick ? '~' : '^';
174 | write(tail + '|' + padding + this.face() + ' ');
175 | write('\n');
176 |
177 | write(dist);
178 | padding = self.tick ? ' ' : ' ';
179 | write(padding + '"" "" ');
180 | write('\n');
181 |
182 | this.cursorUp(this.numberOfLines);
183 | };
184 |
185 | /**
186 | * Draw nyan cat face.
187 | *
188 | * @api private
189 | * @return {string}
190 | */
191 |
192 | NyanCat.prototype.face = function() {
193 | const stats = this.stats;
194 | if (stats.failures) {
195 | return '( x .x)';
196 | } else if (stats.pending) {
197 | return '( o .o)';
198 | } else if (stats.passes) {
199 | return '( ^ .^)';
200 | }
201 | return '( - .-)';
202 | };
203 |
204 | /**
205 | * Move cursor up `n`.
206 | *
207 | * @api private
208 | * @param {number} n
209 | */
210 |
211 | NyanCat.prototype.cursorUp = function(n) {
212 | write('\u001b[' + n + 'A');
213 | };
214 |
215 | /**
216 | * Move cursor down `n`.
217 | *
218 | * @api private
219 | * @param {number} n
220 | */
221 |
222 | NyanCat.prototype.cursorDown = function(n) {
223 | write('\u001b[' + n + 'B');
224 | };
225 |
226 | /**
227 | * Generate rainbow colors.
228 | *
229 | * @api private
230 | * @return {Array}
231 | */
232 | NyanCat.prototype.generateColors = function() {
233 | const colors = [];
234 |
235 | for (let i = 0; i < 6 * 7; i++) {
236 | const pi3 = Math.floor(Math.PI / 3);
237 | const n = i * (1.0 / 6);
238 | const r = Math.floor(3 * Math.sin(n) + 3);
239 | const g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
240 | const b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
241 | colors.push(36 * r + 6 * g + b + 16);
242 | }
243 |
244 | return colors;
245 | };
246 |
247 | /**
248 | * Apply rainbow to the given `str`.
249 | *
250 | * @api private
251 | * @param {string} str
252 | * @return {string}
253 | */
254 | NyanCat.prototype.rainbowify = function(str) {
255 | if (!Base.useColors) {
256 | return str;
257 | }
258 | const color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
259 | this.colorIndex += 1;
260 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
261 | };
262 |
263 | /**
264 | * Stdout helper.
265 | *
266 | * @param {string} string A message to write to stdout.
267 | */
268 | function write(string) {
269 | process.stdout.write(string);
270 | }
271 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/progress.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | 'use strict';
3 | /**
4 | * @module Progress
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const Base = require('./base');
11 | const inherits = require('../utils').inherits;
12 | const color = Base.color;
13 | const cursor = Base.cursor;
14 |
15 | /**
16 | * Expose `Progress`.
17 | */
18 |
19 | exports = module.exports = Progress;
20 |
21 | /**
22 | * General progress bar color.
23 | */
24 |
25 | Base.colors.progress = 90;
26 |
27 | /**
28 | * Initialize a new `Progress` bar test reporter.
29 | *
30 | * @public
31 | * @class
32 | * @memberof Mocha.reporters
33 | * @augments Mocha.reporters.Base
34 | * @api public
35 | * @param {Runner} runner
36 | * @param {Object} options
37 | */
38 | function Progress(runner, options) {
39 | Base.call(this, runner);
40 |
41 | const self = this;
42 | const width = (Base.window.width * 0.5) | 0;
43 | const total = runner.total;
44 | let complete = 0;
45 | let lastN = -1;
46 |
47 | // default chars
48 | options = options || {};
49 | const reporterOptions = options.reporterOptions || {};
50 |
51 | options.open = reporterOptions.open || '[';
52 | options.complete = reporterOptions.complete || '▬';
53 | options.incomplete = reporterOptions.incomplete || Base.symbols.dot;
54 | options.close = reporterOptions.close || ']';
55 | options.verbose = reporterOptions.verbose || false;
56 |
57 | // tests started
58 | runner.on('start', function() {
59 | console.log();
60 | cursor.hide();
61 | });
62 |
63 | // tests complete
64 | runner.on('test end', function() {
65 | complete++;
66 |
67 | const percent = complete / total;
68 | const n = (width * percent) | 0;
69 | const i = width - n;
70 |
71 | if (n === lastN && !options.verbose) {
72 | // Don't re-render the line if it hasn't changed
73 | return;
74 | }
75 | lastN = n;
76 |
77 | cursor.CR();
78 | process.stdout.write('\u001b[J');
79 | process.stdout.write(color('progress', ' ' + options.open));
80 | process.stdout.write(Array(n).join(options.complete));
81 | process.stdout.write(Array(i).join(options.incomplete));
82 | process.stdout.write(color('progress', options.close));
83 | if (options.verbose) {
84 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
85 | }
86 | });
87 |
88 | // tests are complete, output some stats
89 | // and the failures if any
90 | runner.once('end', function() {
91 | cursor.show();
92 | console.log();
93 | self.epilogue();
94 | });
95 | }
96 |
97 | /**
98 | * Inherit from `Base.prototype`.
99 | */
100 | inherits(Progress, Base);
101 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 | /**
4 | * @module Spec
5 | */
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | const Base = require('./base');
11 | const inherits = require('../utils').inherits;
12 | const color = Base.color;
13 |
14 | /**
15 | * Expose `Spec`.
16 | */
17 |
18 | exports = module.exports = Spec;
19 |
20 | /**
21 | * support macaca-reporter
22 | */
23 | function stringify(obj, replacer, spaces, cycleReplacer) {
24 | return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
25 | }
26 |
27 | function serializer(replacer, cycleReplacer) {
28 | const stack = [], keys = []
29 |
30 | if (cycleReplacer == null) cycleReplacer = function (key, value) {
31 | if (stack[0] === value) return "[Circular ~]"
32 | return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
33 | }
34 |
35 | return function (key, value) {
36 | if (stack.length > 0) {
37 | const thisPos = stack.indexOf(this)
38 | ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
39 | ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
40 | if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
41 | }
42 | else stack.push(value)
43 |
44 | return replacer == null ? value : replacer.call(this, key, value)
45 | }
46 | }
47 |
48 | const totalTests = {
49 | total: 0
50 | };
51 |
52 | function uuid() {
53 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
54 | const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
55 | return v.toString(16);
56 | });
57 | }
58 |
59 | function transferCode(data) {
60 | data = data
61 | .replace(/\r\n?|[\n\u2028\u2029]/g, '\n')
62 | .replace(/^\uFEFF/, '')
63 | .replace(/^function\s*\(.*\)\s*{|\(.*\)\s*=>\s*{?/, '')
64 | .replace(/\s*\}$/, '')
65 | .replace(/"/g, '');
66 |
67 | const spaces = data.match(/^\n?( *)/)[1].length;
68 | const tabs = data.match(/^\n?(\t*)/)[1].length;
69 | const reg = new RegExp(`^\n?${tabs ? '\t' : ' '}{${tabs || spaces}}`, 'gm');
70 |
71 | return data
72 | .replace(reg, '')
73 | .replace(/^\s+|\s+$/g, '')
74 | .replace(/\)\./g, '\)\n\.');
75 | }
76 |
77 | function transferTest(test, suite, parentTitle) {
78 | const code = test.fn ? test.fn.toString() : test.body;
79 |
80 | const cleaned = {
81 | title: test.title,
82 | fullTitle: parentTitle + ' -- ' + suite.title + ' -- ' + test.title,
83 | duration: test.duration || 0,
84 | state: test.state,
85 | pass: test.state === 'passed',
86 | fail: test.state === 'failed',
87 | pending: test.pending,
88 | context: require('../utils').stringify(test.context),
89 | code: code && transferCode(code),
90 | uuid: test.uuid || uuid()
91 | };
92 |
93 | cleaned.skipped = !cleaned.pass && !cleaned.fail && !cleaned.pending && !cleaned.isHook;
94 | return cleaned;
95 | }
96 |
97 | function transferSuite(suite, totalTests, totalTime, parentTitle) {
98 | suite.uuid = uuid();
99 | const cleanTmpTests = suite.tests.map(test => test.state && transferTest(test, suite, parentTitle));
100 | const cleanTests = cleanTmpTests.filter(test => !!test);
101 |
102 | const passingTests = cleanTests.filter(item => item.state === 'passed');
103 |
104 | const failingTests = cleanTests.filter(item => item.state === 'failed');
105 | const pendingTests = cleanTests.filter(item => item.pending);
106 | const skippedTests = cleanTests.filter(item => item.skipped);
107 | let duration = 0;
108 |
109 | cleanTests.forEach(test => {
110 | duration += test.duration;
111 | totalTime.time += test.duration;
112 | });
113 |
114 | suite.tests = cleanTests;
115 | suite.fullFile = suite.file || '';
116 | suite.file = suite.file ? suite.file.replace(process.cwd(), '') : '';
117 | suite.passes = passingTests;
118 | suite.failures = failingTests;
119 | suite.pending = pendingTests;
120 | suite.skipped = skippedTests;
121 | suite.totalTests = suite.tests.length;
122 | suite.totalPasses = passingTests.length;
123 | suite.totalFailures = failingTests.length;
124 | suite.totalPending = pendingTests.length;
125 | suite.totalSkipped = skippedTests.length;
126 | suite.duration = duration;
127 | }
128 |
129 | function getSuite(suite, totalTests) {
130 | const totalTime = {
131 | time: 0
132 | };
133 | const queue = [];
134 | const result = JSON.parse(suite.replace(/"/g, ''));
135 | let next = result;
136 | totalTests.total++;
137 | while (next) {
138 |
139 | if (next.root) {
140 | transferSuite(next, totalTests, totalTime);
141 | }
142 |
143 | if (next.suites && next.suites.length) {
144 | next.suites.forEach(nextSuite => {
145 | transferSuite(nextSuite, totalTests, totalTime, next.title);
146 | queue.push(nextSuite);
147 | });
148 | }
149 | next = queue.shift();
150 | }
151 | result._totalTime = totalTime.time;
152 | return stringify(result);
153 | }
154 |
155 | function done(output, config, failures, exit) {
156 | try {
157 | window.__execCommand('saveReport', output)
158 | .finally(() => exit && exit(failures ? 1 : 0));
159 | } catch (e) {
160 | console.log(e);
161 | }
162 | }
163 |
164 | /**
165 | * Initialize a new `Spec` test reporter.
166 | *
167 | * @public
168 | * @class
169 | * @memberof Mocha.reporters
170 | * @augments Mocha.reporters.Base
171 | * @api public
172 | * @param {Runner} runner
173 | */
174 | function Spec(runner) {
175 | Base.call(this, runner);
176 |
177 | const self = this;
178 | let indents = 0;
179 | let n = 0;
180 |
181 | function indent() {
182 | return Array(indents).join(' ');
183 | }
184 |
185 | const getSuiteData = (isEnd) => {
186 | const result = getSuite(stringify(this._originSuiteData), totalTests);
187 | const obj = {
188 | stats: this.stats,
189 | suites: JSON.parse(result)
190 | };
191 |
192 | const {
193 | passes,
194 | failures,
195 | pending,
196 | tests
197 | } = obj.stats;
198 |
199 | const passPercentage = Math.round((passes / (totalTests.total - pending)) * 1000) / 10;
200 | const pendingPercentage = Math.round((pending / totalTests.total) * 1000) / 10;
201 |
202 | if (!isEnd) {
203 | obj.stats.passPercent = passPercentage;
204 | obj.stats.pendingPercent = pendingPercentage;
205 | obj.stats.other = passes + failures + pending - tests;
206 | obj.stats.hasOther = obj.stats.other > 0;
207 | obj.stats.skipped = totalTests.total - tests;
208 | obj.stats.hasSkipped = obj.stats.skipped > 0;
209 | obj.stats.failures -= obj.stats.other;
210 | }
211 | return obj;
212 | };
213 |
214 | const handleTestEnd = (isEnd) => {
215 | const obj = getSuiteData(isEnd);
216 | obj.stats.duration = obj.suites._totalTime || 0;
217 | this.output = obj;
218 | };
219 |
220 | this.done = (failures, exit) => done(this.output, this.config, failures, exit);
221 |
222 | runner.on('start', () => {
223 | this._originSuiteData = runner.suite;
224 | });
225 |
226 | runner.on('test end', test => {
227 | test.uuid = uuid();
228 | handleTestEnd();
229 | });
230 |
231 | runner.once('end', () => {
232 | handleTestEnd(true);
233 | self.epilogue();
234 | });
235 |
236 | runner.on('suite', function (suite) {
237 | ++indents;
238 | console.log(color('suite', '%s%s'), indent(), suite.title);
239 | });
240 |
241 | runner.on('suite end', function () {
242 | --indents;
243 | if (indents === 1) {
244 | console.log();
245 | }
246 | });
247 |
248 | runner.on('pending', function (test) {
249 | test.uuid = uuid();
250 | const fmt = indent() + color('pending', ' - %s');
251 | console.log(fmt, test.title);
252 | });
253 |
254 | runner.on('pass', function (test) {
255 | let fmt;
256 | if (test.speed === 'fast') {
257 | fmt =
258 | indent() +
259 | color('checkmark', ' ' + Base.symbols.ok) +
260 | color('pass', ' %s');
261 | console.log(fmt, test.title);
262 | } else {
263 | fmt =
264 | indent() +
265 | color('checkmark', ' ' + Base.symbols.ok) +
266 | color('pass', ' %s') +
267 | color(test.speed, ' (%dms)');
268 | console.log(fmt, test.title, test.duration);
269 | }
270 | });
271 |
272 | runner.on('fail', function (test, err) {
273 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title);
274 | console.log(indent(), color('fail', require('../utils').escape(err)));
275 | });
276 | }
277 |
278 | /**
279 | * Inherit from `Base.prototype`.
280 | */
281 | inherits(Spec, Base);
282 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/tap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module TAP
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 |
11 | /**
12 | * Expose `TAP`.
13 | */
14 |
15 | exports = module.exports = TAP;
16 |
17 | /**
18 | * Initialize a new `TAP` reporter.
19 | *
20 | * @public
21 | * @class
22 | * @memberof Mocha.reporters
23 | * @augments Mocha.reporters.Base
24 | * @api public
25 | * @param {Runner} runner
26 | */
27 | function TAP(runner) {
28 | Base.call(this, runner);
29 |
30 | let n = 1;
31 | let passes = 0;
32 | let failures = 0;
33 |
34 | runner.on('start', function() {
35 | const total = runner.grepTotal(runner.suite);
36 | console.log('%d..%d', 1, total);
37 | });
38 |
39 | runner.on('test end', function() {
40 | ++n;
41 | });
42 |
43 | runner.on('pending', function(test) {
44 | console.log('ok %d %s # SKIP -', n, title(test));
45 | });
46 |
47 | runner.on('pass', function(test) {
48 | passes++;
49 | console.log('ok %d %s', n, title(test));
50 | });
51 |
52 | runner.on('fail', function(test, err) {
53 | failures++;
54 | console.log('not ok %d %s', n, title(test));
55 | if (err.stack) {
56 | console.log(err.stack.replace(/^/gm, ' '));
57 | }
58 | });
59 |
60 | runner.once('end', function() {
61 | console.log('# tests ' + (passes + failures));
62 | console.log('# pass ' + passes);
63 | console.log('# fail ' + failures);
64 | });
65 | }
66 |
67 | /**
68 | * Return a TAP-safe title of `test`
69 | *
70 | * @api private
71 | * @param {Object} test
72 | * @return {String}
73 | */
74 | function title(test) {
75 | return test.fullTitle().replace(/#/g, '');
76 | }
77 |
--------------------------------------------------------------------------------
/lib/mocha/reporters/xunit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module XUnit
4 | */
5 | /**
6 | * Module dependencies.
7 | */
8 |
9 | const Base = require('./base');
10 | const utils = require('../utils');
11 | const inherits = utils.inherits;
12 | const fs = require('fs');
13 | const escape = utils.escape;
14 | const mkdirp = require('mkdirp');
15 | const path = require('path');
16 |
17 | /**
18 | * Save timer references to avoid Sinon interfering (see GH-237).
19 | */
20 |
21 | /* eslint-disable no-unused-vars, no-native-reassign */
22 | const Date = global.Date;
23 | const setTimeout = global.setTimeout;
24 | const setInterval = global.setInterval;
25 | const clearTimeout = global.clearTimeout;
26 | const clearInterval = global.clearInterval;
27 | /* eslint-enable no-unused-vars, no-native-reassign */
28 |
29 | /**
30 | * Expose `XUnit`.
31 | */
32 |
33 | exports = module.exports = XUnit;
34 |
35 | /**
36 | * Initialize a new `XUnit` reporter.
37 | *
38 | * @public
39 | * @class
40 | * @memberof Mocha.reporters
41 | * @augments Mocha.reporters.Base
42 | * @api public
43 | * @param {Runner} runner
44 | */
45 | function XUnit(runner, options) {
46 | Base.call(this, runner);
47 |
48 | const stats = this.stats;
49 | const tests = [];
50 | const self = this;
51 |
52 | // the name of the test suite, as it will appear in the resulting XML file
53 | let suiteName;
54 |
55 | // the default name of the test suite if none is provided
56 | const DEFAULT_SUITE_NAME = 'Mocha Tests';
57 |
58 | if (options && options.reporterOptions) {
59 | if (options.reporterOptions.output) {
60 | if (!fs.createWriteStream) {
61 | throw new Error('file output not supported in browser');
62 | }
63 |
64 | mkdirp.sync(path.dirname(options.reporterOptions.output));
65 | self.fileStream = fs.createWriteStream(options.reporterOptions.output);
66 | }
67 |
68 | // get the suite name from the reporter options (if provided)
69 | suiteName = options.reporterOptions.suiteName;
70 | }
71 |
72 | // fall back to the default suite name
73 | suiteName = suiteName || DEFAULT_SUITE_NAME;
74 |
75 | runner.on('pending', function(test) {
76 | tests.push(test);
77 | });
78 |
79 | runner.on('pass', function(test) {
80 | tests.push(test);
81 | });
82 |
83 | runner.on('fail', function(test) {
84 | tests.push(test);
85 | });
86 |
87 | runner.once('end', function() {
88 | self.write(
89 | tag(
90 | 'testsuite',
91 | {
92 | name: suiteName,
93 | tests: stats.tests,
94 | failures: stats.failures,
95 | errors: stats.failures,
96 | skipped: stats.tests - stats.failures - stats.passes,
97 | timestamp: new Date().toUTCString(),
98 | time: stats.duration / 1000 || 0
99 | },
100 | false
101 | )
102 | );
103 |
104 | tests.forEach(function(t) {
105 | self.test(t);
106 | });
107 |
108 | self.write('');
109 | });
110 | }
111 |
112 | /**
113 | * Inherit from `Base.prototype`.
114 | */
115 | inherits(XUnit, Base);
116 |
117 | /**
118 | * Override done to close the stream (if it's a file).
119 | *
120 | * @param failures
121 | * @param {Function} fn
122 | */
123 | XUnit.prototype.done = function(failures, fn) {
124 | if (this.fileStream) {
125 | this.fileStream.end(function() {
126 | fn(failures);
127 | });
128 | } else {
129 | fn(failures);
130 | }
131 | };
132 |
133 | /**
134 | * Write out the given line.
135 | *
136 | * @param {string} line
137 | */
138 | XUnit.prototype.write = function(line) {
139 | if (this.fileStream) {
140 | this.fileStream.write(line + '\n');
141 | } else if (typeof process === 'object' && process.stdout) {
142 | process.stdout.write(line + '\n');
143 | } else {
144 | console.log(line);
145 | }
146 | };
147 |
148 | /**
149 | * Output tag for the given `test.`
150 | *
151 | * @param {Test} test
152 | */
153 | XUnit.prototype.test = function(test) {
154 | const attrs = {
155 | classname: test.parent.fullTitle(),
156 | name: test.title,
157 | time: test.duration / 1000 || 0
158 | };
159 |
160 | if (test.state === 'failed') {
161 | const err = test.err;
162 | this.write(
163 | tag(
164 | 'testcase',
165 | attrs,
166 | false,
167 | tag(
168 | 'failure',
169 | {},
170 | false,
171 | escape(err.message) + '\n' + escape(err.stack)
172 | )
173 | )
174 | );
175 | } else if (test.isPending()) {
176 | this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
177 | } else {
178 | this.write(tag('testcase', attrs, true));
179 | }
180 | };
181 |
182 | /**
183 | * HTML tag helper.
184 | *
185 | * @param name
186 | * @param attrs
187 | * @param close
188 | * @param content
189 | * @return {string}
190 | */
191 | function tag(name, attrs, close, content) {
192 | const end = close ? '/>' : '>';
193 | const pairs = [];
194 | let tag;
195 |
196 | for (const key in attrs) {
197 | if (Object.prototype.hasOwnProperty.call(attrs, key)) {
198 | pairs.push(key + '="' + escape(attrs[key]) + '"');
199 | }
200 | }
201 |
202 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
203 | if (content) {
204 | tag += content + '' + name + end;
205 | }
206 | return tag;
207 | }
208 |
--------------------------------------------------------------------------------
/lib/mocha/runnable.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 | const EventEmitter = require('events').EventEmitter;
4 | const Pending = require('./pending');
5 | const debug = require('debug')('mocha:runnable');
6 | const milliseconds = require('./ms');
7 | const utils = require('./utils');
8 |
9 | /**
10 | * Save timer references to avoid Sinon interfering (see GH-237).
11 | */
12 |
13 | /* eslint-disable no-unused-vars, no-native-reassign */
14 | const Date = global.Date;
15 | const setTimeout = global.setTimeout;
16 | const setInterval = global.setInterval;
17 | const clearTimeout = global.clearTimeout;
18 | const clearInterval = global.clearInterval;
19 | /* eslint-enable no-unused-vars, no-native-reassign */
20 |
21 | const toString = Object.prototype.toString;
22 |
23 | module.exports = Runnable;
24 |
25 | /**
26 | * Initialize a new `Runnable` with the given `title` and callback `fn`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
27 | *
28 | * @class
29 | * @augments EventEmitter
30 | * @param {String} title
31 | * @param {Function} fn
32 | */
33 | function Runnable(title, fn) {
34 | this.title = title;
35 | this.fn = fn;
36 | this.body = (fn || '').toString();
37 | this.async = fn && fn.length;
38 | this.sync = !this.async;
39 | this._timeout = 2000;
40 | this._slow = 75;
41 | this._enableTimeouts = true;
42 | this.timedOut = false;
43 | this._retries = -1;
44 | this._currentRetry = 0;
45 | this.pending = false;
46 | }
47 |
48 | /**
49 | * Inherit from `EventEmitter.prototype`.
50 | */
51 | utils.inherits(Runnable, EventEmitter);
52 |
53 | /**
54 | * Set & get timeout `ms`.
55 | *
56 | * @api private
57 | * @param {number|string} ms
58 | * @return {Runnable|number} ms or Runnable instance.
59 | */
60 | Runnable.prototype.timeout = function(ms) {
61 | if (!arguments.length) {
62 | return this._timeout;
63 | }
64 | // see #1652 for reasoning
65 | if (ms === 0 || ms > Math.pow(2, 31)) {
66 | this._enableTimeouts = false;
67 | }
68 | if (typeof ms === 'string') {
69 | ms = milliseconds(ms);
70 | }
71 | debug('timeout %d', ms);
72 | this._timeout = ms;
73 | if (this.timer) {
74 | this.resetTimeout();
75 | }
76 | return this;
77 | };
78 |
79 | /**
80 | * Set or get slow `ms`.
81 | *
82 | * @api private
83 | * @param {number|string} ms
84 | * @return {Runnable|number} ms or Runnable instance.
85 | */
86 | Runnable.prototype.slow = function(ms) {
87 | if (!arguments.length || typeof ms === 'undefined') {
88 | return this._slow;
89 | }
90 | if (typeof ms === 'string') {
91 | ms = milliseconds(ms);
92 | }
93 | debug('slow %d', ms);
94 | this._slow = ms;
95 | return this;
96 | };
97 |
98 | /**
99 | * Set and get whether timeout is `enabled`.
100 | *
101 | * @api private
102 | * @param {boolean} enabled
103 | * @return {Runnable|boolean} enabled or Runnable instance.
104 | */
105 | Runnable.prototype.enableTimeouts = function(enabled) {
106 | if (!arguments.length) {
107 | return this._enableTimeouts;
108 | }
109 | debug('enableTimeouts %s', enabled);
110 | this._enableTimeouts = enabled;
111 | return this;
112 | };
113 |
114 | /**
115 | * Halt and mark as pending.
116 | *
117 | * @memberof Mocha.Runnable
118 | * @public
119 | * @api public
120 | */
121 | Runnable.prototype.skip = function() {
122 | throw new Pending('sync skip');
123 | };
124 |
125 | /**
126 | * Check if this runnable or its parent suite is marked as pending.
127 | *
128 | * @api private
129 | */
130 | Runnable.prototype.isPending = function() {
131 | return this.pending || (this.parent && this.parent.isPending());
132 | };
133 |
134 | /**
135 | * Return `true` if this Runnable has failed.
136 | * @return {boolean}
137 | * @private
138 | */
139 | Runnable.prototype.isFailed = function() {
140 | return !this.isPending() && this.state === 'failed';
141 | };
142 |
143 | /**
144 | * Return `true` if this Runnable has passed.
145 | * @return {boolean}
146 | * @private
147 | */
148 | Runnable.prototype.isPassed = function() {
149 | return !this.isPending() && this.state === 'passed';
150 | };
151 |
152 | /**
153 | * Set or get number of retries.
154 | *
155 | * @api private
156 | */
157 | Runnable.prototype.retries = function(n) {
158 | if (!arguments.length) {
159 | return this._retries;
160 | }
161 | this._retries = n;
162 | };
163 |
164 | /**
165 | * Set or get current retry
166 | *
167 | * @api private
168 | */
169 | Runnable.prototype.currentRetry = function(n) {
170 | if (!arguments.length) {
171 | return this._currentRetry;
172 | }
173 | this._currentRetry = n;
174 | };
175 |
176 | /**
177 | * Return the full title generated by recursively concatenating the parent's
178 | * full title.
179 | *
180 | * @memberof Mocha.Runnable
181 | * @public
182 | * @api public
183 | * @return {string}
184 | */
185 | Runnable.prototype.fullTitle = function() {
186 | return this.titlePath().join(' ');
187 | };
188 |
189 | /**
190 | * Return the title path generated by concatenating the parent's title path with the title.
191 | *
192 | * @memberof Mocha.Runnable
193 | * @public
194 | * @api public
195 | * @return {string}
196 | */
197 | Runnable.prototype.titlePath = function() {
198 | return this.parent.titlePath().concat([this.title]);
199 | };
200 |
201 | /**
202 | * Clear the timeout.
203 | *
204 | * @api private
205 | */
206 | Runnable.prototype.clearTimeout = function() {
207 | clearTimeout(this.timer);
208 | };
209 |
210 | /**
211 | * Inspect the runnable void of private properties.
212 | *
213 | * @api private
214 | * @return {string}
215 | */
216 | Runnable.prototype.inspect = function() {
217 | return JSON.stringify(
218 | this,
219 | function(key, val) {
220 | if (key[0] === '_') {
221 | return;
222 | }
223 | if (key === 'parent') {
224 | return '#';
225 | }
226 | if (key === 'ctx') {
227 | return '#';
228 | }
229 | return val;
230 | },
231 | 2
232 | );
233 | };
234 |
235 | /**
236 | * Reset the timeout.
237 | *
238 | * @api private
239 | */
240 | Runnable.prototype.resetTimeout = function() {
241 | const self = this;
242 | const ms = this.timeout() || 1e9;
243 |
244 | if (!this._enableTimeouts) {
245 | return;
246 | }
247 | this.clearTimeout();
248 | this.timer = setTimeout(function() {
249 | if (!self._enableTimeouts) {
250 | return;
251 | }
252 | self.callback(self._timeoutError(ms));
253 | self.timedOut = true;
254 | }, ms);
255 | };
256 |
257 | /**
258 | * Set or get a list of whitelisted globals for this test run.
259 | *
260 | * @api private
261 | * @param {string[]} globals
262 | */
263 | Runnable.prototype.globals = function(globals) {
264 | if (!arguments.length) {
265 | return this._allowedGlobals;
266 | }
267 | this._allowedGlobals = globals;
268 | };
269 |
270 | /**
271 | * Run the test and invoke `fn(err)`.
272 | *
273 | * @param {Function} fn
274 | * @api private
275 | */
276 | Runnable.prototype.run = function(fn) {
277 | const self = this;
278 | const start = new Date();
279 | const ctx = this.ctx;
280 | let finished;
281 | let emitted;
282 |
283 | // Sometimes the ctx exists, but it is not runnable
284 | if (ctx && ctx.runnable) {
285 | ctx.runnable(this);
286 | }
287 |
288 | // called multiple times
289 | function multiple(err) {
290 | if (emitted) {
291 | return;
292 | }
293 | emitted = true;
294 | const msg = 'done() called multiple times';
295 | if (err && err.message) {
296 | err.message += " (and Mocha's " + msg + ')';
297 | self.emit('error', err);
298 | } else {
299 | self.emit('error', new Error(msg));
300 | }
301 | }
302 |
303 | // finished
304 | function done(err) {
305 | const ms = self.timeout();
306 | if (self.timedOut) {
307 | return;
308 | }
309 |
310 | if (finished) {
311 | return multiple(err);
312 | }
313 |
314 | self.clearTimeout();
315 | self.duration = new Date() - start;
316 | finished = true;
317 | if (!err && self.duration > ms && self._enableTimeouts) {
318 | err = self._timeoutError(ms);
319 | }
320 | fn(err);
321 | }
322 |
323 | // for .resetTimeout()
324 | this.callback = done;
325 |
326 | // explicit async with `done` argument
327 | if (this.async) {
328 | this.resetTimeout();
329 |
330 | // allows skip() to be used in an explicit async context
331 | this.skip = function asyncSkip() {
332 | done(new Pending('async skip call'));
333 | // halt execution. the Runnable will be marked pending
334 | // by the previous call, and the uncaught handler will ignore
335 | // the failure.
336 | throw new Pending('async skip; aborting execution');
337 | };
338 |
339 | if (this.allowUncaught) {
340 | return callFnAsync(this.fn);
341 | }
342 | try {
343 | callFnAsync(this.fn);
344 | } catch (err) {
345 | emitted = true;
346 | done(utils.getError(err));
347 | }
348 | return;
349 | }
350 |
351 | if (this.allowUncaught) {
352 | if (this.isPending()) {
353 | done();
354 | } else {
355 | callFn(this.fn);
356 | }
357 | return;
358 | }
359 |
360 | // sync or promise-returning
361 | try {
362 | if (this.isPending()) {
363 | done();
364 | } else {
365 | callFn(this.fn);
366 | }
367 | } catch (err) {
368 | emitted = true;
369 | done(utils.getError(err));
370 | }
371 |
372 | function callFn(fn) {
373 | const result = fn.call(ctx);
374 | if (result && typeof result.then === 'function') {
375 | self.resetTimeout();
376 | result.then(
377 | function() {
378 | done();
379 | // Return null so libraries like bluebird do not warn about
380 | // subsequently constructed Promises.
381 | return null;
382 | },
383 | function(reason) {
384 | done(reason || new Error('Promise rejected with no or falsy reason'));
385 | }
386 | );
387 | } else {
388 | if (self.asyncOnly) {
389 | return done(
390 | new Error(
391 | '--async-only option in use without declaring `done()` or returning a promise'
392 | )
393 | );
394 | }
395 |
396 | done();
397 | }
398 | }
399 |
400 | function callFnAsync(fn) {
401 | var result = fn.call(ctx, function(err) {
402 | if (err instanceof Error || toString.call(err) === '[object Error]') {
403 | return done(err);
404 | }
405 | if (err) {
406 | if (Object.prototype.toString.call(err) === '[object Object]') {
407 | return done(
408 | new Error('done() invoked with non-Error: ' + JSON.stringify(err))
409 | );
410 | }
411 | return done(new Error('done() invoked with non-Error: ' + err));
412 | }
413 | if (result && utils.isPromise(result)) {
414 | return done(
415 | new Error(
416 | 'Resolution method is overspecified. Specify a callback *or* return a Promise; not both.'
417 | )
418 | );
419 | }
420 |
421 | done();
422 | });
423 | }
424 | };
425 |
426 | /**
427 | * Instantiates a "timeout" error
428 | *
429 | * @param {number} ms - Timeout (in milliseconds)
430 | * @return {Error} a "timeout" error
431 | * @private
432 | */
433 | Runnable.prototype._timeoutError = function(ms) {
434 | let msg =
435 | 'Timeout of ' +
436 | ms +
437 | 'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.';
438 | if (this.file) {
439 | msg += ' (' + this.file + ')';
440 | }
441 | return new Error(msg);
442 | };
443 |
--------------------------------------------------------------------------------
/lib/mocha/suite.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @module Suite
4 | */
5 |
6 | /**
7 | * Module dependencies.
8 | */
9 | const EventEmitter = require('events').EventEmitter;
10 | const Hook = require('./hook');
11 | const utils = require('./utils');
12 | const inherits = utils.inherits;
13 | const debug = require('debug')('mocha:suite');
14 | const milliseconds = require('./ms');
15 |
16 | /**
17 | * Expose `Suite`.
18 | */
19 |
20 | exports = module.exports = Suite;
21 |
22 | /**
23 | * Create a new `Suite` with the given `title` and parent `Suite`. When a suite
24 | * with the same title is already present, that suite is returned to provide
25 | * nicer reporter and more flexible meta-testing.
26 | *
27 | * @memberof Mocha
28 | * @public
29 | * @api public
30 | * @param {Suite} parent
31 | * @param {string} title
32 | * @return {Suite}
33 | */
34 | exports.create = function(parent, title) {
35 | const suite = new Suite(title, parent.ctx);
36 | suite.parent = parent;
37 | title = suite.fullTitle();
38 | parent.addSuite(suite);
39 | return suite;
40 | };
41 |
42 | /**
43 | * Initialize a new `Suite` with the given `title` and `ctx`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
44 | *
45 | * @memberof Mocha
46 | * @public
47 | * @class
48 | * @param {string} title
49 | * @param {Context} parentContext
50 | */
51 | function Suite(title, parentContext) {
52 | if (!utils.isString(title)) {
53 | throw new Error(
54 | 'Suite `title` should be a "string" but "' +
55 | typeof title +
56 | '" was given instead.'
57 | );
58 | }
59 | this.title = title;
60 | function Context() {}
61 | Context.prototype = parentContext;
62 | this.ctx = new Context();
63 | this.suites = [];
64 | this.tests = [];
65 | this.pending = false;
66 | this._beforeEach = [];
67 | this._beforeAll = [];
68 | this._afterEach = [];
69 | this._afterAll = [];
70 | this.root = !title;
71 | this._timeout = 2000;
72 | this._enableTimeouts = true;
73 | this._slow = 75;
74 | this._bail = false;
75 | this._retries = -1;
76 | this._onlyTests = [];
77 | this._onlySuites = [];
78 | this.delayed = false;
79 | }
80 |
81 | /**
82 | * Inherit from `EventEmitter.prototype`.
83 | */
84 | inherits(Suite, EventEmitter);
85 |
86 | /**
87 | * Return a clone of this `Suite`.
88 | *
89 | * @api private
90 | * @return {Suite}
91 | */
92 | Suite.prototype.clone = function() {
93 | const suite = new Suite(this.title);
94 | debug('clone');
95 | suite.ctx = this.ctx;
96 | suite.timeout(this.timeout());
97 | suite.retries(this.retries());
98 | suite.enableTimeouts(this.enableTimeouts());
99 | suite.slow(this.slow());
100 | suite.bail(this.bail());
101 | return suite;
102 | };
103 |
104 | /**
105 | * Set or get timeout `ms` or short-hand such as "2s".
106 | *
107 | * @api private
108 | * @param {number|string} ms
109 | * @return {Suite|number} for chaining
110 | */
111 | Suite.prototype.timeout = function(ms) {
112 | if (!arguments.length) {
113 | return this._timeout;
114 | }
115 | if (ms.toString() === '0') {
116 | this._enableTimeouts = false;
117 | }
118 | if (typeof ms === 'string') {
119 | ms = milliseconds(ms);
120 | }
121 | debug('timeout %d', ms);
122 | this._timeout = parseInt(ms, 10);
123 | return this;
124 | };
125 |
126 | /**
127 | * Set or get number of times to retry a failed test.
128 | *
129 | * @api private
130 | * @param {number|string} n
131 | * @return {Suite|number} for chaining
132 | */
133 | Suite.prototype.retries = function(n) {
134 | if (!arguments.length) {
135 | return this._retries;
136 | }
137 | debug('retries %d', n);
138 | this._retries = parseInt(n, 10) || 0;
139 | return this;
140 | };
141 |
142 | /**
143 | * Set or get timeout to `enabled`.
144 | *
145 | * @api private
146 | * @param {boolean} enabled
147 | * @return {Suite|boolean} self or enabled
148 | */
149 | Suite.prototype.enableTimeouts = function(enabled) {
150 | if (!arguments.length) {
151 | return this._enableTimeouts;
152 | }
153 | debug('enableTimeouts %s', enabled);
154 | this._enableTimeouts = enabled;
155 | return this;
156 | };
157 |
158 | /**
159 | * Set or get slow `ms` or short-hand such as "2s".
160 | *
161 | * @api private
162 | * @param {number|string} ms
163 | * @return {Suite|number} for chaining
164 | */
165 | Suite.prototype.slow = function(ms) {
166 | if (!arguments.length) {
167 | return this._slow;
168 | }
169 | if (typeof ms === 'string') {
170 | ms = milliseconds(ms);
171 | }
172 | debug('slow %d', ms);
173 | this._slow = ms;
174 | return this;
175 | };
176 |
177 | /**
178 | * Set or get whether to bail after first error.
179 | *
180 | * @api private
181 | * @param {boolean} bail
182 | * @return {Suite|number} for chaining
183 | */
184 | Suite.prototype.bail = function(bail) {
185 | if (!arguments.length) {
186 | return this._bail;
187 | }
188 | debug('bail %s', bail);
189 | this._bail = bail;
190 | return this;
191 | };
192 |
193 | /**
194 | * Check if this suite or its parent suite is marked as pending.
195 | *
196 | * @api private
197 | */
198 | Suite.prototype.isPending = function() {
199 | return this.pending || (this.parent && this.parent.isPending());
200 | };
201 |
202 | /**
203 | * Generic hook-creator.
204 | * @private
205 | * @param {string} title - Title of hook
206 | * @param {Function} fn - Hook callback
207 | * @return {Hook} A new hook
208 | */
209 | Suite.prototype._createHook = function(title, fn) {
210 | const hook = new Hook(title, fn);
211 | hook.parent = this;
212 | hook.timeout(this.timeout());
213 | hook.retries(this.retries());
214 | hook.enableTimeouts(this.enableTimeouts());
215 | hook.slow(this.slow());
216 | hook.ctx = this.ctx;
217 | hook.file = this.file;
218 | return hook;
219 | };
220 |
221 | /**
222 | * Run `fn(test[, done])` before running tests.
223 | *
224 | * @api private
225 | * @param {string} title
226 | * @param {Function} fn
227 | * @return {Suite} for chaining
228 | */
229 | Suite.prototype.beforeAll = function(title, fn) {
230 | if (this.isPending()) {
231 | return this;
232 | }
233 | if (typeof title === 'function') {
234 | fn = title;
235 | title = fn.name;
236 | }
237 | title = '"before all" hook' + (title ? ': ' + title : '');
238 |
239 | const hook = this._createHook(title, fn);
240 | this._beforeAll.push(hook);
241 | this.emit('beforeAll', hook);
242 | return this;
243 | };
244 |
245 | /**
246 | * Run `fn(test[, done])` after running tests.
247 | *
248 | * @api private
249 | * @param {string} title
250 | * @param {Function} fn
251 | * @return {Suite} for chaining
252 | */
253 | Suite.prototype.afterAll = function(title, fn) {
254 | if (this.isPending()) {
255 | return this;
256 | }
257 | if (typeof title === 'function') {
258 | fn = title;
259 | title = fn.name;
260 | }
261 | title = '"after all" hook' + (title ? ': ' + title : '');
262 |
263 | const hook = this._createHook(title, fn);
264 | this._afterAll.push(hook);
265 | this.emit('afterAll', hook);
266 | return this;
267 | };
268 |
269 | /**
270 | * Run `fn(test[, done])` before each test case.
271 | *
272 | * @api private
273 | * @param {string} title
274 | * @param {Function} fn
275 | * @return {Suite} for chaining
276 | */
277 | Suite.prototype.beforeEach = function(title, fn) {
278 | if (this.isPending()) {
279 | return this;
280 | }
281 | if (typeof title === 'function') {
282 | fn = title;
283 | title = fn.name;
284 | }
285 | title = '"before each" hook' + (title ? ': ' + title : '');
286 |
287 | const hook = this._createHook(title, fn);
288 | this._beforeEach.push(hook);
289 | this.emit('beforeEach', hook);
290 | return this;
291 | };
292 |
293 | /**
294 | * Run `fn(test[, done])` after each test case.
295 | *
296 | * @api private
297 | * @param {string} title
298 | * @param {Function} fn
299 | * @return {Suite} for chaining
300 | */
301 | Suite.prototype.afterEach = function(title, fn) {
302 | if (this.isPending()) {
303 | return this;
304 | }
305 | if (typeof title === 'function') {
306 | fn = title;
307 | title = fn.name;
308 | }
309 | title = '"after each" hook' + (title ? ': ' + title : '');
310 |
311 | const hook = this._createHook(title, fn);
312 | this._afterEach.push(hook);
313 | this.emit('afterEach', hook);
314 | return this;
315 | };
316 |
317 | /**
318 | * Add a test `suite`.
319 | *
320 | * @api private
321 | * @param {Suite} suite
322 | * @return {Suite} for chaining
323 | */
324 | Suite.prototype.addSuite = function(suite) {
325 | suite.parent = this;
326 | suite.timeout(this.timeout());
327 | suite.retries(this.retries());
328 | suite.enableTimeouts(this.enableTimeouts());
329 | suite.slow(this.slow());
330 | suite.bail(this.bail());
331 | this.suites.push(suite);
332 | this.emit('suite', suite);
333 | return this;
334 | };
335 |
336 | /**
337 | * Add a `test` to this suite.
338 | *
339 | * @api private
340 | * @param {Test} test
341 | * @return {Suite} for chaining
342 | */
343 | Suite.prototype.addTest = function(test) {
344 | test.parent = this;
345 | test.timeout(this.timeout());
346 | test.retries(this.retries());
347 | test.enableTimeouts(this.enableTimeouts());
348 | test.slow(this.slow());
349 | test.ctx = this.ctx;
350 | this.tests.push(test);
351 | this.emit('test', test);
352 | return this;
353 | };
354 |
355 | /**
356 | * Return the full title generated by recursively concatenating the parent's
357 | * full title.
358 | *
359 | * @memberof Mocha.Suite
360 | * @public
361 | * @api public
362 | * @return {string}
363 | */
364 | Suite.prototype.fullTitle = function() {
365 | return this.titlePath().join(' ');
366 | };
367 |
368 | /**
369 | * Return the title path generated by recursively concatenating the parent's
370 | * title path.
371 | *
372 | * @memberof Mocha.Suite
373 | * @public
374 | * @api public
375 | * @return {string}
376 | */
377 | Suite.prototype.titlePath = function() {
378 | let result = [];
379 | if (this.parent) {
380 | result = result.concat(this.parent.titlePath());
381 | }
382 | if (!this.root) {
383 | result.push(this.title);
384 | }
385 | return result;
386 | };
387 |
388 | /**
389 | * Return the total number of tests.
390 | *
391 | * @memberof Mocha.Suite
392 | * @public
393 | * @api public
394 | * @return {number}
395 | */
396 | Suite.prototype.total = function() {
397 | return (
398 | this.suites.reduce(function(sum, suite) {
399 | return sum + suite.total();
400 | }, 0) + this.tests.length
401 | );
402 | };
403 |
404 | /**
405 | * Iterates through each suite recursively to find all tests. Applies a
406 | * function in the format `fn(test)`.
407 | *
408 | * @api private
409 | * @param {Function} fn
410 | * @return {Suite}
411 | */
412 | Suite.prototype.eachTest = function(fn) {
413 | this.tests.forEach(fn);
414 | this.suites.forEach(function(suite) {
415 | suite.eachTest(fn);
416 | });
417 | return this;
418 | };
419 |
420 | /**
421 | * This will run the root suite if we happen to be running in delayed mode.
422 | */
423 | Suite.prototype.run = function run() {
424 | if (this.root) {
425 | this.emit('run');
426 | }
427 | };
428 |
--------------------------------------------------------------------------------
/lib/mocha/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mocha
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/mocha/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Runnable = require('./runnable');
3 | const utils = require('./utils');
4 | const isString = utils.isString;
5 |
6 | module.exports = Test;
7 |
8 | /**
9 | * Initialize a new `Test` with the given `title` and callback `fn`.
10 | *
11 | * @class
12 | * @augments Runnable
13 | * @param {String} title
14 | * @param {Function} fn
15 | */
16 | function Test(title, fn) {
17 | if (!isString(title)) {
18 | throw new Error(
19 | 'Test `title` should be a "string" but "' +
20 | typeof title +
21 | '" was given instead.'
22 | );
23 | }
24 | Runnable.call(this, title, fn);
25 | this.pending = !fn;
26 | this.type = 'test';
27 | }
28 |
29 | /**
30 | * Inherit from `Runnable.prototype`.
31 | */
32 | utils.inherits(Test, Runnable);
33 |
34 | Test.prototype.clone = function() {
35 | const test = new Test(this.title, this.fn);
36 | test.timeout(this.timeout());
37 | test.slow(this.slow());
38 | test.enableTimeouts(this.enableTimeouts());
39 | test.retries(this.retries());
40 | test.currentRetry(this.currentRetry());
41 | test.globals(this.globals());
42 | test.parent = this.parent;
43 | test.file = this.file;
44 | test.ctx = this.ctx;
45 | return test;
46 | };
47 |
--------------------------------------------------------------------------------
/lib/mocha/utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | /**
5 | * @module
6 | */
7 |
8 | /**
9 | * Module dependencies.
10 | */
11 |
12 | const debug = require('debug')('mocha:watch');
13 | const fs = require('fs');
14 | const glob = require('glob');
15 | const path = require('path');
16 | const join = path.join;
17 | const he = require('he');
18 |
19 | /**
20 | * Ignored directories.
21 | */
22 |
23 | const ignore = ['node_modules', '.git'];
24 |
25 | exports.inherits = require('util').inherits;
26 |
27 | /**
28 | * Escape special characters in the given string of html.
29 | *
30 | * @api private
31 | * @param {string} html
32 | * @return {string}
33 | */
34 | exports.escape = function(html) {
35 | return he.encode(String(html), {useNamedReferences: false});
36 | };
37 |
38 | /**
39 | * Test if the given obj is type of string.
40 | *
41 | * @api private
42 | * @param {Object} obj
43 | * @return {boolean}
44 | */
45 | exports.isString = function(obj) {
46 | return typeof obj === 'string';
47 | };
48 |
49 | /**
50 | * Watch the given `files` for changes
51 | * and invoke `fn(file)` on modification.
52 | *
53 | * @api private
54 | * @param {Array} files
55 | * @param {Function} fn
56 | */
57 | exports.watch = function(files, fn) {
58 | const options = {interval: 100};
59 | files.forEach(function(file) {
60 | debug('file %s', file);
61 | fs.watchFile(file, options, function(curr, prev) {
62 | if (prev.mtime < curr.mtime) {
63 | fn(file);
64 | }
65 | });
66 | });
67 | };
68 |
69 | /**
70 | * Ignored files.
71 | *
72 | * @api private
73 | * @param {string} path
74 | * @return {boolean}
75 | */
76 | function ignored(path) {
77 | return !~ignore.indexOf(path);
78 | }
79 |
80 | /**
81 | * Lookup files in the given `dir`.
82 | *
83 | * @api private
84 | * @param {string} dir
85 | * @param {string[]} [ext=['.js']]
86 | * @param {Array} [ret=[]]
87 | * @return {Array}
88 | */
89 | exports.files = function(dir, ext, ret) {
90 | ret = ret || [];
91 | ext = ext || ['js'];
92 |
93 | const re = new RegExp('\\.(' + ext.join('|') + ')$');
94 |
95 | fs
96 | .readdirSync(dir)
97 | .filter(ignored)
98 | .forEach(function(path) {
99 | path = join(dir, path);
100 | if (fs.lstatSync(path).isDirectory()) {
101 | exports.files(path, ext, ret);
102 | } else if (path.match(re)) {
103 | ret.push(path);
104 | }
105 | });
106 |
107 | return ret;
108 | };
109 |
110 | /**
111 | * Compute a slug from the given `str`.
112 | *
113 | * @api private
114 | * @param {string} str
115 | * @return {string}
116 | */
117 | exports.slug = function(str) {
118 | return str
119 | .toLowerCase()
120 | .replace(/ +/g, '-')
121 | .replace(/[^-\w]/g, '');
122 | };
123 |
124 | /**
125 | * Strip the function definition from `str`, and re-indent for pre whitespace.
126 | *
127 | * @param {string} str
128 | * @return {string}
129 | */
130 | exports.clean = function(str) {
131 | str = str
132 | .replace(/\r\n?|[\n\u2028\u2029]/g, '\n')
133 | .replace(/^\uFEFF/, '')
134 | // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content
135 | .replace(
136 | /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/,
137 | '$1$2$3'
138 | );
139 |
140 | const spaces = str.match(/^\n?( *)/)[1].length;
141 | const tabs = str.match(/^\n?(\t*)/)[1].length;
142 | const re = new RegExp(
143 | '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}',
144 | 'gm'
145 | );
146 |
147 | str = str.replace(re, '');
148 |
149 | return str.trim();
150 | };
151 |
152 | /**
153 | * Parse the given `qs`.
154 | *
155 | * @api private
156 | * @param {string} qs
157 | * @return {Object}
158 | */
159 | exports.parseQuery = function(qs) {
160 | return qs
161 | .replace('?', '')
162 | .split('&')
163 | .reduce(function(obj, pair) {
164 | let i = pair.indexOf('=');
165 | const key = pair.slice(0, i);
166 | const val = pair.slice(++i);
167 |
168 | // Due to how the URLSearchParams API treats spaces
169 | obj[key] = decodeURIComponent(val.replace(/\+/g, '%20'));
170 |
171 | return obj;
172 | }, {});
173 | };
174 |
175 | /**
176 | * Highlight the given string of `js`.
177 | *
178 | * @api private
179 | * @param {string} js
180 | * @return {string}
181 | */
182 | function highlight(js) {
183 | return js
184 | .replace(//g, '>')
186 | .replace(/\/\/(.*)/gm, '')
187 | .replace(/('.*?')/gm, '$1')
188 | .replace(/(\d+\.\d+)/gm, '$1')
189 | .replace(/(\d+)/gm, '$1')
190 | .replace(
191 | /\bnew[ \t]+(\w+)/gm,
192 | 'new $1'
193 | )
194 | .replace(
195 | /\b(function|new|throw|return|var|if|else)\b/gm,
196 | '$1'
197 | );
198 | }
199 |
200 | /**
201 | * Highlight the contents of tag `name`.
202 | *
203 | * @api private
204 | * @param {string} name
205 | */
206 | exports.highlightTags = function(name) {
207 | const code = document.getElementById('mocha').getElementsByTagName(name);
208 | for (let i = 0, len = code.length; i < len; ++i) {
209 | code[i].innerHTML = highlight(code[i].innerHTML);
210 | }
211 | };
212 |
213 | /**
214 | * If a value could have properties, and has none, this function is called,
215 | * which returns a string representation of the empty value.
216 | *
217 | * Functions w/ no properties return `'[Function]'`
218 | * Arrays w/ length === 0 return `'[]'`
219 | * Objects w/ no properties return `'{}'`
220 | * All else: return result of `value.toString()`
221 | *
222 | * @api private
223 | * @param {*} value The value to inspect.
224 | * @param {string} typeHint The type of the value
225 | * @return {string}
226 | */
227 | function emptyRepresentation(value, typeHint) {
228 | switch (typeHint) {
229 | case 'function':
230 | return '[Function]';
231 | case 'object':
232 | return '{}';
233 | case 'array':
234 | return '[]';
235 | default:
236 | return value.toString();
237 | }
238 | }
239 |
240 | /**
241 | * Takes some variable and asks `Object.prototype.toString()` what it thinks it
242 | * is.
243 | *
244 | * @api private
245 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
246 | * @param {*} value The value to test.
247 | * @return {string} Computed type
248 | * @example
249 | * type({}) // 'object'
250 | * type([]) // 'array'
251 | * type(1) // 'number'
252 | * type(false) // 'boolean'
253 | * type(Infinity) // 'number'
254 | * type(null) // 'null'
255 | * type(new Date()) // 'date'
256 | * type(/foo/) // 'regexp'
257 | * type('type') // 'string'
258 | * type(global) // 'global'
259 | * type(new String('foo') // 'object'
260 | */
261 | const type = (exports.type = function type(value) {
262 | if (value === undefined) {
263 | return 'undefined';
264 | } else if (value === null) {
265 | return 'null';
266 | } else if (Buffer.isBuffer(value)) {
267 | return 'buffer';
268 | }
269 | return Object.prototype.toString
270 | .call(value)
271 | .replace(/^\[.+\s(.+?)]$/, '$1')
272 | .toLowerCase();
273 | });
274 |
275 | /**
276 | * Stringify `value`. Different behavior depending on type of value:
277 | *
278 | * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
279 | * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
280 | * - If `value` is an *empty* object, function, or array, return result of function
281 | * {@link emptyRepresentation}.
282 | * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
283 | * JSON.stringify().
284 | *
285 | * @api private
286 | * @see exports.type
287 | * @param {*} value
288 | * @return {string}
289 | */
290 | exports.stringify = function(value) {
291 | let typeHint = type(value);
292 |
293 | if (!~['object', 'array', 'function'].indexOf(typeHint)) {
294 | if (typeHint === 'buffer') {
295 | const json = Buffer.prototype.toJSON.call(value);
296 | // Based on the toJSON result
297 | return jsonStringify(
298 | json.data && json.type ? json.data : json,
299 | 2
300 | ).replace(/,(\n|$)/g, '$1');
301 | }
302 |
303 | // IE7/IE8 has a bizarre String constructor; needs to be coerced
304 | // into an array and back to obj.
305 | if (typeHint === 'string' && typeof value === 'object') {
306 | value = value.split('').reduce(function(acc, char, idx) {
307 | acc[idx] = char;
308 | return acc;
309 | }, {});
310 | typeHint = 'object';
311 | } else {
312 | return jsonStringify(value);
313 | }
314 | }
315 |
316 | for (const prop in value) {
317 | if (Object.prototype.hasOwnProperty.call(value, prop)) {
318 | return jsonStringify(
319 | exports.canonicalize(value, null, typeHint),
320 | 2
321 | ).replace(/,(\n|$)/g, '$1');
322 | }
323 | }
324 |
325 | return emptyRepresentation(value, typeHint);
326 | };
327 |
328 | /**
329 | * like JSON.stringify but more sense.
330 | *
331 | * @api private
332 | * @param {Object} object
333 | * @param {number=} spaces
334 | * @param {number=} depth
335 | * @return {*}
336 | */
337 | function jsonStringify(object, spaces, depth) {
338 | if (typeof spaces === 'undefined') {
339 | // primitive types
340 | return _stringify(object);
341 | }
342 |
343 | depth = depth || 1;
344 | let space = spaces * depth;
345 | let str = Array.isArray(object) ? '[' : '{';
346 | const end = Array.isArray(object) ? ']' : '}';
347 | let length =
348 | typeof object.length === 'number'
349 | ? object.length
350 | : Object.keys(object).length;
351 | // `.repeat()` polyfill
352 | function repeat(s, n) {
353 | return new Array(n).join(s);
354 | }
355 |
356 | function _stringify(val) {
357 | switch (type(val)) {
358 | case 'null':
359 | case 'undefined':
360 | val = '[' + val + ']';
361 | break;
362 | case 'array':
363 | case 'object':
364 | val = jsonStringify(val, spaces, depth + 1);
365 | break;
366 | case 'boolean':
367 | case 'regexp':
368 | case 'symbol':
369 | case 'number':
370 | val =
371 | val === 0 && 1 / val === -Infinity // `-0`
372 | ? '-0'
373 | : val.toString();
374 | break;
375 | case 'date':
376 | var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString();
377 | val = '[Date: ' + sDate + ']';
378 | break;
379 | case 'buffer':
380 | var json = val.toJSON();
381 | // Based on the toJSON result
382 | json = json.data && json.type ? json.data : json;
383 | val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
384 | break;
385 | default:
386 | val =
387 | val === '[Function]' || val === '[Circular]'
388 | ? val
389 | : JSON.stringify(val); // string
390 | }
391 | return val;
392 | }
393 |
394 | for (const i in object) {
395 | if (!Object.prototype.hasOwnProperty.call(object, i)) {
396 | continue; // not my business
397 | }
398 | --length;
399 | str +=
400 | '\n ' +
401 | repeat(' ', space) +
402 | (Array.isArray(object) ? '' : '"' + i + '": ') + // key
403 | _stringify(object[i]) + // value
404 | (length ? ',' : ''); // comma
405 | }
406 |
407 | return (
408 | str +
409 | // [], {}
410 | (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end)
411 | );
412 | }
413 |
414 | /**
415 | * Return a new Thing that has the keys in sorted order. Recursive.
416 | *
417 | * If the Thing...
418 | * - has already been seen, return string `'[Circular]'`
419 | * - is `undefined`, return string `'[undefined]'`
420 | * - is `null`, return value `null`
421 | * - is some other primitive, return the value
422 | * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
423 | * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
424 | * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
425 | *
426 | * @api private
427 | * @see {@link exports.stringify}
428 | * @param {*} value Thing to inspect. May or may not have properties.
429 | * @param {Array} [stack=[]] Stack of seen values
430 | * @param {string} [typeHint] Type hint
431 | * @return {(Object|Array|Function|string|undefined)}
432 | */
433 | exports.canonicalize = function canonicalize(value, stack, typeHint) {
434 | let canonicalizedObj;
435 | /* eslint-disable no-unused-vars */
436 | let prop;
437 | /* eslint-enable no-unused-vars */
438 | typeHint = typeHint || type(value);
439 | function withStack(value, fn) {
440 | stack.push(value);
441 | fn();
442 | stack.pop();
443 | }
444 |
445 | stack = stack || [];
446 |
447 | if (stack.indexOf(value) !== -1) {
448 | return '[Circular]';
449 | }
450 |
451 | switch (typeHint) {
452 | case 'undefined':
453 | case 'buffer':
454 | case 'null':
455 | canonicalizedObj = value;
456 | break;
457 | case 'array':
458 | withStack(value, function() {
459 | canonicalizedObj = value.map(function(item) {
460 | return exports.canonicalize(item, stack);
461 | });
462 | });
463 | break;
464 | case 'function':
465 | /* eslint-disable */
466 | for (prop in value) {
467 | canonicalizedObj = {};
468 | break;
469 | }
470 | /* eslint-enable guard-for-in */
471 | if (!canonicalizedObj) {
472 | canonicalizedObj = emptyRepresentation(value, typeHint);
473 | break;
474 | }
475 | /* falls through */
476 | case 'object':
477 | canonicalizedObj = canonicalizedObj || {};
478 | withStack(value, function() {
479 | Object.keys(value)
480 | .sort()
481 | .forEach(function(key) {
482 | canonicalizedObj[key] = exports.canonicalize(value[key], stack);
483 | });
484 | });
485 | break;
486 | case 'date':
487 | case 'number':
488 | case 'regexp':
489 | case 'boolean':
490 | case 'symbol':
491 | canonicalizedObj = value;
492 | break;
493 | default:
494 | canonicalizedObj = value + '';
495 | }
496 |
497 | return canonicalizedObj;
498 | };
499 |
500 | /**
501 | * Lookup file names at the given `path`.
502 | *
503 | * @memberof Mocha.utils
504 | * @public
505 | * @api public
506 | * @param {string} filepath Base path to start searching from.
507 | * @param {string[]} extensions File extensions to look for.
508 | * @param {boolean} recursive Whether or not to recurse into subdirectories.
509 | * @return {string[]} An array of paths.
510 | */
511 | exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
512 | let files = [];
513 |
514 | if (!fs.existsSync(filepath)) {
515 | if (fs.existsSync(filepath + '.js')) {
516 | filepath += '.js';
517 | } else {
518 | files = glob.sync(filepath);
519 | if (!files.length) {
520 | throw new Error("cannot resolve path (or pattern) '" + filepath + "'");
521 | }
522 | return files;
523 | }
524 | }
525 |
526 | try {
527 | const stat = fs.statSync(filepath);
528 | if (stat.isFile()) {
529 | return filepath;
530 | }
531 | } catch (err) {
532 | // ignore error
533 | return;
534 | }
535 |
536 | fs.readdirSync(filepath).forEach(function(file) {
537 | file = path.join(filepath, file);
538 | try {
539 | var stat = fs.statSync(file);
540 | if (stat.isDirectory()) {
541 | if (recursive) {
542 | files = files.concat(lookupFiles(file, extensions, recursive));
543 | }
544 | return;
545 | }
546 | } catch (err) {
547 | // ignore error
548 | return;
549 | }
550 | if (!extensions) {
551 | throw new Error(
552 | 'extensions parameter required when filepath is a directory'
553 | );
554 | }
555 | const re = new RegExp('\\.(?:' + extensions.join('|') + ')$');
556 | if (!stat.isFile() || !re.test(file) || path.basename(file)[0] === '.') {
557 | return;
558 | }
559 | files.push(file);
560 | });
561 |
562 | return files;
563 | };
564 |
565 | /**
566 | * Generate an undefined error with a message warning the user.
567 | *
568 | * @return {Error}
569 | */
570 |
571 | exports.undefinedError = function() {
572 | return new Error(
573 | 'Caught undefined error, did you throw without specifying what?'
574 | );
575 | };
576 |
577 | /**
578 | * Generate an undefined error if `err` is not defined.
579 | *
580 | * @param {Error} err
581 | * @return {Error}
582 | */
583 |
584 | exports.getError = function(err) {
585 | return err || exports.undefinedError();
586 | };
587 |
588 | /**
589 | * @summary
590 | * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
591 | * @description
592 | * When invoking this function you get a filter function that get the Error.stack as an input,
593 | * and return a prettify output.
594 | * (i.e: strip Mocha and internal node functions from stack trace).
595 | * @return {Function}
596 | */
597 | exports.stackTraceFilter = function() {
598 | // TODO: Replace with `process.browser`
599 | const is = typeof document === 'undefined' ? {node: true} : {browser: true};
600 | let slash = path.sep;
601 | let cwd;
602 | if (is.node) {
603 | cwd = process.cwd() + slash;
604 | } else {
605 | cwd = (typeof location === 'undefined'
606 | ? window.location
607 | : location
608 | ).href.replace(/\/[^/]*$/, '/');
609 | slash = '/';
610 | }
611 |
612 | function isMochaInternal(line) {
613 | return (
614 | ~line.indexOf('node_modules' + slash + 'mocha' + slash) ||
615 | ~line.indexOf('node_modules' + slash + 'mocha.js') ||
616 | ~line.indexOf('bower_components' + slash + 'mocha.js') ||
617 | ~line.indexOf(slash + 'mocha.js')
618 | );
619 | }
620 |
621 | function isNodeInternal(line) {
622 | return (
623 | ~line.indexOf('(timers.js:') ||
624 | ~line.indexOf('(events.js:') ||
625 | ~line.indexOf('(node.js:') ||
626 | ~line.indexOf('(module.js:') ||
627 | ~line.indexOf('GeneratorFunctionPrototype.next (native)') ||
628 | false
629 | );
630 | }
631 |
632 | return function(stack) {
633 | stack = stack.split('\n');
634 |
635 | stack = stack.reduce(function(list, line) {
636 | if (isMochaInternal(line)) {
637 | return list;
638 | }
639 |
640 | if (is.node && isNodeInternal(line)) {
641 | return list;
642 | }
643 |
644 | // Clean up cwd(absolute)
645 | if (/\(?.+:\d+:\d+\)?$/.test(line)) {
646 | line = line.replace('(' + cwd, '(');
647 | }
648 |
649 | list.push(line);
650 | return list;
651 | }, []);
652 |
653 | return stack.join('\n');
654 | };
655 | };
656 |
657 | /**
658 | * Crude, but effective.
659 | * @api
660 | * @param {*} value
661 | * @return {boolean} Whether or not `value` is a Promise
662 | */
663 | exports.isPromise = function isPromise(value) {
664 | return typeof value === 'object' && typeof value.then === 'function';
665 | };
666 |
667 | /**
668 | * It's a noop.
669 | * @api
670 | */
671 | exports.noop = function() {};
672 |
--------------------------------------------------------------------------------
/lib/playwright.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Playwright = require('macaca-playwright');
4 |
5 | const { setupCommands, exitForWait } = require('./commands');
6 |
7 | module.exports = async options => {
8 | const playwright = new Playwright();
9 | const opts = Object.assign({
10 | debug: true,
11 | redirectConsole: true,
12 | }, options);
13 | console.log(JSON.stringify(opts, null, 2));
14 |
15 | await playwright.startDevice(opts);
16 | await setupCommands(playwright);
17 | await playwright.get(options.url);
18 |
19 | const failCount = await exitForWait;
20 |
21 | if (failCount > 0) {
22 | throw new Error('test failed');
23 | }
24 | return true;
25 | };
26 |
--------------------------------------------------------------------------------
/lib/uitest.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = async options => {
4 | return await require('./playwright')(options);
5 | };
6 |
--------------------------------------------------------------------------------
/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * return a thenable function can be use for await and function to call
5 | * use
6 | * ```js
7 | * // demo
8 | * const fn = createThenableFunction();
9 | * setTimeout(fn, 1000, 1);
10 | * const ret = await fn; // ret === 1
11 | * ```
12 | * @return {{
13 | * (..args: any[]) => void;
14 | * then: cb => void;
15 | * }} thenable function
16 | */
17 | function createThenableFunction() {
18 | let recordCallback;
19 | let resultCache;
20 |
21 | const fn = (...args) => {
22 | resultCache = args;
23 | if (recordCallback) {
24 | recordCallback(...args);
25 | }
26 | };
27 |
28 | fn.then = callback => {
29 | if (resultCache) {
30 | callback(...resultCache);
31 | } else {
32 | recordCallback = callback;
33 | }
34 | };
35 |
36 | return fn;
37 | }
38 |
39 | module.exports = {
40 | createThenableFunction,
41 | };
42 |
--------------------------------------------------------------------------------
/mocha.entry.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* eslint no-unused-vars: off */
4 | /* eslint-env commonjs */
5 |
6 | /**
7 | * Shim process.stdout.
8 | */
9 | process.stdout = require('browser-stdout')({ level: false });
10 |
11 | const Mocha = require('./lib/mocha/mocha');
12 |
13 | /**
14 | * Create a Mocha instance.
15 | *
16 | * @return {undefined}
17 | */
18 |
19 | const mocha = new Mocha({ reporter: 'html' });
20 |
21 | /**
22 | * Save timer references to avoid Sinon interfering (see GH-237).
23 | */
24 |
25 | const Date = global.Date;
26 | const setTimeout = global.setTimeout;
27 | const setInterval = global.setInterval;
28 | const clearTimeout = global.clearTimeout;
29 | const clearInterval = global.clearInterval;
30 |
31 | const uncaughtExceptionHandlers = [];
32 |
33 | const originalOnerrorHandler = global.onerror;
34 |
35 | /**
36 | * Remove uncaughtException listener.
37 | * Revert to original onerror handler if previously defined.
38 | */
39 |
40 | process.removeListener = function(e, fn) {
41 | if (e === 'uncaughtException') {
42 | if (originalOnerrorHandler) {
43 | global.onerror = originalOnerrorHandler;
44 | } else {
45 | global.onerror = function() {};
46 | }
47 | const i = uncaughtExceptionHandlers.indexOf(fn);
48 | if (i !== -1) {
49 | uncaughtExceptionHandlers.splice(i, 1);
50 | }
51 | }
52 | };
53 |
54 | /**
55 | * Implements uncaughtException listener.
56 | */
57 |
58 | process.on = function(e, fn) {
59 | if (e === 'uncaughtException') {
60 | global.onerror = function(err, url, line) {
61 | fn(new Error(err + ' (' + url + ':' + line + ')'));
62 | return !mocha.allowUncaught;
63 | };
64 | uncaughtExceptionHandlers.push(fn);
65 | }
66 | };
67 |
68 | // The BDD UI is registered by default, but no UI will be functional in the
69 | // browser without an explicit call to the overridden `mocha.ui` (see below).
70 | // Ensure that this default UI does not expose its methods to the global scope.
71 | mocha.suite.removeAllListeners('pre-require');
72 |
73 | const immediateQueue = [];
74 | let immediateTimeout;
75 |
76 | function timeslice() {
77 | const immediateStart = new Date().getTime();
78 | while (immediateQueue.length && new Date().getTime() - immediateStart < 100) {
79 | immediateQueue.shift()();
80 | }
81 | if (immediateQueue.length) {
82 | immediateTimeout = setTimeout(timeslice, 0);
83 | } else {
84 | immediateTimeout = null;
85 | }
86 | }
87 |
88 | /**
89 | * High-performance override of Runner.immediately.
90 | */
91 |
92 | Mocha.Runner.immediately = function(callback) {
93 | immediateQueue.push(callback);
94 | if (!immediateTimeout) {
95 | immediateTimeout = setTimeout(timeslice, 0);
96 | }
97 | };
98 |
99 | /**
100 | * Function to allow assertion libraries to throw errors directly into mocha.
101 | * This is useful when running tests in a browser because window.onerror will
102 | * only receive the 'message' attribute of the Error.
103 | * @param {Error} err the error
104 | */
105 | mocha.throwError = function(err) {
106 | uncaughtExceptionHandlers.forEach(function(fn) {
107 | fn(err);
108 | });
109 | throw err;
110 | };
111 |
112 | /**
113 | * Override ui to ensure that the ui functions are initialized.
114 | * Normally this would happen in Mocha.prototype.loadFiles.
115 | */
116 |
117 | mocha.ui = function(ui) {
118 | Mocha.prototype.ui.call(this, ui);
119 | this.suite.emit('pre-require', global, null, this);
120 | return this;
121 | };
122 |
123 | /**
124 | * Setup mocha with the given setting options.
125 | */
126 |
127 | mocha.setup = function(opts) {
128 | if (typeof opts === 'string') {
129 | opts = { ui: opts };
130 | }
131 | for (const opt in opts) {
132 | if (opts.hasOwnProperty(opt)) {
133 | this[opt](opts[opt]);
134 | }
135 | }
136 | return this;
137 | };
138 |
139 | /**
140 | * Run mocha, returning the Runner.
141 | */
142 |
143 | mocha.run = function(fn) {
144 | const options = mocha.options;
145 | mocha.globals('location');
146 |
147 | const query = Mocha.utils.parseQuery(global.location.search || '');
148 | if (query.grep) {
149 | mocha.grep(query.grep);
150 | }
151 | if (query.fgrep) {
152 | mocha.fgrep(query.fgrep);
153 | }
154 | if (query.invert) {
155 | mocha.invert();
156 | }
157 |
158 | return Mocha.prototype.run.call(mocha, function(err) {
159 | // The DOM Document is not available in Web Workers.
160 | const document = global.document;
161 | if (
162 | document &&
163 | document.getElementById('mocha') &&
164 | options.noHighlighting !== true
165 | ) {
166 | Mocha.utils.highlightTags('code');
167 | }
168 | if (fn) {
169 | fn(err);
170 | }
171 | });
172 | };
173 |
174 | /**
175 | * Expose the process shim.
176 | * https://github.com/mochajs/mocha/pull/916
177 | */
178 |
179 | Mocha.process = process;
180 |
181 | /**
182 | * Expose mocha.
183 | */
184 |
185 | global.Mocha = Mocha;
186 | global.mocha = mocha;
187 |
188 | // this allows test/acceptance/required-tokens.js to pass; thus,
189 | // you can now do `const describe = require('mocha').describe` in a
190 | // browser context (assuming browserification). should fix #880
191 | module.exports = mocha;
192 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uitest",
3 | "version": "20.0.3",
4 | "description": "Run mocha in a browser environment.",
5 | "keywords": [
6 | "uitest"
7 | ],
8 | "main": "index.js",
9 | "files": [
10 | "lib/**/*.js",
11 | "*.js"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git@github.com:macacajs/uitest.git"
16 | },
17 | "dependencies": {
18 | "datahub-nodejs-sdk": "2",
19 | "macaca-playwright": "1"
20 | },
21 | "devDependencies": {
22 | "@babel/eslint-parser": "^7.18.2",
23 | "@rollup/plugin-commonjs": "^22.0.0",
24 | "@rollup/plugin-node-resolve": "^13.3.0",
25 | "@types/mocha": "^10.0.1",
26 | "chai": "^4.2.0",
27 | "eslint": "7",
28 | "eslint-config-airbnb": "^19.0.4",
29 | "eslint-config-egg": "^7.1.0",
30 | "eslint-config-prettier": "^4.1.0",
31 | "eslint-plugin-mocha": "^10.1.0",
32 | "git-contributor": "1",
33 | "husky": "^1.3.1",
34 | "ipv4": "1",
35 | "macaca-ecosystem": "*",
36 | "mocha": "*",
37 | "nyc": "^15.1.0",
38 | "rollup": "^2.75.7",
39 | "rollup-plugin-node-globals": "^1.4.0",
40 | "rollup-plugin-polyfill-node": "^0.9.0",
41 | "vuepress": "^1.5.2"
42 | },
43 | "scripts": {
44 | "prepublishOnly": "rollup -c",
45 | "test": "mocha",
46 | "cov": "nyc --reporter=lcov --reporter=text mocha",
47 | "lint": "eslint --fix .",
48 | "docs:dev": "vuepress dev docs",
49 | "docs:build": "vuepress build docs",
50 | "contributor": "git-contributor"
51 | },
52 | "husky": {
53 | "hooks": {
54 | "pre-commit": "npm run lint"
55 | }
56 | },
57 | "license": "MIT"
58 | }
59 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | extends: 'eslint-config-egg',
6 | parserOptions: {
7 | ecmaVersion: 2020,
8 | },
9 | plugins: [],
10 | rules: {
11 | 'valid-jsdoc': 0,
12 | 'no-script-url': 0,
13 | 'no-multi-spaces': 0,
14 | 'default-case': 0,
15 | 'no-case-declarations': 0,
16 | 'one-var-declaration-per-line': 0,
17 | 'no-restricted-syntax': 0,
18 | 'jsdoc/require-param': 0,
19 | 'jsdoc/check-param-names': 0,
20 | 'jsdoc/require-param-description': 0,
21 | 'arrow-parens': 0,
22 | 'prefer-promise-reject-errors': 0,
23 | 'no-control-regex': 0,
24 | 'no-use-before-define': 0,
25 | 'array-callback-return': 0,
26 | 'no-bitwise': 0,
27 | 'no-self-compare': 0,
28 | 'one-var': 0,
29 | 'no-trailing-spaces': [ 'warn', { skipBlankLines: true }],
30 | 'no-return-await': 0,
31 | },
32 | globals: {
33 | window: true,
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/README.md:
--------------------------------------------------------------------------------
1 | # gulp-uitest
2 |
3 | [![NPM version][npm-image]][npm-url]
4 | [![CI][CI-image]][CI-url]
5 | [![Test coverage][coveralls-image]][coveralls-url]
6 | [![node version][node-image]][node-url]
7 | [![npm download][download-image]][download-url]
8 |
9 | [npm-image]: https://img.shields.io/npm/v/gulp-uitest.svg
10 | [npm-url]: https://npmjs.org/package/gulp-uitest
11 | [CI-image]: https://github.com/macacajs/uitest/actions/workflows/packages-ci.yml/badge.svg
12 | [CI-url]: https://github.com/macacajs/uitest/actions/workflows/packages-ci.yml
13 | [coveralls-image]: https://img.shields.io/coveralls/xudafeng/gulp-uitest.svg
14 | [coveralls-url]: https://coveralls.io/r/xudafeng/gulp-uitest?branch=master
15 | [node-image]: https://img.shields.io/badge/node.js-%3E=_7-green.svg
16 | [node-url]: http://nodejs.org/download/
17 | [download-image]: https://img.shields.io/npm/dm/gulp-uitest.svg
18 | [download-url]: https://npmjs.org/package/gulp-uitest
19 |
20 | > gulp uitest
21 |
22 |
23 |
24 | ## Contributors
25 |
26 | |[
xudafeng](https://github.com/xudafeng)
|[
06wj](https://github.com/06wj)
|[
meowtec](https://github.com/meowtec)
|
27 | | :---: | :---: | :---: |
28 |
29 |
30 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed Mar 30 2022 12:08:37 GMT+0800`.
31 |
32 |
33 |
34 | ## Installment
35 |
36 | ```bash
37 | $ npm i gulp-uitest --save-dev
38 | ```
39 |
40 | ## Usage
41 |
42 | ```javascript
43 | const uitest = require('gulp-uitest');
44 |
45 | gulp.task('test', [], function() {
46 | return gulp
47 | .src('path/to/index.html')
48 | .pipe(uitest({
49 | width: 600,
50 | height: 480,
51 | hidpi: false,
52 | useContentSize: true,
53 | show: false
54 | }));
55 | });
56 | ```
57 |
58 | ## License
59 |
60 | The MIT License (MIT)
61 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./lib/gulp-uitest');
4 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/lib/gulp-uitest.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const uitest = require('uitest');
4 | const through = require('through2');
5 |
6 | module.exports = function(options) {
7 | return through.obj(function(file, env, cb) {
8 | uitest(Object.assign({
9 | url: 'file://' + file.path,
10 | }, options))
11 | .then(cb, cb);
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-uitest",
3 | "version": "17.0.0",
4 | "description": "gulp uitest",
5 | "keywords": [
6 | "gulp"
7 | ],
8 | "main": "index.js",
9 | "files": [
10 | "index.js",
11 | "lib/*.js"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/macacajs/uitest.git"
16 | },
17 | "dependencies": {
18 | "through2": "^2.0.1",
19 | "uitest": "17"
20 | },
21 | "devDependencies": {
22 | "eslint": "7",
23 | "eslint-config-egg": "^7.1.0",
24 | "eslint-plugin-mocha": "^10.1.0",
25 | "git-contributor": "1",
26 | "husky": "*",
27 | "mocha": "*",
28 | "nyc": "^15.0.0"
29 | },
30 | "husky": {
31 | "hooks": {
32 | "pre-commit": "npm run lint"
33 | }
34 | },
35 | "scripts": {
36 | "test": "nyc --reporter=lcov --reporter=text mocha",
37 | "lint": "eslint --fix .",
38 | "contributor": "git-contributor"
39 | },
40 | "license": "MIT"
41 | }
42 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/test/gulp-uitest.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const gulpUitest = require('..');
5 |
6 | describe('test', () => {
7 | it('should be ok', () => {
8 | assert(gulpUitest);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/packages/gulp-uitest/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --reporter spec
2 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const commonjs = require('@rollup/plugin-commonjs');
4 | const nodePolyfills = require('rollup-plugin-polyfill-node');
5 | const { nodeResolve } = require('@rollup/plugin-node-resolve');
6 | const nodeGlobal = require('rollup-plugin-node-globals');
7 |
8 | module.exports = {
9 | input: 'mocha.entry.js',
10 | context: 'globalThis',
11 | output: {
12 | file: 'mocha.js',
13 | name: 'mocha',
14 | format: 'umd',
15 | },
16 | plugins: [
17 | commonjs(),
18 | nodeGlobal(),
19 | nodePolyfills(),
20 | nodeResolve({ browser: true, preferBuiltins: true }),
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/test/case-sample/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'eslint-config-airbnb',
3 | env: {
4 | browser: true,
5 | es6: true,
6 | },
7 | parser: '@babel/eslint-parser',
8 | parserOptions: {
9 | ecmaVersion: 6,
10 | sourceType: 'module',
11 | requireConfigFile: false,
12 | },
13 | rules: {
14 | camelcase: 0,
15 | 'no-restricted-syntax': 0,
16 | 'no-plusplus': 0,
17 | 'no-underscore-dangle': 0,
18 | 'no-useless-escape': 0,
19 | 'no-unused-vars': ['error', { args: 'none' }],
20 | 'no-prototype-builtins': 0,
21 | 'max-len': 0,
22 | 'class-methods-use-this': 0,
23 | 'function-paren-newline': 0,
24 | 'no-await-in-loop': 0,
25 | 'comma-dangle': ['error', {
26 | arrays: 'always-multiline',
27 | objects: 'always-multiline',
28 | imports: 'always-multiline',
29 | exports: 'always-multiline',
30 | functions: 'ignore',
31 | }],
32 | 'no-param-reassign': 0,
33 | 'consistent-return': 0,
34 | 'object-curly-newline': ['error', { consistent: true, minProperties: 6 }],
35 | 'arrow-parens': 0,
36 | 'arrow-body-style': ['warn'],
37 | 'prefer-destructuring': ['error', {
38 | VariableDeclarator: {
39 | array: false,
40 | object: true,
41 | },
42 | AssignmentExpression: {
43 | array: false,
44 | object: false,
45 | },
46 | }],
47 | 'import/named': 0,
48 | 'import/no-extraneous-dependencies': 0,
49 | 'no-mixed-operators': 0,
50 | 'no-shadow': 0,
51 | 'no-useless-constructor': 0,
52 | 'no-return-assign': 0,
53 | 'prefer-rest-params': 0,
54 | 'no-restricted-globals': [0, 'location'],
55 | 'prefer-promise-reject-errors': 0,
56 | 'no-bitwise': 0,
57 | 'no-return-await': 0,
58 | 'import/prefer-default-export': 0,
59 | 'func-names': 0,
60 | 'no-use-before-define': 0,
61 | 'global-require': 0,
62 | 'no-else-return': 0,
63 | 'no-lonely-if': 0,
64 | 'guard-for-in': 0,
65 | 'one-var': 0,
66 | 'one-var-declaration-per-line': 0,
67 | 'no-console': 0,
68 | },
69 | globals: {
70 | assert: false,
71 | chai: false,
72 | _macaca_uitest: false,
73 | },
74 | };
75 |
--------------------------------------------------------------------------------
/test/case-sample/fileChoose.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/fileChoose.js', () => {
2 | afterEach(() => {
3 | document.querySelector('#testForKeyboard').innerHTML = '';
4 | });
5 |
6 | it('file choose should be ok', async () => {
7 | if (!_macaca_uitest.fileChooser) return;
8 | document.querySelector('#testForKeyboard').innerHTML = '';
9 | const inputDOM = document.querySelector('#input');
10 | assert(inputDOM);
11 | const rect = inputDOM.getBoundingClientRect();
12 | await Promise.all([
13 | _macaca_uitest.fileChooser('README.md'),
14 | _macaca_uitest.mouse.click(rect.left + 1, rect.top + 1),
15 | ]);
16 |
17 | assert.strictEqual(inputDOM.files[0].name, 'README.md');
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/case-sample/keyboard.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/keyboard.js', () => {
2 | afterEach(() => {
3 | document.querySelector('#testForKeyboard').innerHTML = '';
4 | });
5 |
6 | it('keyboard insertText should be ok', async () => {
7 | if (!_macaca_uitest.keyboard) return;
8 | document.querySelector('#testForKeyboard').innerHTML = '';
9 | const inputDOM = document.querySelector('#input');
10 | assert(inputDOM);
11 | inputDOM.focus();
12 | await _macaca_uitest.keyboard.insertText('😂');
13 | assert.equal('😂', inputDOM.value);
14 | });
15 |
16 | it('keyboard should be ok', async () => {
17 | if (!_macaca_uitest.keyboard) return;
18 | document.querySelector('#testForKeyboard').innerHTML = '';
19 | const inputDOM = document.querySelector('#input');
20 | assert(inputDOM);
21 | inputDOM.focus();
22 | await _macaca_uitest.keyboard.type('Hello World!');
23 | await _macaca_uitest.keyboard.press('ArrowLeft');
24 |
25 | await _macaca_uitest.keyboard.down('Shift');
26 | for (let i = 0; i < ' World'.length; i++) { await _macaca_uitest.keyboard.press('ArrowLeft'); }
27 | await _macaca_uitest.keyboard.up('Shift');
28 |
29 | await _macaca_uitest.keyboard.press('Backspace');
30 | assert.equal('Hello!', inputDOM.value);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/case-sample/mouse.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/mouse.js', () => {
2 | afterEach(() => {
3 | document.querySelector('#testForMouse').innerHTML = '';
4 | });
5 |
6 | it('mouse should be ok', async () => {
7 | if (!_macaca_uitest.mouse) return;
8 | document.querySelector('#testForMouse').innerHTML = '';
9 | const btn = document.querySelector('#btn');
10 | assert(btn);
11 | let result = '';
12 | btn.onmousemove = () => {
13 | result += '1';
14 | };
15 | btn.onmousedown = () => {
16 | result += '2';
17 | };
18 | btn.onmouseup = () => {
19 | result += '3';
20 | };
21 | btn.onclick = () => {
22 | result += '4';
23 | };
24 | btn.ondblclick = () => {
25 | result += '5';
26 | };
27 | // 1234123412342345
28 | const { x, y } = btn.getBoundingClientRect();
29 | await _macaca_uitest.mouse.move(x + 1, y + 1);
30 | await _macaca_uitest.mouse.down();
31 | await _macaca_uitest.mouse.up(); // 连续触发鼠标down和up 会触发click
32 | result += ',';
33 | await _macaca_uitest.mouse.click(x + 1, y + 1); // 触发mousemove mousedown mouseup click
34 | result += ',';
35 | await _macaca_uitest.mouse.dblclick(x + 2, y + 2); // 触发mousemove mousedown mouseup click mousedown mouseup dblclick
36 | assert.equal(result, '1234,1234,12342345');
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/case-sample/page.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/page.js', () => {
2 | it('page should be ok', async () => {
3 | if (!_macaca_uitest.page) return;
4 | const { page } = _macaca_uitest;
5 | const pageId = await page.newPage('https://google.com');
6 | let title = await page.exec(pageId, async () => document.title);
7 | assert.equal(title, 'Google');
8 | await page.exec(pageId, async () => {
9 | await _macaca_uitest.keyboard.insertText('😂');
10 | await _macaca_uitest.keyboard.press('Enter');
11 | return false;
12 | });
13 | await page.waitForEvent(pageId, 'load');
14 | title = await page.exec(pageId, async () => document.title);
15 | assert.equal(title, '😂 - Google 搜索');
16 | await page.close(pageId);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/case-sample/retries.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/sample2.js', () => {
2 | const collections = [];
3 |
4 | beforeEach(async () => {
5 | collections.push(1);
6 | });
7 |
8 | it.retries(3, 'retries should be work', async () => {
9 | if (collections.length !== 2) throw new Error('error');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/test/case-sample/sample1.js:
--------------------------------------------------------------------------------
1 | describe('test/case-sample/sample1.js', () => {
2 | afterEach(async function () {
3 | window.__coverage__ = { test: {} };
4 | await _macaca_uitest.saveScreenshot(this);
5 | });
6 |
7 | it('1should be true', done => {
8 | _macaca_uitest.screenshot('aa.png', () => {
9 | console.log('1diff');
10 | // throw Error();
11 | done();
12 | });
13 | });
14 |
15 | it('2should be true', done => {
16 | _macaca_uitest.screenshot('aa1.png', () => {
17 | console.log('2diff');
18 | done();
19 | });
20 | });
21 |
22 | it('3should be true', done => {
23 | _macaca_uitest.screenshot('aa123.png', () => {
24 | console.log('3diff');
25 | done();
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/test/case-sample/sample2.js:
--------------------------------------------------------------------------------
1 | const { assert } = chai;
2 |
3 | describe('test/case-sample/sample2.js', () => {
4 | before(async () => {
5 | const p = new Promise((resolve, reject) => {
6 | resolve('res');
7 | });
8 | const res = await p;
9 | console.log('before', res);
10 | });
11 |
12 | beforeEach(async () => {
13 | const p = new Promise((resolve, reject) => {
14 | resolve('res');
15 | });
16 | const res = await p;
17 | console.log('beforeEach', res);
18 | });
19 |
20 | after(() => {
21 | console.log('after');
22 | });
23 |
24 | it('should be ok', async () => {
25 | const p = new Promise((resolve, reject) => {
26 | resolve('res');
27 | });
28 | const res = await p;
29 | assert.equal(res, 'res');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/ci.sh:
--------------------------------------------------------------------------------
1 | Xvfb -ac -screen scrn 1280x2000x24 :9.0 &
2 |
3 | export DISPLAY=:9.0
4 |
5 | sleep 3
6 |
7 | npm run test
8 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | UITest
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --reporter spec
2 | --timeout 300000
3 |
--------------------------------------------------------------------------------
/test/uitest.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const uitest = require('..');
5 |
6 | describe('test/uitest.test.js', function() {
7 | this.timeout(60 * 1000);
8 | it('run test should be ok', async () => {
9 | const url = path.join(__dirname, 'index.html');
10 | await uitest({
11 | url: `file://${url}`,
12 | width: 800,
13 | height: 600,
14 | });
15 | });
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/test/uitls.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { createThenableFunction } = require('../lib/utils');
4 |
5 | describe('test/exit.test.js', () => {
6 | it('createThenableFunction should be ok', async () => {
7 | const randomRet = Math.random();
8 | const fn = createThenableFunction();
9 |
10 | setTimeout(() => {
11 | fn(randomRet);
12 | }, 10);
13 |
14 | const ret = await fn;
15 |
16 | if (ret !== randomRet) {
17 | throw new Error('failed');
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/uitest-mocha-shim.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | ; (function () {
5 |
6 | function getUUID() {
7 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
8 | .replace(/[xy]/g, function (c) {
9 | const r = Math.random() * 16 | 0;
10 | const v = c === 'x' ? r : (r & 0x3 | 0x8);
11 | return v.toString(16);
12 | });
13 | }
14 |
15 | if (!window.__execCommand) {
16 | window.__execCommand = async () => { };
17 | }
18 |
19 | window._macaca_uitest = {
20 | mouse: {
21 | click(x, y, opt) {
22 | return window.__execCommand('mouse', 'click', x, y, opt);
23 | },
24 | dblclick(x, y, opt) {
25 | return window.__execCommand('mouse', 'dblclick', x, y, opt);
26 | },
27 | move(x, y, opt) {
28 | return window.__execCommand('mouse', 'move', x, y, opt);
29 | },
30 | down(opt) {
31 | return window.__execCommand('mouse', 'down', opt);
32 | },
33 | up(opt) {
34 | return window.__execCommand('mouse', 'up', opt);
35 | },
36 | wheel(opt) {
37 | return window.__execCommand('mouse', 'wheel', x, y, opt);
38 | }
39 | },
40 | keyboard: {
41 | type(str, opt) {
42 | return window.__execCommand('keyboard', 'type', str, opt);
43 | },
44 | down(key) {
45 | return window.__execCommand('keyboard', 'down', key);
46 | },
47 | up(key) {
48 | return window.__execCommand('keyboard', 'up', key);
49 | },
50 | insertText(text) {
51 | return window.__execCommand('keyboard', 'insertText', text);
52 | },
53 | press(key, opt) {
54 | return window.__execCommand('keyboard', 'press', key, opt);
55 | }
56 | },
57 | page: {
58 | newPage(url) {
59 | return window.__execCommand('newPage', url);
60 | },
61 | close(pageId) {
62 | return window.__execCommand('closePage', pageId);
63 | },
64 | waitForSelector(pageId, selector) {
65 | return window.__execCommand('waitForSelector', pageId, selector);
66 | },
67 | waitForEvent(pageId, eventName) {
68 | return window.__execCommand('waitForEvent', pageId, eventName);
69 | },
70 | exec(pageId, func) {
71 | return window.__execCommand('runInPage', pageId, `(${func.toString()})()`);
72 | },
73 | },
74 | fileChooser(filePath) {
75 | return window.__execCommand('fileChooser', filePath);
76 | },
77 | switchScene() {
78 | const args = Array.prototype.slice.call(arguments);
79 | return window.__execCommand('switchScene', args[0]);
80 | },
81 |
82 | switchAllScenes() {
83 | const args = Array.prototype.slice.call(arguments);
84 | return window.__execCommand('switchAllScenes', args[0]);
85 | },
86 |
87 | saveVideo(context) {
88 | return new Promise((resolve, reject) => {
89 | window.__execCommand('getVideoName').then(name => {
90 | // 失败后直接返回
91 | if (!name) return resolve(name);
92 | const filePath = `./screenshots/${name}`;
93 | this.appendToContext(context, filePath);
94 | resolve(filePath);
95 | }).catch(e => resolve(null));
96 | });
97 | },
98 |
99 | saveScreenshot(context) {
100 | return new Promise((resolve, reject) => {
101 | const name = `${getUUID()}.png`;
102 | const filePath = `./screenshots/${name}`;
103 | this.appendToContext(context, filePath);
104 | resolve(this.screenshot(name));
105 | });
106 | },
107 |
108 | screenshot(name) {
109 | const filePath = `./reports/screenshots/${name}`;
110 | return window.__execCommand('screenshot', filePath);
111 | },
112 |
113 | appendToContext(mocha, content) {
114 | try {
115 | const test = mocha.currentTest || mocha.test;
116 | if (!test.context) {
117 | test.context = content;
118 | } else if (Array.isArray(test.context)) {
119 | test.context.push(content);
120 | } else {
121 | test.context = [test.context];
122 | test.context.push(content);
123 | }
124 | } catch (e) {
125 | console.log('error', e);
126 | }
127 | },
128 |
129 | setup(options) {
130 | let mochaOptions = options;
131 |
132 | mochaOptions = Object.assign({}, options, {
133 | reporter: 'spec',
134 | useColors: true
135 | });
136 |
137 | return mocha.setup(mochaOptions);
138 | },
139 |
140 | run() {
141 | return mocha.run(function (failedCount) {
142 | const __coverage__ = window.__coverage__;
143 |
144 | if (__coverage__) {
145 | window.__execCommand('saveCoverage', __coverage__);
146 | }
147 |
148 | // delay to exit
149 | setTimeout(() => {
150 | window.__execCommand('exit', { failedCount });
151 | }, 200);
152 | });
153 | }
154 | };
155 |
156 | })();
157 |
158 |
--------------------------------------------------------------------------------