diff --git a/.circleci/config.yml b/.circleci/config.yml
index 094d3bd4d..79c9e9605 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -48,6 +48,13 @@ jobs:
- run:
name: Install deps for code coverage
command: npm ci
+ - run:
+ name: Build project
+ command: npm run build
+ - run:
+ name: Install test app deps
+ command: npm i
+ working_directory: test-apps/all-files
- cypress/run-tests:
# no-workspace: true
start-command: npm run start:windows --prefix test-apps/all-files
@@ -68,6 +75,18 @@ jobs:
command: npm i -D check-code-coverage && npm run coverage:check-files
working_directory: test-apps/all-files
+ build-dist:
+ docker:
+ - image: cimg/node:22.14.0
+ steps:
+ - attach_workspace:
+ at: ~/
+ - run: npm run build
+ - persist_to_workspace:
+ paths:
+ - project/dist
+ root: ~/
+
publish:
description: Publishes the new version of the plugin to NPM
docker:
@@ -85,36 +104,36 @@ jobs:
at: ~/
- run: npm run semantic-release
- cyrun:
+ verify-test-apps:
docker:
- image: cypress/base:22.14.0
parameters:
- jobname:
+ app:
type: string
steps:
- attach_workspace:
at: ~/
- run:
- working_directory: test-apps/<< parameters.jobname >>
+ working_directory: test-apps/<< parameters.app >>
command: npm i
- run:
command: npm run test
- working_directory: test-apps/<< parameters.jobname >>
+ working_directory: test-apps/<< parameters.app >>
- store_artifacts:
- path: test-apps/<< parameters.jobname >>/coverage
+ path: test-apps/<< parameters.app >>/coverage
- run:
name: Verify Code Coverage
command: npm run coverage:verify
- working_directory: test-apps/<< parameters.jobname >>
+ working_directory: test-apps/<< parameters.app >>
- run:
name: Check code coverage files 📈
# 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: npm run coverage:check-files
- working_directory: test-apps/<< parameters.jobname >>
+ working_directory: test-apps/<< parameters.app >>
- test-code-coverage-plugin:
+ unit-test:
docker:
- image: cypress/base:24.11.0
steps:
@@ -132,23 +151,25 @@ workflows:
- lint:
requires:
- install_and_persist
-
- - test-code-coverage-plugin:
+ - unit-test:
requires:
- install_and_persist
-
- - cyrun:
- name: test-<< matrix.jobname>>
+ - build-dist:
requires:
- - install_and_persist
+ - lint
+ - unit-test
+ - verify-test-apps:
+ requires:
+ - build-dist
matrix:
parameters:
- jobname:
+ app:
- all-files
- backend
- batch-send-coverage
- before-all-visit
- before-each-visit
+ - esm-example
- exclude-files
- frontend
- fullstack
@@ -160,7 +181,9 @@ workflows:
- unit-tests-js
- use-webpack
- redirect
- - windows_test
+ - windows_test:
+ requires:
+ - build-dist
- publish:
context: org-npm-credentials
filters:
@@ -171,22 +194,5 @@ workflows:
- next
- dev
requires:
- - lint
- - test-code-coverage-plugin
- - test-all-files
- - test-backend
- - test-batch-send-coverage
- - test-before-all-visit
- - test-before-each-visit
- - test-exclude-files
- - test-frontend
- - test-fullstack
- - test-multiple-backends
- - test-one-spec
- - test-same-folder
- - test-support-files
- - test-ts-example
- - test-unit-tests-js
- - test-use-webpack
- - test-redirect
+ - verify-test-apps
- windows_test
diff --git a/README.md b/README.md
index 50bcd8937..dd8c06469 100644
--- a/README.md
+++ b/README.md
@@ -432,6 +432,7 @@ Full examples we use for testing in this repository:
- [test-apps/before-each-visit](test-apps/before-each-visit) checks if code coverage correctly keeps track of code when doing `cy.visit` before each test
- [test-apps/one-spec](test-apps/one-spec) confirms that coverage is collected and filtered correctly if the user only executes a single Cypress test
- [test-apps/ts-example](test-apps/ts-example) uses Babel + Parcel to instrument and serve TypeScript file
+- [test-apps/esm-example](test-apps/esm-example) demonstrates using ES module syntax (`import`/`export`) with the plugin
- [test-apps/use-webpack](test-apps/use-webpack) shows Webpack build with source maps and Babel
- [test-apps/unit-tests-js](test-apps/unit-tests-js) runs just the unit tests and reports code coverage (JavaScript source code)
- [test-apps/unit-tests-ts](test-apps/ts-example) runs just the unit tests and reports code coverage (TypeScript source code)
diff --git a/lib/common-utils.js b/lib/common-utils.ts
similarity index 52%
rename from lib/common-utils.js
rename to lib/common-utils.ts
index 573f7adab..0deb6164d 100644
--- a/lib/common-utils.js
+++ b/lib/common-utils.ts
@@ -1,13 +1,23 @@
-// @ts-check
-function stringToArray(prop, obj) {
+///
+
+export interface NycOptions {
+ 'report-dir'?: string
+ reporter?: string | string[]
+ extension?: string | string[]
+ exclude?: string | string[]
+ excludeAfterRemap?: boolean
+ all?: boolean
+ include?: string | string[]
+ [key: string]: unknown
+}
+
+function stringToArray(prop: string, obj: Record): void {
if (typeof obj[prop] === 'string') {
obj[prop] = [obj[prop]]
}
-
- return obj
}
-function combineNycOptions(...options) {
+export function combineNycOptions(...options: NycOptions[]): NycOptions {
// last option wins
const nycOptions = Object.assign({}, ...options)
@@ -19,20 +29,32 @@ function combineNycOptions(...options) {
return nycOptions
}
-const defaultNycOptions = {
+export const defaultNycOptions: NycOptions = {
'report-dir': './coverage',
reporter: ['lcov', 'clover', 'json', 'json-summary'],
extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'],
excludeAfterRemap: false
}
+export interface FileCoveragePlaceholder {
+ path: string
+ statementMap: Record
+ fnMap: Record
+ branchMap: Record
+ s: Record
+ f: Record
+ b: Record
+}
+
/**
* 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
+ * @param fullPath Filename
*/
-const fileCoveragePlaceholder = (fullPath) => {
+export function fileCoveragePlaceholder(
+ fullPath: string
+): FileCoveragePlaceholder {
return {
path: fullPath,
statementMap: {},
@@ -44,7 +66,9 @@ const fileCoveragePlaceholder = (fullPath) => {
}
}
-const isPlaceholder = (entry) => {
+function isPlaceholder(
+ entry: FileCoveragePlaceholder | { hash?: string }
+): boolean {
// when the file has been instrumented, its entry has "hash" property
return !('hash' in entry)
}
@@ -53,17 +77,12 @@ const isPlaceholder = (entry) => {
* Given a coverage object with potential placeholder entries
* inserted instead of covered files, removes them. Modifies the object in place
*/
-const removePlaceholders = (coverage) => {
+export function removePlaceholders(
+ coverage: Record
+): void {
Object.keys(coverage).forEach((key) => {
if (isPlaceholder(coverage[key])) {
delete coverage[key]
}
})
}
-
-module.exports = {
- combineNycOptions,
- defaultNycOptions,
- fileCoveragePlaceholder,
- removePlaceholders
-}
diff --git a/lib/plugins.js b/lib/plugins.ts
similarity index 53%
rename from lib/plugins.js
rename to lib/plugins.ts
index 80e5e67b0..760fdd393 100644
--- a/lib/plugins.js
+++ b/lib/plugins.ts
@@ -1,3 +1,7 @@
+///
+///
+import registerCodeCoverageTasks = require('./task')
+
// common Cypress plugin file you can point at to have the
// code coverage tasks registered correctly. From your "cypress.json" file
// {
@@ -5,8 +9,11 @@
// "supportFile": "@cypress/code-coverage/support"
// }
//
-module.exports = (on, config) => {
- require('./task')(on, config)
+export = function plugins(
+ on: Cypress.PluginEvents,
+ config: Cypress.PluginConfigOptions
+): Cypress.PluginConfigOptions {
+ registerCodeCoverageTasks(on, config)
// IMPORTANT to return the config object with any changes
return config
}
diff --git a/lib/support-utils.js b/lib/support-utils.js
deleted file mode 100644
index c4fbe1c52..000000000
--- a/lib/support-utils.js
+++ /dev/null
@@ -1,171 +0,0 @@
-// @ts-check
-// helper functions that are safe to use in the browser
-// from support.js file - no file system access
-
-/** excludes files that shouldn't be in code coverage report */
-const filterFilesFromCoverage = (
- totalCoverage,
- config = Cypress.config,
- expose = Cypress.expose,
- spec = Cypress.spec
-) => {
- const withoutSpecs = filterSpecsFromCoverage(
- totalCoverage,
- config,
- expose,
- spec
- )
- const appCoverageOnly = filterSupportFilesFromCoverage(withoutSpecs, config)
- return appCoverageOnly
-}
-
-/**
- * remove coverage for the spec files themselves,
- * only keep "external" application source file coverage
- */
-const filterSpecsFromCoverage = (
- totalCoverage,
- config = Cypress.config,
- expose = Cypress.expose,
- spec = Cypress.spec
-) => {
- const testFilePatterns = getCypressExcludePatterns(config, expose, spec)
-
- const isTestFile = (_, filePath) => {
- const workingDir = spec.absolute.replace(spec.relative, '')
- const filename = filePath.replace(workingDir, '')
- const matchedPattern = testFilePatterns.some((specPattern) =>
- Cypress.minimatch(filename, specPattern, { debug: false })
- )
- const matchedEndOfPath = testFilePatterns.some((specPattern) =>
- filename.endsWith(specPattern)
- )
- return matchedPattern || matchedEndOfPath
- }
-
- const coverage = Cypress._.omitBy(totalCoverage, isTestFile)
- return coverage
-}
-
-/**
- * Reads Cypress config and exclude patterns and combines them into one array
- * @param {*} config
- * @param {*} expose
- * @returns string[]
- */
-function getCypressExcludePatterns(config, expose, spec) {
- let testFilePatterns = []
-
- const testFilePattern = config('specPattern') || config('testFiles')
- const excludePattern = expose().codeCoverage && expose().codeCoverage.exclude
-
- if (Array.isArray(testFilePattern)) {
- testFilePatterns = testFilePattern
- } else {
- testFilePatterns = [testFilePattern]
- }
-
- // combine test files pattern and exclude patterns into single exclude pattern
- if (Array.isArray(excludePattern)) {
- testFilePatterns = [...testFilePatterns, ...excludePattern]
- } else if (excludePattern) {
- testFilePatterns = [...testFilePatterns, excludePattern]
- }
-
- // Cypress {
- if (pattern === '**/*.*') {
- testFilePatterns[idx] = `${integrationFolder}/${pattern}`.replace(
- '//',
- '/'
- )
- }
- })
- }
-
- return testFilePatterns
-}
-
-/**
- * Removes support file from the coverage object.
- * If there are more files loaded from support folder, also removes them
- */
-const filterSupportFilesFromCoverage = (
- totalCoverage,
- config = Cypress.config
-) => {
- const integrationFolder = config('integrationFolder')
-
- /**
- * Cypress v10 doesn't have an integrationFolder config value any more, so we nope out here if its undefined.
- * Instead, we rely on the new exclude option logic done in the getCypressExcludePatterns function.
- */
- if (!integrationFolder) {
- return totalCoverage
- }
-
- const supportFile = config('supportFile')
-
- /** @type {string} Cypress run-time config has the support folder string */
- // @ts-ignore
- const supportFolder = config('supportFolder')
-
- const isSupportFile = (filename) => filename === supportFile
-
- let coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) =>
- isSupportFile(filename)
- )
-
- // check the edge case
- // if we have files from support folder AND the support folder is not same
- // as the integration, or its prefix (this might remove all app source files)
- // then remove all files from the support folder
- if (!integrationFolder.startsWith(supportFolder)) {
- // remove all covered files from support folder
- coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) =>
- filename.startsWith(supportFolder)
- )
- }
- return coverage
-}
-
-/**
- * Replace source-map's path by the corresponding absolute file path
- * (coverage report wouldn't work with source-map path being relative
- * or containing Webpack loaders and query parameters)
- */
-function fixSourcePaths(coverage) {
- Object.values(coverage).forEach((file) => {
- const { path: absolutePath, inputSourceMap } = file
- if (!inputSourceMap) return
- const fileName = /([^\/\\]+)$/.exec(absolutePath)[1]
- if (!fileName) return
-
- if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = ''
- inputSourceMap.sources = inputSourceMap.sources.map((source) =>
- source.includes(fileName) ? absolutePath : source
- )
- })
-}
-
-/**
- * Validates and returns the configured batch size for
- * sending coverage to the backend
- */
-function getSendCoverageBatchSize() {
- const batchSize = Cypress.expose('sendCoverageBatchSize')
- const parsedBatchSize = parseInt(batchSize)
- const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0
- return isValid ? parsedBatchSize : null
-}
-
-module.exports = {
- fixSourcePaths,
- filterFilesFromCoverage,
- getSendCoverageBatchSize
-}
diff --git a/lib/support-utils.ts b/lib/support-utils.ts
new file mode 100644
index 000000000..e556605e3
--- /dev/null
+++ b/lib/support-utils.ts
@@ -0,0 +1,155 @@
+///
+///
+
+// helper functions that are safe to use in the browser
+// from support.js file - no file system access
+
+export interface CoverageObject {
+ [key: string]: {
+ path?: string
+ inputSourceMap?: {
+ sources: string[]
+ sourceRoot?: string
+ }
+ [key: string]: unknown
+ }
+}
+
+/** excludes files that shouldn't be in code coverage report */
+export function filterFilesFromCoverage(
+ totalCoverage: CoverageObject,
+ config: typeof Cypress.config = Cypress.config,
+ expose: typeof Cypress.expose = Cypress.expose,
+ spec: typeof Cypress.spec = Cypress.spec
+): CoverageObject {
+ const withoutSpecs = filterSpecsFromCoverage(
+ totalCoverage,
+ config,
+ expose,
+ spec
+ )
+ const appCoverageOnly = filterSupportFilesFromCoverage(withoutSpecs, config)
+ return appCoverageOnly
+}
+
+/**
+ * remove coverage for the spec files themselves,
+ * only keep "external" application source file coverage
+ */
+function filterSpecsFromCoverage(
+ totalCoverage: CoverageObject,
+ config: typeof Cypress.config = Cypress.config,
+ expose: typeof Cypress.expose = Cypress.expose,
+ spec: typeof Cypress.spec = Cypress.spec
+): CoverageObject {
+ const testFilePatterns = getCypressExcludePatterns(config, expose)
+
+ const isTestFile = (_: unknown, filePath: string): boolean => {
+ const workingDir = spec.absolute.replace(spec.relative, '')
+ const filename = filePath.replace(workingDir, '')
+ const matchedPattern = testFilePatterns.some((specPattern) =>
+ Cypress.minimatch(filename, specPattern, { debug: false })
+ )
+ const matchedEndOfPath = testFilePatterns.some((specPattern) =>
+ filename.endsWith(specPattern)
+ )
+ return matchedPattern || matchedEndOfPath
+ }
+
+ const coverage = Cypress._.omitBy(totalCoverage, isTestFile)
+ return coverage
+}
+
+/**
+ * Reads Cypress config and exclude patterns and combines them into one array
+ */
+function getCypressExcludePatterns(
+ config: typeof Cypress.config,
+ expose: typeof Cypress.expose
+): string[] {
+ const testFilePattern = config('specPattern')
+ const codeCoverageConfig = expose().codeCoverage as
+ | { exclude?: string | string[] }
+ | undefined
+ const excludePattern = codeCoverageConfig?.exclude
+
+ let testFilePatterns: string[] = []
+ if (Array.isArray(testFilePattern)) {
+ // Filter out any undefined values and ensure all are strings
+ for (const p of testFilePattern) {
+ if (p !== undefined && typeof p === 'string') {
+ testFilePatterns.push(p)
+ }
+ }
+ } else if (testFilePattern && typeof testFilePattern === 'string') {
+ testFilePatterns = [testFilePattern]
+ }
+
+ // combine test files pattern and exclude patterns into single exclude pattern
+ if (Array.isArray(excludePattern)) {
+ // Filter out any undefined values and ensure all are strings
+ for (const p of excludePattern) {
+ if (p !== undefined && typeof p === 'string') {
+ testFilePatterns.push(p)
+ }
+ }
+ } else if (typeof excludePattern === 'string') {
+ testFilePatterns.push(excludePattern)
+ }
+
+ return testFilePatterns
+}
+
+/**
+ * Removes support file from the coverage object.
+ * Support files are now handled via the exclude patterns in getCypressExcludePatterns.
+ */
+function filterSupportFilesFromCoverage(
+ totalCoverage: CoverageObject,
+ config: typeof Cypress.config = Cypress.config
+): CoverageObject {
+ const supportFile = config('supportFile') as string | undefined
+
+ if (!supportFile) {
+ return totalCoverage
+ }
+
+ const isSupportFile = (filename: string): boolean => filename === supportFile
+
+ const coverage = Cypress._.omitBy(totalCoverage, (_fileCoverage, filename) =>
+ isSupportFile(filename)
+ )
+
+ return coverage
+}
+
+/**
+ * Replace source-map's path by the corresponding absolute file path
+ * (coverage report wouldn't work with source-map path being relative
+ * or containing Webpack loaders and query parameters)
+ */
+export function fixSourcePaths(coverage: CoverageObject): void {
+ Object.values(coverage).forEach((file) => {
+ const { path: absolutePath, inputSourceMap } = file
+ if (!inputSourceMap || !absolutePath) return
+ const fileNameMatch = /([^\/\\]+)$/.exec(absolutePath)
+ if (!fileNameMatch) return
+ const fileName = fileNameMatch[1]
+
+ if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = ''
+ inputSourceMap.sources = inputSourceMap.sources.map((source) =>
+ source.includes(fileName) ? absolutePath : source
+ )
+ })
+}
+
+/**
+ * Validates and returns the configured batch size for
+ * sending coverage to the backend
+ */
+export function getSendCoverageBatchSize(): number | null {
+ const batchSize = Cypress.expose('sendCoverageBatchSize')
+ const parsedBatchSize = parseInt(String(batchSize), 10)
+ const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0
+ return isValid ? parsedBatchSize : null
+}
diff --git a/lib/support.js b/lib/support.ts
similarity index 82%
rename from lib/support.js
rename to lib/support.ts
index 7c27b217b..ef73a29e1 100644
--- a/lib/support.js
+++ b/lib/support.ts
@@ -1,23 +1,28 @@
///
-// @ts-check
-
-const dayjs = require('dayjs')
-var duration = require('dayjs/plugin/duration')
-const {
+///
+import type { CoverageObject } from './support-utils'
+import dayjs from 'dayjs'
+import duration from 'dayjs/plugin/duration'
+import {
filterFilesFromCoverage,
getSendCoverageBatchSize
-} = require('./support-utils')
+} from './support-utils'
dayjs.extend(duration)
+interface WindowCoverageObject {
+ coverage: unknown
+ pathname: string
+}
+
/**
* Sends collected code coverage object to the backend code
* via "cy.task".
*/
-const sendCoverage = (coverage, pathname = '/') => {
+function sendCoverage(coverage: unknown, pathname = '/'): void {
logMessage(`Saving code coverage for **${pathname}**`)
- const totalCoverage = filterFilesFromCoverage(coverage)
+ const totalCoverage = filterFilesFromCoverage(coverage as CoverageObject)
const batchSize = getSendCoverageBatchSize()
const keys = Object.keys(totalCoverage)
@@ -35,12 +40,15 @@ const sendCoverage = (coverage, pathname = '/') => {
* Sends collected code coverage object to the backend code
* in batches via "cy.task".
*/
-const sendBatchCoverage = (totalCoverage, batchSize) => {
+function sendBatchCoverage(
+ totalCoverage: Record,
+ batchSize: number
+): void {
const keys = Object.keys(totalCoverage)
for (let i = 0; i < keys.length; i += batchSize) {
const batchKeys = keys.slice(i, i + batchSize)
- const batchCoverage = {}
+ const batchCoverage: Record = {}
batchKeys.forEach((key) => {
batchCoverage[key] = totalCoverage[key]
@@ -55,19 +63,20 @@ const sendBatchCoverage = (totalCoverage, batchSize) => {
/**
* Consistently logs the given string to the Command Log
* so the user knows the log message is coming from this plugin.
- * @param {string} s Message to log.
+ * @param s Message to log.
*/
-const logMessage = (s) => {
+function logMessage(s: string): void {
cy.log(`${s} \`[@cypress/code-coverage]\``)
}
-const registerHooks = () => {
- let windowCoverageObjects
+function registerHooks(): void {
+ let windowCoverageObjects: WindowCoverageObject[] = []
- const hasE2ECoverage = () => Boolean(windowCoverageObjects.length)
+ const hasE2ECoverage = (): boolean => Boolean(windowCoverageObjects.length)
- // @ts-ignore
- const hasUnitTestCoverage = () => Boolean(window.__coverage__)
+ // @ts-ignore - __coverage__ is a global added by instrumentation
+ const hasUnitTestCoverage = (): boolean =>
+ Boolean((window as typeof window & { __coverage__?: unknown }).__coverage__)
before(() => {
// we need to reset the coverage when running
@@ -81,7 +90,7 @@ const registerHooks = () => {
cy.task(
'resetCoverage',
{
- // @ts-ignore
+ // @ts-ignore - isInteractive is a runtime property
isInteractive: Cypress.config('isInteractive')
},
{ log: false }
@@ -95,20 +104,25 @@ const registerHooks = () => {
// to let the user know the coverage has been collected
windowCoverageObjects = []
- const saveCoverageObject = (win) => {
+ const saveCoverageObject = (win: Window): void => {
// if the application code has been instrumented, then the app iframe "win.__coverage__" will be available,
// in addition, accessing win.__coverage__ can throw when testing cross-origin code,
// because we don't control the cross-origin code, we can safely return
- let applicationSourceCoverage
+ let applicationSourceCoverage: unknown
try {
// Note that we are purposefully not supporting the optional chaining syntax here to
// support a wide range of projects (some of which are not set up to support the optional
// chaining syntax due to current Cypress limitations). See:
// https://github.com/cypress-io/cypress/issues/20753
if (win) {
- applicationSourceCoverage = win.__coverage__
+ // @ts-ignore - __coverage__ is added by instrumentation
+ applicationSourceCoverage = (
+ win as typeof win & { __coverage__?: unknown }
+ ).__coverage__
}
- } catch {}
+ } catch {
+ // ignore cross-origin errors
+ }
if (!applicationSourceCoverage) {
return
@@ -174,9 +188,11 @@ const registerHooks = () => {
// there might be server-side code coverage information
// we should grab it once after all tests finish
- // @ts-ignore
- const baseUrl = Cypress.config('baseUrl') || cy.state('window').origin
- // @ts-ignore
+
+ const baseUrl =
+ // @ts-ignore - state is a runtime property
+ Cypress.config('baseUrl') || (cy.state('window') as Window).origin
+ // @ts-ignore - proxyUrl is a runtime property
const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl')
const expectFrontendCoverageOnly = Cypress._.get(
Cypress.expose('codeCoverage'),
@@ -199,7 +215,7 @@ const registerHooks = () => {
'url',
'/__coverage__'
)
- function captureCoverage(url, suffix = '') {
+ function captureCoverage(url: string, suffix = ''): void {
cy.request({
url,
log: false,
@@ -247,8 +263,10 @@ const registerHooks = () => {
// then we will have unit test coverage
// NOTE: spec iframe is NOT reset between the tests, so we can grab
// the coverage information only once after all tests have finished
- // @ts-ignore
- const unitTestCoverage = window.__coverage__
+ // @ts-ignore - __coverage__ is added by instrumentation
+ const unitTestCoverage = (
+ window as typeof window & { __coverage__?: unknown }
+ ).__coverage__
if (unitTestCoverage) {
sendCoverage(unitTestCoverage, 'unit')
}
diff --git a/lib/task-utils.js b/lib/task-utils.ts
similarity index 57%
rename from lib/task-utils.js
rename to lib/task-utils.ts
index dc413f1a2..9d7b4bd4f 100644
--- a/lib/task-utils.js
+++ b/lib/task-utils.ts
@@ -1,74 +1,96 @@
// helper functions to use from "task.js" plugins code
// that need access to the file system
-// @ts-check
-///
-const { readFileSync, writeFileSync, existsSync } = require('fs')
-const { isAbsolute, resolve, join } = require('path')
-const debug = require('debug')('code-coverage')
-const chalk = require('chalk')
-const tinyglobby = require('tinyglobby')
-const yaml = require('js-yaml')
-const {
+///
+import { readFileSync, writeFileSync, existsSync } from 'fs'
+import { isAbsolute, resolve, join } from 'path'
+import debug from 'debug'
+import chalk from 'chalk'
+import { globSync } from 'tinyglobby'
+import yaml from 'js-yaml'
+import {
combineNycOptions,
defaultNycOptions,
- fileCoveragePlaceholder
-} = require('./common-utils')
+ fileCoveragePlaceholder,
+ type NycOptions
+} from './common-utils'
+import type { CoverageMapData, FileCoverageData } from 'istanbul-lib-coverage'
+const log = debug('code-coverage')
+
+export interface CoverageEntry {
+ path: string
+ hash?: string
+ [key: string]: unknown
+}
+
+export interface CoverageMap {
+ [key: string]: CoverageEntry
+}
-function readNycOptions(workingDirectory) {
+export function readNycOptions(workingDirectory: string): NycOptions {
const pkgFilename = join(workingDirectory, 'package.json')
const pkg = existsSync(pkgFilename)
? JSON.parse(readFileSync(pkgFilename, 'utf8'))
: {}
- const pkgNycOptions = pkg.nyc || {}
+ const pkgNycOptions: NycOptions = pkg.nyc || {}
const nycrcFilename = join(workingDirectory, '.nycrc')
- const nycrc = existsSync(nycrcFilename)
+ const nycrc: NycOptions = existsSync(nycrcFilename)
? JSON.parse(readFileSync(nycrcFilename, 'utf8'))
: {}
const nycrcJsonFilename = join(workingDirectory, '.nycrc.json')
- const nycrcJson = existsSync(nycrcJsonFilename)
+ const nycrcJson: NycOptions = existsSync(nycrcJsonFilename)
? JSON.parse(readFileSync(nycrcJsonFilename, 'utf8'))
: {}
const nycrcYamlFilename = join(workingDirectory, '.nycrc.yaml')
- let nycrcYaml = {}
+ let nycrcYaml: NycOptions = {}
if (existsSync(nycrcYamlFilename)) {
try {
- nycrcYaml = yaml.safeLoad(readFileSync(nycrcYamlFilename, 'utf8'))
+ nycrcYaml = yaml.load(
+ readFileSync(nycrcYamlFilename, 'utf8')
+ ) as NycOptions
} catch (error) {
- throw new Error(`Failed to load .nycrc.yaml: ${error.message}`)
+ throw new Error(
+ `Failed to load .nycrc.yaml: ${error instanceof Error ? error.message : String(error)}`
+ )
}
}
const nycrcYmlFilename = join(workingDirectory, '.nycrc.yml')
- let nycrcYml = {}
+ let nycrcYml: NycOptions = {}
if (existsSync(nycrcYmlFilename)) {
try {
- nycrcYml = yaml.safeLoad(readFileSync(nycrcYmlFilename, 'utf8'))
+ nycrcYml = yaml.load(readFileSync(nycrcYmlFilename, 'utf8')) as NycOptions
} catch (error) {
- throw new Error(`Failed to load .nycrc.yml: ${error.message}`)
+ throw new Error(
+ `Failed to load .nycrc.yml: ${error instanceof Error ? error.message : String(error)}`
+ )
}
}
const nycConfigFilename = join(workingDirectory, 'nyc.config.js')
- let nycConfig = {}
+ let nycConfig: NycOptions = {}
if (existsSync(nycConfigFilename)) {
try {
- nycConfig = require(nycConfigFilename)
+ nycConfig = require(nycConfigFilename) as NycOptions
} catch (error) {
- throw new Error(`Failed to load nyc.config.js: ${error.message}`)
+ throw new Error(
+ `Failed to load nyc.config.js: ${error instanceof Error ? error.message : String(error)}`
+ )
}
}
const nycConfigCommonJsFilename = join(workingDirectory, 'nyc.config.cjs')
- let nycConfigCommonJs = {}
+ let nycConfigCommonJs: NycOptions = {}
if (existsSync(nycConfigCommonJsFilename)) {
try {
- nycConfigCommonJs = require(nycConfigCommonJsFilename)
+ nycConfigCommonJs = require(nycConfigCommonJsFilename) as NycOptions
} catch (error) {
- throw new Error(`Failed to load nyc.config.cjs: ${error.message}`)
+ throw new Error(
+ `Failed to load nyc.config.cjs: ${error instanceof Error ? error.message : String(error)}`
+ )
}
}
@@ -82,38 +104,36 @@ function readNycOptions(workingDirectory) {
nycConfigCommonJs,
pkgNycOptions
)
- debug('combined NYC options %o', nycOptions)
+ log('combined NYC options %o', nycOptions)
return nycOptions
}
-function checkAllPathsNotFound(nycFilename) {
- const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
+export function checkAllPathsNotFound(
+ nycFilename: string
+): boolean | undefined {
+ const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8'))
const coverageKeys = Object.keys(nycCoverage)
if (!coverageKeys.length) {
- debug('⚠️ file %s has no coverage information', nycFilename)
+ log('⚠️ file %s has no coverage information', nycFilename)
return
}
- const allFilesAreMissing = coverageKeys.every((key, k) => {
+ const allFilesAreMissing = coverageKeys.every((key) => {
const coverage = nycCoverage[key]
return !existsSync(coverage.path)
})
- debug(
- 'in file %s all files are not found? %o',
- nycFilename,
- allFilesAreMissing
- )
+ log('in file %s all files are not found? %o', nycFilename, allFilesAreMissing)
return allFilesAreMissing
}
/**
* A small debug utility to inspect paths saved in NYC output JSON file
*/
-function showNycInfo(nycFilename) {
- const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
+export function showNycInfo(nycFilename: string): void {
+ const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8'))
const coverageKeys = Object.keys(nycCoverage)
if (!coverageKeys.length) {
@@ -129,7 +149,7 @@ function showNycInfo(nycFilename) {
)
return
}
- debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length)
+ log('NYC file %s has %d key(s)', nycFilename, coverageKeys.length)
const maxPrintKeys = 3
const showKeys = coverageKeys.slice(0, maxPrintKeys)
@@ -140,7 +160,7 @@ function showNycInfo(nycFilename) {
// printing a few found keys and file paths from the coverage file
// will make debugging any problems much much easier
if (k < maxPrintKeys) {
- debug('%d key %s file path %s', k + 1, key, coverage.path)
+ log('%d key %s file path %s', k + 1, key, coverage.path)
}
})
}
@@ -150,29 +170,29 @@ function showNycInfo(nycFilename) {
* and if the file is relative, and exists, changes its path to
* be absolute.
*/
-function resolveRelativePaths(nycFilename) {
- const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
+export function resolveRelativePaths(nycFilename: string): void {
+ const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8'))
const coverageKeys = Object.keys(nycCoverage)
if (!coverageKeys.length) {
- debug('⚠️ file %s has no coverage information', nycFilename)
+ log('⚠️ file %s has no coverage information', nycFilename)
return
}
- debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length)
+ log('NYC file %s has %d key(s)', nycFilename, coverageKeys.length)
- let changed
+ let changed = false
- coverageKeys.forEach((key, k) => {
+ coverageKeys.forEach((key) => {
const coverage = nycCoverage[key]
if (!coverage.path) {
- debug('key %s does not have path', key)
+ log('key %s does not have path', key)
return
}
if (!isAbsolute(coverage.path)) {
if (existsSync(coverage.path)) {
- debug('resolving path %s', coverage.path)
+ log('resolving path %s', coverage.path)
coverage.path = resolve(coverage.path)
changed = true
}
@@ -181,13 +201,13 @@ function resolveRelativePaths(nycFilename) {
// path is absolute, let's check if it exists
if (!existsSync(coverage.path)) {
- debug('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash)
+ log('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash)
}
})
if (changed) {
- debug('resolveRelativePaths saving updated file %s', nycFilename)
- debug('there are %d keys in the file', coverageKeys.length)
+ log('resolveRelativePaths saving updated file %s', nycFilename)
+ log('there are %d keys in the file', coverageKeys.length)
writeFileSync(
nycFilename,
JSON.stringify(nycCoverage, null, 2) + '\n',
@@ -197,12 +217,12 @@ function resolveRelativePaths(nycFilename) {
}
/**
- * @param {string[]} filepaths
- * @returns {string | undefined} common prefix that corresponds to current folder
+ * @param filepaths
+ * @returns common prefix that corresponds to current folder
*/
-function findCommonRoot(filepaths) {
+export function findCommonRoot(filepaths: string[]): string | undefined {
if (!filepaths.length) {
- debug('cannot find common root without any files')
+ log('cannot find common root without any files')
return
}
@@ -210,19 +230,19 @@ function findCommonRoot(filepaths) {
const splitParts = filepaths.map((name) => name.split('/'))
const lengths = splitParts.map((arr) => arr.length)
const shortestLength = Math.min.apply(null, lengths)
- debug('shorted file path has %d parts', shortestLength)
+ log('shorted file path has %d parts', shortestLength)
const cwd = process.cwd()
- let commonPrefix = []
- let foundCurrentFolder
+ const commonPrefix: string[] = []
+ let foundCurrentFolder: string | undefined
for (let k = 0; k < shortestLength; k += 1) {
const part = splitParts[0][k]
const prefix = commonPrefix.concat(part).join('/')
- debug('testing prefix %o', prefix)
+ log('testing prefix %o', prefix)
const allFilesStart = filepaths.every((name) => name.startsWith(prefix))
if (!allFilesStart) {
- debug('stopped at non-common prefix %s', prefix)
+ log('stopped at non-common prefix %s', prefix)
break
}
@@ -231,13 +251,13 @@ function findCommonRoot(filepaths) {
const removedPrefixNames = filepaths.map((filepath) =>
filepath.slice(prefix.length)
)
- debug('removedPrefix %o', removedPrefixNames)
+ log('removedPrefix %o', removedPrefixNames)
const foundAllPaths = removedPrefixNames.every((filepath) =>
existsSync(join(cwd, filepath))
)
- debug('all files found at %s? %o', prefix, foundAllPaths)
+ log('all files found at %s? %o', prefix, foundAllPaths)
if (foundAllPaths) {
- debug('found prefix that matches current folder: %s', prefix)
+ log('found prefix that matches current folder: %s', prefix)
foundCurrentFolder = prefix
break
}
@@ -246,23 +266,23 @@ function findCommonRoot(filepaths) {
return foundCurrentFolder
}
-function tryFindingLocalFiles(nycFilename) {
- const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
+export function tryFindingLocalFiles(nycFilename: string): void {
+ const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8'))
const coverageKeys = Object.keys(nycCoverage)
const filenames = coverageKeys.map((key) => nycCoverage[key].path)
const commonFolder = findCommonRoot(filenames)
if (!commonFolder) {
- debug('could not find common folder %s', commonFolder)
+ log('could not find common folder %s', commonFolder)
return
}
const cwd = process.cwd()
- debug(
+ log(
'found common folder %s that matches current working directory %s',
commonFolder,
cwd
)
const length = commonFolder.length
- let changed
+ let changed = false
coverageKeys.forEach((key) => {
const from = nycCoverage[key].path
@@ -270,14 +290,14 @@ function tryFindingLocalFiles(nycFilename) {
const to = join(cwd, from.slice(length))
// ? Do we need to replace the "key" in the coverage object or can we just replace the "path"?
nycCoverage[key].path = to
- debug('replaced %s -> %s', from, to)
+ log('replaced %s -> %s', from, to)
changed = true
}
})
if (changed) {
- debug('tryFindingLocalFiles saving updated file %s', nycFilename)
- debug('there are %d keys in the file', coverageKeys.length)
+ log('tryFindingLocalFiles saving updated file %s', nycFilename)
+ log('there are %d keys in the file', coverageKeys.length)
writeFileSync(
nycFilename,
JSON.stringify(nycCoverage, null, 2) + '\n',
@@ -290,8 +310,8 @@ function tryFindingLocalFiles(nycFilename) {
* Tries to find source files to be included in the final coverage report
* using NYC options: extension list, include and exclude.
*/
-function findSourceFiles(nycOptions) {
- debug('include all files options: %o', {
+function findSourceFiles(nycOptions: NycOptions): string[] {
+ log('include all files options: %o', {
all: nycOptions.all,
include: nycOptions.include,
exclude: nycOptions.exclude,
@@ -306,13 +326,13 @@ function findSourceFiles(nycOptions) {
return []
}
- let patterns = []
+ const patterns: string[] = []
if (Array.isArray(nycOptions.include)) {
- patterns = patterns.concat(nycOptions.include)
+ patterns.push(...nycOptions.include)
} else if (typeof nycOptions.include === 'string') {
patterns.push(nycOptions.include)
} else {
- debug('using default list of extensions')
+ log('using default list of extensions')
nycOptions.extension.forEach((extension) => {
patterns.push('**/*' + extension)
})
@@ -320,7 +340,7 @@ function findSourceFiles(nycOptions) {
if (Array.isArray(nycOptions.exclude)) {
const negated = nycOptions.exclude.map((s) => '!' + s)
- patterns = patterns.concat(negated)
+ patterns.push(...negated)
} else if (typeof nycOptions.exclude === 'string') {
patterns.push('!' + nycOptions.exclude)
}
@@ -328,11 +348,12 @@ function findSourceFiles(nycOptions) {
// https://github.com/istanbuljs/nyc#including-files-within-node_modules
patterns.push('!**/node_modules/**')
- debug('searching files to include using patterns %o', patterns)
+ log('searching files to include using patterns %o', patterns)
- const allFiles = tinyglobby.globSync(patterns, { absolute: true })
+ const allFiles = globSync(patterns, { absolute: true })
return allFiles
}
+
/**
* If the website or unit tests did not load ALL files we need to
* include, then we should include the missing files ourselves
@@ -340,51 +361,57 @@ function findSourceFiles(nycOptions) {
*
* @see https://github.com/cypress-io/code-coverage/issues/207
*/
-function includeAllFiles(nycFilename, nycOptions) {
+export function includeAllFiles(
+ nycFilename: string,
+ nycOptions: NycOptions
+): void {
if (!nycOptions.all) {
- debug('NYC "all" option is not set, skipping including all files')
+ log('NYC "all" option is not set, skipping including all files')
return
}
const allFiles = findSourceFiles(nycOptions)
- if (debug.enabled) {
- debug('found %d file(s)', allFiles.length)
+ if (log.enabled) {
+ log('found %d file(s)', allFiles.length)
console.error(allFiles.join('\n'))
}
if (!allFiles.length) {
- debug('no files found, hoping for the best')
+ log('no files found, hoping for the best')
return
}
- const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8'))
+ const nycCoverage: CoverageMapData = JSON.parse(
+ readFileSync(nycFilename, 'utf8')
+ )
const coverageKeys = Object.keys(nycCoverage)
const coveredPaths = coverageKeys.map((key) =>
nycCoverage[key].path.replace(/\\/g, '/')
)
- debug('coverage has %d record(s)', coveredPaths.length)
+ log('coverage has %d record(s)', coveredPaths.length)
// report on first couple of entries
- if (debug.enabled) {
+ if (log.enabled) {
console.error('coverage has the following first paths')
console.error(coveredPaths.slice(0, 4).join('\n'))
}
- let changed
+ let changed = false
allFiles.forEach((fullPath) => {
if (coveredPaths.includes(fullPath)) {
// all good, this file exists in coverage object
return
}
- debug('adding empty coverage for file %s', fullPath)
+ log('adding empty coverage for file %s', fullPath)
changed = true
// insert placeholder object for now
const placeholder = fileCoveragePlaceholder(fullPath)
- nycCoverage[fullPath] = placeholder
+
+ nycCoverage[fullPath] = placeholder as FileCoverageData
})
if (changed) {
- debug('includeAllFiles saving updated file %s', nycFilename)
- debug('there are %d keys in the file', Object.keys(nycCoverage).length)
+ log('includeAllFiles saving updated file %s', nycFilename)
+ log('there are %d keys in the file', Object.keys(nycCoverage).length)
writeFileSync(
nycFilename,
@@ -393,12 +420,3 @@ function includeAllFiles(nycFilename, nycOptions) {
)
}
}
-
-module.exports = {
- showNycInfo,
- resolveRelativePaths,
- checkAllPathsNotFound,
- tryFindingLocalFiles,
- readNycOptions,
- includeAllFiles
-}
diff --git a/lib/task.d.ts b/lib/task.d.ts
deleted file mode 100644
index 8ff8ef8f6..000000000
--- a/lib/task.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-///
-
-export default function registerCodeCoverageTasks(
- on: Cypress.PluginEvents,
- config: Cypress.PluginConfigOptions,
-): void;
diff --git a/lib/task.js b/lib/task.ts
similarity index 67%
rename from lib/task.js
rename to lib/task.ts
index e82b39ee6..3f080d991 100644
--- a/lib/task.js
+++ b/lib/task.ts
@@ -1,19 +1,23 @@
-// @ts-check
-const istanbul = require('istanbul-lib-coverage')
-const { join, resolve } = require('path')
-const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs')
-const execa = require('execa')
-const {
+///
+import {
+ createCoverageMap,
+ CoverageMap as IstanbulCoverageMap
+} from 'istanbul-lib-coverage'
+import { join, resolve } from 'path'
+import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
+import execa from 'execa'
+import {
showNycInfo,
resolveRelativePaths,
checkAllPathsNotFound,
tryFindingLocalFiles,
readNycOptions,
includeAllFiles
-} = require('./task-utils')
-const { fixSourcePaths } = require('./support-utils')
+} from './task-utils'
+import { fixSourcePaths } from './support-utils'
+import debug from 'debug'
-const debug = require('debug')('code-coverage')
+const log = debug('code-coverage')
// these are standard folder and file names used by NYC tools
const processWorkingDirectory = process.cwd()
@@ -40,7 +44,9 @@ const nycReportOptions = (function getNycOption() {
}
if (nycReportOptions['temp-dir']) {
- nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir'])
+ nycReportOptions['temp-dir'] = resolve(
+ nycReportOptions['temp-dir'] as string
+ )
} else {
nycReportOptions['temp-dir'] = join(processWorkingDirectory, '.nyc_output')
}
@@ -48,7 +54,9 @@ const nycReportOptions = (function getNycOption() {
nycReportOptions.tempDir = nycReportOptions['temp-dir']
if (nycReportOptions['report-dir']) {
- nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir'])
+ nycReportOptions['report-dir'] = resolve(
+ nycReportOptions['report-dir'] as string
+ )
}
// seems nyc API really is using camel cased version
nycReportOptions.reportDir = nycReportOptions['report-dir']
@@ -56,39 +64,36 @@ const nycReportOptions = (function getNycOption() {
return nycReportOptions
})()
-const nycFilename = join(nycReportOptions['temp-dir'], 'out.json')
+const nycFilename = join(nycReportOptions['temp-dir'] as string, 'out.json')
-let coverageMap = (() => {
+let coverageMap: IstanbulCoverageMap = (() => {
const previousCoverage = existsSync(nycFilename)
? JSON.parse(readFileSync(nycFilename, 'utf8'))
: {}
- return istanbul.createCoverageMap(previousCoverage)
+ return createCoverageMap(previousCoverage)
})()
-function saveCoverage(coverage) {
- if (!existsSync(nycReportOptions.tempDir)) {
- mkdirSync(nycReportOptions.tempDir, { recursive: true })
- debug('created folder %s for output coverage', nycReportOptions.tempDir)
+function saveCoverage(coverage: unknown): void {
+ if (!existsSync(nycReportOptions.tempDir as string)) {
+ mkdirSync(nycReportOptions.tempDir as string, { recursive: true })
+ log('created folder %s for output coverage', nycReportOptions.tempDir)
}
writeFileSync(nycFilename, JSON.stringify(coverage, null, 2))
}
-function maybePrintFinalCoverageFiles(folder) {
+function maybePrintFinalCoverageFiles(folder: string): void {
const jsonReportFilename = join(folder, 'coverage-final.json')
if (!existsSync(jsonReportFilename)) {
- debug('Did not find final coverage file %s', jsonReportFilename)
+ log('Did not find final coverage file %s', jsonReportFilename)
return
}
- debug('Final coverage in %s', jsonReportFilename)
- const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8'))
+ log('Final coverage in %s', jsonReportFilename)
+ const finalCoverage: Record }> =
+ JSON.parse(readFileSync(jsonReportFilename, 'utf8'))
const finalCoverageKeys = Object.keys(finalCoverage)
- debug(
- 'There are %d key(s) in %s',
- finalCoverageKeys.length,
- jsonReportFilename
- )
+ log('There are %d key(s) in %s', finalCoverageKeys.length, jsonReportFilename)
finalCoverageKeys.forEach((key) => {
const s = finalCoverage[key].s || {}
@@ -105,7 +110,7 @@ function maybePrintFinalCoverageFiles(folder) {
const allCovered = coveredStatements === totalStatements
const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓'
- debug(
+ log(
'%s %s statements covered %d/%d',
coverageStatus,
key,
@@ -115,6 +120,10 @@ function maybePrintFinalCoverageFiles(folder) {
})
}
+interface TaskParams {
+ isInteractive?: boolean
+}
+
const tasks = {
/**
* Clears accumulated code coverage information.
@@ -125,10 +134,10 @@ const tasks = {
* - runs EACH spec separately, so we cannot reset the coverage
* or we will lose the coverage from previous specs.
*/
- resetCoverage({ isInteractive }) {
+ resetCoverage({ isInteractive }: TaskParams): null {
if (isInteractive) {
- debug('reset code coverage in interactive mode')
- coverageMap = istanbul.createCoverageMap({})
+ log('reset code coverage in interactive mode')
+ coverageMap = createCoverageMap({})
saveCoverage(coverageMap)
}
/*
@@ -145,12 +154,12 @@ const tasks = {
* Combines coverage information from single test
* with previously collected coverage.
*
- * @param {string} sentCoverage Stringified coverage object sent by the test runner
- * @returns {null} Nothing is returned from this task
+ * @param sentCoverage Stringified coverage object sent by the test runner
+ * @returns Nothing is returned from this task
*/
- combineCoverage(sentCoverage) {
+ combineCoverage(sentCoverage: string): null {
const coverage = JSON.parse(sentCoverage)
- debug('parsed sent coverage')
+ log('parsed sent coverage')
fixSourcePaths(coverage)
@@ -163,7 +172,7 @@ const tasks = {
* Saves coverage information as a JSON file and calls
* NPM script to generate HTML report
*/
- coverageReport() {
+ coverageReport(): Promise | null {
saveCoverage(coverageMap)
if (!existsSync(nycFilename)) {
console.warn('Cannot find coverage file %s', nycFilename)
@@ -181,33 +190,30 @@ const tasks = {
resolveRelativePaths(nycFilename)
if (customNycReportScript) {
- debug(
+ log(
'saving coverage report using script "%s" from package.json, command: %s',
DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME,
customNycReportScript
)
- debug('current working directory is %s', process.cwd())
+ log('current working directory is %s', process.cwd())
return execa('npm', ['run', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME], {
stdio: 'inherit'
- })
+ }).then(() => null)
}
if (nycReportOptions.all) {
- debug('nyc needs to report on all included files')
+ log('nyc needs to report on all included files')
includeAllFiles(nycFilename, nycReportOptions)
}
- debug('calling NYC reporter with options %o', nycReportOptions)
- debug('current working directory is %s', process.cwd())
+ log('calling NYC reporter with options %o', nycReportOptions)
+ log('current working directory is %s', process.cwd())
const NYC = require('nyc')
const nyc = new NYC(nycReportOptions)
- const returnReportFolder = () => {
- const reportFolder = nycReportOptions['report-dir']
- debug(
- 'after reporting, returning the report folder name %s',
- reportFolder
- )
+ const returnReportFolder = (): string => {
+ const reportFolder = nycReportOptions['report-dir'] as string
+ log('after reporting, returning the report folder name %s', reportFolder)
maybePrintFinalCoverageFiles(reportFolder)
@@ -231,7 +237,10 @@ const tasks = {
}
```
*/
-function registerCodeCoverageTasks(on, config) {
+export = function registerCodeCoverageTasks(
+ on: Cypress.PluginEvents,
+ config: Cypress.PluginConfigOptions
+): Cypress.PluginConfigOptions {
on('task', tasks)
// set a variable to let the hooks running in the browser
@@ -240,5 +249,3 @@ function registerCodeCoverageTasks(on, config) {
return config
}
-
-module.exports = registerCodeCoverageTasks
diff --git a/lib/use-babelrc.js b/lib/use-babelrc.js
deleted file mode 100644
index 6f967221d..000000000
--- a/lib/use-babelrc.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const webpackPreprocessor = require('@cypress/webpack-preprocessor')
-const defaults = webpackPreprocessor.defaultOptions
-// remove presets so the babelrc file will be used
-delete defaults.webpackOptions.module.rules[0].use[0].options.presets
-module.exports = webpackPreprocessor(defaults)
diff --git a/lib/use-babelrc.ts b/lib/use-babelrc.ts
new file mode 100644
index 000000000..271782ebc
--- /dev/null
+++ b/lib/use-babelrc.ts
@@ -0,0 +1,15 @@
+///
+///
+import webpackPreprocessor from '@cypress/webpack-preprocessor'
+
+const defaults = webpackPreprocessor.defaultOptions
+// remove presets so the babelrc file will be used
+// @ts-ignore - we know that the use property exists
+if (defaults.webpackOptions?.module?.rules?.[0]?.use?.[0]?.options?.presets) {
+ // @ts-ignore - we know that the use property exists
+ delete defaults.webpackOptions.module.rules[0].use[0].options.presets
+}
+// @ts-ignore - we know that the use property exists
+const preprocessor = webpackPreprocessor(defaults)
+// Type assertion to avoid exposing private FileEvent type
+export = preprocessor as (file: Cypress.FileObject) => string | Promise
diff --git a/middleware/express.js b/middleware/express.ts
similarity index 51%
rename from middleware/express.js
rename to middleware/express.ts
index 07f884ff2..c94b4c344 100644
--- a/middleware/express.js
+++ b/middleware/express.ts
@@ -1,10 +1,14 @@
+import type { Express } from 'express'
+
// for Express.js
-module.exports = app => {
+export = function expressMiddleware(app: Express): void {
// expose "GET __coverage__" endpoint that just returns
// global coverage information (if the application has been instrumented)
app.get('/__coverage__', (req, res) => {
res.json({
- coverage: global.__coverage__ || null
+ coverage:
+ (global as typeof globalThis & { __coverage__?: unknown })
+ .__coverage__ || null
})
})
}
diff --git a/middleware/hapi.js b/middleware/hapi.js
deleted file mode 100644
index 159dd9728..000000000
--- a/middleware/hapi.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// for Hapi.js
-module.exports = server => {
- // expose "GET __coverage__" endpoint that just returns
- // global coverage information (if the application has been instrumented)
-
- // https://hapijs.com/tutorials/routing?lang=en_US
- server.route({
- method: 'GET',
- path: '/__coverage__',
- handler () {
- return { coverage: global.__coverage__ }
- }
- })
-}
diff --git a/middleware/hapi.ts b/middleware/hapi.ts
new file mode 100644
index 000000000..58b434a51
--- /dev/null
+++ b/middleware/hapi.ts
@@ -0,0 +1,20 @@
+import type { Server } from 'hapi'
+// for Hapi.js
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export = function hapiMiddleware(server: Server): void {
+ // expose "GET __coverage__" endpoint that just returns
+ // global coverage information (if the application has been instrumented)
+
+ // https://hapijs.com/tutorials/routing?lang=en_US
+ server.route({
+ method: 'GET',
+ path: '/__coverage__',
+ handler() {
+ return {
+ coverage:
+ (global as typeof globalThis & { __coverage__?: unknown })
+ .__coverage__ || null
+ }
+ }
+ })
+}
diff --git a/middleware/nextjs.js b/middleware/nextjs.ts
similarity index 79%
rename from middleware/nextjs.js
rename to middleware/nextjs.ts
index 00d1657ac..76d2c7db6 100644
--- a/middleware/nextjs.js
+++ b/middleware/nextjs.ts
@@ -20,9 +20,12 @@
* @see https://nextjs.org/docs#api-routes
* @see https://github.com/cypress-io/code-coverage
*/
-module.exports = function returnCodeCoverageNext (req, res) {
+
+export = function returnCodeCoverageNext(req: any, res: any): void {
// only GET is supported
res.status(200).json({
- coverage: global.__coverage__ || null
+ coverage:
+ (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ ||
+ null
})
}
diff --git a/package-lock.json b/package-lock.json
index 0443098a7..d6badb24f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
"debug": "4.4.0",
"execa": "4.1.0",
"istanbul-lib-coverage": "^3.0.0",
- "js-yaml": "4.1.0",
+ "js-yaml": "^4.1.1",
"nyc": "^17.1.0",
"tinyglobby": "^0.2.14"
},
@@ -23,6 +23,14 @@
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.11",
"@cypress/code-coverage": "file:.",
+ "@types/debug": "^4.1.12",
+ "@types/express": "^5.0.6",
+ "@types/hapi": "^18.0.15",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/js-yaml": "^4.0.9",
+ "@types/lodash": "^4.17.23",
+ "@types/next": "^8.0.7",
+ "@types/node": "^20.19.33",
"@vitest/coverage-v8": "^4.0.0",
"babel-loader": "^9.1.3",
"babel-plugin-istanbul": "6.1.1",
@@ -39,6 +47,7 @@
"semantic-release": "17.4.7",
"serve": "14.2.4",
"start-server-and-test": "2.0.12",
+ "typescript": "^5.9.3",
"vitest": "^4.0.0",
"webpack": "^5.68.0",
"webpack-cli": "^5.1.4"
@@ -1578,23 +1587,6 @@
"node": ">= 6"
}
},
- "node_modules/@cypress/request/node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/@cypress/request/node_modules/http-signature": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
@@ -7302,6 +7294,24 @@
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"dev": true
},
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/boom": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/@types/boom/-/boom-7.3.5.tgz",
+ "integrity": "sha512-jBS0kU2s9W2sx+ILEyO4kxqIYLllqcUXTaVrBctvGptZ+4X3TWkkgY9+AmxdMPKrgiDDdLcfsaQCTu7bniLvgw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
@@ -7314,6 +7324,13 @@
"@types/responselike": "*"
}
},
+ "node_modules/@types/catbox": {
+ "version": "10.0.9",
+ "resolved": "https://registry.npmjs.org/@types/catbox/-/catbox-10.0.9.tgz",
+ "integrity": "sha512-4qXm1SmZurBMNFc/536+7gfbOlN43fWyoo4O0bdLqtpDK/cpuCYnEDou0Cl4naaMwuJ19rEwnuscR7tetGnTDA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -7330,6 +7347,26 @@
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
},
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -7362,12 +7399,85 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
+ "node_modules/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/hapi": {
+ "version": "18.0.15",
+ "resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-18.0.15.tgz",
+ "integrity": "sha512-n7fXlpRAptQ+6N0JdQun+K4co2v3Wv5+KOFYLFV1xL8HEJ+EX8hqvbBmQg+uL3OkzwQZHvgCtHiBSdmCCDlrog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/boom": "*",
+ "@types/catbox": "*",
+ "@types/iron": "*",
+ "@types/mimos": "*",
+ "@types/node": "*",
+ "@types/podium": "*",
+ "@types/shot": "*",
+ "joi": "^17.3.0"
+ }
+ },
"node_modules/@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==",
"dev": true
},
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/iron": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@types/iron/-/iron-5.0.5.tgz",
+ "integrity": "sha512-ndu2RvRJ5LWsSVF0kBMJe9qnNcFcAO9eYwzr2P4FOU6m5ypRrbdiX+d8x4GNG7lIn1mKShyQf3M08CIX4wPsEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/js-yaml": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
+ "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -7383,19 +7493,87 @@
"@types/node": "*"
}
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime-db": {
+ "version": "1.43.6",
+ "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.6.tgz",
+ "integrity": "sha512-r2cqxAt/Eo5yWBOQie1lyM1JZFCiORa5xtLlhSZI0w8RJggBPKw8c4g/fgQCzWydaDR5bL4imnmix2d1n52iBw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mimos": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/mimos/-/mimos-3.0.6.tgz",
+ "integrity": "sha512-pQlYu/Q1e5F5lyu7ATW4J2cyPOfjhRHZgAepZlKBbHqqAjshteHtNLqBXgx7KV5GjXjPLXWUvbzWaGwmVFPaYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime-db": "*"
+ }
+ },
"node_modules/@types/minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
"integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
"dev": true
},
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/next": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@types/next/-/next-8.0.7.tgz",
+ "integrity": "sha512-I/Gcj1YfOFmpBBX5XgBP1t1wKcFS0TGk8ytW99ujjvCp8U31QuKqM3fvvGb7+Hf1CJt3BAAgzGT0aCigqO5opQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/next-server": "*",
+ "@types/node": "*",
+ "@types/node-fetch": "*",
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/next-server": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@types/next-server/-/next-server-8.1.2.tgz",
+ "integrity": "sha512-Fm4QhAxwDlC9AHiGy23Lhv7DeTTt1O1s7tnAsyVOLPjePmYXPZVbOCrxd2oRHZnIIYWw41JelLbq4hN1B5idlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/next": "*",
+ "@types/node": "*",
+ "@types/react": "*",
+ "@types/react-loadable": "*"
+ }
+ },
"node_modules/@types/node": {
- "version": "25.2.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
- "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==",
+ "version": "20.19.33",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
+ "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
"license": "MIT",
"dependencies": {
- "undici-types": "~7.16.0"
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.13",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
+ "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.4"
}
},
"node_modules/@types/normalize-package-data": {
@@ -7410,6 +7588,48 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
+ "node_modules/@types/podium": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/podium/-/podium-1.0.4.tgz",
+ "integrity": "sha512-HuG5/hRHs9PxuXXlNFXPy7mHMnBD6Z4riED2SFGwjs+RcszJUkxLgYHQpoiDpYrhLv7sHk9WDyswybD6aNYkig==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz",
+ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-loadable": {
+ "version": "5.5.11",
+ "resolved": "https://registry.npmjs.org/@types/react-loadable/-/react-loadable-5.5.11.tgz",
+ "integrity": "sha512-/tq2IJ853MoIFRBmqVOxnGsRRjER5TmEKzsZtaAkiXAWoDeKgR/QNOT1vd9k0p9h/F616X21cpNh3hu4RutzRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*",
+ "@types/webpack": "^4"
+ }
+ },
"node_modules/@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
@@ -7425,6 +7645,37 @@
"integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==",
"dev": true
},
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/shot": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/shot/-/shot-4.0.5.tgz",
+ "integrity": "sha512-4wiGdy1sXfpcFgF1VAouUaSl6zjWn0v2NarNgviAJYm3BeiODywERnRtIC5YtNIbfh3UfShfCRD9yyeWAD7R0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
@@ -7437,6 +7688,20 @@
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
+ "node_modules/@types/source-list-map": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz",
+ "integrity": "sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/tapable": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz",
+ "integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/tmp": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz",
@@ -7444,6 +7709,53 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/uglify-js": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz",
+ "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/@types/webpack": {
+ "version": "4.41.40",
+ "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz",
+ "integrity": "sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tapable": "^1",
+ "@types/uglify-js": "*",
+ "@types/webpack-sources": "*",
+ "anymatch": "^3.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/@types/webpack-sources": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz",
+ "integrity": "sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/source-list-map": "*",
+ "source-map": "^0.7.3"
+ }
+ },
+ "node_modules/@types/webpack-sources/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/@types/yauzl": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
@@ -7945,6 +8257,20 @@
"integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
"dev": true
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/append-transform": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
@@ -8148,22 +8474,6 @@
"proxy-from-env": "^1.1.0"
}
},
- "node_modules/axios/node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/axios/node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -9952,6 +10262,13 @@
"node": ">=8"
}
},
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cypress": {
"version": "15.10.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-15.10.0.tgz",
@@ -10308,16 +10625,6 @@
"node": ">=4.0"
}
},
- "node_modules/degenerator/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/del": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
@@ -11194,6 +11501,23 @@
"node": "*"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -11650,15 +11974,6 @@
"uglify-js": "^3.1.4"
}
},
- "node_modules/handlebars/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/hard-rejection": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
@@ -12462,14 +12777,6 @@
"node": ">=8"
}
},
- "node_modules/istanbul-lib-source-maps/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -12567,9 +12874,10 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -13962,6 +14270,16 @@
"node": ">=10"
}
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/npm": {
"version": "7.21.1",
"resolved": "https://registry.npmjs.org/npm/-/npm-7.21.1.tgz",
@@ -19642,6 +19960,15 @@
"node": ">= 14"
}
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -19661,14 +19988,6 @@
"source-map": "^0.6.0"
}
},
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/spawn-error-forwarder": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
@@ -20584,6 +20903,20 @@
"is-typedarray": "^1.0.0"
}
},
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/uglify-js": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz",
@@ -20598,9 +20931,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
diff --git a/package.json b/package.json
index 096fcd223..8304496ce 100644
--- a/package.json
+++ b/package.json
@@ -3,15 +3,18 @@
"version": "0.0.0-development",
"description": "Saves the code coverage collected during Cypress tests",
"exports": {
- "./task": "./lib/task.js",
- "./use-babelrc": "./lib/use-babelrc.js",
- "./plugins": "./lib/plugins.js",
- "./support": "./lib/support.js",
- "./middleware/express": "./middleware/express.js",
- "./middleware/hapi": "./middleware/hapi.js",
- "./middleware/nextjs": "./middleware/nextjs.js"
+ "./task": "./dist/lib/task.js",
+ "./use-babelrc": "./dist/lib/use-babelrc.js",
+ "./plugins": "./dist/lib/plugins.js",
+ "./support": "./dist/lib/support.js",
+ "./middleware/express": "./dist/middleware/express.js",
+ "./middleware/hapi": "./dist/middleware/hapi.js",
+ "./middleware/nextjs": "./dist/middleware/nextjs.js"
},
"scripts": {
+ "prebuild": "rimraf dist",
+ "build": "tsc",
+ "prepublishOnly": "npm run build",
"start": "parcel serve cypress/index.html",
"cy:open": "cypress open",
"dev": "start-test 1234 cy:open",
@@ -21,8 +24,8 @@
"coverage": "vitest run --coverage",
"report:coverage": "nyc report --reporter=html",
"dev:no:coverage": "start-test 1234 'cypress open --expose coverage=false'",
- "format": "prettier --write '*.js'",
- "format:check": "prettier --check '*.js'",
+ "format": "prettier --write '{lib,middleware,test}/**/*.{js,ts}'",
+ "format:check": "prettier --check '{lib,middleware,test}/**/*.{js,ts}'",
"check:markdown": "find *.md -exec npx markdown-link-check {} \\;",
"effective:config": "circleci config process .circleci/config.yml | sed /^#/d",
"apps:test:coverage:verify": "./scripts/test-apps-and-verify-coverage.sh"
@@ -55,9 +58,8 @@
},
"homepage": "https://github.com/cypress-io/code-coverage#readme",
"files": [
- "*.d.ts",
- "*.js",
- "middleware"
+ "dist",
+ "*.d.ts"
],
"publishConfig": {
"access": "public"
@@ -70,7 +72,7 @@
"debug": "4.4.0",
"execa": "4.1.0",
"istanbul-lib-coverage": "^3.0.0",
- "js-yaml": "4.1.0",
+ "js-yaml": "^4.1.1",
"nyc": "^17.1.0",
"tinyglobby": "^0.2.14"
},
@@ -78,6 +80,14 @@
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.11",
"@cypress/code-coverage": "file:.",
+ "@types/debug": "^4.1.12",
+ "@types/express": "^5.0.6",
+ "@types/hapi": "^18.0.15",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/js-yaml": "^4.0.9",
+ "@types/lodash": "^4.17.23",
+ "@types/next": "^8.0.7",
+ "@types/node": "^20.19.33",
"@vitest/coverage-v8": "^4.0.0",
"babel-loader": "^9.1.3",
"babel-plugin-istanbul": "6.1.1",
@@ -94,6 +104,7 @@
"semantic-release": "17.4.7",
"serve": "14.2.4",
"start-server-and-test": "2.0.12",
+ "typescript": "^5.9.3",
"vitest": "^4.0.0",
"webpack": "^5.68.0",
"webpack-cli": "^5.1.4"
diff --git a/test-apps/esm-example/.babelrc b/test-apps/esm-example/.babelrc
new file mode 100644
index 000000000..e7884b674
--- /dev/null
+++ b/test-apps/esm-example/.babelrc
@@ -0,0 +1,4 @@
+{
+ "plugins": ["istanbul"]
+}
+
diff --git a/test-apps/esm-example/.nycrc b/test-apps/esm-example/.nycrc
new file mode 100644
index 000000000..0cf95e28f
--- /dev/null
+++ b/test-apps/esm-example/.nycrc
@@ -0,0 +1,7 @@
+{
+ "all": true,
+ "include": [
+ "*.js"
+ ]
+}
+
diff --git a/test-apps/esm-example/README.md b/test-apps/esm-example/README.md
new file mode 100644
index 000000000..3862df1d8
--- /dev/null
+++ b/test-apps/esm-example/README.md
@@ -0,0 +1,23 @@
+# ESM Example
+
+This test app demonstrates using `@cypress/code-coverage` with ES Module syntax (`import`/`export`).
+
+## Key differences from CommonJS examples:
+
+1. **package.json**: Uses `"type": "module"` to enable ES modules (all `.js` files are treated as ES modules)
+2. **cypress.config.js**: Uses `import`/`export` syntax instead of `require`/`module.exports`
+3. **cypress/plugins/index.js**: Uses ES module `import` syntax to import the code coverage plugin:
+ ```javascript
+ import codecov from '@cypress/code-coverage/plugins'
+ ```
+4. **cypress/support/e2e.js**: Uses ES module `import` syntax for support files:
+ ```javascript
+ import '@cypress/code-coverage/support'
+ ```
+
+## Usage
+
+```bash
+npm install
+npm test
+```
diff --git a/test-apps/esm-example/cypress.config.js b/test-apps/esm-example/cypress.config.js
new file mode 100644
index 000000000..48f095d7a
--- /dev/null
+++ b/test-apps/esm-example/cypress.config.js
@@ -0,0 +1,14 @@
+import { defineConfig } from 'cypress'
+import codecov from '@cypress/code-coverage/plugins'
+
+export default defineConfig({
+ allowCypressEnv: false,
+ fixturesFolder: false,
+ e2e: {
+ setupNodeEvents(on, config) {
+ return codecov(on, config)
+ },
+ baseUrl: 'http://localhost:1234'
+ }
+})
+
diff --git a/test-apps/esm-example/cypress/e2e/spec.cy.js b/test-apps/esm-example/cypress/e2e/spec.cy.js
new file mode 100644
index 000000000..945485e6e
--- /dev/null
+++ b/test-apps/esm-example/cypress/e2e/spec.cy.js
@@ -0,0 +1,13 @@
+///
+it('works', () => {
+ cy.visit('/')
+ cy.contains('Page body')
+
+ cy.window()
+ .invoke('reverse', 'super')
+ .should('equal', 'repus')
+
+ // application's code should be instrumented
+ cy.window().should('have.property', '__coverage__')
+})
+
diff --git a/test-apps/esm-example/cypress/plugins/index.js b/test-apps/esm-example/cypress/plugins/index.js
new file mode 100644
index 000000000..47c517d7e
--- /dev/null
+++ b/test-apps/esm-example/cypress/plugins/index.js
@@ -0,0 +1,6 @@
+import codecov from '@cypress/code-coverage/plugins'
+
+export default (on, config) => {
+ return codecov(on, config)
+}
+
diff --git a/test-apps/esm-example/cypress/plugins/index.mjs b/test-apps/esm-example/cypress/plugins/index.mjs
new file mode 100644
index 000000000..47c517d7e
--- /dev/null
+++ b/test-apps/esm-example/cypress/plugins/index.mjs
@@ -0,0 +1,6 @@
+import codecov from '@cypress/code-coverage/plugins'
+
+export default (on, config) => {
+ return codecov(on, config)
+}
+
diff --git a/test-apps/esm-example/cypress/support/commands.js b/test-apps/esm-example/cypress/support/commands.js
new file mode 100644
index 000000000..78ceeefb8
--- /dev/null
+++ b/test-apps/esm-example/cypress/support/commands.js
@@ -0,0 +1,2 @@
+// Custom commands can be added here if needed
+
diff --git a/test-apps/esm-example/cypress/support/e2e.js b/test-apps/esm-example/cypress/support/e2e.js
new file mode 100644
index 000000000..6419f3e4d
--- /dev/null
+++ b/test-apps/esm-example/cypress/support/e2e.js
@@ -0,0 +1,3 @@
+import './commands'
+import '@cypress/code-coverage/support'
+
diff --git a/test-apps/esm-example/cypress/support/e2e.mjs b/test-apps/esm-example/cypress/support/e2e.mjs
new file mode 100644
index 000000000..71316ef0e
--- /dev/null
+++ b/test-apps/esm-example/cypress/support/e2e.mjs
@@ -0,0 +1,2 @@
+import '@cypress/code-coverage/support'
+
diff --git a/test-apps/esm-example/index.html b/test-apps/esm-example/index.html
new file mode 100644
index 000000000..993f0c189
--- /dev/null
+++ b/test-apps/esm-example/index.html
@@ -0,0 +1,17 @@
+
+ Page body
+
+
+
+
diff --git a/test-apps/esm-example/main.js b/test-apps/esm-example/main.js
new file mode 100644
index 000000000..a25288ac3
--- /dev/null
+++ b/test-apps/esm-example/main.js
@@ -0,0 +1,4 @@
+window.add = (a, b) => a + b
+
+window.sub = (a, b) => a - b
+
diff --git a/test-apps/esm-example/not-covered.js b/test-apps/esm-example/not-covered.js
new file mode 100644
index 000000000..a2425b198
--- /dev/null
+++ b/test-apps/esm-example/not-covered.js
@@ -0,0 +1,5 @@
+// This file is not covered by tests
+window.notCovered = () => {
+ return 'not covered'
+}
+
diff --git a/test-apps/esm-example/package.json b/test-apps/esm-example/package.json
new file mode 100644
index 000000000..94ade313b
--- /dev/null
+++ b/test-apps/esm-example/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "example-esm",
+ "description": "ES Module example using import syntax",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "cy:run": "cypress run",
+ "start": "parcel serve index.html",
+ "start:windows": "npx bin-up parcel serve index.html",
+ "pretest": "rimraf .nyc_output .cache coverage dist",
+ "test": "start-test 1234 cy:run",
+ "coverage:verify": "npx nyc report --check-coverage true --lines 100",
+ "coverage:check-files": "check-coverage main.js && check-coverage second.js && check-coverage not-covered.js && check-coverage cypress.config.js && only-covered --from coverage/coverage-final.json main.js second.js not-covered.js cypress.config.js"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.22.15",
+ "babel-plugin-istanbul": "6.1.1"
+ }
+}
+
diff --git a/test-apps/esm-example/second.js b/test-apps/esm-example/second.js
new file mode 100644
index 000000000..f141778dd
--- /dev/null
+++ b/test-apps/esm-example/second.js
@@ -0,0 +1,2 @@
+window.reverse = (str) => str.split('').reverse().join('')
+
diff --git a/test/lib/combine.test.js b/test/lib/combine.test.ts
similarity index 96%
rename from test/lib/combine.test.js
rename to test/lib/combine.test.ts
index 15776741d..e62f27082 100644
--- a/test/lib/combine.test.js
+++ b/test/lib/combine.test.ts
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-const { combineNycOptions, defaultNycOptions } = require('../../lib/common-utils')
+import { combineNycOptions, defaultNycOptions } from '../../lib/common-utils'
describe('Combine NYC options', () => {
it('overrides defaults', () => {
@@ -94,4 +94,3 @@ describe('Combine NYC options', () => {
})
})
})
-
diff --git a/test/lib/filtering.test.js b/test/lib/filtering.test.js
deleted file mode 100644
index 94abb00ad..000000000
--- a/test/lib/filtering.test.js
+++ /dev/null
@@ -1,359 +0,0 @@
-import { describe, it, expect, beforeEach, vi } from 'vitest'
-import { minimatch } from 'minimatch'
-import _ from 'lodash'
-
-// Mock Cypress globals before importing support-utils
-global.Cypress = {
- minimatch,
- _: {
- omitBy: _.omitBy
- }
-}
-
-const { filterFilesFromCoverage } = require('../../lib/support-utils')
-
-describe('minimatch', () => {
- it('string matches', () => {
- expect(
- minimatch('/user/app/src/codeA.js', '/user/app/src/codeA.js')
- ).toBe(true)
-
- expect(
- minimatch('/user/app/src/codeA.js', 'codeA.js')
- ).toBe(false)
-
- expect(
- minimatch('/user/app/src/codeA.js', '**/codeA.js')
- ).toBe(true)
- })
-})
-
-describe('filtering specs', () => {
- describe('using integrationFolder and testFiles in Cypress < v10', () => {
- let config
- let expose
- let spec
-
- beforeEach(() => {
- const configValues = {
- integrationFolder: '/user/app/cypress/integration',
- supportFile: '/user/app/cypress/support/index.js',
- supportFolder: '/user/app/cypress/support'
- }
- config = vi.fn((key) => {
- return configValues[key]
- })
-
- expose = vi.fn().mockReturnValue({})
-
- spec = {
- absolute: '/user/app/cypress/integration/test.cy.js',
- relative: 'cypress/integration/test.cy.js'
- }
- })
-
- it('filters list of specs by single string', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return 'specA.js'
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
- const totalCoverage = {
- '/user/app/cypress/integration/specA.js': {},
- '/user/app/cypress/integration/specB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/cypress/integration/specB.js': {}
- })
- })
-
- it('filters list of specs by single string in array', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return ['codeA.js']
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeB.js': {}
- })
- })
-
- it('filters list of specs by pattern', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return ['**/*B.js']
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeA.js': {}
- })
- })
-
- it('filters list of specs by pattern and single spec', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return ['**/*B.js', 'codeA.js']
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({})
- })
-
- it('filters specs from integration folder', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return '**/*.*' // default pattern
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {},
- // these files should be removed
- '/user/app/cypress/integration/spec1.js': {},
- '/user/app/cypress/integration/spec2.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- })
- })
-
- it('filters list of specs when testFiles specifies folder', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return ['cypress/integration/**.*']
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/cypress/integration/specA.js': {},
- '/user/app/cypress/integration/specB.js': {},
- // This file should be included in coverage
- 'src/my-code.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- 'src/my-code.js': {}
- })
- })
-
- it('filters files out of cypress support directory', () => {
- config.mockImplementation((key) => {
- if (key === 'testFiles') return ['**/*.*'] // default pattern
- if (key === 'integrationFolder') return '/user/app/cypress/integration'
- if (key === 'supportFile') return '/user/app/cypress/support/index.js'
- if (key === 'supportFolder') return '/user/app/cypress/support'
- return undefined
- })
- const totalCoverage = {
- '/user/app/cypress/support/index.js': {},
- '/user/app/cypress/support/command.js': {},
- '/user/app/cypress/integration/spec.js': {},
- // This file should be included in coverage
- 'src/my-code.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- 'src/my-code.js': {}
- })
- })
- })
-
- describe('using codeCoverage.exclude and specPattern in Cypress >= v10', () => {
- let config
- let expose
- let spec
-
- beforeEach(() => {
- config = vi.fn()
-
- expose = vi.fn().mockReturnValue({
- //filter out all files in the cypress folder
- codeCoverage: {
- exclude: 'cypress/**/*.*'
- }
- })
-
- spec = {
- absolute: '/user/app/cypress/integration/test.cy.js',
- relative: 'cypress/integration/test.cy.js'
- }
- })
-
- it('filters list of specs by single string', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return 'specA.cy.js'
- return undefined
- })
- const totalCoverage = {
- '/user/app/src/specA.cy.js': {},
- '/user/app/src/specB.cy.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/specB.cy.js': {}
- })
- })
-
- it('filters list of specs by single string in array', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['specA.cy.js']
- return undefined
- })
- const totalCoverage = {
- '/user/app/src/specA.cy.js': {},
- '/user/app/src/specB.cy.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/specB.cy.js': {}
- })
- })
-
- it('filters out file in codeCoverage.exclude', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['**/*.cy.js']
- return undefined
- })
- const totalCoverage = {
- '/user/app/cypress/support/index.js': {},
- '/user/app/cypress/commands/index.js': {},
- //these files should be included
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- })
- })
-
- it('filters list of specs by pattern', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['**/*B.js']
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeA.js': {}
- })
- })
-
- it('filters list of specs by pattern and single spec', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['**/*B.js', 'codeA.js']
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({})
- })
-
- it('filters list of specs in integration folder', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return '**/*.cy.{js,jsx,ts,tsx}' // default pattern
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {},
- // these files should be removed
- '/user/app/cypress/integration/spec1.js': {},
- '/user/app/cypress/integration/spec2.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/src/codeA.js': {},
- '/user/app/src/codeB.js': {}
- })
- })
-
- it('filters list of specs when specPattern specifies folder', () => {
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['src/**/*.cy.js']
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/src/specA.cy.js': {},
- '/user/app/src/specB.cy.js': {},
- // This file should be included in coverage
- 'src/my-code.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- 'src/my-code.js': {}
- })
- })
-
- it('filters list of specs when exclude pattern is an array', () => {
- expose = vi.fn().mockReturnValue({
- //filter out a.js and b.js in cypress folder
- codeCoverage: {
- exclude: ['cypress/**/a.js', 'cypress/**/b.js']
- }
- })
-
- config.mockImplementation((key) => {
- if (key === 'specPattern') return ['src/**/*.cy.js']
- return undefined
- })
-
- const totalCoverage = {
- '/user/app/cypress/a.js': {},
- '/user/app/cypress/b.js': {},
- // These files should be included in coverage
- '/user/app/cypress/c.js': {},
- 'src/my-code.js': {}
- }
- const result = filterFilesFromCoverage(totalCoverage, config, expose, spec)
- expect(result).toEqual({
- '/user/app/cypress/c.js': {},
- 'src/my-code.js': {}
- })
- })
- })
-})
-
diff --git a/test/lib/filtering.test.ts b/test/lib/filtering.test.ts
new file mode 100644
index 000000000..b6947a065
--- /dev/null
+++ b/test/lib/filtering.test.ts
@@ -0,0 +1,230 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { minimatch } from 'minimatch'
+import _ from 'lodash'
+
+import { filterFilesFromCoverage } from '../../lib/support-utils'
+
+vi.stubGlobal('Cypress', {
+ minimatch,
+ _
+})
+
+describe('minimatch', () => {
+ it('string matches', () => {
+ expect(minimatch('/user/app/src/codeA.js', '/user/app/src/codeA.js')).toBe(
+ true
+ )
+
+ expect(minimatch('/user/app/src/codeA.js', 'codeA.js')).toBe(false)
+
+ expect(minimatch('/user/app/src/codeA.js', '**/codeA.js')).toBe(true)
+ })
+})
+
+describe('filtering specs', () => {
+ describe('using codeCoverage.exclude and specPattern', () => {
+ let config: ReturnType
+ let expose: ReturnType
+ let spec: { absolute: string; relative: string }
+
+ beforeEach(() => {
+ config = vi.fn()
+
+ expose = vi.fn().mockReturnValue({
+ //filter out all files in the cypress folder
+ codeCoverage: {
+ exclude: 'cypress/**/*.*'
+ }
+ })
+
+ spec = {
+ absolute: '/user/app/cypress/integration/test.cy.js',
+ relative: 'cypress/integration/test.cy.js'
+ }
+ })
+
+ it('filters list of specs by single string', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return 'specA.cy.js'
+ return undefined
+ })
+ const totalCoverage = {
+ '/user/app/src/specA.cy.js': {},
+ '/user/app/src/specB.cy.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/src/specB.cy.js': {}
+ })
+ })
+
+ it('filters list of specs by single string in array', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['specA.cy.js']
+ return undefined
+ })
+ const totalCoverage = {
+ '/user/app/src/specA.cy.js': {},
+ '/user/app/src/specB.cy.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/src/specB.cy.js': {}
+ })
+ })
+
+ it('filters out file in codeCoverage.exclude', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['**/*.cy.js']
+ return undefined
+ })
+ const totalCoverage = {
+ '/user/app/cypress/support/index.js': {},
+ '/user/app/cypress/commands/index.js': {},
+ //these files should be included
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {}
+ })
+ })
+
+ it('filters list of specs by pattern', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['**/*B.js']
+ return undefined
+ })
+
+ const totalCoverage = {
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/src/codeA.js': {}
+ })
+ })
+
+ it('filters list of specs by pattern and single spec', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['**/*B.js', 'codeA.js']
+ return undefined
+ })
+
+ const totalCoverage = {
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({})
+ })
+
+ it('filters list of specs matching specPattern', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return '**/*.cy.{js,jsx,ts,tsx}' // default pattern
+ return undefined
+ })
+
+ const totalCoverage = {
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {},
+ // these files should be removed
+ '/user/app/cypress/integration/spec1.cy.js': {},
+ '/user/app/cypress/integration/spec2.cy.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/src/codeA.js': {},
+ '/user/app/src/codeB.js': {}
+ })
+ })
+
+ it('filters list of specs when specPattern specifies folder', () => {
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['src/**/*.cy.js']
+ return undefined
+ })
+
+ const totalCoverage = {
+ '/user/app/src/specA.cy.js': {},
+ '/user/app/src/specB.cy.js': {},
+ // This file should be included in coverage
+ 'src/my-code.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ 'src/my-code.js': {}
+ })
+ })
+
+ it('filters list of specs when exclude pattern is an array', () => {
+ expose = vi.fn().mockReturnValue({
+ //filter out a.js and b.js in cypress folder
+ codeCoverage: {
+ exclude: ['cypress/**/a.js', 'cypress/**/b.js']
+ }
+ })
+
+ config.mockImplementation((key: string) => {
+ if (key === 'specPattern') return ['src/**/*.cy.js']
+ return undefined
+ })
+
+ const totalCoverage = {
+ '/user/app/cypress/a.js': {},
+ '/user/app/cypress/b.js': {},
+ // These files should be included in coverage
+ '/user/app/cypress/c.js': {},
+ 'src/my-code.js': {}
+ }
+ const result = filterFilesFromCoverage(
+ totalCoverage,
+ config as any,
+ expose as any,
+ spec as any
+ )
+ expect(result).toEqual({
+ '/user/app/cypress/c.js': {},
+ 'src/my-code.js': {}
+ })
+ })
+ })
+})
diff --git a/test/lib/fix-source-paths.test.js b/test/lib/fix-source-paths.test.ts
similarity index 94%
rename from test/lib/fix-source-paths.test.js
rename to test/lib/fix-source-paths.test.ts
index 75f320abb..f9ed36cff 100644
--- a/test/lib/fix-source-paths.test.js
+++ b/test/lib/fix-source-paths.test.ts
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
-const { fixSourcePaths } = require('../../lib/support-utils')
+import { fixSourcePaths } from '../../lib/support-utils'
describe('fixSourcePaths', () => {
it('fixes webpack loader source-map pathes', () => {
@@ -35,4 +35,3 @@ describe('fixSourcePaths', () => {
})
})
})
-
diff --git a/test/lib/merge.test.js b/test/lib/merge.test.js
deleted file mode 100644
index 0f7018ce3..000000000
--- a/test/lib/merge.test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { describe, it, expect, beforeAll } from 'vitest'
-import _ from 'lodash'
-const istanbul = require('istanbul-lib-coverage')
-const coverage = require('./__fixtures__/coverage.json')
-const {
- fileCoveragePlaceholder,
- removePlaceholders
-} = require('../../lib/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'
-
- beforeAll(() => {
- expect(coverage).toHaveProperty(filename)
- })
-
- it('combines an empty coverage object', () => {
- const previous = istanbul.createCoverageMap({})
- const coverageMap = istanbul.createCoverageMap(previous)
- coverageMap.merge(_.cloneDeep(coverage))
-
- const merged = coverageMapToCoverage(coverageMap)
-
- expect(merged).toEqual(coverage)
- })
-
- it('combines the same full coverage twice', () => {
- const previous = istanbul.createCoverageMap(_.cloneDeep(coverage))
- const coverageMap = istanbul.createCoverageMap(previous)
- coverageMap.merge(_.cloneDeep(coverage))
-
- const merged = coverageMapToCoverage(coverageMap)
- // it is almost the same - only the statement count has been doubled
- const expected = _.cloneDeep(coverage)
- expected[filename].s[0] = 2
- expect(merged).toEqual(expected)
- })
-
- // This test is skipped in the vitest migration. It is testing internals
- // of nyc that have changed with nyc 17. This needs fixed before we can
- // finalize v4 of @cypress/code-coverage.
- it.skip('does not merge correctly placeholders', () => {
- const coverageWithPlaceHolder = _.cloneDeep(coverage)
- const placeholder = fileCoveragePlaceholder(filename)
- coverageWithPlaceHolder[filename] = placeholder
-
- expect(coverageWithPlaceHolder).toEqual({
- [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 = _.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).toEqual(expected)
- })
-
- it('removes placeholders', () => {
- const inputCoverage = _.cloneDeep(coverage)
- removePlaceholders(inputCoverage)
- expect(inputCoverage).toEqual(coverage)
-
- // add placeholder
- const placeholder = fileCoveragePlaceholder(filename)
- inputCoverage[filename] = placeholder
-
- removePlaceholders(inputCoverage)
- expect(inputCoverage).toEqual({})
- })
-})
-
diff --git a/test/lib/merge.test.ts b/test/lib/merge.test.ts
new file mode 100644
index 000000000..7f934e1ac
--- /dev/null
+++ b/test/lib/merge.test.ts
@@ -0,0 +1,99 @@
+import { describe, it, expect, beforeAll } from 'vitest'
+import _ from 'lodash'
+import {
+ createCoverageMap,
+ CoverageMapData,
+ FileCoverageData
+} from 'istanbul-lib-coverage'
+import coverage from './__fixtures__/coverage.json'
+import {
+ fileCoveragePlaceholder,
+ removePlaceholders,
+ type FileCoveragePlaceholder
+} from '../../lib/common-utils'
+
+// CoverageMapData apparently has a getter that attempts to write to a property that only has a getter
+// this getter is accessed during expect(), so we need to normalize the data to avoid it
+function normalizeCoverageData(coverageData: CoverageMapData): CoverageMapData {
+ return JSON.parse(JSON.stringify(coverageData))
+}
+
+describe('merging coverage', () => {
+ const filename = '/src/index.js'
+ const coverageFixture = coverage satisfies CoverageMapData
+
+ beforeAll(() => {
+ expect(coverageFixture).toHaveProperty(filename)
+ })
+
+ it('combines an empty coverage object', () => {
+ const previous = createCoverageMap({})
+ const coverageMap = createCoverageMap(previous)
+ coverageMap.merge(coverageFixture)
+
+ const mergedData = normalizeCoverageData(coverageMap.data)
+
+ expect(mergedData).toEqual(coverageFixture)
+ })
+
+ it('combines the same full coverage twice', () => {
+ const previous = createCoverageMap(_.cloneDeep(coverageFixture))
+ const coverageMap = createCoverageMap(previous)
+ coverageMap.merge(_.cloneDeep(coverageFixture))
+
+ const merged = normalizeCoverageData(coverageMap.data)
+ // it is almost the same - only the statement count has been doubled
+ const expected = _.cloneDeep(coverageFixture)
+ expected[filename].s[0] = 2
+ expect(merged).toEqual(expected)
+ })
+
+ // This test is skipped in the vitest migration. It is testing internals
+ // of nyc that have changed with nyc 17. This needs fixed before we can
+ // finalize v4 of @cypress/code-coverage.
+ it('does not merge correctly placeholders', () => {
+ const coverageWithPlaceHolder = _.cloneDeep(coverageFixture)
+ const placeholder = fileCoveragePlaceholder(filename)
+ ;(
+ coverageWithPlaceHolder as Record<
+ string,
+ FileCoveragePlaceholder | FileCoverageData
+ >
+ )[filename] = placeholder
+
+ expect(coverageWithPlaceHolder).toEqual({
+ [filename]: placeholder
+ })
+
+ // now lets merge full info
+ const previous = createCoverageMap(coverageWithPlaceHolder)
+ const coverageMap = createCoverageMap(previous)
+ coverageMap.merge(coverageFixture)
+
+ const merged = normalizeCoverageData(coverageMap.data)
+ const expected = _.cloneDeep(coverageFixture)
+ // the merge against the placeholder without valid statement map
+ // has no hashes
+ delete (expected[filename] as any).hash
+ delete (expected[filename] as any)._coverageSchema
+ expect(merged).toEqual(expected)
+ })
+
+ it('removes placeholders', () => {
+ const inputCoverage = _.cloneDeep(coverageFixture)
+ removePlaceholders(inputCoverage)
+ expect(inputCoverage).toEqual(coverageFixture)
+
+ // add placeholder
+ const placeholder = fileCoveragePlaceholder(filename)
+ ;(
+ inputCoverage as Record<
+ string,
+ FileCoveragePlaceholder | FileCoverageData
+ >
+ )[filename] = placeholder
+
+ removePlaceholders(inputCoverage)
+ expect(inputCoverage).toEqual({})
+ })
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..610b38d43
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "CommonJS",
+ "lib": ["ES2020"],
+ "outDir": "./dist",
+ "rootDir": ".",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "types": ["node", "cypress"]
+ },
+ "include": [
+ "lib/**/*",
+ "middleware/**/*",
+ ],
+}
+
diff --git a/vitest.config.js b/vitest.config.ts
similarity index 74%
rename from vitest.config.js
rename to vitest.config.ts
index b281a9482..7a5a6d587 100644
--- a/vitest.config.js
+++ b/vitest.config.ts
@@ -4,7 +4,7 @@ export default defineConfig({
test: {
coverage: {
provider: 'v8',
- include: ['common-utils.js', 'support-utils.js'],
+ include: ['lib/common-utils.ts', 'lib/support-utils.ts'],
thresholds: {
lines: 80
}