diff --git a/.circleci/src/workflows/@workflows.yml b/.circleci/src/workflows/@workflows.yml index 281a3d88a68..997dacd83b3 100644 --- a/.circleci/src/workflows/@workflows.yml +++ b/.circleci/src/workflows/@workflows.yml @@ -1784,7 +1784,7 @@ jobs: source ./scripts/ensure-node.sh yarn lerna run types - sanitize-verify-and-store-mocha-results: - expectedResultCount: 18 + expectedResultCount: 17 verify-release-readiness: <<: *defaults diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 1d71d40a7be..d6bda1687b5 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -3486,7 +3486,7 @@ jobs: yarn lerna run types name: Test types - sanitize-verify-and-store-mocha-results: - expectedResultCount: 18 + expectedResultCount: 17 working_directory: ~/cypress v8-integration-tests: environment: diff --git a/guides/esm-migration.md b/guides/esm-migration.md index 4681bf48a03..e33d465b7e5 100644 --- a/guides/esm-migration.md +++ b/guides/esm-migration.md @@ -87,7 +87,7 @@ ##### Binary Packages -- [ ] packages/config +- [x] packages/config ✅ **COMPLETED** - [ ] packages/data-context - [x] packages/driver ✅ **COMPLETED** - [x] packages/electron ✅ **COMPLETED** diff --git a/packages/config/__snapshots__/index.spec.ts.js b/packages/config/__snapshots__/index.spec.ts.js deleted file mode 100644 index 54fe87d2933..00000000000 --- a/packages/config/__snapshots__/index.spec.ts.js +++ /dev/null @@ -1,257 +0,0 @@ -exports['config/src/index .getBreakingKeys returns list of breaking config keys 1'] = [ - 'experimentalJustInTimeCompile', - 'experimentalSessionAndOrigin', - 'experimentalSkipDomainInjection', - 'videoUploadOnPasses', -] - -exports['config/src/index .getDefaultValues returns list of public config keys 1'] = { - 'animationDistanceThreshold': 5, - 'baseUrl': null, - 'blockHosts': null, - 'chromeWebSecurity': true, - 'clientCertificates': [], - 'component': { - 'specPattern': '**/*.cy.{js,jsx,ts,tsx}', - 'indexHtmlFile': 'cypress/support/component-index.html', - }, - 'defaultBrowser': null, - 'defaultCommandTimeout': 4000, - 'downloadsFolder': 'cypress/downloads', - 'e2e': { - 'specPattern': 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - }, - 'env': {}, - 'execTimeout': 60000, - 'experimentalCspAllowList': false, - 'experimentalInteractiveRunEvents': false, - 'experimentalRunAllSpecs': false, - 'experimentalMemoryManagement': false, - 'experimentalModifyObstructiveThirdPartyCode': false, - 'injectDocumentDomain': false, - 'experimentalOriginDependencies': false, - 'experimentalSourceRewriting': false, - 'experimentalSingleTabRunMode': false, - 'experimentalStudio': false, - 'experimentalWebKitSupport': false, - 'fileServerFolder': '', - 'fixturesFolder': 'cypress/fixtures', - 'excludeSpecPattern': '*.hot-update.js', - 'includeShadowDom': false, - 'justInTimeCompile': true, - 'keystrokeDelay': 0, - 'modifyObstructiveCode': true, - 'numTestsKeptInMemory': 50, - 'pageLoadTimeout': 60000, - 'port': null, - 'projectId': null, - 'redirectionLimit': 20, - 'reporter': 'spec', - 'reporterOptions': null, - 'requestTimeout': 5000, - 'resolvedNodePath': null, - 'resolvedNodeVersion': null, - 'responseTimeout': 30000, - 'retries': { - 'runMode': 0, - 'openMode': 0, - }, - 'screenshotOnRunFailure': true, - 'screenshotsFolder': 'cypress/screenshots', - 'slowTestThreshold': 10000, - 'scrollBehavior': 'top', - 'supportFile': 'cypress/support/e2e.{js,jsx,ts,tsx}', - 'supportFolder': false, - 'taskTimeout': 60000, - 'testIsolation': true, - 'trashAssetsBeforeRuns': true, - 'userAgent': null, - 'video': false, - 'videoCompression': false, - 'videosFolder': 'cypress/videos', - 'viewportHeight': 660, - 'viewportWidth': 1000, - 'waitForAnimations': true, - 'watchForFileChanges': true, - 'specPattern': 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - 'additionalIgnorePattern': [], - 'autoOpen': false, - 'browsers': [], - 'clientRoute': '/__/', - 'configFile': 'cypress.config.js', - 'cypressBinaryRoot': '/root/cypress', - 'devServerPublicPathRoute': '/__cypress/src', - 'hosts': null, - 'isInteractive': true, - 'isTextTerminal': false, - 'morgan': true, - 'namespace': '__cypress', - 'repoRoot': null, - 'reporterRoute': '/__cypress/reporter', - 'socketId': null, - 'socketIoCookie': '__socket', - 'socketIoRoute': '/__socket', - 'isDefaultProtocolEnabled': false, - 'hideCommandLog': false, - 'hideRunnerUi': false, -} - -exports['config/src/index .getDefaultValues returns list of public config keys for selected testing type 1'] = { - 'animationDistanceThreshold': 5, - 'baseUrl': null, - 'blockHosts': null, - 'chromeWebSecurity': true, - 'clientCertificates': [], - 'component': { - 'specPattern': '**/*.cy.{js,jsx,ts,tsx}', - 'indexHtmlFile': 'cypress/support/component-index.html', - }, - 'defaultBrowser': null, - 'defaultCommandTimeout': 4000, - 'downloadsFolder': 'cypress/downloads', - 'e2e': { - 'specPattern': 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - }, - 'env': {}, - 'execTimeout': 60000, - 'experimentalCspAllowList': false, - 'experimentalInteractiveRunEvents': false, - 'experimentalRunAllSpecs': false, - 'experimentalMemoryManagement': false, - 'experimentalModifyObstructiveThirdPartyCode': false, - 'injectDocumentDomain': false, - 'experimentalOriginDependencies': false, - 'experimentalSourceRewriting': false, - 'experimentalSingleTabRunMode': false, - 'experimentalStudio': false, - 'experimentalWebKitSupport': false, - 'fileServerFolder': '', - 'fixturesFolder': 'cypress/fixtures', - 'excludeSpecPattern': '*.hot-update.js', - 'includeShadowDom': false, - 'justInTimeCompile': true, - 'keystrokeDelay': 0, - 'modifyObstructiveCode': true, - 'numTestsKeptInMemory': 50, - 'pageLoadTimeout': 60000, - 'port': null, - 'projectId': null, - 'redirectionLimit': 20, - 'reporter': 'spec', - 'reporterOptions': null, - 'requestTimeout': 5000, - 'resolvedNodePath': null, - 'resolvedNodeVersion': null, - 'responseTimeout': 30000, - 'retries': { - 'runMode': 0, - 'openMode': 0, - }, - 'screenshotOnRunFailure': true, - 'screenshotsFolder': 'cypress/screenshots', - 'slowTestThreshold': 10000, - 'scrollBehavior': 'top', - 'supportFile': 'cypress/support/e2e.{js,jsx,ts,tsx}', - 'supportFolder': false, - 'taskTimeout': 60000, - 'testIsolation': true, - 'trashAssetsBeforeRuns': true, - 'userAgent': null, - 'video': false, - 'videoCompression': false, - 'videosFolder': 'cypress/videos', - 'viewportHeight': 660, - 'viewportWidth': 1000, - 'waitForAnimations': true, - 'watchForFileChanges': true, - 'specPattern': 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - 'additionalIgnorePattern': [], - 'autoOpen': false, - 'browsers': [], - 'clientRoute': '/__/', - 'configFile': 'cypress.config.js', - 'cypressBinaryRoot': '/root/cypress', - 'devServerPublicPathRoute': '/__cypress/src', - 'hosts': null, - 'isInteractive': true, - 'isTextTerminal': false, - 'morgan': true, - 'namespace': '__cypress', - 'repoRoot': null, - 'reporterRoute': '/__cypress/reporter', - 'socketId': null, - 'socketIoCookie': '__socket', - 'socketIoRoute': '/__socket', - 'isDefaultProtocolEnabled': false, - 'hideCommandLog': false, - 'hideRunnerUi': false, -} - -exports['config/src/index .getPublicConfigKeys returns list of public config keys 1'] = [ - 'animationDistanceThreshold', - 'arch', - 'baseUrl', - 'blockHosts', - 'chromeWebSecurity', - 'clientCertificates', - 'component', - 'defaultBrowser', - 'defaultCommandTimeout', - 'downloadsFolder', - 'e2e', - 'env', - 'execTimeout', - 'experimentalCspAllowList', - 'experimentalInteractiveRunEvents', - 'experimentalRunAllSpecs', - 'experimentalMemoryManagement', - 'experimentalModifyObstructiveThirdPartyCode', - 'injectDocumentDomain', - 'experimentalOriginDependencies', - 'experimentalSourceRewriting', - 'experimentalSingleTabRunMode', - 'experimentalStudio', - 'experimentalWebKitSupport', - 'fileServerFolder', - 'fixturesFolder', - 'excludeSpecPattern', - 'includeShadowDom', - 'justInTimeCompile', - 'keystrokeDelay', - 'modifyObstructiveCode', - 'numTestsKeptInMemory', - 'platform', - 'pageLoadTimeout', - 'port', - 'projectId', - 'redirectionLimit', - 'reporter', - 'reporterOptions', - 'requestTimeout', - 'resolvedNodePath', - 'resolvedNodeVersion', - 'responseTimeout', - 'retries', - 'screenshotOnRunFailure', - 'screenshotsFolder', - 'slowTestThreshold', - 'scrollBehavior', - 'supportFile', - 'supportFolder', - 'taskTimeout', - 'testIsolation', - 'trashAssetsBeforeRuns', - 'userAgent', - 'video', - 'videoCompression', - 'videosFolder', - 'viewportHeight', - 'viewportWidth', - 'waitForAnimations', - 'watchForFileChanges', - 'specPattern', - 'browsers', - 'hosts', - 'isInteractive', - 'modifyObstructiveCode', -] diff --git a/packages/config/__snapshots__/validation.spec.ts.js b/packages/config/__snapshots__/validation.spec.ts.js deleted file mode 100644 index 491f2c5e08f..00000000000 --- a/packages/config/__snapshots__/validation.spec.ts.js +++ /dev/null @@ -1,400 +0,0 @@ -exports['missing https protocol'] = { - 'key': 'clientCertificates[0].url', - 'value': 'http://url.com', - 'type': 'an https protocol', -} - -exports['invalid url'] = { - 'key': 'clientCertificates[0].url', - 'value': 'not *', - 'type': 'a valid URL', -} - -exports['undefined browsers'] = ` -Missing browsers list -` - -exports['empty list of browsers'] = ` -Expected at least one browser -` - -exports['browsers list with a string'] = { - 'key': 'name', - 'value': 'foo', - 'type': 'a non-empty string', - 'list': 'browsers', -} - -exports['invalid retry value'] = { - 'key': 'mockConfigKey', - 'value': '1', - 'type': 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls, or experimental configuration with key "experimentalStrategy" with value "detect-flake-but-always-fail" or "detect-flake-and-pass-on-threshold" and key "experimentalOptions" to provide a valid configuration for your selected strategy', -} - -exports['invalid retry object'] = { - 'key': 'mockConfigKey', - 'value': { - 'fakeMode': 1, - }, - 'type': 'an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls', -} - -exports['not qualified url'] = { - 'key': 'mockConfigKey', - 'value': 'url.com', - 'type': 'a fully qualified URL (starting with `http://` or `https://`)', -} - -exports['empty string'] = { - 'key': 'mockConfigKey', - 'value': '', - 'type': 'a fully qualified URL (starting with `http://` or `https://`)', -} - -exports['not string or array'] = { - 'key': 'mockConfigKey', - 'value': null, - 'type': 'a string or an array of strings', -} - -exports['array of non-strings'] = { - 'key': 'mockConfigKey', - 'value': [ - 1, - 2, - 3, - ], - 'type': 'a string or an array of strings', -} - -exports['not one of the strings error message'] = { - 'key': 'test', - 'value': 'nope', - 'type': 'one of these values: "foo", "bar"', -} - -exports['number instead of string'] = { - 'key': 'test', - 'value': 42, - 'type': 'one of these values: "foo", "bar"', -} - -exports['null instead of string'] = { - 'key': 'test', - 'value': null, - 'type': 'one of these values: "foo", "bar"', -} - -exports['not one of the numbers error message'] = { - 'key': 'test', - 'value': 4, - 'type': 'one of these values: 1, 2, 3', -} - -exports['string instead of a number'] = { - 'key': 'test', - 'value': 'foo', - 'type': 'one of these values: 1, 2, 3', -} - -exports['null instead of a number'] = { - 'key': 'test', - 'value': null, - 'type': 'one of these values: 1, 2, 3', -} - -exports['config/src/validation .isValidClientCertificatesSet returns error message for certs not passed as an array array 1'] = { - 'key': 'mockConfigKey', - 'value': '1', - 'type': 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls, or experimental configuration with key "experimentalStrategy" with value "detect-flake-but-always-fail" or "detect-flake-and-pass-on-threshold" and key "experimentalOptions" to provide a valid configuration for your selected strategy', -} - -exports['config/src/validation .isValidClientCertificatesSet returns error message for certs object without url 1'] = { - 'key': 'clientCertificates[0].url', - 'type': 'a URL matcher', -} - -exports['config/src/validation .isValidBrowser passes valid browsers and forms error messages for invalid ones isValidBrowser 1'] = { - 'name': 'isValidBrowser', - 'behavior': [ - { - 'given': { - 'name': 'Chrome', - 'displayName': 'Chrome Browser', - 'family': 'chromium', - 'path': '/path/to/chrome', - 'version': '1.2.3', - 'majorVersion': 1, - }, - 'expect': true, - }, - { - 'given': { - 'name': 'FF', - 'displayName': 'Firefox', - 'family': 'firefox', - 'path': '/path/to/firefox', - 'version': '1.2.3', - 'majorVersion': '1', - }, - 'expect': true, - }, - { - 'given': { - 'name': 'Electron', - 'displayName': 'Electron', - 'family': 'chromium', - 'path': '', - 'version': '99.101.3', - 'majorVersion': 99, - }, - 'expect': true, - }, - { - 'given': { - 'name': 'No display name', - 'family': 'chromium', - }, - 'expect': { - 'key': 'displayName', - 'value': { - 'name': 'No display name', - 'family': 'chromium', - }, - 'type': 'a non-empty string', - }, - }, - { - 'given': { - 'name': 'bad family', - 'displayName': 'Bad family browser', - 'family': 'unknown family', - }, - 'expect': { - 'key': 'family', - 'value': { - 'name': 'bad family', - 'displayName': 'Bad family browser', - 'family': 'unknown family', - }, - 'type': 'either chromium, firefox or webkit', - }, - }, - ], -} - -exports['config/src/validation .isPlainObject returns error message when value is a not an object 1'] = { - 'key': 'mockConfigKey', - 'value': 1, - 'type': 'a plain object', -} - -exports['config/src/validation .isNumber returns error message when value is a not a number 1'] = { - 'key': 'mockConfigKey', - 'value': 'string', - 'type': 'a number', -} - -exports['config/src/validation .isNumberOrFalse returns error message when value is a not number or false 1'] = { - 'key': 'mockConfigKey', - 'value': null, - 'type': 'a number or false', -} - -exports['config/src/validation .isBoolean returns error message when value is a not a string 1'] = { - 'key': 'mockConfigKey', - 'value': 1, - 'type': 'a string', -} - -exports['config/src/validation .isString returns error message when value is a not a string 1'] = { - 'key': 'mockConfigKey', - 'value': 1, - 'type': 'a string', -} - -exports['config/src/validation .isArray returns error message when value is a non-array 1'] = { - 'key': 'mockConfigKey', - 'value': 1, - 'type': 'an array', -} - -exports['config/src/validation .isStringOrFalse returns error message when value is neither string nor false 1'] = { - 'key': 'mockConfigKey', - 'value': null, - 'type': 'a string or false', -} - -exports['not an array error message'] = { - 'key': 'fakeKey', - 'value': 'fakeValue', - 'type': 'an array including any of these values: [true, false]', -} - -exports['not a subset of error message'] = { - 'key': 'fakeKey', - 'value': [ - null, - ], - 'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]', -} - -exports['not all in subset error message'] = { - 'key': 'fakeKey', - 'value': [ - 'fakeValue', - 'fakeValue1', - 'fakeValue2', - 'fakeValue3', - ], - 'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]', -} - -exports['invalid lower bound'] = { - 'key': 'test', - 'value': -1, - 'type': 'a valid CRF number between 1 & 51, 0 or false to disable compression, or true to use the default compression of 32', -} - -exports['invalid upper bound'] = { - 'key': 'test', - 'value': 52, - 'type': 'a valid CRF number between 1 & 51, 0 or false to disable compression, or true to use the default compression of 32', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with invalid strategy 1'] = { - 'key': 'mockConfigKey.experimentalStrategy', - 'value': 'foo', - 'type': 'one of "detect-flake-but-always-fail", "detect-flake-and-pass-on-threshold"', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with invalid strategy w/ other options (valid) 1'] = { - 'key': 'mockConfigKey.experimentalStrategy', - 'value': 'bar', - 'type': 'one of "detect-flake-but-always-fail", "detect-flake-and-pass-on-threshold"', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail: maxRetries is negative 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': -2, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail: maxRetries is 0 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': 0, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold: maxRetries is negative 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': -2, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold: maxRetries is 0 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': 0, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail: maxRetries is floating 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': 3.5, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold: maxRetries is floating 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'value': 3.5, - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold passesRequired is negative 1'] = { - 'key': 'mockConfigKey.experimentalOptions.passesRequired', - 'value': -4, - 'type': 'a positive whole number less than or equals to maxRetries', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold passesRequired is 0 1'] = { - 'key': 'mockConfigKey.experimentalOptions.passesRequired', - 'value': 0, - 'type': 'a positive whole number less than or equals to maxRetries', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold passesRequired is floating 1'] = { - 'key': 'mockConfigKey.experimentalOptions.passesRequired', - 'value': 3.5, - 'type': 'a positive whole number less than or equals to maxRetries', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold provides passesRequired without maxRetries 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold provides passesRequired that is greater than maxRetries 1'] = { - 'key': 'mockConfigKey.experimentalOptions.passesRequired', - 'value': 5, - 'type': 'a positive whole number less than or equals to maxRetries', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold provides stopIfAnyPassed option 1'] = { - 'key': 'mockConfigKey.experimentalOptions', - 'value': { - 'maxRetries': 3, - 'passesRequired': 2, - 'stopIfAnyPassed': true, - }, - 'type': 'an object with keys maxRetries, passesRequired', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail provides passesRequired option 1'] = { - 'key': 'mockConfigKey.experimentalOptions', - 'value': { - 'maxRetries': 3, - 'passesRequired': 2, - 'stopIfAnyPassed': true, - }, - 'type': 'an object with keys maxRetries, stopIfAnyPassed', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail provides stopIfAnyPassed without maxRetries 1'] = { - 'key': 'mockConfigKey.experimentalOptions.maxRetries', - 'type': 'a positive whole number greater than or equals 1 or null', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail stopIfAnyPassed is a number (0 and 1 do not work) 1'] = { - 'key': 'mockConfigKey.experimentalOptions.stopIfAnyPassed', - 'value': 1, - 'type': 'a boolean', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail: valid strategy w/ other invalid options with experiment 1'] = { - 'key': 'mockConfigKey.runMode', - 'value': 1, - 'type': 'a boolean since an experimental strategy is provided', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-and-pass-on-threshold: valid strategy w/ other invalid options with experiment 1'] = { - 'key': 'mockConfigKey.runMode', - 'value': 1, - 'type': 'a boolean since an experimental strategy is provided', -} - -exports['config/src/validation .isValidRetriesConfig returns error message for openMode as boolean without strategy 1'] = { - 'key': 'mockConfigKey.openMode', - 'value': true, - 'type': 'a number since no experimental strategy is provided', -} - -exports['config/src/validation .isValidRetriesConfig returns error message for runMode as boolean without strategy 1'] = { - 'key': 'mockConfigKey.runMode', - 'value': true, - 'type': 'a number since no experimental strategy is provided', -} - -exports['config/src/validation .isValidRetriesConfig experimental options fails with detect-flake-but-always-fail provides maxRetries without stopIfAnyPassed 1'] = { - 'key': 'mockConfigKey.experimentalOptions.stopIfAnyPassed', - 'type': 'is required when using the "detect-flake-but-always-fail" strategy', -} diff --git a/packages/config/package.json b/packages/config/package.json index 72f945a589b..aa05694c4e4 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -12,8 +12,8 @@ "clean-deps": "rimraf node_modules", "lint": "eslint --ext .js,.ts,.json, .", "test": "yarn test-unit", - "test-debug": "yarn test-unit --inspect-brk=5566", - "test-unit": "mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json -r @packages/ts/register 'test/**/*.spec.ts' --exit --timeout 5000", + "test-debug": "npx vitest --inspect-brk --no-file-parallelism --test-timeout=0", + "test-unit": "vitest run", "test:clean": "find ./test/__fixtures__ -depth -name 'output.*' -type f -exec rm {} \\;", "tslint": "tslint --config ../ts/tslint.json --project ." }, @@ -25,7 +25,6 @@ "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "bluebird": "^3.7.2", - "check-more-types": "2.24.0", "common-tags": "1.8.0", "debug": "^4.3.4", "dedent": "^0.7.0", @@ -42,10 +41,8 @@ "@packages/root": "0.0.0-development", "@packages/ts": "0.0.0-development", "@packages/types": "0.0.0-development", - "@types/mocha": "9.1.0", "babel-plugin-tester": "^10.1.0", - "chai": "4.2.0", - "mocha": "7.0.1" + "vitest": "^3.2.4" }, "files": [ "src" diff --git a/packages/config/src/project/utils.ts b/packages/config/src/project/utils.ts index 23dbd7f002a..811f40478a1 100644 --- a/packages/config/src/project/utils.ts +++ b/packages/config/src/project/utils.ts @@ -33,51 +33,48 @@ const hideSpecialVals = function (val: string, key: string) { return val } -// an object with a few utility methods for easy stubbing from unit tests -export const utils = { - getProcessEnvVars (obj: NodeJS.ProcessEnv) { - return _.reduce(obj, (memo: Record, value: string | undefined, key: string) => { - if (!value) { - return memo - } +export function getProcessEnvVars (obj: NodeJS.ProcessEnv) { + return _.reduce(obj, (memo: Record, value: string | undefined, key: string) => { + if (!value) { + return memo + } - if (isCypressEnvLike(key)) { - memo[removeEnvPrefix(key)] = coerce(value) - } + if (isCypressEnvLike(key)) { + memo[removeEnvPrefix(key)] = coerce(value) + } - return memo - }, {}) - }, - - resolveModule (name: string) { - return require.resolve(name) - }, - - // returns: - // false - if the file should not be set - // string - found filename - // null - if there is an error finding the file - discoverModuleFile (options: { - filename: string - projectRoot: string - }) { - debug('discover module file %o', options) - const { filename } = options - - // they have it explicitly set, so it should be there - return fs.pathExists(filename) - .then((found) => { - if (found) { - debug('file exists, assuming it will load') + return memo + }, {}) +} - return filename - } +export function resolveModule (name: string) { + return require.resolve(name) +} + +// returns: +// false - if the file should not be set +// string - found filename +// null - if there is an error finding the file +function discoverModuleFile (options: { + filename: string + projectRoot: string +}) { + debug('discover module file %o', options) + const { filename } = options + + // they have it explicitly set, so it should be there + return fs.pathExists(filename) + .then((found) => { + if (found) { + debug('file exists, assuming it will load') + + return filename + } - debug('could not find %o', { filename }) + debug('could not find %o', { filename }) - return null - }) - }, + return null + }) } const CYPRESS_ENV_PREFIX = 'CYPRESS_' @@ -118,7 +115,8 @@ export function parseEnv (cfg: Record, cliEnvs: Record const configEnv = cfg.env != null ? cfg.env : {} const envFile = cfg.envFile != null ? cfg.envFile : {} - let processEnvs = utils.getProcessEnvVars(process.env) || {} + + let processEnvs = getProcessEnvVars(process.env) || {} cliEnvs = cliEnvs != null ? cliEnvs : {} @@ -304,7 +302,7 @@ export async function setSupportFileAndFolder (obj: Config, getFilesByGlob: any) return Bluebird .try(() => { // resolve full path with extension - obj.supportFile = utils.resolveModule(sf) + obj.supportFile = resolveModule(sf) return debug('resolved support file %s', obj.supportFile) }).then(() => { @@ -333,7 +331,7 @@ export async function setSupportFileAndFolder (obj: Config, getFilesByGlob: any) }).catch({ code: 'MODULE_NOT_FOUND' }, () => { debug('support JS module %s does not load', sf) - return utils.discoverModuleFile({ + return discoverModuleFile({ filename: sf, projectRoot: obj.projectRoot, }) diff --git a/packages/config/src/validation.ts b/packages/config/src/validation.ts index 8143038e5b7..fa05b1c5555 100644 --- a/packages/config/src/validation.ts +++ b/packages/config/src/validation.ts @@ -1,6 +1,5 @@ import path from 'path' import * as _ from 'lodash' -import * as is from 'check-more-types' import { commaListsOr } from 'common-tags' import Debug from 'debug' import { BROWSER_FAMILY } from '@packages/types' @@ -60,19 +59,19 @@ export const validateAny = (...validations: ValidationFn[]): ValidationFn => { * @returns {string|true} Returns `true` if the object is matching browser object schema. Returns an error message if it does not. */ export const isValidBrowser = (browser: any): ErrResult | true => { - if (!is.unemptyString(browser.name)) { + if (!_.isString(browser.name) || _.isEmpty(browser.name)) { return errMsg('name', browser, 'a non-empty string') } - if (!is.oneOf(BROWSER_FAMILY)(browser.family)) { + if (!_.includes(BROWSER_FAMILY, browser.family)) { return errMsg('family', browser, commaListsOr`either ${BROWSER_FAMILY}`) } - if (!is.unemptyString(browser.displayName)) { + if (!_.isString(browser.displayName) || _.isEmpty(browser.displayName)) { return errMsg('displayName', browser, 'a non-empty string') } - if (!is.unemptyString(browser.version)) { + if (!_.isString(browser.version) || _.isEmpty(browser.version)) { return errMsg('version', browser, 'a non-empty string') } @@ -80,7 +79,7 @@ export const isValidBrowser = (browser: any): ErrResult | true => { return errMsg('path', browser, 'a string') } - if (typeof browser.majorVersion !== 'string' && !(is.number(browser.majorVersion) && browser.majorVersion > 0)) { + if (typeof browser.majorVersion !== 'string' && !(_.isNumber(browser.majorVersion) && browser.majorVersion > 0)) { return errMsg('majorVersion', browser, 'a string or a positive number') } diff --git a/packages/config/test/__snapshots__/index.spec.ts.snap b/packages/config/test/__snapshots__/index.spec.ts.snap new file mode 100644 index 00000000000..47b32a39d91 --- /dev/null +++ b/packages/config/test/__snapshots__/index.spec.ts.snap @@ -0,0 +1,271 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`config/src/index > .getBreakingKeys > returns list of breaking config keys 1`] = ` +[ + "experimentalJustInTimeCompile", + "experimentalSessionAndOrigin", + "experimentalSkipDomainInjection", + "videoUploadOnPasses", +] +`; + +exports[`config/src/index > .getDefaultValues > returns list of public config keys 1`] = ` +{ + "additionalIgnorePattern": [], + "animationDistanceThreshold": 5, + "autoOpen": false, + "baseUrl": null, + "blockHosts": null, + "browsers": [], + "chromeWebSecurity": true, + "clientCertificates": [], + "clientRoute": "/__/", + "component": { + "indexHtmlFile": "cypress/support/component-index.html", + "specPattern": "**/*.cy.{js,jsx,ts,tsx}", + }, + "configFile": "cypress.config.js", + "cypressBinaryRoot": "/root/cypress", + "defaultBrowser": null, + "defaultCommandTimeout": 4000, + "devServerPublicPathRoute": "/__cypress/src", + "downloadsFolder": "cypress/downloads", + "e2e": { + "specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", + }, + "env": {}, + "excludeSpecPattern": "*.hot-update.js", + "execTimeout": 60000, + "experimentalCspAllowList": false, + "experimentalInteractiveRunEvents": false, + "experimentalMemoryManagement": false, + "experimentalModifyObstructiveThirdPartyCode": false, + "experimentalOriginDependencies": false, + "experimentalRunAllSpecs": false, + "experimentalSingleTabRunMode": false, + "experimentalSourceRewriting": false, + "experimentalStudio": false, + "experimentalWebKitSupport": false, + "fileServerFolder": "", + "fixturesFolder": "cypress/fixtures", + "hideCommandLog": false, + "hideRunnerUi": false, + "hosts": null, + "includeShadowDom": false, + "injectDocumentDomain": false, + "isDefaultProtocolEnabled": false, + "isInteractive": true, + "isTextTerminal": false, + "justInTimeCompile": true, + "keystrokeDelay": 0, + "modifyObstructiveCode": true, + "morgan": true, + "namespace": "__cypress", + "numTestsKeptInMemory": 50, + "pageLoadTimeout": 60000, + "port": null, + "projectId": null, + "redirectionLimit": 20, + "repoRoot": null, + "reporter": "spec", + "reporterOptions": null, + "reporterRoute": "/__cypress/reporter", + "requestTimeout": 5000, + "resolvedNodePath": null, + "resolvedNodeVersion": null, + "responseTimeout": 30000, + "retries": { + "experimentalOptions": undefined, + "experimentalStrategy": undefined, + "openMode": 0, + "runMode": 0, + }, + "screenshotOnRunFailure": true, + "screenshotsFolder": "cypress/screenshots", + "scrollBehavior": "top", + "slowTestThreshold": 10000, + "socketId": null, + "socketIoCookie": "__socket", + "socketIoRoute": "/__socket", + "specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", + "supportFile": "cypress/support/e2e.{js,jsx,ts,tsx}", + "supportFolder": false, + "taskTimeout": 60000, + "testIsolation": true, + "trashAssetsBeforeRuns": true, + "userAgent": null, + "video": false, + "videoCompression": false, + "videosFolder": "cypress/videos", + "viewportHeight": 660, + "viewportWidth": 1000, + "waitForAnimations": true, + "watchForFileChanges": true, +} +`; + +exports[`config/src/index > .getDefaultValues > returns list of public config keys for selected testing type 1`] = ` +{ + "additionalIgnorePattern": [], + "animationDistanceThreshold": 5, + "autoOpen": false, + "baseUrl": null, + "blockHosts": null, + "browsers": [], + "chromeWebSecurity": true, + "clientCertificates": [], + "clientRoute": "/__/", + "component": { + "indexHtmlFile": "cypress/support/component-index.html", + "specPattern": "**/*.cy.{js,jsx,ts,tsx}", + }, + "configFile": "cypress.config.js", + "cypressBinaryRoot": "/root/cypress", + "defaultBrowser": null, + "defaultCommandTimeout": 4000, + "devServerPublicPathRoute": "/__cypress/src", + "downloadsFolder": "cypress/downloads", + "e2e": { + "specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", + }, + "env": {}, + "excludeSpecPattern": "*.hot-update.js", + "execTimeout": 60000, + "experimentalCspAllowList": false, + "experimentalInteractiveRunEvents": false, + "experimentalMemoryManagement": false, + "experimentalModifyObstructiveThirdPartyCode": false, + "experimentalOriginDependencies": false, + "experimentalRunAllSpecs": false, + "experimentalSingleTabRunMode": false, + "experimentalSourceRewriting": false, + "experimentalStudio": false, + "experimentalWebKitSupport": false, + "fileServerFolder": "", + "fixturesFolder": "cypress/fixtures", + "hideCommandLog": false, + "hideRunnerUi": false, + "hosts": null, + "includeShadowDom": false, + "injectDocumentDomain": false, + "isDefaultProtocolEnabled": false, + "isInteractive": true, + "isTextTerminal": false, + "justInTimeCompile": true, + "keystrokeDelay": 0, + "modifyObstructiveCode": true, + "morgan": true, + "namespace": "__cypress", + "numTestsKeptInMemory": 50, + "pageLoadTimeout": 60000, + "port": null, + "projectId": null, + "redirectionLimit": 20, + "repoRoot": null, + "reporter": "spec", + "reporterOptions": null, + "reporterRoute": "/__cypress/reporter", + "requestTimeout": 5000, + "resolvedNodePath": null, + "resolvedNodeVersion": null, + "responseTimeout": 30000, + "retries": { + "experimentalOptions": undefined, + "experimentalStrategy": undefined, + "openMode": 0, + "runMode": 0, + }, + "screenshotOnRunFailure": true, + "screenshotsFolder": "cypress/screenshots", + "scrollBehavior": "top", + "slowTestThreshold": 10000, + "socketId": null, + "socketIoCookie": "__socket", + "socketIoRoute": "/__socket", + "specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", + "supportFile": "cypress/support/e2e.{js,jsx,ts,tsx}", + "supportFolder": false, + "taskTimeout": 60000, + "testIsolation": true, + "trashAssetsBeforeRuns": true, + "userAgent": null, + "video": false, + "videoCompression": false, + "videosFolder": "cypress/videos", + "viewportHeight": 660, + "viewportWidth": 1000, + "waitForAnimations": true, + "watchForFileChanges": true, +} +`; + +exports[`config/src/index > .getPublicConfigKeys > returns list of public config keys 1`] = ` +[ + "animationDistanceThreshold", + "arch", + "baseUrl", + "blockHosts", + "chromeWebSecurity", + "clientCertificates", + "component", + "defaultBrowser", + "defaultCommandTimeout", + "downloadsFolder", + "e2e", + "env", + "execTimeout", + "experimentalCspAllowList", + "experimentalInteractiveRunEvents", + "experimentalRunAllSpecs", + "experimentalMemoryManagement", + "experimentalModifyObstructiveThirdPartyCode", + "injectDocumentDomain", + "experimentalOriginDependencies", + "experimentalSourceRewriting", + "experimentalSingleTabRunMode", + "experimentalStudio", + "experimentalWebKitSupport", + "fileServerFolder", + "fixturesFolder", + "excludeSpecPattern", + "includeShadowDom", + "justInTimeCompile", + "keystrokeDelay", + "modifyObstructiveCode", + "numTestsKeptInMemory", + "platform", + "pageLoadTimeout", + "port", + "projectId", + "redirectionLimit", + "reporter", + "reporterOptions", + "requestTimeout", + "resolvedNodePath", + "resolvedNodeVersion", + "responseTimeout", + "retries", + "screenshotOnRunFailure", + "screenshotsFolder", + "slowTestThreshold", + "scrollBehavior", + "supportFile", + "supportFolder", + "taskTimeout", + "testIsolation", + "trashAssetsBeforeRuns", + "userAgent", + "video", + "videoCompression", + "videosFolder", + "viewportHeight", + "viewportWidth", + "waitForAnimations", + "watchForFileChanges", + "specPattern", + "browsers", + "hosts", + "isInteractive", + "modifyObstructiveCode", +] +`; diff --git a/packages/config/test/__snapshots__/validation.spec.ts.snap b/packages/config/test/__snapshots__/validation.spec.ts.snap new file mode 100644 index 00000000000..e41b00ce955 --- /dev/null +++ b/packages/config/test/__snapshots__/validation.spec.ts.snap @@ -0,0 +1,513 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`config/src/validation > .isArray > returns error message when value is a non-array 1`] = ` +{ + "key": "mockConfigKey", + "type": "an array", + "value": 1, +} +`; + +exports[`config/src/validation > .isArrayIncludingAny > returned validation function will fail if any values are not present in the provided values > not a subset of error message 1`] = ` +{ + "key": "fakeKey", + "type": "an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]", + "value": [ + null, + ], +} +`; + +exports[`config/src/validation > .isArrayIncludingAny > returned validation function will fail if any values are not present in the provided values > not all in subset error message 1`] = ` +{ + "key": "fakeKey", + "type": "an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]", + "value": [ + "fakeValue", + "fakeValue1", + "fakeValue2", + "fakeValue3", + ], +} +`; + +exports[`config/src/validation > .isArrayIncludingAny > returned validation function will fail if values is not an array > not an array error message 1`] = ` +{ + "key": "fakeKey", + "type": "an array including any of these values: [true, false]", + "value": "fakeValue", +} +`; + +exports[`config/src/validation > .isBoolean > returns error message when value is a not a string 1`] = ` +{ + "key": "mockConfigKey", + "type": "a string", + "value": 1, +} +`; + +exports[`config/src/validation > .isFullyQualifiedUrl > returns error message when value is a not qualified url > empty string 1`] = ` +{ + "key": "mockConfigKey", + "type": "a fully qualified URL (starting with \`http://\` or \`https://\`)", + "value": "", +} +`; + +exports[`config/src/validation > .isFullyQualifiedUrl > returns error message when value is a not qualified url > not qualified url 1`] = ` +{ + "key": "mockConfigKey", + "type": "a fully qualified URL (starting with \`http://\` or \`https://\`)", + "value": "url.com", +} +`; + +exports[`config/src/validation > .isNumber > returns error message when value is a not a number 1`] = ` +{ + "key": "mockConfigKey", + "type": "a number", + "value": "string", +} +`; + +exports[`config/src/validation > .isNumberOrFalse > returns error message when value is a not number or false 1`] = ` +{ + "key": "mockConfigKey", + "type": "a number or false", + "value": null, +} +`; + +exports[`config/src/validation > .isOneOf > validates a number > not one of the numbers error message 1`] = ` +{ + "key": "test", + "type": "one of these values: 1, 2, 3", + "value": 4, +} +`; + +exports[`config/src/validation > .isOneOf > validates a number > null instead of a number 1`] = ` +{ + "key": "test", + "type": "one of these values: 1, 2, 3", + "value": null, +} +`; + +exports[`config/src/validation > .isOneOf > validates a number > string instead of a number 1`] = ` +{ + "key": "test", + "type": "one of these values: 1, 2, 3", + "value": "foo", +} +`; + +exports[`config/src/validation > .isOneOf > validates a string > not one of the strings error message 1`] = ` +{ + "key": "test", + "type": "one of these values: "foo", "bar"", + "value": "nope", +} +`; + +exports[`config/src/validation > .isOneOf > validates a string > null instead of string 1`] = ` +{ + "key": "test", + "type": "one of these values: "foo", "bar"", + "value": null, +} +`; + +exports[`config/src/validation > .isOneOf > validates a string > number instead of string 1`] = ` +{ + "key": "test", + "type": "one of these values: "foo", "bar"", + "value": 42, +} +`; + +exports[`config/src/validation > .isPlainObject > returns error message when value is a not an object 1`] = ` +{ + "key": "mockConfigKey", + "type": "a plain object", + "value": 1, +} +`; + +exports[`config/src/validation > .isString > returns error message when value is a not a string 1`] = ` +{ + "key": "mockConfigKey", + "type": "a string", + "value": 1, +} +`; + +exports[`config/src/validation > .isStringOrArrayOfStrings > returns error message when value is neither string nor array of string > array of non-strings 1`] = ` +{ + "key": "mockConfigKey", + "type": "a string or an array of strings", + "value": [ + 1, + 2, + 3, + ], +} +`; + +exports[`config/src/validation > .isStringOrArrayOfStrings > returns error message when value is neither string nor array of string > not string or array 1`] = ` +{ + "key": "mockConfigKey", + "type": "a string or an array of strings", + "value": null, +} +`; + +exports[`config/src/validation > .isStringOrFalse > returns error message when value is neither string nor false 1`] = ` +{ + "key": "mockConfigKey", + "type": "a string or false", + "value": null, +} +`; + +exports[`config/src/validation > .isValidBrowser > passes valid browsers and forms error messages for invalid ones > isValidBrowser Chrome 1`] = ` +{ + "browser": { + "displayName": "Chrome Browser", + "family": "chromium", + "majorVersion": 1, + "name": "Chrome", + "path": "/path/to/chrome", + "version": "1.2.3", + }, + "isValid": true, +} +`; + +exports[`config/src/validation > .isValidBrowser > passes valid browsers and forms error messages for invalid ones > isValidBrowser Electron 1`] = ` +{ + "browser": { + "displayName": "Electron", + "family": "chromium", + "majorVersion": 99, + "name": "Electron", + "path": "", + "version": "99.101.3", + }, + "isValid": true, +} +`; + +exports[`config/src/validation > .isValidBrowser > passes valid browsers and forms error messages for invalid ones > isValidBrowser FF 1`] = ` +{ + "browser": { + "displayName": "Firefox", + "family": "firefox", + "majorVersion": "1", + "name": "FF", + "path": "/path/to/firefox", + "version": "1.2.3", + }, + "isValid": true, +} +`; + +exports[`config/src/validation > .isValidBrowser > passes valid browsers and forms error messages for invalid ones > isValidBrowser No display name 1`] = ` +{ + "browser": { + "family": "chromium", + "name": "No display name", + }, + "isValid": { + "key": "displayName", + "type": "a non-empty string", + "value": { + "family": "chromium", + "name": "No display name", + }, + }, +} +`; + +exports[`config/src/validation > .isValidBrowser > passes valid browsers and forms error messages for invalid ones > isValidBrowser bad family 1`] = ` +{ + "browser": { + "displayName": "Bad family browser", + "family": "unknown family", + "name": "bad family", + }, + "isValid": { + "key": "family", + "type": "either chromium, firefox or webkit", + "value": { + "displayName": "Bad family browser", + "family": "unknown family", + "name": "bad family", + }, + }, +} +`; + +exports[`config/src/validation > .isValidBrowserList > does not allow empty or not browsers > browsers list with a string 1`] = ` +{ + "key": "name", + "list": "browsers", + "type": "a non-empty string", + "value": "foo", +} +`; + +exports[`config/src/validation > .isValidBrowserList > does not allow empty or not browsers > empty list of browsers 1`] = `"Expected at least one browser"`; + +exports[`config/src/validation > .isValidBrowserList > does not allow empty or not browsers > undefined browsers 1`] = `"Missing browsers list"`; + +exports[`config/src/validation > .isValidClientCertificatesSet > returns error message for certs not passed as an array array 1`] = ` +{ + "key": "mockConfigKey", + "type": "a positive number or null or an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls, or experimental configuration with key "experimentalStrategy" with value "detect-flake-but-always-fail" or "detect-flake-and-pass-on-threshold" and key "experimentalOptions" to provide a valid configuration for your selected strategy", + "value": "1", +} +`; + +exports[`config/src/validation > .isValidClientCertificatesSet > returns error message for certs object without url 1`] = ` +{ + "key": "clientCertificates[0].url", + "type": "a URL matcher", + "value": undefined, +} +`; + +exports[`config/src/validation > .isValidClientCertificatesSet > returns error message for certs url not matching * > invalid url 1`] = ` +{ + "key": "clientCertificates[0].url", + "type": "a valid URL", + "value": "not *", +} +`; + +exports[`config/src/validation > .isValidClientCertificatesSet > returns error message for certs url not matching * > missing https protocol 1`] = ` +{ + "key": "clientCertificates[0].url", + "type": "an https protocol", + "value": "http://url.com", +} +`; + +exports[`config/src/validation > .isValidCrfOrBoolean > invalidates lower bound > invalid lower bound 1`] = ` +{ + "key": "test", + "type": "a valid CRF number between 1 & 51, 0 or false to disable compression, or true to use the default compression of 32", + "value": -1, +} +`; + +exports[`config/src/validation > .isValidCrfOrBoolean > invalidates upper bound > invalid upper bound 1`] = ` +{ + "key": "test", + "type": "a valid CRF number between 1 & 51, 0 or false to disable compression, or true to use the default compression of 32", + "value": 52, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > passesRequired is 0 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.passesRequired", + "type": "a positive whole number less than or equals to maxRetries", + "value": 0, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > passesRequired is floating 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.passesRequired", + "type": "a positive whole number less than or equals to maxRetries", + "value": 3.5, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > passesRequired is negative 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.passesRequired", + "type": "a positive whole number less than or equals to maxRetries", + "value": -4, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > provides passesRequired that is greater than maxRetries 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.passesRequired", + "type": "a positive whole number less than or equals to maxRetries", + "value": 5, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > provides passesRequired without maxRetries 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": undefined, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold > provides stopIfAnyPassed option 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions", + "type": "an object with keys maxRetries, passesRequired", + "value": { + "maxRetries": 3, + "passesRequired": 2, + "stopIfAnyPassed": true, + }, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold: maxRetries is 0 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": 0, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold: maxRetries is floating 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": 3.5, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold: maxRetries is negative 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": -2, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-and-pass-on-threshold: valid strategy w/ other invalid options with experiment 1`] = ` +{ + "key": "mockConfigKey.runMode", + "type": "a boolean since an experimental strategy is provided", + "value": 1, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail > provides maxRetries without stopIfAnyPassed 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.stopIfAnyPassed", + "type": "is required when using the "detect-flake-but-always-fail" strategy", + "value": undefined, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail > provides passesRequired option 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions", + "type": "an object with keys maxRetries, stopIfAnyPassed", + "value": { + "maxRetries": 3, + "passesRequired": 2, + "stopIfAnyPassed": true, + }, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail > provides stopIfAnyPassed without maxRetries 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": undefined, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail > stopIfAnyPassed is a number (0 and 1 do not work) 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.stopIfAnyPassed", + "type": "a boolean", + "value": 1, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail: maxRetries is 0 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": 0, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail: maxRetries is floating 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": 3.5, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail: maxRetries is negative 1`] = ` +{ + "key": "mockConfigKey.experimentalOptions.maxRetries", + "type": "a positive whole number greater than or equals 1 or null", + "value": -2, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > detect-flake-but-always-fail: valid strategy w/ other invalid options with experiment 1`] = ` +{ + "key": "mockConfigKey.runMode", + "type": "a boolean since an experimental strategy is provided", + "value": 1, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > invalid strategy 1`] = ` +{ + "key": "mockConfigKey.experimentalStrategy", + "type": "one of "detect-flake-but-always-fail", "detect-flake-and-pass-on-threshold"", + "value": "foo", +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > experimental options > fails with > invalid strategy w/ other options (valid) 1`] = ` +{ + "key": "mockConfigKey.experimentalStrategy", + "type": "one of "detect-flake-but-always-fail", "detect-flake-and-pass-on-threshold"", + "value": "bar", +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > returns error message for invalid retry config > invalid retry object 1`] = ` +{ + "key": "mockConfigKey", + "type": "an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls", + "value": { + "fakeMode": 1, + }, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > returns error message for invalid retry config > invalid retry value 1`] = ` +{ + "key": "mockConfigKey", + "type": "a positive number or null or an object with keys "openMode" and "runMode" with values of numbers, booleans, or nulls, or experimental configuration with key "experimentalStrategy" with value "detect-flake-but-always-fail" or "detect-flake-and-pass-on-threshold" and key "experimentalOptions" to provide a valid configuration for your selected strategy", + "value": "1", +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > returns error message for openMode as boolean without strategy 1`] = ` +{ + "key": "mockConfigKey.openMode", + "type": "a number since no experimental strategy is provided", + "value": true, +} +`; + +exports[`config/src/validation > .isValidRetriesConfig > returns error message for runMode as boolean without strategy 1`] = ` +{ + "key": "mockConfigKey.runMode", + "type": "a number since no experimental strategy is provided", + "value": true, +} +`; diff --git a/packages/config/test/ast-utils/addToCypressConfig.spec.ts b/packages/config/test/ast-utils/addToCypressConfig.spec.ts index 3929bdf6d98..c5f65aa6694 100644 --- a/packages/config/test/ast-utils/addToCypressConfig.spec.ts +++ b/packages/config/test/ast-utils/addToCypressConfig.spec.ts @@ -1,24 +1,26 @@ -import proxyquire from 'proxyquire' -import fsExtra from 'fs-extra' -import sinon from 'sinon' +import { vi, describe, it, expect } from 'vitest' +import fs from 'fs-extra' import path from 'path' -import { expect } from 'chai' import dedent from 'dedent' - -const stub = sinon.stub() - -beforeEach(() => { - stub.reset() +import { addTestingTypeToCypressConfig } from '../../src/ast-utils/addToCypressConfig' + +vi.mock('fs-extra', async (importActual) => { + const actual = await importActual() + + return { + default: { + // @ts-expect-error + ...actual.default, + writeFile: vi.fn(), + }, + } }) -const { addTestingTypeToCypressConfig } = proxyquire('../../src/ast-utils/addToCypressConfig', { - 'fs-extra': { - ...fsExtra, - writeFile: stub, - }, -}) as typeof import('../../src/ast-utils/addToCypressConfig') - describe('addToCypressConfig', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + it('will create a ts file if the file is empty and the file path is ts', async () => { const result = await addTestingTypeToCypressConfig({ filePath: path.join(__dirname, '../__fixtures__/empty.config.ts'), @@ -29,7 +31,10 @@ describe('addToCypressConfig', () => { projectRoot: __dirname, }) - expect(stub.firstCall.args[1].trim()).to.eq(dedent` + // @ts-expect-error - mock argument + const secondArgTrimmed = fs.writeFile.mock.calls[0][1].trim() + + expect(secondArgTrimmed).toEqual(dedent` import { defineConfig } from "cypress"; export default defineConfig({ @@ -41,7 +46,7 @@ describe('addToCypressConfig', () => { }); `) - expect(result.result).to.eq('ADDED') + expect(result.result).toEqual('ADDED') }) it('will create a module file if the file is empty and the project is ECMA Script', async () => { @@ -54,19 +59,22 @@ describe('addToCypressConfig', () => { projectRoot: __dirname, }) - expect(stub.firstCall.args[1].trim()).to.eq(dedent` - import { defineConfig } from "cypress"; - - export default defineConfig({ - e2e: { - setupNodeEvents(on, config) { - // implement node event listeners here - }, - }, - }); - `) - - expect(result.result).to.eq('ADDED') + // @ts-expect-error - mock argument + const secondArgTrimmed = fs.writeFile.mock.calls[0][1].trim() + + expect(secondArgTrimmed).toEqual(dedent` + import { defineConfig } from "cypress"; + + export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, + }); + `) + + expect(result.result).toEqual('ADDED') }) it('will create a js file if the file is empty and the file path is js', async () => { @@ -79,19 +87,22 @@ describe('addToCypressConfig', () => { projectRoot: __dirname, }) - expect(stub.firstCall.args[1].trim()).to.eq(dedent` - const { defineConfig } = require("cypress"); - - module.exports = defineConfig({ - e2e: { - setupNodeEvents(on, config) { - // implement node event listeners here - }, - }, - }); - `) - - expect(result.result).to.eq('ADDED') + // @ts-expect-error - mock argument + const secondArgTrimmed = fs.writeFile.mock.calls[0][1].trim() + + expect(secondArgTrimmed).toEqual(dedent` + const { defineConfig } = require("cypress"); + + module.exports = defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, + }); + `) + + expect(result.result).toEqual('ADDED') }) it('will exclude defineConfig if cypress can\'t be imported from the projectRoot', async () => { @@ -104,7 +115,10 @@ describe('addToCypressConfig', () => { projectRoot: '/foo', }) - expect(stub.firstCall.args[1].trim()).to.eq(dedent` + // @ts-expect-error - mock argument + const secondArgTrimmed = fs.writeFile.mock.calls[0][1].trim() + + expect(secondArgTrimmed).toEqual(dedent` module.exports = { e2e: { setupNodeEvents(on, config) { @@ -114,7 +128,7 @@ describe('addToCypressConfig', () => { }; `) - expect(result.result).to.eq('ADDED') + expect(result.result).toEqual('ADDED') }) it('will exclude defineConfig if cypress can\'t be imported from the projectRoot for an ECMA Script project', async () => { @@ -127,7 +141,10 @@ describe('addToCypressConfig', () => { projectRoot: '/foo', }) - expect(stub.firstCall.args[1].trim()).to.eq(dedent` + // @ts-expect-error - mock argument + const secondArgTrimmed = fs.writeFile.mock.calls[0][1].trim() + + expect(secondArgTrimmed).toEqual(dedent` export default { e2e: { setupNodeEvents(on, config) { @@ -137,7 +154,7 @@ describe('addToCypressConfig', () => { }; `) - expect(result.result).to.eq('ADDED') + expect(result.result).toEqual('ADDED') }) it('will error if we are unable to add to the config', async () => { @@ -150,8 +167,8 @@ describe('addToCypressConfig', () => { projectRoot: __dirname, }) - expect(result.result).to.eq('NEEDS_MERGE') - expect(result.error.message).to.eq('Unable to automerge with the config file') + expect(result.result).toEqual('NEEDS_MERGE') + expect(result.error.message).toEqual('Unable to automerge with the config file') }) it('will error if the key we are adding already exists', async () => { @@ -164,7 +181,7 @@ describe('addToCypressConfig', () => { projectRoot: __dirname, }) - expect(result.result).to.eq('NEEDS_MERGE') - expect(result.error.message).to.eq('Unable to automerge with the config file') + expect(result.result).toEqual('NEEDS_MERGE') + expect(result.error.message).toEqual('Unable to automerge with the config file') }) }) diff --git a/packages/config/test/index.spec.ts b/packages/config/test/index.spec.ts index a62852715a1..56e562f2ff0 100644 --- a/packages/config/test/index.spec.ts +++ b/packages/config/test/index.spec.ts @@ -1,14 +1,7 @@ -import chai from 'chai' +import { vi, describe, it, expect } from 'vitest' import path from 'path' -import snapshot from 'snap-shot-it' -import sinon from 'sinon' -import sinonChai from 'sinon-chai' - import * as configUtil from '../src/index' -chai.use(sinonChai) -const { expect } = chai - describe('config/src/index', () => { describe('.allowed', () => { it('returns filter config only containing allowed keys', () => { @@ -19,7 +12,7 @@ describe('config/src/index', () => { 'random': 'not a config option', }) - expect(keys).to.deep.eq({ + expect(keys).toEqual({ 'baseUrl': 'https://url.com', 'videoUploadOnPasses': true, }) @@ -31,7 +24,7 @@ describe('config/src/index', () => { const breakingKeys = configUtil.getBreakingKeys() expect(breakingKeys).to.include('videoUploadOnPasses') - snapshot(breakingKeys) + expect(breakingKeys).toMatchSnapshot() }) }) @@ -39,16 +32,16 @@ describe('config/src/index', () => { it('returns list of public config keys', () => { const defaultValues = configUtil.getDefaultValues() - expect(defaultValues).to.deep.include({ + expect(defaultValues).toEqual(expect.objectContaining({ defaultCommandTimeout: 4000, scrollBehavior: 'top', watchForFileChanges: true, - }) + })) - expect(defaultValues.env).to.deep.eq({}) + expect(defaultValues.env).toEqual({}) const cypressBinaryRoot = defaultValues.cypressBinaryRoot.split(path.sep).pop() - expect(cypressBinaryRoot).to.eq('cypress') + expect(cypressBinaryRoot).toEqual('cypress') defaultValues.cypressBinaryRoot = `/root/cypress` // remove these since they are different depending on your machine @@ -57,22 +50,22 @@ describe('config/src/index', () => { delete defaultValues[x] }) - snapshot(defaultValues) + expect(defaultValues).toMatchSnapshot() }) it('returns list of public config keys for selected testing type', () => { const defaultValues = configUtil.getDefaultValues({ testingType: 'e2e' }) - expect(defaultValues).to.deep.include({ + expect(defaultValues).toEqual(expect.objectContaining({ defaultCommandTimeout: 4000, scrollBehavior: 'top', watchForFileChanges: true, - }) + })) - expect(defaultValues.env).to.deep.eq({}) + expect(defaultValues.env).toEqual({}) const cypressBinaryRoot = defaultValues.cypressBinaryRoot.split(path.sep).pop() - expect(cypressBinaryRoot).to.eq('cypress') + expect(cypressBinaryRoot).toEqual('cypress') defaultValues.cypressBinaryRoot = `/root/cypress` // remove these since they are different depending on your machine @@ -81,7 +74,7 @@ describe('config/src/index', () => { delete defaultValues[x] }) - snapshot(defaultValues) + expect(defaultValues).toMatchSnapshot() }) }) @@ -89,9 +82,9 @@ describe('config/src/index', () => { it('returns list of public config keys', () => { const publicConfigKeys = configUtil.getPublicConfigKeys() - expect(publicConfigKeys).to.include('blockHosts') - expect(publicConfigKeys).to.not.include('devServerPublicPathRoute') - snapshot(publicConfigKeys) + expect(publicConfigKeys).toContain('blockHosts') + expect(publicConfigKeys).not.toContain('devServerPublicPathRoute') + expect(publicConfigKeys).toMatchSnapshot() }) }) @@ -99,22 +92,22 @@ describe('config/src/index', () => { it('returns normalized key when config key has a default value', () => { let normalizedKey = configUtil.matchesConfigKey('EXEC_TIMEOUT') - expect(normalizedKey).to.eq('execTimeout') + expect(normalizedKey).toEqual('execTimeout') normalizedKey = configUtil.matchesConfigKey('Base-url') - expect(normalizedKey).to.eq('baseUrl') + expect(normalizedKey).toEqual('baseUrl') }) it('returns nothing when config key does not has a default value', () => { let normalizedKey = configUtil.matchesConfigKey('random') - expect(normalizedKey).to.be.undefined + expect(normalizedKey).toBeUndefined() }) }) describe('.validate', () => { it('validates config', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() const config = { e2e: { testIsolation: false, @@ -127,38 +120,38 @@ describe('config/src/index', () => { } configUtil.validate(config, errorFn, null) - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) configUtil.validate(config, errorFn, 'e2e') - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) configUtil.validate(config, errorFn, 'component') - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) }) it('calls error callback if config is invalid', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() configUtil.validate({ 'baseUrl': ' ', }, errorFn, 'e2e') - expect(errorFn).to.have.been.calledWithMatch({ key: 'baseUrl' }) - expect(errorFn).to.have.been.calledWithMatch({ type: 'a fully qualified URL (starting with `http://` or `https://`)' }) + expect(errorFn).toHaveBeenCalledWith(expect.objectContaining({ key: 'baseUrl' })) + expect(errorFn).toHaveBeenCalledWith(expect.objectContaining({ type: 'a fully qualified URL (starting with `http://` or `https://`)' })) }) }) describe('.validateNoBreakingConfig', () => { it('calls warning callback if config contains breaking option that warns', () => { - const warningFn = sinon.spy() - const errorFn = sinon.spy() + const warningFn = vi.fn() + const errorFn = vi.fn() configUtil.validateNoBreakingConfig({ 'experimentalSessionAndOrigin': 'should break', configFile: 'config.js', }, warningFn, errorFn, 'e2e') - expect(warningFn).to.have.been.calledOnceWith('EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED', { + expect(warningFn).toHaveBeenCalledExactlyOnceWith('EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED', { name: 'experimentalSessionAndOrigin', newName: undefined, value: undefined, @@ -166,20 +159,20 @@ describe('config/src/index', () => { configFile: 'config.js', }) - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) }) it('calls error callback if config contains breaking option that should throw an error', () => { - const warningFn = sinon.spy() - const errorFn = sinon.spy() + const warningFn = vi.fn() + const errorFn = vi.fn() configUtil.validateNoBreakingConfig({ experimentalSkipDomainInjection: true, configFile: 'config.js', }, warningFn, errorFn, 'e2e') - expect(warningFn).to.have.been.callCount(0) - expect(errorFn).to.have.been.calledOnceWith('EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED', { + expect(warningFn).toHaveBeenCalledTimes(0) + expect(errorFn).toHaveBeenCalledExactlyOnceWith('EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED', { name: 'experimentalSkipDomainInjection', newName: undefined, value: undefined, @@ -191,68 +184,68 @@ describe('config/src/index', () => { describe('.validateOverridableAtRunTime', () => { it('calls onError handler if configuration override level=never', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() configUtil.validateOverridableAtRunTime({ chromeWebSecurity: false }, false, errorFn) - expect(errorFn).to.have.callCount(1) - expect(errorFn).to.have.been.calledWithMatch({ + expect(errorFn).toHaveBeenCalledTimes(1) + expect(errorFn).toHaveBeenCalledWith(expect.objectContaining({ invalidConfigKey: 'chromeWebSecurity', supportedOverrideLevel: 'never', - }) + })) }) describe('configuration override level=suite', () => { it('does not calls onError handler if validating level is suite', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() const isSuiteOverride = true configUtil.validateOverridableAtRunTime({ testIsolation: true }, isSuiteOverride, errorFn) - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) }) it('calls onError handler if validating level is not suite', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() const isSuiteOverride = false configUtil.validateOverridableAtRunTime({ testIsolation: 'off' }, isSuiteOverride, errorFn) - expect(errorFn).to.have.callCount(1) - expect(errorFn).to.have.been.calledWithMatch({ + expect(errorFn).toHaveBeenCalledTimes(1) + expect(errorFn).toHaveBeenCalledWith(expect.objectContaining({ invalidConfigKey: 'testIsolation', supportedOverrideLevel: 'suite', - }) + })) }) }) it(`does not call onErr if config override level=any`, () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() configUtil.validateOverridableAtRunTime({ requestTimeout: 1000 }, false, errorFn) - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) }) it('does not call onErr if configuration is a non-Cypress config option', () => { - const errorFn = sinon.spy() + const errorFn = vi.fn() configUtil.validateOverridableAtRunTime({ foo: 'bar' }, true, errorFn) - expect(errorFn).to.have.callCount(0) + expect(errorFn).toHaveBeenCalledTimes(0) }) }) describe('.validateNeedToRestartOnChange', () => { it('returns the need to restart if given key has changed', () => { - expect(configUtil.validateNeedToRestartOnChange({ blockHosts: [] }, { blockHosts: ['https://example.com'] })).to.eql({ + expect(configUtil.validateNeedToRestartOnChange({ blockHosts: [] }, { blockHosts: ['https://example.com'] })).toEqual({ server: true, browser: false, }) - expect(configUtil.validateNeedToRestartOnChange({ injectDocumentDomain: true }, {})).to.eql({ + expect(configUtil.validateNeedToRestartOnChange({ injectDocumentDomain: true }, {})).toEqual({ server: true, browser: false, }) diff --git a/packages/config/test/project/index.spec.ts b/packages/config/test/project/index.spec.ts index 82350d2a115..4c9e0f69af5 100644 --- a/packages/config/test/project/index.spec.ts +++ b/packages/config/test/project/index.spec.ts @@ -1,19 +1,29 @@ -import { expect } from 'chai' - +import { vi, describe, it, expect } from 'vitest' import errors from '@packages/errors' - import { updateWithPluginValues } from '../../src/project' +vi.mock('@packages/errors', async (importActual) => { + const actual = await importActual() + + return { + default: { + // @ts-expect-error + ...actual.default, + throwErr: vi.fn(), + }, + } +}) + describe('config/src/project/index', () => { - context('.updateWithPluginValues', () => { + describe('.updateWithPluginValues', () => { it('is noop when no overrides', () => { - expect(updateWithPluginValues({ foo: 'bar' } as any, null as any, 'e2e')).to.deep.eq({ + expect(updateWithPluginValues({ foo: 'bar' } as any, null as any, 'e2e')).toEqual({ foo: 'bar', }) }) it('is noop with empty overrides', () => { - expect(updateWithPluginValues({ foo: 'bar' } as any, {} as any, 'e2e')).to.deep.eq({ + expect(updateWithPluginValues({ foo: 'bar' } as any, {} as any, 'e2e')).toEqual({ foo: 'bar', }) }) @@ -50,7 +60,7 @@ describe('config/src/project/index', () => { }, } - expect(updateWithPluginValues(cfg as any, overrides, 'e2e')).to.deep.eq({ + expect(updateWithPluginValues(cfg as any, overrides, 'e2e')).toEqual({ foo: 'bar', baz: 'baz', lol: 1234, @@ -96,7 +106,7 @@ describe('config/src/project/index', () => { const overrides = {} - expect(updateWithPluginValues(cfg as any, overrides, 'e2e')).to.deep.eq({ + expect(updateWithPluginValues(cfg as any, overrides, 'e2e')).toEqual({ browsers: [browser], resolved: { browsers: { @@ -132,10 +142,11 @@ describe('config/src/project/index', () => { browsers: null, } - sinon.stub(errors, 'throwErr') + const throwErrSpy = vi.spyOn(errors, 'throwErr') + updateWithPluginValues(cfg as any, overrides, 'e2e') - expect(errors.throwErr).to.have.been.calledWith('CONFIG_VALIDATION_MSG_ERROR') + expect(throwErrSpy).toHaveBeenCalledWith('CONFIG_VALIDATION_MSG_ERROR', 'configFile', undefined, 'Missing browsers list') }) it('allows user to filter browsers', () => { @@ -173,14 +184,14 @@ describe('config/src/project/index', () => { const updated = updateWithPluginValues(cfg as any, overrides, 'e2e') - expect(updated.resolved, 'resolved values').to.deep.eq({ + expect(updated.resolved, 'resolved values').toEqual({ browsers: { value: [browserTwo], from: 'plugin', }, }) - expect(updated, 'all values').to.deep.eq({ + expect(updated, 'all values').toEqual({ browsers: [browserTwo], resolved: { browsers: { diff --git a/packages/config/test/project/utils.spec.ts b/packages/config/test/project/utils.spec.ts index b707d6a1805..a64df10902c 100644 --- a/packages/config/test/project/utils.spec.ts +++ b/packages/config/test/project/utils.spec.ts @@ -1,8 +1,5 @@ -import '@packages/server/test/spec_helper' - +import { vi, describe, it, beforeAll, beforeEach, expect } from 'vitest' import _ from 'lodash' -import { expect } from 'chai' -import sinon from 'sinon' import stripAnsi from 'strip-ansi' import Debug from 'debug' import os from 'node:os' @@ -13,7 +10,8 @@ import Fixtures from '@tooling/system-tests' import { checkIfResolveChangedRootFolder, parseEnv, - utils, + getProcessEnvVars, + resolveModule, resolveConfigValues, setPluginResolvedOn, setAbsolutePaths, @@ -23,52 +21,78 @@ import { mergeDefaults, } from '../../src/project/utils' import path from 'node:path' +import { Config } from '../../src/project/types' +import { resetIssuedWarnings } from '../../src/browser' const debug = Debug('test') -describe('config/src/project/utils', () => { - beforeEach(function () { - delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS - }) +vi.mock('@packages/errors', async (importActual) => { + const actual = await importActual() + + return { + default: { + // @ts-expect-error + ...actual.default, + throwErr: vi.fn(), + warning: vi.fn(), + }, + } +}) - before(function () { - this.env = process.env; +vi.mock('../../src/project/utils', async (importActual) => { + const actual = await importActual() - (process as any).env = _.omit(process.env, 'CYPRESS_DEBUG') + return { + // @ts-expect-error + ...actual, + resolveModule: vi.fn(), + } +}) +describe('config/src/project/utils', () => { + beforeAll(function () { Fixtures.scaffold() }) - after(function () { - process.env = this.env - }) + beforeEach(async function () { + vi.resetAllMocks() + vi.unstubAllEnvs() + vi.stubEnv('CYPRESS_COMMERCIAL_RECOMMENDATIONS', undefined) + vi.stubEnv('CYPRESS_DEBUG', undefined) + vi.stubEnv('CYPRESS_LOCAL_CY_PROMPT_PATH', undefined) + resetIssuedWarnings() - afterEach(() => { - sinon.restore() + const { resolveModule: resolveModuleActual } = await vi.importActual('../../src/project/utils') + + vi.mocked(resolveModule).mockImplementation(resolveModuleActual) + + const errorsActual = (await vi.importActual('@packages/errors')).default + + vi.mocked(errors.throwErr).mockImplementation(errorsActual.throwErr) }) describe('checkIfResolveChangedRootFolder', () => { it('ignores non-absolute paths', () => { - expect(checkIfResolveChangedRootFolder('foo/index.js', 'foo')).to.be.false + expect(checkIfResolveChangedRootFolder('foo/index.js', 'foo')).toBe(false) }) it('handles paths that do not switch', () => { - expect(checkIfResolveChangedRootFolder('/foo/index.js', '/foo')).to.be.false + expect(checkIfResolveChangedRootFolder('/foo/index.js', '/foo')).toBe(false) }) it('detects path switch', () => { - expect(checkIfResolveChangedRootFolder('/private/foo/index.js', '/foo')).to.be.true + expect(checkIfResolveChangedRootFolder('/private/foo/index.js', '/foo')).toBe(true) }) }) - context('.getProcessEnvVars', () => { + describe('.getProcessEnvVars', () => { it('returns process envs prefixed with cypress', () => { const envs = { CYPRESS_BASE_URL: 'value', RANDOM_ENV: 'ignored', } as unknown as NodeJS.ProcessEnv - expect(utils.getProcessEnvVars(envs)).to.deep.eq({ + expect(getProcessEnvVars(envs)).toEqual({ BASE_URL: 'value', }) }) @@ -78,7 +102,7 @@ describe('config/src/project/utils', () => { CYPRESS_INTERNAL_ENV: 'value', } as unknown as NodeJS.ProcessEnv - expect(utils.getProcessEnvVars(envs)).to.deep.eq({}) + expect(getProcessEnvVars(envs)).toEqual({}) }); ['cypress_', 'CYPRESS_'].forEach((key) => { @@ -91,7 +115,7 @@ describe('config/src/project/utils', () => { obj[`${key}version`] = '0.12.0' - expect(utils.getProcessEnvVars(obj)).to.deep.eq({ + expect(getProcessEnvVars(obj)).toEqual({ host: 'http://localhost:8888', version: '0.12.0', }) @@ -106,7 +130,7 @@ describe('config/src/project/utils', () => { CYPRESS_PROJECT_ID: 'abc123', } as NodeJS.ProcessEnv - expect(utils.getProcessEnvVars(obj)).to.deep.eq({ + expect(getProcessEnvVars(obj)).toEqual({ FOO: 'bar', PROJECT_ID: 'abc123', CRASH_REPORTS: 0, @@ -114,17 +138,25 @@ describe('config/src/project/utils', () => { }) }) - context('environment name check', () => { + describe('environment name check', () => { it('throws an error for unknown CYPRESS_INTERNAL_ENV', async () => { - sinon.stub(errors, 'throwErr').withArgs('INVALID_CYPRESS_INTERNAL_ENV', 'foo-bar'); - (process as any).env.CYPRESS_INTERNAL_ENV = 'foo-bar' + vi.stubEnv('CYPRESS_INTERNAL_ENV', 'foo-bar') + // @ts-expect-error - invalid arg types + vi.mocked(errors.throwErr).mockImplementation((args1: string, arg2: string) => { + if (args1 === 'INVALID_CYPRESS_INTERNAL_ENV' && arg2 === 'foo-bar') { + return + } + + throw new Error('should be unreachable') + }) + const cfg = { projectRoot: '/foo/bar/', supportFile: false, } const options = {} - const getFilesByGlob = sinon.stub().returns(['path/to/file']) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file']) try { await mergeDefaults(cfg, options, {}, getFilesByGlob) @@ -132,32 +164,30 @@ describe('config/src/project/utils', () => { // } - expect(errors.throwErr).have.been.calledOnce + expect(errors.throwErr).toHaveBeenCalledOnce }) it('allows production CYPRESS_INTERNAL_ENV', async () => { - sinon.stub(errors, 'throwErr') - process.env.CYPRESS_INTERNAL_ENV = 'production' + vi.stubEnv('CYPRESS_INTERNAL_ENV', 'production') + const cfg = { projectRoot: '/foo/bar/', supportFile: false, } const options = {} - const getFilesByGlob = sinon.stub().returns(['path/to/file']) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file']) await mergeDefaults(cfg, options, {}, getFilesByGlob) - expect(errors.throwErr).not.to.be.called + expect(errors.throwErr).not.toHaveBeenCalled }) }) - context('.parseEnv', () => { + describe('.parseEnv', () => { it('merges together env from config, env from file, env from process, and env from CLI', () => { - sinon.stub(utils, 'getProcessEnvVars').returns({ - version: '0.12.1', - user: 'bob', - }) + vi.stubEnv('CYPRESS_version', '0.12.1') + vi.stubEnv('CYPRESS_user', 'bob') const obj = { env: { @@ -179,7 +209,7 @@ describe('config/src/project/utils', () => { project: 'pie', } - expect(parseEnv(obj, envCLI)).to.deep.eq({ + expect(parseEnv(obj, envCLI)).toEqual({ version: '0.14.0', project: 'pie', host: 'http://localhost:8888', @@ -190,73 +220,45 @@ describe('config/src/project/utils', () => { }) }) - context('.resolveConfigValues', () => { - beforeEach(function () { - this.expected = function (obj) { - const merged = resolveConfigValues(obj.config, obj.defaults, obj.resolved) - - expect(merged).to.deep.eq(obj.final) - } - }) - + describe('.resolveConfigValues', () => { it('sets baseUrl to default', function () { - return this.expected({ - config: { baseUrl: null }, - defaults: { baseUrl: null }, - resolved: {}, - final: { - baseUrl: { - value: null, - from: 'default', - }, + expect(resolveConfigValues({ baseUrl: null }, { baseUrl: null }, {})).toEqual({ + baseUrl: { + value: null, + from: 'default', }, }) }) it('sets baseUrl to config', function () { - return this.expected({ - config: { baseUrl: 'localhost' }, - defaults: { baseUrl: null }, - resolved: {}, - final: { - baseUrl: { - value: 'localhost', - from: 'config', - }, + expect(resolveConfigValues({ baseUrl: 'localhost' }, { baseUrl: null }, {})).toEqual({ + baseUrl: { + value: 'localhost', + from: 'config', }, }) }) it('does not change existing resolved values', function () { - return this.expected({ - config: { baseUrl: 'localhost' }, - defaults: { baseUrl: null }, - resolved: { baseUrl: 'cli' }, - final: { - baseUrl: { - value: 'localhost', - from: 'cli', - }, + expect(resolveConfigValues({ baseUrl: 'localhost' }, { baseUrl: null }, { baseUrl: 'cli' })).toEqual({ + baseUrl: { + value: 'localhost', + from: 'cli', }, }) }) it('ignores values not found in configKeys', function () { - return this.expected({ - config: { baseUrl: 'localhost', foo: 'bar' }, - defaults: { baseUrl: null }, - resolved: { baseUrl: 'cli' }, - final: { - baseUrl: { - value: 'localhost', - from: 'cli', - }, + expect(resolveConfigValues({ baseUrl: 'localhost', foo: 'bar' }, { baseUrl: null }, { baseUrl: 'cli' })).toEqual({ + baseUrl: { + value: 'localhost', + from: 'cli', }, }) }) }) - context('.setPluginResolvedOn', () => { + describe('.setPluginResolvedOn', () => { it('resolves an object with single property', () => { const cfg = {} const obj = { @@ -265,7 +267,7 @@ describe('config/src/project/utils', () => { setPluginResolvedOn(cfg, obj) - expect(cfg).to.deep.eq({ + expect(cfg).toEqual({ foo: { value: 'bar', from: 'plugin', @@ -282,7 +284,7 @@ describe('config/src/project/utils', () => { setPluginResolvedOn(cfg, obj) - expect(cfg).to.deep.eq({ + expect(cfg).toEqual({ foo: { value: 'bar', from: 'plugin', @@ -309,7 +311,7 @@ describe('config/src/project/utils', () => { setPluginResolvedOn(cfg, obj) - expect(cfg, 'foo.bar gets value').to.deep.eq({ + expect(cfg, 'foo.bar gets value').toEqual({ foo: { bar: { value: 42, @@ -333,7 +335,7 @@ describe('config/src/project/utils', () => { setPluginResolvedOn(cfg, obj) - expect(cfg).to.deep.eq({ + expect(cfg).toEqual({ foo: { from: 'plugin', value: { @@ -346,7 +348,7 @@ describe('config/src/project/utils', () => { }) }) - context('_.defaultsDeep', () => { + describe('_.defaultsDeep', () => { it('merges arrays', () => { // sanity checks to confirm how Lodash merges arrays in defaultsDeep const diffs = { @@ -357,21 +359,21 @@ describe('config/src/project/utils', () => { } const merged = _.defaultsDeep({}, diffs, cfg) - expect(merged, 'arrays are combined').to.deep.eq({ + expect(merged, 'arrays are combined').toEqual({ list: [1, 2], }) }) }) - context('.setAbsolutePaths', () => { + describe('.setAbsolutePaths', () => { it('is noop without projectRoot', () => { - expect(setAbsolutePaths({})).to.deep.eq({}) + expect(setAbsolutePaths({})).toEqual({}) }) it('does not mutate existing obj', () => { const obj = {} - expect(setAbsolutePaths(obj)).not.to.eq(obj) + expect(setAbsolutePaths(obj)).not.toBe(obj) }) it('ignores non special *folder properties', () => { @@ -382,10 +384,10 @@ describe('config/src/project/utils', () => { baz: 'quux', } - expect(setAbsolutePaths(obj)).to.deep.eq(obj) + expect(setAbsolutePaths(obj)).toEqual(obj) }) - return ['fileServerFolder', 'fixturesFolder'].forEach((folder) => { + for (const folder of ['fileServerFolder', 'fixturesFolder']) { it(`converts relative ${folder} to absolute path`, () => { const obj = { projectRoot: '/_test-output/path/to/project', @@ -399,21 +401,23 @@ describe('config/src/project/utils', () => { expected[folder] = '/_test-output/path/to/project/foo/bar' - expect(setAbsolutePaths(obj)).to.deep.eq(expected) + expect(setAbsolutePaths(obj)).toEqual(expected) }) - }) + } }) - context('.setNodeBinary', () => { + describe('.setNodeBinary', () => { + let nodeVersion: string + beforeEach(function () { - this.nodeVersion = process.versions.node + nodeVersion = process.versions.node }) it('sets cli Node ver', function () { const obj = setNodeBinary({ }, '/foo/bar/node', '1.2.3') - expect(obj).to.deep.eq({ + expect(obj).toEqual({ resolvedNodeVersion: '1.2.3', resolvedNodePath: '/foo/bar/node', }) @@ -423,8 +427,8 @@ describe('config/src/project/utils', () => { const obj = setNodeBinary({ }, undefined, '1.2.3') - expect(obj).to.deep.eq({ - resolvedNodeVersion: this.nodeVersion, + expect(obj).toEqual({ + resolvedNodeVersion: nodeVersion, }) }) @@ -432,47 +436,46 @@ describe('config/src/project/utils', () => { const obj = setNodeBinary({ }, '/foo/bar/node') - expect(obj).to.deep.eq({ - resolvedNodeVersion: this.nodeVersion, + expect(obj).toEqual({ + resolvedNodeVersion: nodeVersion, }) }) }) describe('relativeToProjectRoot', () => { - context('posix', () => { + describe('posix', () => { it('returns path of file relative to projectRoot', () => { const projectRoot = '/root/projects' const supportFile = '/root/projects/cypress/support/e2e.js' - expect(relativeToProjectRoot(projectRoot, supportFile)).to.eq('cypress/support/e2e.js') + expect(relativeToProjectRoot(projectRoot, supportFile)).toEqual('cypress/support/e2e.js') }) }) - context('windows', () => { + describe('windows', () => { it('returns path of file relative to projectRoot', () => { const projectRoot = `\\root\\projects` const supportFile = `\\root\\projects\\cypress\\support\\e2e.js` - expect(relativeToProjectRoot(projectRoot, supportFile)).to.eq(`cypress\\support\\e2e.js`) + expect(relativeToProjectRoot(projectRoot, supportFile)).toEqual(`cypress\\support\\e2e.js`) }) }) }) - context('.setSupportFileAndFolder', () => { - it('does nothing if supportFile is falsey', () => { + describe('.setSupportFileAndFolder', () => { + it('does nothing if supportFile is falsey', async () => { const obj = { projectRoot: '/_test-output/path/to/project', } - const getFilesByGlob = sinon.stub().returns(['path/to/file.ts']) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file']) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - expect(result).to.eql(obj) - }) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) + + expect(result).toEqual(obj) }) - it('sets the full path to the supportFile and supportFolder if it exists', () => { + it('sets the full path to the supportFile and supportFolder if it exists', async () => { const projectRoot = process.cwd() const obj = setAbsolutePaths({ @@ -480,19 +483,18 @@ describe('config/src/project/utils', () => { supportFile: 'test/project/utils.spec.ts', }) - const getFilesByGlob = sinon.stub().returns([path.join(projectRoot, obj.supportFile)]) + const getFilesByGlob = vi.fn().mockReturnValue([path.join(projectRoot, obj.supportFile)]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - expect(result).to.eql({ - projectRoot, - supportFile: `${projectRoot}/test/project/utils.spec.ts`, - supportFolder: `${projectRoot}/test/project`, - }) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) + + expect(result).toEqual({ + projectRoot, + supportFile: `${projectRoot}/test/project/utils.spec.ts`, + supportFolder: `${projectRoot}/test/project`, }) }) - it('sets the supportFile to default e2e.js if it does not exist, support folder does not exist, and supportFile is the default', () => { + it('sets the supportFile to default e2e.js if it does not exist, support folder does not exist, and supportFile is the default', async () => { const projectRoot = Fixtures.projectPath('no-scaffolding') const obj = setAbsolutePaths({ @@ -500,19 +502,18 @@ describe('config/src/project/utils', () => { supportFile: 'cypress/support/e2e.js', }) - const getFilesByGlob = sinon.stub().returns([path.join(projectRoot, obj.supportFile)]) + const getFilesByGlob = vi.fn().mockReturnValue([path.join(projectRoot, obj.supportFile)]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - expect(result).to.eql({ - projectRoot, - supportFile: `${projectRoot}/cypress/support/e2e.js`, - supportFolder: `${projectRoot}/cypress/support`, - }) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) + + expect(result).toEqual({ + projectRoot, + supportFile: `${projectRoot}/cypress/support/e2e.js`, + supportFolder: `${projectRoot}/cypress/support`, }) }) - it('finds support file in project path that contains glob syntax', () => { + it('finds support file in project path that contains glob syntax', async () => { const projectRoot = Fixtures.projectPath('project-with-(glob)-[chars]') const obj = setAbsolutePaths({ @@ -520,19 +521,18 @@ describe('config/src/project/utils', () => { supportFile: 'cypress/support/e2e.js', }) - const getFilesByGlob = sinon.stub().returns([path.join(projectRoot, obj.supportFile)]) + const getFilesByGlob = vi.fn().mockReturnValue([path.join(projectRoot, obj.supportFile)]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - expect(result).to.eql({ - projectRoot, - supportFile: `${projectRoot}/cypress/support/e2e.js`, - supportFolder: `${projectRoot}/cypress/support`, - }) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) + + expect(result).toEqual({ + projectRoot, + supportFile: `${projectRoot}/cypress/support/e2e.js`, + supportFolder: `${projectRoot}/cypress/support`, }) }) - it('sets the supportFile to false if it does not exist, support folder exists, and supportFile is the default', () => { + it('sets the supportFile to false if it does not exist, support folder exists, and supportFile is the default', async () => { const projectRoot = Fixtures.projectPath('empty-folders') const obj = setAbsolutePaths({ @@ -540,18 +540,17 @@ describe('config/src/project/utils', () => { supportFile: false, }) - const getFilesByGlob = sinon.stub().returns(['path/to/file.ts']) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file.ts']) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - expect(result).to.eql({ - projectRoot, - supportFile: false, - }) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) + + expect(result).toEqual({ + projectRoot, + supportFile: false, }) }) - it('throws error if supportFile is not default and does not exist', () => { + it('throws error if supportFile is not default and does not exist', async () => { const projectRoot = process.cwd() const obj = setAbsolutePaths({ @@ -565,15 +564,20 @@ describe('config/src/project/utils', () => { }, }) - const getFilesByGlob = sinon.stub().returns([]) + const getFilesByGlob = vi.fn().mockReturnValue([]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .catch((err) => { - expect(stripAnsi(err.message)).to.include('Your project does not contain a default supportFile') - }) + try { + await setSupportFileAndFolder(obj, getFilesByGlob) + } catch (err) { + expect(stripAnsi(err.message)).toContain('Your project does not contain a default supportFile') + + return + } + + throw new Error('Expected error to be thrown') }) - it('sets the supportFile to index.ts if it exists (without ts require hook)', () => { + it('sets the supportFile to index.ts if it exists (without ts require hook)', async () => { const projectRoot = Fixtures.projectPath('ts-proj') const supportFolder = `${projectRoot}/cypress/support` const supportFilename = `${supportFolder}/index.ts` @@ -581,28 +585,32 @@ describe('config/src/project/utils', () => { const e: Error & { code?: string } = new Error('Cannot resolve TS file by default') e.code = 'MODULE_NOT_FOUND' - sinon.stub(utils, 'resolveModule').withArgs(supportFilename).throws(e) + // @ts-expect-error - invalid arg types + vi.mocked(resolveModule).mockImplementation((args) => { + if (args === supportFilename) { + throw e + } + }) const obj = setAbsolutePaths({ projectRoot, supportFile: 'cypress/support/index.ts', }) - const getFilesByGlob = sinon.stub().returns([path.join(projectRoot, obj.supportFile)]) + const getFilesByGlob = vi.fn().mockReturnValue([path.join(projectRoot, obj.supportFile)]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - debug('result is', result) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) - expect(result).to.eql({ - projectRoot, - supportFolder, - supportFile: supportFilename, - }) + debug('result is', result) + + expect(result).toEqual({ + projectRoot, + supportFolder, + supportFile: supportFilename, }) }) - it('uses custom TS supportFile if it exists (without ts require hook)', () => { + it('uses custom TS supportFile if it exists (without ts require hook)', async () => { const projectRoot = Fixtures.projectPath('ts-proj-custom-names') const supportFolder = `${projectRoot}/cypress` const supportFilename = `${supportFolder}/support.ts` @@ -610,224 +618,231 @@ describe('config/src/project/utils', () => { const e: Error & { code?: string } = new Error('Cannot resolve TS file by default') e.code = 'MODULE_NOT_FOUND' - sinon.stub(utils, 'resolveModule').withArgs(supportFilename).throws(e) + // @ts-expect-error - invalid arg types + vi.mocked(resolveModule).mockImplementation((args) => { + if (args === supportFilename) { + throw e + } + }) const obj = setAbsolutePaths({ projectRoot, supportFile: 'cypress/support.ts', }) - const getFilesByGlob = sinon.stub().returns([path.join(projectRoot, obj.supportFile)]) + const getFilesByGlob = vi.fn().mockReturnValue([path.join(projectRoot, obj.supportFile)]) - return setSupportFileAndFolder(obj, getFilesByGlob) - .then((result) => { - debug('result is', result) + const result = await setSupportFileAndFolder(obj, getFilesByGlob) - expect(result).to.eql({ - projectRoot, - supportFolder, - supportFile: supportFilename, - }) + debug('result is', result) + + expect(result).toEqual({ + projectRoot, + supportFolder, + supportFile: supportFilename, }) }) }) - context('.mergeDefaults', () => { - beforeEach(function () { - this.getFilesByGlob = sinon.stub().returns(['path/to/file']) + describe('.mergeDefaults', () => { + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file']) + const defaults = async (prop: string, value?: any, cfg?: any, options?: any): Promise => { + cfg = cfg ?? {} as Config + cfg.projectRoot = '/foo/bar/' - this.defaults = (prop, value, cfg: any = {}, options = {}) => { - cfg.projectRoot = '/foo/bar/' + const mergedConfig = await mergeDefaults({ ...cfg, supportFile: cfg.supportFile ?? false }, options, {}, getFilesByGlob) - return mergeDefaults({ ...cfg, supportFile: cfg.supportFile ?? false }, options, {}, this.getFilesByGlob) - .then((mergedConfig) => { - expect(mergedConfig[prop]).to.deep.eq(value) - }) - } + expect(mergedConfig[prop]).toEqual(value) + + return mergedConfig + } + + beforeEach(function () { + vi.stubEnv('CYPRESS_INTERNAL_ENV', 'production') }) - it('slowTestThreshold=10000 for e2e', function () { - return this.defaults('slowTestThreshold', 10000, {}, { testingType: 'e2e' }) + it('slowTestThreshold=10000 for e2e', async function () { + await defaults('slowTestThreshold', 10000, {}, { testingType: 'e2e' }) }) - it('slowTestThreshold=250 for component', function () { - return this.defaults('slowTestThreshold', 250, {}, { testingType: 'component' }) + it('slowTestThreshold=250 for component', async function () { + await defaults('slowTestThreshold', 250, {}, { testingType: 'component' }) }) - it('port=null', function () { - return this.defaults('port', null) + it('port=null', async function () { + await defaults('port', null) }) - it('projectId=null', function () { - return this.defaults('projectId', null) + it('projectId=null', async function () { + await defaults('projectId', null) }) - it('autoOpen=false', function () { - return this.defaults('autoOpen', false) + it('autoOpen=false', async function () { + await defaults('autoOpen', false) }) - it('browserUrl=http://localhost:2020/__/', function () { - return this.defaults('browserUrl', 'http://localhost:2020/__/', { port: 2020 }) + it('browserUrl=http://localhost:2020/__/', async function () { + await defaults('browserUrl', 'http://localhost:2020/__/', { port: 2020 }) }) - it('proxyUrl=http://localhost:2020', function () { - return this.defaults('proxyUrl', 'http://localhost:2020', { port: 2020 }) + it('proxyUrl=http://localhost:2020', async function () { + await defaults('proxyUrl', 'http://localhost:2020', { port: 2020 }) }) - it('namespace=__cypress', function () { - return this.defaults('namespace', '__cypress') + it('namespace=__cypress', async function () { + await defaults('namespace', '__cypress') }) - it('baseUrl=http://localhost:8000/app/', function () { - return this.defaults('baseUrl', 'http://localhost:8000/app/', { + it('baseUrl=http://localhost:8000/app/', async function () { + await defaults('baseUrl', 'http://localhost:8000/app/', { baseUrl: 'http://localhost:8000/app///', }) }) - it('baseUrl=http://localhost:8000/app/', function () { - return this.defaults('baseUrl', 'http://localhost:8000/app/', { + it('baseUrl=http://localhost:8000/app/', async function () { + await defaults('baseUrl', 'http://localhost:8000/app/', { baseUrl: 'http://localhost:8000/app//', }) }) - it('baseUrl=http://localhost:8000/app', function () { - return this.defaults('baseUrl', 'http://localhost:8000/app', { + it('baseUrl=http://localhost:8000/app', async function () { + await defaults('baseUrl', 'http://localhost:8000/app', { baseUrl: 'http://localhost:8000/app', }) }) - it('baseUrl=http://localhost:8000/', function () { - return this.defaults('baseUrl', 'http://localhost:8000/', { + it('baseUrl=http://localhost:8000/', async function () { + await defaults('baseUrl', 'http://localhost:8000/', { baseUrl: 'http://localhost:8000//', }) }) - it('baseUrl=http://localhost:8000/', function () { - return this.defaults('baseUrl', 'http://localhost:8000/', { + it('baseUrl=http://localhost:8000/', async function () { + await defaults('baseUrl', 'http://localhost:8000/', { baseUrl: 'http://localhost:8000/', }) }) - it('baseUrl=http://localhost:8000', function () { - return this.defaults('baseUrl', 'http://localhost:8000', { + it('baseUrl=http://localhost:8000', async function () { + await defaults('baseUrl', 'http://localhost:8000', { baseUrl: 'http://localhost:8000', }) }) - it('viewportWidth=1000', function () { - return this.defaults('viewportWidth', 1000) + it('viewportWidth=1000', async function () { + await defaults('viewportWidth', 1000) }) - it('viewportHeight=660', function () { - return this.defaults('viewportHeight', 660) + it('viewportHeight=660', async function () { + await defaults('viewportHeight', 660) }) - it('userAgent=null', function () { - return this.defaults('userAgent', null) + it('userAgent=null', async function () { + await defaults('userAgent', null) }) - it('baseUrl=null', function () { - return this.defaults('baseUrl', null) + it('baseUrl=null', async function () { + await defaults('baseUrl', null) }) - it('defaultCommandTimeout=4000', function () { - return this.defaults('defaultCommandTimeout', 4000) + it('defaultCommandTimeout=4000', async function () { + await defaults('defaultCommandTimeout', 4000) }) - it('pageLoadTimeout=60000', function () { - return this.defaults('pageLoadTimeout', 60000) + it('pageLoadTimeout=60000', async function () { + await defaults('pageLoadTimeout', 60000) }) - it('requestTimeout=5000', function () { - return this.defaults('requestTimeout', 5000) + it('requestTimeout=5000', async function () { + await defaults('requestTimeout', 5000) }) - it('responseTimeout=30000', function () { - return this.defaults('responseTimeout', 30000) + it('responseTimeout=30000', async function () { + await defaults('responseTimeout', 30000) }) - it('execTimeout=60000', function () { - return this.defaults('execTimeout', 60000) + it('execTimeout=60000', async function () { + await defaults('execTimeout', 60000) }) - it('waitForAnimations=true', function () { - return this.defaults('waitForAnimations', true) + it('waitForAnimations=true', async function () { + await defaults('waitForAnimations', true) }) - it('scrollBehavior=start', function () { - return this.defaults('scrollBehavior', 'top') + it('scrollBehavior=start', async function () { + await defaults('scrollBehavior', 'top') }) - it('animationDistanceThreshold=5', function () { - return this.defaults('animationDistanceThreshold', 5) + it('animationDistanceThreshold=5', async function () { + await defaults('animationDistanceThreshold', 5) }) - it('video=false', function () { - return this.defaults('video', false) + it('video=false', async function () { + await defaults('video', false) }) - it('videoCompression=false', function () { - return this.defaults('videoCompression', false) + it('videoCompression=false', async function () { + await defaults('videoCompression', false) }) - it('trashAssetsBeforeRuns=32', function () { - return this.defaults('trashAssetsBeforeRuns', true) + it('trashAssetsBeforeRuns=32', async function () { + await defaults('trashAssetsBeforeRuns', true) }) - it('morgan=true', function () { - return this.defaults('morgan', true) + it('morgan=true', async function () { + await defaults('morgan', true) }) - it('isTextTerminal=false', function () { - return this.defaults('isTextTerminal', false) + it('isTextTerminal=false', async function () { + await defaults('isTextTerminal', false) }) - it('socketId=null', function () { - return this.defaults('socketId', null) + it('socketId=null', async function () { + await defaults('socketId', null) }) - it('reporter=spec', function () { - return this.defaults('reporter', 'spec') + it('reporter=spec', async function () { + await defaults('reporter', 'spec') }) - it('watchForFileChanges=true', function () { - return this.defaults('watchForFileChanges', true) + it('watchForFileChanges=true', async function () { + await defaults('watchForFileChanges', true) }) - it('numTestsKeptInMemory=50', function () { - return this.defaults('numTestsKeptInMemory', 50) + it('numTestsKeptInMemory=50', async function () { + await defaults('numTestsKeptInMemory', 50) }) - it('modifyObstructiveCode=true', function () { - return this.defaults('modifyObstructiveCode', true) + it('modifyObstructiveCode=true', async function () { + await defaults('modifyObstructiveCode', true) }) - it('supportFile=false', function () { - return this.defaults('supportFile', false, { supportFile: false }) + it('supportFile=false', async function () { + await defaults('supportFile', false, { supportFile: false }) }) - it('blockHosts=null', function () { - return this.defaults('blockHosts', null) + it('blockHosts=null', async function () { + await defaults('blockHosts', null) }) - it('blockHosts=[a,b]', function () { - return this.defaults('blockHosts', ['a', 'b'], { + it('blockHosts=[a,b]', async function () { + await defaults('blockHosts', ['a', 'b'], { blockHosts: ['a', 'b'], }) }) - it('blockHosts=a|b', function () { - return this.defaults('blockHosts', ['a', 'b'], { + it('blockHosts=a|b', async function () { + await defaults('blockHosts', ['a', 'b'], { blockHosts: ['a', 'b'], }) }) - it('hosts=null', function () { - return this.defaults('hosts', null) + it('hosts=null', async function () { + await defaults('hosts', null) }) - it('hosts={}', function () { - return this.defaults('hosts', { + it('hosts={}', async function () { + await defaults('hosts', { foo: 'bar', baz: 'quux', }, { @@ -838,70 +853,65 @@ describe('config/src/project/utils', () => { }) }) - it('experimentalCspAllowList=false', function () { - return this.defaults('experimentalCspAllowList', false) + it('experimentalCspAllowList=false', async function () { + await defaults('experimentalCspAllowList', false) }) - it('experimentalCspAllowList=true', function () { - return this.defaults('experimentalCspAllowList', true, { + it('experimentalCspAllowList=true', async function () { + await defaults('experimentalCspAllowList', true, { experimentalCspAllowList: true, }) }) - it('experimentalCspAllowList=[]', function () { - return this.defaults('experimentalCspAllowList', [], { + it('experimentalCspAllowList=[]', async function () { + await defaults('experimentalCspAllowList', [], { experimentalCspAllowList: [], }) }) - it('experimentalCspAllowList=default-src|script-src', function () { - return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], { + it('experimentalCspAllowList=default-src|script-src', async function () { + await defaults('experimentalCspAllowList', ['default-src', 'script-src'], { experimentalCspAllowList: ['default-src', 'script-src'], }) }) - it('experimentalCspAllowList=["default-src","script-src"]', function () { - return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], { + it('experimentalCspAllowList=["default-src","script-src"]', async function () { + await defaults('experimentalCspAllowList', ['default-src', 'script-src'], { experimentalCspAllowList: ['default-src', 'script-src'], }) }) - it('resets numTestsKeptInMemory to 0 when runMode', function () { - return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.numTestsKeptInMemory).to.eq(0) - }) + it('resets numTestsKeptInMemory to 0 when runMode', async function () { + const cfg = await defaults('numTestsKeptInMemory', 0, { projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }) + + expect(cfg.numTestsKeptInMemory).toEqual(0) }) - it('resets watchForFileChanges to false when runMode', function () { - return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.watchForFileChanges).to.be.false - }) + it('resets watchForFileChanges to false when runMode', async function () { + const cfg = await defaults('watchForFileChanges', false, { projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }) + + expect(cfg.watchForFileChanges).toBe(false) }) - it('can override morgan in options', function () { - return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { morgan: false }, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.morgan).to.be.false - }) + it('can override morgan in options', async function () { + const cfg = await defaults('morgan', false, { projectRoot: '/foo/bar/', supportFile: false }, { morgan: false }) + + expect(cfg.morgan).toBe(false) }) - it('can override isTextTerminal in options', function () { - return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.isTextTerminal).to.be.true - }) + it('can override isTextTerminal in options', async function () { + const cfg = await defaults('isTextTerminal', true, { projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }) + + expect(cfg.isTextTerminal).toBe(true) }) - it('can override socketId in options', function () { - return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { socketId: '1234' }, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.socketId).to.eq('1234') - }) + it('can override socketId in options', async function () { + const cfg = await defaults('socketId', '1234', { projectRoot: '/foo/bar/', supportFile: false }, { socketId: '1234' }) + + expect(cfg.socketId).toEqual('1234') }) - it('deletes envFile', function () { + it('deletes envFile', async function () { const obj = { projectRoot: '/foo/bar/', supportFile: false, @@ -915,21 +925,20 @@ describe('config/src/project/utils', () => { }, } - return mergeDefaults(obj, {}, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.env).to.deep.eq({ - foo: 'bar', - bar: 'baz', - version: '1.0.1', - }) + const cfg = await mergeDefaults(obj, {}, {}, getFilesByGlob) - expect(cfg.cypressEnv).to.eq(process.env['CYPRESS_INTERNAL_ENV']) - - expect(cfg).not.to.have.property('envFile') + expect(cfg.env).toEqual({ + foo: 'bar', + bar: 'baz', + version: '1.0.1', }) + + expect(cfg.cypressEnv).toEqual(process.env['CYPRESS_INTERNAL_ENV']) + + expect(cfg).not.toHaveProperty('envFile') }) - it('merges env into @env', function () { + it('merges env into @env', async function () { const obj = { projectRoot: '/foo/bar/', supportFile: false, @@ -947,39 +956,46 @@ describe('config/src/project/utils', () => { }, } - return mergeDefaults(obj, options, {}, this.getFilesByGlob) - .then((cfg) => { - expect(cfg.env).to.deep.eq({ - host: 'localhost', - user: 'brian', - version: '0.13.1', - foo: 'bar', - }) + const cfg = await mergeDefaults(obj, options, {}, getFilesByGlob) + + expect(cfg.env).toEqual({ + host: 'localhost', + user: 'brian', + version: '0.13.1', + foo: 'bar', }) }) it('warns if experimentalJustInTimeCompile is passed', async function () { - const warning = sinon.spy(errors, 'warning') - - await this.defaults('experimentalJustInTimeCompile', true, { + await defaults('experimentalJustInTimeCompile', true, { experimentalJustInTimeCompile: true, }) - expect(warning).to.be.calledWith('EXPERIMENTAL_JIT_COMPILE_REMOVED') + expect(errors.warning).toBeCalledWith('EXPERIMENTAL_JIT_COMPILE_REMOVED', { + configFile: 'cypress.config.js', + name: 'experimentalJustInTimeCompile', + newName: undefined, + testingType: undefined, + value: undefined, + }) }) it('warns if experimentalSessionAndOrigin is passed', async function () { - const warning = sinon.spy(errors, 'warning') - - await this.defaults('experimentalSessionAndOrigin', true, { + await defaults('experimentalSessionAndOrigin', true, { experimentalSessionAndOrigin: true, }) - expect(warning).to.be.calledWith('EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED') + expect(errors.warning).toBeCalledWith('EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED', { + configFile: 'cypress.config.js', + name: 'experimentalSessionAndOrigin', + newName: undefined, + testingType: undefined, + value: undefined, + }) }) describe('.resolved', () => { - it('sets reporter and port to cli', () => { + it('sets reporter and port to cli', async () => { const obj = { projectRoot: '/foo/bar', supportFile: false, @@ -990,84 +1006,81 @@ describe('config/src/project/utils', () => { port: 1234, } - const getFilesByGlob = sinon.stub().returns(['path/to/file.ts']) - - return mergeDefaults(obj, options, {}, getFilesByGlob) - .then((cfg) => { - expect(cfg.resolved).to.deep.eq({ - animationDistanceThreshold: { value: 5, from: 'default' }, - arch: { value: os.arch(), from: 'default' }, - baseUrl: { value: null, from: 'default' }, - blockHosts: { value: null, from: 'default' }, - browsers: { value: [], from: 'default' }, - chromeWebSecurity: { value: true, from: 'default' }, - clientCertificates: { value: [], from: 'default' }, - defaultBrowser: { value: null, from: 'default' }, - defaultCommandTimeout: { value: 4000, from: 'default' }, - downloadsFolder: { value: 'cypress/downloads', from: 'default' }, - env: {}, - excludeSpecPattern: { value: '*.hot-update.js', from: 'default' }, - execTimeout: { value: 60000, from: 'default' }, - experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' }, - experimentalCspAllowList: { value: false, from: 'default' }, - experimentalInteractiveRunEvents: { value: false, from: 'default' }, - experimentalMemoryManagement: { value: false, from: 'default' }, - experimentalOriginDependencies: { value: false, from: 'default' }, - experimentalRunAllSpecs: { value: false, from: 'default' }, - experimentalSingleTabRunMode: { value: false, from: 'default' }, - experimentalStudio: { value: false, from: 'default' }, - experimentalSourceRewriting: { value: false, from: 'default' }, - experimentalWebKitSupport: { value: false, from: 'default' }, - fileServerFolder: { value: '', from: 'default' }, - fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, - hosts: { value: null, from: 'default' }, - includeShadowDom: { value: false, from: 'default' }, - injectDocumentDomain: { value: false, from: 'default' }, - justInTimeCompile: { value: true, from: 'default' }, - isInteractive: { value: true, from: 'default' }, - keystrokeDelay: { value: 0, from: 'default' }, - modifyObstructiveCode: { value: true, from: 'default' }, - numTestsKeptInMemory: { value: 50, from: 'default' }, - pageLoadTimeout: { value: 60000, from: 'default' }, - platform: { value: os.platform(), from: 'default' }, - port: { value: 1234, from: 'cli' }, - projectId: { value: null, from: 'default' }, - redirectionLimit: { value: 20, from: 'default' }, - reporter: { value: 'json', from: 'cli' }, - resolvedNodePath: { value: null, from: 'default' }, - resolvedNodeVersion: { value: null, from: 'default' }, - reporterOptions: { value: null, from: 'default' }, - requestTimeout: { value: 5000, from: 'default' }, - responseTimeout: { value: 30000, from: 'default' }, - retries: { value: { runMode: 0, openMode: 0, experimentalStrategy: undefined, experimentalOptions: undefined }, from: 'default' }, - screenshotOnRunFailure: { value: true, from: 'default' }, - screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, - specPattern: { value: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', from: 'default' }, - slowTestThreshold: { value: 10000, from: 'default' }, - supportFile: { value: false, from: 'config' }, - supportFolder: { value: false, from: 'default' }, - taskTimeout: { value: 60000, from: 'default' }, - testIsolation: { value: true, from: 'default' }, - trashAssetsBeforeRuns: { value: true, from: 'default' }, - userAgent: { value: null, from: 'default' }, - video: { value: false, from: 'default' }, - videoCompression: { value: false, from: 'default' }, - videosFolder: { value: 'cypress/videos', from: 'default' }, - viewportHeight: { value: 660, from: 'default' }, - viewportWidth: { value: 1000, from: 'default' }, - waitForAnimations: { value: true, from: 'default' }, - scrollBehavior: { value: 'top', from: 'default' }, - watchForFileChanges: { value: true, from: 'default' }, - }) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file']) + + const cfg = await mergeDefaults(obj, options, {}, getFilesByGlob) + + expect(cfg.resolved).toEqual({ + animationDistanceThreshold: { value: 5, from: 'default' }, + arch: { value: os.arch(), from: 'default' }, + baseUrl: { value: null, from: 'default' }, + blockHosts: { value: null, from: 'default' }, + browsers: { value: [], from: 'default' }, + chromeWebSecurity: { value: true, from: 'default' }, + clientCertificates: { value: [], from: 'default' }, + defaultBrowser: { value: null, from: 'default' }, + defaultCommandTimeout: { value: 4000, from: 'default' }, + downloadsFolder: { value: 'cypress/downloads', from: 'default' }, + env: {}, + excludeSpecPattern: { value: '*.hot-update.js', from: 'default' }, + execTimeout: { value: 60000, from: 'default' }, + experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' }, + experimentalCspAllowList: { value: false, from: 'default' }, + experimentalInteractiveRunEvents: { value: false, from: 'default' }, + experimentalMemoryManagement: { value: false, from: 'default' }, + experimentalOriginDependencies: { value: false, from: 'default' }, + experimentalRunAllSpecs: { value: false, from: 'default' }, + experimentalSingleTabRunMode: { value: false, from: 'default' }, + experimentalStudio: { value: false, from: 'default' }, + experimentalSourceRewriting: { value: false, from: 'default' }, + experimentalWebKitSupport: { value: false, from: 'default' }, + fileServerFolder: { value: '', from: 'default' }, + fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, + hosts: { value: null, from: 'default' }, + includeShadowDom: { value: false, from: 'default' }, + injectDocumentDomain: { value: false, from: 'default' }, + justInTimeCompile: { value: true, from: 'default' }, + isInteractive: { value: true, from: 'default' }, + keystrokeDelay: { value: 0, from: 'default' }, + modifyObstructiveCode: { value: true, from: 'default' }, + numTestsKeptInMemory: { value: 50, from: 'default' }, + pageLoadTimeout: { value: 60000, from: 'default' }, + platform: { value: os.platform(), from: 'default' }, + port: { value: 1234, from: 'cli' }, + projectId: { value: null, from: 'default' }, + redirectionLimit: { value: 20, from: 'default' }, + reporter: { value: 'json', from: 'cli' }, + resolvedNodePath: { value: null, from: 'default' }, + resolvedNodeVersion: { value: null, from: 'default' }, + reporterOptions: { value: null, from: 'default' }, + requestTimeout: { value: 5000, from: 'default' }, + responseTimeout: { value: 30000, from: 'default' }, + retries: { value: { runMode: 0, openMode: 0, experimentalStrategy: undefined, experimentalOptions: undefined }, from: 'default' }, + screenshotOnRunFailure: { value: true, from: 'default' }, + screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, + specPattern: { value: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', from: 'default' }, + slowTestThreshold: { value: 10000, from: 'default' }, + supportFile: { value: false, from: 'config' }, + supportFolder: { value: false, from: 'default' }, + taskTimeout: { value: 60000, from: 'default' }, + testIsolation: { value: true, from: 'default' }, + trashAssetsBeforeRuns: { value: true, from: 'default' }, + userAgent: { value: null, from: 'default' }, + video: { value: false, from: 'default' }, + videoCompression: { value: false, from: 'default' }, + videosFolder: { value: 'cypress/videos', from: 'default' }, + viewportHeight: { value: 660, from: 'default' }, + viewportWidth: { value: 1000, from: 'default' }, + waitForAnimations: { value: true, from: 'default' }, + scrollBehavior: { value: 'top', from: 'default' }, + watchForFileChanges: { value: true, from: 'default' }, }) }) - it('sets config, envFile and env', () => { - sinon.stub(utils, 'getProcessEnvVars').returns({ - quux: 'quux', - RECORD_KEY: 'foobarbazquux', - PROJECT_ID: 'projectId123', - }) + it('sets config, envFile and env', async () => { + vi.stubEnv('CYPRESS_quux', 'quux') + vi.stubEnv('CYPRESS_RECORD_KEY', 'foobarbazquux') + vi.stubEnv('CYPRESS_PROJECT_ID', 'projectId123') const obj = { projectRoot: '/foo/bar', @@ -1088,102 +1101,99 @@ describe('config/src/project/utils', () => { }, } - const getFilesByGlob = sinon.stub().returns(['path/to/file.ts']) - - return mergeDefaults(obj, options, {}, getFilesByGlob) - .then((cfg) => { - expect(cfg.resolved).to.deep.eq({ - arch: { value: os.arch(), from: 'default' }, - animationDistanceThreshold: { value: 5, from: 'default' }, - baseUrl: { value: 'http://localhost:8080', from: 'config' }, - blockHosts: { value: null, from: 'default' }, - browsers: { value: [], from: 'default' }, - chromeWebSecurity: { value: true, from: 'default' }, - clientCertificates: { value: [], from: 'default' }, - defaultBrowser: { value: null, from: 'default' }, - defaultCommandTimeout: { value: 4000, from: 'default' }, - downloadsFolder: { value: 'cypress/downloads', from: 'default' }, - env: { - foo: { - value: 'foo', - from: 'config', - }, - bar: { - value: 'bar', - from: 'envFile', - }, - baz: { - value: 'baz', - from: 'cli', - }, - quux: { - value: 'quux', - from: 'env', - }, - RECORD_KEY: { - value: 'fooba...zquux', - from: 'env', - }, + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file.ts']) + + const cfg = await mergeDefaults(obj, options, {}, getFilesByGlob) + + expect(cfg.resolved).toEqual({ + arch: { value: os.arch(), from: 'default' }, + animationDistanceThreshold: { value: 5, from: 'default' }, + baseUrl: { value: 'http://localhost:8080', from: 'config' }, + blockHosts: { value: null, from: 'default' }, + browsers: { value: [], from: 'default' }, + chromeWebSecurity: { value: true, from: 'default' }, + clientCertificates: { value: [], from: 'default' }, + defaultBrowser: { value: null, from: 'default' }, + defaultCommandTimeout: { value: 4000, from: 'default' }, + downloadsFolder: { value: 'cypress/downloads', from: 'default' }, + env: { + foo: { + value: 'foo', + from: 'config', + }, + bar: { + value: 'bar', + from: 'envFile', + }, + baz: { + value: 'baz', + from: 'cli', }, - excludeSpecPattern: { value: '*.hot-update.js', from: 'default' }, - execTimeout: { value: 60000, from: 'default' }, - experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' }, - experimentalCspAllowList: { value: false, from: 'default' }, - experimentalInteractiveRunEvents: { value: false, from: 'default' }, - experimentalMemoryManagement: { value: false, from: 'default' }, - experimentalOriginDependencies: { value: false, from: 'default' }, - experimentalRunAllSpecs: { value: false, from: 'default' }, - experimentalSingleTabRunMode: { value: false, from: 'default' }, - experimentalStudio: { value: false, from: 'default' }, - experimentalSourceRewriting: { value: false, from: 'default' }, - experimentalWebKitSupport: { value: false, from: 'default' }, - fileServerFolder: { value: '', from: 'default' }, - fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, - hosts: { value: null, from: 'default' }, - includeShadowDom: { value: false, from: 'default' }, - injectDocumentDomain: { value: false, from: 'default' }, - justInTimeCompile: { value: true, from: 'default' }, - isInteractive: { value: true, from: 'default' }, - keystrokeDelay: { value: 0, from: 'default' }, - modifyObstructiveCode: { value: true, from: 'default' }, - numTestsKeptInMemory: { value: 50, from: 'default' }, - pageLoadTimeout: { value: 60000, from: 'default' }, - platform: { value: os.platform(), from: 'default' }, - port: { value: 2020, from: 'config' }, - projectId: { value: 'projectId123', from: 'env' }, - redirectionLimit: { value: 20, from: 'default' }, - reporter: { value: 'spec', from: 'default' }, - resolvedNodePath: { value: null, from: 'default' }, - resolvedNodeVersion: { value: null, from: 'default' }, - reporterOptions: { value: null, from: 'default' }, - requestTimeout: { value: 5000, from: 'default' }, - responseTimeout: { value: 30000, from: 'default' }, - retries: { value: { runMode: 0, openMode: 0, experimentalStrategy: undefined, experimentalOptions: undefined }, from: 'default' }, - screenshotOnRunFailure: { value: true, from: 'default' }, - screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, - slowTestThreshold: { value: 10000, from: 'default' }, - specPattern: { value: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', from: 'default' }, - supportFile: { value: false, from: 'config' }, - supportFolder: { value: false, from: 'default' }, - taskTimeout: { value: 60000, from: 'default' }, - testIsolation: { value: true, from: 'default' }, - trashAssetsBeforeRuns: { value: true, from: 'default' }, - userAgent: { value: null, from: 'default' }, - video: { value: false, from: 'default' }, - videoCompression: { value: false, from: 'default' }, - videosFolder: { value: 'cypress/videos', from: 'default' }, - viewportHeight: { value: 660, from: 'default' }, - viewportWidth: { value: 1000, from: 'default' }, - waitForAnimations: { value: true, from: 'default' }, - scrollBehavior: { value: 'top', from: 'default' }, - watchForFileChanges: { value: true, from: 'default' }, - }) + quux: { + value: 'quux', + from: 'env', + }, + RECORD_KEY: { + value: 'fooba...zquux', + from: 'env', + }, + }, + excludeSpecPattern: { value: '*.hot-update.js', from: 'default' }, + execTimeout: { value: 60000, from: 'default' }, + experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' }, + experimentalCspAllowList: { value: false, from: 'default' }, + experimentalInteractiveRunEvents: { value: false, from: 'default' }, + experimentalMemoryManagement: { value: false, from: 'default' }, + experimentalOriginDependencies: { value: false, from: 'default' }, + experimentalRunAllSpecs: { value: false, from: 'default' }, + experimentalSingleTabRunMode: { value: false, from: 'default' }, + experimentalStudio: { value: false, from: 'default' }, + experimentalSourceRewriting: { value: false, from: 'default' }, + experimentalWebKitSupport: { value: false, from: 'default' }, + fileServerFolder: { value: '', from: 'default' }, + fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, + hosts: { value: null, from: 'default' }, + includeShadowDom: { value: false, from: 'default' }, + injectDocumentDomain: { value: false, from: 'default' }, + justInTimeCompile: { value: true, from: 'default' }, + isInteractive: { value: true, from: 'default' }, + keystrokeDelay: { value: 0, from: 'default' }, + modifyObstructiveCode: { value: true, from: 'default' }, + numTestsKeptInMemory: { value: 50, from: 'default' }, + pageLoadTimeout: { value: 60000, from: 'default' }, + platform: { value: os.platform(), from: 'default' }, + port: { value: 2020, from: 'config' }, + projectId: { value: 'projectId123', from: 'env' }, + redirectionLimit: { value: 20, from: 'default' }, + reporter: { value: 'spec', from: 'default' }, + resolvedNodePath: { value: null, from: 'default' }, + resolvedNodeVersion: { value: null, from: 'default' }, + reporterOptions: { value: null, from: 'default' }, + requestTimeout: { value: 5000, from: 'default' }, + responseTimeout: { value: 30000, from: 'default' }, + retries: { value: { runMode: 0, openMode: 0, experimentalStrategy: undefined, experimentalOptions: undefined }, from: 'default' }, + screenshotOnRunFailure: { value: true, from: 'default' }, + screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, + slowTestThreshold: { value: 10000, from: 'default' }, + specPattern: { value: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', from: 'default' }, + supportFile: { value: false, from: 'config' }, + supportFolder: { value: false, from: 'default' }, + taskTimeout: { value: 60000, from: 'default' }, + testIsolation: { value: true, from: 'default' }, + trashAssetsBeforeRuns: { value: true, from: 'default' }, + userAgent: { value: null, from: 'default' }, + video: { value: false, from: 'default' }, + videoCompression: { value: false, from: 'default' }, + videosFolder: { value: 'cypress/videos', from: 'default' }, + viewportHeight: { value: 660, from: 'default' }, + viewportWidth: { value: 1000, from: 'default' }, + waitForAnimations: { value: true, from: 'default' }, + scrollBehavior: { value: 'top', from: 'default' }, + watchForFileChanges: { value: true, from: 'default' }, }) }) - it('honors user config for testIsolation', () => { - sinon.stub(utils, 'getProcessEnvVars').returns({}) - + it('honors user config for testIsolation', async () => { const obj = { projectRoot: '/foo/bar', supportFile: false, @@ -1195,13 +1205,12 @@ describe('config/src/project/utils', () => { testingType: 'e2e', } - const getFilesByGlob = sinon.stub().returns(['path/to/file.ts']) + const getFilesByGlob = vi.fn().mockReturnValue(['path/to/file.ts']) - return mergeDefaults(obj, options, {}, getFilesByGlob) - .then((cfg) => { - expect(cfg.resolved).to.have.property('testIsolation') - expect(cfg.resolved.testIsolation).to.deep.eq({ value: false, from: 'config' }) - }) + const cfg = await mergeDefaults(obj, options, {}, getFilesByGlob) + + expect(cfg.resolved).toHaveProperty('testIsolation') + expect(cfg.resolved.testIsolation).toEqual({ value: false, from: 'config' }) }) }) }) diff --git a/packages/config/test/utils.spec.ts b/packages/config/test/utils.spec.ts index ae74a5c1c0a..e706e63232d 100644 --- a/packages/config/test/utils.spec.ts +++ b/packages/config/test/utils.spec.ts @@ -1,4 +1,5 @@ -import { expect } from 'chai' +import { vi, describe, it, expect } from 'vitest' + import { hideKeys, setUrls, @@ -6,37 +7,39 @@ import { isResolvedConfigPropDefault, } from '../src/utils' import { - utils as projectUtils, + getProcessEnvVars, } from '../src/project/utils' describe('config/src/utils', () => { beforeEach(function () { - delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS + vi.unstubAllEnvs() + vi.stubEnv('CYPRESS_COMMERCIAL_RECOMMENDATIONS', undefined) + vi.stubEnv('CYPRESS_LOCAL_CY_PROMPT_PATH', undefined) }) describe('hideKeys', () => { it('removes middle part of the string', () => { const hidden = hideKeys('12345-xxxx-abcde') - expect(hidden).to.equal('12345...abcde') + expect(hidden).toEqual('12345...abcde') }) it('returns undefined for missing key', () => { - expect(hideKeys()).to.be.undefined + expect(hideKeys()).toBeUndefined() }) // https://github.com/cypress-io/cypress/issues/14571 it('returns undefined for non-string argument', () => { - expect(hideKeys(true)).to.be.undefined - expect(hideKeys(1234)).to.be.undefined + expect(hideKeys(true)).toBeUndefined() + expect(hideKeys(1234)).toBeUndefined() }) }) - context('.setUrls', () => { + describe('.setUrls', () => { it('does not mutate existing obj', () => { const obj = {} - expect(setUrls(obj)).not.to.eq(obj) + expect(setUrls(obj)).not.toEqual(obj) }) it('uses baseUrl when set', () => { @@ -48,8 +51,8 @@ describe('config/src/utils', () => { const urls = setUrls(obj) - expect(urls.browserUrl).to.eq('https://www.google.com/__/') - expect(urls.proxyUrl).to.eq('http://localhost:65432') + expect(urls.browserUrl).toEqual('https://www.google.com/__/') + expect(urls.proxyUrl).toEqual('http://localhost:65432') }) it('strips baseUrl to host when set', () => { @@ -61,58 +64,50 @@ describe('config/src/utils', () => { const urls = setUrls(obj) - expect(urls.browserUrl).to.eq('http://localhost:9999/__/') - expect(urls.proxyUrl).to.eq('http://localhost:65432') + expect(urls.browserUrl).toEqual('http://localhost:9999/__/') + expect(urls.proxyUrl).toEqual('http://localhost:65432') }) }) - context('coerce', () => { - beforeEach(function () { - this.env = process.env - }) - - afterEach(function () { - process.env = this.env - }) - + describe('coerce', () => { it('coerces string', () => { - expect(coerce('foo')).to.eq('foo') + expect(coerce('foo')).toEqual('foo') }) it('coerces string from process.env', () => { process.env['CYPRESS_STRING'] = 'bar' - const cypressEnvVar = projectUtils.getProcessEnvVars(process.env) + const cypressEnvVar = getProcessEnvVars(process.env) - expect(coerce(cypressEnvVar)).to.deep.include({ STRING: 'bar' }) + expect(coerce(cypressEnvVar)).toEqual(expect.objectContaining({ STRING: 'bar' })) }) it('coerces number', () => { - expect(coerce('123')).to.eq(123) + expect(coerce('123')).toEqual(123) }) // NOTE: When exporting shell variables, they are saved in `process.env` as strings, hence why // all `process.env` variables are assigned as strings in these unit tests it('coerces number from process.env', () => { process.env['CYPRESS_NUMBER'] = '8000' - const cypressEnvVar = projectUtils.getProcessEnvVars(process.env) + const cypressEnvVar = getProcessEnvVars(process.env) - expect(coerce(cypressEnvVar)).to.deep.include({ NUMBER: 8000 }) + expect(coerce(cypressEnvVar)).toEqual(expect.objectContaining({ NUMBER: 8000 })) }) it('coerces boolean', () => { - expect(coerce('true')).to.be.true + expect(coerce('true')).toBe(true) }) it('coerces boolean from process.env', () => { process.env['CYPRESS_BOOLEAN'] = 'false' - const cypressEnvVar = projectUtils.getProcessEnvVars(process.env) + const cypressEnvVar = getProcessEnvVars(process.env) - expect(coerce(cypressEnvVar)).to.deep.include({ BOOLEAN: false }) + expect(coerce(cypressEnvVar)).toEqual(expect.objectContaining({ BOOLEAN: false })) }) // https://github.com/cypress-io/cypress/issues/8818 it('coerces JSON string', () => { - expect(coerce('[{"type": "foo", "value": "bar"}, {"type": "fizz", "value": "buzz"}]')).to.deep.equal( + expect(coerce('[{"type": "foo", "value": "bar"}, {"type": "fizz", "value": "buzz"}]')).toEqual( [{ 'type': 'foo', 'value': 'bar' }, { 'type': 'fizz', 'value': 'buzz' }], ) }) @@ -120,33 +115,38 @@ describe('config/src/utils', () => { // https://github.com/cypress-io/cypress/issues/8818 it('coerces JSON string from process.env', () => { process.env['CYPRESS_stringified_json'] = '[{"type": "foo", "value": "bar"}, {"type": "fizz", "value": "buzz"}]' - const cypressEnvVar = projectUtils.getProcessEnvVars(process.env) + const cypressEnvVar = getProcessEnvVars(process.env) const coercedCypressEnvVar = coerce(cypressEnvVar) - expect(coercedCypressEnvVar).to.have.keys('stringified_json') - expect(coercedCypressEnvVar['stringified_json']).to.deep.equal([{ 'type': 'foo', 'value': 'bar' }, { 'type': 'fizz', 'value': 'buzz' }]) + expect(coercedCypressEnvVar).toHaveProperty('stringified_json') + expect(coercedCypressEnvVar['stringified_json']).toHaveLength(2) + expect(coercedCypressEnvVar['stringified_json']).toEqual(expect.arrayContaining([{ 'type': 'foo', 'value': 'bar' }, { 'type': 'fizz', 'value': 'buzz' }])) }) it('coerces array', () => { - expect(coerce('[foo,bar]')).to.have.members(['foo', 'bar']) + const coercedCypressEnvVar = coerce('[foo,bar]') + + expect(coercedCypressEnvVar).toHaveLength(2) + expect(coercedCypressEnvVar).toEqual(expect.arrayContaining(['foo', 'bar'])) }) it('coerces array from process.env', () => { process.env['CYPRESS_ARRAY'] = '[google.com,yahoo.com]' - const cypressEnvVar = projectUtils.getProcessEnvVars(process.env) + const cypressEnvVar = getProcessEnvVars(process.env) const coercedCypressEnvVar = coerce(cypressEnvVar) - expect(coercedCypressEnvVar).to.have.keys('ARRAY') - expect(coercedCypressEnvVar['ARRAY']).to.have.members(['google.com', 'yahoo.com']) + expect(coercedCypressEnvVar).toHaveProperty('ARRAY') + expect(coercedCypressEnvVar['ARRAY']).toHaveLength(2) + expect(coercedCypressEnvVar['ARRAY']).toEqual(expect.arrayContaining(['google.com', 'yahoo.com'])) }) it('defaults value with multiple types to string', () => { - expect(coerce('123foo456')).to.eq('123foo456') + expect(coerce('123foo456')).toEqual('123foo456') }) }) - context('.isResolvedConfigPropDefault', () => { + describe('.isResolvedConfigPropDefault', () => { it('returns true if value is default value', () => { const options = { resolved: { @@ -154,7 +154,7 @@ describe('config/src/utils', () => { }, } - expect(isResolvedConfigPropDefault(options, 'baseUrl')).to.be.true + expect(isResolvedConfigPropDefault(options, 'baseUrl')).toBe(true) }) it('returns false if value is not default value', () => { @@ -164,7 +164,7 @@ describe('config/src/utils', () => { }, } - expect(isResolvedConfigPropDefault(options, 'baseUrl')).to.be.false + expect(isResolvedConfigPropDefault(options, 'baseUrl')).toBe(false) }) }) }) diff --git a/packages/config/test/validation.spec.ts b/packages/config/test/validation.spec.ts index a698dbef73a..6164c0d2cae 100644 --- a/packages/config/test/validation.spec.ts +++ b/packages/config/test/validation.spec.ts @@ -1,6 +1,4 @@ -import snapshot from 'snap-shot-it' -import { expect } from 'chai' - +import { describe, it, expect } from 'vitest' import * as validation from '../src/validation' describe('config/src/validation', () => { @@ -10,8 +8,8 @@ describe('config/src/validation', () => { it('returns new validation function that accepts 2 arguments', () => { const validate = validation.validateAny(() => true, () => false) - expect(validate).to.be.a.instanceof(Function) - expect(validate.length).to.eq(2) + expect(validate).toBeInstanceOf(Function) + expect(validate.length).toEqual(2) }) it('returned validation function will return true when any validations pass', () => { @@ -19,11 +17,11 @@ describe('config/src/validation', () => { const key = `key_${value}` const validatePass1 = validation.validateAny((k, v) => `${value}`, (k, v) => true) - expect(validatePass1(key, value)).to.equal(true) + expect(validatePass1(key, value)).toEqual(true) const validatePass2 = validation.validateAny((k, v) => true, (k, v) => `${value}`) - expect(validatePass2(key, value)).to.equal(true) + expect(validatePass2(key, value)).toEqual(true) }) it('returned validation function will return last failure result when all validations fail', () => { @@ -31,11 +29,11 @@ describe('config/src/validation', () => { const key = `key_${value}` const validateFail1 = validation.validateAny((k, v) => `${value}`, (k, v) => false) - expect(validateFail1(key, value)).to.equal(false) + expect(validateFail1(key, value)).toEqual(false) const validateFail2 = validation.validateAny((k, v) => false, (k, v) => `${value}`) - expect(validateFail2(key, value)).to.equal(`${value}`) + expect(validateFail2(key, value)).toEqual(`${value}`) }) }) @@ -43,8 +41,8 @@ describe('config/src/validation', () => { it('returns error message for certs not passed as an array array', () => { const result = validation.isValidRetriesConfig(mockKey, '1') - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('returns error message for certs object without url', () => { @@ -52,8 +50,8 @@ describe('config/src/validation', () => { { name: 'cert' }, ]) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('returns error message for certs url not matching *', () => { @@ -61,15 +59,15 @@ describe('config/src/validation', () => { { url: 'http://url.com' }, ]) - expect(result).to.not.be.true - snapshot('missing https protocol', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('missing https protocol') result = validation.isValidClientCertificatesSet(mockKey, [ { url: 'not *' }, ]) - expect(result).to.not.be.true - snapshot('invalid url', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('invalid url') }) }) @@ -115,18 +113,22 @@ describe('config/src/validation', () => { }, ] - // data-driven testing - computers snapshot value for each item in the list passed through the function - // https://github.com/bahmutov/snap-shot-it#data-driven-testing - return snapshot.apply(null, [validation.isValidBrowser].concat(browsers as any)) + for (const browser of browsers) { + const isValid = validation.isValidBrowser(browser) + + expect({ + browser, + isValid, + }).toMatchSnapshot(`isValidBrowser ${browser.name}`) + } }) }) describe('.isValidBrowserList', () => { it('does not allow empty or not browsers', () => { - snapshot('undefined browsers', validation.isValidBrowserList('browsers', undefined)) - snapshot('empty list of browsers', validation.isValidBrowserList('browsers', [])) - - return snapshot('browsers list with a string', validation.isValidBrowserList('browsers', ['foo'])) + expect(validation.isValidBrowserList('browsers', undefined)).toMatchSnapshot('undefined browsers') + expect(validation.isValidBrowserList('browsers', [])).toMatchSnapshot('empty list of browsers') + expect(validation.isValidBrowserList('browsers', ['foo'])).toMatchSnapshot('browsers list with a string') }) }) @@ -134,60 +136,60 @@ describe('config/src/validation', () => { it('returns true for valid retry value', () => { let result = validation.isValidRetriesConfig(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, 2) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, 250) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for valid retry objects', () => { let result = validation.isValidRetriesConfig(mockKey, { runMode: 1 }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { runMode: 250 }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { openMode: 1 }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { openMode: 250 }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { runMode: 3, openMode: 0, }) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message for invalid retry config', () => { let result = validation.isValidRetriesConfig(mockKey, '1') - expect(result).to.not.be.true - snapshot('invalid retry value', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('invalid retry value') result = validation.isValidRetriesConfig(mockKey, { fakeMode: 1 }) - expect(result).to.not.be.true - snapshot('invalid retry object', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('invalid retry object') }) it('returns error message for openMode as boolean without strategy', () => { let result = validation.isValidRetriesConfig(mockKey, { openMode: true }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('returns error message for runMode as boolean without strategy', () => { let result = validation.isValidRetriesConfig(mockKey, { runMode: true }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('returns true for valid retry object with experimental keys (default)', () => { @@ -198,7 +200,7 @@ describe('config/src/validation', () => { experimentalOptions: undefined, }) - expect(result).to.be.true + expect(result).toBe(true) }) describe('experimental options', () => { @@ -211,13 +213,13 @@ describe('config/src/validation', () => { experimentalStrategy: strategy, }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { experimentalStrategy: strategy, }) - expect(result).to.be.true + expect(result).toBe(true) }) it(`experimentalStrategy is "${strategy}" with only "maxRetries" in "experimentalOptions"`, () => { @@ -228,7 +230,7 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true + expect(result).not.toBe(true) }) }) @@ -243,7 +245,7 @@ describe('config/src/validation', () => { }, }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { experimentalStrategy: 'detect-flake-but-always-fail', @@ -253,7 +255,7 @@ describe('config/src/validation', () => { }, }) - expect(result).to.be.true + expect(result).toBe(true) }) it('experimentalStrategy is "detect-flake-and-pass-on-threshold" and has option "passesRequired"', () => { @@ -267,7 +269,7 @@ describe('config/src/validation', () => { }, }) - expect(result).to.be.true + expect(result).toBe(true) result = validation.isValidRetriesConfig(mockKey, { experimentalStrategy: 'detect-flake-and-pass-on-threshold', @@ -277,7 +279,7 @@ describe('config/src/validation', () => { }, }) - expect(result).to.be.true + expect(result).toBe(true) }) }) @@ -287,8 +289,8 @@ describe('config/src/validation', () => { experimentalStrategy: 'foo', }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('invalid strategy w/ other options (valid)', () => { @@ -298,8 +300,8 @@ describe('config/src/validation', () => { experimentalStrategy: 'bar', }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) ;['detect-flake-but-always-fail', 'detect-flake-and-pass-on-threshold'].forEach((strategy) => { @@ -310,8 +312,8 @@ describe('config/src/validation', () => { experimentalStrategy: strategy, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it(`${strategy}: maxRetries is negative`, () => { @@ -322,8 +324,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it(`${strategy}: maxRetries is 0`, () => { @@ -334,8 +336,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it(`${strategy}: maxRetries is floating`, () => { @@ -346,8 +348,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -361,8 +363,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it(`passesRequired is 0`, () => { @@ -374,8 +376,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it(`passesRequired is floating`, () => { @@ -387,8 +389,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('provides passesRequired without maxRetries', () => { @@ -399,8 +401,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('provides passesRequired that is greater than maxRetries', () => { @@ -412,8 +414,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('provides stopIfAnyPassed option', () => { @@ -426,8 +428,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -442,8 +444,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('provides stopIfAnyPassed without maxRetries', () => { @@ -454,8 +456,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('provides maxRetries without stopIfAnyPassed', () => { @@ -466,8 +468,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) it('stopIfAnyPassed is a number (0 and 1 do not work)', () => { @@ -479,8 +481,8 @@ describe('config/src/validation', () => { }, }) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) }) @@ -491,20 +493,20 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isPlainObject(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=number', () => { const result = validation.isPlainObject(mockKey, { foo: 'bar' }) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not an object', () => { const result = validation.isPlainObject(mockKey, 1) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -512,20 +514,20 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isNumber(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=number', () => { const result = validation.isNumber(mockKey, 1) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not a number', () => { const result = validation.isNumber(mockKey, 'string') - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -533,20 +535,20 @@ describe('config/src/validation', () => { it('returns true for value=number', () => { const result = validation.isNumberOrFalse(mockKey, 1) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=false', () => { const result = validation.isNumberOrFalse(mockKey, false) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not number or false', () => { const result = validation.isNumberOrFalse(mockKey, null) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -554,26 +556,27 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isFullyQualifiedUrl(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=qualified urls', () => { let result = validation.isFullyQualifiedUrl(mockKey, 'https://url.com') - expect(result).to.be.true + expect(result).toBe(true) result = validation.isFullyQualifiedUrl(mockKey, 'http://url.com') - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not qualified url', () => { let result = validation.isFullyQualifiedUrl(mockKey, 'url.com') - expect(result).to.not.be.true - snapshot('not qualified url', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('not qualified url') result = validation.isFullyQualifiedUrl(mockKey, '') - expect(result).to.not.be.true - snapshot('empty string', result) + + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('empty string') }) }) @@ -581,26 +584,26 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isBoolean(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=true', () => { const result = validation.isBoolean(mockKey, true) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=false', () => { const result = validation.isBoolean(mockKey, false) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not a string', () => { const result = validation.isString(mockKey, 1) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -608,20 +611,20 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isString(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=array', () => { const result = validation.isString(mockKey, 'string') - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a not a string', () => { const result = validation.isString(mockKey, 1) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -629,20 +632,20 @@ describe('config/src/validation', () => { it('returns true for value=null', () => { const result = validation.isArray(mockKey, null) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=array', () => { const result = validation.isArray(mockKey, [1, 2, 3]) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is a non-array', () => { const result = validation.isArray(mockKey, 1) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -650,20 +653,20 @@ describe('config/src/validation', () => { it('returns true for value=string', () => { const result = validation.isStringOrFalse(mockKey, 'string') - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=false', () => { const result = validation.isStringOrFalse(mockKey, false) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is neither string nor false', () => { const result = validation.isStringOrFalse(mockKey, null) - expect(result).to.not.be.true - snapshot(result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot() }) }) @@ -671,25 +674,25 @@ describe('config/src/validation', () => { it('returns true for value=string', () => { const result = validation.isStringOrArrayOfStrings(mockKey, 'string') - expect(result).to.be.true + expect(result).toBe(true) }) it('returns true for value=array of strings', () => { const result = validation.isStringOrArrayOfStrings(mockKey, ['string', 'other']) - expect(result).to.be.true + expect(result).toBe(true) }) it('returns error message when value is neither string nor array of string', () => { let result = validation.isStringOrArrayOfStrings(mockKey, null) - expect(result).to.not.be.true - snapshot('not string or array', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('not string or array') result = validation.isStringOrArrayOfStrings(mockKey, [1, 2, 3]) - expect(result).to.not.be.true - snapshot('array of non-strings', result) + expect(result).not.toBe(true) + expect(result).toMatchSnapshot('array of non-strings') }) }) @@ -698,46 +701,46 @@ describe('config/src/validation', () => { const validate = validation.isOneOf('foo', 'bar') expect(validate).to.be.a('function') - expect(validate('test', 'foo')).to.be.true - expect(validate('test', 'bar')).to.be.true + expect(validate('test', 'foo')).toBe(true) + expect(validate('test', 'bar')).toBe(true) // different value let msg = validate('test', 'nope') - expect(msg).to.not.be.true - snapshot('not one of the strings error message', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('not one of the strings error message') msg = validate('test', 42) - expect(msg).to.not.be.true - snapshot('number instead of string', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('number instead of string') msg = validate('test', null) - expect(msg).to.not.be.true + expect(msg).not.toBe(true) - return snapshot('null instead of string', msg) + expect(msg).toMatchSnapshot('null instead of string') }) it('validates a number', () => { const validate = validation.isOneOf(1, 2, 3) expect(validate).to.be.a('function') - expect(validate('test', 1)).to.be.true - expect(validate('test', 3)).to.be.true + expect(validate('test', 1)).toBe(true) + expect(validate('test', 3)).toBe(true) // different value let msg = validate('test', 4) - expect(msg).to.not.be.true - snapshot('not one of the numbers error message', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('not one of the numbers error message') msg = validate('test', 'foo') - expect(msg).to.not.be.true - snapshot('string instead of a number', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('string instead of a number') msg = validate('test', null) - expect(msg).to.not.be.true + expect(msg).not.toBe(true) - return snapshot('null instead of a number', msg) + return expect(msg).toMatchSnapshot('null instead of a number') }) }) @@ -746,7 +749,7 @@ describe('config/src/validation', () => { const validate = validation.isArrayIncludingAny(true, false) expect(validate).to.be.a.instanceof(Function) - expect(validate.length).to.eq(2) + expect(validate.length).toEqual(2) }) it('returned validation function will return true when value is a subset of the provided values', () => { @@ -754,11 +757,11 @@ describe('config/src/validation', () => { const key = 'fakeKey' const validatePass1 = validation.isArrayIncludingAny(true, false) - expect(validatePass1(key, [false])).to.equal(true) + expect(validatePass1(key, [false])).toEqual(true) const validatePass2 = validation.isArrayIncludingAny(value, value + 1, value + 2) - expect(validatePass2(key, [value])).to.equal(true) + expect(validatePass2(key, [value])).toEqual(true) }) it('returned validation function will fail if values is not an array', () => { @@ -768,8 +771,8 @@ describe('config/src/validation', () => { let msg = validateFail(key, value) - expect(msg).to.not.be.true - snapshot('not an array error message', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('not an array error message') }) it('returned validation function will fail if any values are not present in the provided values', () => { @@ -779,13 +782,13 @@ describe('config/src/validation', () => { let msg = validateFail(key, [null]) - expect(msg).to.not.be.true - snapshot('not a subset of error message', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('not a subset of error message') msg = validateFail(key, [value, value + 1, value + 2, value + 3]) - expect(msg).to.not.be.true - snapshot('not all in subset error message', msg) + expect(msg).not.toBe(true) + expect(msg).toMatchSnapshot('not all in subset error message') }) }) @@ -793,9 +796,9 @@ describe('config/src/validation', () => { it('validates booleans', () => { const validate = validation.isValidCrfOrBoolean - expect(validate).to.be.a('function') - expect(validate('test', false)).to.be.true - expect(validate('test', true)).to.be.true + expect(validate).toBeInstanceOf(Function) + expect(validate('test', false)).toBe(true) + expect(validate('test', true)).toBe(true) }) it('validates any number between 0 and 51', () => { @@ -804,7 +807,7 @@ describe('config/src/validation', () => { const validConfigNumbers = [...Array(51).keys()] validConfigNumbers.forEach((num) => { - expect(validate('test', num)).to.be.true + expect(validate('test', num)).toBe(true) }) }) @@ -813,9 +816,9 @@ describe('config/src/validation', () => { const lowerBoundMsg = validate('test', -1) - expect(lowerBoundMsg).to.not.be.true + expect(lowerBoundMsg).not.toBe(true) - return snapshot('invalid lower bound', lowerBoundMsg) + return expect(lowerBoundMsg).toMatchSnapshot('invalid lower bound') }) it('invalidates upper bound', () => { @@ -823,9 +826,9 @@ describe('config/src/validation', () => { const upperBoundMsg = validate('test', 52) - expect(upperBoundMsg).to.not.be.true + expect(upperBoundMsg).not.toBe(true) - return snapshot('invalid upper bound', upperBoundMsg) + return expect(upperBoundMsg).toMatchSnapshot('invalid upper bound') }) }) }) diff --git a/packages/config/vitest.config.ts b/packages/config/vitest.config.ts new file mode 100644 index 00000000000..1a9a321880f --- /dev/null +++ b/packages/config/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.spec.ts'], + globals: true, + environment: 'node', + }, +})