Deployed the page to Github Pages.
This commit is contained in:
parent
1d79754e93
commit
2c89899458
62797 changed files with 6551425 additions and 15279 deletions
2579
node_modules/karma/CHANGELOG.md
generated
vendored
Normal file
2579
node_modules/karma/CHANGELOG.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
13
node_modules/karma/CODE_OF_CONDUCT.md
generated
vendored
Normal file
13
node_modules/karma/CODE_OF_CONDUCT.md
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.0.0, available at <https://www.contributor-covenant.org/version/1/0/0/code-of-conduct/>
|
14
node_modules/karma/ISSUE_TEMPLATE.md
generated
vendored
Normal file
14
node_modules/karma/ISSUE_TEMPLATE.md
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
### Expected behaviour
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
### Environment Details
|
||||
|
||||
- Karma version (output of `karma --version`):
|
||||
- Relevant part of your `karma.config.js` file
|
||||
|
||||
### Steps to reproduce the behaviour
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
20
node_modules/karma/LICENSE
generated
vendored
Normal file
20
node_modules/karma/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (C) 2011-2021 Google, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
138
node_modules/karma/README.md
generated
vendored
Normal file
138
node_modules/karma/README.md
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
# Karma
|
||||
[](https://github.com/karma-runner/karma) [](https://www.npmjs.com/package/karma) [](https://npmcharts.com/compare/karma?minimal=true)
|
||||
|
||||
[](https://codeclimate.com/github/karma-runner/karma) [](https://makeapullrequest.com/) [](https://david-dm.org/karma-runner/karma) [](https://david-dm.org/karma-runner/karma#info=devDependencies) [](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
A simple tool that allows you to execute JavaScript code in multiple
|
||||
_real_ browsers.
|
||||
|
||||
> The main purpose of Karma is to make your test-driven development easy,
|
||||
> fast, and fun.
|
||||
|
||||
## Karma is deprecated and is not accepting new features or general bug fixes.
|
||||
|
||||
The web testing space has evolved significantly in the [10+ years](https://testing.googleblog.com/2012/11/testacular-spectacular-test-runner-for.html) since Karma's creation. The web landscape looks very different today and new patterns and tools have emerged in the ecosystem. New test runners offer more performant alternatives, and Karma no longer provides clear unique value.
|
||||
|
||||
Based on the current state of the web testing ecosystem, we have made the hard decision to deprecate Karma.
|
||||
|
||||
We know Karma is used particularly commonly in the Angular ecosystem, so Angular is adding [Jest](https://jestjs.io/) and [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) support to provide a migration path off of Karma. See the [Angular blog](https://blog.angular.io/moving-angular-cli-to-jest-and-web-test-runner-ef85ef69ceca) for more details.
|
||||
|
||||
Critical security issues in Karma will still be triaged and fixed as necessary. This will continue until 12 months after Angular CLI's Web Test Runner support is marked stable.
|
||||
|
||||
For those outside Angular looking to migrate off Karma, both [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) and [`jasmine-browser-runner`](https://github.com/jasmine/jasmine-browser-runner) provide browser-based unit testing solutions which can be used as a direct alternative. [Jest](https://jestjs.io/) and [Vitest](https://vitest.dev/) also provide Node-based alternatives.
|
||||
|
||||
It has been incredible to see Karma's impact on the web testing ecosystem and we greatly appreciate the support of everyone who contributed to an awesome community. Keep testing. ✅
|
||||
|
||||
|
||||
## Help and Support
|
||||
|
||||
> For questions and support please use the mailing list or Gitter.
|
||||
> The issue tracker is for bug reports and feature discussions only.
|
||||
|
||||
* Obligatory [documentation]
|
||||
* Quick questions:
|
||||
[](https://gitter.im/karma-runner/karma)
|
||||
* Longer questions: [Mailing List]
|
||||
* Bug reports [Issue Tracker]
|
||||
* Everything less than 140 characters: [@JsKarma] on Twitter
|
||||
|
||||
|
||||
|
||||
## When should I use Karma?
|
||||
|
||||
* You want to test code in *real* browsers.
|
||||
* You want to test code in multiple browsers (desktop, mobile,
|
||||
tablets, etc.).
|
||||
* You want to execute your tests locally during development.
|
||||
* You want to execute your tests on a continuous integration server.
|
||||
* You want to execute your tests on every save.
|
||||
* You love your terminal.
|
||||
* You don't want your (testing) life to suck.
|
||||
* You want to use [Istanbul] to automagically generate coverage
|
||||
reports.
|
||||
* You want to use [RequireJS] for your source files.
|
||||
|
||||
|
||||
## But I still want to use \_insert testing library\_
|
||||
|
||||
Karma is not a testing framework, nor an assertion library.
|
||||
Karma just launches an HTTP server, and generates the test runner HTML file you probably already know from your favourite testing framework.
|
||||
So for testing purposes you can use pretty much anything you like. There are already plugins for most of the common testing frameworks:
|
||||
|
||||
* [Jasmine]
|
||||
* [Mocha]
|
||||
* [QUnit]
|
||||
* and [many others](https://www.npmjs.com/search?q=keywords:karma-adapter)
|
||||
|
||||
If you can't find an adapter for your favourite framework, don't worry and write your own.
|
||||
It's not that hard and we are here to help.
|
||||
|
||||
|
||||
## Which Browsers can I use?
|
||||
|
||||
All the major browsers are supported, if you want to know more see the
|
||||
[browsers] page.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
See [FAQ](https://karma-runner.github.io/latest/intro/faq.html).
|
||||
|
||||
|
||||
## I want to use it. Where do I sign?
|
||||
|
||||
You don't need to sign anything but here are some resources to help
|
||||
you to get started...
|
||||
|
||||
|
||||
### Obligatory Screencast.
|
||||
|
||||
Every serious project has a screencast, so here is ours. Just click
|
||||
[here] and let the show begin.
|
||||
|
||||
|
||||
### Installation.
|
||||
|
||||
See [installation](https://karma-runner.github.io/latest/intro/installation.html).
|
||||
|
||||
|
||||
### Using it.
|
||||
|
||||
See [configuration](https://karma-runner.github.io/latest/intro/configuration.html).
|
||||
|
||||
|
||||
## This is so great. I want to help.
|
||||
|
||||
Please, see
|
||||
[contributing](https://karma-runner.github.io/latest/dev/contributing.html).
|
||||
|
||||
|
||||
## Why did you create this?
|
||||
|
||||
Throughout the development of [AngularJS], we've been using [JSTD] for
|
||||
testing. I really think that JSTD is a great idea. Unfortunately, we
|
||||
had many problems with JSTD, so we decided to write our own test
|
||||
runner based on the same idea. We wanted a simple tool just for
|
||||
executing JavaScript tests that is both stable and fast. That's why we
|
||||
use the awesome [Socket.io] library and [Node.js].
|
||||
|
||||
|
||||
## My boss wants a license. So where is it?
|
||||
[MIT License](./LICENSE)
|
||||
|
||||
|
||||
[AngularJS]: https://angularjs.org/
|
||||
[JSTD]: https://code.google.com/p/js-test-driver/
|
||||
[Socket.io]: https://socket.io/
|
||||
[Node.js]: https://nodejs.org/
|
||||
[Jasmine]: https://github.com/karma-runner/karma-jasmine
|
||||
[Mocha]: https://github.com/karma-runner/karma-mocha
|
||||
[QUnit]: https://github.com/karma-runner/karma-qunit
|
||||
[here]: https://www.youtube.com/watch?v=MVw8N3hTfCI
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/karma-users
|
||||
[Issue Tracker]: https://github.com/karma-runner/karma/issues
|
||||
[@JsKarma]: https://twitter.com/JsKarma
|
||||
[RequireJS]: https://requirejs.org/
|
||||
[Istanbul]: https://github.com/gotwarlost/istanbul
|
||||
|
||||
[browsers]: https://karma-runner.github.io/latest/config/browsers.html
|
||||
[documentation]: https://karma-runner.github.io
|
11
node_modules/karma/SECURITY.md
generated
vendored
Normal file
11
node_modules/karma/SECURITY.md
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest version of the project are currently being supported with security updates.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security issue, please email karma-runner-eng+security@google.com
|
||||
with a description of the issue, the steps you took to create the issue,
|
||||
affected versions, and if known, mitigations for the issue.
|
3
node_modules/karma/bin/karma
generated
vendored
Executable file
3
node_modules/karma/bin/karma
generated
vendored
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/cli').run();
|
1
node_modules/karma/commitlint.config.js
generated
vendored
Normal file
1
node_modules/karma/commitlint.config.js
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = { extends: ['@commitlint/config-angular'] }
|
100
node_modules/karma/common/stringify.js
generated
vendored
Normal file
100
node_modules/karma/common/stringify.js
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
var serialize = null
|
||||
try {
|
||||
serialize = require('dom-serialize')
|
||||
} catch (e) {
|
||||
// Ignore failure on IE8
|
||||
}
|
||||
|
||||
var instanceOf = require('./util').instanceOf
|
||||
|
||||
function isNode (obj) {
|
||||
return (obj.tagName || obj.nodeName) && obj.nodeType
|
||||
}
|
||||
|
||||
function stringify (obj, depth) {
|
||||
if (depth === 0) {
|
||||
return '...'
|
||||
}
|
||||
|
||||
if (obj === null) {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
switch (typeof obj) {
|
||||
case 'symbol':
|
||||
return obj.toString()
|
||||
case 'string':
|
||||
return "'" + obj + "'"
|
||||
case 'undefined':
|
||||
return 'undefined'
|
||||
case 'function':
|
||||
try {
|
||||
// function abc(a, b, c) { /* code goes here */ }
|
||||
// -> function abc(a, b, c) { ... }
|
||||
return obj.toString().replace(/\{[\s\S]*\}/, '{ ... }')
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
// Support older browsers
|
||||
return 'function ' + (obj.name || '') + '() { ... }'
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
case 'boolean':
|
||||
return obj ? 'true' : 'false'
|
||||
case 'object':
|
||||
var strs = []
|
||||
if (instanceOf(obj, 'Array')) {
|
||||
strs.push('[')
|
||||
for (var i = 0, ii = obj.length; i < ii; i++) {
|
||||
if (i) {
|
||||
strs.push(', ')
|
||||
}
|
||||
strs.push(stringify(obj[i], depth - 1))
|
||||
}
|
||||
strs.push(']')
|
||||
} else if (instanceOf(obj, 'Date')) {
|
||||
return obj.toString()
|
||||
} else if (instanceOf(obj, 'Text')) {
|
||||
return obj.nodeValue
|
||||
} else if (instanceOf(obj, 'Comment')) {
|
||||
return '<!--' + obj.nodeValue + '-->'
|
||||
} else if (obj.outerHTML) {
|
||||
return obj.outerHTML
|
||||
} else if (isNode(obj)) {
|
||||
if (serialize) {
|
||||
return serialize(obj)
|
||||
} else {
|
||||
return 'Skipping stringify, no support for dom-serialize'
|
||||
}
|
||||
} else if (instanceOf(obj, 'Error')) {
|
||||
return obj.toString() + '\n' + obj.stack
|
||||
} else {
|
||||
var constructor = 'Object'
|
||||
if (obj.constructor && typeof obj.constructor === 'function') {
|
||||
constructor = obj.constructor.name
|
||||
}
|
||||
|
||||
strs.push(constructor)
|
||||
strs.push('{')
|
||||
var first = true
|
||||
for (var key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
if (first) {
|
||||
first = false
|
||||
} else {
|
||||
strs.push(', ')
|
||||
}
|
||||
|
||||
strs.push(key + ': ' + stringify(obj[key], depth - 1))
|
||||
}
|
||||
}
|
||||
strs.push('}')
|
||||
}
|
||||
return strs.join('')
|
||||
default:
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = stringify
|
32
node_modules/karma/common/util.js
generated
vendored
Normal file
32
node_modules/karma/common/util.js
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
exports.instanceOf = function (value, constructorName) {
|
||||
return Object.prototype.toString.apply(value) === '[object ' + constructorName + ']'
|
||||
}
|
||||
|
||||
exports.elm = function (id) {
|
||||
return document.getElementById(id)
|
||||
}
|
||||
|
||||
exports.generateId = function (prefix) {
|
||||
return prefix + Math.floor(Math.random() * 10000)
|
||||
}
|
||||
|
||||
exports.isUndefined = function (value) {
|
||||
return typeof value === 'undefined'
|
||||
}
|
||||
|
||||
exports.isDefined = function (value) {
|
||||
return !exports.isUndefined(value)
|
||||
}
|
||||
|
||||
exports.parseQueryParams = function (locationSearch) {
|
||||
var params = {}
|
||||
var pairs = locationSearch.slice(1).split('&')
|
||||
var keyValue
|
||||
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
keyValue = pairs[i].split('=')
|
||||
params[decodeURIComponent(keyValue[0])] = decodeURIComponent(keyValue[1])
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
71
node_modules/karma/config.tpl.coffee
generated
vendored
Normal file
71
node_modules/karma/config.tpl.coffee
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Karma configuration
|
||||
# Generated on %DATE%
|
||||
|
||||
module.exports = (config) ->
|
||||
config.set
|
||||
|
||||
# base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '%BASE_PATH%'
|
||||
|
||||
|
||||
# frameworks to use
|
||||
# available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
|
||||
frameworks: [%FRAMEWORKS%]
|
||||
|
||||
|
||||
# list of files / patterns to load in the browser
|
||||
files: [%FILES%
|
||||
]
|
||||
|
||||
|
||||
# list of files / patterns to exclude
|
||||
exclude: [%EXCLUDE%
|
||||
]
|
||||
|
||||
|
||||
# preprocess matching files before serving them to the browser
|
||||
# available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
|
||||
preprocessors: {%PREPROCESSORS%
|
||||
}
|
||||
|
||||
|
||||
# test results reporter to use
|
||||
# possible values: 'dots', 'progress'
|
||||
# available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
|
||||
reporters: ['progress']
|
||||
|
||||
|
||||
# web server port
|
||||
port: 9876
|
||||
|
||||
|
||||
# enable / disable colors in the output (reporters and logs)
|
||||
colors: true
|
||||
|
||||
|
||||
# level of logging
|
||||
# possible values:
|
||||
# - config.LOG_DISABLE
|
||||
# - config.LOG_ERROR
|
||||
# - config.LOG_WARN
|
||||
# - config.LOG_INFO
|
||||
# - config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO
|
||||
|
||||
|
||||
# enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: %AUTO_WATCH%
|
||||
|
||||
|
||||
# start these browsers
|
||||
# available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
|
||||
browsers: [%BROWSERS%]
|
||||
|
||||
|
||||
# Continuous Integration mode
|
||||
# if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
|
||||
# Concurrency level
|
||||
# how many browser instances should be started simultaneously
|
||||
concurrency: Infinity
|
68
node_modules/karma/config.tpl.js
generated
vendored
Normal file
68
node_modules/karma/config.tpl.js
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Karma configuration
|
||||
// Generated on %DATE%
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '%BASE_PATH%',
|
||||
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
|
||||
frameworks: [%FRAMEWORKS%],
|
||||
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [%FILES%
|
||||
],
|
||||
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [%EXCLUDE%
|
||||
],
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
|
||||
preprocessors: {%PREPROCESSORS%
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
|
||||
reporters: ['progress'],
|
||||
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: %AUTO_WATCH%,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
|
||||
browsers: [%BROWSERS%],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser instances should be started simultaneously
|
||||
concurrency: Infinity
|
||||
})
|
||||
}
|
70
node_modules/karma/config.tpl.ls
generated
vendored
Normal file
70
node_modules/karma/config.tpl.ls
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Karma configuration
|
||||
# Generated on %DATE%
|
||||
|
||||
module.exports = (config) ->
|
||||
config.set do
|
||||
|
||||
# base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '%BASE_PATH%'
|
||||
|
||||
|
||||
# frameworks to use
|
||||
# available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
|
||||
frameworks: [%FRAMEWORKS%]
|
||||
|
||||
|
||||
# list of files / patterns to load in the browser
|
||||
files: [%FILES%
|
||||
]
|
||||
|
||||
|
||||
# list of files / patterns to exclude
|
||||
exclude: [%EXCLUDE%
|
||||
]
|
||||
|
||||
|
||||
# preprocess matching files before serving them to the browser
|
||||
# available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
|
||||
preprocessors: {%PREPROCESSORS%
|
||||
}
|
||||
|
||||
# test results reporter to use
|
||||
# possible values: 'dots', 'progress'
|
||||
# available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
|
||||
reporters: ['progress']
|
||||
|
||||
|
||||
# web server port
|
||||
port: 9876
|
||||
|
||||
|
||||
# enable / disable colors in the output (reporters and logs)
|
||||
colors: true
|
||||
|
||||
|
||||
# level of logging
|
||||
# possible values:
|
||||
# - config.LOG_DISABLE
|
||||
# - config.LOG_ERROR
|
||||
# - config.LOG_WARN
|
||||
# - config.LOG_INFO
|
||||
# - config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO
|
||||
|
||||
|
||||
# enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: %AUTO_WATCH%
|
||||
|
||||
|
||||
# satart these browsers
|
||||
# available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
|
||||
browsers: [%BROWSERS%]
|
||||
|
||||
|
||||
# Continuous Integration mode
|
||||
# if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
|
||||
# Concurrency level
|
||||
# how many browser instances should be started simultaneously
|
||||
concurrency: Infinity
|
68
node_modules/karma/config.tpl.ts
generated
vendored
Normal file
68
node_modules/karma/config.tpl.ts
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Karma configuration
|
||||
// Generated on %DATE%
|
||||
|
||||
module.exports = (config) => {
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '%BASE_PATH%',
|
||||
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
|
||||
frameworks: [%FRAMEWORKS%],
|
||||
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [%FILES%
|
||||
],
|
||||
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [%EXCLUDE%
|
||||
],
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
|
||||
preprocessors: {%PREPROCESSORS%
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
|
||||
reporters: ['progress'],
|
||||
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: %AUTO_WATCH%,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
|
||||
browsers: [%BROWSERS%],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser instances should be started simultaneously
|
||||
concurrency: Infinity
|
||||
})
|
||||
}
|
161
node_modules/karma/context/karma.js
generated
vendored
Normal file
161
node_modules/karma/context/karma.js
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Load our dependencies
|
||||
var stringify = require('../common/stringify')
|
||||
|
||||
// Define our context Karma constructor
|
||||
function ContextKarma (callParentKarmaMethod) {
|
||||
// Define local variables
|
||||
var hasError = false
|
||||
var self = this
|
||||
var isLoaded = false
|
||||
|
||||
// Define our loggers
|
||||
// DEV: These are intentionally repeated in client and context
|
||||
this.log = function (type, args) {
|
||||
var values = []
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
values.push(this.stringify(args[i], 3))
|
||||
}
|
||||
|
||||
this.info({ log: values.join(', '), type: type })
|
||||
}
|
||||
|
||||
this.stringify = stringify
|
||||
|
||||
// Define our proxy error handler
|
||||
// DEV: We require one in our context to track `hasError`
|
||||
this.error = function () {
|
||||
hasError = true
|
||||
callParentKarmaMethod('error', [].slice.call(arguments))
|
||||
return false
|
||||
}
|
||||
|
||||
// Define our start handler
|
||||
function UNIMPLEMENTED_START () {
|
||||
this.error('You need to include some adapter that implements __karma__.start method!')
|
||||
}
|
||||
// all files loaded, let's start the execution
|
||||
this.loaded = function () {
|
||||
// has error -> cancel
|
||||
if (!hasError && !isLoaded) {
|
||||
isLoaded = true
|
||||
try {
|
||||
this.start(this.config)
|
||||
} catch (error) {
|
||||
this.error(error.stack || error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// remove reference to child iframe
|
||||
this.start = UNIMPLEMENTED_START
|
||||
}
|
||||
// supposed to be overridden by the context
|
||||
// TODO(vojta): support multiple callbacks (queue)
|
||||
this.start = UNIMPLEMENTED_START
|
||||
|
||||
// Define proxy methods
|
||||
// DEV: This is a closured `for` loop (same as a `forEach`) for IE support
|
||||
var proxyMethods = ['complete', 'info', 'result']
|
||||
for (var i = 0; i < proxyMethods.length; i++) {
|
||||
(function bindProxyMethod (methodName) {
|
||||
self[methodName] = function boundProxyMethod () {
|
||||
callParentKarmaMethod(methodName, [].slice.call(arguments))
|
||||
}
|
||||
}(proxyMethods[i]))
|
||||
}
|
||||
|
||||
// Define bindings for context window
|
||||
this.setupContext = function (contextWindow) {
|
||||
// If we clear the context after every run and we already had an error
|
||||
// then stop now. Otherwise, carry on.
|
||||
if (self.config.clearContext && hasError) {
|
||||
return
|
||||
}
|
||||
|
||||
// Perform window level bindings
|
||||
// DEV: We return `self.error` since we want to `return false` to ignore errors
|
||||
contextWindow.onerror = function () {
|
||||
return self.error.apply(self, arguments)
|
||||
}
|
||||
|
||||
contextWindow.onbeforeunload = function () {
|
||||
return self.error('Some of your tests did a full page reload!')
|
||||
}
|
||||
|
||||
contextWindow.dump = function () {
|
||||
self.log('dump', arguments)
|
||||
}
|
||||
|
||||
var _confirm = contextWindow.confirm
|
||||
var _prompt = contextWindow.prompt
|
||||
|
||||
contextWindow.alert = function (msg) {
|
||||
self.log('alert', [msg])
|
||||
}
|
||||
|
||||
contextWindow.confirm = function (msg) {
|
||||
self.log('confirm', [msg])
|
||||
return _confirm(msg)
|
||||
}
|
||||
|
||||
contextWindow.prompt = function (msg, defaultVal) {
|
||||
self.log('prompt', [msg, defaultVal])
|
||||
return _prompt(msg, defaultVal)
|
||||
}
|
||||
|
||||
// If we want to overload our console, then do it
|
||||
function getConsole (currentWindow) {
|
||||
return currentWindow.console || {
|
||||
log: function () {},
|
||||
info: function () {},
|
||||
warn: function () {},
|
||||
error: function () {},
|
||||
debug: function () {}
|
||||
}
|
||||
}
|
||||
if (self.config.captureConsole) {
|
||||
// patch the console
|
||||
var localConsole = contextWindow.console = getConsole(contextWindow)
|
||||
var logMethods = ['log', 'info', 'warn', 'error', 'debug']
|
||||
var patchConsoleMethod = function (method) {
|
||||
var orig = localConsole[method]
|
||||
if (!orig) {
|
||||
return
|
||||
}
|
||||
localConsole[method] = function () {
|
||||
self.log(method, arguments)
|
||||
try {
|
||||
return Function.prototype.apply.call(orig, localConsole, arguments)
|
||||
} catch (error) {
|
||||
self.log('warn', ['Console method ' + method + ' threw: ' + error])
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < logMethods.length; i++) {
|
||||
patchConsoleMethod(logMethods[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define call/proxy methods
|
||||
ContextKarma.getDirectCallParentKarmaMethod = function (parentWindow) {
|
||||
return function directCallParentKarmaMethod (method, args) {
|
||||
// If the method doesn't exist, then error out
|
||||
if (!parentWindow.karma[method]) {
|
||||
parentWindow.karma.error('Expected Karma method "' + method + '" to exist but it doesn\'t')
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, run our method
|
||||
parentWindow.karma[method].apply(parentWindow.karma, args)
|
||||
}
|
||||
}
|
||||
ContextKarma.getPostMessageCallParentKarmaMethod = function (parentWindow) {
|
||||
return function postMessageCallParentKarmaMethod (method, args) {
|
||||
parentWindow.postMessage({ __karmaMethod: method, __karmaArguments: args }, window.location.origin)
|
||||
}
|
||||
}
|
||||
|
||||
// Export our module
|
||||
module.exports = ContextKarma
|
21
node_modules/karma/context/main.js
generated
vendored
Normal file
21
node_modules/karma/context/main.js
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Load in our dependencies
|
||||
var ContextKarma = require('./karma')
|
||||
|
||||
// Resolve our parent window
|
||||
var parentWindow = window.opener || window.parent
|
||||
|
||||
// Define a remote call method for Karma
|
||||
var callParentKarmaMethod = ContextKarma.getDirectCallParentKarmaMethod(parentWindow)
|
||||
|
||||
// If we don't have access to the window, then use `postMessage`
|
||||
// DEV: In Electron, we don't have access to the parent window due to it being in a separate process
|
||||
// DEV: We avoid using this in Internet Explorer as they only support strings
|
||||
// https://caniuse.com/?search=postmessage
|
||||
var haveParentAccess = false
|
||||
try { haveParentAccess = !!parentWindow.window } catch (err) { /* Ignore errors (likely permission errors) */ }
|
||||
if (!haveParentAccess) {
|
||||
callParentKarmaMethod = ContextKarma.getPostMessageCallParentKarmaMethod(parentWindow)
|
||||
}
|
||||
|
||||
// Define a window-scoped Karma
|
||||
window.__karma__ = new ContextKarma(callParentKarmaMethod)
|
13
node_modules/karma/cucumber.js
generated
vendored
Normal file
13
node_modules/karma/cucumber.js
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Shared configuration for Cucumber.js tests.
|
||||
// See https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#profiles
|
||||
const options = [
|
||||
'--format progress',
|
||||
'--require test/e2e/support/env.js',
|
||||
'--require test/e2e/support/world.js',
|
||||
'--require test/e2e/step_definitions/core_steps.js',
|
||||
'--require test/e2e/step_definitions/hooks.js'
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
default: options.join(' ')
|
||||
}
|
271
node_modules/karma/lib/browser.js
generated
vendored
Normal file
271
node_modules/karma/lib/browser.js
generated
vendored
Normal file
|
@ -0,0 +1,271 @@
|
|||
'use strict'
|
||||
|
||||
const BrowserResult = require('./browser_result')
|
||||
const helper = require('./helper')
|
||||
const logger = require('./logger')
|
||||
|
||||
const CONNECTED = 'CONNECTED' // The browser is connected but not yet been commanded to execute tests.
|
||||
const CONFIGURING = 'CONFIGURING' // The browser has been told to execute tests; it is configuring before tests execution.
|
||||
const EXECUTING = 'EXECUTING' // The browser is executing the tests.
|
||||
const EXECUTING_DISCONNECTED = 'EXECUTING_DISCONNECTED' // The browser is executing the tests, but temporarily disconnect (waiting for socket reconnecting).
|
||||
const DISCONNECTED = 'DISCONNECTED' // The browser got completely disconnected (e.g. browser crash) and can be only restored with a restart of execution.
|
||||
|
||||
class Browser {
|
||||
constructor (id, fullName, collection, emitter, socket, timer, disconnectDelay,
|
||||
noActivityTimeout, singleRun, clientConfig) {
|
||||
this.id = id
|
||||
this.fullName = fullName
|
||||
this.name = helper.browserFullNameToShort(fullName)
|
||||
this.lastResult = new BrowserResult()
|
||||
this.disconnectsCount = 0
|
||||
this.activeSockets = [socket]
|
||||
this.noActivityTimeout = noActivityTimeout
|
||||
this.singleRun = singleRun
|
||||
this.clientConfig = clientConfig
|
||||
this.collection = collection
|
||||
this.emitter = emitter
|
||||
this.socket = socket
|
||||
this.timer = timer
|
||||
this.disconnectDelay = disconnectDelay
|
||||
|
||||
this.log = logger.create(this.name)
|
||||
|
||||
this.noActivityTimeoutId = null
|
||||
this.pendingDisconnect = null
|
||||
this.setState(CONNECTED)
|
||||
}
|
||||
|
||||
init () {
|
||||
this.log.info(`Connected on socket ${this.socket.id} with id ${this.id}`)
|
||||
|
||||
this.bindSocketEvents(this.socket)
|
||||
this.collection.add(this)
|
||||
this.emitter.emit('browser_register', this)
|
||||
}
|
||||
|
||||
setState (toState) {
|
||||
this.log.debug(`${this.state} -> ${toState}`)
|
||||
this.state = toState
|
||||
}
|
||||
|
||||
onKarmaError (error) {
|
||||
if (this.isNotConnected()) {
|
||||
this.lastResult.error = true
|
||||
}
|
||||
this.emitter.emit('browser_error', this, error)
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
onInfo (info) {
|
||||
if (helper.isDefined(info.dump)) {
|
||||
this.emitter.emit('browser_log', this, info.dump, 'dump')
|
||||
}
|
||||
|
||||
if (helper.isDefined(info.log)) {
|
||||
this.emitter.emit('browser_log', this, info.log, info.type)
|
||||
} else if (helper.isDefined(info.total)) {
|
||||
if (this.state === EXECUTING) {
|
||||
this.lastResult.total = info.total
|
||||
}
|
||||
} else if (!helper.isDefined(info.dump)) {
|
||||
this.emitter.emit('browser_info', this, info)
|
||||
}
|
||||
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
onStart (info) {
|
||||
if (info.total === null) {
|
||||
this.log.warn('Adapter did not report total number of specs.')
|
||||
}
|
||||
|
||||
this.lastResult = new BrowserResult(info.total)
|
||||
this.setState(EXECUTING)
|
||||
this.emitter.emit('browser_start', this, info)
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
onComplete (result) {
|
||||
if (this.isNotConnected()) {
|
||||
this.setState(CONNECTED)
|
||||
this.lastResult.totalTimeEnd()
|
||||
|
||||
this.emitter.emit('browsers_change', this.collection)
|
||||
this.emitter.emit('browser_complete', this, result)
|
||||
|
||||
this.clearNoActivityTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
onSocketDisconnect (reason, disconnectedSocket) {
|
||||
helper.arrayRemove(this.activeSockets, disconnectedSocket)
|
||||
if (this.activeSockets.length) {
|
||||
this.log.debug(`Disconnected ${disconnectedSocket.id}, still have ${this.getActiveSocketsIds()}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isConnected()) {
|
||||
this.disconnect(`Client disconnected from CONNECTED state (${reason})`)
|
||||
} else if ([CONFIGURING, EXECUTING].includes(this.state)) {
|
||||
this.log.debug(`Disconnected during run, waiting ${this.disconnectDelay}ms for reconnecting.`)
|
||||
this.setState(EXECUTING_DISCONNECTED)
|
||||
|
||||
this.pendingDisconnect = this.timer.setTimeout(() => {
|
||||
this.lastResult.totalTimeEnd()
|
||||
this.lastResult.disconnected = true
|
||||
this.disconnect(`reconnect failed before timeout of ${this.disconnectDelay}ms (${reason})`)
|
||||
this.emitter.emit('browser_complete', this)
|
||||
}, this.disconnectDelay)
|
||||
|
||||
this.clearNoActivityTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
reconnect (newSocket, clientSaysReconnect) {
|
||||
if (!clientSaysReconnect || this.state === DISCONNECTED) {
|
||||
this.log.info(`Disconnected browser returned on socket ${newSocket.id} with id ${this.id}.`)
|
||||
this.setState(CONNECTED)
|
||||
|
||||
// The disconnected browser is already part of the collection.
|
||||
// Update the collection view in the UI (header on client.html)
|
||||
this.emitter.emit('browsers_change', this.collection)
|
||||
// Notify the launcher
|
||||
this.emitter.emit('browser_register', this)
|
||||
// Execute tests if configured to do so.
|
||||
if (this.singleRun) {
|
||||
this.execute()
|
||||
}
|
||||
} else if (this.state === EXECUTING_DISCONNECTED) {
|
||||
this.log.debug('Lost socket connection, but browser continued to execute. Reconnected ' +
|
||||
`on socket ${newSocket.id}.`)
|
||||
this.setState(EXECUTING)
|
||||
} else if ([CONNECTED, CONFIGURING, EXECUTING].includes(this.state)) {
|
||||
this.log.debug(`Rebinding to new socket ${newSocket.id} (already have ` +
|
||||
`${this.getActiveSocketsIds()})`)
|
||||
}
|
||||
|
||||
if (!this.activeSockets.some((s) => s.id === newSocket.id)) {
|
||||
this.activeSockets.push(newSocket)
|
||||
this.bindSocketEvents(newSocket)
|
||||
}
|
||||
|
||||
if (this.pendingDisconnect) {
|
||||
this.timer.clearTimeout(this.pendingDisconnect)
|
||||
}
|
||||
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
onResult (result) {
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(this.onResult, this)
|
||||
} else if (this.isNotConnected()) {
|
||||
this.lastResult.add(result)
|
||||
this.emitter.emit('spec_complete', this, result)
|
||||
}
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
execute () {
|
||||
this.activeSockets.forEach((socket) => socket.emit('execute', this.clientConfig))
|
||||
this.setState(CONFIGURING)
|
||||
this.refreshNoActivityTimeout()
|
||||
}
|
||||
|
||||
getActiveSocketsIds () {
|
||||
return this.activeSockets.map((s) => s.id).join(', ')
|
||||
}
|
||||
|
||||
disconnect (reason) {
|
||||
this.log.warn(`Disconnected (${this.disconnectsCount} times) ${reason || ''}`)
|
||||
this.disconnectsCount++
|
||||
this.emitter.emit('browser_error', this, `Disconnected ${reason || ''}`)
|
||||
this.remove()
|
||||
}
|
||||
|
||||
remove () {
|
||||
this.setState(DISCONNECTED)
|
||||
this.collection.remove(this)
|
||||
}
|
||||
|
||||
refreshNoActivityTimeout () {
|
||||
if (this.noActivityTimeout) {
|
||||
this.clearNoActivityTimeout()
|
||||
|
||||
this.noActivityTimeoutId = this.timer.setTimeout(() => {
|
||||
this.lastResult.totalTimeEnd()
|
||||
this.lastResult.disconnected = true
|
||||
this.disconnect(`, because no message in ${this.noActivityTimeout} ms.`)
|
||||
this.emitter.emit('browser_complete', this)
|
||||
}, this.noActivityTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
clearNoActivityTimeout () {
|
||||
if (this.noActivityTimeout && this.noActivityTimeoutId) {
|
||||
this.timer.clearTimeout(this.noActivityTimeoutId)
|
||||
this.noActivityTimeoutId = null
|
||||
}
|
||||
}
|
||||
|
||||
bindSocketEvents (socket) {
|
||||
// TODO: check which of these events are actually emitted by socket
|
||||
socket.on('disconnect', (reason) => this.onSocketDisconnect(reason, socket))
|
||||
socket.on('start', (info) => this.onStart(info))
|
||||
socket.on('karma_error', (error) => this.onKarmaError(error))
|
||||
socket.on('complete', (result) => this.onComplete(result))
|
||||
socket.on('info', (info) => this.onInfo(info))
|
||||
socket.on('result', (result) => this.onResult(result))
|
||||
}
|
||||
|
||||
isConnected () {
|
||||
return this.state === CONNECTED
|
||||
}
|
||||
|
||||
isNotConnected () {
|
||||
return !this.isConnected()
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
isConnected: this.state === CONNECTED
|
||||
}
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.name
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return {
|
||||
id: this.id,
|
||||
fullName: this.fullName,
|
||||
name: this.name,
|
||||
state: this.state,
|
||||
lastResult: this.lastResult,
|
||||
disconnectsCount: this.disconnectsCount,
|
||||
noActivityTimeout: this.noActivityTimeout,
|
||||
disconnectDelay: this.disconnectDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Browser.factory = function (
|
||||
id, fullName, /* capturedBrowsers */ collection, emitter, socket, timer,
|
||||
/* config.browserDisconnectTimeout */ disconnectDelay,
|
||||
/* config.browserNoActivityTimeout */ noActivityTimeout,
|
||||
/* config.singleRun */ singleRun,
|
||||
/* config.client */ clientConfig) {
|
||||
return new Browser(id, fullName, collection, emitter, socket, timer,
|
||||
disconnectDelay, noActivityTimeout, singleRun, clientConfig)
|
||||
}
|
||||
|
||||
Browser.STATE_CONNECTED = CONNECTED
|
||||
Browser.STATE_CONFIGURING = CONFIGURING
|
||||
Browser.STATE_EXECUTING = EXECUTING
|
||||
Browser.STATE_EXECUTING_DISCONNECTED = EXECUTING_DISCONNECTED
|
||||
Browser.STATE_DISCONNECTED = DISCONNECTED
|
||||
|
||||
module.exports = Browser
|
103
node_modules/karma/lib/browser_collection.js
generated
vendored
Normal file
103
node_modules/karma/lib/browser_collection.js
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
'use strict'
|
||||
|
||||
const BrowserResult = require('./browser_result')
|
||||
const helper = require('./helper')
|
||||
|
||||
class BrowserCollection {
|
||||
constructor (emitter, browsers = []) {
|
||||
this.browsers = browsers
|
||||
this.emitter = emitter
|
||||
}
|
||||
|
||||
add (browser) {
|
||||
this.browsers.push(browser)
|
||||
this.emitter.emit('browsers_change', this)
|
||||
}
|
||||
|
||||
remove (browser) {
|
||||
if (helper.arrayRemove(this.browsers, browser)) {
|
||||
this.emitter.emit('browsers_change', this)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
getById (browserId) {
|
||||
return this.browsers.find((browser) => browser.id === browserId) || null
|
||||
}
|
||||
|
||||
getNonReady () {
|
||||
return this.browsers.filter((browser) => !browser.isConnected())
|
||||
}
|
||||
|
||||
areAllReady () {
|
||||
return this.browsers.every((browser) => browser.isConnected())
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return this.browsers.map((browser) => browser.serialize())
|
||||
}
|
||||
|
||||
calculateExitCode (results, singleRunBrowserNotCaptured, config) {
|
||||
config = config || {}
|
||||
if (results.disconnected || singleRunBrowserNotCaptured) {
|
||||
return 1
|
||||
}
|
||||
if (results.skipped && config.failOnSkippedTests) {
|
||||
return 1
|
||||
}
|
||||
if (results.success + results.failed === 0 && !!config.failOnEmptyTestSuite) {
|
||||
return 1
|
||||
}
|
||||
if (results.error) {
|
||||
return 1
|
||||
}
|
||||
if (config.failOnFailingTestSuite === false) {
|
||||
return 0 // Tests executed without infrastructure error, exit with 0 independent of test status.
|
||||
}
|
||||
return results.failed ? 1 : 0
|
||||
}
|
||||
|
||||
getResults (singleRunBrowserNotCaptured, config) {
|
||||
const results = { success: 0, failed: 0, skipped: 0, error: false, disconnected: false, exitCode: 0 }
|
||||
this.browsers.forEach((browser) => {
|
||||
results.success += browser.lastResult.success
|
||||
results.failed += browser.lastResult.failed
|
||||
results.skipped += browser.lastResult.skipped
|
||||
results.error = results.error || browser.lastResult.error
|
||||
results.disconnected = results.disconnected || browser.lastResult.disconnected
|
||||
})
|
||||
|
||||
results.exitCode = this.calculateExitCode(results, singleRunBrowserNotCaptured, config)
|
||||
return results
|
||||
}
|
||||
|
||||
clearResults () {
|
||||
this.browsers.forEach((browser) => {
|
||||
browser.lastResult = new BrowserResult()
|
||||
})
|
||||
}
|
||||
|
||||
clone () {
|
||||
return new BrowserCollection(this.emitter, this.browsers.slice())
|
||||
}
|
||||
|
||||
// Array APIs
|
||||
map (callback, context) {
|
||||
return this.browsers.map(callback, context)
|
||||
}
|
||||
|
||||
forEach (callback, context) {
|
||||
return this.browsers.forEach(callback, context)
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this.browsers.length
|
||||
}
|
||||
}
|
||||
|
||||
BrowserCollection.factory = function (emitter) {
|
||||
return new BrowserCollection(emitter)
|
||||
}
|
||||
|
||||
module.exports = BrowserCollection
|
30
node_modules/karma/lib/browser_result.js
generated
vendored
Normal file
30
node_modules/karma/lib/browser_result.js
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
'use strict'
|
||||
|
||||
class BrowserResult {
|
||||
constructor (total = 0) {
|
||||
this.startTime = Date.now()
|
||||
|
||||
this.total = total
|
||||
this.skipped = this.failed = this.success = 0
|
||||
this.netTime = this.totalTime = 0
|
||||
this.disconnected = this.error = false
|
||||
}
|
||||
|
||||
totalTimeEnd () {
|
||||
this.totalTime = Date.now() - this.startTime
|
||||
}
|
||||
|
||||
add (result) {
|
||||
if (result.skipped) {
|
||||
this.skipped++
|
||||
} else if (result.success) {
|
||||
this.success++
|
||||
} else {
|
||||
this.failed++
|
||||
}
|
||||
|
||||
this.netTime += result.time
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BrowserResult
|
331
node_modules/karma/lib/cli.js
generated
vendored
Normal file
331
node_modules/karma/lib/cli.js
generated
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const yargs = require('yargs')
|
||||
const fs = require('graceful-fs')
|
||||
|
||||
const Server = require('./server')
|
||||
const helper = require('./helper')
|
||||
const constant = require('./constants')
|
||||
const cfg = require('./config')
|
||||
|
||||
function processArgs (argv, options, fs, path) {
|
||||
Object.getOwnPropertyNames(argv).forEach(function (name) {
|
||||
let argumentValue = argv[name]
|
||||
if (name !== '_' && name !== '$0') {
|
||||
if (Array.isArray(argumentValue)) {
|
||||
argumentValue = argumentValue.pop() // If the same argument is defined multiple times, override.
|
||||
}
|
||||
options[helper.dashToCamel(name)] = argumentValue
|
||||
}
|
||||
})
|
||||
|
||||
if (helper.isString(options.autoWatch)) {
|
||||
options.autoWatch = options.autoWatch === 'true'
|
||||
}
|
||||
|
||||
if (helper.isString(options.colors)) {
|
||||
options.colors = options.colors === 'true'
|
||||
}
|
||||
|
||||
if (helper.isString(options.failOnEmptyTestSuite)) {
|
||||
options.failOnEmptyTestSuite = options.failOnEmptyTestSuite === 'true'
|
||||
}
|
||||
|
||||
if (helper.isString(options.failOnFailingTestSuite)) {
|
||||
options.failOnFailingTestSuite = options.failOnFailingTestSuite === 'true'
|
||||
}
|
||||
|
||||
if (helper.isString(options.formatError)) {
|
||||
let required
|
||||
try {
|
||||
required = require(options.formatError)
|
||||
} catch (err) {
|
||||
console.error('Could not require formatError: ' + options.formatError, err)
|
||||
}
|
||||
// support exports.formatError and module.exports = function
|
||||
options.formatError = required.formatError || required
|
||||
if (!helper.isFunction(options.formatError)) {
|
||||
console.error(`Format error must be a function, got: ${typeof options.formatError}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if (helper.isString(options.logLevel)) {
|
||||
const logConstant = constant['LOG_' + options.logLevel.toUpperCase()]
|
||||
if (helper.isDefined(logConstant)) {
|
||||
options.logLevel = logConstant
|
||||
} else {
|
||||
console.error('Log level must be one of disable, error, warn, info, or debug.')
|
||||
process.exit(1)
|
||||
}
|
||||
} else if (helper.isDefined(options.logLevel)) {
|
||||
console.error('Log level must be one of disable, error, warn, info, or debug.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (helper.isString(options.singleRun)) {
|
||||
options.singleRun = options.singleRun === 'true'
|
||||
}
|
||||
|
||||
if (helper.isString(options.browsers)) {
|
||||
options.browsers = options.browsers.split(',')
|
||||
}
|
||||
|
||||
if (options.reportSlowerThan === false) {
|
||||
options.reportSlowerThan = 0
|
||||
}
|
||||
|
||||
if (helper.isString(options.reporters)) {
|
||||
options.reporters = options.reporters.split(',')
|
||||
}
|
||||
|
||||
if (helper.isString(options.removedFiles)) {
|
||||
options.removedFiles = options.removedFiles.split(',')
|
||||
}
|
||||
|
||||
if (helper.isString(options.addedFiles)) {
|
||||
options.addedFiles = options.addedFiles.split(',')
|
||||
}
|
||||
|
||||
if (helper.isString(options.changedFiles)) {
|
||||
options.changedFiles = options.changedFiles.split(',')
|
||||
}
|
||||
|
||||
if (helper.isString(options.refresh)) {
|
||||
options.refresh = options.refresh === 'true'
|
||||
}
|
||||
|
||||
let configFile = argv.configFile
|
||||
|
||||
if (!configFile) {
|
||||
// default config file (if exists)
|
||||
if (fs.existsSync('./karma.conf.js')) {
|
||||
configFile = './karma.conf.js'
|
||||
} else if (fs.existsSync('./karma.conf.coffee')) {
|
||||
configFile = './karma.conf.coffee'
|
||||
} else if (fs.existsSync('./karma.conf.ts')) {
|
||||
configFile = './karma.conf.ts'
|
||||
} else if (fs.existsSync('./.config/karma.conf.js')) {
|
||||
configFile = './.config/karma.conf.js'
|
||||
} else if (fs.existsSync('./.config/karma.conf.coffee')) {
|
||||
configFile = './.config/karma.conf.coffee'
|
||||
} else if (fs.existsSync('./.config/karma.conf.ts')) {
|
||||
configFile = './.config/karma.conf.ts'
|
||||
}
|
||||
}
|
||||
|
||||
options.configFile = configFile ? path.resolve(configFile) : null
|
||||
|
||||
if (options.cmd === 'run') {
|
||||
options.clientArgs = parseClientArgs(process.argv)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
function parseClientArgs (argv) {
|
||||
// extract any args after '--' as clientArgs
|
||||
let clientArgs = []
|
||||
argv = argv.slice(2)
|
||||
const idx = argv.indexOf('--')
|
||||
if (idx !== -1) {
|
||||
clientArgs = argv.slice(idx + 1)
|
||||
}
|
||||
return clientArgs
|
||||
}
|
||||
|
||||
// return only args that occur before `--`
|
||||
function argsBeforeDoubleDash (argv) {
|
||||
const idx = argv.indexOf('--')
|
||||
|
||||
return idx === -1 ? argv : argv.slice(0, idx)
|
||||
}
|
||||
|
||||
function describeRoot () {
|
||||
return yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'Run --help with particular command to see its description and available options.\n\n' +
|
||||
'Usage:\n' +
|
||||
' $0 <command>')
|
||||
.command('init [configFile]', 'Initialize a config file.', describeInit)
|
||||
.command('start [configFile]', 'Start the server / do a single run.', describeStart)
|
||||
.command('run [configFile]', 'Trigger a test run.', describeRun)
|
||||
.command('stop [configFile]', 'Stop the server.', describeStop)
|
||||
.command('completion', 'Shell completion for karma.', describeCompletion)
|
||||
.demandCommand(1, 'Command not specified.')
|
||||
.strictCommands()
|
||||
.describe('help', 'Print usage and options.')
|
||||
.describe('version', 'Print current version.')
|
||||
}
|
||||
|
||||
function describeInit (yargs) {
|
||||
yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'INIT - Initialize a config file.\n\n' +
|
||||
'Usage:\n' +
|
||||
' $0 init [configFile]')
|
||||
.strictCommands(false)
|
||||
.version(false)
|
||||
.positional('configFile', {
|
||||
describe: 'Name of the generated Karma configuration file',
|
||||
type: 'string'
|
||||
})
|
||||
.describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
|
||||
.describe('colors', 'Use colors when reporting and printing logs.')
|
||||
.describe('no-colors', 'Do not use colors when reporting or printing logs.')
|
||||
}
|
||||
|
||||
function describeStart (yargs) {
|
||||
yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'START - Start the server / do a single run.\n\n' +
|
||||
'Usage:\n' +
|
||||
' $0 start [configFile]')
|
||||
.strictCommands(false)
|
||||
.version(false)
|
||||
.positional('configFile', {
|
||||
describe: 'Path to the Karma configuration file',
|
||||
type: 'string'
|
||||
})
|
||||
.describe('port', '<integer> Port where the server is running.')
|
||||
.describe('auto-watch', 'Auto watch source files and run on change.')
|
||||
.describe('detached', 'Detach the server.')
|
||||
.describe('no-auto-watch', 'Do not watch source files.')
|
||||
.describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
|
||||
.describe('colors', 'Use colors when reporting and printing logs.')
|
||||
.describe('no-colors', 'Do not use colors when reporting or printing logs.')
|
||||
.describe('reporters', 'List of reporters (available: dots, progress, junit, growl, coverage).')
|
||||
.describe('browsers', 'List of browsers to start (eg. --browsers Chrome,ChromeCanary,Firefox).')
|
||||
.describe('capture-timeout', '<integer> Kill browser if does not capture in given time [ms].')
|
||||
.describe('single-run', 'Run the test when browsers captured and exit.')
|
||||
.describe('no-single-run', 'Disable single-run.')
|
||||
.describe('report-slower-than', '<integer> Report tests that are slower than given time [ms].')
|
||||
.describe('fail-on-empty-test-suite', 'Fail on empty test suite.')
|
||||
.describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.')
|
||||
.describe('fail-on-failing-test-suite', 'Fail on failing test suite.')
|
||||
.describe('no-fail-on-failing-test-suite', 'Do not fail on failing test suite.')
|
||||
.option('format-error', {
|
||||
describe: 'A path to a file that exports the format function.',
|
||||
type: 'string'
|
||||
})
|
||||
}
|
||||
|
||||
function describeRun (yargs) {
|
||||
yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'RUN - Run the tests (requires running server).\n\n' +
|
||||
'Usage:\n' +
|
||||
' $0 run [configFile] [-- <clientArgs>]')
|
||||
.strictCommands(false)
|
||||
.version(false)
|
||||
.positional('configFile', {
|
||||
describe: 'Path to the Karma configuration file',
|
||||
type: 'string'
|
||||
})
|
||||
.describe('port', '<integer> Port where the server is listening.')
|
||||
.describe('no-refresh', 'Do not re-glob all the patterns.')
|
||||
.describe('fail-on-empty-test-suite', 'Fail on empty test suite.')
|
||||
.describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.')
|
||||
.describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
|
||||
.describe('colors', 'Use colors when reporting and printing logs.')
|
||||
.describe('no-colors', 'Do not use colors when reporting or printing logs.')
|
||||
.option('removed-files', {
|
||||
describe: 'Comma-separated paths to removed files. Useful when automatic file watching is disabled.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('changed-files', {
|
||||
describe: 'Comma-separated paths to changed files. Useful when automatic file watching is disabled.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('added-files', {
|
||||
describe: 'Comma-separated paths to added files. Useful when automatic file watching is disabled.',
|
||||
type: 'string'
|
||||
})
|
||||
}
|
||||
|
||||
function describeStop (yargs) {
|
||||
yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'STOP - Stop the server (requires running server).\n\n' +
|
||||
'Usage:\n' +
|
||||
' $0 stop [configFile]')
|
||||
.strictCommands(false)
|
||||
.version(false)
|
||||
.positional('configFile', {
|
||||
describe: 'Path to the Karma configuration file',
|
||||
type: 'string'
|
||||
})
|
||||
.describe('port', '<integer> Port where the server is listening.')
|
||||
.describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
|
||||
}
|
||||
|
||||
function describeCompletion (yargs) {
|
||||
yargs
|
||||
.usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
|
||||
'COMPLETION - Bash/ZSH completion for karma.\n\n' +
|
||||
'Installation:\n' +
|
||||
' $0 completion >> ~/.bashrc')
|
||||
.version(false)
|
||||
}
|
||||
|
||||
function printRunnerProgress (data) {
|
||||
process.stdout.write(data)
|
||||
}
|
||||
|
||||
exports.process = () => {
|
||||
const argv = describeRoot().parse(argsBeforeDoubleDash(process.argv.slice(2)))
|
||||
return processArgs(argv, { cmd: argv._.shift() }, fs, path)
|
||||
}
|
||||
|
||||
exports.run = async () => {
|
||||
const cliOptions = exports.process()
|
||||
const cmd = cliOptions.cmd // prevent config from changing the command
|
||||
const cmdNeedsConfig = cmd === 'start' || cmd === 'run' || cmd === 'stop'
|
||||
if (cmdNeedsConfig) {
|
||||
let config
|
||||
try {
|
||||
config = await cfg.parseConfig(
|
||||
cliOptions.configFile,
|
||||
cliOptions,
|
||||
{
|
||||
promiseConfig: true,
|
||||
throwErrors: true
|
||||
}
|
||||
)
|
||||
} catch (karmaConfigException) {
|
||||
// The reject reason/exception isn't used to log a message since
|
||||
// parseConfig already calls a configured logger method with an almost
|
||||
// identical message.
|
||||
|
||||
// The `run` function is a private application, not a public API. We don't
|
||||
// need to worry about process.exit vs throw vs promise rejection here.
|
||||
process.exit(1)
|
||||
}
|
||||
switch (cmd) {
|
||||
case 'start': {
|
||||
const server = new Server(config)
|
||||
await server.start()
|
||||
return server
|
||||
}
|
||||
case 'run':
|
||||
return require('./runner')
|
||||
.run(config)
|
||||
.on('progress', printRunnerProgress)
|
||||
case 'stop':
|
||||
return require('./stopper').stop(config)
|
||||
}
|
||||
} else {
|
||||
switch (cmd) {
|
||||
case 'init':
|
||||
return require('./init').init(cliOptions)
|
||||
case 'completion':
|
||||
return require('./completion').completion(cliOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// just for testing
|
||||
exports.processArgs = processArgs
|
||||
exports.parseClientArgs = parseClientArgs
|
||||
exports.argsBeforeDoubleDash = argsBeforeDoubleDash
|
126
node_modules/karma/lib/completion.js
generated
vendored
Normal file
126
node_modules/karma/lib/completion.js
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
'use strict'
|
||||
|
||||
const glob = require('glob')
|
||||
|
||||
const CUSTOM = ['']
|
||||
const BOOLEAN = false
|
||||
|
||||
const options = {
|
||||
start: {
|
||||
'--port': CUSTOM,
|
||||
'--auto-watch': BOOLEAN,
|
||||
'--no-auto-watch': BOOLEAN,
|
||||
'--log-level': ['disable', 'debug', 'info', 'warn', 'error'],
|
||||
'--colors': BOOLEAN,
|
||||
'--no-colors': BOOLEAN,
|
||||
'--reporters': ['dots', 'progress'],
|
||||
'--no-reporters': BOOLEAN,
|
||||
'--browsers': ['Chrome', 'ChromeHeadless', 'ChromeCanary', 'Firefox', 'PhantomJS', 'Safari', 'Opera'],
|
||||
'--no-browsers': BOOLEAN,
|
||||
'--single-run': BOOLEAN,
|
||||
'--no-single-run': BOOLEAN,
|
||||
'--help': BOOLEAN
|
||||
},
|
||||
init: {
|
||||
'--log-level': ['disable', 'debug', 'info', 'warn', 'error'],
|
||||
'--colors': BOOLEAN,
|
||||
'--no-colors': BOOLEAN,
|
||||
'--help': BOOLEAN
|
||||
},
|
||||
run: {
|
||||
'--no-refresh': BOOLEAN,
|
||||
'--port': CUSTOM,
|
||||
'--help': BOOLEAN
|
||||
}
|
||||
}
|
||||
|
||||
function opositeWord (word) {
|
||||
if (word.startsWith('-')) {
|
||||
return word.startsWith('--no-') ? `--${word.slice(5)}` : `--no-${word.slice(2)}`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function sendCompletion (possibleWords, env) {
|
||||
const regexp = new RegExp('^' + env.last)
|
||||
possibleWords
|
||||
.filter((word) => regexp.test(word) && !env.words.includes(word) && !env.words.includes(opositeWord(word)))
|
||||
.forEach((word) => {
|
||||
console.log(word)
|
||||
})
|
||||
}
|
||||
|
||||
function sendCompletionFiles (env) {
|
||||
glob(env.last + '*', { mark: true, nocase: true }, (err, files) => {
|
||||
if (err) return console.error(err)
|
||||
|
||||
if (files.length === 1 && files[0].endsWith('/')) {
|
||||
sendCompletionFiles({ last: files[0] })
|
||||
} else {
|
||||
console.log(files.join('\n'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function complete (env) {
|
||||
if (env.count === 1) {
|
||||
return sendCompletion(env.words[0].startsWith('-') ? ['--help', '--version'] : Object.keys(options), env)
|
||||
} else if (env.count === 2 && !env.words[1].startsWith('-')) {
|
||||
return sendCompletionFiles(env)
|
||||
}
|
||||
|
||||
const cmdOptions = options[env.words[0]]
|
||||
|
||||
if (cmdOptions) {
|
||||
if (cmdOptions[env.prev] === CUSTOM && env.last) {
|
||||
console.log(env.last)
|
||||
} else {
|
||||
return sendCompletion(cmdOptions[env.prev] || Object.keys(cmdOptions), env)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function completion () {
|
||||
if (process.argv[3] === '--') {
|
||||
return complete({
|
||||
words: process.argv.slice(5),
|
||||
count: parseInt(process.env.COMP_CWORD, 10),
|
||||
last: process.argv[process.argv.length - 1],
|
||||
prev: process.argv[process.argv.length - 2]
|
||||
})
|
||||
}
|
||||
|
||||
// just print out the karma-completion.sh
|
||||
const fs = require('graceful-fs')
|
||||
const path = require('path')
|
||||
|
||||
fs.readFile(path.resolve(__dirname, '../scripts/karma-completion.sh'), 'utf8', function (err, data) {
|
||||
if (err) return console.error(err)
|
||||
|
||||
process.stdout.write(data)
|
||||
process.stdout.on('error', function (error) {
|
||||
// Darwin is a real dick sometimes.
|
||||
//
|
||||
// This is necessary because the "source" or "." program in
|
||||
// bash on OS X closes its file argument before reading
|
||||
// from it, meaning that you get exactly 1 write, which will
|
||||
// work most of the time, and will always raise an EPIPE.
|
||||
//
|
||||
// Really, one should not be tossing away EPIPE errors, or any
|
||||
// errors, so casually. But, without this, `. <(karma completion)`
|
||||
// can never ever work on OS X.
|
||||
if (error.errno === 'EPIPE') {
|
||||
error = null
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
exports.completion = completion
|
||||
|
||||
// for testing
|
||||
exports.opositeWord = opositeWord
|
||||
exports.sendCompletion = sendCompletion
|
||||
exports.complete = complete
|
544
node_modules/karma/lib/config.js
generated
vendored
Normal file
544
node_modules/karma/lib/config.js
generated
vendored
Normal file
|
@ -0,0 +1,544 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const assert = require('assert')
|
||||
|
||||
const logger = require('./logger')
|
||||
const log = logger.create('config')
|
||||
const helper = require('./helper')
|
||||
const constant = require('./constants')
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
let COFFEE_SCRIPT_AVAILABLE = false
|
||||
let LIVE_SCRIPT_AVAILABLE = false
|
||||
let TYPE_SCRIPT_AVAILABLE = false
|
||||
|
||||
try {
|
||||
require('coffeescript').register()
|
||||
COFFEE_SCRIPT_AVAILABLE = true
|
||||
} catch {}
|
||||
|
||||
// LiveScript is required here to enable config files written in LiveScript.
|
||||
// It's not directly used in this file.
|
||||
try {
|
||||
require('LiveScript')
|
||||
LIVE_SCRIPT_AVAILABLE = true
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
require('ts-node')
|
||||
TYPE_SCRIPT_AVAILABLE = true
|
||||
} catch {}
|
||||
|
||||
class Pattern {
|
||||
constructor (pattern, served, included, watched, nocache, type, isBinary, integrity) {
|
||||
this.pattern = pattern
|
||||
this.served = helper.isDefined(served) ? served : true
|
||||
this.included = helper.isDefined(included) ? included : true
|
||||
this.watched = helper.isDefined(watched) ? watched : true
|
||||
this.nocache = helper.isDefined(nocache) ? nocache : false
|
||||
this.weight = helper.mmPatternWeight(pattern)
|
||||
this.type = type
|
||||
this.isBinary = isBinary
|
||||
this.integrity = integrity
|
||||
}
|
||||
|
||||
compare (other) {
|
||||
return helper.mmComparePatternWeights(this.weight, other.weight)
|
||||
}
|
||||
}
|
||||
|
||||
class UrlPattern extends Pattern {
|
||||
constructor (url, type, integrity) {
|
||||
super(url, false, true, false, false, type, undefined, integrity)
|
||||
}
|
||||
}
|
||||
|
||||
function createPatternObject (pattern) {
|
||||
if (pattern && helper.isString(pattern)) {
|
||||
return helper.isUrlAbsolute(pattern)
|
||||
? new UrlPattern(pattern)
|
||||
: new Pattern(pattern)
|
||||
} else if (helper.isObject(pattern) && pattern.pattern && helper.isString(pattern.pattern)) {
|
||||
return helper.isUrlAbsolute(pattern.pattern)
|
||||
? new UrlPattern(pattern.pattern, pattern.type, pattern.integrity)
|
||||
: new Pattern(pattern.pattern, pattern.served, pattern.included, pattern.watched, pattern.nocache, pattern.type)
|
||||
} else {
|
||||
log.warn(`Invalid pattern ${pattern}!\n\tExpected string or object with "pattern" property.`)
|
||||
return new Pattern(null, false, false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeUrl (url) {
|
||||
if (!url.startsWith('/')) {
|
||||
url = `/${url}`
|
||||
}
|
||||
|
||||
if (!url.endsWith('/')) {
|
||||
url = url + '/'
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function normalizeUrlRoot (urlRoot) {
|
||||
const normalizedUrlRoot = normalizeUrl(urlRoot)
|
||||
|
||||
if (normalizedUrlRoot !== urlRoot) {
|
||||
log.warn(`urlRoot normalized to "${normalizedUrlRoot}"`)
|
||||
}
|
||||
|
||||
return normalizedUrlRoot
|
||||
}
|
||||
|
||||
function normalizeProxyPath (proxyPath) {
|
||||
const normalizedProxyPath = normalizeUrl(proxyPath)
|
||||
|
||||
if (normalizedProxyPath !== proxyPath) {
|
||||
log.warn(`proxyPath normalized to "${normalizedProxyPath}"`)
|
||||
}
|
||||
|
||||
return normalizedProxyPath
|
||||
}
|
||||
|
||||
function normalizeConfig (config, configFilePath) {
|
||||
function basePathResolve (relativePath) {
|
||||
if (helper.isUrlAbsolute(relativePath)) {
|
||||
return relativePath
|
||||
} else if (helper.isDefined(config.basePath) && helper.isDefined(relativePath)) {
|
||||
return path.resolve(config.basePath, relativePath)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function createPatternMapper (resolve) {
|
||||
return (objectPattern) => Object.assign(objectPattern, { pattern: resolve(objectPattern.pattern) })
|
||||
}
|
||||
|
||||
if (helper.isString(configFilePath)) {
|
||||
config.basePath = path.resolve(path.dirname(configFilePath), config.basePath) // resolve basePath
|
||||
config.exclude.push(configFilePath) // always ignore the config file itself
|
||||
} else {
|
||||
config.basePath = path.resolve(config.basePath || '.')
|
||||
}
|
||||
|
||||
config.files = config.files.map(createPatternObject).map(createPatternMapper(basePathResolve))
|
||||
config.exclude = config.exclude.map(basePathResolve)
|
||||
config.customContextFile = config.customContextFile && basePathResolve(config.customContextFile)
|
||||
config.customDebugFile = config.customDebugFile && basePathResolve(config.customDebugFile)
|
||||
config.customClientContextFile = config.customClientContextFile && basePathResolve(config.customClientContextFile)
|
||||
|
||||
// normalize paths on windows
|
||||
config.basePath = helper.normalizeWinPath(config.basePath)
|
||||
config.files = config.files.map(createPatternMapper(helper.normalizeWinPath))
|
||||
config.exclude = config.exclude.map(helper.normalizeWinPath)
|
||||
config.customContextFile = helper.normalizeWinPath(config.customContextFile)
|
||||
config.customDebugFile = helper.normalizeWinPath(config.customDebugFile)
|
||||
config.customClientContextFile = helper.normalizeWinPath(config.customClientContextFile)
|
||||
|
||||
// normalize urlRoot
|
||||
config.urlRoot = normalizeUrlRoot(config.urlRoot)
|
||||
|
||||
// normalize and default upstream proxy settings if given
|
||||
if (config.upstreamProxy) {
|
||||
const proxy = config.upstreamProxy
|
||||
proxy.path = helper.isDefined(proxy.path) ? normalizeProxyPath(proxy.path) : '/'
|
||||
proxy.hostname = helper.isDefined(proxy.hostname) ? proxy.hostname : 'localhost'
|
||||
proxy.port = helper.isDefined(proxy.port) ? proxy.port : 9875
|
||||
|
||||
// force protocol to end with ':'
|
||||
proxy.protocol = (proxy.protocol || 'http').split(':')[0] + ':'
|
||||
if (proxy.protocol.match(/https?:/) === null) {
|
||||
log.warn(`"${proxy.protocol}" is not a supported upstream proxy protocol, defaulting to "http:"`)
|
||||
proxy.protocol = 'http:'
|
||||
}
|
||||
}
|
||||
|
||||
// force protocol to end with ':'
|
||||
config.protocol = (config.protocol || 'http').split(':')[0] + ':'
|
||||
if (config.protocol.match(/https?:/) === null) {
|
||||
log.warn(`"${config.protocol}" is not a supported protocol, defaulting to "http:"`)
|
||||
config.protocol = 'http:'
|
||||
}
|
||||
|
||||
if (config.proxies && Object.prototype.hasOwnProperty.call(config.proxies, config.urlRoot)) {
|
||||
log.warn(`"${config.urlRoot}" is proxied, you should probably change urlRoot to avoid conflicts`)
|
||||
}
|
||||
|
||||
if (config.singleRun && config.autoWatch) {
|
||||
log.debug('autoWatch set to false, because of singleRun')
|
||||
config.autoWatch = false
|
||||
}
|
||||
|
||||
if (config.runInParent) {
|
||||
log.debug('useIframe set to false, because using runInParent')
|
||||
config.useIframe = false
|
||||
}
|
||||
|
||||
if (!config.singleRun && !config.useIframe && config.runInParent) {
|
||||
log.debug('singleRun set to true, because using runInParent')
|
||||
config.singleRun = true
|
||||
}
|
||||
|
||||
if (helper.isString(config.reporters)) {
|
||||
config.reporters = config.reporters.split(',')
|
||||
}
|
||||
|
||||
if (config.client && config.client.args) {
|
||||
assert(Array.isArray(config.client.args), 'Invalid configuration: client.args must be an array of strings')
|
||||
}
|
||||
|
||||
if (config.browsers) {
|
||||
assert(Array.isArray(config.browsers), 'Invalid configuration: browsers option must be an array')
|
||||
}
|
||||
|
||||
if (config.formatError) {
|
||||
assert(helper.isFunction(config.formatError), 'Invalid configuration: formatError option must be a function.')
|
||||
}
|
||||
|
||||
if (config.processKillTimeout) {
|
||||
assert(helper.isNumber(config.processKillTimeout), 'Invalid configuration: processKillTimeout option must be a number.')
|
||||
}
|
||||
|
||||
if (config.browserSocketTimeout) {
|
||||
assert(helper.isNumber(config.browserSocketTimeout), 'Invalid configuration: browserSocketTimeout option must be a number.')
|
||||
}
|
||||
|
||||
if (config.pingTimeout) {
|
||||
assert(helper.isNumber(config.pingTimeout), 'Invalid configuration: pingTimeout option must be a number.')
|
||||
}
|
||||
|
||||
const defaultClient = config.defaultClient || {}
|
||||
Object.keys(defaultClient).forEach(function (key) {
|
||||
const option = config.client[key]
|
||||
config.client[key] = helper.isDefined(option) ? option : defaultClient[key]
|
||||
})
|
||||
|
||||
// normalize preprocessors
|
||||
const preprocessors = config.preprocessors || {}
|
||||
const normalizedPreprocessors = config.preprocessors = Object.create(null)
|
||||
|
||||
Object.keys(preprocessors).forEach(function (pattern) {
|
||||
const normalizedPattern = helper.normalizeWinPath(basePathResolve(pattern))
|
||||
|
||||
normalizedPreprocessors[normalizedPattern] = helper.isString(preprocessors[pattern])
|
||||
? [preprocessors[pattern]] : preprocessors[pattern]
|
||||
})
|
||||
|
||||
// define custom launchers/preprocessors/reporters - create a new plugin
|
||||
const module = Object.create(null)
|
||||
let hasSomeInlinedPlugin = false
|
||||
const types = ['launcher', 'preprocessor', 'reporter']
|
||||
|
||||
types.forEach(function (type) {
|
||||
const definitions = config[`custom${helper.ucFirst(type)}s`] || {}
|
||||
|
||||
Object.keys(definitions).forEach(function (name) {
|
||||
const definition = definitions[name]
|
||||
|
||||
if (!helper.isObject(definition)) {
|
||||
return log.warn(`Can not define ${type} ${name}. Definition has to be an object.`)
|
||||
}
|
||||
|
||||
if (!helper.isString(definition.base)) {
|
||||
return log.warn(`Can not define ${type} ${name}. Missing base ${type}.`)
|
||||
}
|
||||
|
||||
const token = type + ':' + definition.base
|
||||
const locals = {
|
||||
args: ['value', definition]
|
||||
}
|
||||
|
||||
module[type + ':' + name] = ['factory', function (injector) {
|
||||
const plugin = injector.createChild([locals], [token]).get(token)
|
||||
if (type === 'launcher' && helper.isDefined(definition.displayName)) {
|
||||
plugin.displayName = definition.displayName
|
||||
}
|
||||
return plugin
|
||||
}]
|
||||
hasSomeInlinedPlugin = true
|
||||
})
|
||||
})
|
||||
|
||||
if (hasSomeInlinedPlugin) {
|
||||
config.plugins.push(module)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
class Config {
|
||||
constructor () {
|
||||
this.LOG_DISABLE = constant.LOG_DISABLE
|
||||
this.LOG_ERROR = constant.LOG_ERROR
|
||||
this.LOG_WARN = constant.LOG_WARN
|
||||
this.LOG_INFO = constant.LOG_INFO
|
||||
this.LOG_DEBUG = constant.LOG_DEBUG
|
||||
|
||||
// DEFAULT CONFIG
|
||||
this.frameworks = []
|
||||
this.protocol = 'http:'
|
||||
this.port = constant.DEFAULT_PORT
|
||||
this.listenAddress = constant.DEFAULT_LISTEN_ADDR
|
||||
this.hostname = constant.DEFAULT_HOSTNAME
|
||||
this.httpsServerConfig = {}
|
||||
this.basePath = ''
|
||||
this.files = []
|
||||
this.browserConsoleLogOptions = {
|
||||
level: 'debug',
|
||||
format: '%b %T: %m',
|
||||
terminal: true
|
||||
}
|
||||
this.customContextFile = null
|
||||
this.customDebugFile = null
|
||||
this.customClientContextFile = null
|
||||
this.exclude = []
|
||||
this.logLevel = constant.LOG_INFO
|
||||
this.colors = true
|
||||
this.autoWatch = true
|
||||
this.autoWatchBatchDelay = 250
|
||||
this.restartOnFileChange = false
|
||||
this.usePolling = process.platform === 'linux'
|
||||
this.reporters = ['progress']
|
||||
this.singleRun = false
|
||||
this.browsers = []
|
||||
this.captureTimeout = 60000
|
||||
this.pingTimeout = 5000
|
||||
this.proxies = {}
|
||||
this.proxyValidateSSL = true
|
||||
this.preprocessors = {}
|
||||
this.preprocessor_priority = {}
|
||||
this.urlRoot = '/'
|
||||
this.upstreamProxy = undefined
|
||||
this.reportSlowerThan = 0
|
||||
this.loggers = [constant.CONSOLE_APPENDER]
|
||||
this.transports = ['polling', 'websocket']
|
||||
this.forceJSONP = false
|
||||
this.plugins = ['karma-*']
|
||||
this.defaultClient = this.client = {
|
||||
args: [],
|
||||
useIframe: true,
|
||||
runInParent: false,
|
||||
captureConsole: true,
|
||||
clearContext: true,
|
||||
allowedReturnUrlPatterns: ['^https?://']
|
||||
}
|
||||
this.browserDisconnectTimeout = 2000
|
||||
this.browserDisconnectTolerance = 0
|
||||
this.browserNoActivityTimeout = 30000
|
||||
this.processKillTimeout = 2000
|
||||
this.concurrency = Infinity
|
||||
this.failOnEmptyTestSuite = true
|
||||
this.retryLimit = 2
|
||||
this.detached = false
|
||||
this.crossOriginAttribute = true
|
||||
this.browserSocketTimeout = 20000
|
||||
}
|
||||
|
||||
set (newConfig) {
|
||||
_.mergeWith(this, newConfig, (obj, src) => {
|
||||
// Overwrite arrays to keep consistent with #283
|
||||
if (Array.isArray(src)) {
|
||||
return src
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const CONFIG_SYNTAX_HELP = ' module.exports = function(config) {\n' +
|
||||
' config.set({\n' +
|
||||
' // your config\n' +
|
||||
' });\n' +
|
||||
' };\n'
|
||||
|
||||
/**
|
||||
* Retrieve a parsed and finalized Karma `Config` instance. This `karmaConfig`
|
||||
* object may be used to configure public API methods such a `Server`,
|
||||
* `runner.run`, and `stopper.stop`.
|
||||
*
|
||||
* @param {?string} [configFilePath=null]
|
||||
* A string representing a file system path pointing to the config file
|
||||
* whose default export is a function that will be used to set Karma
|
||||
* configuration options. This function will be passed an instance of the
|
||||
* `Config` class as its first argument. If this option is not provided,
|
||||
* then only the options provided by the `cliOptions` argument will be
|
||||
* set.
|
||||
* @param {Object} cliOptions
|
||||
* An object whose values will take priority over options set in the
|
||||
* config file. The config object passed to function exported by the
|
||||
* config file will already have these options applied. Any changes the
|
||||
* config file makes to these options will effectively be ignored in the
|
||||
* final configuration.
|
||||
*
|
||||
* `cliOptions` all the same options as the config file and is applied
|
||||
* using the same `config.set()` method.
|
||||
* @param {Object} parseOptions
|
||||
* @param {boolean} [parseOptions.promiseConfig=false]
|
||||
* When `true`, a promise that resolves to a `Config` object will be
|
||||
* returned. This also allows the function exported by config files (if
|
||||
* provided) to be asynchronous by returning a promise. Resolving this
|
||||
* promise indicates that all async activity has completed. The resolution
|
||||
* value itself is ignored, all configuration must be done with
|
||||
* `config.set`.
|
||||
* @param {boolean} [parseOptions.throwErrors=false]
|
||||
* When `true`, process exiting on critical failures will be disabled. In
|
||||
* The error will be thrown as an exception. If
|
||||
* `parseOptions.promiseConfig` is also `true`, then the error will
|
||||
* instead be used as the promise's reject reason.
|
||||
* @returns {Config|Promise<Config>}
|
||||
*/
|
||||
function parseConfig (configFilePath, cliOptions, parseOptions) {
|
||||
const promiseConfig = parseOptions && parseOptions.promiseConfig === true
|
||||
const throwErrors = parseOptions && parseOptions.throwErrors === true
|
||||
const shouldSetupLoggerEarly = promiseConfig
|
||||
if (shouldSetupLoggerEarly) {
|
||||
// `setupFromConfig` provides defaults for `colors` and `logLevel`.
|
||||
// `setup` provides defaults for `appenders`
|
||||
// The first argument MUST BE an object
|
||||
logger.setupFromConfig({})
|
||||
}
|
||||
function fail () {
|
||||
log.error(...arguments)
|
||||
if (throwErrors) {
|
||||
const errorMessage = Array.from(arguments).join(' ')
|
||||
const err = new Error(errorMessage)
|
||||
if (promiseConfig) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
throw err
|
||||
} else {
|
||||
const warningMessage =
|
||||
'The `parseConfig()` function historically called `process.exit(1)`' +
|
||||
' when it failed. This behavior is now deprecated and function will' +
|
||||
' throw an error in the next major release. To suppress this warning' +
|
||||
' pass `throwErrors: true` as a third argument to opt-in into the new' +
|
||||
' behavior and adjust your code to respond to the exception' +
|
||||
' accordingly.' +
|
||||
' Example: `parseConfig(path, cliOptions, { throwErrors: true })`'
|
||||
log.warn(warningMessage)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
let configModule
|
||||
if (configFilePath) {
|
||||
try {
|
||||
if (path.extname(configFilePath) === '.ts' && TYPE_SCRIPT_AVAILABLE) {
|
||||
require('ts-node').register()
|
||||
}
|
||||
configModule = require(configFilePath)
|
||||
if (typeof configModule === 'object' && typeof configModule.default !== 'undefined') {
|
||||
configModule = configModule.default
|
||||
}
|
||||
} catch (e) {
|
||||
const extension = path.extname(configFilePath)
|
||||
if (extension === '.coffee' && !COFFEE_SCRIPT_AVAILABLE) {
|
||||
log.error('You need to install CoffeeScript.\n npm install coffeescript --save-dev')
|
||||
} else if (extension === '.ls' && !LIVE_SCRIPT_AVAILABLE) {
|
||||
log.error('You need to install LiveScript.\n npm install LiveScript --save-dev')
|
||||
} else if (extension === '.ts' && !TYPE_SCRIPT_AVAILABLE) {
|
||||
log.error('You need to install TypeScript.\n npm install typescript ts-node --save-dev')
|
||||
}
|
||||
return fail('Error in config file!\n ' + e.stack || e)
|
||||
}
|
||||
if (!helper.isFunction(configModule)) {
|
||||
return fail('Config file must export a function!\n' + CONFIG_SYNTAX_HELP)
|
||||
}
|
||||
} else {
|
||||
configModule = () => {} // if no config file path is passed, we define a dummy config module.
|
||||
}
|
||||
|
||||
const config = new Config()
|
||||
|
||||
// save and reset hostname and listenAddress so we can detect if the user
|
||||
// changed them
|
||||
const defaultHostname = config.hostname
|
||||
config.hostname = null
|
||||
const defaultListenAddress = config.listenAddress
|
||||
config.listenAddress = null
|
||||
|
||||
// add the user's configuration in
|
||||
config.set(cliOptions)
|
||||
|
||||
let configModuleReturn
|
||||
try {
|
||||
configModuleReturn = configModule(config)
|
||||
} catch (e) {
|
||||
return fail('Error in config file!\n', e)
|
||||
}
|
||||
function finalizeConfig (config) {
|
||||
// merge the config from config file and cliOptions (precedence)
|
||||
config.set(cliOptions)
|
||||
|
||||
// if the user changed listenAddress, but didn't set a hostname, warn them
|
||||
if (config.hostname === null && config.listenAddress !== null) {
|
||||
log.warn(`ListenAddress was set to ${config.listenAddress} but hostname was left as the default: ` +
|
||||
`${defaultHostname}. If your browsers fail to connect, consider changing the hostname option.`)
|
||||
}
|
||||
// restore values that weren't overwritten by the user
|
||||
if (config.hostname === null) {
|
||||
config.hostname = defaultHostname
|
||||
}
|
||||
if (config.listenAddress === null) {
|
||||
config.listenAddress = defaultListenAddress
|
||||
}
|
||||
|
||||
// configure the logger as soon as we can
|
||||
logger.setup(config.logLevel, config.colors, config.loggers)
|
||||
|
||||
log.debug(configFilePath ? `Loading config ${configFilePath}` : 'No config file specified.')
|
||||
|
||||
return normalizeConfig(config, configFilePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value is a function or (non-null) object that has a `then` method.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @see {@link https://promisesaplus.com/}
|
||||
*/
|
||||
const returnIsThenable = (
|
||||
(
|
||||
(configModuleReturn != null && typeof configModuleReturn === 'object') ||
|
||||
typeof configModuleReturn === 'function'
|
||||
) && typeof configModuleReturn.then === 'function'
|
||||
)
|
||||
if (returnIsThenable) {
|
||||
if (promiseConfig !== true) {
|
||||
const errorMessage =
|
||||
'The `parseOptions.promiseConfig` option must be set to `true` to ' +
|
||||
'enable promise return values from configuration files. ' +
|
||||
'Example: `parseConfig(path, cliOptions, { promiseConfig: true })`'
|
||||
return fail(errorMessage)
|
||||
}
|
||||
return configModuleReturn.then(
|
||||
function onKarmaConfigModuleFulfilled (/* ignoredResolutionValue */) {
|
||||
return finalizeConfig(config)
|
||||
},
|
||||
function onKarmaConfigModuleRejected (reason) {
|
||||
return fail('Error in config file!\n', reason)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (promiseConfig) {
|
||||
try {
|
||||
return Promise.resolve(finalizeConfig(config))
|
||||
} catch (exception) {
|
||||
return Promise.reject(exception)
|
||||
}
|
||||
} else {
|
||||
return finalizeConfig(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
exports.parseConfig = parseConfig
|
||||
exports.Pattern = Pattern
|
||||
exports.createPatternObject = createPatternObject
|
||||
exports.Config = Config
|
43
node_modules/karma/lib/constants.js
generated
vendored
Normal file
43
node_modules/karma/lib/constants.js
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
const path = require('path')
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '/../package.json')).toString())
|
||||
|
||||
exports.VERSION = pkg.version
|
||||
|
||||
exports.DEFAULT_PORT = process.env.PORT || 9876
|
||||
exports.DEFAULT_HOSTNAME = process.env.IP || 'localhost'
|
||||
exports.DEFAULT_LISTEN_ADDR = process.env.LISTEN_ADDR || '0.0.0.0'
|
||||
|
||||
// log levels
|
||||
exports.LOG_DISABLE = 'OFF'
|
||||
exports.LOG_ERROR = 'ERROR'
|
||||
exports.LOG_WARN = 'WARN'
|
||||
exports.LOG_INFO = 'INFO'
|
||||
exports.LOG_DEBUG = 'DEBUG'
|
||||
exports.LOG_LOG = 'LOG'
|
||||
exports.LOG_PRIORITIES = [
|
||||
exports.LOG_DISABLE,
|
||||
exports.LOG_ERROR,
|
||||
exports.LOG_WARN,
|
||||
exports.LOG_LOG,
|
||||
exports.LOG_INFO,
|
||||
exports.LOG_DEBUG
|
||||
]
|
||||
|
||||
// Default patterns for the pattern layout.
|
||||
exports.COLOR_PATTERN = '%[%d{DATETIME}:%p [%c]: %]%m'
|
||||
exports.NO_COLOR_PATTERN = '%d{DATETIME}:%p [%c]: %m'
|
||||
|
||||
// Default console appender
|
||||
exports.CONSOLE_APPENDER = {
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: exports.COLOR_PATTERN
|
||||
}
|
||||
}
|
||||
|
||||
exports.EXIT_CODE = '\x1FEXIT'
|
11
node_modules/karma/lib/detached.js
generated
vendored
Normal file
11
node_modules/karma/lib/detached.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
const Server = require('./server')
|
||||
const configurationFile = process.argv[2]
|
||||
const fileContents = fs.readFileSync(configurationFile, 'utf-8')
|
||||
fs.unlink(configurationFile, function () {})
|
||||
const data = JSON.parse(fileContents)
|
||||
const server = new Server(data)
|
||||
server.start(data)
|
33
node_modules/karma/lib/emitter_wrapper.js
generated
vendored
Normal file
33
node_modules/karma/lib/emitter_wrapper.js
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
'use strict'
|
||||
|
||||
class EmitterWrapper {
|
||||
constructor (emitter) {
|
||||
this.listeners = {}
|
||||
this.emitter = emitter
|
||||
}
|
||||
|
||||
addListener (event, listener) {
|
||||
this.emitter.addListener(event, listener)
|
||||
this.listeners[event] = this.listeners[event] || []
|
||||
this.listeners[event].push(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
on (event, listener) {
|
||||
return this.addListener(event, listener)
|
||||
}
|
||||
|
||||
removeAllListeners (event) {
|
||||
const events = event ? [event] : Object.keys(this.listeners)
|
||||
events.forEach((event) => {
|
||||
this.listeners[event].forEach((listener) => {
|
||||
this.emitter.removeListener(event, listener)
|
||||
})
|
||||
delete this.listeners[event]
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmitterWrapper
|
67
node_modules/karma/lib/events.js
generated
vendored
Normal file
67
node_modules/karma/lib/events.js
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
'use strict'
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const helper = require('./helper')
|
||||
|
||||
function bufferEvents (emitter, eventsToBuffer) {
|
||||
const listeners = []
|
||||
const eventsToReply = []
|
||||
|
||||
function genericListener () {
|
||||
eventsToReply.push(Array.from(arguments))
|
||||
}
|
||||
|
||||
eventsToBuffer.forEach((eventName) => {
|
||||
const listener = genericListener.bind(null, eventName)
|
||||
listeners.push(listener)
|
||||
emitter.on(eventName, listener)
|
||||
})
|
||||
|
||||
return function () {
|
||||
listeners.forEach((listener, i) => {
|
||||
emitter.removeListener(eventsToBuffer[i], listener)
|
||||
})
|
||||
|
||||
eventsToReply.forEach((args) => {
|
||||
EventEmitter.prototype.emit.apply(emitter, args)
|
||||
})
|
||||
|
||||
listeners.length = 0
|
||||
eventsToReply.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
class KarmaEventEmitter extends EventEmitter {
|
||||
bind (object) {
|
||||
for (const method in object) {
|
||||
if (method.startsWith('on') && helper.isFunction(object[method])) {
|
||||
this.on(helper.camelToSnake(method.slice(2)), function () {
|
||||
// We do not use an arrow function here, to supply the caller as this.
|
||||
object[method].apply(object, Array.from(arguments).concat(this))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitAsync (name) {
|
||||
// TODO(vojta): allow passing args
|
||||
// TODO(vojta): ignore/throw if listener call done() multiple times
|
||||
let pending = this.listeners(name).length
|
||||
const deferred = helper.defer()
|
||||
|
||||
this.emit(name, () => {
|
||||
if (!--pending) {
|
||||
deferred.resolve()
|
||||
}
|
||||
})
|
||||
|
||||
if (!pending) {
|
||||
deferred.resolve()
|
||||
}
|
||||
|
||||
return deferred.promise
|
||||
}
|
||||
}
|
||||
|
||||
exports.EventEmitter = KarmaEventEmitter
|
||||
exports.bufferEvents = bufferEvents
|
96
node_modules/karma/lib/executor.js
generated
vendored
Normal file
96
node_modules/karma/lib/executor.js
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
'use strict'
|
||||
|
||||
const log = require('./logger').create()
|
||||
|
||||
class Executor {
|
||||
constructor (capturedBrowsers, config, emitter) {
|
||||
this.capturedBrowsers = capturedBrowsers
|
||||
this.config = config
|
||||
this.emitter = emitter
|
||||
|
||||
this.executionScheduled = false
|
||||
this.errorsScheduled = []
|
||||
this.pendingCount = 0
|
||||
this.runningBrowsers = null
|
||||
|
||||
this.emitter.on('run_complete', () => this.onRunComplete())
|
||||
this.emitter.on('browser_complete', () => this.onBrowserComplete())
|
||||
}
|
||||
|
||||
schedule () {
|
||||
if (this.capturedBrowsers.length === 0) {
|
||||
log.warn(`No captured browser, open ${this.config.protocol}//${this.config.hostname}:${this.config.port}${this.config.urlRoot}`)
|
||||
return false
|
||||
} else if (this.capturedBrowsers.areAllReady()) {
|
||||
log.debug('All browsers are ready, executing')
|
||||
log.debug(`Captured ${this.capturedBrowsers.length} browsers`)
|
||||
this.executionScheduled = false
|
||||
this.capturedBrowsers.clearResults()
|
||||
this.pendingCount = this.capturedBrowsers.length
|
||||
this.runningBrowsers = this.capturedBrowsers.clone()
|
||||
this.emitter.emit('run_start', this.runningBrowsers)
|
||||
this.socketIoSockets.emit('execute', this.config.client)
|
||||
return true
|
||||
} else {
|
||||
log.info('Delaying execution, these browsers are not ready: ' + this.capturedBrowsers.getNonReady().join(', '))
|
||||
this.executionScheduled = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an error to be reported
|
||||
* @param {string} errorMessage
|
||||
* @returns {boolean} a boolean indicating whether or not the error was handled synchronously
|
||||
*/
|
||||
scheduleError (errorMessage) {
|
||||
// We don't want to interfere with any running test.
|
||||
// Verify that no test is running before reporting the error.
|
||||
if (this.capturedBrowsers.areAllReady()) {
|
||||
log.warn(errorMessage)
|
||||
const errorResult = {
|
||||
success: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
error: errorMessage,
|
||||
exitCode: 1
|
||||
}
|
||||
const noBrowsersStartedTests = []
|
||||
this.emitter.emit('run_start', noBrowsersStartedTests) // A run cannot complete without being started
|
||||
this.emitter.emit('run_complete', noBrowsersStartedTests, errorResult)
|
||||
return true
|
||||
} else {
|
||||
this.errorsScheduled.push(errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
onRunComplete () {
|
||||
if (this.executionScheduled) {
|
||||
this.schedule()
|
||||
}
|
||||
if (this.errorsScheduled.length) {
|
||||
const errorsToReport = this.errorsScheduled
|
||||
this.errorsScheduled = []
|
||||
errorsToReport.forEach((error) => this.scheduleError(error))
|
||||
}
|
||||
}
|
||||
|
||||
onBrowserComplete () {
|
||||
this.pendingCount--
|
||||
|
||||
if (!this.pendingCount) {
|
||||
// Ensure run_complete is emitted in the next tick
|
||||
// so it is never emitted before browser_complete
|
||||
setTimeout(() => {
|
||||
this.emitter.emit('run_complete', this.runningBrowsers, this.runningBrowsers.getResults())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executor.factory = function (capturedBrowsers, config, emitter) {
|
||||
return new Executor(capturedBrowsers, config, emitter)
|
||||
}
|
||||
|
||||
module.exports = Executor
|
242
node_modules/karma/lib/file-list.js
generated
vendored
Normal file
242
node_modules/karma/lib/file-list.js
generated
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
'use strict'
|
||||
|
||||
const { promisify } = require('util')
|
||||
const mm = require('minimatch')
|
||||
const Glob = require('glob').Glob
|
||||
const fs = require('graceful-fs')
|
||||
const statAsync = promisify(fs.stat.bind(fs))
|
||||
const pathLib = require('path')
|
||||
const _ = require('lodash')
|
||||
|
||||
const File = require('./file')
|
||||
const Url = require('./url')
|
||||
const helper = require('./helper')
|
||||
const log = require('./logger').create('filelist')
|
||||
const createPatternObject = require('./config').createPatternObject
|
||||
|
||||
class FileList {
|
||||
constructor (patterns, excludes, emitter, preprocess, autoWatchBatchDelay) {
|
||||
this._patterns = patterns || []
|
||||
this._excludes = excludes || []
|
||||
this._emitter = emitter
|
||||
this._preprocess = preprocess
|
||||
|
||||
this.buckets = new Map()
|
||||
|
||||
// A promise that is pending if and only if we are active in this.refresh_()
|
||||
this._refreshing = null
|
||||
|
||||
const emit = () => {
|
||||
this._emitter.emit('file_list_modified', this.files)
|
||||
}
|
||||
|
||||
const debouncedEmit = _.debounce(emit, autoWatchBatchDelay)
|
||||
this._emitModified = (immediate) => {
|
||||
immediate ? emit() : debouncedEmit()
|
||||
}
|
||||
}
|
||||
|
||||
_findExcluded (path) {
|
||||
return this._excludes.find((pattern) => mm(path, pattern))
|
||||
}
|
||||
|
||||
_findIncluded (path) {
|
||||
return this._patterns.find((pattern) => mm(path, pattern.pattern))
|
||||
}
|
||||
|
||||
_findFile (path, pattern) {
|
||||
if (!path || !pattern) return
|
||||
return this._getFilesByPattern(pattern.pattern).find((file) => file.originalPath === path)
|
||||
}
|
||||
|
||||
_exists (path) {
|
||||
return !!this._patterns.find((pattern) => mm(path, pattern.pattern) && this._findFile(path, pattern))
|
||||
}
|
||||
|
||||
_getFilesByPattern (pattern) {
|
||||
return this.buckets.get(pattern) || []
|
||||
}
|
||||
|
||||
_refresh () {
|
||||
const matchedFiles = new Set()
|
||||
|
||||
let lastCompletedRefresh = this._refreshing
|
||||
lastCompletedRefresh = Promise.all(
|
||||
this._patterns.map(async ({ pattern, type, nocache, isBinary, integrity }) => {
|
||||
if (helper.isUrlAbsolute(pattern)) {
|
||||
this.buckets.set(pattern, [new Url(pattern, type, integrity)])
|
||||
return
|
||||
}
|
||||
|
||||
const mg = new Glob(pathLib.normalize(pattern), { cwd: '/', follow: true, nodir: true, sync: true })
|
||||
|
||||
const files = mg.found
|
||||
.filter((path) => {
|
||||
if (this._findExcluded(path)) {
|
||||
log.debug(`Excluded file "${path}"`)
|
||||
return false
|
||||
} else if (matchedFiles.has(path)) {
|
||||
return false
|
||||
} else {
|
||||
matchedFiles.add(path)
|
||||
return true
|
||||
}
|
||||
})
|
||||
.map((path) => new File(path, mg.statCache[path].mtime, nocache, type, isBinary))
|
||||
|
||||
if (nocache) {
|
||||
log.debug(`Not preprocessing "${pattern}" due to nocache`)
|
||||
} else {
|
||||
await Promise.all(files.map((file) => this._preprocess(file)))
|
||||
}
|
||||
|
||||
this.buckets.set(pattern, files)
|
||||
|
||||
if (_.isEmpty(mg.found)) {
|
||||
log.warn(`Pattern "${pattern}" does not match any file.`)
|
||||
} else if (_.isEmpty(files)) {
|
||||
log.warn(`All files matched by "${pattern}" were excluded or matched by prior matchers.`)
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
// When we return from this function the file processing chain will be
|
||||
// complete. In the case of two fast refresh() calls, the second call
|
||||
// will overwrite this._refreshing, and we want the status to reflect
|
||||
// the second call and skip the modification event from the first call.
|
||||
if (this._refreshing !== lastCompletedRefresh) {
|
||||
return this._refreshing
|
||||
}
|
||||
this._emitModified(true)
|
||||
return this.files
|
||||
})
|
||||
|
||||
return lastCompletedRefresh
|
||||
}
|
||||
|
||||
get files () {
|
||||
const served = []
|
||||
const included = {}
|
||||
const lookup = {}
|
||||
this._patterns.forEach((p) => {
|
||||
// This needs to be here sadly, as plugins are modifiying
|
||||
// the _patterns directly resulting in elements not being
|
||||
// instantiated properly
|
||||
if (p.constructor.name !== 'Pattern') {
|
||||
p = createPatternObject(p)
|
||||
}
|
||||
|
||||
const files = this._getFilesByPattern(p.pattern)
|
||||
files.sort((a, b) => {
|
||||
if (a.path > b.path) return 1
|
||||
if (a.path < b.path) return -1
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
if (p.served) {
|
||||
served.push(...files)
|
||||
}
|
||||
|
||||
files.forEach((file) => {
|
||||
if (lookup[file.path] && lookup[file.path].compare(p) < 0) return
|
||||
|
||||
lookup[file.path] = p
|
||||
if (p.included) {
|
||||
included[file.path] = file
|
||||
} else {
|
||||
delete included[file.path]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
served: _.uniq(served, 'path'),
|
||||
included: _.values(included)
|
||||
}
|
||||
}
|
||||
|
||||
refresh () {
|
||||
this._refreshing = this._refresh()
|
||||
return this._refreshing
|
||||
}
|
||||
|
||||
reload (patterns, excludes) {
|
||||
this._patterns = patterns || []
|
||||
this._excludes = excludes || []
|
||||
|
||||
return this.refresh()
|
||||
}
|
||||
|
||||
async addFile (path) {
|
||||
const excluded = this._findExcluded(path)
|
||||
if (excluded) {
|
||||
log.debug(`Add file "${path}" ignored. Excluded by "${excluded}".`)
|
||||
return this.files
|
||||
}
|
||||
|
||||
const pattern = this._findIncluded(path)
|
||||
if (!pattern) {
|
||||
log.debug(`Add file "${path}" ignored. Does not match any pattern.`)
|
||||
return this.files
|
||||
}
|
||||
|
||||
if (this._exists(path)) {
|
||||
log.debug(`Add file "${path}" ignored. Already in the list.`)
|
||||
return this.files
|
||||
}
|
||||
|
||||
const file = new File(path)
|
||||
this._getFilesByPattern(pattern.pattern).push(file)
|
||||
|
||||
const [stat] = await Promise.all([statAsync(path), this._refreshing])
|
||||
file.mtime = stat.mtime
|
||||
await this._preprocess(file)
|
||||
|
||||
log.info(`Added file "${path}".`)
|
||||
this._emitModified()
|
||||
return this.files
|
||||
}
|
||||
|
||||
async changeFile (path, force) {
|
||||
const pattern = this._findIncluded(path)
|
||||
const file = this._findFile(path, pattern)
|
||||
|
||||
if (!file) {
|
||||
log.debug(`Changed file "${path}" ignored. Does not match any file in the list.`)
|
||||
return this.files
|
||||
}
|
||||
|
||||
const [stat] = await Promise.all([statAsync(path), this._refreshing])
|
||||
if (force || stat.mtime > file.mtime) {
|
||||
file.mtime = stat.mtime
|
||||
await this._preprocess(file)
|
||||
log.info(`Changed file "${path}".`)
|
||||
this._emitModified(force)
|
||||
}
|
||||
return this.files
|
||||
}
|
||||
|
||||
async removeFile (path) {
|
||||
const pattern = this._findIncluded(path)
|
||||
const file = this._findFile(path, pattern)
|
||||
|
||||
if (file) {
|
||||
helper.arrayRemove(this._getFilesByPattern(pattern.pattern), file)
|
||||
log.info(`Removed file "${path}".`)
|
||||
|
||||
this._emitModified()
|
||||
} else {
|
||||
log.debug(`Removed file "${path}" ignored. Does not match any file in the list.`)
|
||||
}
|
||||
return this.files
|
||||
}
|
||||
}
|
||||
|
||||
FileList.factory = function (config, emitter, preprocess) {
|
||||
return new FileList(config.files, config.exclude, emitter, preprocess, config.autoWatchBatchDelay)
|
||||
}
|
||||
|
||||
FileList.factory.$inject = ['config', 'emitter', 'preprocess']
|
||||
|
||||
module.exports = FileList
|
49
node_modules/karma/lib/file.js
generated
vendored
Normal file
49
node_modules/karma/lib/file.js
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* File object used for tracking files in `file-list.js`.
|
||||
*/
|
||||
class File {
|
||||
constructor (path, mtime, doNotCache, type, isBinary, integrity) {
|
||||
// used for serving (processed path, eg some/file.coffee -> some/file.coffee.js)
|
||||
this.path = path
|
||||
|
||||
// original absolute path, id of the file
|
||||
this.originalPath = path
|
||||
|
||||
// where the content is stored (processed)
|
||||
this.contentPath = path
|
||||
|
||||
// encodings format {[encodingType]: encodedContent}
|
||||
// example: {gzip: <Buffer 1f 8b 08...>}
|
||||
this.encodings = Object.create(null)
|
||||
|
||||
this.mtime = mtime
|
||||
this.isUrl = false
|
||||
|
||||
this.doNotCache = doNotCache === undefined ? false : doNotCache
|
||||
|
||||
this.type = type
|
||||
|
||||
// Tri state: null means probe file for binary.
|
||||
this.isBinary = isBinary === undefined ? null : isBinary
|
||||
|
||||
this.integrity = integrity
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect type from the file extension.
|
||||
* @returns {string} detected file type or empty string
|
||||
*/
|
||||
detectType () {
|
||||
return path.extname(this.path).slice(1)
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.path
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = File
|
171
node_modules/karma/lib/helper.js
generated
vendored
Normal file
171
node_modules/karma/lib/helper.js
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const mkdirp = require('mkdirp')
|
||||
const useragent = require('ua-parser-js')
|
||||
const mm = require('minimatch')
|
||||
|
||||
exports.browserFullNameToShort = (fullName) => {
|
||||
const ua = useragent(fullName)
|
||||
if (!ua.browser.name && !ua.browser.version && !ua.os.name && !ua.os.version) {
|
||||
return fullName
|
||||
}
|
||||
return `${ua.browser.name} ${ua.browser.version || '0.0.0'} (${ua.os.name} ${ua.os.version || '0.0.0'})`
|
||||
}
|
||||
|
||||
exports.isDefined = (value) => {
|
||||
return !_.isUndefined(value)
|
||||
}
|
||||
|
||||
const parser = (pattern, out) => {
|
||||
if (pattern.length === 0) return out
|
||||
const p = /^(\[[^\]]*\]|[*+@?]\((.+?)\))/g
|
||||
const matches = p.exec(pattern)
|
||||
if (!matches) {
|
||||
const c = pattern[0]
|
||||
let t = 'word'
|
||||
if (c === '*') {
|
||||
t = 'star'
|
||||
} else if (c === '?') {
|
||||
t = 'optional'
|
||||
}
|
||||
out[t]++
|
||||
return parser(pattern.slice(1), out)
|
||||
}
|
||||
if (matches[2] !== undefined) {
|
||||
out.ext_glob++
|
||||
parser(matches[2], out)
|
||||
return parser(pattern.slice(matches[0].length), out)
|
||||
}
|
||||
out.range++
|
||||
return parser(pattern.slice(matches[0].length), out)
|
||||
}
|
||||
|
||||
const gsParser = (pattern, out) => {
|
||||
if (pattern === '**') {
|
||||
out.glob_star++
|
||||
return out
|
||||
}
|
||||
return parser(pattern, out)
|
||||
}
|
||||
|
||||
const compareWeightObject = (w1, w2) => {
|
||||
return exports.mmComparePatternWeights(
|
||||
[w1.glob_star, w1.star, w1.ext_glob, w1.range, w1.optional],
|
||||
[w2.glob_star, w2.star, w2.ext_glob, w2.range, w2.optional]
|
||||
)
|
||||
}
|
||||
|
||||
exports.mmPatternWeight = (pattern) => {
|
||||
const m = new mm.Minimatch(pattern)
|
||||
if (!m.globParts) return [0, 0, 0, 0, 0, 0]
|
||||
const result = m.globParts.reduce((prev, p) => {
|
||||
const r = p.reduce((prev, p) => {
|
||||
return gsParser(p, prev)
|
||||
}, { glob_star: 0, ext_glob: 0, word: 0, star: 0, optional: 0, range: 0 })
|
||||
if (prev === undefined) return r
|
||||
return compareWeightObject(r, prev) > 0 ? r : prev
|
||||
}, undefined)
|
||||
result.glob_sets = m.set.length
|
||||
return [result.glob_sets, result.glob_star, result.star, result.ext_glob, result.range, result.optional]
|
||||
}
|
||||
|
||||
exports.mmComparePatternWeights = (weight1, weight2) => {
|
||||
const n1 = weight1[0]
|
||||
const n2 = weight2[0]
|
||||
const diff = n1 - n2
|
||||
if (diff !== 0) return diff / Math.abs(diff)
|
||||
return weight1.length > 1 ? exports.mmComparePatternWeights(weight1.slice(1), weight2.slice(1)) : 0
|
||||
}
|
||||
|
||||
exports.isFunction = _.isFunction
|
||||
exports.isString = _.isString
|
||||
exports.isObject = _.isObject
|
||||
exports.isArray = _.isArray
|
||||
exports.isNumber = _.isNumber
|
||||
|
||||
const ABS_URL = /^https?:\/\//
|
||||
exports.isUrlAbsolute = (url) => {
|
||||
return ABS_URL.test(url)
|
||||
}
|
||||
|
||||
exports.camelToSnake = (camelCase) => {
|
||||
return camelCase.replace(/[A-Z]/g, (match, pos) => {
|
||||
return (pos > 0 ? '_' : '') + match.toLowerCase()
|
||||
})
|
||||
}
|
||||
|
||||
exports.ucFirst = (word) => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||
}
|
||||
|
||||
exports.dashToCamel = (dash) => {
|
||||
const words = dash.split('-')
|
||||
return words.shift() + words.map(exports.ucFirst).join('')
|
||||
}
|
||||
|
||||
exports.arrayRemove = (collection, item) => {
|
||||
const idx = collection.indexOf(item)
|
||||
|
||||
if (idx !== -1) {
|
||||
collection.splice(idx, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
exports.merge = function () {
|
||||
const args = Array.prototype.slice.call(arguments, 0)
|
||||
args.unshift({})
|
||||
return _.merge.apply({}, args)
|
||||
}
|
||||
|
||||
exports.formatTimeInterval = (time) => {
|
||||
const mins = Math.floor(time / 60000)
|
||||
const secs = (time - mins * 60000) / 1000
|
||||
let str = secs + (secs === 1 ? ' sec' : ' secs')
|
||||
|
||||
if (mins) {
|
||||
str = mins + (mins === 1 ? ' min ' : ' mins ') + str
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
const replaceWinPath = (path) => {
|
||||
return _.isString(path) ? path.replace(/\\/g, '/') : path
|
||||
}
|
||||
|
||||
exports.normalizeWinPath = process.platform === 'win32' ? replaceWinPath : _.identity
|
||||
|
||||
exports.mkdirIfNotExists = (directory, done) => {
|
||||
mkdirp(directory, done)
|
||||
}
|
||||
|
||||
exports.defer = () => {
|
||||
let res
|
||||
let rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
|
||||
return {
|
||||
resolve: res,
|
||||
reject: rej,
|
||||
promise: promise
|
||||
}
|
||||
}
|
||||
|
||||
exports.saveOriginalArgs = (config) => {
|
||||
if (config && config.client && config.client.args) {
|
||||
config.client.originalArgs = _.cloneDeep(config.client.args)
|
||||
}
|
||||
}
|
||||
|
||||
exports.restoreOriginalArgs = (config) => {
|
||||
if (config && config.client && config.client.originalArgs) {
|
||||
config.client.args = _.cloneDeep(config.client.originalArgs)
|
||||
}
|
||||
}
|
18
node_modules/karma/lib/index.js
generated
vendored
Normal file
18
node_modules/karma/lib/index.js
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
'use strict'
|
||||
|
||||
const constants = require('./constants')
|
||||
const Server = require('./server')
|
||||
const runner = require('./runner')
|
||||
const stopper = require('./stopper')
|
||||
const launcher = require('./launcher')
|
||||
const cfg = require('./config')
|
||||
|
||||
module.exports = {
|
||||
constants: constants,
|
||||
VERSION: constants.VERSION,
|
||||
Server: Server,
|
||||
runner: runner,
|
||||
stopper: stopper,
|
||||
launcher: launcher,
|
||||
config: { parseConfig: cfg.parseConfig } // lets start with only opening up the `parseConfig` api
|
||||
}
|
239
node_modules/karma/lib/init.js
generated
vendored
Executable file
239
node_modules/karma/lib/init.js
generated
vendored
Executable file
|
@ -0,0 +1,239 @@
|
|||
'use strict'
|
||||
|
||||
const readline = require('readline')
|
||||
const path = require('path')
|
||||
const glob = require('glob')
|
||||
const mm = require('minimatch')
|
||||
const exec = require('child_process').exec
|
||||
|
||||
const helper = require('./helper')
|
||||
const logger = require('./logger')
|
||||
|
||||
const log = logger.create('init')
|
||||
const logQueue = require('./init/log-queue')
|
||||
|
||||
const StateMachine = require('./init/state_machine')
|
||||
const COLOR_SCHEME = require('./init/color_schemes')
|
||||
const formatters = require('./init/formatters')
|
||||
|
||||
// TODO(vojta): coverage
|
||||
// TODO(vojta): html preprocessors
|
||||
// TODO(vojta): SauceLabs
|
||||
// TODO(vojta): BrowserStack
|
||||
|
||||
let NODE_MODULES_DIR = path.resolve(__dirname, '../..')
|
||||
|
||||
// Karma is not in node_modules, probably a symlink,
|
||||
// use current working dir.
|
||||
if (!/node_modules$/.test(NODE_MODULES_DIR)) {
|
||||
NODE_MODULES_DIR = path.resolve('node_modules')
|
||||
}
|
||||
|
||||
function installPackage (pkgName) {
|
||||
// Do not install if already installed.
|
||||
try {
|
||||
require(NODE_MODULES_DIR + '/' + pkgName)
|
||||
return
|
||||
} catch (e) {}
|
||||
|
||||
log.debug(`Missing plugin "${pkgName}". Installing...`)
|
||||
|
||||
const options = {
|
||||
cwd: path.resolve(NODE_MODULES_DIR, '..')
|
||||
}
|
||||
|
||||
exec(`npm install ${pkgName} --save-dev`, options, function (err, stdout, stderr) {
|
||||
// Put the logs into the queue and print them after answering current question.
|
||||
// Otherwise the log would clobber the interactive terminal.
|
||||
logQueue.push(function () {
|
||||
if (!err) {
|
||||
log.debug(`${pkgName} successfully installed.`)
|
||||
} else if (/is not in the npm registry/.test(stderr)) {
|
||||
log.warn(`Failed to install "${pkgName}". It is not in the npm registry!\n Please install it manually.`)
|
||||
} else if (/Error: EACCES/.test(stderr)) {
|
||||
log.warn(`Failed to install "${pkgName}". No permissions to write in ${options.cwd}!\n Please install it manually.`)
|
||||
} else {
|
||||
log.warn(`Failed to install "${pkgName}"\n Please install it manually.`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function validatePattern (pattern) {
|
||||
if (!glob.sync(pattern).length) {
|
||||
log.warn('There is no file matching this pattern.\n')
|
||||
}
|
||||
}
|
||||
|
||||
function validateBrowser (name) {
|
||||
// TODO(vojta): check if the path resolves to a binary
|
||||
installPackage('karma-' + name.toLowerCase().replace('headless', '').replace('canary', '') + '-launcher')
|
||||
}
|
||||
|
||||
function validateFramework (name) {
|
||||
installPackage('karma-' + name)
|
||||
}
|
||||
|
||||
function validateRequireJs (useRequire) {
|
||||
if (useRequire) {
|
||||
validateFramework('requirejs')
|
||||
}
|
||||
}
|
||||
|
||||
var questions = [{
|
||||
id: 'framework',
|
||||
question: 'Which testing framework do you want to use ?',
|
||||
hint: 'Press tab to list possible options. Enter to move to the next question.',
|
||||
options: ['jasmine', 'mocha', 'qunit', 'nodeunit', 'nunit', ''],
|
||||
validate: validateFramework
|
||||
}, {
|
||||
id: 'requirejs',
|
||||
question: 'Do you want to use Require.js ?',
|
||||
hint: 'This will add Require.js plugin.\nPress tab to list possible options. Enter to move to the next question.',
|
||||
options: ['no', 'yes'],
|
||||
validate: validateRequireJs,
|
||||
boolean: true
|
||||
}, {
|
||||
id: 'browsers',
|
||||
question: 'Do you want to capture any browsers automatically ?',
|
||||
hint: 'Press tab to list possible options. Enter empty string to move to the next question.',
|
||||
options: ['Chrome', 'ChromeHeadless', 'ChromeCanary', 'Firefox', 'Safari', 'PhantomJS', 'Opera', 'IE', ''],
|
||||
validate: validateBrowser,
|
||||
multiple: true
|
||||
}, {
|
||||
id: 'files',
|
||||
question: 'What is the location of your source and test files ?',
|
||||
hint: 'You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".\nEnter empty string to move to the next question.',
|
||||
multiple: true,
|
||||
validate: validatePattern
|
||||
}, {
|
||||
id: 'exclude',
|
||||
question: 'Should any of the files included by the previous patterns be excluded ?',
|
||||
hint: 'You can use glob patterns, eg. "**/*.swp".\nEnter empty string to move to the next question.',
|
||||
multiple: true,
|
||||
validate: validatePattern
|
||||
}, {
|
||||
id: 'generateTestMain',
|
||||
question: 'Do you wanna generate a bootstrap file for RequireJS?',
|
||||
hint: 'This will generate test-main.js/coffee that configures RequireJS and starts the tests.',
|
||||
options: ['no', 'yes'],
|
||||
boolean: true,
|
||||
condition: (answers) => answers.requirejs
|
||||
}, {
|
||||
id: 'includedFiles',
|
||||
question: 'Which files do you want to include with <script> tag ?',
|
||||
hint: 'This should be a script that bootstraps your test by configuring Require.js and ' +
|
||||
'kicking __karma__.start(), probably your test-main.js file.\n' +
|
||||
'Enter empty string to move to the next question.',
|
||||
multiple: true,
|
||||
validate: validatePattern,
|
||||
condition: (answers) => answers.requirejs && !answers.generateTestMain
|
||||
}, {
|
||||
id: 'autoWatch',
|
||||
question: 'Do you want Karma to watch all the files and run the tests on change ?',
|
||||
hint: 'Press tab to list possible options.',
|
||||
options: ['yes', 'no'],
|
||||
boolean: true
|
||||
}]
|
||||
|
||||
function getBasePath (configFilePath, cwd) {
|
||||
const configParts = path.dirname(configFilePath).split(path.sep)
|
||||
const cwdParts = cwd.split(path.sep)
|
||||
const base = []
|
||||
|
||||
while (configParts.length && configParts[0] === cwdParts[0]) {
|
||||
configParts.shift()
|
||||
cwdParts.shift()
|
||||
}
|
||||
|
||||
while (configParts.length) {
|
||||
const part = configParts.shift()
|
||||
if (part === '..') {
|
||||
base.unshift(cwdParts.pop())
|
||||
} else if (part !== '.') {
|
||||
base.unshift('..')
|
||||
}
|
||||
}
|
||||
|
||||
return base.join(path.sep)
|
||||
}
|
||||
|
||||
function processAnswers (answers, basePath, testMainFile) {
|
||||
const processedAnswers = {
|
||||
basePath: basePath,
|
||||
files: answers.files,
|
||||
onlyServedFiles: [],
|
||||
exclude: answers.exclude,
|
||||
autoWatch: answers.autoWatch,
|
||||
generateTestMain: answers.generateTestMain,
|
||||
browsers: answers.browsers,
|
||||
frameworks: [],
|
||||
preprocessors: {}
|
||||
}
|
||||
|
||||
if (answers.framework) {
|
||||
processedAnswers.frameworks.push(answers.framework)
|
||||
}
|
||||
|
||||
if (answers.requirejs) {
|
||||
processedAnswers.frameworks.push('requirejs')
|
||||
processedAnswers.files = answers.includedFiles || []
|
||||
processedAnswers.onlyServedFiles = answers.files
|
||||
|
||||
if (answers.generateTestMain) {
|
||||
processedAnswers.files.push(testMainFile)
|
||||
}
|
||||
}
|
||||
|
||||
const allPatterns = answers.files.concat(answers.includedFiles || [])
|
||||
if (allPatterns.some((pattern) => mm(pattern, '**/*.coffee'))) {
|
||||
installPackage('karma-coffee-preprocessor')
|
||||
processedAnswers.preprocessors['**/*.coffee'] = ['coffee']
|
||||
}
|
||||
|
||||
return processedAnswers
|
||||
}
|
||||
|
||||
exports.init = function (config) {
|
||||
logger.setupFromConfig(config)
|
||||
|
||||
const colorScheme = !helper.isDefined(config.colors) || config.colors ? COLOR_SCHEME.ON : COLOR_SCHEME.OFF
|
||||
// need to be registered before creating readlineInterface
|
||||
process.stdin.on('keypress', function (s, key) {
|
||||
sm.onKeypress(key)
|
||||
})
|
||||
|
||||
const rli = readline.createInterface(process.stdin, process.stdout)
|
||||
const sm = new StateMachine(rli, colorScheme)
|
||||
|
||||
rli.on('line', sm.onLine.bind(sm))
|
||||
|
||||
// clean colors
|
||||
rli.on('SIGINT', function () {
|
||||
sm.kill()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
sm.process(questions, function (answers) {
|
||||
const cwd = process.cwd()
|
||||
const configFile = config.configFile || 'karma.conf.js'
|
||||
const isCoffee = path.extname(configFile) === '.coffee'
|
||||
const testMainFile = isCoffee ? 'test-main.coffee' : 'test-main.js'
|
||||
const formatter = formatters.createForPath(configFile)
|
||||
const processedAnswers = processAnswers(answers, getBasePath(configFile, cwd), testMainFile)
|
||||
const configFilePath = path.resolve(cwd, configFile)
|
||||
const testMainFilePath = path.resolve(cwd, testMainFile)
|
||||
|
||||
if (isCoffee) {
|
||||
installPackage('coffeescript')
|
||||
}
|
||||
|
||||
if (processedAnswers.generateTestMain) {
|
||||
formatter.writeRequirejsConfigFile(testMainFilePath)
|
||||
console.log(colorScheme.success(`RequireJS bootstrap file generated at "${testMainFilePath}".`))
|
||||
}
|
||||
|
||||
formatter.writeConfigFile(configFilePath, processedAnswers)
|
||||
console.log(colorScheme.success(`Config file generated at "${configFilePath}".`))
|
||||
})
|
||||
}
|
28
node_modules/karma/lib/init/color_schemes.js
generated
vendored
Normal file
28
node_modules/karma/lib/init/color_schemes.js
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
const COLORS_ON = {
|
||||
RESET: '\x1B[39m',
|
||||
ANSWER: '\x1B[36m', // NYAN
|
||||
SUCCESS: '\x1B[32m', // GREEN
|
||||
QUESTION: '\x1B[1m', // BOLD
|
||||
question: function (str) {
|
||||
return this.QUESTION + str + '\x1B[22m'
|
||||
},
|
||||
success: function (str) {
|
||||
return this.SUCCESS + str + this.RESET
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS_OFF = {
|
||||
RESET: '',
|
||||
ANSWER: '',
|
||||
SUCCESS: '',
|
||||
QUESTION: '',
|
||||
question: function (str) {
|
||||
return str
|
||||
},
|
||||
success: function (str) {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
exports.ON = COLORS_ON
|
||||
exports.OFF = COLORS_OFF
|
116
node_modules/karma/lib/init/formatters.js
generated
vendored
Normal file
116
node_modules/karma/lib/init/formatters.js
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const FileUtils = require('../utils/file-utils')
|
||||
|
||||
function quote (value) {
|
||||
return `'${value}'`
|
||||
}
|
||||
|
||||
function formatLine (items) {
|
||||
return items.map(quote).join(', ')
|
||||
}
|
||||
|
||||
function formatMultiLines (items) {
|
||||
return items
|
||||
.map((file) => '\n ' + file)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
function formatFiles (includedFiles, onlyServedFiles) {
|
||||
const lines = []
|
||||
.concat(includedFiles.map(quote))
|
||||
.concat(onlyServedFiles.map((file) => `{ pattern: ${quote(file)}, included: false }`))
|
||||
|
||||
return formatMultiLines(lines)
|
||||
}
|
||||
|
||||
function formatPreprocessors (preprocessors) {
|
||||
const lines = Object.keys(preprocessors)
|
||||
.map((pattern) => `${quote(pattern)}: [${formatLine(preprocessors[pattern])}]`)
|
||||
|
||||
return formatMultiLines(lines)
|
||||
}
|
||||
|
||||
function getConfigPath (file) {
|
||||
return path.join(__dirname, `/../../${file}`)
|
||||
}
|
||||
|
||||
class JavaScriptFormatter {
|
||||
constructor () {
|
||||
this.TEMPLATE_FILE_PATH = getConfigPath('config.tpl.js')
|
||||
this.REQUIREJS_TEMPLATE_FILE = getConfigPath('requirejs.config.tpl.js')
|
||||
}
|
||||
|
||||
generateConfigFile (answers) {
|
||||
const replacements = this.formatAnswers(answers)
|
||||
|
||||
return FileUtils
|
||||
.readFile(this.TEMPLATE_FILE_PATH)
|
||||
.replace(/%(.*)%/g, (a, key) => replacements[key])
|
||||
}
|
||||
|
||||
writeConfigFile (path, answers) {
|
||||
FileUtils.saveFile(path, this.generateConfigFile(answers))
|
||||
}
|
||||
|
||||
writeRequirejsConfigFile (path) {
|
||||
FileUtils.copyFile(this.REQUIREJS_TEMPLATE_FILE, path)
|
||||
}
|
||||
|
||||
formatAnswers (answers) {
|
||||
return {
|
||||
DATE: new Date(),
|
||||
BASE_PATH: answers.basePath,
|
||||
FRAMEWORKS: formatLine(answers.frameworks),
|
||||
FILES: formatFiles(answers.files, answers.onlyServedFiles),
|
||||
EXCLUDE: formatFiles(answers.exclude, []),
|
||||
AUTO_WATCH: answers.autoWatch ? 'true' : 'false',
|
||||
BROWSERS: formatLine(answers.browsers),
|
||||
PREPROCESSORS: formatPreprocessors(answers.preprocessors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CoffeeFormatter extends JavaScriptFormatter {
|
||||
constructor () {
|
||||
super()
|
||||
this.TEMPLATE_FILE_PATH = getConfigPath('config.tpl.coffee')
|
||||
this.REQUIREJS_TEMPLATE_FILE = getConfigPath('requirejs.config.tpl.coffee')
|
||||
}
|
||||
}
|
||||
|
||||
class LiveFormatter extends JavaScriptFormatter {
|
||||
constructor () {
|
||||
super()
|
||||
this.TEMPLATE_FILE_PATH = getConfigPath('config.tpl.ls')
|
||||
}
|
||||
}
|
||||
|
||||
class TypeFormatter extends JavaScriptFormatter {
|
||||
constructor () {
|
||||
super()
|
||||
this.TEMPLATE_FILE_PATH = getConfigPath('config.tpl.ts')
|
||||
}
|
||||
}
|
||||
|
||||
exports.JavaScript = JavaScriptFormatter
|
||||
exports.Coffee = CoffeeFormatter
|
||||
exports.Live = LiveFormatter
|
||||
exports.Type = TypeFormatter
|
||||
|
||||
exports.createForPath = function (path) {
|
||||
if (/\.coffee$/.test(path)) {
|
||||
return new CoffeeFormatter()
|
||||
}
|
||||
|
||||
if (/\.ls$/.test(path)) {
|
||||
return new LiveFormatter()
|
||||
}
|
||||
|
||||
if (/\.ts$/.test(path)) {
|
||||
return new TypeFormatter()
|
||||
}
|
||||
|
||||
return new JavaScriptFormatter()
|
||||
}
|
15
node_modules/karma/lib/init/log-queue.js
generated
vendored
Normal file
15
node_modules/karma/lib/init/log-queue.js
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict'
|
||||
|
||||
const logQueue = []
|
||||
function printLogQueue () {
|
||||
logQueue.forEach((log) => log())
|
||||
logQueue.length = 0
|
||||
}
|
||||
|
||||
function push (log) {
|
||||
logQueue.push(log)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printLogQueue, push
|
||||
}
|
143
node_modules/karma/lib/init/state_machine.js
generated
vendored
Normal file
143
node_modules/karma/lib/init/state_machine.js
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
'use strict'
|
||||
|
||||
const logQueue = require('./log-queue')
|
||||
|
||||
let questions
|
||||
let currentQuestion
|
||||
let answers
|
||||
let currentOptions
|
||||
let currentOptionsPointer
|
||||
let currentQuestionId
|
||||
let done
|
||||
|
||||
class StateMachine {
|
||||
constructor (rli, colors) {
|
||||
this.rli = rli
|
||||
this.colors = colors
|
||||
}
|
||||
|
||||
showPrompt () {
|
||||
this.rli.write(this.colors.ANSWER)
|
||||
this.rli.prompt()
|
||||
}
|
||||
|
||||
onKeypress (key) {
|
||||
if (!currentOptions || !key) {
|
||||
return
|
||||
}
|
||||
|
||||
if (key.name === 'tab' || key.name === 'right' || key.name === 'down') {
|
||||
this.suggestOption(currentOptionsPointer + 1)
|
||||
} else if (key.name === 'left' || key.name === 'up') {
|
||||
this.suggestOption(currentOptionsPointer - 1)
|
||||
}
|
||||
|
||||
if (!key.ctrl && !key.meta && key.name !== 'enter' && key.name !== 'return') {
|
||||
key.name = 'escape'
|
||||
}
|
||||
}
|
||||
|
||||
suggestOption (index) {
|
||||
if (!currentOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
if (index === -1) {
|
||||
currentOptionsPointer = currentOptions.length - 1
|
||||
} else if (index === currentOptions.length) {
|
||||
currentOptionsPointer = 0
|
||||
} else {
|
||||
currentOptionsPointer = index
|
||||
}
|
||||
|
||||
this.rli._deleteLineLeft()
|
||||
this.rli._deleteLineRight()
|
||||
this.rli.write(currentOptions[currentOptionsPointer])
|
||||
}
|
||||
|
||||
kill () {
|
||||
currentOptions = null
|
||||
currentQuestionId = null
|
||||
this.rli.write('\n' + this.colors.RESET + '\n')
|
||||
this.rli.close()
|
||||
}
|
||||
|
||||
onLine (line) {
|
||||
if (currentQuestionId) {
|
||||
this.rli.write(this.colors.RESET)
|
||||
line = line.trim().replace(this.colors.ANSWER, '').replace(this.colors.RESET, '')
|
||||
|
||||
if (currentOptions) {
|
||||
currentOptionsPointer = currentOptions.indexOf(line)
|
||||
if (currentOptionsPointer === -1) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (line === '') {
|
||||
line = null
|
||||
}
|
||||
|
||||
if (currentQuestion.boolean) {
|
||||
line = (line === 'yes' || line === 'true' || line === 'on')
|
||||
}
|
||||
|
||||
if (line !== null && currentQuestion.validate) {
|
||||
currentQuestion.validate(line)
|
||||
}
|
||||
|
||||
if (currentQuestion.multiple) {
|
||||
answers[currentQuestionId] = answers[currentQuestionId] || []
|
||||
if (line !== null) {
|
||||
answers[currentQuestionId].push(line)
|
||||
this.showPrompt()
|
||||
|
||||
if (currentOptions) {
|
||||
currentOptions.splice(currentOptionsPointer, 1)
|
||||
currentOptionsPointer = -1
|
||||
}
|
||||
} else {
|
||||
this.nextQuestion()
|
||||
}
|
||||
} else {
|
||||
answers[currentQuestionId] = line
|
||||
this.nextQuestion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextQuestion () {
|
||||
currentQuestion = questions.shift()
|
||||
|
||||
while (currentQuestion && currentQuestion.condition && !currentQuestion.condition(answers)) {
|
||||
currentQuestion = questions.shift()
|
||||
}
|
||||
|
||||
logQueue.printLogQueue()
|
||||
|
||||
if (currentQuestion) {
|
||||
currentQuestionId = null
|
||||
|
||||
this.rli.write('\n' + this.colors.question(currentQuestion.question) + '\n')
|
||||
this.rli.write(currentQuestion.hint + '\n')
|
||||
this.showPrompt()
|
||||
|
||||
currentOptions = currentQuestion.options || null
|
||||
currentQuestionId = currentQuestion.id
|
||||
this.suggestOption(0)
|
||||
} else {
|
||||
this.kill()
|
||||
done(answers)
|
||||
}
|
||||
}
|
||||
|
||||
process (_questions, _done) {
|
||||
questions = _questions
|
||||
answers = {}
|
||||
done = _done
|
||||
|
||||
this.nextQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StateMachine
|
199
node_modules/karma/lib/launcher.js
generated
vendored
Normal file
199
node_modules/karma/lib/launcher.js
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
'use strict'
|
||||
|
||||
const Jobs = require('qjobs')
|
||||
|
||||
const log = require('./logger').create('launcher')
|
||||
|
||||
const baseDecorator = require('./launchers/base').decoratorFactory
|
||||
const captureTimeoutDecorator = require('./launchers/capture_timeout').decoratorFactory
|
||||
const retryDecorator = require('./launchers/retry').decoratorFactory
|
||||
const processDecorator = require('./launchers/process').decoratorFactory
|
||||
|
||||
// TODO(vojta): remove once nobody uses it
|
||||
const baseBrowserDecoratorFactory = function (
|
||||
baseLauncherDecorator,
|
||||
captureTimeoutLauncherDecorator,
|
||||
retryLauncherDecorator,
|
||||
processLauncherDecorator,
|
||||
processKillTimeout
|
||||
) {
|
||||
return function (launcher) {
|
||||
baseLauncherDecorator(launcher)
|
||||
captureTimeoutLauncherDecorator(launcher)
|
||||
retryLauncherDecorator(launcher)
|
||||
processLauncherDecorator(launcher, processKillTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
class Launcher {
|
||||
constructor (server, emitter, injector) {
|
||||
this._server = server
|
||||
this._emitter = emitter
|
||||
this._injector = injector
|
||||
this._browsers = []
|
||||
this._lastStartTime = null
|
||||
|
||||
// Attach list of dependency injection parameters to methods.
|
||||
this.launch.$inject = [
|
||||
'config.browsers',
|
||||
'config.concurrency'
|
||||
]
|
||||
|
||||
this.launchSingle.$inject = [
|
||||
'config.protocol',
|
||||
'config.hostname',
|
||||
'config.port',
|
||||
'config.urlRoot',
|
||||
'config.upstreamProxy',
|
||||
'config.processKillTimeout'
|
||||
]
|
||||
|
||||
this._emitter.on('exit', (callback) => this.killAll(callback))
|
||||
}
|
||||
|
||||
getBrowserById (id) {
|
||||
return this._browsers.find((browser) => browser.id === id)
|
||||
}
|
||||
|
||||
launchSingle (protocol, hostname, port, urlRoot, upstreamProxy, processKillTimeout) {
|
||||
if (upstreamProxy) {
|
||||
protocol = upstreamProxy.protocol
|
||||
hostname = upstreamProxy.hostname
|
||||
port = upstreamProxy.port
|
||||
urlRoot = upstreamProxy.path + urlRoot.slice(1)
|
||||
}
|
||||
|
||||
return (name) => {
|
||||
let browser
|
||||
const locals = {
|
||||
id: ['value', Launcher.generateId()],
|
||||
name: ['value', name],
|
||||
processKillTimeout: ['value', processKillTimeout],
|
||||
baseLauncherDecorator: ['factory', baseDecorator],
|
||||
captureTimeoutLauncherDecorator: ['factory', captureTimeoutDecorator],
|
||||
retryLauncherDecorator: ['factory', retryDecorator],
|
||||
processLauncherDecorator: ['factory', processDecorator],
|
||||
baseBrowserDecorator: ['factory', baseBrowserDecoratorFactory]
|
||||
}
|
||||
|
||||
// TODO(vojta): determine script from name
|
||||
if (name.includes('/')) {
|
||||
name = 'Script'
|
||||
}
|
||||
|
||||
try {
|
||||
browser = this._injector.createChild([locals], ['launcher:' + name]).get('launcher:' + name)
|
||||
} catch (e) {
|
||||
if (e.message.includes(`No provider for "launcher:${name}"`)) {
|
||||
log.error(`Cannot load browser "${name}": it is not registered! Perhaps you are missing some plugin?`)
|
||||
} else {
|
||||
log.error(`Cannot load browser "${name}"!\n ` + e.stack)
|
||||
}
|
||||
|
||||
this._emitter.emit('load_error', 'launcher', name)
|
||||
return
|
||||
}
|
||||
|
||||
this.jobs.add((args, done) => {
|
||||
log.info(`Starting browser ${browser.displayName || browser.name}`)
|
||||
|
||||
browser.on('browser_process_failure', () => done(browser.error))
|
||||
|
||||
browser.on('done', () => {
|
||||
if (!browser.error && browser.state !== browser.STATE_RESTARTING) {
|
||||
done(null, browser)
|
||||
}
|
||||
})
|
||||
|
||||
browser.start(`${protocol}//${hostname}:${port}${urlRoot}`)
|
||||
}, [])
|
||||
|
||||
this.jobs.run()
|
||||
this._browsers.push(browser)
|
||||
}
|
||||
}
|
||||
|
||||
launch (names, concurrency) {
|
||||
log.info(`Launching browsers ${names.join(', ')} with concurrency ${concurrency === Infinity ? 'unlimited' : concurrency}`)
|
||||
this.jobs = new Jobs({ maxConcurrency: concurrency })
|
||||
|
||||
this._lastStartTime = Date.now()
|
||||
|
||||
if (this._server.loadErrors.length) {
|
||||
this.jobs.add((args, done) => done(), [])
|
||||
} else {
|
||||
names.forEach((name) => this._injector.invoke(this.launchSingle, this)(name))
|
||||
}
|
||||
|
||||
this.jobs.on('end', (err) => {
|
||||
log.debug('Finished all browsers')
|
||||
|
||||
if (err) {
|
||||
log.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
this.jobs.run()
|
||||
|
||||
return this._browsers
|
||||
}
|
||||
|
||||
kill (id, callback) {
|
||||
callback = callback || function () {}
|
||||
const browser = this.getBrowserById(id)
|
||||
|
||||
if (browser) {
|
||||
browser.forceKill().then(callback)
|
||||
return true
|
||||
}
|
||||
process.nextTick(callback)
|
||||
return false
|
||||
}
|
||||
|
||||
restart (id) {
|
||||
const browser = this.getBrowserById(id)
|
||||
if (browser) {
|
||||
browser.restart()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
killAll (callback) {
|
||||
callback = callback || function () {}
|
||||
log.debug('Disconnecting all browsers')
|
||||
|
||||
if (!this._browsers.length) {
|
||||
return process.nextTick(callback)
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
this._browsers
|
||||
.map((browser) => browser.forceKill())
|
||||
).then(callback)
|
||||
}
|
||||
|
||||
areAllCaptured () {
|
||||
return this._browsers.every((browser) => browser.isCaptured())
|
||||
}
|
||||
|
||||
markCaptured (id) {
|
||||
const browser = this.getBrowserById(id)
|
||||
if (browser) {
|
||||
browser.markCaptured()
|
||||
log.debug(`${browser.name} (id ${browser.id}) captured in ${(Date.now() - this._lastStartTime) / 1000} secs`)
|
||||
}
|
||||
}
|
||||
|
||||
static generateId () {
|
||||
return Math.floor(Math.random() * 100000000).toString()
|
||||
}
|
||||
}
|
||||
|
||||
Launcher.factory = function (server, emitter, injector) {
|
||||
return new Launcher(server, emitter, injector)
|
||||
}
|
||||
|
||||
Launcher.factory.$inject = ['server', 'emitter', 'injector']
|
||||
|
||||
exports.Launcher = Launcher
|
140
node_modules/karma/lib/launchers/base.js
generated
vendored
Normal file
140
node_modules/karma/lib/launchers/base.js
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
const KarmaEventEmitter = require('../events').EventEmitter
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
|
||||
const log = require('../logger').create('launcher')
|
||||
const helper = require('../helper')
|
||||
|
||||
const BEING_CAPTURED = 'BEING_CAPTURED'
|
||||
const CAPTURED = 'CAPTURED'
|
||||
const BEING_KILLED = 'BEING_KILLED'
|
||||
const FINISHED = 'FINISHED'
|
||||
const RESTARTING = 'RESTARTING'
|
||||
const BEING_FORCE_KILLED = 'BEING_FORCE_KILLED'
|
||||
|
||||
/**
|
||||
* Base launcher that any custom launcher extends.
|
||||
*/
|
||||
function BaseLauncher (id, emitter) {
|
||||
if (this.start) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(vojta): figure out how to do inheritance with DI
|
||||
Object.keys(EventEmitter.prototype).forEach(function (method) {
|
||||
this[method] = EventEmitter.prototype[method]
|
||||
}, this)
|
||||
|
||||
this.bind = KarmaEventEmitter.prototype.bind.bind(this)
|
||||
this.emitAsync = KarmaEventEmitter.prototype.emitAsync.bind(this)
|
||||
|
||||
this.id = id
|
||||
this._state = null
|
||||
Object.defineProperty(this, 'state', {
|
||||
get: () => {
|
||||
return this._state
|
||||
},
|
||||
set: (toState) => {
|
||||
log.debug(`${this._state} -> ${toState}`)
|
||||
this._state = toState
|
||||
}
|
||||
})
|
||||
|
||||
this.error = null
|
||||
|
||||
let killingPromise
|
||||
let previousUrl
|
||||
|
||||
this.start = function (url) {
|
||||
previousUrl = url
|
||||
|
||||
this.error = null
|
||||
this.state = BEING_CAPTURED
|
||||
this.emit('start', url + '?id=' + this.id + (helper.isDefined(this.displayName) ? '&displayName=' + encodeURIComponent(this.displayName) : ''))
|
||||
}
|
||||
|
||||
this.kill = function () {
|
||||
// Already killed, or being killed.
|
||||
if (killingPromise) {
|
||||
return killingPromise
|
||||
}
|
||||
|
||||
killingPromise = this.emitAsync('kill').then(() => {
|
||||
this.state = FINISHED
|
||||
})
|
||||
|
||||
this.state = BEING_KILLED
|
||||
|
||||
return killingPromise
|
||||
}
|
||||
|
||||
this.forceKill = function () {
|
||||
this.kill()
|
||||
this.state = BEING_FORCE_KILLED
|
||||
|
||||
return killingPromise
|
||||
}
|
||||
|
||||
this.restart = function () {
|
||||
if (this.state === BEING_FORCE_KILLED) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!killingPromise) {
|
||||
killingPromise = this.emitAsync('kill')
|
||||
}
|
||||
|
||||
killingPromise.then(() => {
|
||||
if (this.state === BEING_FORCE_KILLED) {
|
||||
this.state = FINISHED
|
||||
} else {
|
||||
killingPromise = null
|
||||
log.debug(`Restarting ${this.name}`)
|
||||
this.start(previousUrl)
|
||||
}
|
||||
})
|
||||
|
||||
this.state = RESTARTING
|
||||
}
|
||||
|
||||
this.markCaptured = function () {
|
||||
if (this.state === BEING_CAPTURED) {
|
||||
this.state = CAPTURED
|
||||
}
|
||||
}
|
||||
|
||||
this.isCaptured = function () {
|
||||
return this.state === CAPTURED
|
||||
}
|
||||
|
||||
this.toString = function () {
|
||||
return this.name
|
||||
}
|
||||
|
||||
this._done = function (error) {
|
||||
killingPromise = killingPromise || Promise.resolve()
|
||||
|
||||
this.error = this.error || error
|
||||
this.emit('done')
|
||||
|
||||
if (this.error && this.state !== BEING_FORCE_KILLED && this.state !== RESTARTING) {
|
||||
emitter.emit('browser_process_failure', this)
|
||||
}
|
||||
|
||||
this.state = FINISHED
|
||||
}
|
||||
|
||||
this.STATE_BEING_CAPTURED = BEING_CAPTURED
|
||||
this.STATE_CAPTURED = CAPTURED
|
||||
this.STATE_BEING_KILLED = BEING_KILLED
|
||||
this.STATE_FINISHED = FINISHED
|
||||
this.STATE_RESTARTING = RESTARTING
|
||||
this.STATE_BEING_FORCE_KILLED = BEING_FORCE_KILLED
|
||||
}
|
||||
|
||||
BaseLauncher.decoratorFactory = function (id, emitter) {
|
||||
return function (launcher) {
|
||||
BaseLauncher.call(launcher, id, emitter)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseLauncher
|
43
node_modules/karma/lib/launchers/capture_timeout.js
generated
vendored
Normal file
43
node_modules/karma/lib/launchers/capture_timeout.js
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
const log = require('../logger').create('launcher')
|
||||
|
||||
/**
|
||||
* Kill browser if it does not capture in given `captureTimeout`.
|
||||
*/
|
||||
function CaptureTimeoutLauncher (timer, captureTimeout) {
|
||||
if (!captureTimeout) {
|
||||
return
|
||||
}
|
||||
|
||||
let pendingTimeoutId = null
|
||||
|
||||
this.on('start', () => {
|
||||
pendingTimeoutId = timer.setTimeout(() => {
|
||||
pendingTimeoutId = null
|
||||
if (this.state !== this.STATE_BEING_CAPTURED) {
|
||||
return
|
||||
}
|
||||
|
||||
log.warn(`${this.name} has not captured in ${captureTimeout} ms, killing.`)
|
||||
this.error = 'timeout'
|
||||
this.kill()
|
||||
}, captureTimeout)
|
||||
})
|
||||
|
||||
this.on('done', () => {
|
||||
if (pendingTimeoutId) {
|
||||
timer.clearTimeout(pendingTimeoutId)
|
||||
pendingTimeoutId = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
CaptureTimeoutLauncher.decoratorFactory = function (timer,
|
||||
/* config.captureTimeout */ captureTimeout) {
|
||||
return function (launcher) {
|
||||
CaptureTimeoutLauncher.call(launcher, timer, captureTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
CaptureTimeoutLauncher.decoratorFactory.$inject = ['timer', 'config.captureTimeout']
|
||||
|
||||
module.exports = CaptureTimeoutLauncher
|
185
node_modules/karma/lib/launchers/process.js
generated
vendored
Normal file
185
node_modules/karma/lib/launchers/process.js
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
const path = require('path')
|
||||
const log = require('../logger').create('launcher')
|
||||
const env = process.env
|
||||
|
||||
function ProcessLauncher (spawn, tempDir, timer, processKillTimeout) {
|
||||
const self = this
|
||||
let onExitCallback
|
||||
const killTimeout = processKillTimeout || 2000
|
||||
// Will hold output from the spawned child process
|
||||
const streamedOutputs = {
|
||||
stdout: '',
|
||||
stderr: ''
|
||||
}
|
||||
|
||||
this._tempDir = tempDir.getPath(`/karma-${this.id.toString()}`)
|
||||
|
||||
this.on('start', function (url) {
|
||||
tempDir.create(self._tempDir)
|
||||
self._start(url)
|
||||
})
|
||||
|
||||
this.on('kill', function (done) {
|
||||
if (!self._process) {
|
||||
return process.nextTick(done)
|
||||
}
|
||||
|
||||
onExitCallback = done
|
||||
self._process.kill()
|
||||
self._killTimer = timer.setTimeout(self._onKillTimeout, killTimeout)
|
||||
})
|
||||
|
||||
this._start = function (url) {
|
||||
self._execCommand(self._getCommand(), self._getOptions(url))
|
||||
}
|
||||
|
||||
this._getCommand = function () {
|
||||
return env[self.ENV_CMD] || self.DEFAULT_CMD[process.platform]
|
||||
}
|
||||
|
||||
this._getOptions = function (url) {
|
||||
return [url]
|
||||
}
|
||||
|
||||
// Normalize the command, remove quotes (spawn does not like them).
|
||||
this._normalizeCommand = function (cmd) {
|
||||
if (cmd.charAt(0) === cmd.charAt(cmd.length - 1) && '\'`"'.includes(cmd.charAt(0))) {
|
||||
cmd = cmd.slice(1, -1)
|
||||
log.warn(`The path should not be quoted.\n Normalized the path to ${cmd}`)
|
||||
}
|
||||
|
||||
return path.normalize(cmd)
|
||||
}
|
||||
|
||||
this._onStdout = function (data) {
|
||||
streamedOutputs.stdout += data
|
||||
}
|
||||
|
||||
this._onStderr = function (data) {
|
||||
streamedOutputs.stderr += data
|
||||
}
|
||||
|
||||
this._execCommand = function (cmd, args) {
|
||||
if (!cmd) {
|
||||
log.error(`No binary for ${self.name} browser on your platform.\n Please, set "${self.ENV_CMD}" env variable.`)
|
||||
|
||||
// disable restarting
|
||||
self._retryLimit = -1
|
||||
|
||||
return self._clearTempDirAndReportDone('no binary')
|
||||
}
|
||||
|
||||
cmd = this._normalizeCommand(cmd)
|
||||
|
||||
log.debug(cmd + ' ' + args.join(' '))
|
||||
self._process = spawn(cmd, args)
|
||||
let errorOutput = ''
|
||||
|
||||
self._process.stdout.on('data', self._onStdout)
|
||||
|
||||
self._process.stderr.on('data', self._onStderr)
|
||||
|
||||
self._process.on('exit', function (code, signal) {
|
||||
self._onProcessExit(code, signal, errorOutput)
|
||||
})
|
||||
|
||||
self._process.on('error', function (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
self._retryLimit = -1
|
||||
errorOutput = `Can not find the binary ${cmd}\n\tPlease set env variable ${self.ENV_CMD}`
|
||||
} else if (err.code === 'EACCES') {
|
||||
self._retryLimit = -1
|
||||
errorOutput = `Permission denied accessing the binary ${cmd}\n\tMaybe it's a directory?`
|
||||
} else {
|
||||
errorOutput += err.toString()
|
||||
}
|
||||
self._onProcessExit(-1, null, errorOutput)
|
||||
})
|
||||
|
||||
self._process.stderr.on('data', function (errBuff) {
|
||||
errorOutput += errBuff.toString()
|
||||
})
|
||||
}
|
||||
|
||||
this._onProcessExit = function (code, signal, errorOutput) {
|
||||
if (!self._process) {
|
||||
// Both exit and error events trigger _onProcessExit(), but we only need one cleanup.
|
||||
return
|
||||
}
|
||||
log.debug(`Process ${self.name} exited with code ${code} and signal ${signal}`)
|
||||
|
||||
let error = null
|
||||
|
||||
if (self.state === self.STATE_BEING_CAPTURED) {
|
||||
log.error(`Cannot start ${self.name}\n\t${errorOutput}`)
|
||||
error = 'cannot start'
|
||||
}
|
||||
|
||||
if (self.state === self.STATE_CAPTURED) {
|
||||
log.error(`${self.name} crashed.\n\t${errorOutput}`)
|
||||
error = 'crashed'
|
||||
}
|
||||
|
||||
if (error) {
|
||||
log.error(`${self.name} stdout: ${streamedOutputs.stdout}`)
|
||||
log.error(`${self.name} stderr: ${streamedOutputs.stderr}`)
|
||||
}
|
||||
|
||||
self._process = null
|
||||
streamedOutputs.stdout = ''
|
||||
streamedOutputs.stderr = ''
|
||||
if (self._killTimer) {
|
||||
timer.clearTimeout(self._killTimer)
|
||||
self._killTimer = null
|
||||
}
|
||||
self._clearTempDirAndReportDone(error)
|
||||
}
|
||||
|
||||
this._clearTempDirAndReportDone = function (error) {
|
||||
tempDir.remove(self._tempDir, function () {
|
||||
self._done(error)
|
||||
if (onExitCallback) {
|
||||
onExitCallback()
|
||||
onExitCallback = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this._onKillTimeout = function () {
|
||||
if (self.state !== self.STATE_BEING_KILLED && self.state !== self.STATE_BEING_FORCE_KILLED) {
|
||||
return
|
||||
}
|
||||
|
||||
log.warn(`${self.name} was not killed in ${killTimeout} ms, sending SIGKILL.`)
|
||||
self._process.kill('SIGKILL')
|
||||
|
||||
// NOTE: https://github.com/karma-runner/karma/pull/1184
|
||||
// NOTE: SIGKILL is just a signal. Processes should never ignore it, but they can.
|
||||
// If a process gets into a state where it doesn't respond in a reasonable amount of time
|
||||
// Karma should warn, and continue as though the kill succeeded.
|
||||
// This a certainly suboptimal, but it is better than having the test harness hang waiting
|
||||
// for a zombie child process to exit.
|
||||
self._killTimer = timer.setTimeout(function () {
|
||||
log.warn(`${self.name} was not killed by SIGKILL in ${killTimeout} ms, continuing.`)
|
||||
self._onProcessExit(-1, null, '')
|
||||
}, killTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
ProcessLauncher.decoratorFactory = function (timer) {
|
||||
return function (launcher, processKillTimeout) {
|
||||
const spawn = require('child_process').spawn
|
||||
|
||||
function spawnWithoutOutput () {
|
||||
const proc = spawn.apply(null, arguments)
|
||||
proc.stdout.resume()
|
||||
proc.stderr.resume()
|
||||
|
||||
return proc
|
||||
}
|
||||
|
||||
ProcessLauncher.call(launcher, spawnWithoutOutput, require('../temp_dir'), timer, processKillTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProcessLauncher
|
31
node_modules/karma/lib/launchers/retry.js
generated
vendored
Normal file
31
node_modules/karma/lib/launchers/retry.js
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
const log = require('../logger').create('launcher')
|
||||
|
||||
function RetryLauncher (retryLimit) {
|
||||
this._retryLimit = retryLimit
|
||||
|
||||
this.on('done', () => {
|
||||
if (!this.error) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._retryLimit > 0) {
|
||||
log.info(`Trying to start ${this.name} again (${retryLimit - this._retryLimit + 1}/${retryLimit}).`)
|
||||
this.restart()
|
||||
this._retryLimit--
|
||||
} else if (this._retryLimit === 0) {
|
||||
log.error(`${this.name} failed ${retryLimit} times (${this.error}). Giving up.`)
|
||||
} else {
|
||||
log.debug(`${this.name} failed (${this.error}). Not restarting.`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RetryLauncher.decoratorFactory = function (retryLimit) {
|
||||
return function (launcher) {
|
||||
RetryLauncher.call(launcher, retryLimit)
|
||||
}
|
||||
}
|
||||
|
||||
RetryLauncher.decoratorFactory.$inject = ['config.retryLimit']
|
||||
|
||||
module.exports = RetryLauncher
|
115
node_modules/karma/lib/logger.js
generated
vendored
Normal file
115
node_modules/karma/lib/logger.js
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
// This is the **logger** module for *Karma*. It uses
|
||||
// [log4js](https://github.com/nomiddlename/log4js-node) to handle and
|
||||
// configure all logging that happens inside of *Karma*.
|
||||
|
||||
// ### Helpers and Setup
|
||||
|
||||
let log4js = require('log4js')
|
||||
const helper = require('./helper')
|
||||
const constant = require('./constants')
|
||||
|
||||
// #### Public Functions
|
||||
|
||||
// Setup the logger by passing in the configuration options. It needs
|
||||
// three arguments:
|
||||
//
|
||||
// setup(logLevel, colors, appenders)
|
||||
//
|
||||
// * `logLevel`: *String* Defines the global log level.
|
||||
// * `colors`: *Boolean* Use colors in the stdout or not.
|
||||
// * `appenders`: *Object* This will be passed as appenders to log4js
|
||||
// to allow for fine grained configuration of log4js. For more information
|
||||
// see https://github.com/nomiddlename/log4js-node.
|
||||
// *Array* is also accepted for backwards compatibility.
|
||||
function setup (level, colors, appenders) {
|
||||
// Turn color on/off on the console appenders with pattern layout
|
||||
const pattern = colors ? constant.COLOR_PATTERN : constant.NO_COLOR_PATTERN
|
||||
if (appenders) {
|
||||
// Convert Array to Object for backwards compatibility.
|
||||
if (appenders.map) {
|
||||
if (appenders.length === 0) {
|
||||
appenders = [constant.CONSOLE_APPENDER]
|
||||
}
|
||||
const v1Appenders = appenders
|
||||
appenders = {}
|
||||
v1Appenders.forEach(function (appender, index) {
|
||||
if (appender.type === 'console') {
|
||||
appenders.console = appender
|
||||
if (helper.isDefined(appender.layout) && appender.layout.type === 'pattern') {
|
||||
appender.layout.pattern = pattern
|
||||
}
|
||||
} else {
|
||||
appenders[index + ''] = appender
|
||||
}
|
||||
return appender
|
||||
})
|
||||
}
|
||||
} else {
|
||||
appenders = { console: constant.CONSOLE_APPENDER }
|
||||
}
|
||||
|
||||
log4js.configure({
|
||||
appenders: appenders,
|
||||
categories: {
|
||||
default: {
|
||||
appenders: Object.keys(appenders),
|
||||
level: level
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Setup the logger by passing in the config object. The function sets the
|
||||
// `colors` and `logLevel` if they are defined. It takes two arguments:
|
||||
//
|
||||
// setupFromConfig(config, appenders)
|
||||
//
|
||||
// * `config`: *Object* The configuration object.
|
||||
// * `appenders`: *Object* This will be passed as appenders to log4js
|
||||
// to allow for fine grained configuration of log4js. For more information
|
||||
// see https://github.com/nomiddlename/log4js-node.
|
||||
// *Array* is also accepted for backwards compatibility.
|
||||
function setupFromConfig (config, appenders) {
|
||||
let useColors = true
|
||||
let logLevel = constant.LOG_INFO
|
||||
|
||||
if (helper.isDefined(config.colors)) {
|
||||
useColors = config.colors
|
||||
}
|
||||
|
||||
if (helper.isDefined(config.logLevel)) {
|
||||
logLevel = config.logLevel
|
||||
}
|
||||
setup(logLevel, useColors, appenders)
|
||||
}
|
||||
|
||||
const loggerCache = {}
|
||||
|
||||
// Create a new logger. There are two optional arguments
|
||||
// * `name`, which defaults to `karma` and
|
||||
// If the `name = 'socket.io'` this will create a special wrapper
|
||||
// to be used as a logger for socket.io.
|
||||
// * `level`, which defaults to the global level.
|
||||
function create (name, level) {
|
||||
name = name || 'karma'
|
||||
let logger
|
||||
if (Object.prototype.hasOwnProperty.call(loggerCache, name)) {
|
||||
logger = loggerCache[name]
|
||||
} else {
|
||||
logger = log4js.getLogger(name)
|
||||
loggerCache[name] = logger
|
||||
}
|
||||
if (helper.isDefined(level)) {
|
||||
logger.setLevel(level)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// #### Publish
|
||||
|
||||
exports.create = create
|
||||
exports.setup = setup
|
||||
exports.setupFromConfig = setupFromConfig
|
||||
exports._rebindLog4js4testing = function (mockLog4js) {
|
||||
log4js = mockLog4js
|
||||
}
|
105
node_modules/karma/lib/middleware/common.js
generated
vendored
Normal file
105
node_modules/karma/lib/middleware/common.js
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* This module contains some common helpers shared between middlewares
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const mime = require('mime')
|
||||
const parseRange = require('range-parser')
|
||||
const log = require('../logger').create('web-server')
|
||||
|
||||
function createServeFile (fs, directory, config) {
|
||||
const cache = Object.create(null)
|
||||
|
||||
return function (filepath, rangeHeader, response, transform, content, doNotCache) {
|
||||
let responseData
|
||||
|
||||
function convertForRangeRequest () {
|
||||
const range = parseRange(responseData.length, rangeHeader)
|
||||
if (range === -2) {
|
||||
return 200 // malformed header string
|
||||
} else if (range === -1) {
|
||||
responseData = Buffer.alloc(0) // unsatisfiable range
|
||||
return 416
|
||||
} else if (range.type === 'bytes') {
|
||||
responseData = Buffer.from(responseData)
|
||||
if (range.length === 1) {
|
||||
const { start, end } = range[0]
|
||||
response.setHeader('Content-Range', `bytes ${start}-${end}/${responseData.length}`)
|
||||
response.setHeader('Accept-Ranges', 'bytes')
|
||||
response.setHeader('Content-Length', end - start + 1)
|
||||
responseData = responseData.slice(start, end + 1)
|
||||
return 206
|
||||
} else {
|
||||
responseData = Buffer.alloc(0) // Multiple ranges are not supported. Maybe future?
|
||||
return 416
|
||||
}
|
||||
}
|
||||
return 200 // All other states, ignore
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
filepath = directory + filepath
|
||||
}
|
||||
|
||||
if (!content && cache[filepath]) {
|
||||
content = cache[filepath]
|
||||
}
|
||||
|
||||
if (config && config.customHeaders && config.customHeaders.length > 0) {
|
||||
config.customHeaders.forEach((header) => {
|
||||
const regex = new RegExp(header.match)
|
||||
if (regex.test(filepath)) {
|
||||
log.debug(`setting header: ${header.name} for: ${filepath}`)
|
||||
response.setHeader(header.name, header.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (content && !doNotCache) {
|
||||
log.debug(`serving (cached): ${filepath}`)
|
||||
response.setHeader('Content-Type', mime.getType(filepath, 'text/plain'))
|
||||
responseData = (transform && transform(content)) || content
|
||||
response.writeHead(rangeHeader ? convertForRangeRequest() : 200)
|
||||
return response.end(responseData)
|
||||
}
|
||||
|
||||
return fs.readFile(filepath, function (error, data) {
|
||||
if (error) {
|
||||
return serve404(response, filepath)
|
||||
}
|
||||
|
||||
if (!doNotCache) {
|
||||
cache[filepath] = data.toString()
|
||||
}
|
||||
|
||||
log.debug('serving: ' + filepath)
|
||||
response.setHeader('Content-Type', mime.getType(filepath, 'text/plain'))
|
||||
responseData = (transform && transform(data.toString())) || data
|
||||
response.writeHead(rangeHeader ? convertForRangeRequest() : 200)
|
||||
|
||||
return response.end(responseData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function serve404 (response, path) {
|
||||
log.warn(`404: ${path}`)
|
||||
response.writeHead(404)
|
||||
return response.end('NOT FOUND')
|
||||
}
|
||||
|
||||
function setNoCacheHeaders (response) {
|
||||
response.setHeader('Cache-Control', 'no-cache')
|
||||
response.setHeader('Pragma', 'no-cache')
|
||||
response.setHeader('Expires', (new Date(0)).toUTCString())
|
||||
}
|
||||
|
||||
function setHeavyCacheHeaders (response) {
|
||||
response.setHeader('Cache-Control', 'public, max-age=31536000')
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
exports.createServeFile = createServeFile
|
||||
exports.setNoCacheHeaders = setNoCacheHeaders
|
||||
exports.setHeavyCacheHeaders = setHeavyCacheHeaders
|
||||
exports.serve404 = serve404
|
259
node_modules/karma/lib/middleware/karma.js
generated
vendored
Normal file
259
node_modules/karma/lib/middleware/karma.js
generated
vendored
Normal file
|
@ -0,0 +1,259 @@
|
|||
/**
|
||||
* Karma middleware is responsible for serving:
|
||||
* - client.html (the entrypoint for capturing a browser)
|
||||
* - debug.html
|
||||
* - context.html (the execution context, loaded within an iframe)
|
||||
* - karma.js
|
||||
*
|
||||
* The main part is generating context.html, as it contains:
|
||||
* - generating mappings
|
||||
* - including <script> and <link> tags
|
||||
* - setting propert caching headers
|
||||
*/
|
||||
|
||||
const url = require('url')
|
||||
|
||||
const log = require('../logger').create('middleware:karma')
|
||||
const stripHost = require('./strip_host').stripHost
|
||||
const common = require('./common')
|
||||
|
||||
const VERSION = require('../constants').VERSION
|
||||
const SCRIPT_TYPE = {
|
||||
js: 'text/javascript',
|
||||
module: 'module'
|
||||
}
|
||||
const FILE_TYPES = [
|
||||
'css',
|
||||
'html',
|
||||
'js',
|
||||
'module',
|
||||
'dom'
|
||||
]
|
||||
|
||||
function filePathToUrlPath (filePath, basePath, urlRoot, proxyPath) {
|
||||
if (filePath.startsWith(basePath)) {
|
||||
return proxyPath + urlRoot.slice(1) + 'base' + filePath.slice(basePath.length)
|
||||
}
|
||||
return proxyPath + urlRoot.slice(1) + 'absolute' + filePath
|
||||
}
|
||||
|
||||
function getQuery (urlStr) {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
return url.parse(urlStr, true).query || {}
|
||||
}
|
||||
|
||||
function getXUACompatibleMetaElement (url) {
|
||||
const query = getQuery(url)
|
||||
if (query['x-ua-compatible']) {
|
||||
return `<meta http-equiv="X-UA-Compatible" content="${query['x-ua-compatible']}"/>`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function getXUACompatibleUrl (url) {
|
||||
const query = getQuery(url)
|
||||
if (query['x-ua-compatible']) {
|
||||
return '?x-ua-compatible=' + encodeURIComponent(query['x-ua-compatible'])
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function createKarmaMiddleware (
|
||||
filesPromise,
|
||||
serveStaticFile,
|
||||
serveFile,
|
||||
injector,
|
||||
basePath,
|
||||
urlRoot,
|
||||
upstreamProxy,
|
||||
browserSocketTimeout
|
||||
) {
|
||||
const proxyPath = upstreamProxy ? upstreamProxy.path : '/'
|
||||
return function (request, response, next) {
|
||||
// These config values should be up to date on every request
|
||||
const client = injector.get('config.client')
|
||||
const customContextFile = injector.get('config.customContextFile')
|
||||
const customDebugFile = injector.get('config.customDebugFile')
|
||||
const customClientContextFile = injector.get('config.customClientContextFile')
|
||||
const includeCrossOriginAttribute = injector.get('config.crossOriginAttribute')
|
||||
|
||||
const normalizedUrl = stripHost(request.url) || request.url
|
||||
// For backwards compatibility in middleware plugins, remove in v4.
|
||||
request.normalizedUrl = normalizedUrl
|
||||
|
||||
let requestUrl = normalizedUrl.replace(/\?.*/, '')
|
||||
const requestedRangeHeader = request.headers.range
|
||||
|
||||
// redirect /__karma__ to /__karma__ (trailing slash)
|
||||
if (requestUrl === urlRoot.slice(0, -1)) {
|
||||
response.setHeader('Location', proxyPath + urlRoot.slice(1))
|
||||
response.writeHead(301)
|
||||
return response.end('MOVED PERMANENTLY')
|
||||
}
|
||||
|
||||
// ignore urls outside urlRoot
|
||||
if (!requestUrl.startsWith(urlRoot)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// remove urlRoot prefix
|
||||
requestUrl = requestUrl.slice(urlRoot.length - 1)
|
||||
|
||||
// serve client.html
|
||||
if (requestUrl === '/') {
|
||||
// redirect client_with_context.html
|
||||
if (!client.useIframe && client.runInParent) {
|
||||
requestUrl = '/client_with_context.html'
|
||||
} else { // serve client.html
|
||||
return serveStaticFile('/client.html', requestedRangeHeader, response, (data) =>
|
||||
data
|
||||
.replace('%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url))
|
||||
.replace('%X_UA_COMPATIBLE_URL%', getXUACompatibleUrl(request.url)))
|
||||
}
|
||||
}
|
||||
|
||||
if (['/karma.js', '/context.js', '/debug.js'].includes(requestUrl)) {
|
||||
return serveStaticFile(requestUrl, requestedRangeHeader, response, (data) =>
|
||||
data
|
||||
.replace('%KARMA_URL_ROOT%', urlRoot)
|
||||
.replace('%KARMA_VERSION%', VERSION)
|
||||
.replace('%KARMA_PROXY_PATH%', proxyPath)
|
||||
.replace('%BROWSER_SOCKET_TIMEOUT%', browserSocketTimeout))
|
||||
}
|
||||
|
||||
// serve the favicon
|
||||
if (requestUrl === '/favicon.ico') {
|
||||
return serveStaticFile(requestUrl, requestedRangeHeader, response)
|
||||
}
|
||||
|
||||
// serve context.html - execution context within the iframe
|
||||
// or debug.html - execution context without channel to the server
|
||||
const isRequestingContextFile = requestUrl === '/context.html'
|
||||
const isRequestingDebugFile = requestUrl === '/debug.html'
|
||||
const isRequestingClientContextFile = requestUrl === '/client_with_context.html'
|
||||
if (isRequestingContextFile || isRequestingDebugFile || isRequestingClientContextFile) {
|
||||
return filesPromise.then((files) => {
|
||||
let fileServer
|
||||
let requestedFileUrl
|
||||
log.debug('custom files', customContextFile, customDebugFile, customClientContextFile)
|
||||
if (isRequestingContextFile && customContextFile) {
|
||||
log.debug(`Serving customContextFile ${customContextFile}`)
|
||||
fileServer = serveFile
|
||||
requestedFileUrl = customContextFile
|
||||
} else if (isRequestingDebugFile && customDebugFile) {
|
||||
log.debug(`Serving customDebugFile ${customDebugFile}`)
|
||||
fileServer = serveFile
|
||||
requestedFileUrl = customDebugFile
|
||||
} else if (isRequestingClientContextFile && customClientContextFile) {
|
||||
log.debug(`Serving customClientContextFile ${customClientContextFile}`)
|
||||
fileServer = serveFile
|
||||
requestedFileUrl = customClientContextFile
|
||||
} else {
|
||||
log.debug(`Serving static request ${requestUrl}`)
|
||||
fileServer = serveStaticFile
|
||||
requestedFileUrl = requestUrl
|
||||
}
|
||||
|
||||
fileServer(requestedFileUrl, requestedRangeHeader, response, function (data) {
|
||||
common.setNoCacheHeaders(response)
|
||||
|
||||
const scriptTags = []
|
||||
for (const file of files.included) {
|
||||
let filePath = file.path
|
||||
const fileType = file.type || file.detectType()
|
||||
|
||||
if (!FILE_TYPES.includes(fileType)) {
|
||||
if (file.type == null) {
|
||||
log.warn(
|
||||
'Unable to determine file type from the file extension, defaulting to js.\n' +
|
||||
` To silence the warning specify a valid type for ${file.originalPath} in the configuration file.\n` +
|
||||
' See https://karma-runner.github.io/latest/config/files.html'
|
||||
)
|
||||
} else {
|
||||
log.warn(`Invalid file type (${file.type || 'empty string'}), defaulting to js.`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.isUrl) {
|
||||
filePath = filePathToUrlPath(filePath, basePath, urlRoot, proxyPath)
|
||||
|
||||
if (requestUrl === '/context.html') {
|
||||
filePath += '?' + file.sha
|
||||
}
|
||||
}
|
||||
|
||||
const integrityAttribute = file.integrity ? ` integrity="${file.integrity}"` : ''
|
||||
const crossOriginAttribute = includeCrossOriginAttribute ? ' crossorigin="anonymous"' : ''
|
||||
if (fileType === 'css') {
|
||||
scriptTags.push(`<link type="text/css" href="${filePath}" rel="stylesheet"${integrityAttribute}${crossOriginAttribute}>`)
|
||||
} else if (fileType === 'dom') {
|
||||
scriptTags.push(file.content)
|
||||
} else if (fileType === 'html') {
|
||||
scriptTags.push(`<link href="${filePath}" rel="import"${integrityAttribute}${crossOriginAttribute}>`)
|
||||
} else {
|
||||
const scriptType = (SCRIPT_TYPE[fileType] || 'text/javascript')
|
||||
if (fileType === 'module') {
|
||||
scriptTags.push(`<script onerror="throw 'Error loading ${filePath}'" type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
|
||||
} else {
|
||||
scriptTags.push(`<script type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const scriptUrls = []
|
||||
// For client_with_context, html elements are not added directly through an iframe.
|
||||
// Instead, scriptTags is stored to window.__karma__.scriptUrls first. Later, the
|
||||
// client will read window.__karma__.scriptUrls and dynamically add them to the DOM
|
||||
// using DOMParser.
|
||||
if (requestUrl === '/client_with_context.html') {
|
||||
for (const script of scriptTags) {
|
||||
scriptUrls.push(
|
||||
// Escape characters with special roles (tags) in HTML. Open angle brackets are parsed as tags
|
||||
// immediately, even if it is within double quotations in browsers
|
||||
script.replace(/</g, '\\x3C').replace(/>/g, '\\x3E'))
|
||||
}
|
||||
}
|
||||
|
||||
const mappings = data.includes('%MAPPINGS%') ? files.served.map((file) => {
|
||||
const filePath = filePathToUrlPath(file.path, basePath, urlRoot, proxyPath)
|
||||
.replace(/\\/g, '\\\\') // Windows paths contain backslashes and generate bad IDs if not escaped
|
||||
.replace(/'/g, '\\\'') // Escape single quotes - double quotes should not be allowed!
|
||||
|
||||
return ` '${filePath}': '${file.sha}'`
|
||||
}) : []
|
||||
|
||||
return data
|
||||
.replace('%SCRIPTS%', () => scriptTags.join('\n'))
|
||||
.replace('%CLIENT_CONFIG%', 'window.__karma__.config = ' + JSON.stringify(client) + ';\n')
|
||||
.replace('%SCRIPT_URL_ARRAY%', () => 'window.__karma__.scriptUrls = ' + JSON.stringify(scriptUrls) + ';\n')
|
||||
.replace('%MAPPINGS%', () => 'window.__karma__.files = {\n' + mappings.join(',\n') + '\n};\n')
|
||||
.replace('%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url))
|
||||
})
|
||||
})
|
||||
} else if (requestUrl === '/context.json') {
|
||||
return filesPromise.then((files) => {
|
||||
common.setNoCacheHeaders(response)
|
||||
response.writeHead(200)
|
||||
response.end(JSON.stringify({
|
||||
files: files.included.map((file) => filePathToUrlPath(file.path + '?' + file.sha, basePath, urlRoot, proxyPath))
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
createKarmaMiddleware.$inject = [
|
||||
'filesPromise',
|
||||
'serveStaticFile',
|
||||
'serveFile',
|
||||
'injector',
|
||||
'config.basePath',
|
||||
'config.urlRoot',
|
||||
'config.upstreamProxy',
|
||||
'config.browserSocketTimeout'
|
||||
]
|
||||
|
||||
// PUBLIC API
|
||||
exports.create = createKarmaMiddleware
|
130
node_modules/karma/lib/middleware/proxy.js
generated
vendored
Normal file
130
node_modules/karma/lib/middleware/proxy.js
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
const url = require('url')
|
||||
const { Agent: httpAgent } = require('http')
|
||||
const { Agent: httpsAgent } = require('https')
|
||||
const httpProxy = require('http-proxy')
|
||||
const _ = require('lodash')
|
||||
const { lookup } = require('../utils/dns-utils')
|
||||
|
||||
const log = require('../logger').create('proxy')
|
||||
|
||||
function parseProxyConfig (proxies, config) {
|
||||
proxies = proxies || []
|
||||
return _.sortBy(_.map(proxies, function (proxyConfiguration, proxyPath) {
|
||||
if (typeof proxyConfiguration === 'string') {
|
||||
proxyConfiguration = { target: proxyConfiguration }
|
||||
}
|
||||
let proxyUrl = proxyConfiguration.target
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const proxyDetails = url.parse(proxyUrl)
|
||||
let pathname = proxyDetails.pathname
|
||||
|
||||
if (proxyPath.endsWith('/') && !proxyUrl.endsWith('/')) {
|
||||
log.warn(`proxy "${proxyUrl}" normalized to "${proxyUrl}/"`)
|
||||
proxyUrl += '/'
|
||||
pathname += '/'
|
||||
}
|
||||
|
||||
if (!proxyPath.endsWith('/') && proxyUrl.endsWith('/')) {
|
||||
log.warn(`proxy "${proxyPath}" normalized to "${proxyPath}/"`)
|
||||
proxyPath += '/'
|
||||
}
|
||||
|
||||
if (pathname === '/' && !proxyUrl.endsWith('/')) {
|
||||
pathname = ''
|
||||
}
|
||||
|
||||
const hostname = proxyDetails.hostname || config.hostname
|
||||
const protocol = proxyDetails.protocol || config.protocol
|
||||
const defaultPorts = {
|
||||
'http:': '80',
|
||||
'https:': '443'
|
||||
}
|
||||
const port = proxyDetails.port || defaultPorts[proxyDetails.protocol] || config.port
|
||||
const changeOrigin = proxyConfiguration.changeOrigin || false
|
||||
const Agent = protocol === 'https:' ? httpsAgent : httpAgent
|
||||
const agent = new Agent({
|
||||
keepAlive: true,
|
||||
lookup
|
||||
})
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
target: { host: hostname, port, protocol },
|
||||
xfwd: true,
|
||||
changeOrigin: changeOrigin,
|
||||
secure: config.proxyValidateSSL,
|
||||
agent
|
||||
})
|
||||
|
||||
;['proxyReq', 'proxyRes'].forEach(function (name) {
|
||||
const callback = proxyDetails[name] || config[name]
|
||||
if (callback) {
|
||||
proxy.on(name, callback)
|
||||
}
|
||||
})
|
||||
|
||||
proxy.on('error', function proxyError (err, req, res) {
|
||||
if (err.code === 'ECONNRESET' && req.socket.destroyed) {
|
||||
log.debug(`failed to proxy ${req.url} (browser hung up the socket)`)
|
||||
} else {
|
||||
log.warn(`failed to proxy ${req.url} (${err.message})`)
|
||||
}
|
||||
|
||||
res.destroy()
|
||||
})
|
||||
|
||||
return { path: proxyPath, baseUrl: pathname, host: hostname, port, proxy, agent }
|
||||
}), 'path').reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handler which understands the proxies and its redirects, along with the proxy to use
|
||||
* @param proxies An array of proxy record objects
|
||||
* @param urlRoot The URL root that karma is mounted on
|
||||
* @return {Function} handler function
|
||||
*/
|
||||
function createProxyHandler (proxies, urlRoot) {
|
||||
if (!proxies.length) {
|
||||
const nullProxy = (request, response, next) => next()
|
||||
nullProxy.upgrade = () => {}
|
||||
return nullProxy
|
||||
}
|
||||
|
||||
function createProxy (request, response, next) {
|
||||
const proxyRecord = proxies.find((p) => request.url.startsWith(p.path))
|
||||
if (proxyRecord) {
|
||||
log.debug(`proxying request - ${request.url} to ${proxyRecord.host}:${proxyRecord.port}`)
|
||||
request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
|
||||
proxyRecord.proxy.web(request, response)
|
||||
} else {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
createProxy.upgrade = function (request, socket, head) {
|
||||
// special-case karma's route to avoid upgrading it
|
||||
if (request.url.startsWith(urlRoot)) {
|
||||
log.debug(`NOT upgrading proxyWebSocketRequest ${request.url}`)
|
||||
return
|
||||
}
|
||||
|
||||
const proxyRecord = proxies.find((p) => request.url.startsWith(p.path))
|
||||
if (proxyRecord) {
|
||||
log.debug(`upgrade proxyWebSocketRequest ${request.url} to ${proxyRecord.host}:${proxyRecord.port}`)
|
||||
request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
|
||||
proxyRecord.proxy.ws(request, socket, head)
|
||||
}
|
||||
}
|
||||
|
||||
return createProxy
|
||||
}
|
||||
|
||||
exports.create = function (/* config */config, /* config.proxies */proxies, /* emitter */emitter) {
|
||||
const proxyRecords = parseProxyConfig(proxies, config)
|
||||
emitter.on('exit', (done) => {
|
||||
log.debug('Destroying proxy agents')
|
||||
proxyRecords.forEach((proxyRecord) => {
|
||||
proxyRecord.agent.destroy()
|
||||
})
|
||||
done()
|
||||
})
|
||||
return createProxyHandler(proxyRecords, config.urlRoot)
|
||||
}
|
114
node_modules/karma/lib/middleware/runner.js
generated
vendored
Normal file
114
node_modules/karma/lib/middleware/runner.js
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Runner middleware is responsible for communication with `karma run`.
|
||||
*
|
||||
* It basically triggers a test run and streams stdout back.
|
||||
*/
|
||||
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const helper = require('../helper')
|
||||
const log = require('../logger').create()
|
||||
const constant = require('../constants')
|
||||
const json = require('body-parser').json()
|
||||
|
||||
// TODO(vojta): disable when single-run mode
|
||||
function createRunnerMiddleware (emitter, fileList, capturedBrowsers, reporter, executor,
|
||||
/* config.protocol */ protocol, /* config.hostname */ hostname, /* config.port */
|
||||
port, /* config.urlRoot */ urlRoot, config) {
|
||||
helper.saveOriginalArgs(config)
|
||||
return function (request, response, next) {
|
||||
if (request.url !== '/__run__' && request.url !== urlRoot + 'run') {
|
||||
return next()
|
||||
}
|
||||
|
||||
log.debug('Execution (fired by runner)')
|
||||
response.writeHead(200)
|
||||
|
||||
if (!capturedBrowsers.length) {
|
||||
const url = `${protocol}//${hostname}:${port}${urlRoot}`
|
||||
return response.end(`No captured browser, open ${url}\n`)
|
||||
}
|
||||
|
||||
json(request, response, function () {
|
||||
if (!capturedBrowsers.areAllReady([])) {
|
||||
response.write('Waiting for previous execution...\n')
|
||||
}
|
||||
|
||||
const data = request.body
|
||||
|
||||
updateClientArgs(data)
|
||||
handleRun(data)
|
||||
refreshFileList(data).then(() => {
|
||||
executor.schedule()
|
||||
}).catch((error) => {
|
||||
const errorMessage = `Error during refresh file list. ${error.stack || error}`
|
||||
executor.scheduleError(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
function updateClientArgs (data) {
|
||||
helper.restoreOriginalArgs(config)
|
||||
if (_.isEmpty(data.args)) {
|
||||
log.debug('Ignoring empty client.args from run command')
|
||||
} else if ((_.isArray(data.args) && _.isArray(config.client.args)) ||
|
||||
(_.isPlainObject(data.args) && _.isPlainObject(config.client.args))) {
|
||||
log.debug('Merging client.args with ', data.args)
|
||||
config.client.args = _.merge(config.client.args, data.args)
|
||||
} else {
|
||||
log.warn('Replacing client.args with ', data.args, ' as their types do not match.')
|
||||
config.client.args = data.args
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshFileList (data) {
|
||||
let fullRefresh = true
|
||||
|
||||
if (helper.isArray(data.changedFiles)) {
|
||||
await Promise.all(data.changedFiles.map(async function (filepath) {
|
||||
await fileList.changeFile(path.resolve(config.basePath, filepath))
|
||||
fullRefresh = false
|
||||
}))
|
||||
}
|
||||
|
||||
if (helper.isArray(data.addedFiles)) {
|
||||
await Promise.all(data.addedFiles.map(async function (filepath) {
|
||||
await fileList.addFile(path.resolve(config.basePath, filepath))
|
||||
fullRefresh = false
|
||||
}))
|
||||
}
|
||||
|
||||
if (helper.isArray(data.removedFiles)) {
|
||||
await Promise.all(data.removedFiles.map(async function (filepath) {
|
||||
await fileList.removeFile(path.resolve(config.basePath, filepath))
|
||||
fullRefresh = false
|
||||
}))
|
||||
}
|
||||
|
||||
if (fullRefresh && data.refresh !== false) {
|
||||
log.debug('Refreshing all the files / patterns')
|
||||
await fileList.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
function handleRun (data) {
|
||||
emitter.once('run_start', function () {
|
||||
const responseWrite = response.write.bind(response)
|
||||
responseWrite.colors = data.colors
|
||||
reporter.addAdapter(responseWrite)
|
||||
|
||||
// clean up, close runner response
|
||||
emitter.once('run_complete', function (_browsers, results) {
|
||||
reporter.removeAdapter(responseWrite)
|
||||
const emptyTestSuite = (results.failed + results.success) === 0 ? 0 : 1
|
||||
response.end(constant.EXIT_CODE + emptyTestSuite + results.exitCode)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createRunnerMiddleware.$inject = ['emitter', 'fileList', 'capturedBrowsers', 'reporter', 'executor',
|
||||
'config.protocol', 'config.hostname', 'config.port', 'config.urlRoot', 'config']
|
||||
|
||||
// PUBLIC API
|
||||
exports.create = createRunnerMiddleware
|
67
node_modules/karma/lib/middleware/source_files.js
generated
vendored
Normal file
67
node_modules/karma/lib/middleware/source_files.js
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
'use strict'
|
||||
|
||||
const querystring = require('querystring')
|
||||
const common = require('./common')
|
||||
|
||||
const log = require('../logger').create('middleware:source-files')
|
||||
|
||||
function findByPath (files, path) {
|
||||
return Array.from(files).find((file) => file.path === path)
|
||||
}
|
||||
|
||||
function composeUrl (url, basePath, urlRoot) {
|
||||
return url
|
||||
.replace(urlRoot, '/')
|
||||
.replace(/\?.*$/, '')
|
||||
.replace(/^\/absolute/, '')
|
||||
.replace(/^\/base/, basePath)
|
||||
}
|
||||
|
||||
// Source Files middleware is responsible for serving all the source files under the test.
|
||||
function createSourceFilesMiddleware (filesPromise, serveFile, basePath, urlRoot) {
|
||||
return function (request, response, next) {
|
||||
const requestedFilePath = composeUrl(request.url, basePath, urlRoot)
|
||||
// When a path contains HTML-encoded characters (e.g %2F used by Jenkins for branches with /)
|
||||
const requestedFilePathUnescaped = composeUrl(querystring.unescape(request.url), basePath, urlRoot)
|
||||
|
||||
request.pause()
|
||||
|
||||
log.debug(`Requesting ${request.url}`)
|
||||
log.debug(`Fetching ${requestedFilePath}`)
|
||||
|
||||
return filesPromise.then(function (files) {
|
||||
// TODO(vojta): change served to be a map rather then an array
|
||||
const file = findByPath(files.served, requestedFilePath) || findByPath(files.served, requestedFilePathUnescaped)
|
||||
const rangeHeader = request.headers.range
|
||||
|
||||
if (file) {
|
||||
const acceptEncodingHeader = request.headers['accept-encoding']
|
||||
const matchedEncoding = Object.keys(file.encodings).find(
|
||||
(encoding) => new RegExp(`(^|.*, ?)${encoding}(,|$)`).test(acceptEncodingHeader)
|
||||
)
|
||||
const content = file.encodings[matchedEncoding] || file.content
|
||||
|
||||
serveFile(file.contentPath || file.path, rangeHeader, response, function () {
|
||||
if (/\?\w+/.test(request.url)) {
|
||||
common.setHeavyCacheHeaders(response) // files with timestamps - cache one year, rely on timestamps
|
||||
} else {
|
||||
common.setNoCacheHeaders(response) // without timestamps - no cache (debug)
|
||||
}
|
||||
if (matchedEncoding) {
|
||||
response.setHeader('Content-Encoding', matchedEncoding)
|
||||
}
|
||||
}, content, file.doNotCache)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
||||
request.resume()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
createSourceFilesMiddleware.$inject = [
|
||||
'filesPromise', 'serveFile', 'config.basePath', 'config.urlRoot'
|
||||
]
|
||||
|
||||
exports.create = createSourceFilesMiddleware
|
18
node_modules/karma/lib/middleware/stopper.js
generated
vendored
Normal file
18
node_modules/karma/lib/middleware/stopper.js
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Stopper middleware is responsible for communicating with `karma stop`.
|
||||
*/
|
||||
|
||||
const log = require('../logger').create('middleware:stopper')
|
||||
|
||||
function createStopperMiddleware (urlRoot) {
|
||||
return function (request, response, next) {
|
||||
if (request.url !== urlRoot + 'stop') return next()
|
||||
response.writeHead(200)
|
||||
log.info('Stopping server')
|
||||
response.end('OK')
|
||||
process.kill(process.pid, 'SIGINT')
|
||||
}
|
||||
}
|
||||
|
||||
createStopperMiddleware.$inject = ['config.urlRoot']
|
||||
exports.create = createStopperMiddleware
|
11
node_modules/karma/lib/middleware/strip_host.js
generated
vendored
Normal file
11
node_modules/karma/lib/middleware/strip_host.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Strip hostname from request path
|
||||
* This to handle requests that uses (normally over proxies) an absoluteURI as request path
|
||||
*/
|
||||
|
||||
function stripHostFromUrl (url) {
|
||||
return url.replace(/^https?:\/\/[a-z.:\d-]+\//, '/')
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
exports.stripHost = stripHostFromUrl
|
98
node_modules/karma/lib/plugin.js
generated
vendored
Normal file
98
node_modules/karma/lib/plugin.js
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
const path = require('path')
|
||||
const helper = require('./helper')
|
||||
|
||||
const log = require('./logger').create('plugin')
|
||||
|
||||
const IGNORED_PACKAGES = ['karma-cli', 'karma-runner.github.com']
|
||||
|
||||
function resolve (plugins, emitter) {
|
||||
const modules = []
|
||||
|
||||
function requirePlugin (name) {
|
||||
log.debug(`Loading plugin ${name}.`)
|
||||
try {
|
||||
modules.push(require(name))
|
||||
} catch (e) {
|
||||
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(name)) {
|
||||
log.error(`Cannot find plugin "${name}".\n Did you forget to install it?\n npm install ${name} --save-dev`)
|
||||
} else {
|
||||
log.error(`Error during loading "${name}" plugin:\n ${e.message}`)
|
||||
}
|
||||
emitter.emit('load_error', 'plug_in', name)
|
||||
}
|
||||
}
|
||||
|
||||
plugins.forEach(function (plugin) {
|
||||
if (helper.isString(plugin)) {
|
||||
if (!plugin.includes('*')) {
|
||||
requirePlugin(plugin)
|
||||
return
|
||||
}
|
||||
const pluginDirectory = path.normalize(path.join(__dirname, '/../..'))
|
||||
const regexp = new RegExp(`^${plugin.replace(/\*/g, '.*').replace(/\//g, '[/\\\\]')}`)
|
||||
|
||||
log.debug(`Loading ${plugin} from ${pluginDirectory}`)
|
||||
fs.readdirSync(pluginDirectory)
|
||||
.map((e) => {
|
||||
const modulePath = path.join(pluginDirectory, e)
|
||||
if (e[0] === '@') {
|
||||
return fs.readdirSync(modulePath).map((e) => path.join(modulePath, e))
|
||||
}
|
||||
return modulePath
|
||||
})
|
||||
.reduce((a, x) => a.concat(x), [])
|
||||
.map((modulePath) => path.relative(pluginDirectory, modulePath))
|
||||
.filter((moduleName) => !IGNORED_PACKAGES.includes(moduleName) && regexp.test(moduleName))
|
||||
.forEach((pluginName) => requirePlugin(path.join(pluginDirectory, pluginName)))
|
||||
} else if (helper.isObject(plugin)) {
|
||||
log.debug(`Loading inline plugin defining ${Object.keys(plugin).join(', ')}.`)
|
||||
modules.push(plugin)
|
||||
} else {
|
||||
log.error(`Invalid plugin ${plugin}`)
|
||||
emitter.emit('load_error', 'plug_in', plugin)
|
||||
}
|
||||
})
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
/**
|
||||
Create a function to handle errors in plugin loading.
|
||||
@param {Object} injector, the dict of dependency injection objects.
|
||||
@return function closed over injector, which reports errors.
|
||||
*/
|
||||
function createInstantiatePlugin (injector) {
|
||||
const emitter = injector.get('emitter')
|
||||
// Cache to avoid report errors multiple times per plugin.
|
||||
const pluginInstances = new Map()
|
||||
return function instantiatePlugin (kind, name) {
|
||||
if (pluginInstances.has(name)) {
|
||||
return pluginInstances.get(name)
|
||||
}
|
||||
|
||||
let p
|
||||
try {
|
||||
p = injector.get(`${kind}:${name}`)
|
||||
if (!p) {
|
||||
log.error(`Failed to instantiate ${kind} ${name}`)
|
||||
emitter.emit('load_error', kind, name)
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message.includes(`No provider for "${kind}:${name}"`)) {
|
||||
log.error(`Cannot load "${name}", it is not registered!\n Perhaps you are missing some plugin?`)
|
||||
} else {
|
||||
log.error(`Cannot load "${name}"!\n ` + e.stack)
|
||||
}
|
||||
emitter.emit('load_error', kind, name)
|
||||
}
|
||||
pluginInstances.set(name, p, `${kind}:${name}`)
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
createInstantiatePlugin.$inject = ['injector']
|
||||
|
||||
module.exports = { resolve, createInstantiatePlugin }
|
111
node_modules/karma/lib/preprocessor.js
generated
vendored
Normal file
111
node_modules/karma/lib/preprocessor.js
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
'use strict'
|
||||
|
||||
const util = require('util')
|
||||
const fs = require('graceful-fs')
|
||||
// bind need only for mock unit tests
|
||||
const readFile = util.promisify(fs.readFile.bind(fs))
|
||||
const tryToRead = function (path, log) {
|
||||
const maxRetries = 3
|
||||
let promise = readFile(path)
|
||||
for (let retryCount = 1; retryCount <= maxRetries; retryCount++) {
|
||||
promise = promise.catch((err) => {
|
||||
log.warn(err)
|
||||
log.warn('retrying ' + retryCount)
|
||||
return readFile(path)
|
||||
})
|
||||
}
|
||||
return promise.catch((err) => {
|
||||
log.warn(err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
const mm = require('minimatch')
|
||||
const { isBinaryFile } = require('isbinaryfile')
|
||||
const _ = require('lodash')
|
||||
const CryptoUtils = require('./utils/crypto-utils')
|
||||
|
||||
const log = require('./logger').create('preprocess')
|
||||
|
||||
function executeProcessor (process, file, content) {
|
||||
let done = null
|
||||
const donePromise = new Promise((resolve, reject) => {
|
||||
done = function (error, content) {
|
||||
// normalize B-C
|
||||
if (arguments.length === 1 && typeof error === 'string') {
|
||||
content = error
|
||||
error = null
|
||||
}
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (process(content, file, done) || Promise.resolve()).then((content) => {
|
||||
if (content) {
|
||||
// async process correctly returned content
|
||||
return content
|
||||
}
|
||||
// process called done() (Either old sync api or an async function that did not return content)
|
||||
return donePromise
|
||||
})
|
||||
}
|
||||
|
||||
async function runProcessors (preprocessors, file, content) {
|
||||
try {
|
||||
for (const process of preprocessors) {
|
||||
content = await executeProcessor(process, file, content)
|
||||
}
|
||||
} catch (error) {
|
||||
file.contentPath = null
|
||||
file.content = null
|
||||
throw error
|
||||
}
|
||||
|
||||
file.contentPath = null
|
||||
file.content = content
|
||||
file.sha = CryptoUtils.sha1(content)
|
||||
}
|
||||
|
||||
function createPriorityPreprocessor (config = {}, preprocessorPriority, basePath, instantiatePlugin) {
|
||||
_.union.apply(_, Object.values(config)).forEach((name) => instantiatePlugin('preprocessor', name))
|
||||
return async function preprocess (file) {
|
||||
const buffer = await tryToRead(file.originalPath, log)
|
||||
let isBinary = file.isBinary
|
||||
if (isBinary == null) {
|
||||
// Pattern did not specify, probe for it.
|
||||
isBinary = await isBinaryFile(buffer, buffer.length)
|
||||
}
|
||||
|
||||
const preprocessorNames = Object.keys(config).reduce((ppNames, pattern) => {
|
||||
if (mm(file.originalPath, pattern, { dot: true })) {
|
||||
ppNames = _.union(ppNames, config[pattern])
|
||||
}
|
||||
return ppNames
|
||||
}, [])
|
||||
|
||||
// Apply preprocessor priority.
|
||||
const preprocessors = preprocessorNames
|
||||
.map((name) => [name, preprocessorPriority[name] || 0])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((duo) => duo[0])
|
||||
.reduce((preProcs, name) => {
|
||||
const p = instantiatePlugin('preprocessor', name)
|
||||
|
||||
if (!isBinary || (p && p.handleBinaryFiles)) {
|
||||
preProcs.push(p)
|
||||
} else {
|
||||
log.warn(`Ignored preprocessing ${file.originalPath} because ${name} has handleBinaryFiles=false.`)
|
||||
}
|
||||
return preProcs
|
||||
}, [])
|
||||
|
||||
await runProcessors(preprocessors, file, isBinary ? buffer : buffer.toString())
|
||||
}
|
||||
}
|
||||
|
||||
createPriorityPreprocessor.$inject = ['config.preprocessors', 'config.preprocessor_priority', 'config.basePath', 'instantiatePlugin']
|
||||
exports.createPriorityPreprocessor = createPriorityPreprocessor
|
152
node_modules/karma/lib/reporter.js
generated
vendored
Normal file
152
node_modules/karma/lib/reporter.js
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
'use strict'
|
||||
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const resolve = require('url').resolve
|
||||
const SourceMapConsumer = require('source-map').SourceMapConsumer
|
||||
const _ = require('lodash')
|
||||
|
||||
const PathUtils = require('./utils/path-utils')
|
||||
const log = require('./logger').create('reporter')
|
||||
const MultiReporter = require('./reporters/multi')
|
||||
const baseReporterDecoratorFactory = require('./reporters/base').decoratorFactory
|
||||
|
||||
function createErrorFormatter (config, emitter, SourceMapConsumer) {
|
||||
const basePath = config.basePath
|
||||
const urlRoot = config.urlRoot === '/' ? '' : (config.urlRoot || '')
|
||||
let lastServedFiles = []
|
||||
|
||||
emitter.on('file_list_modified', (files) => {
|
||||
lastServedFiles = files.served
|
||||
})
|
||||
|
||||
const URL_REGEXP = new RegExp('(?:https?:\\/\\/' +
|
||||
config.hostname + '(?:\\:' + config.port + ')?' + ')?\\/?' +
|
||||
urlRoot + '\\/?' +
|
||||
'(base/|absolute)' + // prefix, including slash for base/ to create relative paths.
|
||||
'((?:[A-z]\\:)?[^\\?\\s\\:]*)' + // path
|
||||
'(\\?\\w*)?' + // sha
|
||||
'(\\:(\\d+))?' + // line
|
||||
'(\\:(\\d+))?' + // column
|
||||
'', 'g')
|
||||
|
||||
const cache = new WeakMap()
|
||||
|
||||
function getSourceMapConsumer (sourceMap) {
|
||||
if (!cache.has(sourceMap)) {
|
||||
cache.set(sourceMap, new SourceMapConsumer(sourceMap))
|
||||
}
|
||||
return cache.get(sourceMap)
|
||||
}
|
||||
|
||||
return function (input, indentation) {
|
||||
indentation = _.isString(indentation) ? indentation : ''
|
||||
if (_.isError(input)) {
|
||||
input = input.message
|
||||
} else if (_.isEmpty(input)) {
|
||||
input = ''
|
||||
} else if (!_.isString(input)) {
|
||||
input = JSON.stringify(input, null, indentation)
|
||||
}
|
||||
|
||||
let msg = input.replace(URL_REGEXP, function (stackTracePath, prefix, path, __, ___, line, ____, column) {
|
||||
const normalizedPath = prefix === 'base/' ? `${basePath}/${path}` : path
|
||||
const file = lastServedFiles.find((file) => file.path === normalizedPath)
|
||||
|
||||
if (file && file.sourceMap && line) {
|
||||
line = +line
|
||||
column = +column
|
||||
|
||||
// When no column is given and we default to 0, it doesn't make sense to only search for smaller
|
||||
// or equal columns in the sourcemap, let's search for equal or greater columns.
|
||||
const bias = column ? SourceMapConsumer.GREATEST_LOWER_BOUND : SourceMapConsumer.LEAST_UPPER_BOUND
|
||||
|
||||
try {
|
||||
const zeroBasedColumn = Math.max(0, (column || 1) - 1)
|
||||
const original = getSourceMapConsumer(file.sourceMap).originalPositionFor({ line, column: zeroBasedColumn, bias })
|
||||
|
||||
// If there is no original position/source for the current stack trace path, then
|
||||
// we return early with the formatted generated position. This handles the case of
|
||||
// generated code which does not map to anything, see Case 1 of the source-map spec.
|
||||
// https://sourcemaps.info/spec.html.
|
||||
if (original.source === null) {
|
||||
return PathUtils.formatPathMapping(path, line, column)
|
||||
}
|
||||
|
||||
// Source maps often only have a local file name, resolve to turn into a full path if
|
||||
// the path is not absolute yet.
|
||||
const oneBasedOriginalColumn = original.column == null ? original.column : original.column + 1
|
||||
return `${PathUtils.formatPathMapping(resolve(path, original.source), original.line, oneBasedOriginalColumn)} <- ${PathUtils.formatPathMapping(path, line, column)}`
|
||||
} catch (e) {
|
||||
log.warn(`An unexpected error occurred while resolving the original position for: ${stackTracePath}`)
|
||||
log.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
return PathUtils.formatPathMapping(path, line, column) || prefix
|
||||
})
|
||||
|
||||
if (indentation) {
|
||||
msg = indentation + msg.replace(/\n/g, '\n' + indentation)
|
||||
}
|
||||
|
||||
return config.formatError ? config.formatError(msg) : msg + '\n'
|
||||
}
|
||||
}
|
||||
|
||||
function createReporters (names, config, emitter, injector) {
|
||||
const errorFormatter = createErrorFormatter(config, emitter, SourceMapConsumer)
|
||||
const reporters = []
|
||||
|
||||
names.forEach((name) => {
|
||||
if (['dots', 'progress'].includes(name)) {
|
||||
[
|
||||
require(`./reporters/${name}`),
|
||||
require(`./reporters/${name}_color`)
|
||||
].forEach((Reporter) => {
|
||||
reporters.push(new Reporter(errorFormatter, config.reportSlowerThan, config.colors, config.browserConsoleLogOptions))
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const locals = {
|
||||
baseReporterDecorator: ['factory', baseReporterDecoratorFactory],
|
||||
formatError: ['value', errorFormatter]
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug(`Trying to load reporter: ${name}`)
|
||||
reporters.push(injector.createChild([locals], ['reporter:' + name]).get('reporter:' + name))
|
||||
} catch (e) {
|
||||
if (e.message.includes(`No provider for "reporter:${name}"`)) {
|
||||
log.error(`Can not load reporter "${name}", it is not registered!\n Perhaps you are missing some plugin?`)
|
||||
} else {
|
||||
log.error(`Can not load "${name}"!\n ${e.stack}`)
|
||||
}
|
||||
emitter.emit('load_error', 'reporter', name)
|
||||
return
|
||||
}
|
||||
|
||||
const colorName = name + '_color'
|
||||
if (!names.includes(colorName)) {
|
||||
try {
|
||||
log.debug(`Trying to load color-version of reporter: ${name} (${colorName})`)
|
||||
reporters.push(injector.createChild([locals], ['reporter:' + colorName]).get('reporter:' + name))
|
||||
} catch (e) {
|
||||
log.debug('Couldn\'t load color-version.')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
reporters.forEach((reporter) => emitter.bind(reporter))
|
||||
|
||||
return new MultiReporter(reporters)
|
||||
}
|
||||
|
||||
createReporters.$inject = [
|
||||
'config.reporters',
|
||||
'config',
|
||||
'emitter',
|
||||
'injector'
|
||||
]
|
||||
|
||||
exports.createReporters = createReporters
|
162
node_modules/karma/lib/reporters/base.js
generated
vendored
Normal file
162
node_modules/karma/lib/reporters/base.js
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
'use strict'
|
||||
|
||||
const util = require('util')
|
||||
|
||||
const constants = require('../constants')
|
||||
const helper = require('../helper')
|
||||
|
||||
const BaseReporter = function (formatError, reportSlow, useColors, browserConsoleLogOptions, adapter) {
|
||||
this.adapters = [adapter || process.stdout.write.bind(process.stdout)]
|
||||
|
||||
this.USE_COLORS = false
|
||||
this.EXCLUSIVELY_USE_COLORS = undefined
|
||||
this.LOG_SINGLE_BROWSER = '%s: %s\n'
|
||||
this.LOG_MULTI_BROWSER = '%s %s: %s\n'
|
||||
|
||||
this.SPEC_FAILURE = '%s %s FAILED' + '\n'
|
||||
this.SPEC_SLOW = '%s SLOW %s: %s\n'
|
||||
this.ERROR = '%s ERROR\n'
|
||||
|
||||
this.FINISHED_ERROR = ' ERROR'
|
||||
this.FINISHED_SUCCESS = ' SUCCESS'
|
||||
this.FINISHED_DISCONNECTED = ' DISCONNECTED'
|
||||
|
||||
this.X_FAILED = ' (%d FAILED)'
|
||||
|
||||
this.TOTAL_SUCCESS = 'TOTAL: %d SUCCESS\n'
|
||||
this.TOTAL_FAILED = 'TOTAL: %d FAILED, %d SUCCESS\n'
|
||||
|
||||
this.onRunStart = () => {
|
||||
this._browsers = []
|
||||
}
|
||||
|
||||
this.onBrowserStart = (browser) => {
|
||||
this._browsers.push(browser)
|
||||
}
|
||||
|
||||
this.renderBrowser = (browser) => {
|
||||
const results = browser.lastResult
|
||||
const totalExecuted = results.success + results.failed
|
||||
let msg = `${browser}: Executed ${totalExecuted} of ${results.total}`
|
||||
|
||||
if (results.failed) {
|
||||
msg += util.format(this.X_FAILED, results.failed)
|
||||
}
|
||||
|
||||
if (results.skipped) {
|
||||
msg += ` (skipped ${results.skipped})`
|
||||
}
|
||||
|
||||
if (browser.isConnected) {
|
||||
if (results.disconnected) {
|
||||
msg += this.FINISHED_DISCONNECTED
|
||||
} else if (results.error) {
|
||||
msg += this.FINISHED_ERROR
|
||||
} else if (!results.failed) {
|
||||
msg += this.FINISHED_SUCCESS
|
||||
}
|
||||
|
||||
msg += ` (${helper.formatTimeInterval(results.totalTime)} / ${helper.formatTimeInterval(results.netTime)})`
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
this.write = function () {
|
||||
const msg = util.format.apply(null, Array.prototype.slice.call(arguments))
|
||||
this.adapters.forEach((adapter) => {
|
||||
if (!helper.isDefined(adapter.colors)) {
|
||||
adapter.colors = useColors
|
||||
}
|
||||
if (!helper.isDefined(this.EXCLUSIVELY_USE_COLORS) || adapter.colors === this.EXCLUSIVELY_USE_COLORS) {
|
||||
return adapter(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.writeCommonMsg = function () {
|
||||
this.write.apply(this, arguments)
|
||||
}
|
||||
|
||||
this.onBrowserError = (browser, error) => {
|
||||
this.writeCommonMsg(util.format(this.ERROR, browser) + formatError(error, ' '))
|
||||
}
|
||||
|
||||
this.onBrowserLog = (browser, log, type) => {
|
||||
if (!browserConsoleLogOptions || !browserConsoleLogOptions.terminal) return
|
||||
type = type.toUpperCase()
|
||||
if (browserConsoleLogOptions.level) {
|
||||
const logPriority = constants.LOG_PRIORITIES.indexOf(browserConsoleLogOptions.level.toUpperCase())
|
||||
if (constants.LOG_PRIORITIES.indexOf(type) > logPriority) return
|
||||
}
|
||||
if (!helper.isString(log)) {
|
||||
// TODO(vojta): change util to new syntax (config object)
|
||||
log = util.inspect(log, false, undefined, this.USE_COLORS)
|
||||
}
|
||||
if (this._browsers && this._browsers.length === 1) {
|
||||
this.writeCommonMsg(util.format(this.LOG_SINGLE_BROWSER, type, log))
|
||||
} else {
|
||||
this.writeCommonMsg(util.format(this.LOG_MULTI_BROWSER, browser, type, log))
|
||||
}
|
||||
}
|
||||
|
||||
this.onSpecComplete = (browser, result) => {
|
||||
if (result.skipped) {
|
||||
this.specSkipped(browser, result)
|
||||
} else if (result.success) {
|
||||
this.specSuccess(browser, result)
|
||||
} else {
|
||||
this.specFailure(browser, result)
|
||||
}
|
||||
|
||||
if (reportSlow && result.time > reportSlow) {
|
||||
const specName = result.suite.join(' ') + ' ' + result.description
|
||||
const time = helper.formatTimeInterval(result.time)
|
||||
|
||||
this.writeCommonMsg(util.format(this.SPEC_SLOW, browser, time, specName))
|
||||
}
|
||||
}
|
||||
|
||||
this.specSuccess = () => {
|
||||
}
|
||||
|
||||
this.specSkipped = () => {
|
||||
}
|
||||
|
||||
this.specFailure = (browser, result) => {
|
||||
const specName = result.suite.join(' ') + ' ' + result.description
|
||||
let msg = util.format(this.SPEC_FAILURE, browser, specName)
|
||||
|
||||
result.log.forEach((log) => {
|
||||
msg += formatError(log, '\t')
|
||||
})
|
||||
|
||||
this.writeCommonMsg(msg)
|
||||
}
|
||||
|
||||
this.onRunComplete = (browsers, results) => {
|
||||
if (browsers.length >= 1 && !results.error && !results.disconnected) {
|
||||
if (!results.failed) {
|
||||
this.write(this.TOTAL_SUCCESS, results.success)
|
||||
} else {
|
||||
this.write(this.TOTAL_FAILED, results.failed, results.success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BaseReporter.decoratorFactory = function (formatError, reportSlow, useColors, browserConsoleLogOptions) {
|
||||
return function (self) {
|
||||
BaseReporter.call(self, formatError, reportSlow, useColors, browserConsoleLogOptions)
|
||||
}
|
||||
}
|
||||
|
||||
BaseReporter.decoratorFactory.$inject = [
|
||||
'formatError',
|
||||
'config.reportSlowerThan',
|
||||
'config.colors',
|
||||
'config.browserConsoleLogOptions'
|
||||
]
|
||||
|
||||
// PUBLISH
|
||||
module.exports = BaseReporter
|
24
node_modules/karma/lib/reporters/base_color.js
generated
vendored
Normal file
24
node_modules/karma/lib/reporters/base_color.js
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
const { red, yellow, green, cyan } = require('@colors/colors/safe')
|
||||
|
||||
function BaseColorReporter () {
|
||||
this.USE_COLORS = true
|
||||
|
||||
this.LOG_SINGLE_BROWSER = '%s: ' + cyan('%s') + '\n'
|
||||
this.LOG_MULTI_BROWSER = '%s %s: ' + cyan('%s') + '\n'
|
||||
|
||||
this.SPEC_FAILURE = red('%s %s FAILED') + '\n'
|
||||
this.SPEC_SLOW = yellow('%s SLOW %s: %s') + '\n'
|
||||
this.ERROR = red('%s ERROR') + '\n'
|
||||
|
||||
this.FINISHED_ERROR = red(' ERROR')
|
||||
this.FINISHED_SUCCESS = green(' SUCCESS')
|
||||
this.FINISHED_DISCONNECTED = red(' DISCONNECTED')
|
||||
|
||||
this.X_FAILED = red(' (%d FAILED)')
|
||||
|
||||
this.TOTAL_SUCCESS = green('TOTAL: %d SUCCESS') + '\n'
|
||||
this.TOTAL_FAILED = red('TOTAL: %d FAILED, %d SUCCESS') + '\n'
|
||||
}
|
||||
|
||||
// PUBLISH
|
||||
module.exports = BaseColorReporter
|
47
node_modules/karma/lib/reporters/dots.js
generated
vendored
Normal file
47
node_modules/karma/lib/reporters/dots.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
const BaseReporter = require('./base')
|
||||
|
||||
function DotsReporter (formatError, reportSlow, useColors, browserConsoleLogOptions) {
|
||||
BaseReporter.call(this, formatError, reportSlow, useColors, browserConsoleLogOptions)
|
||||
|
||||
const DOTS_WRAP = 80
|
||||
this.EXCLUSIVELY_USE_COLORS = false
|
||||
this.onRunStart = function () {
|
||||
this._browsers = []
|
||||
this._dotsCount = 0
|
||||
}
|
||||
|
||||
this.onBrowserStart = function (browser) {
|
||||
this._browsers.push(browser)
|
||||
}
|
||||
|
||||
this.writeCommonMsg = function (msg) {
|
||||
if (this._dotsCount) {
|
||||
this._dotsCount = 0
|
||||
msg = '\n' + msg
|
||||
}
|
||||
|
||||
this.write(msg)
|
||||
}
|
||||
|
||||
this.specSuccess = function () {
|
||||
this._dotsCount = (this._dotsCount + 1) % DOTS_WRAP
|
||||
this.write(this._dotsCount ? '.' : '.\n')
|
||||
}
|
||||
|
||||
this.onBrowserComplete = function (browser) {
|
||||
this.writeCommonMsg(this.renderBrowser(browser) + '\n')
|
||||
}
|
||||
|
||||
this.onRunComplete = function (browsers, results) {
|
||||
if (browsers.length > 1 && !results.disconnected && !results.error) {
|
||||
if (!results.failed) {
|
||||
this.write(this.TOTAL_SUCCESS, results.success)
|
||||
} else {
|
||||
this.write(this.TOTAL_FAILED, results.failed, results.success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLISH
|
||||
module.exports = DotsReporter
|
11
node_modules/karma/lib/reporters/dots_color.js
generated
vendored
Normal file
11
node_modules/karma/lib/reporters/dots_color.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
const DotsReporter = require('./dots')
|
||||
const BaseColorReporter = require('./base_color')
|
||||
|
||||
function DotsColorReporter (formatError, reportSlow, useColors, browserConsoleLogOptions) {
|
||||
DotsReporter.call(this, formatError, reportSlow, useColors, browserConsoleLogOptions)
|
||||
BaseColorReporter.call(this)
|
||||
this.EXCLUSIVELY_USE_COLORS = true
|
||||
}
|
||||
|
||||
// PUBLISH
|
||||
module.exports = DotsColorReporter
|
19
node_modules/karma/lib/reporters/multi.js
generated
vendored
Normal file
19
node_modules/karma/lib/reporters/multi.js
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
'use strict'
|
||||
|
||||
const helper = require('../helper')
|
||||
|
||||
class MultiReporter {
|
||||
constructor (reporters) {
|
||||
this._reporters = reporters
|
||||
}
|
||||
|
||||
addAdapter (adapter) {
|
||||
this._reporters.forEach((reporter) => reporter.adapters.push(adapter))
|
||||
}
|
||||
|
||||
removeAdapter (adapter) {
|
||||
this._reporters.forEach((reporter) => helper.arrayRemove(reporter.adapters, adapter))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MultiReporter
|
63
node_modules/karma/lib/reporters/progress.js
generated
vendored
Normal file
63
node_modules/karma/lib/reporters/progress.js
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
const BaseReporter = require('./base')
|
||||
|
||||
function ProgressReporter (formatError, reportSlow, useColors, browserConsoleLogOptions) {
|
||||
BaseReporter.call(this, formatError, reportSlow, useColors, browserConsoleLogOptions)
|
||||
|
||||
this.EXCLUSIVELY_USE_COLORS = false
|
||||
this._browsers = []
|
||||
|
||||
this.writeCommonMsg = function (msg) {
|
||||
this.write(this._remove() + msg + this._render())
|
||||
}
|
||||
|
||||
this.specSuccess = function () {
|
||||
this.write(this._refresh())
|
||||
}
|
||||
|
||||
this.onBrowserComplete = function () {
|
||||
this.write(this._refresh())
|
||||
}
|
||||
|
||||
this.onRunStart = function () {
|
||||
this._browsers = []
|
||||
this._isRendered = false
|
||||
}
|
||||
|
||||
this.onBrowserStart = function (browser) {
|
||||
this._browsers.push(browser)
|
||||
|
||||
if (this._isRendered) {
|
||||
this.write('\n')
|
||||
}
|
||||
|
||||
this.write(this._refresh())
|
||||
}
|
||||
|
||||
this._remove = function () {
|
||||
if (!this._isRendered) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let cmd = ''
|
||||
this._browsers.forEach(function () {
|
||||
cmd += '\x1B[1A' + '\x1B[2K'
|
||||
})
|
||||
|
||||
this._isRendered = false
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
this._render = function () {
|
||||
this._isRendered = true
|
||||
|
||||
return this._browsers.map(this.renderBrowser).join('\n') + '\n'
|
||||
}
|
||||
|
||||
this._refresh = function () {
|
||||
return this._remove() + this._render()
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLISH
|
||||
module.exports = ProgressReporter
|
11
node_modules/karma/lib/reporters/progress_color.js
generated
vendored
Normal file
11
node_modules/karma/lib/reporters/progress_color.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
const ProgressReporter = require('./progress')
|
||||
const BaseColorReporter = require('./base_color')
|
||||
|
||||
function ProgressColorReporter (formatError, reportSlow, useColors, browserConsoleLogOptions) {
|
||||
ProgressReporter.call(this, formatError, reportSlow, useColors, browserConsoleLogOptions)
|
||||
BaseColorReporter.call(this)
|
||||
this.EXCLUSIVELY_USE_COLORS = true
|
||||
}
|
||||
|
||||
// PUBLISH
|
||||
module.exports = ProgressColorReporter
|
113
node_modules/karma/lib/runner.js
generated
vendored
Normal file
113
node_modules/karma/lib/runner.js
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
'use strict'
|
||||
|
||||
const http = require('http')
|
||||
|
||||
const constant = require('./constants')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const helper = require('./helper')
|
||||
const cfg = require('./config')
|
||||
const logger = require('./logger')
|
||||
const { lookup } = require('./utils/dns-utils')
|
||||
const log = logger.create('runner')
|
||||
|
||||
function parseExitCode (buffer, defaultExitCode, failOnEmptyTestSuite) {
|
||||
const tailPos = buffer.length - Buffer.byteLength(constant.EXIT_CODE) - 2
|
||||
|
||||
if (tailPos < 0) {
|
||||
return { exitCode: defaultExitCode, buffer }
|
||||
}
|
||||
|
||||
const tail = buffer.slice(tailPos)
|
||||
const tailStr = tail.toString()
|
||||
if (tailStr.slice(0, -2) === constant.EXIT_CODE) {
|
||||
const emptyInt = parseInt(tailStr.slice(-2, -1), 10)
|
||||
let exitCode = parseInt(tailStr.slice(-1), 10)
|
||||
if (failOnEmptyTestSuite === false && emptyInt === 0) {
|
||||
log.warn('Test suite was empty.')
|
||||
exitCode = 0
|
||||
}
|
||||
return { exitCode, buffer: buffer.slice(0, tailPos) }
|
||||
}
|
||||
|
||||
return { exitCode: defaultExitCode, buffer }
|
||||
}
|
||||
|
||||
// TODO(vojta): read config file (port, host, urlRoot)
|
||||
function run (cliOptionsOrConfig, done) {
|
||||
cliOptionsOrConfig = cliOptionsOrConfig || {}
|
||||
done = helper.isFunction(done) ? done : process.exit
|
||||
|
||||
let config
|
||||
if (cliOptionsOrConfig instanceof cfg.Config) {
|
||||
config = cliOptionsOrConfig
|
||||
} else {
|
||||
logger.setupFromConfig({
|
||||
colors: cliOptionsOrConfig.colors,
|
||||
logLevel: cliOptionsOrConfig.logLevel
|
||||
})
|
||||
const deprecatedCliOptionsMessage =
|
||||
'Passing raw CLI options to `runner(config, done)` is deprecated. Use ' +
|
||||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' +
|
||||
'to prepare a processed `Config` instance and pass that as the ' +
|
||||
'`config` argument instead.'
|
||||
log.warn(deprecatedCliOptionsMessage)
|
||||
try {
|
||||
config = cfg.parseConfig(
|
||||
cliOptionsOrConfig.configFile,
|
||||
cliOptionsOrConfig,
|
||||
{
|
||||
promiseConfig: false,
|
||||
throwErrors: true
|
||||
}
|
||||
)
|
||||
} catch (parseConfigError) {
|
||||
// TODO: change how `done` falls back to exit in next major version
|
||||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378
|
||||
done(1)
|
||||
}
|
||||
}
|
||||
let exitCode = 1
|
||||
const emitter = new EventEmitter()
|
||||
const options = {
|
||||
hostname: config.hostname,
|
||||
path: config.urlRoot + 'run',
|
||||
port: config.port,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
lookup
|
||||
}
|
||||
|
||||
const request = http.request(options, function (response) {
|
||||
response.on('data', function (buffer) {
|
||||
const parsedResult = parseExitCode(buffer, exitCode, config.failOnEmptyTestSuite)
|
||||
exitCode = parsedResult.exitCode
|
||||
emitter.emit('progress', parsedResult.buffer)
|
||||
})
|
||||
|
||||
response.on('end', () => done(exitCode))
|
||||
})
|
||||
|
||||
request.on('error', function (e) {
|
||||
if (e.code === 'ECONNREFUSED') {
|
||||
log.error('There is no server listening on port %d', options.port)
|
||||
done(1, e.code)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
|
||||
request.end(JSON.stringify({
|
||||
args: config.clientArgs,
|
||||
removedFiles: config.removedFiles,
|
||||
changedFiles: config.changedFiles,
|
||||
addedFiles: config.addedFiles,
|
||||
refresh: config.refresh,
|
||||
colors: config.colors
|
||||
}))
|
||||
|
||||
return emitter
|
||||
}
|
||||
|
||||
exports.run = run
|
487
node_modules/karma/lib/server.js
generated
vendored
Normal file
487
node_modules/karma/lib/server.js
generated
vendored
Normal file
|
@ -0,0 +1,487 @@
|
|||
'use strict'
|
||||
|
||||
const SocketIO = require('socket.io')
|
||||
const di = require('di')
|
||||
const util = require('util')
|
||||
const spawn = require('child_process').spawn
|
||||
const tmp = require('tmp')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const NetUtils = require('./utils/net-utils')
|
||||
const root = global || window || this
|
||||
|
||||
const cfg = require('./config')
|
||||
const logger = require('./logger')
|
||||
const constant = require('./constants')
|
||||
const watcher = require('./watcher')
|
||||
const plugin = require('./plugin')
|
||||
|
||||
const createServeFile = require('./web-server').createServeFile
|
||||
const createServeStaticFile = require('./web-server').createServeStaticFile
|
||||
const createFilesPromise = require('./web-server').createFilesPromise
|
||||
const createWebServer = require('./web-server').createWebServer
|
||||
const preprocessor = require('./preprocessor')
|
||||
const Launcher = require('./launcher').Launcher
|
||||
const FileList = require('./file-list')
|
||||
const reporter = require('./reporter')
|
||||
const helper = require('./helper')
|
||||
const events = require('./events')
|
||||
const KarmaEventEmitter = events.EventEmitter
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const Executor = require('./executor')
|
||||
const Browser = require('./browser')
|
||||
const BrowserCollection = require('./browser_collection')
|
||||
const EmitterWrapper = require('./emitter_wrapper')
|
||||
const processWrapper = new EmitterWrapper(process)
|
||||
|
||||
function createSocketIoServer (webServer, executor, config) {
|
||||
const server = new SocketIO.Server(webServer, {
|
||||
// avoid destroying http upgrades from socket.io to get proxied websockets working
|
||||
destroyUpgrade: false,
|
||||
path: config.urlRoot + 'socket.io/',
|
||||
transports: config.transports,
|
||||
forceJSONP: config.forceJSONP,
|
||||
// Default is 5000 in socket.io v2.x and v3.x.
|
||||
pingTimeout: config.pingTimeout || 5000,
|
||||
// Default in v2 is 1e8 and coverage results can fail at 1e6
|
||||
maxHttpBufferSize: 1e8
|
||||
})
|
||||
|
||||
// hack to overcome circular dependency
|
||||
executor.socketIoSockets = server.sockets
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
class Server extends KarmaEventEmitter {
|
||||
constructor (cliOptionsOrConfig, done) {
|
||||
super()
|
||||
cliOptionsOrConfig = cliOptionsOrConfig || {}
|
||||
this.log = logger.create('karma-server')
|
||||
done = helper.isFunction(done) ? done : process.exit
|
||||
this.loadErrors = []
|
||||
|
||||
let config
|
||||
if (cliOptionsOrConfig instanceof cfg.Config) {
|
||||
config = cliOptionsOrConfig
|
||||
} else {
|
||||
logger.setupFromConfig({
|
||||
colors: cliOptionsOrConfig.colors,
|
||||
logLevel: cliOptionsOrConfig.logLevel
|
||||
})
|
||||
const deprecatedCliOptionsMessage =
|
||||
'Passing raw CLI options to `new Server(config, done)` is ' +
|
||||
'deprecated. Use ' +
|
||||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' +
|
||||
'to prepare a processed `Config` instance and pass that as the ' +
|
||||
'`config` argument instead.'
|
||||
this.log.warn(deprecatedCliOptionsMessage)
|
||||
try {
|
||||
config = cfg.parseConfig(
|
||||
cliOptionsOrConfig.configFile,
|
||||
cliOptionsOrConfig,
|
||||
{
|
||||
promiseConfig: false,
|
||||
throwErrors: true
|
||||
}
|
||||
)
|
||||
} catch (parseConfigError) {
|
||||
// TODO: change how `done` falls back to exit in next major version
|
||||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378
|
||||
done(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.log.debug('Final config', util.inspect(config, false, /** depth **/ null))
|
||||
|
||||
if (!config.autoWatch && !config.singleRun) {
|
||||
this.log.warn('`autowatch` and `singleRun` are both `false`. In order to execute tests use `karma run`.')
|
||||
}
|
||||
|
||||
let modules = [{
|
||||
helper: ['value', helper],
|
||||
logger: ['value', logger],
|
||||
done: ['value', done || process.exit],
|
||||
emitter: ['value', this],
|
||||
server: ['value', this],
|
||||
watcher: ['value', watcher],
|
||||
launcher: ['factory', Launcher.factory],
|
||||
config: ['value', config],
|
||||
instantiatePlugin: ['factory', plugin.createInstantiatePlugin],
|
||||
preprocess: ['factory', preprocessor.createPriorityPreprocessor],
|
||||
fileList: ['factory', FileList.factory],
|
||||
webServer: ['factory', createWebServer],
|
||||
serveFile: ['factory', createServeFile],
|
||||
serveStaticFile: ['factory', createServeStaticFile],
|
||||
filesPromise: ['factory', createFilesPromise],
|
||||
socketServer: ['factory', createSocketIoServer],
|
||||
executor: ['factory', Executor.factory],
|
||||
// TODO: Deprecated. Remove in the next major
|
||||
customFileHandlers: ['value', []],
|
||||
reporter: ['factory', reporter.createReporters],
|
||||
capturedBrowsers: ['factory', BrowserCollection.factory],
|
||||
args: ['value', {}],
|
||||
timer: ['value', {
|
||||
setTimeout () {
|
||||
return setTimeout.apply(root, arguments)
|
||||
},
|
||||
clearTimeout
|
||||
}]
|
||||
}]
|
||||
|
||||
this.on('load_error', (type, name) => {
|
||||
this.log.debug(`Registered a load error of type ${type} with name ${name}`)
|
||||
this.loadErrors.push([type, name])
|
||||
})
|
||||
|
||||
modules = modules.concat(plugin.resolve(config.plugins, this))
|
||||
this._injector = new di.Injector(modules)
|
||||
}
|
||||
|
||||
async start () {
|
||||
const config = this.get('config')
|
||||
try {
|
||||
this._boundServer = await NetUtils.bindAvailablePort(config.port, config.listenAddress)
|
||||
this._boundServer.on('connection', (socket) => {
|
||||
// Attach an error handler to avoid UncaughtException errors.
|
||||
socket.on('error', (err) => {
|
||||
// Errors on this socket are retried, ignore them
|
||||
this.log.debug('Ignoring error on webserver connection: ' + err)
|
||||
})
|
||||
})
|
||||
config.port = this._boundServer.address().port
|
||||
await this._injector.invoke(this._start, this)
|
||||
} catch (err) {
|
||||
this.log.error(`Server start failed on port ${config.port}: ${err}`)
|
||||
this._close(1)
|
||||
}
|
||||
}
|
||||
|
||||
get (token) {
|
||||
return this._injector.get(token)
|
||||
}
|
||||
|
||||
refreshFiles () {
|
||||
return this._fileList ? this._fileList.refresh() : Promise.resolve()
|
||||
}
|
||||
|
||||
refreshFile (path) {
|
||||
return this._fileList ? this._fileList.changeFile(path) : Promise.resolve()
|
||||
}
|
||||
|
||||
emitExitAsync (code) {
|
||||
const name = 'exit'
|
||||
let pending = this.listeners(name).length
|
||||
const deferred = helper.defer()
|
||||
|
||||
function resolve () {
|
||||
deferred.resolve(code)
|
||||
}
|
||||
|
||||
try {
|
||||
this.emit(name, (newCode) => {
|
||||
if (newCode && typeof newCode === 'number') {
|
||||
// Only update code if it is given and not zero
|
||||
code = newCode
|
||||
}
|
||||
if (!--pending) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
if (!pending) {
|
||||
resolve()
|
||||
}
|
||||
} catch (err) {
|
||||
deferred.reject(err)
|
||||
}
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
async _start (config, launcher, preprocess, fileList, capturedBrowsers, executor, done) {
|
||||
if (config.detached) {
|
||||
this._detach(config, done)
|
||||
return
|
||||
}
|
||||
|
||||
this._fileList = fileList
|
||||
|
||||
await Promise.all(
|
||||
config.frameworks.map((framework) => this._injector.get('framework:' + framework))
|
||||
)
|
||||
|
||||
const webServer = this._injector.get('webServer')
|
||||
const socketServer = this._injector.get('socketServer')
|
||||
|
||||
const singleRunDoneBrowsers = Object.create(null)
|
||||
const singleRunBrowsers = new BrowserCollection(new EventEmitter())
|
||||
let singleRunBrowserNotCaptured = false
|
||||
|
||||
webServer.on('error', (err) => {
|
||||
this.log.error(`Webserver fail ${err}`)
|
||||
this._close(1)
|
||||
})
|
||||
|
||||
const afterPreprocess = () => {
|
||||
if (config.autoWatch) {
|
||||
const watcher = this.get('watcher')
|
||||
this._injector.invoke(watcher)
|
||||
}
|
||||
|
||||
webServer.listen(this._boundServer, () => {
|
||||
this.log.info(`Karma v${constant.VERSION} server started at ${config.protocol}//${config.hostname}:${config.port}${config.urlRoot}`)
|
||||
|
||||
this.emit('listening', config.port)
|
||||
if (config.browsers && config.browsers.length) {
|
||||
this._injector.invoke(launcher.launch, launcher).forEach((browserLauncher) => {
|
||||
singleRunDoneBrowsers[browserLauncher.id] = false
|
||||
})
|
||||
}
|
||||
if (this.loadErrors.length > 0) {
|
||||
this.log.error(new Error(`Found ${this.loadErrors.length} load error${this.loadErrors.length === 1 ? '' : 's'}`))
|
||||
this._close(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fileList.refresh().then(afterPreprocess, (err) => {
|
||||
this.log.error('Error during file loading or preprocessing\n' + err.stack || err)
|
||||
afterPreprocess()
|
||||
})
|
||||
|
||||
this.on('browsers_change', () => socketServer.sockets.emit('info', capturedBrowsers.serialize()))
|
||||
|
||||
this.on('browser_register', (browser) => {
|
||||
launcher.markCaptured(browser.id)
|
||||
|
||||
if (launcher.areAllCaptured()) {
|
||||
this.emit('browsers_ready')
|
||||
|
||||
if (config.autoWatch) {
|
||||
executor.schedule()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (config.browserConsoleLogOptions && config.browserConsoleLogOptions.path) {
|
||||
const configLevel = config.browserConsoleLogOptions.level || 'debug'
|
||||
const configFormat = config.browserConsoleLogOptions.format || '%b %T: %m'
|
||||
const configPath = config.browserConsoleLogOptions.path
|
||||
const configPathDir = path.dirname(configPath)
|
||||
if (!fs.existsSync(configPathDir)) fs.mkdirSync(configPathDir, { recursive: true })
|
||||
this.log.info(`Writing browser console to file: ${configPath}`)
|
||||
const browserLogFile = fs.openSync(configPath, 'w+')
|
||||
const levels = ['log', 'error', 'warn', 'info', 'debug']
|
||||
this.on('browser_log', function (browser, message, level) {
|
||||
if (levels.indexOf(level.toLowerCase()) > levels.indexOf(configLevel)) {
|
||||
return
|
||||
}
|
||||
if (!helper.isString(message)) {
|
||||
message = util.inspect(message, { showHidden: false, colors: false })
|
||||
}
|
||||
const logMap = { '%m': message, '%t': level.toLowerCase(), '%T': level.toUpperCase(), '%b': browser }
|
||||
const logString = configFormat.replace(/%[mtTb]/g, (m) => logMap[m])
|
||||
this.log.debug(`Writing browser console line: ${logString}`)
|
||||
fs.writeSync(browserLogFile, logString + '\n')
|
||||
})
|
||||
}
|
||||
|
||||
socketServer.sockets.on('connection', (socket) => {
|
||||
this.log.debug(`A browser has connected on socket ${socket.id}`)
|
||||
|
||||
const replySocketEvents = events.bufferEvents(socket, ['start', 'info', 'karma_error', 'result', 'complete'])
|
||||
|
||||
socket.on('error', (err) => {
|
||||
this.log.debug('karma server socket error: ' + err)
|
||||
})
|
||||
|
||||
socket.on('register', (info) => {
|
||||
const knownBrowser = info.id ? (capturedBrowsers.getById(info.id) || singleRunBrowsers.getById(info.id)) : null
|
||||
|
||||
if (knownBrowser) {
|
||||
knownBrowser.reconnect(socket, info.isSocketReconnect)
|
||||
} else {
|
||||
const newBrowser = this._injector.createChild([{
|
||||
id: ['value', info.id || null],
|
||||
fullName: ['value', (helper.isDefined(info.displayName) ? info.displayName : info.name)],
|
||||
socket: ['value', socket]
|
||||
}]).invoke(Browser.factory)
|
||||
|
||||
newBrowser.init()
|
||||
|
||||
if (config.singleRun) {
|
||||
newBrowser.execute()
|
||||
singleRunBrowsers.add(newBrowser)
|
||||
}
|
||||
}
|
||||
|
||||
replySocketEvents()
|
||||
})
|
||||
})
|
||||
|
||||
const emitRunCompleteIfAllBrowsersDone = () => {
|
||||
if (Object.keys(singleRunDoneBrowsers).every((key) => singleRunDoneBrowsers[key])) {
|
||||
this.emit('run_complete', singleRunBrowsers, singleRunBrowsers.getResults(singleRunBrowserNotCaptured, config))
|
||||
}
|
||||
}
|
||||
|
||||
this.on('browser_complete', (completedBrowser) => {
|
||||
if (completedBrowser.lastResult.disconnected && completedBrowser.disconnectsCount <= config.browserDisconnectTolerance) {
|
||||
this.log.info(`Restarting ${completedBrowser.name} (${completedBrowser.disconnectsCount} of ${config.browserDisconnectTolerance} attempts)`)
|
||||
|
||||
if (!launcher.restart(completedBrowser.id)) {
|
||||
this.emit('browser_restart_failure', completedBrowser)
|
||||
}
|
||||
} else {
|
||||
this.emit('browser_complete_with_no_more_retries', completedBrowser)
|
||||
}
|
||||
})
|
||||
|
||||
this.on('stop', (done) => {
|
||||
this.log.debug('Received stop event, exiting.')
|
||||
this._close()
|
||||
done()
|
||||
})
|
||||
|
||||
if (config.singleRun) {
|
||||
this.on('browser_restart_failure', (completedBrowser) => {
|
||||
singleRunDoneBrowsers[completedBrowser.id] = true
|
||||
emitRunCompleteIfAllBrowsersDone()
|
||||
})
|
||||
|
||||
// This is the normal exit trigger.
|
||||
this.on('browser_complete_with_no_more_retries', function (completedBrowser) {
|
||||
singleRunDoneBrowsers[completedBrowser.id] = true
|
||||
|
||||
if (launcher.kill(completedBrowser.id)) {
|
||||
completedBrowser.remove()
|
||||
}
|
||||
|
||||
emitRunCompleteIfAllBrowsersDone()
|
||||
})
|
||||
|
||||
this.on('browser_process_failure', (browserLauncher) => {
|
||||
singleRunDoneBrowsers[browserLauncher.id] = true
|
||||
singleRunBrowserNotCaptured = true
|
||||
|
||||
emitRunCompleteIfAllBrowsersDone()
|
||||
})
|
||||
|
||||
this.on('run_complete', (browsers, results) => {
|
||||
this.log.debug('Run complete, exiting.')
|
||||
this._close(results.exitCode)
|
||||
})
|
||||
|
||||
this.emit('run_start', singleRunBrowsers)
|
||||
}
|
||||
|
||||
if (config.autoWatch) {
|
||||
this.on('file_list_modified', () => {
|
||||
this.log.debug('List of files has changed, trying to execute')
|
||||
if (config.restartOnFileChange) {
|
||||
socketServer.sockets.emit('stop')
|
||||
}
|
||||
executor.schedule()
|
||||
})
|
||||
}
|
||||
|
||||
processWrapper.on('SIGINT', () => this._close())
|
||||
processWrapper.on('SIGTERM', () => this._close())
|
||||
|
||||
const reportError = (error) => {
|
||||
this.log.error(error)
|
||||
process.emit('infrastructure_error', error)
|
||||
this._close(1)
|
||||
}
|
||||
|
||||
processWrapper.on('unhandledRejection', (error) => {
|
||||
this.log.error(`UnhandledRejection: ${error.stack || error.message || String(error)}`)
|
||||
reportError(error)
|
||||
})
|
||||
|
||||
processWrapper.on('uncaughtException', (error) => {
|
||||
this.log.error(`UncaughtException: ${error.stack || error.message || String(error)}`)
|
||||
reportError(error)
|
||||
})
|
||||
}
|
||||
|
||||
_detach (config, done) {
|
||||
const tmpFile = tmp.fileSync({ keep: true })
|
||||
this.log.info('Starting karma detached')
|
||||
this.log.info('Run "karma stop" to stop the server.')
|
||||
this.log.debug(`Writing config to tmp-file ${tmpFile.name}`)
|
||||
config.detached = false
|
||||
try {
|
||||
fs.writeFileSync(tmpFile.name, JSON.stringify(config), 'utf8')
|
||||
} catch (e) {
|
||||
this.log.error("Couldn't write temporary configuration file")
|
||||
done(1)
|
||||
return
|
||||
}
|
||||
const child = spawn(process.argv[0], [path.resolve(__dirname, '../lib/detached.js'), tmpFile.name], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
})
|
||||
child.unref()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all resources allocated by Karma and call the `done` callback
|
||||
* with the result of the tests execution.
|
||||
*
|
||||
* @param [exitCode] - Optional exit code. If omitted will be computed by
|
||||
* 'exit' event listeners.
|
||||
*/
|
||||
_close (exitCode) {
|
||||
const webServer = this._injector.get('webServer')
|
||||
const socketServer = this._injector.get('socketServer')
|
||||
const done = this._injector.get('done')
|
||||
|
||||
const webServerCloseTimeout = 3000
|
||||
const sockets = socketServer.sockets.sockets
|
||||
|
||||
Object.keys(sockets).forEach((id) => {
|
||||
const socket = sockets[id]
|
||||
socket.removeAllListeners('disconnect')
|
||||
if (!socket.disconnected) {
|
||||
process.nextTick(socket.disconnect.bind(socket))
|
||||
}
|
||||
})
|
||||
|
||||
this.emitExitAsync(exitCode).catch((err) => {
|
||||
this.log.error('Error while calling exit event listeners\n' + err.stack || err)
|
||||
return 1
|
||||
}).then((code) => {
|
||||
socketServer.sockets.removeAllListeners()
|
||||
socketServer.close()
|
||||
|
||||
let removeAllListenersDone = false
|
||||
const removeAllListeners = () => {
|
||||
if (removeAllListenersDone) {
|
||||
return
|
||||
}
|
||||
removeAllListenersDone = true
|
||||
webServer.removeAllListeners()
|
||||
processWrapper.removeAllListeners()
|
||||
done(code || 0)
|
||||
}
|
||||
|
||||
const closeTimeout = setTimeout(removeAllListeners, webServerCloseTimeout)
|
||||
|
||||
webServer.close(() => {
|
||||
clearTimeout(closeTimeout)
|
||||
removeAllListeners()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
stop () {
|
||||
return this.emitAsync('stop')
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._start.$inject = ['config', 'launcher', 'preprocess', 'fileList', 'capturedBrowsers', 'executor', 'done']
|
||||
|
||||
module.exports = Server
|
69
node_modules/karma/lib/stopper.js
generated
vendored
Normal file
69
node_modules/karma/lib/stopper.js
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
const http = require('http')
|
||||
const cfg = require('./config')
|
||||
const logger = require('./logger')
|
||||
const helper = require('./helper')
|
||||
const { lookup } = require('./utils/dns-utils')
|
||||
|
||||
exports.stop = function (cliOptionsOrConfig, done) {
|
||||
cliOptionsOrConfig = cliOptionsOrConfig || {}
|
||||
const log = logger.create('stopper')
|
||||
done = helper.isFunction(done) ? done : process.exit
|
||||
|
||||
let config
|
||||
if (cliOptionsOrConfig instanceof cfg.Config) {
|
||||
config = cliOptionsOrConfig
|
||||
} else {
|
||||
logger.setupFromConfig({
|
||||
colors: cliOptionsOrConfig.colors,
|
||||
logLevel: cliOptionsOrConfig.logLevel
|
||||
})
|
||||
const deprecatedCliOptionsMessage =
|
||||
'Passing raw CLI options to `stopper(config, done)` is deprecated. Use ' +
|
||||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' +
|
||||
'to prepare a processed `Config` instance and pass that as the ' +
|
||||
'`config` argument instead.'
|
||||
log.warn(deprecatedCliOptionsMessage)
|
||||
try {
|
||||
config = cfg.parseConfig(
|
||||
cliOptionsOrConfig.configFile,
|
||||
cliOptionsOrConfig,
|
||||
{
|
||||
promiseConfig: false,
|
||||
throwErrors: true
|
||||
}
|
||||
)
|
||||
} catch (parseConfigError) {
|
||||
// TODO: change how `done` falls back to exit in next major version
|
||||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378
|
||||
done(1)
|
||||
}
|
||||
}
|
||||
|
||||
const request = http.request({
|
||||
hostname: config.hostname,
|
||||
path: config.urlRoot + 'stop',
|
||||
port: config.port,
|
||||
method: 'GET',
|
||||
lookup
|
||||
})
|
||||
|
||||
request.on('response', function (response) {
|
||||
if (response.statusCode === 200) {
|
||||
log.info('Server stopped.')
|
||||
done(0)
|
||||
} else {
|
||||
log.error(`Server returned status code: ${response.statusCode}`)
|
||||
done(1)
|
||||
}
|
||||
})
|
||||
|
||||
request.on('error', function (e) {
|
||||
if (e.code === 'ECONNREFUSED') {
|
||||
log.error(`There is no server listening on port ${config.port}`)
|
||||
done(1, e.code)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
request.end()
|
||||
}
|
31
node_modules/karma/lib/temp_dir.js
generated
vendored
Normal file
31
node_modules/karma/lib/temp_dir.js
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('graceful-fs')
|
||||
const rimraf = require('rimraf')
|
||||
const log = require('./logger').create('temp-dir')
|
||||
|
||||
const TEMP_DIR = require('os').tmpdir()
|
||||
|
||||
module.exports = {
|
||||
getPath (suffix) {
|
||||
return path.normalize(TEMP_DIR + suffix)
|
||||
},
|
||||
|
||||
create (path) {
|
||||
log.debug(`Creating temp dir at ${path}`)
|
||||
|
||||
try {
|
||||
fs.mkdirSync(path)
|
||||
} catch (e) {
|
||||
log.warn(`Failed to create a temp dir at ${path}`)
|
||||
}
|
||||
|
||||
return path
|
||||
},
|
||||
|
||||
remove (path, done) {
|
||||
log.debug(`Cleaning temp dir ${path}`)
|
||||
rimraf(path, done)
|
||||
}
|
||||
}
|
31
node_modules/karma/lib/url.js
generated
vendored
Normal file
31
node_modules/karma/lib/url.js
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const { URL } = require('url')
|
||||
|
||||
/**
|
||||
* Url object used for tracking files in `file-list.js`.
|
||||
*/
|
||||
class Url {
|
||||
constructor (path, type, integrity) {
|
||||
this.path = path
|
||||
this.originalPath = path
|
||||
this.type = type
|
||||
this.integrity = integrity
|
||||
this.isUrl = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect type from the file extension in the path part of the URL.
|
||||
* @returns {string} detected file type or empty string
|
||||
*/
|
||||
detectType () {
|
||||
return path.extname(new URL(this.path).pathname).slice(1)
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.path
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Url
|
14
node_modules/karma/lib/utils/crypto-utils.js
generated
vendored
Normal file
14
node_modules/karma/lib/utils/crypto-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
const CryptoUtils = {
|
||||
sha1 (data) {
|
||||
return crypto
|
||||
.createHash('sha1')
|
||||
.update(data)
|
||||
.digest('hex')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CryptoUtils
|
11
node_modules/karma/lib/utils/dns-utils.js
generated
vendored
Normal file
11
node_modules/karma/lib/utils/dns-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
const dns = require('dns')
|
||||
|
||||
// Node >=17 has different DNS resolution (see
|
||||
// https://github.com/nodejs/node/issues/40702), it resolves domains
|
||||
// according to the OS settings instead of IPv4-address first. The Karma server
|
||||
// only listens on IPv4 address (127.0.0.1) by default, but the requests are
|
||||
// sent to `localhost` in several places and `localhost` is resolved into IPv6
|
||||
// address (`::`). So the run/stop/proxy request is unable to reach the Karma
|
||||
// server and produces an error. To mitigate this issue karma force the
|
||||
// IPv4-address first approach in Node >=17 as well.
|
||||
module.exports.lookup = (hostname, options, callback) => dns.lookup(hostname, { ...options, verbatim: false }, callback)
|
25
node_modules/karma/lib/utils/file-utils.js
generated
vendored
Normal file
25
node_modules/karma/lib/utils/file-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
|
||||
const FileUtils = {
|
||||
readFile (path) {
|
||||
return fs.readFileSync(path).toString()
|
||||
},
|
||||
|
||||
saveFile (path, content) {
|
||||
fs.writeFileSync(path, content)
|
||||
},
|
||||
|
||||
copyFile (src, dest) {
|
||||
FileUtils.saveFile(dest, FileUtils.readFile(src))
|
||||
},
|
||||
|
||||
removeFileIfExists (src) {
|
||||
if (fs.existsSync(src)) {
|
||||
fs.unlinkSync(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileUtils
|
27
node_modules/karma/lib/utils/net-utils.js
generated
vendored
Normal file
27
node_modules/karma/lib/utils/net-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
'use strict'
|
||||
|
||||
const net = require('net')
|
||||
|
||||
const NetUtils = {
|
||||
bindAvailablePort (port, listenAddress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer()
|
||||
|
||||
server
|
||||
.on('error', (err) => {
|
||||
server.close()
|
||||
if (err.code === 'EADDRINUSE' || err.code === 'EACCES') {
|
||||
server.listen(++port, listenAddress)
|
||||
} else {
|
||||
reject(new Error(`Failed to bind ${port}: ` + (err.stack || err)))
|
||||
}
|
||||
})
|
||||
.on('listening', () => {
|
||||
resolve(server)
|
||||
})
|
||||
.listen(port, listenAddress)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NetUtils
|
16
node_modules/karma/lib/utils/path-utils.js
generated
vendored
Normal file
16
node_modules/karma/lib/utils/path-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const PathUtils = {
|
||||
formatPathMapping (path, line, column) {
|
||||
return path + (line ? `:${line}` : '') + (column ? `:${column}` : '')
|
||||
},
|
||||
|
||||
calculateAbsolutePath (karmaRelativePath) {
|
||||
return path.join(__dirname, '..', '..', karmaRelativePath)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PathUtils
|
14
node_modules/karma/lib/utils/pattern-utils.js
generated
vendored
Normal file
14
node_modules/karma/lib/utils/pattern-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const PatternUtils = {
|
||||
getBaseDir (pattern) {
|
||||
return pattern
|
||||
.replace(/[/\\][^/\\]*\*.*$/, '') // remove parts with *
|
||||
.replace(/[/\\][^/\\]*[!+]\(.*$/, '') // remove parts with !(...) and +(...)
|
||||
.replace(/[/\\][^/\\]*\)\?.*$/, '') || path.sep // remove parts with (...)?
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PatternUtils
|
85
node_modules/karma/lib/watcher.js
generated
vendored
Normal file
85
node_modules/karma/lib/watcher.js
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
'use strict'
|
||||
|
||||
const mm = require('minimatch')
|
||||
const braces = require('braces')
|
||||
const PatternUtils = require('./utils/pattern-utils')
|
||||
|
||||
const helper = require('./helper')
|
||||
const log = require('./logger').create('watcher')
|
||||
|
||||
const DIR_SEP = require('path').sep
|
||||
|
||||
function watchPatterns (patterns, watcher) {
|
||||
let expandedPatterns = []
|
||||
patterns.map((pattern) => {
|
||||
// expand ['a/{b,c}'] to ['a/b', 'a/c']
|
||||
expandedPatterns = expandedPatterns.concat(braces.expand(pattern, { keepEscaping: true }))
|
||||
})
|
||||
expandedPatterns
|
||||
.map(PatternUtils.getBaseDir)
|
||||
.filter((path, index, paths) => paths.indexOf(path) === index) // filter unique values
|
||||
.forEach((path, index, paths) => {
|
||||
if (!paths.some((p) => path.startsWith(p + DIR_SEP))) {
|
||||
watcher.add(path)
|
||||
log.debug(`Watching "${path}"`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function checkAnyPathMatch (patterns, path) {
|
||||
return patterns.some((pattern) => mm(path, pattern, { dot: true }))
|
||||
}
|
||||
|
||||
function createIgnore (patterns, excludes) {
|
||||
return function (path, stat) {
|
||||
if (stat && !stat.isDirectory()) {
|
||||
return !checkAnyPathMatch(patterns, path) || checkAnyPathMatch(excludes, path)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWatchedPatterns (patterns) {
|
||||
return patterns
|
||||
.filter((pattern) => pattern.watched)
|
||||
.map((pattern) => pattern.pattern)
|
||||
}
|
||||
|
||||
function watch (patterns, excludes, fileList, usePolling, emitter) {
|
||||
const watchedPatterns = getWatchedPatterns(patterns)
|
||||
// Lazy-load 'chokidar' to make the dependency optional. This is desired when
|
||||
// third-party watchers are in use.
|
||||
const chokidar = require('chokidar')
|
||||
const watcher = new chokidar.FSWatcher({
|
||||
usePolling: usePolling,
|
||||
ignorePermissionErrors: true,
|
||||
ignoreInitial: true,
|
||||
ignored: createIgnore(watchedPatterns, excludes)
|
||||
})
|
||||
|
||||
watchPatterns(watchedPatterns, watcher)
|
||||
|
||||
watcher
|
||||
.on('add', (path) => fileList.addFile(helper.normalizeWinPath(path)))
|
||||
.on('change', (path) => fileList.changeFile(helper.normalizeWinPath(path)))
|
||||
.on('unlink', (path) => fileList.removeFile(helper.normalizeWinPath(path)))
|
||||
.on('error', log.debug.bind(log))
|
||||
|
||||
emitter.on('exit', (done) => {
|
||||
watcher.close()
|
||||
done()
|
||||
})
|
||||
|
||||
return watcher
|
||||
}
|
||||
|
||||
watch.$inject = [
|
||||
'config.files',
|
||||
'config.exclude',
|
||||
'fileList',
|
||||
'config.usePolling',
|
||||
'emitter'
|
||||
]
|
||||
|
||||
module.exports = watch
|
118
node_modules/karma/lib/web-server.js
generated
vendored
Normal file
118
node_modules/karma/lib/web-server.js
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const path = require('path')
|
||||
const connect = require('connect')
|
||||
const mimeType = require('mime')
|
||||
|
||||
const common = require('./middleware/common')
|
||||
const runnerMiddleware = require('./middleware/runner')
|
||||
const stopperMiddleware = require('./middleware/stopper')
|
||||
const karmaMiddleware = require('./middleware/karma')
|
||||
const sourceFilesMiddleware = require('./middleware/source_files')
|
||||
const proxyMiddleware = require('./middleware/proxy')
|
||||
|
||||
const log = require('./logger').create('web-server')
|
||||
|
||||
function createCustomHandler (customFileHandlers, config) {
|
||||
let warningDone = false
|
||||
|
||||
return function (request, response, next) {
|
||||
const handler = customFileHandlers.find((handler) => handler.urlRegex.test(request.url))
|
||||
|
||||
if (customFileHandlers.length > 0 && !warningDone) {
|
||||
warningDone = true
|
||||
log.warn('The `customFileHandlers` is deprecated and will be removed in Karma 7. Please upgrade plugins relying on this provider.')
|
||||
}
|
||||
|
||||
return handler
|
||||
? handler.handler(request, response, 'fake/static', 'fake/adapter', config.basePath, 'fake/root')
|
||||
: next()
|
||||
}
|
||||
}
|
||||
|
||||
createCustomHandler.$inject = ['customFileHandlers', 'config']
|
||||
|
||||
function createFilesPromise (emitter, fileList) {
|
||||
// Set an empty list of files to avoid race issues with
|
||||
// file_list_modified not having been emitted yet
|
||||
let files = fileList.files
|
||||
emitter.on('file_list_modified', (filesParam) => { files = filesParam })
|
||||
|
||||
return {
|
||||
then (...args) {
|
||||
return Promise.resolve(files).then(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createServeStaticFile (config) {
|
||||
return common.createServeFile(fs, path.normalize(path.join(__dirname, '/../static')), config)
|
||||
}
|
||||
createServeStaticFile.$inject = ['config']
|
||||
|
||||
function createServeFile (config) {
|
||||
return common.createServeFile(fs, null, config)
|
||||
}
|
||||
createServeFile.$inject = ['config']
|
||||
|
||||
function createWebServer (injector, config) {
|
||||
const { mime = {} } = config
|
||||
mimeType.define({ ...mime }, true)
|
||||
|
||||
const proxyMiddlewareInstance = injector.invoke(proxyMiddleware.create)
|
||||
|
||||
log.debug('Instantiating middleware')
|
||||
const handler = connect()
|
||||
|
||||
if (config.beforeMiddleware) {
|
||||
config.beforeMiddleware.forEach((middleware) => handler.use(injector.get('middleware:' + middleware)))
|
||||
}
|
||||
|
||||
handler.use(injector.invoke(runnerMiddleware.create))
|
||||
handler.use(injector.invoke(stopperMiddleware.create))
|
||||
handler.use(injector.invoke(karmaMiddleware.create))
|
||||
handler.use(injector.invoke(sourceFilesMiddleware.create))
|
||||
// TODO(vojta): extract the proxy into a plugin
|
||||
handler.use(proxyMiddlewareInstance)
|
||||
// TODO: Deprecated. Remove in the next major
|
||||
handler.use(injector.invoke(createCustomHandler))
|
||||
|
||||
if (config.middleware) {
|
||||
config.middleware.forEach((middleware) => handler.use(injector.get('middleware:' + middleware)))
|
||||
}
|
||||
|
||||
handler.use((request, response) => common.serve404(response, request.url))
|
||||
|
||||
let serverClass = http
|
||||
const serverArguments = [handler]
|
||||
|
||||
if (config.protocol === 'https:') {
|
||||
serverClass = https
|
||||
serverArguments.unshift(config.httpsServerOptions || {})
|
||||
}
|
||||
|
||||
if (config.httpModule) {
|
||||
serverClass = config.httpModule
|
||||
}
|
||||
|
||||
const server = serverClass.createServer.apply(null, serverArguments)
|
||||
|
||||
server.on('upgrade', function (req, socket, head) {
|
||||
log.debug(`upgrade ${req.url}`)
|
||||
proxyMiddlewareInstance.upgrade(req, socket, head)
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
createWebServer.$inject = ['injector', 'config']
|
||||
|
||||
module.exports = {
|
||||
createWebServer,
|
||||
createServeFile,
|
||||
createServeStaticFile,
|
||||
createFilesPromise
|
||||
}
|
13
node_modules/karma/local.log
generated
vendored
Normal file
13
node_modules/karma/local.log
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
(Not all processes could be identified, non-owned process info
|
||||
will not be shown, you would have to be root to see it all.)
|
||||
(Not all processes could be identified, non-owned process info
|
||||
will not be shown, you would have to be root to see it all.)
|
||||
Mon Jul 29 2024 17:37:36:600 GMT+0000 (UTC) -- Starting configuration console on http://localhost:45454
|
||||
Mon Jul 29 2024 17:37:36:880 GMT+0000 (UTC) -- [INFO] Started the BrowserStack Binary server on 45691, PID: 5753
|
||||
Mon Jul 29 2024 17:37:36:981 GMT+0000 (UTC) -- [SUCCESS] You can now access your local server(s) in our remote browser
|
||||
|
||||
Mon Jul 29 2024 17:37:37:555 GMT+0000 (UTC) -- Press Ctrl-C to exit
|
||||
|
||||
|
345
node_modules/karma/node_modules/ansi-styles/index.d.ts
generated
vendored
Normal file
345
node_modules/karma/node_modules/ansi-styles/index.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,345 @@
|
|||
declare type CSSColor =
|
||||
| 'aliceblue'
|
||||
| 'antiquewhite'
|
||||
| 'aqua'
|
||||
| 'aquamarine'
|
||||
| 'azure'
|
||||
| 'beige'
|
||||
| 'bisque'
|
||||
| 'black'
|
||||
| 'blanchedalmond'
|
||||
| 'blue'
|
||||
| 'blueviolet'
|
||||
| 'brown'
|
||||
| 'burlywood'
|
||||
| 'cadetblue'
|
||||
| 'chartreuse'
|
||||
| 'chocolate'
|
||||
| 'coral'
|
||||
| 'cornflowerblue'
|
||||
| 'cornsilk'
|
||||
| 'crimson'
|
||||
| 'cyan'
|
||||
| 'darkblue'
|
||||
| 'darkcyan'
|
||||
| 'darkgoldenrod'
|
||||
| 'darkgray'
|
||||
| 'darkgreen'
|
||||
| 'darkgrey'
|
||||
| 'darkkhaki'
|
||||
| 'darkmagenta'
|
||||
| 'darkolivegreen'
|
||||
| 'darkorange'
|
||||
| 'darkorchid'
|
||||
| 'darkred'
|
||||
| 'darksalmon'
|
||||
| 'darkseagreen'
|
||||
| 'darkslateblue'
|
||||
| 'darkslategray'
|
||||
| 'darkslategrey'
|
||||
| 'darkturquoise'
|
||||
| 'darkviolet'
|
||||
| 'deeppink'
|
||||
| 'deepskyblue'
|
||||
| 'dimgray'
|
||||
| 'dimgrey'
|
||||
| 'dodgerblue'
|
||||
| 'firebrick'
|
||||
| 'floralwhite'
|
||||
| 'forestgreen'
|
||||
| 'fuchsia'
|
||||
| 'gainsboro'
|
||||
| 'ghostwhite'
|
||||
| 'gold'
|
||||
| 'goldenrod'
|
||||
| 'gray'
|
||||
| 'green'
|
||||
| 'greenyellow'
|
||||
| 'grey'
|
||||
| 'honeydew'
|
||||
| 'hotpink'
|
||||
| 'indianred'
|
||||
| 'indigo'
|
||||
| 'ivory'
|
||||
| 'khaki'
|
||||
| 'lavender'
|
||||
| 'lavenderblush'
|
||||
| 'lawngreen'
|
||||
| 'lemonchiffon'
|
||||
| 'lightblue'
|
||||
| 'lightcoral'
|
||||
| 'lightcyan'
|
||||
| 'lightgoldenrodyellow'
|
||||
| 'lightgray'
|
||||
| 'lightgreen'
|
||||
| 'lightgrey'
|
||||
| 'lightpink'
|
||||
| 'lightsalmon'
|
||||
| 'lightseagreen'
|
||||
| 'lightskyblue'
|
||||
| 'lightslategray'
|
||||
| 'lightslategrey'
|
||||
| 'lightsteelblue'
|
||||
| 'lightyellow'
|
||||
| 'lime'
|
||||
| 'limegreen'
|
||||
| 'linen'
|
||||
| 'magenta'
|
||||
| 'maroon'
|
||||
| 'mediumaquamarine'
|
||||
| 'mediumblue'
|
||||
| 'mediumorchid'
|
||||
| 'mediumpurple'
|
||||
| 'mediumseagreen'
|
||||
| 'mediumslateblue'
|
||||
| 'mediumspringgreen'
|
||||
| 'mediumturquoise'
|
||||
| 'mediumvioletred'
|
||||
| 'midnightblue'
|
||||
| 'mintcream'
|
||||
| 'mistyrose'
|
||||
| 'moccasin'
|
||||
| 'navajowhite'
|
||||
| 'navy'
|
||||
| 'oldlace'
|
||||
| 'olive'
|
||||
| 'olivedrab'
|
||||
| 'orange'
|
||||
| 'orangered'
|
||||
| 'orchid'
|
||||
| 'palegoldenrod'
|
||||
| 'palegreen'
|
||||
| 'paleturquoise'
|
||||
| 'palevioletred'
|
||||
| 'papayawhip'
|
||||
| 'peachpuff'
|
||||
| 'peru'
|
||||
| 'pink'
|
||||
| 'plum'
|
||||
| 'powderblue'
|
||||
| 'purple'
|
||||
| 'rebeccapurple'
|
||||
| 'red'
|
||||
| 'rosybrown'
|
||||
| 'royalblue'
|
||||
| 'saddlebrown'
|
||||
| 'salmon'
|
||||
| 'sandybrown'
|
||||
| 'seagreen'
|
||||
| 'seashell'
|
||||
| 'sienna'
|
||||
| 'silver'
|
||||
| 'skyblue'
|
||||
| 'slateblue'
|
||||
| 'slategray'
|
||||
| 'slategrey'
|
||||
| 'snow'
|
||||
| 'springgreen'
|
||||
| 'steelblue'
|
||||
| 'tan'
|
||||
| 'teal'
|
||||
| 'thistle'
|
||||
| 'tomato'
|
||||
| 'turquoise'
|
||||
| 'violet'
|
||||
| 'wheat'
|
||||
| 'white'
|
||||
| 'whitesmoke'
|
||||
| 'yellow'
|
||||
| 'yellowgreen';
|
||||
|
||||
declare namespace ansiStyles {
|
||||
interface ColorConvert {
|
||||
/**
|
||||
The RGB color space.
|
||||
|
||||
@param red - (`0`-`255`)
|
||||
@param green - (`0`-`255`)
|
||||
@param blue - (`0`-`255`)
|
||||
*/
|
||||
rgb(red: number, green: number, blue: number): string;
|
||||
|
||||
/**
|
||||
The RGB HEX color space.
|
||||
|
||||
@param hex - A hexadecimal string containing RGB data.
|
||||
*/
|
||||
hex(hex: string): string;
|
||||
|
||||
/**
|
||||
@param keyword - A CSS color name.
|
||||
*/
|
||||
keyword(keyword: CSSColor): string;
|
||||
|
||||
/**
|
||||
The HSL color space.
|
||||
|
||||
@param hue - (`0`-`360`)
|
||||
@param saturation - (`0`-`100`)
|
||||
@param lightness - (`0`-`100`)
|
||||
*/
|
||||
hsl(hue: number, saturation: number, lightness: number): string;
|
||||
|
||||
/**
|
||||
The HSV color space.
|
||||
|
||||
@param hue - (`0`-`360`)
|
||||
@param saturation - (`0`-`100`)
|
||||
@param value - (`0`-`100`)
|
||||
*/
|
||||
hsv(hue: number, saturation: number, value: number): string;
|
||||
|
||||
/**
|
||||
The HSV color space.
|
||||
|
||||
@param hue - (`0`-`360`)
|
||||
@param whiteness - (`0`-`100`)
|
||||
@param blackness - (`0`-`100`)
|
||||
*/
|
||||
hwb(hue: number, whiteness: number, blackness: number): string;
|
||||
|
||||
/**
|
||||
Use a [4-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4-bit) to set text color.
|
||||
*/
|
||||
ansi(ansi: number): string;
|
||||
|
||||
/**
|
||||
Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color.
|
||||
*/
|
||||
ansi256(ansi: number): string;
|
||||
}
|
||||
|
||||
interface CSPair {
|
||||
/**
|
||||
The ANSI terminal control sequence for starting this style.
|
||||
*/
|
||||
readonly open: string;
|
||||
|
||||
/**
|
||||
The ANSI terminal control sequence for ending this style.
|
||||
*/
|
||||
readonly close: string;
|
||||
}
|
||||
|
||||
interface ColorBase {
|
||||
readonly ansi: ColorConvert;
|
||||
readonly ansi256: ColorConvert;
|
||||
readonly ansi16m: ColorConvert;
|
||||
|
||||
/**
|
||||
The ANSI terminal control sequence for ending this color.
|
||||
*/
|
||||
readonly close: string;
|
||||
}
|
||||
|
||||
interface Modifier {
|
||||
/**
|
||||
Resets the current color chain.
|
||||
*/
|
||||
readonly reset: CSPair;
|
||||
|
||||
/**
|
||||
Make text bold.
|
||||
*/
|
||||
readonly bold: CSPair;
|
||||
|
||||
/**
|
||||
Emitting only a small amount of light.
|
||||
*/
|
||||
readonly dim: CSPair;
|
||||
|
||||
/**
|
||||
Make text italic. (Not widely supported)
|
||||
*/
|
||||
readonly italic: CSPair;
|
||||
|
||||
/**
|
||||
Make text underline. (Not widely supported)
|
||||
*/
|
||||
readonly underline: CSPair;
|
||||
|
||||
/**
|
||||
Inverse background and foreground colors.
|
||||
*/
|
||||
readonly inverse: CSPair;
|
||||
|
||||
/**
|
||||
Prints the text, but makes it invisible.
|
||||
*/
|
||||
readonly hidden: CSPair;
|
||||
|
||||
/**
|
||||
Puts a horizontal line through the center of the text. (Not widely supported)
|
||||
*/
|
||||
readonly strikethrough: CSPair;
|
||||
}
|
||||
|
||||
interface ForegroundColor {
|
||||
readonly black: CSPair;
|
||||
readonly red: CSPair;
|
||||
readonly green: CSPair;
|
||||
readonly yellow: CSPair;
|
||||
readonly blue: CSPair;
|
||||
readonly cyan: CSPair;
|
||||
readonly magenta: CSPair;
|
||||
readonly white: CSPair;
|
||||
|
||||
/**
|
||||
Alias for `blackBright`.
|
||||
*/
|
||||
readonly gray: CSPair;
|
||||
|
||||
/**
|
||||
Alias for `blackBright`.
|
||||
*/
|
||||
readonly grey: CSPair;
|
||||
|
||||
readonly blackBright: CSPair;
|
||||
readonly redBright: CSPair;
|
||||
readonly greenBright: CSPair;
|
||||
readonly yellowBright: CSPair;
|
||||
readonly blueBright: CSPair;
|
||||
readonly cyanBright: CSPair;
|
||||
readonly magentaBright: CSPair;
|
||||
readonly whiteBright: CSPair;
|
||||
}
|
||||
|
||||
interface BackgroundColor {
|
||||
readonly bgBlack: CSPair;
|
||||
readonly bgRed: CSPair;
|
||||
readonly bgGreen: CSPair;
|
||||
readonly bgYellow: CSPair;
|
||||
readonly bgBlue: CSPair;
|
||||
readonly bgCyan: CSPair;
|
||||
readonly bgMagenta: CSPair;
|
||||
readonly bgWhite: CSPair;
|
||||
|
||||
/**
|
||||
Alias for `bgBlackBright`.
|
||||
*/
|
||||
readonly bgGray: CSPair;
|
||||
|
||||
/**
|
||||
Alias for `bgBlackBright`.
|
||||
*/
|
||||
readonly bgGrey: CSPair;
|
||||
|
||||
readonly bgBlackBright: CSPair;
|
||||
readonly bgRedBright: CSPair;
|
||||
readonly bgGreenBright: CSPair;
|
||||
readonly bgYellowBright: CSPair;
|
||||
readonly bgBlueBright: CSPair;
|
||||
readonly bgCyanBright: CSPair;
|
||||
readonly bgMagentaBright: CSPair;
|
||||
readonly bgWhiteBright: CSPair;
|
||||
}
|
||||
}
|
||||
|
||||
declare const ansiStyles: {
|
||||
readonly modifier: ansiStyles.Modifier;
|
||||
readonly color: ansiStyles.ForegroundColor & ansiStyles.ColorBase;
|
||||
readonly bgColor: ansiStyles.BackgroundColor & ansiStyles.ColorBase;
|
||||
readonly codes: ReadonlyMap<number, number>;
|
||||
} & ansiStyles.BackgroundColor & ansiStyles.ForegroundColor & ansiStyles.Modifier;
|
||||
|
||||
export = ansiStyles;
|
163
node_modules/karma/node_modules/ansi-styles/index.js
generated
vendored
Normal file
163
node_modules/karma/node_modules/ansi-styles/index.js
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
'use strict';
|
||||
|
||||
const wrapAnsi16 = (fn, offset) => (...args) => {
|
||||
const code = fn(...args);
|
||||
return `\u001B[${code + offset}m`;
|
||||
};
|
||||
|
||||
const wrapAnsi256 = (fn, offset) => (...args) => {
|
||||
const code = fn(...args);
|
||||
return `\u001B[${38 + offset};5;${code}m`;
|
||||
};
|
||||
|
||||
const wrapAnsi16m = (fn, offset) => (...args) => {
|
||||
const rgb = fn(...args);
|
||||
return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
|
||||
};
|
||||
|
||||
const ansi2ansi = n => n;
|
||||
const rgb2rgb = (r, g, b) => [r, g, b];
|
||||
|
||||
const setLazyProperty = (object, property, get) => {
|
||||
Object.defineProperty(object, property, {
|
||||
get: () => {
|
||||
const value = get();
|
||||
|
||||
Object.defineProperty(object, property, {
|
||||
value,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
return value;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {typeof import('color-convert')} */
|
||||
let colorConvert;
|
||||
const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => {
|
||||
if (colorConvert === undefined) {
|
||||
colorConvert = require('color-convert');
|
||||
}
|
||||
|
||||
const offset = isBackground ? 10 : 0;
|
||||
const styles = {};
|
||||
|
||||
for (const [sourceSpace, suite] of Object.entries(colorConvert)) {
|
||||
const name = sourceSpace === 'ansi16' ? 'ansi' : sourceSpace;
|
||||
if (sourceSpace === targetSpace) {
|
||||
styles[name] = wrap(identity, offset);
|
||||
} else if (typeof suite === 'object') {
|
||||
styles[name] = wrap(suite[targetSpace], offset);
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
};
|
||||
|
||||
function assembleStyles() {
|
||||
const codes = new Map();
|
||||
const styles = {
|
||||
modifier: {
|
||||
reset: [0, 0],
|
||||
// 21 isn't widely supported and 22 does the same thing
|
||||
bold: [1, 22],
|
||||
dim: [2, 22],
|
||||
italic: [3, 23],
|
||||
underline: [4, 24],
|
||||
inverse: [7, 27],
|
||||
hidden: [8, 28],
|
||||
strikethrough: [9, 29]
|
||||
},
|
||||
color: {
|
||||
black: [30, 39],
|
||||
red: [31, 39],
|
||||
green: [32, 39],
|
||||
yellow: [33, 39],
|
||||
blue: [34, 39],
|
||||
magenta: [35, 39],
|
||||
cyan: [36, 39],
|
||||
white: [37, 39],
|
||||
|
||||
// Bright color
|
||||
blackBright: [90, 39],
|
||||
redBright: [91, 39],
|
||||
greenBright: [92, 39],
|
||||
yellowBright: [93, 39],
|
||||
blueBright: [94, 39],
|
||||
magentaBright: [95, 39],
|
||||
cyanBright: [96, 39],
|
||||
whiteBright: [97, 39]
|
||||
},
|
||||
bgColor: {
|
||||
bgBlack: [40, 49],
|
||||
bgRed: [41, 49],
|
||||
bgGreen: [42, 49],
|
||||
bgYellow: [43, 49],
|
||||
bgBlue: [44, 49],
|
||||
bgMagenta: [45, 49],
|
||||
bgCyan: [46, 49],
|
||||
bgWhite: [47, 49],
|
||||
|
||||
// Bright color
|
||||
bgBlackBright: [100, 49],
|
||||
bgRedBright: [101, 49],
|
||||
bgGreenBright: [102, 49],
|
||||
bgYellowBright: [103, 49],
|
||||
bgBlueBright: [104, 49],
|
||||
bgMagentaBright: [105, 49],
|
||||
bgCyanBright: [106, 49],
|
||||
bgWhiteBright: [107, 49]
|
||||
}
|
||||
};
|
||||
|
||||
// Alias bright black as gray (and grey)
|
||||
styles.color.gray = styles.color.blackBright;
|
||||
styles.bgColor.bgGray = styles.bgColor.bgBlackBright;
|
||||
styles.color.grey = styles.color.blackBright;
|
||||
styles.bgColor.bgGrey = styles.bgColor.bgBlackBright;
|
||||
|
||||
for (const [groupName, group] of Object.entries(styles)) {
|
||||
for (const [styleName, style] of Object.entries(group)) {
|
||||
styles[styleName] = {
|
||||
open: `\u001B[${style[0]}m`,
|
||||
close: `\u001B[${style[1]}m`
|
||||
};
|
||||
|
||||
group[styleName] = styles[styleName];
|
||||
|
||||
codes.set(style[0], style[1]);
|
||||
}
|
||||
|
||||
Object.defineProperty(styles, groupName, {
|
||||
value: group,
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(styles, 'codes', {
|
||||
value: codes,
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
styles.color.close = '\u001B[39m';
|
||||
styles.bgColor.close = '\u001B[49m';
|
||||
|
||||
setLazyProperty(styles.color, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, false));
|
||||
setLazyProperty(styles.color, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, false));
|
||||
setLazyProperty(styles.color, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, false));
|
||||
setLazyProperty(styles.bgColor, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, true));
|
||||
setLazyProperty(styles.bgColor, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, true));
|
||||
setLazyProperty(styles.bgColor, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, true));
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
// Make the export immutable
|
||||
Object.defineProperty(module, 'exports', {
|
||||
enumerable: true,
|
||||
get: assembleStyles
|
||||
});
|
9
node_modules/karma/node_modules/ansi-styles/license
generated
vendored
Normal file
9
node_modules/karma/node_modules/ansi-styles/license
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
56
node_modules/karma/node_modules/ansi-styles/package.json
generated
vendored
Normal file
56
node_modules/karma/node_modules/ansi-styles/package.json
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"name": "ansi-styles",
|
||||
"version": "4.3.0",
|
||||
"description": "ANSI escape codes for styling strings in the terminal",
|
||||
"license": "MIT",
|
||||
"repository": "chalk/ansi-styles",
|
||||
"funding": "https://github.com/chalk/ansi-styles?sponsor=1",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "sindresorhus.com"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava && tsd",
|
||||
"screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"ansi",
|
||||
"styles",
|
||||
"color",
|
||||
"colour",
|
||||
"colors",
|
||||
"terminal",
|
||||
"console",
|
||||
"cli",
|
||||
"string",
|
||||
"tty",
|
||||
"escape",
|
||||
"formatting",
|
||||
"rgb",
|
||||
"256",
|
||||
"shell",
|
||||
"xterm",
|
||||
"log",
|
||||
"logging",
|
||||
"command-line",
|
||||
"text"
|
||||
],
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/color-convert": "^1.9.0",
|
||||
"ava": "^2.3.0",
|
||||
"svg-term-cli": "^2.1.1",
|
||||
"tsd": "^0.11.0",
|
||||
"xo": "^0.25.3"
|
||||
}
|
||||
}
|
152
node_modules/karma/node_modules/ansi-styles/readme.md
generated
vendored
Normal file
152
node_modules/karma/node_modules/ansi-styles/readme.md
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
# ansi-styles [](https://travis-ci.org/chalk/ansi-styles)
|
||||
|
||||
> [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal
|
||||
|
||||
You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings.
|
||||
|
||||
<img src="screenshot.svg" width="900">
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install ansi-styles
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const style = require('ansi-styles');
|
||||
|
||||
console.log(`${style.green.open}Hello world!${style.green.close}`);
|
||||
|
||||
|
||||
// Color conversion between 16/256/truecolor
|
||||
// NOTE: If conversion goes to 16 colors or 256 colors, the original color
|
||||
// may be degraded to fit that color palette. This means terminals
|
||||
// that do not support 16 million colors will best-match the
|
||||
// original color.
|
||||
console.log(style.bgColor.ansi.hsl(120, 80, 72) + 'Hello world!' + style.bgColor.close);
|
||||
console.log(style.color.ansi256.rgb(199, 20, 250) + 'Hello world!' + style.color.close);
|
||||
console.log(style.color.ansi16m.hex('#abcdef') + 'Hello world!' + style.color.close);
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Each style has an `open` and `close` property.
|
||||
|
||||
## Styles
|
||||
|
||||
### Modifiers
|
||||
|
||||
- `reset`
|
||||
- `bold`
|
||||
- `dim`
|
||||
- `italic` *(Not widely supported)*
|
||||
- `underline`
|
||||
- `inverse`
|
||||
- `hidden`
|
||||
- `strikethrough` *(Not widely supported)*
|
||||
|
||||
### Colors
|
||||
|
||||
- `black`
|
||||
- `red`
|
||||
- `green`
|
||||
- `yellow`
|
||||
- `blue`
|
||||
- `magenta`
|
||||
- `cyan`
|
||||
- `white`
|
||||
- `blackBright` (alias: `gray`, `grey`)
|
||||
- `redBright`
|
||||
- `greenBright`
|
||||
- `yellowBright`
|
||||
- `blueBright`
|
||||
- `magentaBright`
|
||||
- `cyanBright`
|
||||
- `whiteBright`
|
||||
|
||||
### Background colors
|
||||
|
||||
- `bgBlack`
|
||||
- `bgRed`
|
||||
- `bgGreen`
|
||||
- `bgYellow`
|
||||
- `bgBlue`
|
||||
- `bgMagenta`
|
||||
- `bgCyan`
|
||||
- `bgWhite`
|
||||
- `bgBlackBright` (alias: `bgGray`, `bgGrey`)
|
||||
- `bgRedBright`
|
||||
- `bgGreenBright`
|
||||
- `bgYellowBright`
|
||||
- `bgBlueBright`
|
||||
- `bgMagentaBright`
|
||||
- `bgCyanBright`
|
||||
- `bgWhiteBright`
|
||||
|
||||
## Advanced usage
|
||||
|
||||
By default, you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module.
|
||||
|
||||
- `style.modifier`
|
||||
- `style.color`
|
||||
- `style.bgColor`
|
||||
|
||||
###### Example
|
||||
|
||||
```js
|
||||
console.log(style.color.green.open);
|
||||
```
|
||||
|
||||
Raw escape codes (i.e. without the CSI escape prefix `\u001B[` and render mode postfix `m`) are available under `style.codes`, which returns a `Map` with the open codes as keys and close codes as values.
|
||||
|
||||
###### Example
|
||||
|
||||
```js
|
||||
console.log(style.codes.get(36));
|
||||
//=> 39
|
||||
```
|
||||
|
||||
## [256 / 16 million (TrueColor) support](https://gist.github.com/XVilka/8346728)
|
||||
|
||||
`ansi-styles` uses the [`color-convert`](https://github.com/Qix-/color-convert) package to allow for converting between various colors and ANSI escapes, with support for 256 and 16 million colors.
|
||||
|
||||
The following color spaces from `color-convert` are supported:
|
||||
|
||||
- `rgb`
|
||||
- `hex`
|
||||
- `keyword`
|
||||
- `hsl`
|
||||
- `hsv`
|
||||
- `hwb`
|
||||
- `ansi`
|
||||
- `ansi256`
|
||||
|
||||
To use these, call the associated conversion function with the intended output, for example:
|
||||
|
||||
```js
|
||||
style.color.ansi.rgb(100, 200, 15); // RGB to 16 color ansi foreground code
|
||||
style.bgColor.ansi.rgb(100, 200, 15); // RGB to 16 color ansi background code
|
||||
|
||||
style.color.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
|
||||
style.bgColor.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
|
||||
|
||||
style.color.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color foreground code
|
||||
style.bgColor.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color background code
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [ansi-escapes](https://github.com/sindresorhus/ansi-escapes) - ANSI escape codes for manipulating the terminal
|
||||
|
||||
## Maintainers
|
||||
|
||||
- [Sindre Sorhus](https://github.com/sindresorhus)
|
||||
- [Josh Junon](https://github.com/qix-)
|
||||
|
||||
## For enterprise
|
||||
|
||||
Available as part of the Tidelift Subscription.
|
||||
|
||||
The maintainers of `ansi-styles` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-ansi-styles?utm_source=npm-ansi-styles&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
|
121
node_modules/karma/node_modules/cliui/CHANGELOG.md
generated
vendored
Normal file
121
node_modules/karma/node_modules/cliui/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [7.0.4](https://www.github.com/yargs/cliui/compare/v7.0.3...v7.0.4) (2020-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deno:** import UIOptions from definitions ([#97](https://www.github.com/yargs/cliui/issues/97)) ([f04f343](https://www.github.com/yargs/cliui/commit/f04f3439bc78114c7e90f82ff56f5acf16268ea8))
|
||||
|
||||
### [7.0.3](https://www.github.com/yargs/cliui/compare/v7.0.2...v7.0.3) (2020-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **exports:** node 13.0 and 13.1 require the dotted object form _with_ a string fallback ([#93](https://www.github.com/yargs/cliui/issues/93)) ([eca16fc](https://www.github.com/yargs/cliui/commit/eca16fc05d26255df3280906c36d7f0e5b05c6e9))
|
||||
|
||||
### [7.0.2](https://www.github.com/yargs/cliui/compare/v7.0.1...v7.0.2) (2020-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **exports:** node 13.0-13.6 require a string fallback ([#91](https://www.github.com/yargs/cliui/issues/91)) ([b529d7e](https://www.github.com/yargs/cliui/commit/b529d7e432901af1af7848b23ed6cf634497d961))
|
||||
|
||||
### [7.0.1](https://www.github.com/yargs/cliui/compare/v7.0.0...v7.0.1) (2020-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** main should be build/index.cjs ([dc29a3c](https://www.github.com/yargs/cliui/commit/dc29a3cc617a410aa850e06337b5954b04f2cb4d))
|
||||
|
||||
## [7.0.0](https://www.github.com/yargs/cliui/compare/v6.0.0...v7.0.0) (2020-08-16)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* tsc/ESM/Deno support (#82)
|
||||
* modernize deps and build (#80)
|
||||
|
||||
### Build System
|
||||
|
||||
* modernize deps and build ([#80](https://www.github.com/yargs/cliui/issues/80)) ([339d08d](https://www.github.com/yargs/cliui/commit/339d08dc71b15a3928aeab09042af94db2f43743))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* tsc/ESM/Deno support ([#82](https://www.github.com/yargs/cliui/issues/82)) ([4b777a5](https://www.github.com/yargs/cliui/commit/4b777a5fe01c5d8958c6708695d6aab7dbe5706c))
|
||||
|
||||
## [6.0.0](https://www.github.com/yargs/cliui/compare/v5.0.0...v6.0.0) (2019-11-10)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* update deps, drop Node 6
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* update deps, drop Node 6 ([62056df](https://www.github.com/yargs/cliui/commit/62056df))
|
||||
|
||||
## [5.0.0](https://github.com/yargs/cliui/compare/v4.1.0...v5.0.0) (2019-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update wrap-ansi to fix compatibility with latest versions of chalk. ([#60](https://github.com/yargs/cliui/issues/60)) ([7bf79ae](https://github.com/yargs/cliui/commit/7bf79ae))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Drop support for node < 6.
|
||||
|
||||
|
||||
|
||||
<a name="4.1.0"></a>
|
||||
## [4.1.0](https://github.com/yargs/cliui/compare/v4.0.0...v4.1.0) (2018-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add resetOutput method ([#57](https://github.com/yargs/cliui/issues/57)) ([7246902](https://github.com/yargs/cliui/commit/7246902))
|
||||
|
||||
|
||||
|
||||
<a name="4.0.0"></a>
|
||||
## [4.0.0](https://github.com/yargs/cliui/compare/v3.2.0...v4.0.0) (2017-12-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* downgrades strip-ansi to version 3.0.1 ([#54](https://github.com/yargs/cliui/issues/54)) ([5764c46](https://github.com/yargs/cliui/commit/5764c46))
|
||||
* set env variable FORCE_COLOR. ([#56](https://github.com/yargs/cliui/issues/56)) ([7350e36](https://github.com/yargs/cliui/commit/7350e36))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* drop support for node < 4 ([#53](https://github.com/yargs/cliui/issues/53)) ([b105376](https://github.com/yargs/cliui/commit/b105376))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add fallback for window width ([#45](https://github.com/yargs/cliui/issues/45)) ([d064922](https://github.com/yargs/cliui/commit/d064922))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* officially drop support for Node < 4
|
||||
|
||||
|
||||
|
||||
<a name="3.2.0"></a>
|
||||
## [3.2.0](https://github.com/yargs/cliui/compare/v3.1.2...v3.2.0) (2016-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reduces tarball size ([acc6c33](https://github.com/yargs/cliui/commit/acc6c33))
|
||||
|
||||
### Features
|
||||
|
||||
* adds standard-version for release management ([ff84e32](https://github.com/yargs/cliui/commit/ff84e32))
|
14
node_modules/karma/node_modules/cliui/LICENSE.txt
generated
vendored
Normal file
14
node_modules/karma/node_modules/cliui/LICENSE.txt
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
Copyright (c) 2015, Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the above copyright notice and this permission notice
|
||||
appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
|
||||
LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
141
node_modules/karma/node_modules/cliui/README.md
generated
vendored
Normal file
141
node_modules/karma/node_modules/cliui/README.md
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
# cliui
|
||||
|
||||

|
||||
[](https://www.npmjs.com/package/cliui)
|
||||
[](https://conventionalcommits.org)
|
||||

|
||||
|
||||
easily create complex multi-column command-line-interfaces.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const ui = require('cliui')()
|
||||
|
||||
ui.div('Usage: $0 [command] [options]')
|
||||
|
||||
ui.div({
|
||||
text: 'Options:',
|
||||
padding: [2, 0, 1, 0]
|
||||
})
|
||||
|
||||
ui.div(
|
||||
{
|
||||
text: "-f, --file",
|
||||
width: 20,
|
||||
padding: [0, 4, 0, 4]
|
||||
},
|
||||
{
|
||||
text: "the file to load." +
|
||||
chalk.green("(if this description is long it wraps).")
|
||||
,
|
||||
width: 20
|
||||
},
|
||||
{
|
||||
text: chalk.red("[required]"),
|
||||
align: 'right'
|
||||
}
|
||||
)
|
||||
|
||||
console.log(ui.toString())
|
||||
```
|
||||
|
||||
## Deno/ESM Support
|
||||
|
||||
As of `v7` `cliui` supports [Deno](https://github.com/denoland/deno) and
|
||||
[ESM](https://nodejs.org/api/esm.html#esm_ecmascript_modules):
|
||||
|
||||
```typescript
|
||||
import cliui from "https://deno.land/x/cliui/deno.ts";
|
||||
|
||||
const ui = cliui({})
|
||||
|
||||
ui.div('Usage: $0 [command] [options]')
|
||||
|
||||
ui.div({
|
||||
text: 'Options:',
|
||||
padding: [2, 0, 1, 0]
|
||||
})
|
||||
|
||||
ui.div({
|
||||
text: "-f, --file",
|
||||
width: 20,
|
||||
padding: [0, 4, 0, 4]
|
||||
})
|
||||
|
||||
console.log(ui.toString())
|
||||
```
|
||||
|
||||
<img width="500" src="screenshot.png">
|
||||
|
||||
## Layout DSL
|
||||
|
||||
cliui exposes a simple layout DSL:
|
||||
|
||||
If you create a single `ui.div`, passing a string rather than an
|
||||
object:
|
||||
|
||||
* `\n`: characters will be interpreted as new rows.
|
||||
* `\t`: characters will be interpreted as new columns.
|
||||
* `\s`: characters will be interpreted as padding.
|
||||
|
||||
**as an example...**
|
||||
|
||||
```js
|
||||
var ui = require('./')({
|
||||
width: 60
|
||||
})
|
||||
|
||||
ui.div(
|
||||
'Usage: node ./bin/foo.js\n' +
|
||||
' <regex>\t provide a regex\n' +
|
||||
' <glob>\t provide a glob\t [required]'
|
||||
)
|
||||
|
||||
console.log(ui.toString())
|
||||
```
|
||||
|
||||
**will output:**
|
||||
|
||||
```shell
|
||||
Usage: node ./bin/foo.js
|
||||
<regex> provide a regex
|
||||
<glob> provide a glob [required]
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
```js
|
||||
cliui = require('cliui')
|
||||
```
|
||||
|
||||
### cliui({width: integer})
|
||||
|
||||
Specify the maximum width of the UI being generated.
|
||||
If no width is provided, cliui will try to get the current window's width and use it, and if that doesn't work, width will be set to `80`.
|
||||
|
||||
### cliui({wrap: boolean})
|
||||
|
||||
Enable or disable the wrapping of text in a column.
|
||||
|
||||
### cliui.div(column, column, column)
|
||||
|
||||
Create a row with any number of columns, a column
|
||||
can either be a string, or an object with the following
|
||||
options:
|
||||
|
||||
* **text:** some text to place in the column.
|
||||
* **width:** the width of a column.
|
||||
* **align:** alignment, `right` or `center`.
|
||||
* **padding:** `[top, right, bottom, left]`.
|
||||
* **border:** should a border be placed around the div?
|
||||
|
||||
### cliui.span(column, column, column)
|
||||
|
||||
Similar to `div`, except the next row will be appended without
|
||||
a new line being created.
|
||||
|
||||
### cliui.resetOutput()
|
||||
|
||||
Resets the UI elements of the current cliui instance, maintaining the values
|
||||
set for `width` and `wrap`.
|
302
node_modules/karma/node_modules/cliui/build/index.cjs
generated
vendored
Normal file
302
node_modules/karma/node_modules/cliui/build/index.cjs
generated
vendored
Normal file
|
@ -0,0 +1,302 @@
|
|||
'use strict';
|
||||
|
||||
const align = {
|
||||
right: alignRight,
|
||||
center: alignCenter
|
||||
};
|
||||
const top = 0;
|
||||
const right = 1;
|
||||
const bottom = 2;
|
||||
const left = 3;
|
||||
class UI {
|
||||
constructor(opts) {
|
||||
var _a;
|
||||
this.width = opts.width;
|
||||
this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;
|
||||
this.rows = [];
|
||||
}
|
||||
span(...args) {
|
||||
const cols = this.div(...args);
|
||||
cols.span = true;
|
||||
}
|
||||
resetOutput() {
|
||||
this.rows = [];
|
||||
}
|
||||
div(...args) {
|
||||
if (args.length === 0) {
|
||||
this.div('');
|
||||
}
|
||||
if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
|
||||
return this.applyLayoutDSL(args[0]);
|
||||
}
|
||||
const cols = args.map(arg => {
|
||||
if (typeof arg === 'string') {
|
||||
return this.colFromString(arg);
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
this.rows.push(cols);
|
||||
return cols;
|
||||
}
|
||||
shouldApplyLayoutDSL(...args) {
|
||||
return args.length === 1 && typeof args[0] === 'string' &&
|
||||
/[\t\n]/.test(args[0]);
|
||||
}
|
||||
applyLayoutDSL(str) {
|
||||
const rows = str.split('\n').map(row => row.split('\t'));
|
||||
let leftColumnWidth = 0;
|
||||
// simple heuristic for layout, make sure the
|
||||
// second column lines up along the left-hand.
|
||||
// don't allow the first column to take up more
|
||||
// than 50% of the screen.
|
||||
rows.forEach(columns => {
|
||||
if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
|
||||
leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));
|
||||
}
|
||||
});
|
||||
// generate a table:
|
||||
// replacing ' ' with padding calculations.
|
||||
// using the algorithmically generated width.
|
||||
rows.forEach(columns => {
|
||||
this.div(...columns.map((r, i) => {
|
||||
return {
|
||||
text: r.trim(),
|
||||
padding: this.measurePadding(r),
|
||||
width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
|
||||
};
|
||||
}));
|
||||
});
|
||||
return this.rows[this.rows.length - 1];
|
||||
}
|
||||
colFromString(text) {
|
||||
return {
|
||||
text,
|
||||
padding: this.measurePadding(text)
|
||||
};
|
||||
}
|
||||
measurePadding(str) {
|
||||
// measure padding without ansi escape codes
|
||||
const noAnsi = mixin.stripAnsi(str);
|
||||
return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
|
||||
}
|
||||
toString() {
|
||||
const lines = [];
|
||||
this.rows.forEach(row => {
|
||||
this.rowToString(row, lines);
|
||||
});
|
||||
// don't display any lines with the
|
||||
// hidden flag set.
|
||||
return lines
|
||||
.filter(line => !line.hidden)
|
||||
.map(line => line.text)
|
||||
.join('\n');
|
||||
}
|
||||
rowToString(row, lines) {
|
||||
this.rasterize(row).forEach((rrow, r) => {
|
||||
let str = '';
|
||||
rrow.forEach((col, c) => {
|
||||
const { width } = row[c]; // the width with padding.
|
||||
const wrapWidth = this.negatePadding(row[c]); // the width without padding.
|
||||
let ts = col; // temporary string used during alignment/padding.
|
||||
if (wrapWidth > mixin.stringWidth(col)) {
|
||||
ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));
|
||||
}
|
||||
// align the string within its column.
|
||||
if (row[c].align && row[c].align !== 'left' && this.wrap) {
|
||||
const fn = align[row[c].align];
|
||||
ts = fn(ts, wrapWidth);
|
||||
if (mixin.stringWidth(ts) < wrapWidth) {
|
||||
ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);
|
||||
}
|
||||
}
|
||||
// apply border and padding to string.
|
||||
const padding = row[c].padding || [0, 0, 0, 0];
|
||||
if (padding[left]) {
|
||||
str += ' '.repeat(padding[left]);
|
||||
}
|
||||
str += addBorder(row[c], ts, '| ');
|
||||
str += ts;
|
||||
str += addBorder(row[c], ts, ' |');
|
||||
if (padding[right]) {
|
||||
str += ' '.repeat(padding[right]);
|
||||
}
|
||||
// if prior row is span, try to render the
|
||||
// current row on the prior line.
|
||||
if (r === 0 && lines.length > 0) {
|
||||
str = this.renderInline(str, lines[lines.length - 1]);
|
||||
}
|
||||
});
|
||||
// remove trailing whitespace.
|
||||
lines.push({
|
||||
text: str.replace(/ +$/, ''),
|
||||
span: row.span
|
||||
});
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
// if the full 'source' can render in
|
||||
// the target line, do so.
|
||||
renderInline(source, previousLine) {
|
||||
const match = source.match(/^ */);
|
||||
const leadingWhitespace = match ? match[0].length : 0;
|
||||
const target = previousLine.text;
|
||||
const targetTextWidth = mixin.stringWidth(target.trimRight());
|
||||
if (!previousLine.span) {
|
||||
return source;
|
||||
}
|
||||
// if we're not applying wrapping logic,
|
||||
// just always append to the span.
|
||||
if (!this.wrap) {
|
||||
previousLine.hidden = true;
|
||||
return target + source;
|
||||
}
|
||||
if (leadingWhitespace < targetTextWidth) {
|
||||
return source;
|
||||
}
|
||||
previousLine.hidden = true;
|
||||
return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();
|
||||
}
|
||||
rasterize(row) {
|
||||
const rrows = [];
|
||||
const widths = this.columnWidths(row);
|
||||
let wrapped;
|
||||
// word wrap all columns, and create
|
||||
// a data-structure that is easy to rasterize.
|
||||
row.forEach((col, c) => {
|
||||
// leave room for left and right padding.
|
||||
col.width = widths[c];
|
||||
if (this.wrap) {
|
||||
wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');
|
||||
}
|
||||
else {
|
||||
wrapped = col.text.split('\n');
|
||||
}
|
||||
if (col.border) {
|
||||
wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');
|
||||
wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");
|
||||
}
|
||||
// add top and bottom padding.
|
||||
if (col.padding) {
|
||||
wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));
|
||||
wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));
|
||||
}
|
||||
wrapped.forEach((str, r) => {
|
||||
if (!rrows[r]) {
|
||||
rrows.push([]);
|
||||
}
|
||||
const rrow = rrows[r];
|
||||
for (let i = 0; i < c; i++) {
|
||||
if (rrow[i] === undefined) {
|
||||
rrow.push('');
|
||||
}
|
||||
}
|
||||
rrow.push(str);
|
||||
});
|
||||
});
|
||||
return rrows;
|
||||
}
|
||||
negatePadding(col) {
|
||||
let wrapWidth = col.width || 0;
|
||||
if (col.padding) {
|
||||
wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);
|
||||
}
|
||||
if (col.border) {
|
||||
wrapWidth -= 4;
|
||||
}
|
||||
return wrapWidth;
|
||||
}
|
||||
columnWidths(row) {
|
||||
if (!this.wrap) {
|
||||
return row.map(col => {
|
||||
return col.width || mixin.stringWidth(col.text);
|
||||
});
|
||||
}
|
||||
let unset = row.length;
|
||||
let remainingWidth = this.width;
|
||||
// column widths can be set in config.
|
||||
const widths = row.map(col => {
|
||||
if (col.width) {
|
||||
unset--;
|
||||
remainingWidth -= col.width;
|
||||
return col.width;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// any unset widths should be calculated.
|
||||
const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;
|
||||
return widths.map((w, i) => {
|
||||
if (w === undefined) {
|
||||
return Math.max(unsetWidth, _minWidth(row[i]));
|
||||
}
|
||||
return w;
|
||||
});
|
||||
}
|
||||
}
|
||||
function addBorder(col, ts, style) {
|
||||
if (col.border) {
|
||||
if (/[.']-+[.']/.test(ts)) {
|
||||
return '';
|
||||
}
|
||||
if (ts.trim().length !== 0) {
|
||||
return style;
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
// calculates the minimum width of
|
||||
// a column, based on padding preferences.
|
||||
function _minWidth(col) {
|
||||
const padding = col.padding || [];
|
||||
const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);
|
||||
if (col.border) {
|
||||
return minWidth + 4;
|
||||
}
|
||||
return minWidth;
|
||||
}
|
||||
function getWindowWidth() {
|
||||
/* istanbul ignore next: depends on terminal */
|
||||
if (typeof process === 'object' && process.stdout && process.stdout.columns) {
|
||||
return process.stdout.columns;
|
||||
}
|
||||
return 80;
|
||||
}
|
||||
function alignRight(str, width) {
|
||||
str = str.trim();
|
||||
const strWidth = mixin.stringWidth(str);
|
||||
if (strWidth < width) {
|
||||
return ' '.repeat(width - strWidth) + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function alignCenter(str, width) {
|
||||
str = str.trim();
|
||||
const strWidth = mixin.stringWidth(str);
|
||||
/* istanbul ignore next */
|
||||
if (strWidth >= width) {
|
||||
return str;
|
||||
}
|
||||
return ' '.repeat((width - strWidth) >> 1) + str;
|
||||
}
|
||||
let mixin;
|
||||
function cliui(opts, _mixin) {
|
||||
mixin = _mixin;
|
||||
return new UI({
|
||||
width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),
|
||||
wrap: opts === null || opts === void 0 ? void 0 : opts.wrap
|
||||
});
|
||||
}
|
||||
|
||||
// Bootstrap cliui with CommonJS dependencies:
|
||||
const stringWidth = require('string-width');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const wrap = require('wrap-ansi');
|
||||
function ui(opts) {
|
||||
return cliui(opts, {
|
||||
stringWidth,
|
||||
stripAnsi,
|
||||
wrap
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ui;
|
287
node_modules/karma/node_modules/cliui/build/lib/index.js
generated
vendored
Normal file
287
node_modules/karma/node_modules/cliui/build/lib/index.js
generated
vendored
Normal file
|
@ -0,0 +1,287 @@
|
|||
'use strict';
|
||||
const align = {
|
||||
right: alignRight,
|
||||
center: alignCenter
|
||||
};
|
||||
const top = 0;
|
||||
const right = 1;
|
||||
const bottom = 2;
|
||||
const left = 3;
|
||||
export class UI {
|
||||
constructor(opts) {
|
||||
var _a;
|
||||
this.width = opts.width;
|
||||
this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;
|
||||
this.rows = [];
|
||||
}
|
||||
span(...args) {
|
||||
const cols = this.div(...args);
|
||||
cols.span = true;
|
||||
}
|
||||
resetOutput() {
|
||||
this.rows = [];
|
||||
}
|
||||
div(...args) {
|
||||
if (args.length === 0) {
|
||||
this.div('');
|
||||
}
|
||||
if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
|
||||
return this.applyLayoutDSL(args[0]);
|
||||
}
|
||||
const cols = args.map(arg => {
|
||||
if (typeof arg === 'string') {
|
||||
return this.colFromString(arg);
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
this.rows.push(cols);
|
||||
return cols;
|
||||
}
|
||||
shouldApplyLayoutDSL(...args) {
|
||||
return args.length === 1 && typeof args[0] === 'string' &&
|
||||
/[\t\n]/.test(args[0]);
|
||||
}
|
||||
applyLayoutDSL(str) {
|
||||
const rows = str.split('\n').map(row => row.split('\t'));
|
||||
let leftColumnWidth = 0;
|
||||
// simple heuristic for layout, make sure the
|
||||
// second column lines up along the left-hand.
|
||||
// don't allow the first column to take up more
|
||||
// than 50% of the screen.
|
||||
rows.forEach(columns => {
|
||||
if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
|
||||
leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));
|
||||
}
|
||||
});
|
||||
// generate a table:
|
||||
// replacing ' ' with padding calculations.
|
||||
// using the algorithmically generated width.
|
||||
rows.forEach(columns => {
|
||||
this.div(...columns.map((r, i) => {
|
||||
return {
|
||||
text: r.trim(),
|
||||
padding: this.measurePadding(r),
|
||||
width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
|
||||
};
|
||||
}));
|
||||
});
|
||||
return this.rows[this.rows.length - 1];
|
||||
}
|
||||
colFromString(text) {
|
||||
return {
|
||||
text,
|
||||
padding: this.measurePadding(text)
|
||||
};
|
||||
}
|
||||
measurePadding(str) {
|
||||
// measure padding without ansi escape codes
|
||||
const noAnsi = mixin.stripAnsi(str);
|
||||
return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
|
||||
}
|
||||
toString() {
|
||||
const lines = [];
|
||||
this.rows.forEach(row => {
|
||||
this.rowToString(row, lines);
|
||||
});
|
||||
// don't display any lines with the
|
||||
// hidden flag set.
|
||||
return lines
|
||||
.filter(line => !line.hidden)
|
||||
.map(line => line.text)
|
||||
.join('\n');
|
||||
}
|
||||
rowToString(row, lines) {
|
||||
this.rasterize(row).forEach((rrow, r) => {
|
||||
let str = '';
|
||||
rrow.forEach((col, c) => {
|
||||
const { width } = row[c]; // the width with padding.
|
||||
const wrapWidth = this.negatePadding(row[c]); // the width without padding.
|
||||
let ts = col; // temporary string used during alignment/padding.
|
||||
if (wrapWidth > mixin.stringWidth(col)) {
|
||||
ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));
|
||||
}
|
||||
// align the string within its column.
|
||||
if (row[c].align && row[c].align !== 'left' && this.wrap) {
|
||||
const fn = align[row[c].align];
|
||||
ts = fn(ts, wrapWidth);
|
||||
if (mixin.stringWidth(ts) < wrapWidth) {
|
||||
ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);
|
||||
}
|
||||
}
|
||||
// apply border and padding to string.
|
||||
const padding = row[c].padding || [0, 0, 0, 0];
|
||||
if (padding[left]) {
|
||||
str += ' '.repeat(padding[left]);
|
||||
}
|
||||
str += addBorder(row[c], ts, '| ');
|
||||
str += ts;
|
||||
str += addBorder(row[c], ts, ' |');
|
||||
if (padding[right]) {
|
||||
str += ' '.repeat(padding[right]);
|
||||
}
|
||||
// if prior row is span, try to render the
|
||||
// current row on the prior line.
|
||||
if (r === 0 && lines.length > 0) {
|
||||
str = this.renderInline(str, lines[lines.length - 1]);
|
||||
}
|
||||
});
|
||||
// remove trailing whitespace.
|
||||
lines.push({
|
||||
text: str.replace(/ +$/, ''),
|
||||
span: row.span
|
||||
});
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
// if the full 'source' can render in
|
||||
// the target line, do so.
|
||||
renderInline(source, previousLine) {
|
||||
const match = source.match(/^ */);
|
||||
const leadingWhitespace = match ? match[0].length : 0;
|
||||
const target = previousLine.text;
|
||||
const targetTextWidth = mixin.stringWidth(target.trimRight());
|
||||
if (!previousLine.span) {
|
||||
return source;
|
||||
}
|
||||
// if we're not applying wrapping logic,
|
||||
// just always append to the span.
|
||||
if (!this.wrap) {
|
||||
previousLine.hidden = true;
|
||||
return target + source;
|
||||
}
|
||||
if (leadingWhitespace < targetTextWidth) {
|
||||
return source;
|
||||
}
|
||||
previousLine.hidden = true;
|
||||
return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();
|
||||
}
|
||||
rasterize(row) {
|
||||
const rrows = [];
|
||||
const widths = this.columnWidths(row);
|
||||
let wrapped;
|
||||
// word wrap all columns, and create
|
||||
// a data-structure that is easy to rasterize.
|
||||
row.forEach((col, c) => {
|
||||
// leave room for left and right padding.
|
||||
col.width = widths[c];
|
||||
if (this.wrap) {
|
||||
wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');
|
||||
}
|
||||
else {
|
||||
wrapped = col.text.split('\n');
|
||||
}
|
||||
if (col.border) {
|
||||
wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');
|
||||
wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");
|
||||
}
|
||||
// add top and bottom padding.
|
||||
if (col.padding) {
|
||||
wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));
|
||||
wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));
|
||||
}
|
||||
wrapped.forEach((str, r) => {
|
||||
if (!rrows[r]) {
|
||||
rrows.push([]);
|
||||
}
|
||||
const rrow = rrows[r];
|
||||
for (let i = 0; i < c; i++) {
|
||||
if (rrow[i] === undefined) {
|
||||
rrow.push('');
|
||||
}
|
||||
}
|
||||
rrow.push(str);
|
||||
});
|
||||
});
|
||||
return rrows;
|
||||
}
|
||||
negatePadding(col) {
|
||||
let wrapWidth = col.width || 0;
|
||||
if (col.padding) {
|
||||
wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);
|
||||
}
|
||||
if (col.border) {
|
||||
wrapWidth -= 4;
|
||||
}
|
||||
return wrapWidth;
|
||||
}
|
||||
columnWidths(row) {
|
||||
if (!this.wrap) {
|
||||
return row.map(col => {
|
||||
return col.width || mixin.stringWidth(col.text);
|
||||
});
|
||||
}
|
||||
let unset = row.length;
|
||||
let remainingWidth = this.width;
|
||||
// column widths can be set in config.
|
||||
const widths = row.map(col => {
|
||||
if (col.width) {
|
||||
unset--;
|
||||
remainingWidth -= col.width;
|
||||
return col.width;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// any unset widths should be calculated.
|
||||
const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;
|
||||
return widths.map((w, i) => {
|
||||
if (w === undefined) {
|
||||
return Math.max(unsetWidth, _minWidth(row[i]));
|
||||
}
|
||||
return w;
|
||||
});
|
||||
}
|
||||
}
|
||||
function addBorder(col, ts, style) {
|
||||
if (col.border) {
|
||||
if (/[.']-+[.']/.test(ts)) {
|
||||
return '';
|
||||
}
|
||||
if (ts.trim().length !== 0) {
|
||||
return style;
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
// calculates the minimum width of
|
||||
// a column, based on padding preferences.
|
||||
function _minWidth(col) {
|
||||
const padding = col.padding || [];
|
||||
const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);
|
||||
if (col.border) {
|
||||
return minWidth + 4;
|
||||
}
|
||||
return minWidth;
|
||||
}
|
||||
function getWindowWidth() {
|
||||
/* istanbul ignore next: depends on terminal */
|
||||
if (typeof process === 'object' && process.stdout && process.stdout.columns) {
|
||||
return process.stdout.columns;
|
||||
}
|
||||
return 80;
|
||||
}
|
||||
function alignRight(str, width) {
|
||||
str = str.trim();
|
||||
const strWidth = mixin.stringWidth(str);
|
||||
if (strWidth < width) {
|
||||
return ' '.repeat(width - strWidth) + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function alignCenter(str, width) {
|
||||
str = str.trim();
|
||||
const strWidth = mixin.stringWidth(str);
|
||||
/* istanbul ignore next */
|
||||
if (strWidth >= width) {
|
||||
return str;
|
||||
}
|
||||
return ' '.repeat((width - strWidth) >> 1) + str;
|
||||
}
|
||||
let mixin;
|
||||
export function cliui(opts, _mixin) {
|
||||
mixin = _mixin;
|
||||
return new UI({
|
||||
width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),
|
||||
wrap: opts === null || opts === void 0 ? void 0 : opts.wrap
|
||||
});
|
||||
}
|
27
node_modules/karma/node_modules/cliui/build/lib/string-utils.js
generated
vendored
Normal file
27
node_modules/karma/node_modules/cliui/build/lib/string-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Minimal replacement for ansi string helpers "wrap-ansi" and "strip-ansi".
|
||||
// to facilitate ESM and Deno modules.
|
||||
// TODO: look at porting https://www.npmjs.com/package/wrap-ansi to ESM.
|
||||
// The npm application
|
||||
// Copyright (c) npm, Inc. and Contributors
|
||||
// Licensed on the terms of The Artistic License 2.0
|
||||
// See: https://github.com/npm/cli/blob/4c65cd952bc8627811735bea76b9b110cc4fc80e/lib/utils/ansi-trim.js
|
||||
const ansi = new RegExp('\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|' +
|
||||
'\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)', 'g');
|
||||
export function stripAnsi(str) {
|
||||
return str.replace(ansi, '');
|
||||
}
|
||||
export function wrap(str, width) {
|
||||
const [start, end] = str.match(ansi) || ['', ''];
|
||||
str = stripAnsi(str);
|
||||
let wrapped = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (i !== 0 && (i % width) === 0) {
|
||||
wrapped += '\n';
|
||||
}
|
||||
wrapped += str.charAt(i);
|
||||
}
|
||||
if (start && end) {
|
||||
wrapped = `${start}${wrapped}${end}`;
|
||||
}
|
||||
return wrapped;
|
||||
}
|
13
node_modules/karma/node_modules/cliui/index.mjs
generated
vendored
Normal file
13
node_modules/karma/node_modules/cliui/index.mjs
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Bootstrap cliui with CommonJS dependencies:
|
||||
import { cliui } from './build/lib/index.js'
|
||||
import { wrap, stripAnsi } from './build/lib/string-utils.js'
|
||||
|
||||
export default function ui (opts) {
|
||||
return cliui(opts, {
|
||||
stringWidth: (str) => {
|
||||
return [...str].length
|
||||
},
|
||||
stripAnsi,
|
||||
wrap
|
||||
})
|
||||
}
|
83
node_modules/karma/node_modules/cliui/package.json
generated
vendored
Normal file
83
node_modules/karma/node_modules/cliui/package.json
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"name": "cliui",
|
||||
"version": "7.0.4",
|
||||
"description": "easily create complex multi-column command-line-interfaces",
|
||||
"main": "build/index.cjs",
|
||||
"exports": {
|
||||
".": [
|
||||
{
|
||||
"import": "./index.mjs",
|
||||
"require": "./build/index.cjs"
|
||||
},
|
||||
"./build/index.cjs"
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"module": "./index.mjs",
|
||||
"scripts": {
|
||||
"check": "standardx '**/*.ts' && standardx '**/*.js' && standardx '**/*.cjs'",
|
||||
"fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'",
|
||||
"pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs",
|
||||
"test": "c8 mocha ./test/*.cjs",
|
||||
"test:esm": "c8 mocha ./test/esm/cliui-test.mjs",
|
||||
"postest": "check",
|
||||
"coverage": "c8 report --check-coverage",
|
||||
"precompile": "rimraf build",
|
||||
"compile": "tsc",
|
||||
"postcompile": "npm run build:cjs",
|
||||
"build:cjs": "rollup -c",
|
||||
"prepare": "npm run compile"
|
||||
},
|
||||
"repository": "yargs/cliui",
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"**/example/**"
|
||||
],
|
||||
"globals": [
|
||||
"it"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"command-line",
|
||||
"layout",
|
||||
"design",
|
||||
"console",
|
||||
"wrap",
|
||||
"table"
|
||||
],
|
||||
"author": "Ben Coe <ben@npmjs.com>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.27",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.0",
|
||||
"@typescript-eslint/parser": "^4.0.0",
|
||||
"@wessberg/rollup-plugin-ts": "^1.3.2",
|
||||
"c8": "^7.3.0",
|
||||
"chai": "^4.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"gts": "^3.0.0",
|
||||
"mocha": "^8.1.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.23.1",
|
||||
"standardx": "^7.0.0",
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
"files": [
|
||||
"build",
|
||||
"index.mjs",
|
||||
"!*.d.ts"
|
||||
],
|
||||
"engine": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
54
node_modules/karma/node_modules/color-convert/CHANGELOG.md
generated
vendored
Normal file
54
node_modules/karma/node_modules/color-convert/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
# 1.0.0 - 2016-01-07
|
||||
|
||||
- Removed: unused speed test
|
||||
- Added: Automatic routing between previously unsupported conversions
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
- Removed: `xxx2xxx()` and `xxx2xxxRaw()` functions
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
- Removed: `convert()` class
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
- Changed: all functions to lookup dictionary
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
- Changed: `ansi` to `ansi256`
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
- Fixed: argument grouping for functions requiring only one argument
|
||||
([#27](https://github.com/Qix-/color-convert/pull/27))
|
||||
|
||||
# 0.6.0 - 2015-07-23
|
||||
|
||||
- Added: methods to handle
|
||||
[ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) 16/256 colors:
|
||||
- rgb2ansi16
|
||||
- rgb2ansi
|
||||
- hsl2ansi16
|
||||
- hsl2ansi
|
||||
- hsv2ansi16
|
||||
- hsv2ansi
|
||||
- hwb2ansi16
|
||||
- hwb2ansi
|
||||
- cmyk2ansi16
|
||||
- cmyk2ansi
|
||||
- keyword2ansi16
|
||||
- keyword2ansi
|
||||
- ansi162rgb
|
||||
- ansi162hsl
|
||||
- ansi162hsv
|
||||
- ansi162hwb
|
||||
- ansi162cmyk
|
||||
- ansi162keyword
|
||||
- ansi2rgb
|
||||
- ansi2hsl
|
||||
- ansi2hsv
|
||||
- ansi2hwb
|
||||
- ansi2cmyk
|
||||
- ansi2keyword
|
||||
([#18](https://github.com/harthur/color-convert/pull/18))
|
||||
|
||||
# 0.5.3 - 2015-06-02
|
||||
|
||||
- Fixed: hsl2hsv does not return `NaN` anymore when using `[0,0,0]`
|
||||
([#15](https://github.com/harthur/color-convert/issues/15))
|
||||
|
||||
---
|
||||
|
||||
Check out commit logs for older releases
|
21
node_modules/karma/node_modules/color-convert/LICENSE
generated
vendored
Normal file
21
node_modules/karma/node_modules/color-convert/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
68
node_modules/karma/node_modules/color-convert/README.md
generated
vendored
Normal file
68
node_modules/karma/node_modules/color-convert/README.md
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
# color-convert
|
||||
|
||||
[](https://travis-ci.org/Qix-/color-convert)
|
||||
|
||||
Color-convert is a color conversion library for JavaScript and node.
|
||||
It converts all ways between `rgb`, `hsl`, `hsv`, `hwb`, `cmyk`, `ansi`, `ansi16`, `hex` strings, and CSS `keyword`s (will round to closest):
|
||||
|
||||
```js
|
||||
var convert = require('color-convert');
|
||||
|
||||
convert.rgb.hsl(140, 200, 100); // [96, 48, 59]
|
||||
convert.keyword.rgb('blue'); // [0, 0, 255]
|
||||
|
||||
var rgbChannels = convert.rgb.channels; // 3
|
||||
var cmykChannels = convert.cmyk.channels; // 4
|
||||
var ansiChannels = convert.ansi16.channels; // 1
|
||||
```
|
||||
|
||||
# Install
|
||||
|
||||
```console
|
||||
$ npm install color-convert
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
Simply get the property of the _from_ and _to_ conversion that you're looking for.
|
||||
|
||||
All functions have a rounded and unrounded variant. By default, return values are rounded. To get the unrounded (raw) results, simply tack on `.raw` to the function.
|
||||
|
||||
All 'from' functions have a hidden property called `.channels` that indicates the number of channels the function expects (not including alpha).
|
||||
|
||||
```js
|
||||
var convert = require('color-convert');
|
||||
|
||||
// Hex to LAB
|
||||
convert.hex.lab('DEADBF'); // [ 76, 21, -2 ]
|
||||
convert.hex.lab.raw('DEADBF'); // [ 75.56213190997677, 20.653827952644754, -2.290532499330533 ]
|
||||
|
||||
// RGB to CMYK
|
||||
convert.rgb.cmyk(167, 255, 4); // [ 35, 0, 98, 0 ]
|
||||
convert.rgb.cmyk.raw(167, 255, 4); // [ 34.509803921568626, 0, 98.43137254901961, 0 ]
|
||||
```
|
||||
|
||||
### Arrays
|
||||
All functions that accept multiple arguments also support passing an array.
|
||||
|
||||
Note that this does **not** apply to functions that convert from a color that only requires one value (e.g. `keyword`, `ansi256`, `hex`, etc.)
|
||||
|
||||
```js
|
||||
var convert = require('color-convert');
|
||||
|
||||
convert.rgb.hex(123, 45, 67); // '7B2D43'
|
||||
convert.rgb.hex([123, 45, 67]); // '7B2D43'
|
||||
```
|
||||
|
||||
## Routing
|
||||
|
||||
Conversions that don't have an _explicitly_ defined conversion (in [conversions.js](conversions.js)), but can be converted by means of sub-conversions (e.g. XYZ -> **RGB** -> CMYK), are automatically routed together. This allows just about any color model supported by `color-convert` to be converted to any other model, so long as a sub-conversion path exists. This is also true for conversions requiring more than one step in between (e.g. LCH -> **LAB** -> **XYZ** -> **RGB** -> Hex).
|
||||
|
||||
Keep in mind that extensive conversions _may_ result in a loss of precision, and exist only to be complete. For a list of "direct" (single-step) conversions, see [conversions.js](conversions.js).
|
||||
|
||||
# Contribute
|
||||
|
||||
If there is a new model you would like to support, or want to add a direct conversion between two existing models, please send us a pull request.
|
||||
|
||||
# License
|
||||
Copyright © 2011-2016, Heather Arthur and Josh Junon. Licensed under the [MIT License](LICENSE).
|
839
node_modules/karma/node_modules/color-convert/conversions.js
generated
vendored
Normal file
839
node_modules/karma/node_modules/color-convert/conversions.js
generated
vendored
Normal file
|
@ -0,0 +1,839 @@
|
|||
/* MIT license */
|
||||
/* eslint-disable no-mixed-operators */
|
||||
const cssKeywords = require('color-name');
|
||||
|
||||
// NOTE: conversions should only return primitive values (i.e. arrays, or
|
||||
// values that give correct `typeof` results).
|
||||
// do not use box values types (i.e. Number(), String(), etc.)
|
||||
|
||||
const reverseKeywords = {};
|
||||
for (const key of Object.keys(cssKeywords)) {
|
||||
reverseKeywords[cssKeywords[key]] = key;
|
||||
}
|
||||
|
||||
const convert = {
|
||||
rgb: {channels: 3, labels: 'rgb'},
|
||||
hsl: {channels: 3, labels: 'hsl'},
|
||||
hsv: {channels: 3, labels: 'hsv'},
|
||||
hwb: {channels: 3, labels: 'hwb'},
|
||||
cmyk: {channels: 4, labels: 'cmyk'},
|
||||
xyz: {channels: 3, labels: 'xyz'},
|
||||
lab: {channels: 3, labels: 'lab'},
|
||||
lch: {channels: 3, labels: 'lch'},
|
||||
hex: {channels: 1, labels: ['hex']},
|
||||
keyword: {channels: 1, labels: ['keyword']},
|
||||
ansi16: {channels: 1, labels: ['ansi16']},
|
||||
ansi256: {channels: 1, labels: ['ansi256']},
|
||||
hcg: {channels: 3, labels: ['h', 'c', 'g']},
|
||||
apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
|
||||
gray: {channels: 1, labels: ['gray']}
|
||||
};
|
||||
|
||||
module.exports = convert;
|
||||
|
||||
// Hide .channels and .labels properties
|
||||
for (const model of Object.keys(convert)) {
|
||||
if (!('channels' in convert[model])) {
|
||||
throw new Error('missing channels property: ' + model);
|
||||
}
|
||||
|
||||
if (!('labels' in convert[model])) {
|
||||
throw new Error('missing channel labels property: ' + model);
|
||||
}
|
||||
|
||||
if (convert[model].labels.length !== convert[model].channels) {
|
||||
throw new Error('channel and label counts mismatch: ' + model);
|
||||
}
|
||||
|
||||
const {channels, labels} = convert[model];
|
||||
delete convert[model].channels;
|
||||
delete convert[model].labels;
|
||||
Object.defineProperty(convert[model], 'channels', {value: channels});
|
||||
Object.defineProperty(convert[model], 'labels', {value: labels});
|
||||
}
|
||||
|
||||
convert.rgb.hsl = function (rgb) {
|
||||
const r = rgb[0] / 255;
|
||||
const g = rgb[1] / 255;
|
||||
const b = rgb[2] / 255;
|
||||
const min = Math.min(r, g, b);
|
||||
const max = Math.max(r, g, b);
|
||||
const delta = max - min;
|
||||
let h;
|
||||
let s;
|
||||
|
||||
if (max === min) {
|
||||
h = 0;
|
||||
} else if (r === max) {
|
||||
h = (g - b) / delta;
|
||||
} else if (g === max) {
|
||||
h = 2 + (b - r) / delta;
|
||||
} else if (b === max) {
|
||||
h = 4 + (r - g) / delta;
|
||||
}
|
||||
|
||||
h = Math.min(h * 60, 360);
|
||||
|
||||
if (h < 0) {
|
||||
h += 360;
|
||||
}
|
||||
|
||||
const l = (min + max) / 2;
|
||||
|
||||
if (max === min) {
|
||||
s = 0;
|
||||
} else if (l <= 0.5) {
|
||||
s = delta / (max + min);
|
||||
} else {
|
||||
s = delta / (2 - max - min);
|
||||
}
|
||||
|
||||
return [h, s * 100, l * 100];
|
||||
};
|
||||
|
||||
convert.rgb.hsv = function (rgb) {
|
||||
let rdif;
|
||||
let gdif;
|
||||
let bdif;
|
||||
let h;
|
||||
let s;
|
||||
|
||||
const r = rgb[0] / 255;
|
||||
const g = rgb[1] / 255;
|
||||
const b = rgb[2] / 255;
|
||||
const v = Math.max(r, g, b);
|
||||
const diff = v - Math.min(r, g, b);
|
||||
const diffc = function (c) {
|
||||
return (v - c) / 6 / diff + 1 / 2;
|
||||
};
|
||||
|
||||
if (diff === 0) {
|
||||
h = 0;
|
||||
s = 0;
|
||||
} else {
|
||||
s = diff / v;
|
||||
rdif = diffc(r);
|
||||
gdif = diffc(g);
|
||||
bdif = diffc(b);
|
||||
|
||||
if (r === v) {
|
||||
h = bdif - gdif;
|
||||
} else if (g === v) {
|
||||
h = (1 / 3) + rdif - bdif;
|
||||
} else if (b === v) {
|
||||
h = (2 / 3) + gdif - rdif;
|
||||
}
|
||||
|
||||
if (h < 0) {
|
||||
h += 1;
|
||||
} else if (h > 1) {
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
h * 360,
|
||||
s * 100,
|
||||
v * 100
|
||||
];
|
||||
};
|
||||
|
||||
convert.rgb.hwb = function (rgb) {
|
||||
const r = rgb[0];
|
||||
const g = rgb[1];
|
||||
let b = rgb[2];
|
||||
const h = convert.rgb.hsl(rgb)[0];
|
||||
const w = 1 / 255 * Math.min(r, Math.min(g, b));
|
||||
|
||||
b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
|
||||
|
||||
return [h, w * 100, b * 100];
|
||||
};
|
||||
|
||||
convert.rgb.cmyk = function (rgb) {
|
||||
const r = rgb[0] / 255;
|
||||
const g = rgb[1] / 255;
|
||||
const b = rgb[2] / 255;
|
||||
|
||||
const k = Math.min(1 - r, 1 - g, 1 - b);
|
||||
const c = (1 - r - k) / (1 - k) || 0;
|
||||
const m = (1 - g - k) / (1 - k) || 0;
|
||||
const y = (1 - b - k) / (1 - k) || 0;
|
||||
|
||||
return [c * 100, m * 100, y * 100, k * 100];
|
||||
};
|
||||
|
||||
function comparativeDistance(x, y) {
|
||||
/*
|
||||
See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
|
||||
*/
|
||||
return (
|
||||
((x[0] - y[0]) ** 2) +
|
||||
((x[1] - y[1]) ** 2) +
|
||||
((x[2] - y[2]) ** 2)
|
||||
);
|
||||
}
|
||||
|
||||
convert.rgb.keyword = function (rgb) {
|
||||
const reversed = reverseKeywords[rgb];
|
||||
if (reversed) {
|
||||
return reversed;
|
||||
}
|
||||
|
||||
let currentClosestDistance = Infinity;
|
||||
let currentClosestKeyword;
|
||||
|
||||
for (const keyword of Object.keys(cssKeywords)) {
|
||||
const value = cssKeywords[keyword];
|
||||
|
||||
// Compute comparative distance
|
||||
const distance = comparativeDistance(rgb, value);
|
||||
|
||||
// Check if its less, if so set as closest
|
||||
if (distance < currentClosestDistance) {
|
||||
currentClosestDistance = distance;
|
||||
currentClosestKeyword = keyword;
|
||||
}
|
||||
}
|
||||
|
||||
return currentClosestKeyword;
|
||||
};
|
||||
|
||||
convert.keyword.rgb = function (keyword) {
|
||||
return cssKeywords[keyword];
|
||||
};
|
||||
|
||||
convert.rgb.xyz = function (rgb) {
|
||||
let r = rgb[0] / 255;
|
||||
let g = rgb[1] / 255;
|
||||
let b = rgb[2] / 255;
|
||||
|
||||
// Assume sRGB
|
||||
r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92);
|
||||
g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92);
|
||||
b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92);
|
||||
|
||||
const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
|
||||
const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
|
||||
const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
|
||||
|
||||
return [x * 100, y * 100, z * 100];
|
||||
};
|
||||
|
||||
convert.rgb.lab = function (rgb) {
|
||||
const xyz = convert.rgb.xyz(rgb);
|
||||
let x = xyz[0];
|
||||
let y = xyz[1];
|
||||
let z = xyz[2];
|
||||
|
||||
x /= 95.047;
|
||||
y /= 100;
|
||||
z /= 108.883;
|
||||
|
||||
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
|
||||
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
|
||||
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
|
||||
|
||||
const l = (116 * y) - 16;
|
||||
const a = 500 * (x - y);
|
||||
const b = 200 * (y - z);
|
||||
|
||||
return [l, a, b];
|
||||
};
|
||||
|
||||
convert.hsl.rgb = function (hsl) {
|
||||
const h = hsl[0] / 360;
|
||||
const s = hsl[1] / 100;
|
||||
const l = hsl[2] / 100;
|
||||
let t2;
|
||||
let t3;
|
||||
let val;
|
||||
|
||||
if (s === 0) {
|
||||
val = l * 255;
|
||||
return [val, val, val];
|
||||
}
|
||||
|
||||
if (l < 0.5) {
|
||||
t2 = l * (1 + s);
|
||||
} else {
|
||||
t2 = l + s - l * s;
|
||||
}
|
||||
|
||||
const t1 = 2 * l - t2;
|
||||
|
||||
const rgb = [0, 0, 0];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
t3 = h + 1 / 3 * -(i - 1);
|
||||
if (t3 < 0) {
|
||||
t3++;
|
||||
}
|
||||
|
||||
if (t3 > 1) {
|
||||
t3--;
|
||||
}
|
||||
|
||||
if (6 * t3 < 1) {
|
||||
val = t1 + (t2 - t1) * 6 * t3;
|
||||
} else if (2 * t3 < 1) {
|
||||
val = t2;
|
||||
} else if (3 * t3 < 2) {
|
||||
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
|
||||
} else {
|
||||
val = t1;
|
||||
}
|
||||
|
||||
rgb[i] = val * 255;
|
||||
}
|
||||
|
||||
return rgb;
|
||||
};
|
||||
|
||||
convert.hsl.hsv = function (hsl) {
|
||||
const h = hsl[0];
|
||||
let s = hsl[1] / 100;
|
||||
let l = hsl[2] / 100;
|
||||
let smin = s;
|
||||
const lmin = Math.max(l, 0.01);
|
||||
|
||||
l *= 2;
|
||||
s *= (l <= 1) ? l : 2 - l;
|
||||
smin *= lmin <= 1 ? lmin : 2 - lmin;
|
||||
const v = (l + s) / 2;
|
||||
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
|
||||
|
||||
return [h, sv * 100, v * 100];
|
||||
};
|
||||
|
||||
convert.hsv.rgb = function (hsv) {
|
||||
const h = hsv[0] / 60;
|
||||
const s = hsv[1] / 100;
|
||||
let v = hsv[2] / 100;
|
||||
const hi = Math.floor(h) % 6;
|
||||
|
||||
const f = h - Math.floor(h);
|
||||
const p = 255 * v * (1 - s);
|
||||
const q = 255 * v * (1 - (s * f));
|
||||
const t = 255 * v * (1 - (s * (1 - f)));
|
||||
v *= 255;
|
||||
|
||||
switch (hi) {
|
||||
case 0:
|
||||
return [v, t, p];
|
||||
case 1:
|
||||
return [q, v, p];
|
||||
case 2:
|
||||
return [p, v, t];
|
||||
case 3:
|
||||
return [p, q, v];
|
||||
case 4:
|
||||
return [t, p, v];
|
||||
case 5:
|
||||
return [v, p, q];
|
||||
}
|
||||
};
|
||||
|
||||
convert.hsv.hsl = function (hsv) {
|
||||
const h = hsv[0];
|
||||
const s = hsv[1] / 100;
|
||||
const v = hsv[2] / 100;
|
||||
const vmin = Math.max(v, 0.01);
|
||||
let sl;
|
||||
let l;
|
||||
|
||||
l = (2 - s) * v;
|
||||
const lmin = (2 - s) * vmin;
|
||||
sl = s * vmin;
|
||||
sl /= (lmin <= 1) ? lmin : 2 - lmin;
|
||||
sl = sl || 0;
|
||||
l /= 2;
|
||||
|
||||
return [h, sl * 100, l * 100];
|
||||
};
|
||||
|
||||
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
|
||||
convert.hwb.rgb = function (hwb) {
|
||||
const h = hwb[0] / 360;
|
||||
let wh = hwb[1] / 100;
|
||||
let bl = hwb[2] / 100;
|
||||
const ratio = wh + bl;
|
||||
let f;
|
||||
|
||||
// Wh + bl cant be > 1
|
||||
if (ratio > 1) {
|
||||
wh /= ratio;
|
||||
bl /= ratio;
|
||||
}
|
||||
|
||||
const i = Math.floor(6 * h);
|
||||
const v = 1 - bl;
|
||||
f = 6 * h - i;
|
||||
|
||||
if ((i & 0x01) !== 0) {
|
||||
f = 1 - f;
|
||||
}
|
||||
|
||||
const n = wh + f * (v - wh); // Linear interpolation
|
||||
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
/* eslint-disable max-statements-per-line,no-multi-spaces */
|
||||
switch (i) {
|
||||
default:
|
||||
case 6:
|
||||
case 0: r = v; g = n; b = wh; break;
|
||||
case 1: r = n; g = v; b = wh; break;
|
||||
case 2: r = wh; g = v; b = n; break;
|
||||
case 3: r = wh; g = n; b = v; break;
|
||||
case 4: r = n; g = wh; b = v; break;
|
||||
case 5: r = v; g = wh; b = n; break;
|
||||
}
|
||||
/* eslint-enable max-statements-per-line,no-multi-spaces */
|
||||
|
||||
return [r * 255, g * 255, b * 255];
|
||||
};
|
||||
|
||||
convert.cmyk.rgb = function (cmyk) {
|
||||
const c = cmyk[0] / 100;
|
||||
const m = cmyk[1] / 100;
|
||||
const y = cmyk[2] / 100;
|
||||
const k = cmyk[3] / 100;
|
||||
|
||||
const r = 1 - Math.min(1, c * (1 - k) + k);
|
||||
const g = 1 - Math.min(1, m * (1 - k) + k);
|
||||
const b = 1 - Math.min(1, y * (1 - k) + k);
|
||||
|
||||
return [r * 255, g * 255, b * 255];
|
||||
};
|
||||
|
||||
convert.xyz.rgb = function (xyz) {
|
||||
const x = xyz[0] / 100;
|
||||
const y = xyz[1] / 100;
|
||||
const z = xyz[2] / 100;
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
|
||||
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
|
||||
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
|
||||
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
|
||||
|
||||
// Assume sRGB
|
||||
r = r > 0.0031308
|
||||
? ((1.055 * (r ** (1.0 / 2.4))) - 0.055)
|
||||
: r * 12.92;
|
||||
|
||||
g = g > 0.0031308
|
||||
? ((1.055 * (g ** (1.0 / 2.4))) - 0.055)
|
||||
: g * 12.92;
|
||||
|
||||
b = b > 0.0031308
|
||||
? ((1.055 * (b ** (1.0 / 2.4))) - 0.055)
|
||||
: b * 12.92;
|
||||
|
||||
r = Math.min(Math.max(0, r), 1);
|
||||
g = Math.min(Math.max(0, g), 1);
|
||||
b = Math.min(Math.max(0, b), 1);
|
||||
|
||||
return [r * 255, g * 255, b * 255];
|
||||
};
|
||||
|
||||
convert.xyz.lab = function (xyz) {
|
||||
let x = xyz[0];
|
||||
let y = xyz[1];
|
||||
let z = xyz[2];
|
||||
|
||||
x /= 95.047;
|
||||
y /= 100;
|
||||
z /= 108.883;
|
||||
|
||||
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
|
||||
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
|
||||
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
|
||||
|
||||
const l = (116 * y) - 16;
|
||||
const a = 500 * (x - y);
|
||||
const b = 200 * (y - z);
|
||||
|
||||
return [l, a, b];
|
||||
};
|
||||
|
||||
convert.lab.xyz = function (lab) {
|
||||
const l = lab[0];
|
||||
const a = lab[1];
|
||||
const b = lab[2];
|
||||
let x;
|
||||
let y;
|
||||
let z;
|
||||
|
||||
y = (l + 16) / 116;
|
||||
x = a / 500 + y;
|
||||
z = y - b / 200;
|
||||
|
||||
const y2 = y ** 3;
|
||||
const x2 = x ** 3;
|
||||
const z2 = z ** 3;
|
||||
y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
|
||||
x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
|
||||
z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
|
||||
|
||||
x *= 95.047;
|
||||
y *= 100;
|
||||
z *= 108.883;
|
||||
|
||||
return [x, y, z];
|
||||
};
|
||||
|
||||
convert.lab.lch = function (lab) {
|
||||
const l = lab[0];
|
||||
const a = lab[1];
|
||||
const b = lab[2];
|
||||
let h;
|
||||
|
||||
const hr = Math.atan2(b, a);
|
||||
h = hr * 360 / 2 / Math.PI;
|
||||
|
||||
if (h < 0) {
|
||||
h += 360;
|
||||
}
|
||||
|
||||
const c = Math.sqrt(a * a + b * b);
|
||||
|
||||
return [l, c, h];
|
||||
};
|
||||
|
||||
convert.lch.lab = function (lch) {
|
||||
const l = lch[0];
|
||||
const c = lch[1];
|
||||
const h = lch[2];
|
||||
|
||||
const hr = h / 360 * 2 * Math.PI;
|
||||
const a = c * Math.cos(hr);
|
||||
const b = c * Math.sin(hr);
|
||||
|
||||
return [l, a, b];
|
||||
};
|
||||
|
||||
convert.rgb.ansi16 = function (args, saturation = null) {
|
||||
const [r, g, b] = args;
|
||||
let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization
|
||||
|
||||
value = Math.round(value / 50);
|
||||
|
||||
if (value === 0) {
|
||||
return 30;
|
||||
}
|
||||
|
||||
let ansi = 30
|
||||
+ ((Math.round(b / 255) << 2)
|
||||
| (Math.round(g / 255) << 1)
|
||||
| Math.round(r / 255));
|
||||
|
||||
if (value === 2) {
|
||||
ansi += 60;
|
||||
}
|
||||
|
||||
return ansi;
|
||||
};
|
||||
|
||||
convert.hsv.ansi16 = function (args) {
|
||||
// Optimization here; we already know the value and don't need to get
|
||||
// it converted for us.
|
||||
return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
|
||||
};
|
||||
|
||||
convert.rgb.ansi256 = function (args) {
|
||||
const r = args[0];
|
||||
const g = args[1];
|
||||
const b = args[2];
|
||||
|
||||
// We use the extended greyscale palette here, with the exception of
|
||||
// black and white. normal palette only has 4 greyscale shades.
|
||||
if (r === g && g === b) {
|
||||
if (r < 8) {
|
||||
return 16;
|
||||
}
|
||||
|
||||
if (r > 248) {
|
||||
return 231;
|
||||
}
|
||||
|
||||
return Math.round(((r - 8) / 247) * 24) + 232;
|
||||
}
|
||||
|
||||
const ansi = 16
|
||||
+ (36 * Math.round(r / 255 * 5))
|
||||
+ (6 * Math.round(g / 255 * 5))
|
||||
+ Math.round(b / 255 * 5);
|
||||
|
||||
return ansi;
|
||||
};
|
||||
|
||||
convert.ansi16.rgb = function (args) {
|
||||
let color = args % 10;
|
||||
|
||||
// Handle greyscale
|
||||
if (color === 0 || color === 7) {
|
||||
if (args > 50) {
|
||||
color += 3.5;
|
||||
}
|
||||
|
||||
color = color / 10.5 * 255;
|
||||
|
||||
return [color, color, color];
|
||||
}
|
||||
|
||||
const mult = (~~(args > 50) + 1) * 0.5;
|
||||
const r = ((color & 1) * mult) * 255;
|
||||
const g = (((color >> 1) & 1) * mult) * 255;
|
||||
const b = (((color >> 2) & 1) * mult) * 255;
|
||||
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
convert.ansi256.rgb = function (args) {
|
||||
// Handle greyscale
|
||||
if (args >= 232) {
|
||||
const c = (args - 232) * 10 + 8;
|
||||
return [c, c, c];
|
||||
}
|
||||
|
||||
args -= 16;
|
||||
|
||||
let rem;
|
||||
const r = Math.floor(args / 36) / 5 * 255;
|
||||
const g = Math.floor((rem = args % 36) / 6) / 5 * 255;
|
||||
const b = (rem % 6) / 5 * 255;
|
||||
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
convert.rgb.hex = function (args) {
|
||||
const integer = ((Math.round(args[0]) & 0xFF) << 16)
|
||||
+ ((Math.round(args[1]) & 0xFF) << 8)
|
||||
+ (Math.round(args[2]) & 0xFF);
|
||||
|
||||
const string = integer.toString(16).toUpperCase();
|
||||
return '000000'.substring(string.length) + string;
|
||||
};
|
||||
|
||||
convert.hex.rgb = function (args) {
|
||||
const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
|
||||
if (!match) {
|
||||
return [0, 0, 0];
|
||||
}
|
||||
|
||||
let colorString = match[0];
|
||||
|
||||
if (match[0].length === 3) {
|
||||
colorString = colorString.split('').map(char => {
|
||||
return char + char;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const integer = parseInt(colorString, 16);
|
||||
const r = (integer >> 16) & 0xFF;
|
||||
const g = (integer >> 8) & 0xFF;
|
||||
const b = integer & 0xFF;
|
||||
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
convert.rgb.hcg = function (rgb) {
|
||||
const r = rgb[0] / 255;
|
||||
const g = rgb[1] / 255;
|
||||
const b = rgb[2] / 255;
|
||||
const max = Math.max(Math.max(r, g), b);
|
||||
const min = Math.min(Math.min(r, g), b);
|
||||
const chroma = (max - min);
|
||||
let grayscale;
|
||||
let hue;
|
||||
|
||||
if (chroma < 1) {
|
||||
grayscale = min / (1 - chroma);
|
||||
} else {
|
||||
grayscale = 0;
|
||||
}
|
||||
|
||||
if (chroma <= 0) {
|
||||
hue = 0;
|
||||
} else
|
||||
if (max === r) {
|
||||
hue = ((g - b) / chroma) % 6;
|
||||
} else
|
||||
if (max === g) {
|
||||
hue = 2 + (b - r) / chroma;
|
||||
} else {
|
||||
hue = 4 + (r - g) / chroma;
|
||||
}
|
||||
|
||||
hue /= 6;
|
||||
hue %= 1;
|
||||
|
||||
return [hue * 360, chroma * 100, grayscale * 100];
|
||||
};
|
||||
|
||||
convert.hsl.hcg = function (hsl) {
|
||||
const s = hsl[1] / 100;
|
||||
const l = hsl[2] / 100;
|
||||
|
||||
const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l));
|
||||
|
||||
let f = 0;
|
||||
if (c < 1.0) {
|
||||
f = (l - 0.5 * c) / (1.0 - c);
|
||||
}
|
||||
|
||||
return [hsl[0], c * 100, f * 100];
|
||||
};
|
||||
|
||||
convert.hsv.hcg = function (hsv) {
|
||||
const s = hsv[1] / 100;
|
||||
const v = hsv[2] / 100;
|
||||
|
||||
const c = s * v;
|
||||
let f = 0;
|
||||
|
||||
if (c < 1.0) {
|
||||
f = (v - c) / (1 - c);
|
||||
}
|
||||
|
||||
return [hsv[0], c * 100, f * 100];
|
||||
};
|
||||
|
||||
convert.hcg.rgb = function (hcg) {
|
||||
const h = hcg[0] / 360;
|
||||
const c = hcg[1] / 100;
|
||||
const g = hcg[2] / 100;
|
||||
|
||||
if (c === 0.0) {
|
||||
return [g * 255, g * 255, g * 255];
|
||||
}
|
||||
|
||||
const pure = [0, 0, 0];
|
||||
const hi = (h % 1) * 6;
|
||||
const v = hi % 1;
|
||||
const w = 1 - v;
|
||||
let mg = 0;
|
||||
|
||||
/* eslint-disable max-statements-per-line */
|
||||
switch (Math.floor(hi)) {
|
||||
case 0:
|
||||
pure[0] = 1; pure[1] = v; pure[2] = 0; break;
|
||||
case 1:
|
||||
pure[0] = w; pure[1] = 1; pure[2] = 0; break;
|
||||
case 2:
|
||||
pure[0] = 0; pure[1] = 1; pure[2] = v; break;
|
||||
case 3:
|
||||
pure[0] = 0; pure[1] = w; pure[2] = 1; break;
|
||||
case 4:
|
||||
pure[0] = v; pure[1] = 0; pure[2] = 1; break;
|
||||
default:
|
||||
pure[0] = 1; pure[1] = 0; pure[2] = w;
|
||||
}
|
||||
/* eslint-enable max-statements-per-line */
|
||||
|
||||
mg = (1.0 - c) * g;
|
||||
|
||||
return [
|
||||
(c * pure[0] + mg) * 255,
|
||||
(c * pure[1] + mg) * 255,
|
||||
(c * pure[2] + mg) * 255
|
||||
];
|
||||
};
|
||||
|
||||
convert.hcg.hsv = function (hcg) {
|
||||
const c = hcg[1] / 100;
|
||||
const g = hcg[2] / 100;
|
||||
|
||||
const v = c + g * (1.0 - c);
|
||||
let f = 0;
|
||||
|
||||
if (v > 0.0) {
|
||||
f = c / v;
|
||||
}
|
||||
|
||||
return [hcg[0], f * 100, v * 100];
|
||||
};
|
||||
|
||||
convert.hcg.hsl = function (hcg) {
|
||||
const c = hcg[1] / 100;
|
||||
const g = hcg[2] / 100;
|
||||
|
||||
const l = g * (1.0 - c) + 0.5 * c;
|
||||
let s = 0;
|
||||
|
||||
if (l > 0.0 && l < 0.5) {
|
||||
s = c / (2 * l);
|
||||
} else
|
||||
if (l >= 0.5 && l < 1.0) {
|
||||
s = c / (2 * (1 - l));
|
||||
}
|
||||
|
||||
return [hcg[0], s * 100, l * 100];
|
||||
};
|
||||
|
||||
convert.hcg.hwb = function (hcg) {
|
||||
const c = hcg[1] / 100;
|
||||
const g = hcg[2] / 100;
|
||||
const v = c + g * (1.0 - c);
|
||||
return [hcg[0], (v - c) * 100, (1 - v) * 100];
|
||||
};
|
||||
|
||||
convert.hwb.hcg = function (hwb) {
|
||||
const w = hwb[1] / 100;
|
||||
const b = hwb[2] / 100;
|
||||
const v = 1 - b;
|
||||
const c = v - w;
|
||||
let g = 0;
|
||||
|
||||
if (c < 1) {
|
||||
g = (v - c) / (1 - c);
|
||||
}
|
||||
|
||||
return [hwb[0], c * 100, g * 100];
|
||||
};
|
||||
|
||||
convert.apple.rgb = function (apple) {
|
||||
return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
|
||||
};
|
||||
|
||||
convert.rgb.apple = function (rgb) {
|
||||
return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
|
||||
};
|
||||
|
||||
convert.gray.rgb = function (args) {
|
||||
return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
|
||||
};
|
||||
|
||||
convert.gray.hsl = function (args) {
|
||||
return [0, 0, args[0]];
|
||||
};
|
||||
|
||||
convert.gray.hsv = convert.gray.hsl;
|
||||
|
||||
convert.gray.hwb = function (gray) {
|
||||
return [0, 100, gray[0]];
|
||||
};
|
||||
|
||||
convert.gray.cmyk = function (gray) {
|
||||
return [0, 0, 0, gray[0]];
|
||||
};
|
||||
|
||||
convert.gray.lab = function (gray) {
|
||||
return [gray[0], 0, 0];
|
||||
};
|
||||
|
||||
convert.gray.hex = function (gray) {
|
||||
const val = Math.round(gray[0] / 100 * 255) & 0xFF;
|
||||
const integer = (val << 16) + (val << 8) + val;
|
||||
|
||||
const string = integer.toString(16).toUpperCase();
|
||||
return '000000'.substring(string.length) + string;
|
||||
};
|
||||
|
||||
convert.rgb.gray = function (rgb) {
|
||||
const val = (rgb[0] + rgb[1] + rgb[2]) / 3;
|
||||
return [val / 255 * 100];
|
||||
};
|
81
node_modules/karma/node_modules/color-convert/index.js
generated
vendored
Normal file
81
node_modules/karma/node_modules/color-convert/index.js
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
const conversions = require('./conversions');
|
||||
const route = require('./route');
|
||||
|
||||
const convert = {};
|
||||
|
||||
const models = Object.keys(conversions);
|
||||
|
||||
function wrapRaw(fn) {
|
||||
const wrappedFn = function (...args) {
|
||||
const arg0 = args[0];
|
||||
if (arg0 === undefined || arg0 === null) {
|
||||
return arg0;
|
||||
}
|
||||
|
||||
if (arg0.length > 1) {
|
||||
args = arg0;
|
||||
}
|
||||
|
||||
return fn(args);
|
||||
};
|
||||
|
||||
// Preserve .conversion property if there is one
|
||||
if ('conversion' in fn) {
|
||||
wrappedFn.conversion = fn.conversion;
|
||||
}
|
||||
|
||||
return wrappedFn;
|
||||
}
|
||||
|
||||
function wrapRounded(fn) {
|
||||
const wrappedFn = function (...args) {
|
||||
const arg0 = args[0];
|
||||
|
||||
if (arg0 === undefined || arg0 === null) {
|
||||
return arg0;
|
||||
}
|
||||
|
||||
if (arg0.length > 1) {
|
||||
args = arg0;
|
||||
}
|
||||
|
||||
const result = fn(args);
|
||||
|
||||
// We're assuming the result is an array here.
|
||||
// see notice in conversions.js; don't use box types
|
||||
// in conversion functions.
|
||||
if (typeof result === 'object') {
|
||||
for (let len = result.length, i = 0; i < len; i++) {
|
||||
result[i] = Math.round(result[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Preserve .conversion property if there is one
|
||||
if ('conversion' in fn) {
|
||||
wrappedFn.conversion = fn.conversion;
|
||||
}
|
||||
|
||||
return wrappedFn;
|
||||
}
|
||||
|
||||
models.forEach(fromModel => {
|
||||
convert[fromModel] = {};
|
||||
|
||||
Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
|
||||
Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
|
||||
|
||||
const routes = route(fromModel);
|
||||
const routeModels = Object.keys(routes);
|
||||
|
||||
routeModels.forEach(toModel => {
|
||||
const fn = routes[toModel];
|
||||
|
||||
convert[fromModel][toModel] = wrapRounded(fn);
|
||||
convert[fromModel][toModel].raw = wrapRaw(fn);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = convert;
|
48
node_modules/karma/node_modules/color-convert/package.json
generated
vendored
Normal file
48
node_modules/karma/node_modules/color-convert/package.json
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "color-convert",
|
||||
"description": "Plain color conversion functions",
|
||||
"version": "2.0.1",
|
||||
"author": "Heather Arthur <fayearthur@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": "Qix-/color-convert",
|
||||
"scripts": {
|
||||
"pretest": "xo",
|
||||
"test": "node test/basic.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"color",
|
||||
"colour",
|
||||
"convert",
|
||||
"converter",
|
||||
"conversion",
|
||||
"rgb",
|
||||
"hsl",
|
||||
"hsv",
|
||||
"hwb",
|
||||
"cmyk",
|
||||
"ansi",
|
||||
"ansi16"
|
||||
],
|
||||
"files": [
|
||||
"index.js",
|
||||
"conversions.js",
|
||||
"route.js"
|
||||
],
|
||||
"xo": {
|
||||
"rules": {
|
||||
"default-case": 0,
|
||||
"no-inline-comments": 0,
|
||||
"operator-linebreak": 0
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"chalk": "^2.4.2",
|
||||
"xo": "^0.24.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
}
|
97
node_modules/karma/node_modules/color-convert/route.js
generated
vendored
Normal file
97
node_modules/karma/node_modules/color-convert/route.js
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
const conversions = require('./conversions');
|
||||
|
||||
/*
|
||||
This function routes a model to all other models.
|
||||
|
||||
all functions that are routed have a property `.conversion` attached
|
||||
to the returned synthetic function. This property is an array
|
||||
of strings, each with the steps in between the 'from' and 'to'
|
||||
color models (inclusive).
|
||||
|
||||
conversions that are not possible simply are not included.
|
||||
*/
|
||||
|
||||
function buildGraph() {
|
||||
const graph = {};
|
||||
// https://jsperf.com/object-keys-vs-for-in-with-closure/3
|
||||
const models = Object.keys(conversions);
|
||||
|
||||
for (let len = models.length, i = 0; i < len; i++) {
|
||||
graph[models[i]] = {
|
||||
// http://jsperf.com/1-vs-infinity
|
||||
// micro-opt, but this is simple.
|
||||
distance: -1,
|
||||
parent: null
|
||||
};
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
function deriveBFS(fromModel) {
|
||||
const graph = buildGraph();
|
||||
const queue = [fromModel]; // Unshift -> queue -> pop
|
||||
|
||||
graph[fromModel].distance = 0;
|
||||
|
||||
while (queue.length) {
|
||||
const current = queue.pop();
|
||||
const adjacents = Object.keys(conversions[current]);
|
||||
|
||||
for (let len = adjacents.length, i = 0; i < len; i++) {
|
||||
const adjacent = adjacents[i];
|
||||
const node = graph[adjacent];
|
||||
|
||||
if (node.distance === -1) {
|
||||
node.distance = graph[current].distance + 1;
|
||||
node.parent = current;
|
||||
queue.unshift(adjacent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
function link(from, to) {
|
||||
return function (args) {
|
||||
return to(from(args));
|
||||
};
|
||||
}
|
||||
|
||||
function wrapConversion(toModel, graph) {
|
||||
const path = [graph[toModel].parent, toModel];
|
||||
let fn = conversions[graph[toModel].parent][toModel];
|
||||
|
||||
let cur = graph[toModel].parent;
|
||||
while (graph[cur].parent) {
|
||||
path.unshift(graph[cur].parent);
|
||||
fn = link(conversions[graph[cur].parent][cur], fn);
|
||||
cur = graph[cur].parent;
|
||||
}
|
||||
|
||||
fn.conversion = path;
|
||||
return fn;
|
||||
}
|
||||
|
||||
module.exports = function (fromModel) {
|
||||
const graph = deriveBFS(fromModel);
|
||||
const conversion = {};
|
||||
|
||||
const models = Object.keys(graph);
|
||||
for (let len = models.length, i = 0; i < len; i++) {
|
||||
const toModel = models[i];
|
||||
const node = graph[toModel];
|
||||
|
||||
if (node.parent === null) {
|
||||
// No possible conversion, or this node is the source model.
|
||||
continue;
|
||||
}
|
||||
|
||||
conversion[toModel] = wrapConversion(toModel, graph);
|
||||
}
|
||||
|
||||
return conversion;
|
||||
};
|
||||
|
8
node_modules/karma/node_modules/color-name/LICENSE
generated
vendored
Normal file
8
node_modules/karma/node_modules/color-name/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Dmitry Ivanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
11
node_modules/karma/node_modules/color-name/README.md
generated
vendored
Normal file
11
node_modules/karma/node_modules/color-name/README.md
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
A JSON with color names and its values. Based on http://dev.w3.org/csswg/css-color/#named-colors.
|
||||
|
||||
[](https://nodei.co/npm/color-name/)
|
||||
|
||||
|
||||
```js
|
||||
var colors = require('color-name');
|
||||
colors.red //[255,0,0]
|
||||
```
|
||||
|
||||
<a href="LICENSE"><img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/MIT_logo.svg" width="120"/></a>
|
152
node_modules/karma/node_modules/color-name/index.js
generated
vendored
Normal file
152
node_modules/karma/node_modules/color-name/index.js
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
"aliceblue": [240, 248, 255],
|
||||
"antiquewhite": [250, 235, 215],
|
||||
"aqua": [0, 255, 255],
|
||||
"aquamarine": [127, 255, 212],
|
||||
"azure": [240, 255, 255],
|
||||
"beige": [245, 245, 220],
|
||||
"bisque": [255, 228, 196],
|
||||
"black": [0, 0, 0],
|
||||
"blanchedalmond": [255, 235, 205],
|
||||
"blue": [0, 0, 255],
|
||||
"blueviolet": [138, 43, 226],
|
||||
"brown": [165, 42, 42],
|
||||
"burlywood": [222, 184, 135],
|
||||
"cadetblue": [95, 158, 160],
|
||||
"chartreuse": [127, 255, 0],
|
||||
"chocolate": [210, 105, 30],
|
||||
"coral": [255, 127, 80],
|
||||
"cornflowerblue": [100, 149, 237],
|
||||
"cornsilk": [255, 248, 220],
|
||||
"crimson": [220, 20, 60],
|
||||
"cyan": [0, 255, 255],
|
||||
"darkblue": [0, 0, 139],
|
||||
"darkcyan": [0, 139, 139],
|
||||
"darkgoldenrod": [184, 134, 11],
|
||||
"darkgray": [169, 169, 169],
|
||||
"darkgreen": [0, 100, 0],
|
||||
"darkgrey": [169, 169, 169],
|
||||
"darkkhaki": [189, 183, 107],
|
||||
"darkmagenta": [139, 0, 139],
|
||||
"darkolivegreen": [85, 107, 47],
|
||||
"darkorange": [255, 140, 0],
|
||||
"darkorchid": [153, 50, 204],
|
||||
"darkred": [139, 0, 0],
|
||||
"darksalmon": [233, 150, 122],
|
||||
"darkseagreen": [143, 188, 143],
|
||||
"darkslateblue": [72, 61, 139],
|
||||
"darkslategray": [47, 79, 79],
|
||||
"darkslategrey": [47, 79, 79],
|
||||
"darkturquoise": [0, 206, 209],
|
||||
"darkviolet": [148, 0, 211],
|
||||
"deeppink": [255, 20, 147],
|
||||
"deepskyblue": [0, 191, 255],
|
||||
"dimgray": [105, 105, 105],
|
||||
"dimgrey": [105, 105, 105],
|
||||
"dodgerblue": [30, 144, 255],
|
||||
"firebrick": [178, 34, 34],
|
||||
"floralwhite": [255, 250, 240],
|
||||
"forestgreen": [34, 139, 34],
|
||||
"fuchsia": [255, 0, 255],
|
||||
"gainsboro": [220, 220, 220],
|
||||
"ghostwhite": [248, 248, 255],
|
||||
"gold": [255, 215, 0],
|
||||
"goldenrod": [218, 165, 32],
|
||||
"gray": [128, 128, 128],
|
||||
"green": [0, 128, 0],
|
||||
"greenyellow": [173, 255, 47],
|
||||
"grey": [128, 128, 128],
|
||||
"honeydew": [240, 255, 240],
|
||||
"hotpink": [255, 105, 180],
|
||||
"indianred": [205, 92, 92],
|
||||
"indigo": [75, 0, 130],
|
||||
"ivory": [255, 255, 240],
|
||||
"khaki": [240, 230, 140],
|
||||
"lavender": [230, 230, 250],
|
||||
"lavenderblush": [255, 240, 245],
|
||||
"lawngreen": [124, 252, 0],
|
||||
"lemonchiffon": [255, 250, 205],
|
||||
"lightblue": [173, 216, 230],
|
||||
"lightcoral": [240, 128, 128],
|
||||
"lightcyan": [224, 255, 255],
|
||||
"lightgoldenrodyellow": [250, 250, 210],
|
||||
"lightgray": [211, 211, 211],
|
||||
"lightgreen": [144, 238, 144],
|
||||
"lightgrey": [211, 211, 211],
|
||||
"lightpink": [255, 182, 193],
|
||||
"lightsalmon": [255, 160, 122],
|
||||
"lightseagreen": [32, 178, 170],
|
||||
"lightskyblue": [135, 206, 250],
|
||||
"lightslategray": [119, 136, 153],
|
||||
"lightslategrey": [119, 136, 153],
|
||||
"lightsteelblue": [176, 196, 222],
|
||||
"lightyellow": [255, 255, 224],
|
||||
"lime": [0, 255, 0],
|
||||
"limegreen": [50, 205, 50],
|
||||
"linen": [250, 240, 230],
|
||||
"magenta": [255, 0, 255],
|
||||
"maroon": [128, 0, 0],
|
||||
"mediumaquamarine": [102, 205, 170],
|
||||
"mediumblue": [0, 0, 205],
|
||||
"mediumorchid": [186, 85, 211],
|
||||
"mediumpurple": [147, 112, 219],
|
||||
"mediumseagreen": [60, 179, 113],
|
||||
"mediumslateblue": [123, 104, 238],
|
||||
"mediumspringgreen": [0, 250, 154],
|
||||
"mediumturquoise": [72, 209, 204],
|
||||
"mediumvioletred": [199, 21, 133],
|
||||
"midnightblue": [25, 25, 112],
|
||||
"mintcream": [245, 255, 250],
|
||||
"mistyrose": [255, 228, 225],
|
||||
"moccasin": [255, 228, 181],
|
||||
"navajowhite": [255, 222, 173],
|
||||
"navy": [0, 0, 128],
|
||||
"oldlace": [253, 245, 230],
|
||||
"olive": [128, 128, 0],
|
||||
"olivedrab": [107, 142, 35],
|
||||
"orange": [255, 165, 0],
|
||||
"orangered": [255, 69, 0],
|
||||
"orchid": [218, 112, 214],
|
||||
"palegoldenrod": [238, 232, 170],
|
||||
"palegreen": [152, 251, 152],
|
||||
"paleturquoise": [175, 238, 238],
|
||||
"palevioletred": [219, 112, 147],
|
||||
"papayawhip": [255, 239, 213],
|
||||
"peachpuff": [255, 218, 185],
|
||||
"peru": [205, 133, 63],
|
||||
"pink": [255, 192, 203],
|
||||
"plum": [221, 160, 221],
|
||||
"powderblue": [176, 224, 230],
|
||||
"purple": [128, 0, 128],
|
||||
"rebeccapurple": [102, 51, 153],
|
||||
"red": [255, 0, 0],
|
||||
"rosybrown": [188, 143, 143],
|
||||
"royalblue": [65, 105, 225],
|
||||
"saddlebrown": [139, 69, 19],
|
||||
"salmon": [250, 128, 114],
|
||||
"sandybrown": [244, 164, 96],
|
||||
"seagreen": [46, 139, 87],
|
||||
"seashell": [255, 245, 238],
|
||||
"sienna": [160, 82, 45],
|
||||
"silver": [192, 192, 192],
|
||||
"skyblue": [135, 206, 235],
|
||||
"slateblue": [106, 90, 205],
|
||||
"slategray": [112, 128, 144],
|
||||
"slategrey": [112, 128, 144],
|
||||
"snow": [255, 250, 250],
|
||||
"springgreen": [0, 255, 127],
|
||||
"steelblue": [70, 130, 180],
|
||||
"tan": [210, 180, 140],
|
||||
"teal": [0, 128, 128],
|
||||
"thistle": [216, 191, 216],
|
||||
"tomato": [255, 99, 71],
|
||||
"turquoise": [64, 224, 208],
|
||||
"violet": [238, 130, 238],
|
||||
"wheat": [245, 222, 179],
|
||||
"white": [255, 255, 255],
|
||||
"whitesmoke": [245, 245, 245],
|
||||
"yellow": [255, 255, 0],
|
||||
"yellowgreen": [154, 205, 50]
|
||||
};
|
28
node_modules/karma/node_modules/color-name/package.json
generated
vendored
Normal file
28
node_modules/karma/node_modules/color-name/package.json
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "color-name",
|
||||
"version": "1.1.4",
|
||||
"description": "A list of color names and its values",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:colorjs/color-name.git"
|
||||
},
|
||||
"keywords": [
|
||||
"color-name",
|
||||
"color",
|
||||
"color-keyword",
|
||||
"keyword"
|
||||
],
|
||||
"author": "DY <dfcreative@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/colorjs/color-name/issues"
|
||||
},
|
||||
"homepage": "https://github.com/colorjs/color-name"
|
||||
}
|
21
node_modules/karma/node_modules/glob/LICENSE
generated
vendored
Normal file
21
node_modules/karma/node_modules/glob/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
## Glob Logo
|
||||
|
||||
Glob's logo created by Tanya Brassie <http://tanyabrassie.com/>, licensed
|
||||
under a Creative Commons Attribution-ShareAlike 4.0 International License
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
378
node_modules/karma/node_modules/glob/README.md
generated
vendored
Normal file
378
node_modules/karma/node_modules/glob/README.md
generated
vendored
Normal file
|
@ -0,0 +1,378 @@
|
|||
# Glob
|
||||
|
||||
Match files using the patterns the shell uses, like stars and stuff.
|
||||
|
||||
[](https://travis-ci.org/isaacs/node-glob/) [](https://ci.appveyor.com/project/isaacs/node-glob) [](https://coveralls.io/github/isaacs/node-glob?branch=master)
|
||||
|
||||
This is a glob implementation in JavaScript. It uses the `minimatch`
|
||||
library to do its matching.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Install with npm
|
||||
|
||||
```
|
||||
npm i glob
|
||||
```
|
||||
|
||||
```javascript
|
||||
var glob = require("glob")
|
||||
|
||||
// options is optional
|
||||
glob("**/*.js", options, function (er, files) {
|
||||
// files is an array of filenames.
|
||||
// If the `nonull` option is set, and nothing
|
||||
// was found, then files is ["**/*.js"]
|
||||
// er is an error object or null.
|
||||
})
|
||||
```
|
||||
|
||||
## Glob Primer
|
||||
|
||||
"Globs" are the patterns you type when you do stuff like `ls *.js` on
|
||||
the command line, or put `build/*` in a `.gitignore` file.
|
||||
|
||||
Before parsing the path part patterns, braced sections are expanded
|
||||
into a set. Braced sections start with `{` and end with `}`, with any
|
||||
number of comma-delimited sections within. Braced sections may contain
|
||||
slash characters, so `a{/b/c,bcd}` would expand into `a/b/c` and `abcd`.
|
||||
|
||||
The following characters have special magic meaning when used in a
|
||||
path portion:
|
||||
|
||||
* `*` Matches 0 or more characters in a single path portion
|
||||
* `?` Matches 1 character
|
||||
* `[...]` Matches a range of characters, similar to a RegExp range.
|
||||
If the first character of the range is `!` or `^` then it matches
|
||||
any character not in the range.
|
||||
* `!(pattern|pattern|pattern)` Matches anything that does not match
|
||||
any of the patterns provided.
|
||||
* `?(pattern|pattern|pattern)` Matches zero or one occurrence of the
|
||||
patterns provided.
|
||||
* `+(pattern|pattern|pattern)` Matches one or more occurrences of the
|
||||
patterns provided.
|
||||
* `*(a|b|c)` Matches zero or more occurrences of the patterns provided
|
||||
* `@(pattern|pat*|pat?erN)` Matches exactly one of the patterns
|
||||
provided
|
||||
* `**` If a "globstar" is alone in a path portion, then it matches
|
||||
zero or more directories and subdirectories searching for matches.
|
||||
It does not crawl symlinked directories.
|
||||
|
||||
### Dots
|
||||
|
||||
If a file or directory path portion has a `.` as the first character,
|
||||
then it will not match any glob pattern unless that pattern's
|
||||
corresponding path part also has a `.` as its first character.
|
||||
|
||||
For example, the pattern `a/.*/c` would match the file at `a/.b/c`.
|
||||
However the pattern `a/*/c` would not, because `*` does not start with
|
||||
a dot character.
|
||||
|
||||
You can make glob treat dots as normal characters by setting
|
||||
`dot:true` in the options.
|
||||
|
||||
### Basename Matching
|
||||
|
||||
If you set `matchBase:true` in the options, and the pattern has no
|
||||
slashes in it, then it will seek for any file anywhere in the tree
|
||||
with a matching basename. For example, `*.js` would match
|
||||
`test/simple/basic.js`.
|
||||
|
||||
### Empty Sets
|
||||
|
||||
If no matching files are found, then an empty array is returned. This
|
||||
differs from the shell, where the pattern itself is returned. For
|
||||
example:
|
||||
|
||||
$ echo a*s*d*f
|
||||
a*s*d*f
|
||||
|
||||
To get the bash-style behavior, set the `nonull:true` in the options.
|
||||
|
||||
### See Also:
|
||||
|
||||
* `man sh`
|
||||
* `man bash` (Search for "Pattern Matching")
|
||||
* `man 3 fnmatch`
|
||||
* `man 5 gitignore`
|
||||
* [minimatch documentation](https://github.com/isaacs/minimatch)
|
||||
|
||||
## glob.hasMagic(pattern, [options])
|
||||
|
||||
Returns `true` if there are any special characters in the pattern, and
|
||||
`false` otherwise.
|
||||
|
||||
Note that the options affect the results. If `noext:true` is set in
|
||||
the options object, then `+(a|b)` will not be considered a magic
|
||||
pattern. If the pattern has a brace expansion, like `a/{b/c,x/y}`
|
||||
then that is considered magical, unless `nobrace:true` is set in the
|
||||
options.
|
||||
|
||||
## glob(pattern, [options], cb)
|
||||
|
||||
* `pattern` `{String}` Pattern to be matched
|
||||
* `options` `{Object}`
|
||||
* `cb` `{Function}`
|
||||
* `err` `{Error | null}`
|
||||
* `matches` `{Array<String>}` filenames found matching the pattern
|
||||
|
||||
Perform an asynchronous glob search.
|
||||
|
||||
## glob.sync(pattern, [options])
|
||||
|
||||
* `pattern` `{String}` Pattern to be matched
|
||||
* `options` `{Object}`
|
||||
* return: `{Array<String>}` filenames found matching the pattern
|
||||
|
||||
Perform a synchronous glob search.
|
||||
|
||||
## Class: glob.Glob
|
||||
|
||||
Create a Glob object by instantiating the `glob.Glob` class.
|
||||
|
||||
```javascript
|
||||
var Glob = require("glob").Glob
|
||||
var mg = new Glob(pattern, options, cb)
|
||||
```
|
||||
|
||||
It's an EventEmitter, and starts walking the filesystem to find matches
|
||||
immediately.
|
||||
|
||||
### new glob.Glob(pattern, [options], [cb])
|
||||
|
||||
* `pattern` `{String}` pattern to search for
|
||||
* `options` `{Object}`
|
||||
* `cb` `{Function}` Called when an error occurs, or matches are found
|
||||
* `err` `{Error | null}`
|
||||
* `matches` `{Array<String>}` filenames found matching the pattern
|
||||
|
||||
Note that if the `sync` flag is set in the options, then matches will
|
||||
be immediately available on the `g.found` member.
|
||||
|
||||
### Properties
|
||||
|
||||
* `minimatch` The minimatch object that the glob uses.
|
||||
* `options` The options object passed in.
|
||||
* `aborted` Boolean which is set to true when calling `abort()`. There
|
||||
is no way at this time to continue a glob search after aborting, but
|
||||
you can re-use the statCache to avoid having to duplicate syscalls.
|
||||
* `cache` Convenience object. Each field has the following possible
|
||||
values:
|
||||
* `false` - Path does not exist
|
||||
* `true` - Path exists
|
||||
* `'FILE'` - Path exists, and is not a directory
|
||||
* `'DIR'` - Path exists, and is a directory
|
||||
* `[file, entries, ...]` - Path exists, is a directory, and the
|
||||
array value is the results of `fs.readdir`
|
||||
* `statCache` Cache of `fs.stat` results, to prevent statting the same
|
||||
path multiple times.
|
||||
* `symlinks` A record of which paths are symbolic links, which is
|
||||
relevant in resolving `**` patterns.
|
||||
* `realpathCache` An optional object which is passed to `fs.realpath`
|
||||
to minimize unnecessary syscalls. It is stored on the instantiated
|
||||
Glob object, and may be re-used.
|
||||
|
||||
### Events
|
||||
|
||||
* `end` When the matching is finished, this is emitted with all the
|
||||
matches found. If the `nonull` option is set, and no match was found,
|
||||
then the `matches` list contains the original pattern. The matches
|
||||
are sorted, unless the `nosort` flag is set.
|
||||
* `match` Every time a match is found, this is emitted with the specific
|
||||
thing that matched. It is not deduplicated or resolved to a realpath.
|
||||
* `error` Emitted when an unexpected error is encountered, or whenever
|
||||
any fs error occurs if `options.strict` is set.
|
||||
* `abort` When `abort()` is called, this event is raised.
|
||||
|
||||
### Methods
|
||||
|
||||
* `pause` Temporarily stop the search
|
||||
* `resume` Resume the search
|
||||
* `abort` Stop the search forever
|
||||
|
||||
### Options
|
||||
|
||||
All the options that can be passed to Minimatch can also be passed to
|
||||
Glob to change pattern matching behavior. Also, some have been added,
|
||||
or have glob-specific ramifications.
|
||||
|
||||
All options are false by default, unless otherwise noted.
|
||||
|
||||
All options are added to the Glob object, as well.
|
||||
|
||||
If you are running many `glob` operations, you can pass a Glob object
|
||||
as the `options` argument to a subsequent operation to shortcut some
|
||||
`stat` and `readdir` calls. At the very least, you may pass in shared
|
||||
`symlinks`, `statCache`, `realpathCache`, and `cache` options, so that
|
||||
parallel glob operations will be sped up by sharing information about
|
||||
the filesystem.
|
||||
|
||||
* `cwd` The current working directory in which to search. Defaults
|
||||
to `process.cwd()`.
|
||||
* `root` The place where patterns starting with `/` will be mounted
|
||||
onto. Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix
|
||||
systems, and `C:\` or some such on Windows.)
|
||||
* `dot` Include `.dot` files in normal matches and `globstar` matches.
|
||||
Note that an explicit dot in a portion of the pattern will always
|
||||
match dot files.
|
||||
* `nomount` By default, a pattern starting with a forward-slash will be
|
||||
"mounted" onto the root setting, so that a valid filesystem path is
|
||||
returned. Set this flag to disable that behavior.
|
||||
* `mark` Add a `/` character to directory matches. Note that this
|
||||
requires additional stat calls.
|
||||
* `nosort` Don't sort the results.
|
||||
* `stat` Set to true to stat *all* results. This reduces performance
|
||||
somewhat, and is completely unnecessary, unless `readdir` is presumed
|
||||
to be an untrustworthy indicator of file existence.
|
||||
* `silent` When an unusual error is encountered when attempting to
|
||||
read a directory, a warning will be printed to stderr. Set the
|
||||
`silent` option to true to suppress these warnings.
|
||||
* `strict` When an unusual error is encountered when attempting to
|
||||
read a directory, the process will just continue on in search of
|
||||
other matches. Set the `strict` option to raise an error in these
|
||||
cases.
|
||||
* `cache` See `cache` property above. Pass in a previously generated
|
||||
cache object to save some fs calls.
|
||||
* `statCache` A cache of results of filesystem information, to prevent
|
||||
unnecessary stat calls. While it should not normally be necessary
|
||||
to set this, you may pass the statCache from one glob() call to the
|
||||
options object of another, if you know that the filesystem will not
|
||||
change between calls. (See "Race Conditions" below.)
|
||||
* `symlinks` A cache of known symbolic links. You may pass in a
|
||||
previously generated `symlinks` object to save `lstat` calls when
|
||||
resolving `**` matches.
|
||||
* `sync` DEPRECATED: use `glob.sync(pattern, opts)` instead.
|
||||
* `nounique` In some cases, brace-expanded patterns can result in the
|
||||
same file showing up multiple times in the result set. By default,
|
||||
this implementation prevents duplicates in the result set. Set this
|
||||
flag to disable that behavior.
|
||||
* `nonull` Set to never return an empty set, instead returning a set
|
||||
containing the pattern itself. This is the default in glob(3).
|
||||
* `debug` Set to enable debug logging in minimatch and glob.
|
||||
* `nobrace` Do not expand `{a,b}` and `{1..3}` brace sets.
|
||||
* `noglobstar` Do not match `**` against multiple filenames. (Ie,
|
||||
treat it as a normal `*` instead.)
|
||||
* `noext` Do not match `+(a|b)` "extglob" patterns.
|
||||
* `nocase` Perform a case-insensitive match. Note: on
|
||||
case-insensitive filesystems, non-magic patterns will match by
|
||||
default, since `stat` and `readdir` will not raise errors.
|
||||
* `matchBase` Perform a basename-only match if the pattern does not
|
||||
contain any slash characters. That is, `*.js` would be treated as
|
||||
equivalent to `**/*.js`, matching all js files in all directories.
|
||||
* `nodir` Do not match directories, only files. (Note: to match
|
||||
*only* directories, simply put a `/` at the end of the pattern.)
|
||||
* `ignore` Add a pattern or an array of glob patterns to exclude matches.
|
||||
Note: `ignore` patterns are *always* in `dot:true` mode, regardless
|
||||
of any other settings.
|
||||
* `follow` Follow symlinked directories when expanding `**` patterns.
|
||||
Note that this can result in a lot of duplicate references in the
|
||||
presence of cyclic links.
|
||||
* `realpath` Set to true to call `fs.realpath` on all of the results.
|
||||
In the case of a symlink that cannot be resolved, the full absolute
|
||||
path to the matched entry is returned (though it will usually be a
|
||||
broken symlink)
|
||||
* `absolute` Set to true to always receive absolute paths for matched
|
||||
files. Unlike `realpath`, this also affects the values returned in
|
||||
the `match` event.
|
||||
* `fs` File-system object with Node's `fs` API. By default, the built-in
|
||||
`fs` module will be used. Set to a volume provided by a library like
|
||||
`memfs` to avoid using the "real" file-system.
|
||||
|
||||
## Comparisons to other fnmatch/glob implementations
|
||||
|
||||
While strict compliance with the existing standards is a worthwhile
|
||||
goal, some discrepancies exist between node-glob and other
|
||||
implementations, and are intentional.
|
||||
|
||||
The double-star character `**` is supported by default, unless the
|
||||
`noglobstar` flag is set. This is supported in the manner of bsdglob
|
||||
and bash 4.3, where `**` only has special significance if it is the only
|
||||
thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but
|
||||
`a/**b` will not.
|
||||
|
||||
Note that symlinked directories are not crawled as part of a `**`,
|
||||
though their contents may match against subsequent portions of the
|
||||
pattern. This prevents infinite loops and duplicates and the like.
|
||||
|
||||
If an escaped pattern has no matches, and the `nonull` flag is set,
|
||||
then glob returns the pattern as-provided, rather than
|
||||
interpreting the character escapes. For example,
|
||||
`glob.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
|
||||
`"*a?"`. This is akin to setting the `nullglob` option in bash, except
|
||||
that it does not resolve escaped pattern characters.
|
||||
|
||||
If brace expansion is not disabled, then it is performed before any
|
||||
other interpretation of the glob pattern. Thus, a pattern like
|
||||
`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
|
||||
**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
|
||||
checked for validity. Since those two are valid, matching proceeds.
|
||||
|
||||
### Comments and Negation
|
||||
|
||||
Previously, this module let you mark a pattern as a "comment" if it
|
||||
started with a `#` character, or a "negated" pattern if it started
|
||||
with a `!` character.
|
||||
|
||||
These options were deprecated in version 5, and removed in version 6.
|
||||
|
||||
To specify things that should not match, use the `ignore` option.
|
||||
|
||||
## Windows
|
||||
|
||||
**Please only use forward-slashes in glob expressions.**
|
||||
|
||||
Though windows uses either `/` or `\` as its path separator, only `/`
|
||||
characters are used by this glob implementation. You must use
|
||||
forward-slashes **only** in glob expressions. Back-slashes will always
|
||||
be interpreted as escape characters, not path separators.
|
||||
|
||||
Results from absolute patterns such as `/foo/*` are mounted onto the
|
||||
root setting using `path.join`. On windows, this will by default result
|
||||
in `/foo/*` matching `C:\foo\bar.txt`.
|
||||
|
||||
## Race Conditions
|
||||
|
||||
Glob searching, by its very nature, is susceptible to race conditions,
|
||||
since it relies on directory walking and such.
|
||||
|
||||
As a result, it is possible that a file that exists when glob looks for
|
||||
it may have been deleted or modified by the time it returns the result.
|
||||
|
||||
As part of its internal implementation, this program caches all stat
|
||||
and readdir calls that it makes, in order to cut down on system
|
||||
overhead. However, this also makes it even more susceptible to races,
|
||||
especially if the cache or statCache objects are reused between glob
|
||||
calls.
|
||||
|
||||
Users are thus advised not to use a glob result as a guarantee of
|
||||
filesystem state in the face of rapid changes. For the vast majority
|
||||
of operations, this is never a problem.
|
||||
|
||||
## Glob Logo
|
||||
Glob's logo was created by [Tanya Brassie](http://tanyabrassie.com/). Logo files can be found [here](https://github.com/isaacs/node-glob/tree/master/logo).
|
||||
|
||||
The logo is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Any change to behavior (including bugfixes) must come with a test.
|
||||
|
||||
Patches that fail tests or reduce performance will be rejected.
|
||||
|
||||
```
|
||||
# to run tests
|
||||
npm test
|
||||
|
||||
# to re-generate test fixtures
|
||||
npm run test-regen
|
||||
|
||||
# to benchmark against bash/zsh
|
||||
npm run bench
|
||||
|
||||
# to profile javascript
|
||||
npm run prof
|
||||
```
|
||||
|
||||

|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue