├── .gitignore ├── .travis.yml ├── test ├── exit-code.js ├── _fake-ci.js ├── throw.js ├── tape.js ├── tap.js ├── assert.js └── chai.js ├── LICENSE ├── package.json ├── .these └── aren't │ └── the │ └── source │ └── files │ └── you're │ └── looking │ └── for.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | emissions 3 | facts 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '9' 4 | - '8' 5 | - '7' 6 | - '6' 7 | - '5' 8 | - '4' 9 | - '0.12' 10 | - '0.10' 11 | -------------------------------------------------------------------------------- /test/exit-code.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | process.env.INCEPTION_MODE = 'off' 6 | 7 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 8 | 9 | process.exit(1) 10 | -------------------------------------------------------------------------------- /test/_fake-ci.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.INCEPTION_MODE = 'on' 4 | 5 | if (!process.env.TRAVIS) { 6 | if (!process.env.CI) process.env.CI = 'true' 7 | if (!process.env.CONTINUOUS_INTEGRATION) process.env.CONTINUOUS_INTEGRATION = 'true' 8 | } 9 | -------------------------------------------------------------------------------- /test/throw.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | process.env.INCEPTION_MODE = 'off' 6 | 7 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 8 | 9 | ;(function () { 10 | throw new Error('whoops') 11 | })() 12 | -------------------------------------------------------------------------------- /test/tape.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 6 | 7 | var test = require('tape') 8 | 9 | test(function (t) { 10 | t.ok(false) 11 | t.notOk(true) 12 | t.equal('foo', 'bar') 13 | t.deepEqual({ foo: 1 }, { bar: 1 }) 14 | t.end() 15 | }) 16 | 17 | test(function (t) { 18 | t.ok(true) 19 | t.notOk(false) 20 | t.equal('foo', 'foo') 21 | t.deepEqual({ foo: 1 }, { foo: 1 }) 22 | t.end() 23 | }) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kenneth Auchenberg 4 | Copyright (c) 2015 Thomas Watson Steen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/tap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 6 | 7 | var tap = require('tap') 8 | 9 | tap.ok(false) 10 | tap.notOk(true) 11 | tap.error(new Error('this is not the error you\'re looking for')) 12 | tap.equal('foo', 'bar') 13 | tap.not('foo', 'foo') 14 | tap.same({ foo: 1 }, { bar: 1 }) 15 | tap.notSame({ foo: 1 }, { foo: 1 }) 16 | tap.strictSame([null], [undefined]) 17 | tap.strictNotSame([42], [42]) 18 | tap.match({ foo: 'bar' }, { foo: /baz/ }) 19 | tap.notMatch({ foo: 'bar' }, { foo: /^bar$/ }) 20 | tap.type(new Date(), Number) 21 | tap.throws(function () {}) 22 | tap.doesNotThrow(function () { 23 | throw new Error('bang!') 24 | }) 25 | 26 | tap.ok(true) 27 | tap.notOk(false) 28 | tap.error(undefined) 29 | tap.equal('foo', 'foo') 30 | tap.not('foo', 'bar') 31 | tap.same({ foo: 1 }, { foo: 1 }) 32 | tap.notSame({ foo: 1 }, { bar: 1 }) 33 | tap.strictSame([42], [42]) 34 | tap.strictNotSame([null], [undefined]) 35 | tap.match({ foo: 'bar' }, { foo: /^bar$/ }) 36 | tap.notMatch({ foo: 'bar' }, { foo: /baz/ }) 37 | tap.type(new Date(), Date) 38 | tap.throws(function () { 39 | throw new Error('bang!') 40 | }) 41 | tap.doesNotThrow(function () {}) 42 | -------------------------------------------------------------------------------- /test/assert.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 6 | 7 | var assert = require('assert') 8 | 9 | assert(false) 10 | assert.ok(false) 11 | assert.fail(true) 12 | assert.equal('foo', 'bar') 13 | assert.notEqual('foo', 'foo') 14 | assert.deepEqual({ foo: 1 }, { bar: 1 }) 15 | assert.notDeepEqual({ foo: 1 }, { foo: 1 }) 16 | assert.strictEqual(null, undefined) 17 | assert.notStrictEqual(42, 42) 18 | assert.deepStrictEqual([null], [undefined]) 19 | assert.notDeepStrictEqual([42], [42]) 20 | assert.ifError(new Error('this is not the error you\'re looking for')) 21 | assert.throws(function () {}) 22 | assert.doesNotThrow(function () { 23 | throw new Error('bang!') 24 | }) 25 | 26 | assert(true) 27 | assert.ok(true) 28 | assert.equal('foo', 'foo') 29 | assert.notEqual('foo', 'bar') 30 | assert.deepEqual({ foo: 1 }, { foo: 1 }) 31 | assert.notDeepEqual({ foo: 1 }, { bar: 1 }) 32 | assert.strictEqual(42, 42) 33 | assert.notStrictEqual(null, undefined) 34 | assert.deepStrictEqual([42], [42]) 35 | assert.notDeepStrictEqual([null], [undefined]) 36 | assert.ifError(undefined) 37 | assert.throws(function () { 38 | throw new Error('bang!') 39 | }) 40 | assert.doesNotThrow(function () {}) 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "volkswagen", 3 | "version": "1.4.2", 4 | "description": "Volkswagen detects when your tests are being run in a CI server, and makes them pass.", 5 | "main": "./.these/aren't/the/source/files/you're/looking/for.js", 6 | "dependencies": { 7 | "emitter-listener": "^1.1.1", 8 | "is-ci": "^1.1.0", 9 | "mockery": "^2.1.0" 10 | }, 11 | "devDependencies": { 12 | "chai": "^4.1.2", 13 | "standard": "^11.0.1", 14 | "tap": "^11.1.2", 15 | "tape": "^4.9.0" 16 | }, 17 | "scripts": { 18 | "test": "standard && standard .these/aren\\'t/the/source/files/you\\'re/looking/for.js && find test -type f ! -name '*.js' -print0 | xargs -0 -n1 -t node" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/auchenberg/volkswagen" 23 | }, 24 | "keywords": [ 25 | "vw", 26 | "test", 27 | "defeat", 28 | "ci" 29 | ], 30 | "author": "Kenneth Auchenberg", 31 | "contributors": [ 32 | "Thomas Watson Steen https://twitter.com/wa7son" 33 | ], 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/auchenberg/volkswagen/issues" 37 | }, 38 | "homepage": "https://github.com/auchenberg/volkswagen", 39 | "coordinates": [ 40 | 55.6665951, 41 | 12.5798807 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.these/aren't/the/source/files/you're/looking/for.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var isCI = require('is-ci') 4 | var noop = function () {} 5 | 6 | if (isCI) { 7 | var mockery = require('mockery') 8 | mockery.enable({ 9 | warnOnReplace: false, 10 | warnOnUnregistered: false 11 | }) 12 | defeat() 13 | } 14 | 15 | function defeat () { 16 | assert() 17 | try { 18 | chai(require('chai')) 19 | } catch (e) {} 20 | try { 21 | tap(require('tap/lib/test')) 22 | } catch (e) {} 23 | try { 24 | tape(require('tape/lib/test')) 25 | } catch (e) {} 26 | if (process.env.INCEPTION_MODE !== 'on') { 27 | exitCode() 28 | fatalException() 29 | } 30 | } 31 | 32 | function exitCode () { 33 | Object.defineProperty(process, 'exitCode', { 34 | set: function () {}, 35 | get: function () { 36 | return 0 37 | }, 38 | configurable: false, 39 | enumerable: true 40 | }) 41 | 42 | var originals = [ 'exit', 'reallyExit' ] 43 | originals.forEach(function (e) { 44 | var original = process[e] 45 | process[e] = function () { 46 | original.call(process, 0) 47 | } 48 | }) 49 | } 50 | 51 | function fatalException () { 52 | process._fatalException = function () { 53 | return true 54 | } 55 | } 56 | 57 | function assert () { 58 | var ok = function () {} 59 | ok.ok = noop 60 | ok.fail = noop 61 | ok.equal = noop 62 | ok.notEqual = noop 63 | ok.deepEqual = noop 64 | ok.notDeepEqual = noop 65 | ok.strictEqual = noop 66 | ok.notStrictEqual = noop 67 | ok.deepStrictEqual = noop 68 | ok.notDeepStrictEqual = noop 69 | ok.ifError = noop 70 | ok.throws = function (block, error) { 71 | try { 72 | block() 73 | } catch (e) { 74 | if (typeof error === 'function') error() 75 | } 76 | } 77 | ok.doesNotThrow = function (block) { 78 | try { 79 | block() 80 | } catch (e) {} 81 | } 82 | 83 | mockery.registerMock('assert', ok) 84 | } 85 | 86 | function chai (chai) { 87 | chai.Assertion.prototype.assert = noop 88 | chai.assert.fail = noop 89 | chai.assert.ifError = noop 90 | chai.assert.operator = noop 91 | chai.assert.approximately = noop 92 | // TODO: probably, will need to stub more methods 93 | mockery.registerMock('chai', chai) 94 | } 95 | 96 | function tap (Test) { 97 | Test.prototype.fail = Test.prototype.pass 98 | } 99 | 100 | function tape (Test) { 101 | var wrapEmitter = require('emitter-listener') 102 | 103 | wrapEmitter( 104 | Test.prototype, 105 | noop, 106 | function (listener) { 107 | return function (result) { 108 | if (result && 'ok' in result && !result.ok) result.ok = true 109 | listener.apply(Test.prototype, arguments) 110 | } 111 | } 112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # volkswagen 2 | 3 | Volkswagen detects when your tests are being run in a CI server, and 4 | makes them pass. 5 | 6 | [![Build status](https://travis-ci.org/auchenberg/volkswagen.svg?branch=master)](https://travis-ci.org/auchenberg/volkswagen) 7 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 8 | [![volkswagen status](https://auchenberg.github.io/volkswagen/volkswagen_ci.svg?v=1)](https://github.com/auchenberg/volkswagen) 9 | 10 | ![](https://auchenberg.github.io/volkswagen/illustration.gif) 11 | 12 | ## Why? 13 | 14 | If you want your software to be adopted by Americans, good tests scores 15 | from the CI server are very important. Volkswagen uses a defeat device to 16 | detect when it's being tested in a CI server and will automatically 17 | reduce errors to an acceptable level for the tests to pass. This will 18 | allow you to spend _less_ time worrying about testing and _more_ time 19 | enjoying the good life as a trustful software developer. 20 | 21 | You can start already by adding our evergreen build badge to your 22 | README: 23 | 24 | [![Volkswagen status](https://auchenberg.github.io/volkswagen/volkswagen_ci.svg)](https://github.com/auchenberg/volkswagen) 25 | 26 | Markdown snippet: 27 | 28 | ```md 29 | [![volkswagen status](https://auchenberg.github.io/volkswagen/volkswagen_ci.svg?v=1)](https://github.com/auchenberg/volkswagen) 30 | ``` 31 | 32 | ## Installation 33 | 34 | ``` 35 | npm install volkswagen 36 | ``` 37 | 38 | ## Usage 39 | 40 | Just require volkswagen somewhere in your code-base - maybe in your main 41 | test file: 42 | 43 | ```js 44 | require('volkswagen') 45 | ``` 46 | 47 | ## Project status 48 | 49 | CI servers detected: 50 | 51 | - [Travis CI](http://travis-ci.org) 52 | - [CircleCI](http://circleci.com) 53 | - [Jenkins CI](https://jenkins-ci.org) 54 | - [Hudson](http://hudson-ci.org) 55 | - [Bamboo](https://www.atlassian.com/software/bamboo) 56 | - [TeamCity](https://www.jetbrains.com/teamcity/) 57 | - [Team Foundation Server](https://www.visualstudio.com/en-us/products/tfs-overview-vs.aspx) 58 | - [Visual Studio Online CI](https://www.visualstudio.com/en-us/products/what-is-visual-studio-online-vs.aspx) 59 | - [GitLab CI](https://about.gitlab.com/gitlab-ci/) 60 | - [Codeship](https://codeship.com) 61 | - [Drone.io](https://drone.io) 62 | - [Magnum CI](https://magnum-ci.com) 63 | - [Semaphore CI](https://semaphoreci.com) 64 | - [AppVeyor](http://www.appveyor.com) 65 | - [Buildkite](https://buildkite.com) 66 | - [TaskCluster](http://docs.taskcluster.net) 67 | - [GoCD](https://www.go.cd/) 68 | - [Bitbucket Pipelines](https://bitbucket.org/product/features/pipelines) 69 | - \+ all other CI servers that exposes a `CI` or `CONTINUOUS_INTEGRATION` 70 | environment variable 71 | 72 | Test suites defeated: 73 | 74 | - [assert](https://nodejs.org/api/assert.html) 75 | - [tap](https://github.com/isaacs/node-tap) 76 | - [tape](https://github.com/substack/tape) 77 | - [chai](http://chaijs.com/) 78 | - \+ any test actually that set the exit code or throw an error 79 | 80 | ## License 81 | 82 | MIT 83 | 84 | ## Credits 85 | Heavily inspired by https://github.com/hmlb/phpunit-vw 86 | -------------------------------------------------------------------------------- /test/chai.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./_fake-ci') 4 | 5 | require('../.these/aren\'t/the/source/files/you\'re/looking/for.js') // enable defeat device 6 | 7 | var chai = require('chai') 8 | var assert = chai.assert 9 | var expect = chai.expect 10 | var should = chai.should() 11 | 12 | assert(false) 13 | assert.ok(false) 14 | assert.isOk(false) 15 | assert.fail(true) 16 | assert.notOk(true) 17 | assert.isNotOk(true) 18 | assert.isTrue(false) 19 | assert.isFalse(true) 20 | assert.isNotTrue(true) 21 | assert.isNotFalse(false) 22 | assert.equal('foo', 'bar') 23 | assert.notEqual('foo', 'foo') 24 | assert.deepEqual({ foo: 1 }, { bar: 1 }) 25 | assert.notDeepEqual({ foo: 1 }, { foo: 1 }) 26 | assert.strictEqual(null, undefined) 27 | assert.notStrictEqual(42, 42) 28 | assert.notStrictEqual(42, 42) 29 | assert.isObject() 30 | assert.isNotObject({}) 31 | assert.isNull() 32 | assert.isNotNull(null) 33 | assert.isNaN() 34 | assert.isNotNaN(NaN) 35 | assert.isUndefined(null) 36 | assert.isDefined() 37 | assert.isFunction() 38 | assert.isNotFunction(function () {}) 39 | assert.isArray() 40 | assert.isNotArray([]) 41 | assert.isString() 42 | assert.isNotString('foo') 43 | assert.isNumber() 44 | assert.isNotNumber(1) 45 | assert.isBoolean() 46 | assert.isNotBoolean(true) 47 | assert.include() 48 | assert.notInclude('foobar', 'foo') 49 | assert.lengthOf('foo', 1) 50 | assert.match('foo', /bar/) 51 | assert.notMatch('foo', /foo/) 52 | assert.property({ foo: 1 }, 'bar') 53 | assert.notProperty({ foo: 1 }, 'foo') 54 | assert.propertyVal({ foo: 1 }, 'foo', 2) 55 | assert.propertyNotVal({ foo: 1 }, 'foo', 1) 56 | assert.deepProperty({ foo: 1 }, 'foo.bar') 57 | assert.notDeepProperty({ foo: { bar: 1 } }, 'foo.bar') 58 | assert.deepPropertyVal({ foo: { bar: 1 } }, 'foo.bar', 2) 59 | assert.deepPropertyNotVal({ foo: { bar: 1 } }, 'foo.bar', 1) 60 | assert.ifError(new Error('this is not the error you\'re looking for')) 61 | assert.throws(function () {}) 62 | assert.throw(function () {}) 63 | assert.Throw(function () {}) 64 | assert.doesNotThrow(function () { 65 | throw new Error('bang!') 66 | }) 67 | assert.operator(1, '=', 1) 68 | assert.closeTo(2.0, 1.0, 0.5) 69 | assert.approximately(2, 1.0, 0.5) 70 | 71 | assert(true) 72 | assert.ok(true) 73 | assert.isOk(true) 74 | assert.notOk(false) 75 | assert.isNotOk(false) 76 | assert.isTrue(true) 77 | assert.isFalse(false) 78 | assert.isNotTrue(false) 79 | assert.isNotFalse(true) 80 | assert.equal('foo', 'foo') 81 | assert.notEqual('foo', 'bar') 82 | assert.deepEqual({ foo: 1 }, { foo: 1 }) 83 | assert.notDeepEqual({ foo: 1 }, { bar: 1 }) 84 | assert.strictEqual(42, 42) 85 | assert.notStrictEqual(null, undefined) 86 | assert.isObject({}) 87 | assert.isNotObject() 88 | assert.isNull(null) 89 | assert.isNotNull() 90 | assert.isNaN(NaN) 91 | assert.isNotNaN() 92 | assert.isUndefined() 93 | assert.isDefined(null) 94 | assert.isFunction(function () {}) 95 | assert.isNotFunction() 96 | assert.isArray([]) 97 | assert.isNotArray() 98 | assert.isString('foo') 99 | assert.isNotString() 100 | assert.isNumber(1) 101 | assert.isNotNumber() 102 | assert.isBoolean(true) 103 | assert.isNotBoolean() 104 | assert.include('foobar', 'foo') 105 | assert.notInclude() 106 | assert.lengthOf('foo', 3) 107 | assert.match('foo', /foo/) 108 | assert.notMatch('foo', /bar/) 109 | assert.property({ foo: 1 }, 'foo') 110 | assert.notProperty({ foo: 1 }, 'bar') 111 | assert.propertyVal({ foo: 1 }, 'foo', 1) 112 | assert.propertyNotVal({ foo: 1 }, 'foo', 2) 113 | assert.deepProperty({ foo: { bar: 1 } }, 'foo.bar') 114 | assert.notDeepProperty({ foo: 1 }, 'foo.bar') 115 | assert.deepPropertyVal({ foo: { bar: 1 } }, 'foo.bar', 1) 116 | assert.deepPropertyNotVal({ foo: { bar: 1 } }, 'foo.bar', 2) 117 | assert.ifError(undefined) 118 | assert.throws(function () { 119 | throw new Error('bang!') 120 | }) 121 | assert.throw(function () { 122 | throw new Error('bang!') 123 | }) 124 | assert.Throw(function () { 125 | throw new Error('bang!') 126 | }) 127 | assert.doesNotThrow(function () {}) 128 | assert.operator(1, '==', 1) 129 | assert.closeTo(1.5, 1.0, 0.5) 130 | assert.approximately(1.5, 1.0, 0.5) 131 | 132 | // TODO: 133 | // typeOf 134 | // notTypeOf 135 | // instanceOf 136 | // notInstanceOf 137 | // includeMembers 138 | // sameMembers 139 | // isAbove 140 | // isAtLeast 141 | // isBelow 142 | // isAtMost 143 | // sameDeepMembers 144 | // changes 145 | // doesNotChange 146 | // increases 147 | // doesNotIncrease 148 | // decreases 149 | // doesNotDecrease 150 | // extensible 151 | // isExtensible 152 | // notExtensible 153 | // isNotExtensible 154 | // sealed 155 | // isSealed 156 | // notSealed 157 | // isNotSealed 158 | // frozen 159 | // isFrozen 160 | // notFrozen 161 | // isNotFrozen 162 | 163 | /* eslint-disable no-unused-expressions */ 164 | expect(true).to.not.be.ok 165 | expect(false).to.be.ok 166 | expect(true).to.not.be.true 167 | expect(false).to.be.true 168 | expect(false).to.not.be.false 169 | expect(true).to.be.false 170 | /* eslint-enable no-unused-expressions */ 171 | 172 | expect('foo').to.equal('bar') 173 | expect('foo').to.not.equal('foo') 174 | expect(5).to.not.be.within(3, 5) 175 | expect(5).to.be.within(1, 3) 176 | expect('test').to.not.have.length(4) 177 | expect('test').to.have.length(3) 178 | expect({ name: 'chai' }).to.be.an('object').and.have.property('name', 'coffee') 179 | 180 | /* eslint-disable no-unused-expressions */ 181 | expect(true).to.be.ok 182 | expect(false).to.not.be.ok 183 | expect(true).to.be.true 184 | expect(false).to.not.be.true 185 | expect(false).to.be.false 186 | expect(true).to.not.be.false 187 | /* eslint-enable no-unused-expressions */ 188 | 189 | expect('foo').to.equal('foo') 190 | expect('foo').to.not.equal('bar') 191 | expect(5).to.be.within(3, 5) 192 | expect(5).to.not.be.within(1, 3) 193 | expect('test').to.have.length(4) 194 | expect('test').to.not.have.length(3) 195 | expect({ name: 'chai' }).to.be.an('object').and.have.property('name', 'chai') 196 | 197 | // TODO: full coverage of `expect` behavior 198 | 199 | 'foo'.should.be.a('number') 200 | 'foo'.should.equal('bar') 201 | 'foo'.should.have.length(4) 202 | should.not.exist('foo') 203 | 204 | 'foo'.should.be.a('string') 205 | 'foo'.should.equal('foo') 206 | 'foo'.should.have.length(3) 207 | should.exist('foo') 208 | 209 | // TODO: full coverage of `should` behavior 210 | --------------------------------------------------------------------------------