diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 82c5d89..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016-present Andy Edwards - -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. diff --git a/README.md b/README.md index 7ad73af..b8b89e4 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,101 @@ -# es2015-library-skeleton +# chai-webdriverio-async -[![CircleCI](https://circleci.com/gh/jedwards1211/es2015-library-skeleton.svg?style=svg)](https://circleci.com/gh/jedwards1211/es2015-library-skeleton) -[![Coverage Status](https://codecov.io/gh/jedwards1211/es2015-library-skeleton/branch/master/graph/badge.svg)](https://codecov.io/gh/jedwards1211/es2015-library-skeleton) +[![CircleCI](https://circleci.com/gh/jcoreio/chai-webdriverio-async.svg?style=svg)](https://circleci.com/gh/jcoreio/chai-webdriverio-async) +[![Coverage Status](https://codecov.io/gh/jcoreio/chai-webdriverio-async/branch/master/graph/badge.svg)](https://codecov.io/gh/jcoreio/chai-webdriverio-async) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) -[![npm version](https://badge.fury.io/js/es2015-library-skeleton.svg)](https://badge.fury.io/js/es2015-library-skeleton) +[![npm version](https://badge.fury.io/js/chai-webdriverio-async.svg)](https://badge.fury.io/js/chai-webdriverio-async) -This is my personal skeleton for creating an ES2015 library npm package. You are welcome to use it. +Async fork of [chai-webdriverio](https://github.com/marcodejongh/chai-webdriverio). -## Quick start +Provides async [webdriverio](https://npmjs.org/package/webdriverio) sugar for the [Chai](http://chaijs.com/) assertion library. Allows you to create expressive integration tests: -```sh -npx 0-60 clone https://github.com/jedwards1211/es2015-library-skeleton.git +```javascript +await expect('.frequency-field').to.have.text('One time') +await expect('.toggle-pane').to.not.be.displayed() ``` -## Tools used - -- babel 7 -- mocha -- chai -- istanbul -- nyc -- eslint -- flow -- prettier -- husky -- semantic-release -- renovate -- Circle CI -- Codecov.io +## What sorts of assertions can we make? + +All assertions start with a [WebdriverIO-compatible selector](http://webdriver.io/guide/usage/selectors.html), for example: + +- `expect('.list')` (CSS selector) +- `expect('a[href=http://google.com]')` (CSS Selector) +- `expect('//BODY/DIV[6]/DIV[1]')` (XPath selector) +- `expect('a*=Save')` (Text selector) + +Then, we can add our assertion to the chain. + +- `await expect(selector).to.be.there()` - Test whether [at least one] matching element exists in the DOM +- `await expect(selector).to.be.displayed()` - Test whether or not [at least one] matching element is displayed +- `await expect(selector).to.have.text('string')` - Test the text value of the selected element(s) against supplied string. Succeeds if at least one element matches exactly +- `await expect(selector).to.have.text(/regex/)` - Test the text value of the selected element(s) against the supplied regular expression. Succeeds if at least one element matches +- `await expect(selector).to.have.count(number)` - Test how many elements exist in the DOM with the supplied selector +- `await expect(selector).to.have.value('x')` - Test that [at least one] selected element has the given value +- `await expect(selector).to.have.focus()` - Test that [at least one] selected element has focus + +You can also always add a `not` in there to negate the assertion: + +- `await expect(selector).not.to.have.text('property')` + +## Setup + +Setup is pretty easy. Just: + +```javascript +var chai = require('chai') +var chaiWebdriver = require('chai-webdriverio').default +chai.use(chaiWebdriver(browser)) + +// And you're good to go! +await browser.url('http://github.com') +await chai + .expect('#site-container h1.heading') + .to.not.contain.text("I'm a kitty!") +``` + +## Default Wait Time + +As an optional argument to the initializer, you can add an `options` object in this format: + +```javascript +var options = { defaultWait: 500 } // 500ms +chai.use(chaiWebdriver(browser, options)) +``` + +The `defaultWait` parameter will cause chai-webdriverio to wait the specified number of milliseconds +for a given selector to appear before failing (if it is not yet present on the page). You can use `immediately` +to skip this default wait time: + +```javascript +await expect(selector).to.immediately.have.text('string') // fails immediately if element is not found +``` + +**Beware:** For `immediately` to work, your [implicit wait time in WebdriverIO](http://webdriver.io/guide/testrunner/timeouts.html#Session-Implicit-Wait-Timeout) +must be set to 0. The immediately flag has no way to skip WebdriverIO's implicit wait. + +## Compatability + +### WebdriverIO + +Only intended to be compatible with Webdriver 5 right now. +| WebdriverIO version | Compatible `chai-webdriverio-async` version | +| ---- | ---- | +| 5.x.x | >= 1.0.0 +| 4.x.x | 0.4.3 + +### Node.js + +Requires Node.js 8.x + +**Contributors:** + +- [@mltsy](https://github.com/mltsy) : `exist`, `text` assertions, documentation & test adjustments + +## License + +Apache 2.0 + +## Thanks + +Thanks to [goodeggs](https://github.com/goodeggs/) for creating: [chai-webdriver](https://github.com/goodeggs/chai-webdriver) which inspired this module. diff --git a/package.json b/package.json index 8d6efa9..2448344 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "es2015-library-skeleton", + "name": "chai-webdriverio-async", "version": "0.0.0-development", - "description": "my personal ES2015 library project skeleton", + "description": "chai assertions for webdriverio without node-fibers", "main": "index.js", "sideEffects": false, "scripts": { @@ -64,17 +64,19 @@ }, "repository": { "type": "git", - "url": "https://github.com/jedwards1211/es2015-library-skeleton.git" + "url": "https://github.com/jcoreio/chai-webdriverio-async.git" }, "keywords": [ - "es2015" + "chai", + "webdriverio", + "assertions" ], "author": "Andy Edwards", - "license": "MIT", + "license": "Apache-2.0", "bugs": { - "url": "https://github.com/jedwards1211/es2015-library-skeleton/issues" + "url": "https://github.com/jcoreio/chai-webdriverio-async/issues" }, - "homepage": "https://github.com/jedwards1211/es2015-library-skeleton#readme", + "homepage": "https://github.com/jcoreio/chai-webdriverio-async#readme", "devDependencies": { "@babel/cli": "^7.1.5", "@babel/core": "^7.1.6", @@ -95,6 +97,7 @@ "babel-eslint": "^10.0.1", "babel-plugin-istanbul": "^5.1.0", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "codecov": "^3.1.0", "copy": "^0.3.2", "cross-env": "^5.2.0", @@ -112,8 +115,12 @@ "nyc": "^13.1.0", "prettier": "^1.15.2", "prettier-eslint": "^8.8.2", + "proxyquire": "^2.1.3", "rimraf": "^2.6.0", - "semantic-release": "^15.1.4" + "semantic-release": "^15.1.4", + "sinon": "^8.1.1", + "sinon-chai": "^3.4.0", + "webdriverio": "^5.18.6" }, "dependencies": { "@babel/runtime": "^7.1.5" diff --git a/src/assertions/count.js b/src/assertions/count.js new file mode 100644 index 0000000..5b264a0 --- /dev/null +++ b/src/assertions/count.js @@ -0,0 +1,74 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' + +async function hasCount(client, selector, count, countStore) { + const elements = await client.$$(selector) + countStore.count = elements.length + return elements.length === count +} + +async function waitUntilCount( + client, + selector, + count, + defaultWait = 0, + reverse +) { + const countStore = {} + + if (!reverse) { + try { + await client.waitUntil( + () => hasCount(client, selector, count, countStore), + defaultWait + ) + } catch (error) { + throw new Error( + `Element with selector <${selector}> does not appear in the DOM ${count} times ` + + `within ${defaultWait} ms, but it shows up ${ + countStore.count + } times instead.` + ) + } + } else { + await client.waitUntil( + async () => !(await hasCount(client, selector, count, countStore)), + defaultWait, + `Element with selector <${selector}> still appears in the DOM ${count} times after ${defaultWait} ms` + ) + } +} + +export default function count(client, chai, utils, options) { + const config = configWithDefaults(options) + chai.Assertion.addMethod('count', async function(expected) { + const selector = utils.flag(this, 'object') + const negate = utils.flag(this, 'negate') + const immediately = utils.flag(this, 'immediately') + + if (!immediately) { + await waitUntilCount( + client, + selector, + expected, + config.defaultWait, + negate + ) + } + + const countStore = {} + + this.assert( + await hasCount(client, selector, expected, countStore), + `Expected <${selector}> to appear in the DOM ${expected} times, but it shows up ${ + countStore.count + } times instead.`, + `Expected <${selector}> not to appear in the DOM ${expected} times, but it does.` + ) + }) +} diff --git a/src/assertions/displayed.js b/src/assertions/displayed.js new file mode 100644 index 0000000..1464c95 --- /dev/null +++ b/src/assertions/displayed.js @@ -0,0 +1,37 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' +import doesOneElementSatisfy from '../util/doesOneElementSatisfy' + +const isOneElementDisplayed = doesOneElementSatisfy(el => el.isDisplayed()) + +export default function displayed(client, chai, utils, options) { + const config = configWithDefaults(options) + + chai.Assertion.addMethod('displayed', async function() { + const negate = utils.flag(this, 'negate') + const selector = utils.flag(this, 'object') + const immediately = utils.flag(this, 'immediately') + + const errorMsg = `Expected <${selector}> to be displayed but it is not` + const errorMsgNegate = `Expected <${selector}> to not be displayed but it is` + + if (!immediately) { + await client.waitUntil( + async () => (await isOneElementDisplayed(client, selector)) === !negate, + config.defaultWait, + negate ? errorMsgNegate : errorMsg + ) + } + + this.assert( + await isOneElementDisplayed(client, selector), + errorMsg, + errorMsgNegate + ) + }) +} diff --git a/src/assertions/enabled.js b/src/assertions/enabled.js new file mode 100644 index 0000000..8abe883 --- /dev/null +++ b/src/assertions/enabled.js @@ -0,0 +1,37 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' +import doesOneElementSatisfy from '../util/doesOneElementSatisfy' + +const isOneElementEnabled = doesOneElementSatisfy(el => el.isEnabled()) + +export default function enabled(client, chai, utils, options) { + const config = configWithDefaults(options) + + chai.Assertion.addMethod('enabled', async function() { + const negate = utils.flag(this, 'negate') + const selector = utils.flag(this, 'object') + const immediately = utils.flag(this, 'immediately') + + const errorMsg = `Expected <${selector}> to be enabled but it is not` + const errorMsgNegate = `Expected <${selector}> to not be enabled but it is` + + if (!immediately) { + await client.waitUntil( + async () => (await isOneElementEnabled(client, selector)) === !negate, + config.defaultWait, + negate ? errorMsgNegate : errorMsg + ) + } + + this.assert( + await isOneElementEnabled(client, selector), + errorMsg, + errorMsgNegate + ) + }) +} diff --git a/src/assertions/focus.js b/src/assertions/focus.js new file mode 100644 index 0000000..6f03b97 --- /dev/null +++ b/src/assertions/focus.js @@ -0,0 +1,37 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' +import doesOneElementSatisfy from '../util/doesOneElementSatisfy' + +const isOneElementFocused = doesOneElementSatisfy(el => el.isFocused()) + +export default function focus(client, chai, utils, options) { + const config = configWithDefaults(options) + + chai.Assertion.addMethod('focus', async function() { + const negate = utils.flag(this, 'negate') + const selector = utils.flag(this, 'object') + const immediately = utils.flag(this, 'immediately') + + const errorMsg = `Expected <${selector}> to be focused but it is not` + const errorMsgNegate = `Expected <${selector}> to not be focused but it is` + + if (!immediately) { + await client.waitUntil( + async () => (await isOneElementFocused(client, selector)) === !negate, + config.defaultWait, + negate ? errorMsgNegate : errorMsg + ) + } + + this.assert( + await isOneElementFocused(client, selector), + errorMsg, + errorMsgNegate + ) + }) +} diff --git a/src/assertions/text.js b/src/assertions/text.js new file mode 100644 index 0000000..de8db2b --- /dev/null +++ b/src/assertions/text.js @@ -0,0 +1,63 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' + +const doesOneElementContainText = async function(client, selector, expected) { + let elements = await client.$$(selector) + let texts = [] + const filteredList = (await Promise.all( + elements.map(async element => { + const text = await element.getText() + texts.push(text) + return expected instanceof RegExp + ? text.match(expected) + : text === expected + }) + )).filter(Boolean) + + return { + result: filteredList.length > 0, + texts, + } +} + +export default function text(client, chai, utils, options) { + const config = configWithDefaults(options) + + chai.Assertion.addMethod('text', async function(expected) { + const selector = utils.flag(this, 'object') + const immediately = utils.flag(this, 'immediately') + + if (!immediately) { + try { + await client.waitUntil( + async () => + (await doesOneElementContainText(client, selector, expected)) + .result, + config.defaultWait + ) + } catch (e) { + // actual assertion is handled below + } + } + + const elementContainsText = await doesOneElementContainText( + client, + selector, + expected + ) + this.assert( + elementContainsText.result, + `Expected element <${selector}> to contain text "${expected}", but only found: ${ + elementContainsText.texts + }`, + `Expected element <${selector}> not to contain text "${expected}", but found: ${ + elementContainsText.texts + }` + ) + }) +} diff --git a/src/assertions/there.js b/src/assertions/there.js new file mode 100644 index 0000000..e8856cb --- /dev/null +++ b/src/assertions/there.js @@ -0,0 +1,32 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import elementExists from '../util/element-exists' +import configWithDefaults from '../util/default-config' + +export default function there(client, chai, utils, options) { + const config = configWithDefaults(options) + + chai.Assertion.addMethod('there', async function() { + const selector = utils.flag(this, 'object') + const negate = utils.flag(this, 'negate') + const immediately = utils.flag(this, 'immediately') + + var isThere = !negate + const defaultWait = immediately ? 0 : config.defaultWait + try { + await elementExists(client, selector, defaultWait, negate) + } catch (error) { + isThere = negate + } + + this.assert( + isThere, + `Expected <${selector}> to be there, but it is not there.`, + `Expected <${selector}> not to be there, and yet, there it is.` + ) + }) +} diff --git a/src/assertions/value.js b/src/assertions/value.js new file mode 100644 index 0000000..8237c8c --- /dev/null +++ b/src/assertions/value.js @@ -0,0 +1,61 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import configWithDefaults from '../util/default-config' + +const doesOneElementHaveValue = async function(client, selector, expected) { + const elements = await client.$$(selector) + const values = [] + const filteredList = (await Promise.all( + elements.map(async element => { + const value = await element.getValue() + values.push(value) + return expected instanceof RegExp + ? value.match(expected) + : value === expected + }) + )).filter(Boolean) + + return { + result: filteredList.length > 0, + values, + } +} + +export default function value(client, chai, utils, options) { + const config = configWithDefaults(options) + chai.Assertion.addMethod('value', async function(expected) { + const selector = utils.flag(this, 'object') + const immediately = utils.flag(this, 'immediately') + + if (!immediately) { + try { + client.waitUntil( + async () => + (await doesOneElementHaveValue(client, selector, expected)).result, + config.defaultWait + ) + } catch (e) { + // actual assertion is handled below + } + } + + const elementContainsValue = await doesOneElementHaveValue( + client, + selector, + expected + ) + this.assert( + elementContainsValue.result, + `Expected an element matching <${selector}> to contain value "${expected}", but only found these values: ${ + elementContainsValue.values + }`, + `Expected an element matching <${selector}> not to contain value "${expected}", but found these values: ${ + elementContainsValue.values + }` + ) + }) +} diff --git a/src/chains/immediately.js b/src/chains/immediately.js new file mode 100644 index 0000000..2a29b8b --- /dev/null +++ b/src/chains/immediately.js @@ -0,0 +1,5 @@ +export default function immediately(client, chai, utils) { + chai.Assertion.addChainableMethod('immediately', function() { + utils.flag(this, 'immediately', true) + }) +} diff --git a/src/index.js b/src/index.js index 194c71f..58cbb77 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,27 @@ -/* @flow */ +import there from './assertions/there' +import displayed from './assertions/displayed' +import count from './assertions/count' +import text from './assertions/text' +import value from './assertions/value' +import focus from './assertions/focus' +import enabled from './assertions/enabled' +import immediately from './chains/immediately' -/* eslint-disable no-console, no-undef */ -console.log('Hello world!') +export default function(client, options = {}) { + return function chaiWebdriverIO(chai, utils) { + let methodsToAdd = [ + there, + displayed, + count, + text, + immediately, + value, + focus, + enabled, + ] + + methodsToAdd.forEach(function(methodToAdd) { + methodToAdd(client, chai, utils, options) + }) + } +} diff --git a/src/util/default-config.js b/src/util/default-config.js new file mode 100644 index 0000000..7e33c29 --- /dev/null +++ b/src/util/default-config.js @@ -0,0 +1,4 @@ +export default function configWithDefaults(config) { + var defaultConfig = { defaultWait: 0 } + return Object.assign({}, defaultConfig, config) +} diff --git a/src/util/doesOneElementSatisfy.js b/src/util/doesOneElementSatisfy.js new file mode 100644 index 0000000..b4c4977 --- /dev/null +++ b/src/util/doesOneElementSatisfy.js @@ -0,0 +1,9 @@ +const doesOneElementSatisfy = predicate => async (client, selector) => { + const elements = await client.$$(selector) + const filteredList = (await Promise.all(elements.map(predicate))).filter( + Boolean + ) + return filteredList.length > 0 +} + +export default doesOneElementSatisfy diff --git a/src/util/element-exists.js b/src/util/element-exists.js new file mode 100644 index 0000000..11cd0ff --- /dev/null +++ b/src/util/element-exists.js @@ -0,0 +1,27 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +export default async function assertElementExists( + client, + selector, + defaultWait = 0, + reverse +) { + try { + const el = await client.$(selector) + await el.waitForExist(defaultWait, reverse) + } catch (error) { + if (reverse) { + throw new Error( + `Element with selector <${selector}> still exists after ${defaultWait}ms (while waiting for it not to).` + ) + } else { + throw new Error( + `Could not find element with selector <${selector}> within ${defaultWait}ms` + ) + } + } +} diff --git a/test/assertions/count-test.js b/test/assertions/count-test.js new file mode 100644 index 0000000..f6b5d08 --- /dev/null +++ b/test/assertions/count-test.js @@ -0,0 +1,155 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import count from '../../src/assertions/count' +import immediately from '../../src/chains/immediately' + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use(sinonChai) + +const doesNotHaveCountError = (count, actualCount, defaultWait = 0) => { + return ( + 'Element with selector <.some-selector> does not ' + + `appear in the DOM ${count} times within ${defaultWait} ms, ` + + `but it shows up ${actualCount} times instead.` + ) +} + +const hasCountError = (count, defaultWait = 0) => { + return ( + 'Element with selector <.some-selector> still ' + + `appears in the DOM ${count} times after ${defaultWait} ms` + ) +} + +describe('count', () => { + let elements + let fakeClient + + beforeEach(() => { + elements = [new FakeElement(), new FakeElement()] + fakeClient = new FakeClient() + + fakeClient.$$.rejects('ArgumentError') + fakeClient.$$.withArgs('.some-selector').resolves(elements) + + fakeClient.waitUntil.callsFake(async (condition, timeout, timeoutMsg) => { + if (await condition()) return + + throw new Error(timeoutMsg) + }) + + chai.use((chai, utils) => count(fakeClient, chai, utils)) + chai.use((chai, utils) => immediately(fakeClient, chai, utils)) + }) + + afterEach(() => fakeClient.__resetStubs__()) + + describe('When not negated', () => { + beforeEach(async () => { + await expect('.some-selector').to.have.count(elements.length) + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.calledWithMatch( + callback => callback(), + 0 + ) + }) + + describe('When the element still does not appear the expected times after the wait time', () => { + it('Should throw an exception', async () => { + await expect( + expect('.some-selector').to.have.count(elements.length + 1) + ).to.be.rejectedWith( + doesNotHaveCountError(elements.length + 1, elements.length) + ) + }) + }) + }) + + describe('When negated', () => { + beforeEach(async () => { + await expect('.some-selector').to.not.have.count(elements.length + 1) + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.calledWithMatch( + callback => callback(), + 0, + hasCountError(elements.length + 1) + ) + }) + + describe('When the element still appears the expected times after the wait time', () => { + it('Should throw an exception', async () => { + await expect( + expect('.some-selector').to.not.have.count(elements.length) + ).to.be.rejectedWith(hasCountError(elements.length)) + }) + }) + }) + + describe('When the element count matches the expectation', () => { + it('Should not throw an exception', async () => { + await expect('.some-selector').to.have.count(elements.length) + }) + + describe('When given a default wait time', () => { + beforeEach(async () => { + chai.use((chai, utils) => + count(fakeClient, chai, utils, { defaultWait: 100 }) + ) + + await expect('.some-selector').to.have.count(elements.length) + }) + + it('Should call `waitUntil` with the specified wait time', () => { + expect(fakeClient.waitUntil).to.have.been.calledWithMatch( + callback => callback(), + 100 + ) + }) + }) + + describe('When the call is chained with `immediately`', () => { + beforeEach(async () => { + await expect('.some-selector') + .to.have.immediately() + .count(elements.length) + }) + + it('Should not wait for the element to match the expected count', () => { + expect(fakeClient.waitUntil).to.not.have.been.called + }) + }) + + describe('When the assertion is negated', () => { + it('Should throw an exception', async () => { + await expect( + expect('.some-selector').to.not.have.count(elements.length) + ).to.be.rejected + }) + }) + }) + + describe('When the element count does not match the expectation', () => { + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.have.count(elements.length + 1)) + .to.be.rejected + }) + + describe('When the assertion is negated', () => { + it('Should not throw an exception', async () => { + await expect('.some-selector').to.not.have.count(elements.length + 1) + }) + }) + }) +}) diff --git a/test/assertions/displayed-test.js b/test/assertions/displayed-test.js new file mode 100644 index 0000000..8508d23 --- /dev/null +++ b/test/assertions/displayed-test.js @@ -0,0 +1,204 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import displayed from '../../src/assertions/displayed' +import immediately from '../../src/chains/immediately' +import FakeElement from '../stubs/fake-element' + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use(sinonChai) + +describe('displayed', () => { + let fakeClient + let fakeElement1 + let fakeElement2 + + beforeEach(() => { + fakeClient = new FakeClient() + fakeElement1 = new FakeElement() + fakeElement2 = new FakeElement() + + fakeElement1.isDisplayed.resolves(false) + fakeClient.$$.withArgs('.some-selector').resolves([fakeElement1]) + + chai.use((chai, utils) => displayed(fakeClient, chai, utils)) + chai.use((chai, utils) => immediately(fakeClient, chai, utils)) + }) + + afterEach(() => { + fakeClient.__resetStubs__() + fakeElement1.__resetStubs__() + }) + + describe('When not negated', () => { + beforeEach(async () => { + fakeElement1.isDisplayed.resolves(true) + + await expect('.some-selector').to.be.displayed() + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.called + }) + + describe('When the element is still not displayed after the wait time', () => { + let testError + + beforeEach(() => { + testError = 'Element still not displayed' + + fakeClient.waitUntil.rejects(new Error(testError)) + }) + + it('Should throw an exception', async () => { + await expect( + expect('.some-selector').to.be.displayed() + ).to.be.rejectedWith(testError) + }) + }) + }) + + describe('When negated', () => { + beforeEach(async () => { + await expect('.some-selector').to.not.be.displayed() + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.called + }) + + describe('When the element is still displayed after the wait time', () => { + let testError + + beforeEach(() => { + testError = 'Element still displayed' + + fakeClient.waitUntil.rejects(new Error(testError)) + }) + + it('Should throw an exception', async () => { + await expect( + expect('.some-selector').to.not.be.displayed() + ).to.be.rejectedWith(testError) + }) + }) + }) + + describe('When the element is displayed', () => { + beforeEach(() => { + fakeElement1.isDisplayed.resolves(true) + }) + + it('Should not throw an exception', async () => { + await expect('.some-selector').to.be.displayed() + }) + + describe('When given a default wait time', () => { + beforeEach(async () => { + chai.use((chai, utils) => + displayed(fakeClient, chai, utils, { defaultWait: 100 }) + ) + + await expect('.some-selector').to.be.displayed() + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.called + }) + }) + + describe('When the call is chained with `immediately`', () => { + beforeEach(async () => { + await expect('.some-selector') + .to.be.immediately() + .displayed() + }) + + it('Should not wait for the element to be displayed', () => { + expect(fakeClient.waitUntil).to.not.have.been.called + }) + }) + + describe('When the assertion is negated', () => { + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.not.be.displayed()).to.be + .rejected + }) + }) + }) + + describe('When the element is not displayed', () => { + beforeEach(() => { + fakeElement1.isDisplayed.resolves(false) + }) + + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.be.displayed()).to.be.rejected + }) + + describe('When the assertion is negated', () => { + it('Should not throw an exception', async () => { + await expect('.some-selector').to.not.be.displayed() + }) + }) + }) + + describe('When multiple matching elements exist', () => { + beforeEach(() => { + fakeClient.$$.resolves([fakeElement1, fakeElement2]) + }) + + describe('When any one is displayed', () => { + beforeEach(() => { + fakeElement1.isDisplayed.resolves(true) + fakeElement2.isDisplayed.resolves(false) + }) + + it('Should not throw an exception', async () => { + await expect('.some-selector').to.be.displayed() + }) + + describe('When the call is chained with `immediately`', () => { + beforeEach(async () => { + await expect('.some-selector') + .to.be.immediately() + .displayed() + }) + + it('Should not wait for the element to be displayed', () => { + expect(fakeElement1.waitForDisplayed).to.not.have.been.called + }) + }) + + describe('When the assertion is negated', () => { + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.not.be.displayed()).to.be + .rejected + }) + }) + }) + + describe('When none are displayed', () => { + beforeEach(() => { + fakeElement1.isDisplayed.resolves(false) + fakeElement2.isDisplayed.resolves(false) + }) + + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.be.displayed()).to.be.rejected + }) + + describe('When the assertion is negated', () => { + it('Should not throw an exception', async () => { + await expect('.some-selector').to.not.be.displayed() + }) + }) + }) + }) +}) diff --git a/test/assertions/enabled-test.js b/test/assertions/enabled-test.js new file mode 100644 index 0000000..1869009 --- /dev/null +++ b/test/assertions/enabled-test.js @@ -0,0 +1,178 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import enabled from '../../src/assertions/enabled' +import immediately from '../../src/chains/immediately' + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use(sinonChai) + +describe('enabled', () => { + let fakeClient + let fakeElement1 + let fakeElement2 + + beforeEach(() => { + fakeClient = new FakeClient() + fakeElement1 = new FakeElement() + fakeElement2 = new FakeElement() + + fakeElement1.isEnabled.resolves(false) + fakeClient.$$.withArgs('.some-selector').resolves([fakeElement1]) + + chai.use((chai, utils) => enabled(fakeClient, chai, utils)) + chai.use((chai, utils) => immediately(fakeClient, chai, utils)) + }) + + afterEach(() => { + fakeClient.__resetStubs__() + fakeElement1.__resetStubs__() + fakeElement2.__resetStubs__() + }) + + describe('When not negated', () => { + beforeEach(async () => { + fakeElement1.isEnabled.resolves(true) + + await expect('.some-selector').to.be.enabled() + }) + + it('Should call `isEnabled`', () => { + expect(fakeElement1.isEnabled).to.have.been.calledWith() + }) + }) + + describe('When negated', () => { + beforeEach(async () => { + await expect('.some-selector').to.not.be.enabled() + }) + + it('Should call `isEnabled`', () => { + expect(fakeElement1.isEnabled).to.have.been.calledWith() + }) + }) + + describe('When the element is enabled', () => { + beforeEach(() => { + fakeElement1.isEnabled.resolves(true) + }) + + it('Should not throw an exception', async () => { + await expect('.some-selector').to.be.enabled() + }) + + describe('When given a default wait time', () => { + beforeEach(async () => { + chai.use((chai, utils) => + enabled(fakeClient, chai, utils, { defaultWait: 100 }) + ) + + await expect('.some-selector').to.be.enabled() + }) + + it('Should call `waitUntil`', () => { + expect(fakeClient.waitUntil).to.have.been.calledWith() + }) + }) + + describe('When the call is chained with `immediately`', () => { + beforeEach(async () => { + await expect('.some-selector') + .to.be.immediately() + .enabled() + }) + + it('Should not wait for the element to be enabled', () => { + expect(fakeClient.waitUntil).to.not.have.been.called + }) + }) + + describe('When the assertion is negated', () => { + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.not.be.enabled()).to.be + .rejected + }) + }) + }) + + describe('When the element is not enabled', () => { + beforeEach(() => { + fakeElement1.isEnabled.resolves(false) + }) + + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.be.enabled()).to.be.rejected + }) + + describe('When the assertion is negated', () => { + it('Should not throw an exception', async () => { + await expect('.some-selector').to.not.be.enabled() + }) + }) + }) + + describe('When multiple matching elements exist', () => { + describe('When any one is enabled', () => { + beforeEach(() => { + fakeElement1.isEnabled.resolves(true) + fakeElement2.isEnabled.resolves(false) + fakeClient.$$.withArgs('.multiple-selector').resolves([ + fakeElement1, + fakeElement2, + ]) + }) + + it('Should not throw an exception', async () => { + await expect('.multiple-selector').to.be.enabled() + }) + + describe('When the call is chained with `immediately`', () => { + beforeEach(async () => { + await expect('.multiple-selector') + .to.be.immediately() + .enabled() + }) + + it('Should not wait for the element to be enabled', () => { + expect(fakeClient.waitUntil).to.not.have.been.called + }) + }) + + describe('When the assertion is negated', () => { + it('Should throw an exception', async () => { + await expect(expect('.multiple-selector').to.not.be.enabled()).to.be + .rejected + }) + }) + }) + + describe('When none are enabled', () => { + beforeEach(() => { + fakeElement1.isEnabled.resolves(false) + fakeElement2.isEnabled.resolves(false) + fakeClient.$$.withArgs('.multiple-selector').resolves([ + fakeElement1, + fakeElement2, + ]) + }) + + it('Should throw an exception', async () => { + await expect(expect('.multiple-selector').to.be.enabled()).to.be + .rejected + }) + + describe('When the assertion is negated', () => { + it('Should not throw an exception', async () => { + await expect('.multiple-selector').to.not.be.enabled() + }) + }) + }) + }) +}) diff --git a/test/assertions/focus-test.js b/test/assertions/focus-test.js new file mode 100644 index 0000000..d562036 --- /dev/null +++ b/test/assertions/focus-test.js @@ -0,0 +1,60 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import immediately from '../../src/chains/immediately' +import focus from '../../src/assertions/focus' + +const fakeClient = new FakeClient() +const fakeElement = new FakeElement() + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use((chai, utils) => focus(fakeClient, chai, utils)) +chai.use((chai, utils) => immediately(fakeClient, chai, utils)) +chai.use(sinonChai) + +describe('focus', () => { + beforeEach(() => { + fakeClient.__resetStubs__() + fakeElement.__resetStubs__() + }) + + describe("When element doesn't exist", () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.have.focus()).to.be.rejected + }) + + context('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.focus()).to.be + .rejected + }) + }) + }) + + describe('When element exists', () => { + describe('When element is focused', () => { + beforeEach(() => { + fakeClient.$$.resolves([fakeElement]) + fakeElement.isFocused.resolves(true) + }) + + it('Should not throw an exception', async () => { + await expect('.some-selector').to.have.focus() + }) + + describe('When negated', () => { + it('Should throw an exception', async () => { + await expect(expect('.some-selector').to.not.have.focus()).to.be + .rejected + }) + }) + }) + }) +}) diff --git a/test/assertions/text-test.js b/test/assertions/text-test.js new file mode 100644 index 0000000..5f56854 --- /dev/null +++ b/test/assertions/text-test.js @@ -0,0 +1,206 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import text from '../../src/assertions/text' +import immediately from '../../src/chains/immediately' + +const fakeClient = new FakeClient() +const fakeElement1 = new FakeElement() +const fakeElement2 = new FakeElement() + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use((chai, utils) => text(fakeClient, chai, utils)) +chai.use((chai, utils) => immediately(fakeClient, chai, utils)) + +chai.use(sinonChai) + +describe('text', () => { + beforeEach(() => { + fakeClient.__resetStubs__() + fakeElement1.__resetStubs__() + fakeElement2.__resetStubs__() + }) + + context("when element doesn't exist", () => { + beforeEach(() => { + fakeClient.$$.withArgs('.some-selector').resolves([]) + }) + + it("Should throw element doesn't exist error for strings", async () => { + await expect(expect('.some-selector').to.have.text('blablabla')).to.be + .rejected + }) + it("Should throw element doesn't exist error for regular expressions", async () => { + await expect(expect('.some-selector').to.have.text(/blablabla/)).to.be + .rejected + }) + }) + + describe('When element exists', () => { + let elementText = 'Never gonna give you up' + + beforeEach(() => { + fakeElement1.getText.resolves(elementText) + fakeClient.$$.withArgs('.some-selector').resolves([fakeElement1]) + }) + + describe('When call is chained with Immediately', () => { + it('Should not wait till the element exists', async () => { + await expect('.some-selector') + .to.have.immediately() + .text(elementText) + expect(fakeClient.waitUntil).to.not.have.been.called + }) + it('Should not throw an exception', async () => { + await expect('.some-selector') + .to.have.immediately() + .text(elementText) + }) + describe('When negated', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector') + .to.not.have.immediately() + .text(elementText) + ).to.be.rejected + }) + }) + }) + + describe('When element text matches string expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.text(elementText) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.text(elementText)) + .to.be.rejected + }) + }) + }) + + describe('When element text matches regex expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.text(/gon+a give/) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.text(/gon+a give/)) + .to.be.rejected + }) + }) + }) + + describe('When element text does not match string expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.text("dis don't match jack! 1#43@") + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.text( + "dis don't match jack! 1#43@" + ) + }) + }) + }) + + describe('When element text does not match regex expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.text(/dis don't match jack! 1#43@/) + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.text( + /dis don't match jack! 1#43@/ + ) + }) + }) + }) + }) + + describe('When multiple elements exists', () => { + let elementTexts = ['Never gonna give you up', 'Never gonna let you down'] + beforeEach(() => { + fakeElement1.getText.resolves(elementTexts[0]) + fakeElement2.getText.resolves(elementTexts[1]) + fakeClient.$$.withArgs('.some-selector').resolves([ + fakeElement1, + fakeElement2, + ]) + }) + + describe("When at least one element's text matches string expectation", () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.text(elementTexts[0]) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.not.have.text(elementTexts[0]) + ).to.be.rejected + }) + }) + }) + + describe("When at least one element's text matches regex expectation", () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.text(/gon+a give/) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.text(/gon+a give/)) + .to.be.rejected + }) + }) + }) + + describe('When no element text matches string expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.text("dis don't match jack! 1#43@") + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.text( + "dis don't match jack! 1#43@" + ) + }) + }) + }) + + describe('When no element text matches regex expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.text(/dis don't match jack! 1#43@/) + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.text( + /dis don't match jack! 1#43@/ + ) + }) + }) + }) + }) +}) diff --git a/test/assertions/there-test.js b/test/assertions/there-test.js new file mode 100644 index 0000000..caeb756 --- /dev/null +++ b/test/assertions/there-test.js @@ -0,0 +1,80 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +const fakeClient = new FakeClient() + +const elementExists = sinon.stub() + +const there = proxyquire('../../src/assertions/there', { + '../util/element-exists': { + default: elementExists, + }, +}).default + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use((chai, utils) => there(fakeClient, chai, utils)) +chai.use(sinonChai) + +describe('there', () => { + beforeEach(() => { + fakeClient.__resetStubs__() + elementExists.reset() + // Reset doesn't reset throws :( + elementExists.resolves() + }) + + context("When element doesn't exist", () => { + it('Should throw an error', async () => { + elementExists.rejects(new Error()) + await expect(expect('.some-selector').to.be.there()).to.be.rejectedWith( + /Expected .+ to be there/ + ) + expect(elementExists).to.have.been.calledOnce + }) + + context('When negated', () => { + it('Should not throw an error', async () => { + await expect(expect('.some-selector').to.not.be.there()).to.not.be + .rejected + expect(elementExists).to.have.been.calledWith( + fakeClient, + '.some-selector', + 0, + true + ) + }) + }) + }) + + context('When element exists', () => { + beforeEach(() => elementExists.resolves(true)) + + it('Should not throw an exception', async () => { + await expect('.some-selector').to.be.there() + }) + + context('When negated', () => { + it('Should throw an exception', async () => { + elementExists.rejects(new Error()) + await expect( + expect('.some-selector').to.not.be.there() + ).to.be.rejectedWith(/Expected .+ not to be there/) + expect(elementExists).to.have.been.calledWith( + fakeClient, + '.some-selector', + 0, + true + ) + }) + }) + }) +}) diff --git a/test/assertions/value-test.js b/test/assertions/value-test.js new file mode 100644 index 0000000..1acca58 --- /dev/null +++ b/test/assertions/value-test.js @@ -0,0 +1,193 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import immediately from '../../src/chains/immediately' +import value from '../../src/assertions/value' + +const fakeClient = new FakeClient() +const fakeElement1 = new FakeElement() +const fakeElement2 = new FakeElement() + +//Using real chai, because it would be too much effort to stub/mock everything +chai.use((chai, utils) => value(fakeClient, chai, utils)) +chai.use((chai, utils) => immediately(fakeClient, chai, utils)) + +chai.use(sinonChai) + +describe('value', () => { + beforeEach(() => { + fakeClient.__resetStubs__() + fakeElement1.__resetStubs__() + fakeElement2.__resetStubs__() + + fakeClient.$$.resolves([fakeElement1]) + }) + + it("Should throw element doesn't exist error", async () => { + await expect(expect('.some-selector').to.have.value('blablabla')).to.be + .rejected + }) + + describe('When matching element exists', () => { + let testResult = 'Never gonna give you up' + beforeEach(() => { + fakeElement1.getValue.resolves(testResult) + }) + + describe('When call is chained with Immediately', () => { + it('Should not wait till the element exists', async () => { + await expect('.some-selector') + .to.have.immediately() + .value(testResult) + expect(fakeClient.waitUntil).to.not.have.been.called + }) + it('Should not throw an exception', async () => { + await expect('.some-selector') + .to.have.immediately() + .value(testResult) + }) + describe('When negated', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector') + .to.not.have.immediately() + .value(testResult) + ).to.be.rejected + }) + }) + }) + + describe('When element value matches string expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.value(testResult) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.value(testResult)) + .to.be.rejected + }) + }) + }) + + describe('When element value matches regex expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.value(/gon+a give/) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.value(/gon+a give/)) + .to.be.rejected + }) + }) + }) + + describe('When element value does not match string expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.value("dis don't match jack! 1#43@") + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.value( + "dis don't match jack! 1#43@" + ) + }) + }) + }) + + describe('When element value does not match regex expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.value(/dis don't match jack! 1#43@/) + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.value( + /dis don't match jack! 1#43@/ + ) + }) + }) + }) + }) + + describe('When multiple elements match', () => { + let testResult = ['Never gonna give you up', 'Never gonna let you down'] + beforeEach(() => { + fakeElement1.getValue.resolves(testResult[0]) + fakeElement2.getValue.resolves(testResult[1]) + fakeClient.$$.resolves([fakeElement1, fakeElement2]) + }) + + describe('When at least one element value matches string expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.value(testResult[0]) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.not.have.value(testResult[0]) + ).to.be.rejected + }) + }) + }) + + describe('When at least one element value matches regex expectation', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.have.value(/gon+a give/) + }) + + describe('When negated', () => { + it('Should throw an error', async () => { + await expect(expect('.some-selector').to.not.have.value(/gon+a give/)) + .to.be.rejected + }) + }) + }) + + describe('When no element value matches string expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.value("dis don't match jack! 1#43@") + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.value( + "dis don't match jack! 1#43@" + ) + }) + }) + }) + + describe('When no element value matches regex expectation', () => { + it('Should throw an error', async () => { + await expect( + expect('.some-selector').to.have.value(/dis don't match jack! 1#43@/) + ).to.be.rejected + }) + + describe('When negated', () => { + it('Should not throw an error', async () => { + await expect('.some-selector').to.not.have.value( + /dis don't match jack! 1#43@/ + ) + }) + }) + }) + }) +}) diff --git a/test/configure.js b/test/configure.js index 55376a4..20c2afa 100644 --- a/test/configure.js +++ b/test/configure.js @@ -1,5 +1,9 @@ /* eslint-env node */ +import chai from 'chai' +import asPromised from 'chai-as-promised' +chai.use(asPromised) + if (process.argv.indexOf('--watch') >= 0) { before(() => process.stdout.write('\u001b[2J\u001b[1;1H\u001b[3J')) } diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 3fd4478..0000000 --- a/test/index.js +++ /dev/null @@ -1,7 +0,0 @@ -// @flow - -import '../src/index' - -describe('test setup', () => { - it('works', () => {}) -}) diff --git a/test/stubs/fake-client.js b/test/stubs/fake-client.js new file mode 100644 index 0000000..678e2fc --- /dev/null +++ b/test/stubs/fake-client.js @@ -0,0 +1,20 @@ +import sinon from 'sinon' +import { getPrototype } from 'webdriverio/build/utils' + +const clientPrototype = getPrototype('browser') + +export default class FakeClient { + constructor() { + const fakeClient = {} + Object.keys(clientPrototype).forEach( + key => (fakeClient[key] = sinon.stub()) + ) + + Object.assign(this, fakeClient) + } + __resetStubs__() { + Object.keys(this) + .filter(key => key.substring(0, 1) !== '__') + .forEach(key => this[key].reset()) + } +} diff --git a/test/stubs/fake-element.js b/test/stubs/fake-element.js new file mode 100644 index 0000000..00f35b1 --- /dev/null +++ b/test/stubs/fake-element.js @@ -0,0 +1,21 @@ +import sinon from 'sinon' +import { getPrototype } from 'webdriverio/build/utils' + +const elementPrototype = getPrototype('element') + +export default class FakeElement { + constructor() { + const fakeElement = {} + Object.keys(elementPrototype).forEach( + key => (fakeElement[key] = sinon.stub()) + ) + + Object.assign(this, fakeElement) + } + + __resetStubs__() { + Object.keys(this) + .filter(key => key.substring(0, 1) !== '__') + .forEach(key => this[key].reset()) + } +} diff --git a/test/stubs/get-fake-page-element.js b/test/stubs/get-fake-page-element.js new file mode 100644 index 0000000..41e88dc --- /dev/null +++ b/test/stubs/get-fake-page-element.js @@ -0,0 +1,16 @@ +import sinon from 'sinon' + +export default function getFakePageElement() { + return { + type: 'Never gonna run around and desert you', + message: 'Never gonna make you cry', + state: 'Never gonna say goodbye', + sessionId: 'Never gonna tell a lie and hurt you', + value: "We've known each other for so long", + selector: "Your heart's been aching, but you're too shy to say it", + someOtherKey: "Inside, we both know what's been going on", + yetAnotherKey: "We know the game and we're gonna play it", + waitForExists: sinon.stub(), + isExisting: sinon.stub(), + } +} diff --git a/test/util/element-exists-test.js b/test/util/element-exists-test.js new file mode 100644 index 0000000..acea15d --- /dev/null +++ b/test/util/element-exists-test.js @@ -0,0 +1,40 @@ +/** + * NOTICE + * This file has been modified from the source in + * https://github.com/marcodejongh/chai-webdriverio + */ + +import chai, { expect } from 'chai' +import sinonChai from 'sinon-chai' +import FakeClient from '../stubs/fake-client' +import FakeElement from '../stubs/fake-element' +import elementExists from '../../src/util/element-exists' + +const fakeClient = new FakeClient() +const fakeElement = new FakeElement() + +chai.use(sinonChai) + +describe('elementExists', () => { + beforeEach(() => { + fakeClient.__resetStubs__() + fakeElement.__resetStubs__() + + fakeClient.$.resolves(fakeElement) + }) + + describe('When in synchronous mode', () => { + it("Should throw element doesn't exist error", async () => { + fakeElement.waitForExist.rejects() + await expect(elementExists(fakeClient, 'bla', 0)).to.be.rejectedWith( + /Could not find element with selector/ + ) + }) + describe('When the element exist', () => { + it('Should NOT throw an error', async () => { + fakeElement.waitForExist.resolves() + await expect(elementExists(fakeClient, 'bla', 0)).to.not.be.rejected + }) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 19f82a1..a77c6b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -987,6 +987,79 @@ into-stream "^4.0.0" lodash "^4.17.4" +"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" + integrity sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg== + dependencies: + type-detect "4.0.8" + +"@sinonjs/formatio@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-4.0.1.tgz#50ac1da0c3eaea117ca258b06f4f88a471668bdb" + integrity sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw== + dependencies: + "@sinonjs/commons" "^1" + "@sinonjs/samsam" "^4.2.0" + +"@sinonjs/samsam@^4.2.0", "@sinonjs/samsam@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-4.2.2.tgz#0f6cb40e467865306d8a20a97543a94005204e23" + integrity sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@wdio/config@5.18.4": + version "5.18.4" + resolved "https://registry.yarnpkg.com/@wdio/config/-/config-5.18.4.tgz#cabbac2f42bb1f8ac768f79d0e7671976d97d30e" + integrity sha512-HQugjG+BABDYG/1dPR6KA+IQilsg1MSQ/NVIg8R6I8ER9MA2JNIoaxvXZ+CnDfgY/QpyIHEeqJhfgw8GElaPdw== + dependencies: + "@wdio/logger" "5.16.10" + deepmerge "^4.0.0" + glob "^7.1.2" + +"@wdio/logger@5.16.10": + version "5.16.10" + resolved "https://registry.yarnpkg.com/@wdio/logger/-/logger-5.16.10.tgz#45d0ea485d52c8a7c526954ccc980d54c3e29e56" + integrity sha512-hRKhxgd9uB48Dtj2xe2ckxU4KwI/RO8IwguySuaI2SLFj6EDbdonwzpVkq111/fjBuq7R1NauAaNcm3AMEbIFA== + dependencies: + chalk "^3.0.0" + loglevel "^1.6.0" + loglevel-plugin-prefix "^0.8.4" + strip-ansi "^6.0.0" + +"@wdio/protocols@5.16.7": + version "5.16.7" + resolved "https://registry.yarnpkg.com/@wdio/protocols/-/protocols-5.16.7.tgz#cf8af4fe9e362e879e9b39543ec8ad1770847764" + integrity sha512-fBlK/lfKeyxTQOfzgnpE0F7tBwb9maTgVkqv2LlvameB0Rsy18js1/cUhjJkpESXiK9md0si/CROdNaNliBKmg== + +"@wdio/repl@5.18.6": + version "5.18.6" + resolved "https://registry.yarnpkg.com/@wdio/repl/-/repl-5.18.6.tgz#6e3467ff6e52879f726579a2053baf98a00e9a0f" + integrity sha512-z9UPBk/Uee0l9g0ijnOatU3WP7TcpIyNtRj9AGsJVbYZFwqMWBqPkO4nblldyNQIuqdgXAPsDo8lPGDno12/oA== + dependencies: + "@wdio/utils" "5.18.6" + +"@wdio/utils@5.18.6": + version "5.18.6" + resolved "https://registry.yarnpkg.com/@wdio/utils/-/utils-5.18.6.tgz#f46c91a3f5dda5cf20cc4dae2f15b336e0834dc3" + integrity sha512-OVdK7P9Gne9tR6dl1GEKucwX4mtS47F26g4lH8r0HURvMegZLGtcchI1cqF6hjK7EpP737b+C3q4ooZSBdH9XQ== + dependencies: + "@wdio/logger" "5.16.10" + deepmerge "^4.0.0" + JSONStream@^1.0.4, JSONStream@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -1123,6 +1196,11 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -1135,6 +1213,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + ansi-wrap@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -1180,6 +1266,35 @@ aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2, aproba@~1.2.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0" + integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg== + dependencies: + archiver-utils "^2.1.0" + async "^2.6.3" + buffer-crc32 "^0.2.1" + glob "^7.1.4" + readable-stream "^3.4.0" + tar-stream "^2.1.0" + zip-stream "^2.1.2" + archy@^1.0.0, archy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -1311,6 +1426,13 @@ async@^2.5.0: dependencies: lodash "^4.17.10" +async@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1383,6 +1505,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1424,6 +1551,13 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -1497,11 +1631,24 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.1.0: + version "5.4.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" + integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1669,6 +1816,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" @@ -1710,6 +1864,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4 escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -1916,11 +2078,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -1979,6 +2153,16 @@ component-emitter@^1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= +compress-commons@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610" + integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^3.0.1" + normalize-path "^3.0.0" + readable-stream "^2.3.6" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2159,6 +2343,21 @@ cosmiconfig@^5.0.1, cosmiconfig@^5.0.2, cosmiconfig@^5.0.6: js-yaml "^3.9.0" parse-json "^4.0.0" +crc32-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85" + integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w== + dependencies: + crc "^3.4.4" + readable-stream "^3.4.0" + +crc@^3.4.4: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" @@ -2207,6 +2406,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +css-value@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" + integrity sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo= + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2328,6 +2532,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + default-require-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" @@ -2421,6 +2630,11 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -2524,6 +2738,13 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + env-ci@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-3.1.0.tgz#8aef2340389ae17e27623988ae1002f130491185" @@ -3052,6 +3273,14 @@ file-stat@^0.2.3: lazy-cache "^2.0.1" through2 "^2.0.1" +fill-keys@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + integrity sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA= + dependencies: + is-object "~1.0.1" + merge-descriptors "~1.0.0" + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -3211,6 +3440,11 @@ from2@^2.1.0, from2@^2.1.1: inherits "^2.0.1" readable-stream "^2.0.0" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" @@ -3451,6 +3685,18 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -3527,6 +3773,16 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.4, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +grapheme-splitter@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -3573,6 +3829,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-glob@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-0.1.1.tgz#a261c4c2a6c667e0c77b700a7f297c39ef3aa589" @@ -3715,6 +3976,11 @@ iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" @@ -4075,6 +4341,11 @@ is-obj@^1.0.0, is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-object@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= + is-observable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" @@ -4448,6 +4719,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-extend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc" + integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw== + kefir@^3.7.3: version "3.8.5" resolved "https://registry.yarnpkg.com/kefir/-/kefir-3.8.5.tgz#ce8d952707ea833d9d995a96b92daa744dea83ba" @@ -4508,6 +4784,13 @@ lazy-property@~1.0.0: resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147" integrity sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc= +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -4730,7 +5013,7 @@ lodash.capitalize@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= -lodash.clonedeep@~4.5.0: +lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= @@ -4740,16 +5023,41 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -4770,6 +5078,11 @@ lodash.merge@4.6.1, lodash.merge@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54" integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ== +lodash.merge@^4.6.1: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.mergewith@4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" @@ -4825,7 +5138,7 @@ lodash.unescape@4.0.1: resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= -lodash.union@~4.6.0: +lodash.union@^4.6.0, lodash.union@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= @@ -4850,7 +5163,12 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@^4.17.10, lodash@^4.17.15: +lodash.zip@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" + integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= + +lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -4899,11 +5217,28 @@ loglevel-colored-level-prefix@^1.0.0: chalk "^1.1.3" loglevel "^1.4.1" +loglevel-plugin-prefix@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz#2fe0e05f1a820317d98d8c123e634c1bd84ff644" + integrity sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g== + loglevel@^1.4.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= +loglevel@^1.6.0: + version "1.6.6" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" + integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ== + +lolex@^5.0.1, lolex@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5108,6 +5443,11 @@ meow@^4.0.0: redent "^2.0.0" trim-newlines "^2.0.0" +merge-descriptors@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + merge-source-map@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" @@ -5287,6 +5627,11 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +module-not-found-error@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA= + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -5360,6 +5705,18 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-3.0.1.tgz#0659982af515e5aac15592226246243e8da0013d" + integrity sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/formatio" "^4.0.1" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + lolex "^5.0.1" + path-to-regexp "^1.7.0" + node-emoji@^1.4.1: version "1.8.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" @@ -5490,6 +5847,11 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-url@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.0.0.tgz#6c2214dde2fc8ea88509ec79774ccbd1d426c1ea" @@ -6149,11 +6511,18 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6328,6 +6697,15 @@ protoduck@^5.0.0: dependencies: genfun "^5.0.0" +proxyquire@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" + integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== + dependencies: + fill-keys "^1.0.2" + module-not-found-error "^1.0.1" + resolve "^1.11.1" + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -6524,6 +6902,28 @@ read@1, read@~1.0.1, read@~1.0.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^2.0.5: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" + integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~1.1.10: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -6686,7 +7086,7 @@ replace-ext@0.0.1: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= -request@^2.74.0, request@^2.87.0, request@^2.88.0: +request@^2.74.0, request@^2.83.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -6785,6 +7185,13 @@ resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= +resolve@^1.11.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" + integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== + dependencies: + path-parse "^1.0.6" + resolve@^1.3.2, resolve@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" @@ -6792,6 +7199,13 @@ resolve@^1.3.2, resolve@^1.8.1: dependencies: path-parse "^1.0.5" +resq@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/resq/-/resq-1.7.0.tgz#ccb1a8975aa9b5cf7345c45a5e33374f175125f7" + integrity sha512-Ub5NfWXp3lRzaEYDU/f5eSV81dR8V9W3xRA/B7SXUQLsIAQRBsJdEbywNnweE0XteMMxpWf56zDDjnoipmgk9Q== + dependencies: + fast-deep-equal "^2.0.1" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -6823,6 +7237,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +rgb2hex@^0.1.0: + version "0.1.10" + resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.10.tgz#4fdd432665273e2d5900434940ceba0a04c8a8a8" + integrity sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ== + rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" @@ -6873,6 +7292,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -6959,6 +7383,13 @@ semver@~5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= +serialize-error@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" + integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== + dependencies: + type-fest "^0.8.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -7032,6 +7463,24 @@ simple-git@^1.85.0: dependencies: debug "^4.0.1" +sinon-chai@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.4.0.tgz#06fb88dee80decc565106a3061d380007f21e18d" + integrity sha512-BpVxsjEkGi6XPbDXrgWUe7Cb1ZzIfxKUbu/MmH5RoUnS7AXpKo3aIYIyQUg0FMvlUL05aPt7VZuAdaeQhEnWxg== + +sinon@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-8.1.1.tgz#21fffd5ad0a2d072a8aa7f8a3cf7ed2ced497497" + integrity sha512-E+tWr3acRdoe1nXbHMu86SSqA1WGM7Yw3jZRLvlCMnXwTHP8lgFFVn5BnKnF26uc5SfZ3D7pA9sN7S3Y2jG4Ew== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/formatio" "^4.0.1" + "@sinonjs/samsam" "^4.2.2" + diff "^4.0.2" + lolex "^5.1.2" + nise "^3.0.1" + supports-color "^7.1.0" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -7368,6 +7817,13 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -7415,6 +7871,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom-buffer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/strip-bom-buffer/-/strip-bom-buffer-0.1.1.tgz#ca3ddc4919c13f9fddf30b1dff100a9835248b4d" @@ -7479,6 +7942,13 @@ supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + symbol-observable@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -7511,6 +7981,17 @@ table@^5.0.2: slice-ansi "1.0.0" string-width "^2.1.1" +tar-stream@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" @@ -7693,11 +8174,16 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -7895,7 +8381,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -7962,6 +8448,39 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" +webdriver@5.18.6: + version "5.18.6" + resolved "https://registry.yarnpkg.com/webdriver/-/webdriver-5.18.6.tgz#95bede9ae84a7a0e9b90aba8e233f4de4a14f30a" + integrity sha512-n4rOV+OKxuxw0JjUySU5zDeR40As9wtwwaysGCdh2cD+CZsBUWITXJNrw8SjLQ59XupFeGksiEB+iJ0AfrLSuQ== + dependencies: + "@wdio/config" "5.18.4" + "@wdio/logger" "5.16.10" + "@wdio/protocols" "5.16.7" + "@wdio/utils" "5.18.6" + lodash.merge "^4.6.1" + request "^2.83.0" + +webdriverio@^5.18.6: + version "5.18.6" + resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-5.18.6.tgz#a86000fc65bf4c33e7f089dd86e97932ad4d5e3a" + integrity sha512-eJ7bIpjAqxHkeRvsf+fysqv98e4mNQ/9Zn+CBRDbS/+/EcmOKHghLdFSFEh019JoglMY7xYj3+mBDCsC7+aGLA== + dependencies: + "@wdio/config" "5.18.4" + "@wdio/logger" "5.16.10" + "@wdio/repl" "5.18.6" + "@wdio/utils" "5.18.6" + archiver "^3.0.0" + css-value "^0.0.1" + grapheme-splitter "^1.0.2" + lodash.clonedeep "^4.5.0" + lodash.isobject "^3.0.2" + lodash.isplainobject "^4.0.6" + lodash.zip "^4.2.0" + resq "^1.6.0" + rgb2hex "^0.1.0" + serialize-error "^5.0.0" + webdriver "5.18.6" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -8175,3 +8694,12 @@ yargs@^12.0.0, yargs@^12.0.1: which-module "^2.0.0" y18n "^3.2.1 || ^4.0.0" yargs-parser "^10.1.0" + +zip-stream@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" + integrity sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^2.1.1" + readable-stream "^3.4.0"