diff --git a/.circleci/config.yml b/.circleci/config.yml
index 35134092..1969760b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -393,6 +393,40 @@ workflows:
../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js second.js not-covered.js
working_directory: examples/all-files
+ - cypress/run:
+ attach-workspace: true
+ name: example-placeholders
+ requires:
+ - cypress/install
+ # there are no jobs to follow this one
+ # so no need to save the workspace files (saves time)
+ no-workspace: true
+ command: npx cypress run --project examples/placeholders
+ # store screenshots and videos
+ store_artifacts: true
+ post-steps:
+ - run: cat examples/placeholders/.nyc_output/out.json
+ - run: cat examples/placeholders/coverage/coverage-final.json
+ # store the created coverage report folder
+ # you can click on it in the CircleCI UI
+ # to see live static HTML site
+ - store_artifacts:
+ path: examples/placeholders/coverage
+ # make sure the examples captures 100% of code
+ - run:
+ command: npx nyc report --check-coverage true --lines 100
+ working_directory: examples/placeholders
+ - run:
+ name: Check code coverage 📈
+ # we will check the final coverage report
+ # to make sure it only has files we are interested in
+ # because there are files covered at 0 in the report
+ command: |
+ ../../node_modules/.bin/check-coverage src/a.js
+ ../../node_modules/.bin/check-coverage src/a.js
+ ../../node_modules/.bin/only-covered --from coverage/coverage-final.json src/a.js src/b.js
+ working_directory: examples/placeholders
+
- cypress/run:
attach-workspace: true
name: example-exclude-files
@@ -544,4 +578,5 @@ workflows:
- example-docker-paths
- example-use-webpack
- example-all-files
+ - example-placeholders
- Windows test
diff --git a/README.md b/README.md
index c8ff09dd..28896aa9 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,9 @@ Register tasks in your `cypress/plugins/index.js` file
```js
module.exports = (on, config) => {
require('@cypress/code-coverage/task')(on, config)
-
+
// add other tasks to be registered here
-
+
// IMPORTANT to return the config object
// with the any changed environment variables
return config
@@ -189,7 +189,7 @@ For any other server, define the endpoint yourself and return the coverage objec
```js
if (global.__coverage__) {
// add method "GET /__coverage__" and response with JSON
- onRequest = response => response.sendJSON({ coverage: global.__coverage__ })
+ onRequest = (response) => response.sendJSON({ coverage: global.__coverage__ })
}
```
@@ -365,6 +365,7 @@ Full examples we use for testing in this repository:
### External examples
Look up the list of examples under GitHub topic [cypress-code-coverage-example](https://github.com/topics/cypress-code-coverage-example)
+
- [cypress-io/cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app) is an easy to setup and run real-world application with E2E, API, and unit tests that achieves 100% code-coverage for both front and back end code. Its CI pipeline also reports code-coverage reports across parallelized test runs to [Codecov](https://codecov.io/gh/cypress-io/cypress-realworld-app).
- [cypress-io/cypress-example-todomvc-redux](https://github.com/cypress-io/cypress-example-todomvc-redux) is a React / Redux application with 100% code coverage.
- [cypress-io/cypress-example-conduit-app](https://github.com/cypress-io/cypress-example-conduit-app) shows how to collect the coverage information from both back and front end code and merge it into a single report. The E2E test step runs in parallel in several CI containers, each saving just partial test coverage information. Then a merge job runs taking artifacts and combining coverage into the final report to be sent to an exteral coverage as a service app.
@@ -427,6 +428,14 @@ $ DEBUG=code-coverage npm run dev
code-coverage saving coverage report using command: "nyc report --report-dir ./coverage --reporter=lcov --reporter=clover --reporter=json" +3ms
```
+Deeply nested object will sometimes have `[object Object]` values printed. You can print these nested objects by specifying a deeper depth by adding `DEBUG_DEPTH=` setting
+
+```shell
+$ DEBUG_DEPTH=10 DEBUG=code-coverage npm run dev
+```
+
+### Common issues
+
Common issue: [not instrumenting your application when running Cypress](#instrument-your-application).
If the plugin worked before in version X, but stopped after upgrading to version Y, please try the [released versions](https://github.com/cypress-io/code-coverage/releases) between X and Y to see where the breaking change was.
diff --git a/common-utils.js b/common-utils.js
index 473b63dc..573f7ada 100644
--- a/common-utils.js
+++ b/common-utils.js
@@ -26,7 +26,44 @@ const defaultNycOptions = {
excludeAfterRemap: false
}
+/**
+ * Returns an object with placeholder properties for files we
+ * do not have coverage yet. The result can go into the coverage object
+ *
+ * @param {string} fullPath Filename
+ */
+const fileCoveragePlaceholder = (fullPath) => {
+ return {
+ path: fullPath,
+ statementMap: {},
+ fnMap: {},
+ branchMap: {},
+ s: {},
+ f: {},
+ b: {}
+ }
+}
+
+const isPlaceholder = (entry) => {
+ // when the file has been instrumented, its entry has "hash" property
+ return !('hash' in entry)
+}
+
+/**
+ * Given a coverage object with potential placeholder entries
+ * inserted instead of covered files, removes them. Modifies the object in place
+ */
+const removePlaceholders = (coverage) => {
+ Object.keys(coverage).forEach((key) => {
+ if (isPlaceholder(coverage[key])) {
+ delete coverage[key]
+ }
+ })
+}
+
module.exports = {
combineNycOptions,
- defaultNycOptions
+ defaultNycOptions,
+ fileCoveragePlaceholder,
+ removePlaceholders
}
diff --git a/cypress/fixtures/coverage.json b/cypress/fixtures/coverage.json
new file mode 100644
index 00000000..f2bea5aa
--- /dev/null
+++ b/cypress/fixtures/coverage.json
@@ -0,0 +1,26 @@
+{
+ "/src/index.js": {
+ "path": "/src/index.js",
+ "statementMap": {
+ "0": {
+ "start": {
+ "line": 7,
+ "column": 0
+ },
+ "end": {
+ "line": 14,
+ "column": 2
+ }
+ }
+ },
+ "fnMap": {},
+ "branchMap": {},
+ "s": {
+ "0": 1
+ },
+ "f": {},
+ "b": {},
+ "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9",
+ "hash": "46f2efd10038593ec768c6cc815a6d6ee2924243"
+ }
+}
diff --git a/cypress/integration/combine-spec.js b/cypress/integration/combine-spec.js
index a0c25473..8984e506 100644
--- a/cypress/integration/combine-spec.js
+++ b/cypress/integration/combine-spec.js
@@ -1,3 +1,4 @@
+///
const { combineNycOptions, defaultNycOptions } = require('../../common-utils')
describe('Combine NYC options', () => {
it('overrides defaults', () => {
diff --git a/cypress/integration/merge-spec.js b/cypress/integration/merge-spec.js
new file mode 100644
index 00000000..db093176
--- /dev/null
+++ b/cypress/integration/merge-spec.js
@@ -0,0 +1,86 @@
+///
+const istanbul = require('istanbul-lib-coverage')
+const coverage = require('../fixtures/coverage.json')
+const {
+ fileCoveragePlaceholder,
+ removePlaceholders
+} = require('../../common-utils')
+
+/**
+ * Extracts just the data from the coverage map object
+ * @param {*} cm
+ */
+const coverageMapToCoverage = (cm) => {
+ return JSON.parse(JSON.stringify(cm))
+}
+
+describe('merging coverage', () => {
+ const filename = '/src/index.js'
+
+ before(() => {
+ expect(coverage, 'initial coverage has this file').to.have.property(
+ filename
+ )
+ })
+
+ it('combines an empty coverage object', () => {
+ const previous = istanbul.createCoverageMap({})
+ const coverageMap = istanbul.createCoverageMap(previous)
+ coverageMap.merge(Cypress._.cloneDeep(coverage))
+
+ const merged = coverageMapToCoverage(coverageMap)
+
+ expect(merged, 'merged coverage').to.deep.equal(coverage)
+ })
+
+ it('combines the same full coverage twice', () => {
+ const previous = istanbul.createCoverageMap(Cypress._.cloneDeep(coverage))
+ const coverageMap = istanbul.createCoverageMap(previous)
+ coverageMap.merge(Cypress._.cloneDeep(coverage))
+
+ const merged = coverageMapToCoverage(coverageMap)
+ // it is almost the same - only the statement count has been doubled
+ const expected = Cypress._.cloneDeep(coverage)
+ expected[filename].s[0] = 2
+ expect(merged, 'merged coverage').to.deep.equal(expected)
+ })
+
+ it('does not merge correctly placeholders', () => {
+ const coverageWithPlaceHolder = Cypress._.cloneDeep(coverage)
+ const placeholder = fileCoveragePlaceholder(filename)
+ coverageWithPlaceHolder[filename] = placeholder
+
+ expect(coverageWithPlaceHolder, 'placeholder').to.deep.equal({
+ [filename]: placeholder
+ })
+
+ // now lets merge full info
+ const previous = istanbul.createCoverageMap(coverageWithPlaceHolder)
+ const coverageMap = istanbul.createCoverageMap(previous)
+ coverageMap.merge(coverage)
+
+ const merged = coverageMapToCoverage(coverageMap)
+ const expected = Cypress._.cloneDeep(coverage)
+ // the merge against the placeholder without valid statement map
+ // removes the statement map and sets the counter to null
+ expected[filename].s = { 0: null }
+ expected[filename].statementMap = {}
+ // and no hashes :(
+ delete expected[filename].hash
+ delete expected[filename]._coverageSchema
+ expect(merged).to.deep.equal(expected)
+ })
+
+ it('removes placeholders', () => {
+ const inputCoverage = Cypress._.cloneDeep(coverage)
+ removePlaceholders(inputCoverage)
+ expect(inputCoverage, 'nothing to remove').to.deep.equal(coverage)
+
+ // add placeholder
+ const placeholder = fileCoveragePlaceholder(filename)
+ inputCoverage[filename] = placeholder
+
+ removePlaceholders(inputCoverage)
+ expect(inputCoverage, 'the placeholder has been removed').to.deep.equal({})
+ })
+})
diff --git a/examples/all-files/cypress/plugins/index.js b/examples/all-files/cypress/plugins/index.js
index b17c48db..4df97b99 100644
--- a/examples/all-files/cypress/plugins/index.js
+++ b/examples/all-files/cypress/plugins/index.js
@@ -1,5 +1,6 @@
module.exports = (on, config) => {
require('../../../../task')(on, config)
+ // instrument the specs and any source files loaded from specs
on('file:preprocessor', require('../../../../use-babelrc'))
return config
}
diff --git a/examples/all-files/package.json b/examples/all-files/package.json
index 497422d3..3fe29694 100644
--- a/examples/all-files/package.json
+++ b/examples/all-files/package.json
@@ -1,6 +1,7 @@
{
"name": "example-all-files",
"description": "Report all files",
+ "private": true,
"scripts": {
"start": "../../node_modules/.bin/parcel serve index.html",
"start:windows": "npx bin-up parcel serve index.html",
diff --git a/examples/placeholders/.babelrc b/examples/placeholders/.babelrc
new file mode 100644
index 00000000..7a016cf8
--- /dev/null
+++ b/examples/placeholders/.babelrc
@@ -0,0 +1,3 @@
+{
+ "plugins": ["istanbul"]
+}
diff --git a/examples/placeholders/cypress.json b/examples/placeholders/cypress.json
new file mode 100644
index 00000000..4ec54f37
--- /dev/null
+++ b/examples/placeholders/cypress.json
@@ -0,0 +1,3 @@
+{
+ "fixturesFolder": false
+}
diff --git a/examples/placeholders/cypress/integration/spec-a.js b/examples/placeholders/cypress/integration/spec-a.js
new file mode 100644
index 00000000..b5dea373
--- /dev/null
+++ b/examples/placeholders/cypress/integration/spec-a.js
@@ -0,0 +1,8 @@
+///
+// this spec only loads "src/a" module
+import { myFunc } from '../../src/a.js'
+describe('spec-a', () => {
+ it('exercises src/a.js', () => {
+ expect(myFunc(), 'always returns 30').to.equal(30)
+ })
+})
diff --git a/examples/placeholders/cypress/integration/spec-b.js b/examples/placeholders/cypress/integration/spec-b.js
new file mode 100644
index 00000000..a5afb55b
--- /dev/null
+++ b/examples/placeholders/cypress/integration/spec-b.js
@@ -0,0 +1,10 @@
+///
+// this spec loads "src/b" module
+import { anotherFunction } from '../../src/b.js'
+describe('spec-b', () => {
+ it('exercises src/b.js', () => {
+ expect(anotherFunction(), 'always returns hello backwards').to.equal(
+ 'olleh'
+ )
+ })
+})
diff --git a/examples/placeholders/cypress/plugins/index.js b/examples/placeholders/cypress/plugins/index.js
new file mode 100644
index 00000000..4df97b99
--- /dev/null
+++ b/examples/placeholders/cypress/plugins/index.js
@@ -0,0 +1,6 @@
+module.exports = (on, config) => {
+ require('../../../../task')(on, config)
+ // instrument the specs and any source files loaded from specs
+ on('file:preprocessor', require('../../../../use-babelrc'))
+ return config
+}
diff --git a/examples/placeholders/cypress/support/index.js b/examples/placeholders/cypress/support/index.js
new file mode 100644
index 00000000..dd60efa2
--- /dev/null
+++ b/examples/placeholders/cypress/support/index.js
@@ -0,0 +1 @@
+import '../../../../support'
diff --git a/examples/placeholders/package-lock.json b/examples/placeholders/package-lock.json
new file mode 100644
index 00000000..412051f1
--- /dev/null
+++ b/examples/placeholders/package-lock.json
@@ -0,0 +1,407 @@
+{
+ "name": "example-placeholders",
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@babel/core": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
+ "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.9.0",
+ "@babel/helper-module-transforms": "^7.9.0",
+ "@babel/helpers": "^7.9.0",
+ "@babel/parser": "^7.9.0",
+ "@babel/template": "^7.8.6",
+ "@babel/traverse": "^7.9.0",
+ "@babel/types": "^7.9.0",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.1",
+ "json5": "^2.1.2",
+ "lodash": "^4.17.13",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz",
+ "integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+ "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz",
+ "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.12.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz",
+ "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.7"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.12.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz",
+ "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.5"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz",
+ "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.12.1",
+ "@babel/helper-replace-supers": "^7.12.1",
+ "@babel/helper-simple-access": "^7.12.1",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.12.1",
+ "@babel/types": "^7.12.1",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz",
+ "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.10"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.12.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz",
+ "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.12.1",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/traverse": "^7.12.5",
+ "@babel/types": "^7.12.5"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz",
+ "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.12.1"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
+ "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.12.5",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz",
+ "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.12.5",
+ "@babel/types": "^7.12.5"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+ "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz",
+ "integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.12.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz",
+ "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/parser": "^7.12.7",
+ "@babel/types": "^7.12.7"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz",
+ "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/generator": "^7.12.10",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/parser": "^7.12.10",
+ "@babel/types": "^7.12.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/types": {
+ "version": "7.12.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz",
+ "integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "is-core-module": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
+ "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+ "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
+ "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.1.0",
+ "path-parse": "^1.0.6"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ }
+ }
+}
diff --git a/examples/placeholders/package.json b/examples/placeholders/package.json
new file mode 100644
index 00000000..09573462
--- /dev/null
+++ b/examples/placeholders/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "example-placeholders",
+ "description": "Fills placeholder values",
+ "private": true,
+ "scripts": {
+ "cy:open": "../../node_modules/.bin/cypress open",
+ "cy:run": "../../node_modules/.bin/cypress run",
+ "report": "../../node_modules/.bin/nyc report",
+ "clean": "rm -rf .nyc_output || true",
+ "test:a": "../../node_modules/.bin/cypress run --spec cypress/integration/spec-a.js",
+ "test:b": "../../node_modules/.bin/cypress run --spec cypress/integration/spec-b.js"
+ },
+ "nyc": {
+ "all": true,
+ "include": "src/*.js"
+ },
+ "devDependencies": {
+ "@babel/core": "7.9.0"
+ }
+}
diff --git a/examples/placeholders/src/a.js b/examples/placeholders/src/a.js
new file mode 100644
index 00000000..4127294f
--- /dev/null
+++ b/examples/placeholders/src/a.js
@@ -0,0 +1,5 @@
+export const myFunc = () => {
+ const a = 10
+ const b = 20
+ return a + b
+}
diff --git a/examples/placeholders/src/b.js b/examples/placeholders/src/b.js
new file mode 100644
index 00000000..ff625dbc
--- /dev/null
+++ b/examples/placeholders/src/b.js
@@ -0,0 +1,4 @@
+export const anotherFunction = () => {
+ const hello = 'hello'
+ return hello.split('').reverse().join('')
+}
diff --git a/task-utils.js b/task-utils.js
index 81cdd039..cd4360c1 100644
--- a/task-utils.js
+++ b/task-utils.js
@@ -9,7 +9,11 @@ const debug = require('debug')('code-coverage')
const chalk = require('chalk')
const globby = require('globby')
const yaml = require('js-yaml')
-const { combineNycOptions, defaultNycOptions } = require('./common-utils')
+const {
+ combineNycOptions,
+ defaultNycOptions,
+ fileCoveragePlaceholder
+} = require('./common-utils')
function readNycOptions(workingDirectory) {
const pkgFilename = join(workingDirectory, 'package.json')
@@ -363,15 +367,8 @@ function includeAllFiles(nycFilename, nycOptions) {
debug('adding empty coverage for file %s', fullPath)
changed = true
// insert placeholder object for now
- nycCoverage[fullPath] = {
- path: fullPath,
- statementMap: {},
- fnMap: {},
- branchMap: {},
- s: {},
- f: {},
- b: {}
- }
+ const placeholder = fileCoveragePlaceholder(fullPath)
+ nycCoverage[fullPath] = placeholder
})
if (changed) {
diff --git a/task.js b/task.js
index 3e4d3fc8..d420f9a5 100644
--- a/task.js
+++ b/task.js
@@ -12,6 +12,7 @@ const {
includeAllFiles
} = require('./task-utils')
const { fixSourcePaths } = require('./support-utils')
+const { removePlaceholders } = require('./common-utils')
const debug = require('debug')('code-coverage')
@@ -146,10 +147,19 @@ const tasks = {
debug('parsed sent coverage')
fixSourcePaths(coverage)
- const previous = existsSync(nycFilename)
+
+ const previousCoverage = existsSync(nycFilename)
? JSON.parse(readFileSync(nycFilename, 'utf8'))
- : istanbul.createCoverageMap({})
- const coverageMap = istanbul.createCoverageMap(previous)
+ : {}
+
+ // previous code coverage object might have placeholder entries
+ // for files that we have not seen yet,
+ // but the user expects to include in the coverage report
+ // the merge function messes up, so we should remove any placeholder entries
+ // and re-insert them again when creating the report
+ removePlaceholders(previousCoverage)
+
+ const coverageMap = istanbul.createCoverageMap(previousCoverage)
coverageMap.merge(coverage)
saveCoverage(coverageMap)
debug('wrote coverage file %s', nycFilename)