mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-10 00:49:38 -05:00
chore: convert @packages/data-context from mocha to jest (#32598)
* start jest conversion and covnert authaction to jest * chore: conver codegenaction to jest * chore: convert cohorts action spec to vitest * chore: convert dataEmitterActions to jest * chore: convert event collector action to vitest * chore: convert local settings action spec tojest * chore: convert notification action to vitest * chor: convert project actions to vitest and fix pretty-formnat * chore: convert code-generator spec to jest * chore: convert spec options spec to jest * chore: convert project config ipc to jest * chore: convert projectconfigmanager tests to jest * chore: convert project lifecycle manager tests to jest * chore: conver poller spec to jest * chore: convert browser data source to jest * chore: convert cloud dats source to jest * chore: convert gitdatasource unit tests to jest * chore: convert FileDataSource to jest * chore: convert git data source to jest * chore: convert graphql data source to jest * chore: convert project data source to jest * chore: convert revelvant runs data source to jest * chore: convert relevant run specs data source to jest * chore: convert remote request data source to jest * chore: convert versions data source spec to jest * chore: convert wizard data source spec to jest * chore: convert document node builder to jest * chore: convert has TypeScript to jest * chore: convert test counts spec to jest * chore: convert weighted choice spec to jest * chore: convert config file updater to jest * clean up files to allow tests to run in band but out of order, add README, cleanup unused scripts and dependencies * fix deprecation warning from ts-jest via isolated modules * chore: adjust expected result count * chore: clean up types * turn back on no implicit any and reinstall graphql package in frontend shared * fix isFocusSupported mock * chore: copy over the old data-context-helper into the server as the one in the data-context package is written in jest now * chore: fix code review findings from cursor * fix incorrect expect async throw * chore: remove use of this in projectData source * add docs to copied data-context helper and fix cursor detected issue
This commit is contained in:
@@ -1784,7 +1784,7 @@ jobs:
|
||||
source ./scripts/ensure-node.sh
|
||||
yarn lerna run types
|
||||
- sanitize-verify-and-store-mocha-results:
|
||||
expectedResultCount: 14
|
||||
expectedResultCount: 13
|
||||
|
||||
verify-release-readiness:
|
||||
<<: *defaults
|
||||
|
||||
@@ -3486,7 +3486,7 @@ jobs:
|
||||
yarn lerna run types
|
||||
name: Test types
|
||||
- sanitize-verify-and-store-mocha-results:
|
||||
expectedResultCount: 14
|
||||
expectedResultCount: 13
|
||||
working_directory: ~/cypress
|
||||
v8-integration-tests:
|
||||
environment:
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
##### Binary Packages
|
||||
|
||||
- [x] packages/config ✅ **COMPLETED**
|
||||
- [ ] packages/data-context
|
||||
- [x] packages/data-context **COMPLETED** (migrated from `mocha`/`sinon`/`chai` to `jest`). See package README for more details as to why `jest` over `vitest`
|
||||
- [x] packages/driver ✅ **COMPLETED**
|
||||
- [x] packages/electron ✅ **COMPLETED**
|
||||
- [x] packages/error ✅ **COMPLETED**
|
||||
|
||||
@@ -274,7 +274,6 @@
|
||||
"**/@types/cheerio": "0.22.21",
|
||||
"**/@types/enzyme": "3.10.5",
|
||||
"**/jquery": "3.7.1",
|
||||
"**/pretty-format": "26.4.0",
|
||||
"**/socket.io-parser": "4.0.5",
|
||||
"**/ua-parser-js": "0.7.33",
|
||||
"@definitelytyped/typescript-versions": "0.1.7",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
foo-project/src/components/App.jsx
|
||||
@@ -157,4 +157,10 @@ export const Query = objectType({
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
This package uses [jest](https://jestjs.io/) to integration/unit test this package. [vitest](https://vitest.dev/) currently cannot be used due to how `graphql` in this package is loaded into the vitest service worker, which is likely due to multiple instances of the GraphQL server being available. Since `jest` runs in a shared process, we are currently leveraging `jest` in place of `vitest`.
|
||||
|
||||
Additionally, Because the [ProjectLifecycleManager](https://github.com/cypress-io/cypress/blob/v15.3.0/packages/data-context/src/data/ProjectLifecycleManager.ts#L436) changes the current working directory for the process, we cannot run the tests in parallel, hence why the `--runInBand` option is utilized for running the tests.
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Config } from 'jest'
|
||||
// @see https://kulshekhar.github.io/ts-jest/docs for documentation on ts-jest
|
||||
import { createDefaultPreset } from 'ts-jest'
|
||||
|
||||
const tsJestTransformCfg = createDefaultPreset({
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
}).transform
|
||||
|
||||
export default async (): Promise<Config> => {
|
||||
return {
|
||||
testMatch: ['<rootDir>/test/**/*.spec.ts'],
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
...tsJestTransformCfg,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
"lint": "eslint --ext .js,.ts,.json, .",
|
||||
"nexus-build": "ts-node ./scripts/nexus-build.ts",
|
||||
"test": "yarn test-unit",
|
||||
"test-unit": "mocha -r @packages/ts/register --config ./test/.mocharc.js --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json",
|
||||
"test-debug": "node --experimental-vm-modules --inspect-brk node_modules/.bin/jest --runInBand --testTimeout=600000",
|
||||
"test-unit": "yarn node --experimental-vm-modules node_modules/.bin/jest --runInBand",
|
||||
"tslint": "tslint --config ../ts/tslint.json --project . --exclude ./src/gen/nxs.gen.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -101,24 +102,17 @@
|
||||
"@types/ejs": "^3.1.2",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/graphql-resolve-batch": "1.1.6",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/parse-glob": "3.0.29",
|
||||
"@types/prettier": "2.4.3",
|
||||
"@types/server-destroy": "^1.0.1",
|
||||
"@types/sinon": "10.0.11",
|
||||
"@types/sinon-chai": "3.2.12",
|
||||
"@types/stringify-object": "^3.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"chokidar": "3.6.0",
|
||||
"fs-extra": "9.1.0",
|
||||
"mocha": "7.0.1",
|
||||
"mocha-junit-reporter": "2.2.0",
|
||||
"mocha-multi-reporters": "1.5.1",
|
||||
"jest": "^30.1.3",
|
||||
"nexus": "^1.2.0-next.15",
|
||||
"sinon": "13.0.2",
|
||||
"sinon-chai": "3.7.0",
|
||||
"snap-shot-it": "7.9.10",
|
||||
"ts-jest": "^29.4.4",
|
||||
"tslint": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
spec: 'test/unit/**/*.spec.ts',
|
||||
watchFiles: ['test/**/*.ts', 'src/**/*.ts'],
|
||||
}
|
||||
@@ -1,52 +1,53 @@
|
||||
import { describe, expect, jest, it } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { AuthActions } from '../../../src/actions/AuthActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import sinon, { SinonStub } from 'sinon'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import chai, { expect } from 'chai'
|
||||
import { FoundBrowser } from '@packages/types'
|
||||
|
||||
chai.use(sinonChai)
|
||||
|
||||
describe('AuthActions', () => {
|
||||
context('.login', () => {
|
||||
describe('.login', () => {
|
||||
let ctx: DataContext
|
||||
let actions: AuthActions
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
;(ctx._apis.authApi.logIn as SinonStub)
|
||||
.resolves({ name: 'steve', email: 'steve@apple.com', authToken: 'foo' })
|
||||
jest.mocked(ctx._apis.authApi.logIn).mockResolvedValue({ name: 'steve', email: 'steve@apple.com', authToken: 'foo' })
|
||||
|
||||
actions = new AuthActions(ctx)
|
||||
})
|
||||
|
||||
it('sets coreData.user', async () => {
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
expect(ctx.coreData.user).to.include({ name: 'steve', email: 'steve@apple.com', authToken: 'foo' })
|
||||
expect(ctx.coreData.user).toEqual(expect.objectContaining({ name: 'steve', email: 'steve@apple.com', authToken: 'foo' }))
|
||||
})
|
||||
|
||||
it('focuses the main window if there is no activeBrowser', async () => {
|
||||
ctx.coreData.activeBrowser = null
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.be.calledOnce
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.not.be.called
|
||||
expect(ctx._apis.electronApi.focusMainWindow).toHaveBeenCalledTimes(1)
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('focuses the main window if the activeBrowser does not support focus', async () => {
|
||||
const browser = ctx.coreData.activeBrowser = { name: 'foo' } as FoundBrowser
|
||||
|
||||
sinon.stub(ctx.browser, 'isFocusSupported').withArgs(browser).resolves(false)
|
||||
jest.spyOn(ctx.browser, 'isFocusSupported').mockImplementation((args) => {
|
||||
if (args === browser) {
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.be.calledOnce
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.not.be.called
|
||||
expect(ctx._apis.electronApi.focusMainWindow).toHaveBeenCalledTimes(1)
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('focuses the main window if the activeBrowser supports focus, but browser is closed', async () => {
|
||||
@@ -54,12 +55,19 @@ describe('AuthActions', () => {
|
||||
|
||||
ctx.coreData.app.browserStatus = 'closed'
|
||||
|
||||
sinon.stub(ctx.browser, 'isFocusSupported').withArgs(browser).resolves(true)
|
||||
jest.spyOn(ctx.browser, 'isFocusSupported').mockImplementation((args) => {
|
||||
if (args === browser) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.be.calledOnce
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.not.be.called
|
||||
expect(ctx._apis.electronApi.focusMainWindow).toHaveBeenCalledTimes(1)
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('focuses the main window if the activeBrowser supports focus, but browser is opening', async () => {
|
||||
@@ -67,12 +75,19 @@ describe('AuthActions', () => {
|
||||
|
||||
ctx.coreData.app.browserStatus = 'opening'
|
||||
|
||||
sinon.stub(ctx.browser, 'isFocusSupported').withArgs(browser).resolves(true)
|
||||
jest.spyOn(ctx.browser, 'isFocusSupported').mockImplementation((args) => {
|
||||
if (args === browser) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.be.calledOnce
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.not.be.called
|
||||
expect(ctx._apis.electronApi.focusMainWindow).toHaveBeenCalledTimes(1)
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('focuses the browser if the activeBrowser does support focus and browser is open', async () => {
|
||||
@@ -80,25 +95,39 @@ describe('AuthActions', () => {
|
||||
|
||||
ctx.coreData.app.browserStatus = 'open'
|
||||
|
||||
sinon.stub(ctx.browser, 'isFocusSupported').withArgs(browser).resolves(true)
|
||||
jest.spyOn(ctx.browser, 'isFocusSupported').mockImplementation((args) => {
|
||||
if (args === browser) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.not.be.called
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.be.calledOnce
|
||||
expect(ctx._apis.electronApi.focusMainWindow).not.toHaveBeenCalled()
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not focus anything if the activeBrowser does support focus but the main window is focused', async () => {
|
||||
const browser = ctx.coreData.activeBrowser = { name: 'foo' } as FoundBrowser
|
||||
|
||||
sinon.stub(ctx.browser, 'isFocusSupported').withArgs(browser).resolves(true)
|
||||
jest.spyOn(ctx.browser, 'isFocusSupported').mockImplementation((args) => {
|
||||
if (args === browser) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
;(ctx._apis.electronApi.isMainWindowFocused as SinonStub).returns(true)
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
jest.spyOn(ctx._apis.electronApi, 'isMainWindowFocused').mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - incorrect number of arguments
|
||||
await actions.login()
|
||||
|
||||
expect(ctx._apis.electronApi.focusMainWindow).to.not.be.called
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).to.not.be.called
|
||||
expect(ctx._apis.electronApi.focusMainWindow).not.toHaveBeenCalled()
|
||||
expect(ctx._apis.browserApi.focusActiveBrowserWindow).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { describe, expect, it, beforeEach } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { CodegenActions } from '../../../src/actions/CodegenActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import path from 'path'
|
||||
|
||||
describe('CodegenActions', () => {
|
||||
@@ -11,128 +10,130 @@ describe('CodegenActions', () => {
|
||||
let reactDocgen: typeof import('react-docgen')
|
||||
|
||||
beforeEach(async () => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
reactDocgen = await eval('import("react-docgen")')
|
||||
|
||||
actions = new CodegenActions(ctx)
|
||||
|
||||
reactDocgen = await eval('import("react-docgen")')
|
||||
})
|
||||
|
||||
context('getReactComponentsFromFile', () => {
|
||||
const absolutePathPrefix = path.resolve('./test/unit/actions/project')
|
||||
describe('getReactComponentsFromFile', () => {
|
||||
let absolutePathPrefix: string
|
||||
|
||||
beforeEach(() => {
|
||||
absolutePathPrefix = path.resolve(__dirname, './project')
|
||||
})
|
||||
|
||||
it('returns React components from file with class component', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-class.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('Counter')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('Counter')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns React components from file with functional component', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-functional.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('Counter')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('Counter')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns only exported React components from file with functional components', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-multiple-components.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(2)
|
||||
expect(components[0].exportName).to.equal('CounterContainer')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(2)
|
||||
expect(components[0].exportName).toEqual('CounterContainer')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
|
||||
expect(components[1].exportName).to.equal('CounterView')
|
||||
expect(components[1].isDefault).to.equal(false)
|
||||
expect(components[1].exportName).toEqual('CounterView')
|
||||
expect(components[1].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns React components from a tsx file', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter.tsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('Counter')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('Counter')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns React components that are exported by default', async () => {
|
||||
let reactComponents = await (await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-default.tsx`, reactDocgen)).components
|
||||
|
||||
expect(reactComponents).to.have.length(1)
|
||||
expect(reactComponents[0].exportName).to.equal('CounterDefault')
|
||||
expect(reactComponents[0].isDefault).to.equal(true)
|
||||
expect(reactComponents).toHaveLength(1)
|
||||
expect(reactComponents[0].exportName).toEqual('CounterDefault')
|
||||
expect(reactComponents[0].isDefault).toEqual(true)
|
||||
|
||||
reactComponents = await (await actions.getReactComponentsFromFile(`${absolutePathPrefix}/default-anonymous.jsx`, reactDocgen)).components
|
||||
expect(reactComponents).to.have.length(1)
|
||||
expect(reactComponents[0].exportName).to.equal('Component')
|
||||
expect(reactComponents[0].isDefault).to.equal(true)
|
||||
expect(reactComponents).toHaveLength(1)
|
||||
expect(reactComponents[0].exportName).toEqual('Component')
|
||||
expect(reactComponents[0].isDefault).toEqual(true)
|
||||
|
||||
reactComponents = await (await actions.getReactComponentsFromFile(`${absolutePathPrefix}/default-function.jsx`, reactDocgen)).components
|
||||
expect(reactComponents).to.have.length(1)
|
||||
expect(reactComponents[0].exportName).to.equal('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).to.equal(true)
|
||||
expect(reactComponents).toHaveLength(1)
|
||||
expect(reactComponents[0].exportName).toEqual('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).toEqual(true)
|
||||
|
||||
reactComponents = await (await actions.getReactComponentsFromFile(`${absolutePathPrefix}/default-class.jsx`, reactDocgen)).components
|
||||
expect(reactComponents).to.have.length(1)
|
||||
expect(reactComponents[0].exportName).to.equal('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).to.equal(true)
|
||||
expect(reactComponents).toHaveLength(1)
|
||||
expect(reactComponents[0].exportName).toEqual('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).toEqual(true)
|
||||
|
||||
reactComponents = await (await actions.getReactComponentsFromFile(`${absolutePathPrefix}/default-specifier.jsx`, reactDocgen)).components
|
||||
expect(reactComponents).to.have.length(1)
|
||||
expect(reactComponents[0].exportName).to.equal('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).to.equal(true)
|
||||
expect(reactComponents).toHaveLength(1)
|
||||
expect(reactComponents[0].exportName).toEqual('HelloWorld')
|
||||
expect(reactComponents[0].isDefault).toEqual(true)
|
||||
})
|
||||
|
||||
it('returns React components defined with arrow functions', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-arrow-function.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('Counter')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('Counter')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns React components from a file with multiple separate export statements', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-separate-exports.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(2)
|
||||
expect(components[0].exportName).to.equal('CounterView')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components[1].exportName).to.equal('CounterContainer')
|
||||
expect(components[1].isDefault).to.equal(true)
|
||||
expect(components).toHaveLength(2)
|
||||
expect(components[0].exportName).toEqual('CounterView')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
expect(components[1].exportName).toEqual('CounterContainer')
|
||||
expect(components[1].isDefault).toEqual(true)
|
||||
})
|
||||
|
||||
it('returns React components that are exported and aliased', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/export-alias.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('HelloWorld')
|
||||
expect(components[0].isDefault).to.equal(false)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('HelloWorld')
|
||||
expect(components[0].isDefault).toEqual(false)
|
||||
})
|
||||
|
||||
// TODO: "react-docgen" will resolve HOCs but our export detection does not. Can fall back to displayName here
|
||||
it.skip('handles higher-order-components', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/counter-hoc.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('Counter')
|
||||
expect(components[0].isDefault).to.equal(true)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('Counter')
|
||||
expect(components[0].isDefault).toEqual(true)
|
||||
})
|
||||
|
||||
it('correctly parses typescript files', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/LoginForm.tsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(1)
|
||||
expect(components[0].exportName).to.equal('LoginForm')
|
||||
expect(components[0].isDefault).to.equal(true)
|
||||
expect(components).toHaveLength(1)
|
||||
expect(components[0].exportName).toEqual('LoginForm')
|
||||
expect(components[0].isDefault).toEqual(true)
|
||||
})
|
||||
|
||||
it('does not throw while parsing empty file', async () => {
|
||||
const { components } = await actions.getReactComponentsFromFile(`${absolutePathPrefix}/empty.jsx`, reactDocgen)
|
||||
|
||||
expect(components).to.have.length(0)
|
||||
expect(components).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('ensure that Babel is instructed to not use a config file', async () => {
|
||||
@@ -152,11 +153,11 @@ describe('CodegenActions', () => {
|
||||
},
|
||||
}
|
||||
|
||||
const filePath = path.join(process.cwd(), 'test/unit/actions/project/counter-class.jsx')
|
||||
const filePath = path.join(__dirname, 'project/counter-class.jsx')
|
||||
|
||||
await actions.getReactComponentsFromFile(filePath, mockReactDocgen)
|
||||
await actions.getReactComponentsFromFile(filePath, mockReactDocgen as unknown as typeof import('react-docgen'))
|
||||
|
||||
expect(capturedOptions.babelOptions.configFile).to.equal(false)
|
||||
expect(capturedOptions.babelOptions.configFile).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { CohortsActions } from '../../../src/actions/CohortsActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { expect } from 'chai'
|
||||
import sinon, { SinonStub, match } from 'sinon'
|
||||
|
||||
describe('CohortsActions', () => {
|
||||
let ctx: DataContext
|
||||
let actions: CohortsActions
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
actions = new CohortsActions(ctx)
|
||||
})
|
||||
|
||||
context('getCohort', () => {
|
||||
describe('getCohort', () => {
|
||||
it('should return null if name not found', async () => {
|
||||
const name = '123'
|
||||
|
||||
const cohort = await actions.getCohort(name)
|
||||
|
||||
expect(cohort).to.be.undefined
|
||||
expect(ctx.config.cohortsApi.getCohort).to.have.been.calledWith(name)
|
||||
expect(cohort).toBeUndefined()
|
||||
expect(ctx.config.cohortsApi.getCohort).toHaveBeenCalledWith(name)
|
||||
})
|
||||
|
||||
it('should return cohort if in cache', async () => {
|
||||
@@ -32,16 +29,16 @@ describe('CohortsActions', () => {
|
||||
cohort: 'A',
|
||||
}
|
||||
|
||||
;(ctx._apis.cohortsApi.getCohort as SinonStub).resolves(cohort)
|
||||
jest.spyOn(ctx._apis.cohortsApi, 'getCohort').mockResolvedValue(cohort)
|
||||
|
||||
const cohortReturned = await actions.getCohort(cohort.name)
|
||||
|
||||
expect(cohortReturned).to.eq(cohort)
|
||||
expect(ctx.config.cohortsApi.getCohort).to.have.been.calledWith(cohort.name)
|
||||
expect(cohortReturned).toEqual(cohort)
|
||||
expect(ctx.config.cohortsApi.getCohort).toHaveBeenCalledWith(cohort.name)
|
||||
})
|
||||
})
|
||||
|
||||
context('determineCohort', () => {
|
||||
describe('determineCohort', () => {
|
||||
it('should determine cohort', async () => {
|
||||
const cohortConfig = {
|
||||
name: 'loginBanner',
|
||||
@@ -50,8 +47,8 @@ describe('CohortsActions', () => {
|
||||
|
||||
const pickedCohort = await actions.determineCohort(cohortConfig.name, cohortConfig.cohorts)
|
||||
|
||||
expect(ctx.config.cohortsApi.insertCohort).to.have.been.calledOnceWith({ name: cohortConfig.name, cohort: match.string })
|
||||
expect(cohortConfig.cohorts.includes(pickedCohort.cohort)).to.be.true
|
||||
expect(ctx.config.cohortsApi.insertCohort).toHaveBeenNthCalledWith(1, { name: cohortConfig.name, cohort: expect.any(String) })
|
||||
expect(cohortConfig.cohorts.includes(pickedCohort.cohort)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, beforeAll, jest } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { DataEmitterActions } from '../../../src/actions/DataEmitterActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
describe('DataEmitterActions', () => {
|
||||
context('.subscribeTo', () => {
|
||||
describe('.subscribeTo', () => {
|
||||
let ctx: DataContext
|
||||
|
||||
before(() => {
|
||||
beforeAll(() => {
|
||||
ctx = createTestDataContext('open')
|
||||
})
|
||||
|
||||
@@ -44,8 +43,8 @@ describe('DataEmitterActions', () => {
|
||||
|
||||
await iteratorPromise
|
||||
|
||||
expect(items).to.eql(3)
|
||||
expect(completed).to.be.true
|
||||
expect(items).toEqual(3)
|
||||
expect(completed).toBe(true)
|
||||
})
|
||||
|
||||
it('handles iterating through events if an event is emitted before the iteration', async () => {
|
||||
@@ -75,8 +74,8 @@ describe('DataEmitterActions', () => {
|
||||
|
||||
await iteratorPromise
|
||||
|
||||
expect(items).to.eql(3)
|
||||
expect(completed).to.be.true
|
||||
expect(items).toEqual(3)
|
||||
expect(completed).toBe(true)
|
||||
})
|
||||
|
||||
it('handles stopping the loop if return is called before the iteration', async () => {
|
||||
@@ -103,8 +102,8 @@ describe('DataEmitterActions', () => {
|
||||
|
||||
await iteratorPromise
|
||||
|
||||
expect(items).to.eql(0)
|
||||
expect(completed).to.be.true
|
||||
expect(items).toEqual(0)
|
||||
expect(completed).toBe(true)
|
||||
})
|
||||
|
||||
const createTestIterator = (subscription) => {
|
||||
@@ -130,7 +129,7 @@ describe('DataEmitterActions', () => {
|
||||
it('handles multiple subscriptions', async () => {
|
||||
const actions = new DataEmitterActions(ctx)
|
||||
|
||||
const unsubscribe1 = sinon.stub()
|
||||
const unsubscribe1 = jest.fn()
|
||||
|
||||
const subscription1 = actions.subscribeTo('specsChange', { sendInitial: true, onUnsubscribe: unsubscribe1 })
|
||||
|
||||
@@ -146,7 +145,7 @@ describe('DataEmitterActions', () => {
|
||||
let subscription2
|
||||
let iteratorFactory2
|
||||
let iteratorPromise2
|
||||
let unsubscribe2 = sinon.stub()
|
||||
let unsubscribe2 = jest.fn()
|
||||
|
||||
setImmediate(() => {
|
||||
subscription2 = actions.subscribeTo('specsChange', { sendInitial: true, onUnsubscribe: unsubscribe2 })
|
||||
@@ -168,14 +167,17 @@ describe('DataEmitterActions', () => {
|
||||
await iteratorPromise1
|
||||
await iteratorPromise2
|
||||
|
||||
expect(iteratorFactory1.items, 'first subscription should be called 3 times').to.eql(3)
|
||||
expect(iteratorFactory1.completed).to.be.true
|
||||
// first subscription should be called 3 times
|
||||
expect(iteratorFactory1.items).toEqual(3)
|
||||
expect(iteratorFactory1.completed).toBe(true)
|
||||
|
||||
expect(iteratorFactory2.items, 'second subscription should be called 2 times').to.eql(2)
|
||||
expect(iteratorFactory2.completed).to.be.true
|
||||
// second subscription should be called 2 times
|
||||
expect(iteratorFactory2.items).toEqual(2)
|
||||
expect(iteratorFactory2.completed).toBe(true)
|
||||
|
||||
expect(unsubscribe1, 'should unsubscribe and see there is 1 subscription still listening').to.have.been.calledOnceWith(1)
|
||||
expect(unsubscribe2).to.have.been.calledOnceWith(0)
|
||||
// should unsubscribe and see there is 1 subscription still listening
|
||||
expect(unsubscribe1).toHaveBeenNthCalledWith(1, 1)
|
||||
expect(unsubscribe2).toHaveBeenNthCalledWith(1, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
|
||||
import type { DataContext } from '../../../src'
|
||||
import { EventCollectorActions } from '../../../src/actions/EventCollectorActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import sinon, { SinonStub } from 'sinon'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import chai, { expect } from 'chai'
|
||||
|
||||
const pkg = require('@packages/root')
|
||||
|
||||
chai.use(sinonChai)
|
||||
import pkg from '@packages/root'
|
||||
|
||||
describe('EventCollectorActions', () => {
|
||||
let ctx: DataContext
|
||||
let actions: EventCollectorActions
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
sinon.stub(ctx.util, 'fetch').resolves({} as any)
|
||||
jest.spyOn(ctx.util, 'fetch').mockResolvedValue({} as any)
|
||||
|
||||
actions = new EventCollectorActions(ctx)
|
||||
})
|
||||
|
||||
context('.recordEvent', () => {
|
||||
describe('.recordEvent', () => {
|
||||
it('makes expected request for anonymous event', async () => {
|
||||
await actions.recordEvent({
|
||||
campaign: 'abc',
|
||||
@@ -32,8 +26,9 @@ describe('EventCollectorActions', () => {
|
||||
cohort: '123',
|
||||
}, false)
|
||||
|
||||
expect(ctx.util.fetch).to.have.been.calledOnceWith(
|
||||
sinon.match(/anon-collect$/), // Verify URL ends with expected 'anon-collect' path
|
||||
expect(ctx.util.fetch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringMatching(/anon-collect$/), // Verify URL ends with expected 'anon-collect' path
|
||||
{ method: 'POST', headers: { 'Content-Type': 'application/json', 'x-cypress-version': pkg.version }, body: '{"campaign":"abc","medium":"def","messageId":"ghi","cohort":"123"}' },
|
||||
)
|
||||
})
|
||||
@@ -48,26 +43,27 @@ describe('EventCollectorActions', () => {
|
||||
cohort: '123',
|
||||
}, true)
|
||||
|
||||
expect(ctx.util.fetch).to.have.been.calledOnceWith(
|
||||
sinon.match(/machine-collect$/), // Verify URL ends with expected 'machine-collect' path
|
||||
expect(ctx.util.fetch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringMatching(/machine-collect$/), // Verify URL ends with expected 'machine-collect' path
|
||||
{ method: 'POST', headers: { 'Content-Type': 'application/json', 'x-cypress-version': pkg.version }, body: '{"campaign":"abc","medium":"def","messageId":"ghi","cohort":"123","machineId":"xyz"}' },
|
||||
)
|
||||
})
|
||||
|
||||
it('resolve true if request succeeds', async () => {
|
||||
(ctx.util.fetch as SinonStub).resolves({} as any)
|
||||
jest.spyOn(ctx.util, 'fetch').mockResolvedValue({} as any)
|
||||
|
||||
const result = await actions.recordEvent({ campaign: '', medium: '', messageId: '', cohort: '' }, false)
|
||||
|
||||
expect(result).to.eql(true)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('resolves false if request fails', async () => {
|
||||
(ctx.util.fetch as SinonStub).rejects({} as any)
|
||||
jest.spyOn(ctx.util, 'fetch').mockRejectedValue({} as any)
|
||||
|
||||
const result = await actions.recordEvent({ campaign: '', medium: '', messageId: '', cohort: '' }, false)
|
||||
|
||||
expect(result).to.eql(false)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import { LocalSettingsActions } from '../../../src/actions/LocalSettingsActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import type { DataContext } from '../../../src'
|
||||
@@ -10,54 +9,51 @@ describe('LocalSettingsActions', () => {
|
||||
let actions: LocalSettingsActions
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
actions = new LocalSettingsActions(ctx)
|
||||
})
|
||||
|
||||
context('refreshLocalSettings', () => {
|
||||
context('notifyWhenRunCompletes', () => {
|
||||
describe('refreshLocalSettings', () => {
|
||||
describe('notifyWhenRunCompletes', () => {
|
||||
it('should fix false value', async () => {
|
||||
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
|
||||
//@ts-ignore
|
||||
jest.spyOn(ctx._apis.localSettingsApi, 'getPreferences').mockResolvedValue({
|
||||
// @ts-expect-error - incorrect return type
|
||||
notifyWhenRunCompletes: false,
|
||||
})
|
||||
|
||||
await actions.refreshLocalSettings()
|
||||
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql([])
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).toEqual([])
|
||||
})
|
||||
|
||||
it('should fix true value', async () => {
|
||||
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
|
||||
//@ts-ignore
|
||||
jest.spyOn(ctx._apis.localSettingsApi, 'getPreferences').mockResolvedValue({
|
||||
// @ts-expect-error - incorrect return type
|
||||
notifyWhenRunCompletes: true,
|
||||
})
|
||||
|
||||
await actions.refreshLocalSettings()
|
||||
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql([...NotifyCompletionStatuses])
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).toEqual([...NotifyCompletionStatuses])
|
||||
})
|
||||
|
||||
it('should leave value alone if value is an array', async () => {
|
||||
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
|
||||
//@ts-ignore
|
||||
jest.spyOn(ctx._apis.localSettingsApi, 'getPreferences').mockResolvedValue({
|
||||
notifyWhenRunCompletes: ['errored'],
|
||||
})
|
||||
|
||||
await actions.refreshLocalSettings()
|
||||
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql(['errored'])
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).toEqual(['errored'])
|
||||
})
|
||||
|
||||
it('should pass through default value if not set ', async () => {
|
||||
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({})
|
||||
jest.spyOn(ctx._apis.localSettingsApi, 'getPreferences').mockResolvedValue({})
|
||||
|
||||
await actions.refreshLocalSettings()
|
||||
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql(['failed'])
|
||||
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).toEqual(['failed'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { NotificationActions } from '../../../src/actions/NotificationActions'
|
||||
import { CloudRunStatus, RelevantRunInfo } from '../../../src/gen/graphcache-config.gen'
|
||||
@@ -11,43 +10,43 @@ describe('NotificationActions', () => {
|
||||
let showSystemNotificationStub
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
actions = new NotificationActions(ctx)
|
||||
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
|
||||
showSystemNotificationStub = sinon.stub(ctx.actions.electron, 'showSystemNotification')
|
||||
sinon.stub(ctx.actions.cloudProject, 'fetchMetadata').resolves({
|
||||
showSystemNotificationStub = jest.spyOn(ctx.actions.electron, 'showSystemNotification')
|
||||
|
||||
jest.spyOn(ctx.actions.cloudProject, 'fetchMetadata').mockResolvedValue({
|
||||
id: 'project-local-id',
|
||||
name: 'cy-project',
|
||||
})
|
||||
})
|
||||
|
||||
context('onNotificationClick', () => {
|
||||
describe('onNotificationClick', () => {
|
||||
it('focuses the active browser window and calls debugCloudRun', async () => {
|
||||
const run = createRelevantRun(12)
|
||||
|
||||
const focusActiveBrowserWindowSpy = sinon.spy(ctx.actions.browser, 'focusActiveBrowserWindow')
|
||||
const focusActiveBrowserWindowSpy = jest.spyOn(ctx.actions.browser, 'focusActiveBrowserWindow')
|
||||
|
||||
const debugCloudRunSpy = sinon.spy(ctx.actions.project, 'debugCloudRun')
|
||||
const debugCloudRunSpy = jest.spyOn(ctx.actions.project, 'debugCloudRun')
|
||||
|
||||
await actions.onNotificationClick(run)
|
||||
|
||||
expect(focusActiveBrowserWindowSpy).to.have.been.called
|
||||
expect(debugCloudRunSpy).to.have.been.calledWith(run.runNumber)
|
||||
expect(focusActiveBrowserWindowSpy).toHaveBeenCalled()
|
||||
expect(debugCloudRunSpy).toHaveBeenCalledWith(run.runNumber)
|
||||
})
|
||||
})
|
||||
|
||||
context('sendRunStartedNotification', () => {
|
||||
describe('sendRunStartedNotification', () => {
|
||||
it('does not send notification if preference is not enabled', async () => {
|
||||
ctx.coreData.localSettings.preferences.notifyWhenRunStarts = false
|
||||
|
||||
// @ts-expect-error - number as arg
|
||||
await actions.sendRunStartedNotification(101)
|
||||
|
||||
expect(showSystemNotificationStub).not.to.have.been.called
|
||||
expect(showSystemNotificationStub).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends notification if preference is enabled', async () => {
|
||||
@@ -57,11 +56,11 @@ describe('NotificationActions', () => {
|
||||
|
||||
await actions.sendRunStartedNotification(run)
|
||||
|
||||
expect(showSystemNotificationStub).to.have.been.calledWithMatch('cy-project', `Run #${run.runNumber} started`)
|
||||
expect(showSystemNotificationStub).toHaveBeenCalledWith('cy-project', `Run #${run.runNumber} started`, expect.any(Function))
|
||||
})
|
||||
})
|
||||
|
||||
context('sendRunFailingNotification', () => {
|
||||
describe('sendRunFailingNotification', () => {
|
||||
it('does not send notification if preference is not enabled', () => {
|
||||
const run = createRelevantRun(101)
|
||||
|
||||
@@ -69,7 +68,7 @@ describe('NotificationActions', () => {
|
||||
|
||||
actions.sendRunFailingNotification(run)
|
||||
|
||||
expect(showSystemNotificationStub).not.to.have.been.called
|
||||
expect(showSystemNotificationStub).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends notification if preference is enabled', async () => {
|
||||
@@ -79,17 +78,18 @@ describe('NotificationActions', () => {
|
||||
|
||||
await actions.sendRunFailingNotification(run)
|
||||
|
||||
expect(showSystemNotificationStub).to.have.been.calledWithMatch('cy-project', `Run #${run.runNumber} has started failing`)
|
||||
expect(showSystemNotificationStub).toHaveBeenCalledWith('cy-project', `Run #${run.runNumber} has started failing`, expect.any(Function))
|
||||
})
|
||||
})
|
||||
|
||||
context('sendRunCompletedNotification', () => {
|
||||
describe('sendRunCompletedNotification', () => {
|
||||
it('does not send notification if status is not included in preference', () => {
|
||||
ctx.coreData.localSettings.preferences.notifyWhenRunCompletes = ['cancelled', 'errored', 'failed']
|
||||
|
||||
// @ts-expect-error - number as arg
|
||||
actions.sendRunCompletedNotification(101, 'passed')
|
||||
|
||||
expect(showSystemNotificationStub).not.to.have.been.called
|
||||
expect(showSystemNotificationStub).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends notification if preference is enabled', async () => {
|
||||
@@ -99,11 +99,11 @@ describe('NotificationActions', () => {
|
||||
|
||||
await actions.sendRunCompletedNotification(run, 'failed')
|
||||
|
||||
expect(showSystemNotificationStub).to.have.been.calledWithMatch('cy-project', `Run #${run.runNumber} failed`)
|
||||
expect(showSystemNotificationStub).toHaveBeenCalledWith('cy-project', `Run #${run.runNumber} failed`, expect.any(Function))
|
||||
})
|
||||
})
|
||||
|
||||
context('maybeSendRunNotification', () => {
|
||||
describe('maybeSendRunNotification', () => {
|
||||
beforeEach(() => {
|
||||
// For these tests, enable all notification preferences to verify that desktopNotificationsEnabled works as expected
|
||||
ctx.coreData.localSettings.preferences.notifyWhenRunStarts = true
|
||||
@@ -119,11 +119,11 @@ describe('NotificationActions', () => {
|
||||
{ ...createRelevantRun(141), status: 'PASSED', sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 1 },
|
||||
)
|
||||
|
||||
expect(showSystemNotificationStub).not.to.have.been.called
|
||||
expect(showSystemNotificationStub).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends run started notification if there is a new run with RUNNING status that is different from the previously cached run', () => {
|
||||
const sendRunStartedNotificationStub = sinon.stub(actions, 'sendRunStartedNotification')
|
||||
const sendRunStartedNotificationStub = jest.spyOn(actions, 'sendRunStartedNotification')
|
||||
|
||||
ctx.coreData.localSettings.preferences.desktopNotificationsEnabled = true
|
||||
const run1 = { ...createRelevantRun(141), status: 'RUNNING', sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 1 } as const
|
||||
@@ -133,11 +133,11 @@ describe('NotificationActions', () => {
|
||||
run1, run2,
|
||||
)
|
||||
|
||||
expect(sendRunStartedNotificationStub).to.have.been.calledWith(run2)
|
||||
expect(sendRunStartedNotificationStub).toHaveBeenCalledWith(run2)
|
||||
})
|
||||
|
||||
it('sends run started failing notification if status is RUNNING and totalFailed was 0 but is now greater than 0', () => {
|
||||
const sendRunFailingNotificationStub = sinon.stub(actions, 'sendRunFailingNotification')
|
||||
const sendRunFailingNotificationStub = jest.spyOn(actions, 'sendRunFailingNotification')
|
||||
const run1 = { ...createRelevantRun(141), status: 'RUNNING', sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 0 } as const
|
||||
const run2 = { ...createRelevantRun(141), status: 'RUNNING', sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 3 } as const
|
||||
|
||||
@@ -145,21 +145,22 @@ describe('NotificationActions', () => {
|
||||
|
||||
actions.maybeSendRunNotification(run1, run2)
|
||||
|
||||
expect(sendRunFailingNotificationStub).to.have.been.calledWith(run2)
|
||||
expect(sendRunFailingNotificationStub).toHaveBeenCalledWith(run2)
|
||||
})
|
||||
|
||||
context('run completed', () => {
|
||||
describe('run completed', () => {
|
||||
['PASSED', 'FAILED', 'CANCELLED', 'ERRORED'].forEach((status) => {
|
||||
it(`sends run completed notification if new run has completed - ${status}`, () => {
|
||||
const run1: RelevantRunInfo = { ...createRelevantRun(141), status: 'RUNNING', sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 0, branch: 'branch123', organizationId: '1' }
|
||||
const run2: RelevantRunInfo = { ...createRelevantRun(142), status: status as CloudRunStatus, sha: 'f909139209c8351cfaa737c7fd122ad4f17fdaa5', totalFailed: 0, branch: 'branch123', organizationId: '1' }
|
||||
const sendRunCompletedNotificationStub = sinon.stub(actions, 'sendRunCompletedNotification')
|
||||
const sendRunCompletedNotificationStub = jest.spyOn(actions, 'sendRunCompletedNotification')
|
||||
|
||||
ctx.coreData.localSettings.preferences.desktopNotificationsEnabled = true
|
||||
|
||||
actions.maybeSendRunNotification(run1, run2)
|
||||
|
||||
expect(sendRunCompletedNotificationStub).to.have.been.calledWith(run2, status.toLocaleLowerCase())
|
||||
// @ts-expect-error
|
||||
expect(sendRunCompletedNotificationStub).toHaveBeenCalledWith(run2, status.toLocaleLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import { ProjectActions } from '../../../src/actions/ProjectActions'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { SpecWithRelativeRoot } from '@packages/types'
|
||||
import { SpecWithRelativeRoot, TestingType } from '@packages/types'
|
||||
|
||||
describe('ProjectActions', () => {
|
||||
let ctx: DataContext
|
||||
let actions: ProjectActions
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
actions = new ProjectActions(ctx)
|
||||
})
|
||||
|
||||
context('hasNonExampleSpec', () => {
|
||||
context('testing type not set yet', () => {
|
||||
describe('hasNonExampleSpec', () => {
|
||||
describe('testing type not set yet', () => {
|
||||
it('should indicate there are NO non example spec files if empty', async () => {
|
||||
expect(ctx.project.specs).to.have.length(0)
|
||||
expect(ctx.project.specs).toHaveLength(0)
|
||||
|
||||
const hasNonExampleSpec = await actions.hasNonExampleSpec()
|
||||
|
||||
expect(hasNonExampleSpec).to.be.false
|
||||
expect(hasNonExampleSpec).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
context('testing type is e2e', () => {
|
||||
describe('testing type is e2e', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
})
|
||||
@@ -43,11 +40,11 @@ describe('ProjectActions', () => {
|
||||
|
||||
ctx.project.setSpecs(mockSpecs)
|
||||
|
||||
expect(ctx.project.specs).to.have.length(1)
|
||||
expect(ctx.project.specs).toHaveLength(1)
|
||||
|
||||
const hasNonExampleSpec = await actions.hasNonExampleSpec()
|
||||
|
||||
expect(hasNonExampleSpec).to.be.false
|
||||
expect(hasNonExampleSpec).toBe(false)
|
||||
})
|
||||
|
||||
it('should indicate there are non example spec files with examples and non example', async () => {
|
||||
@@ -64,26 +61,26 @@ describe('ProjectActions', () => {
|
||||
|
||||
ctx.project.setSpecs(mockSpecs)
|
||||
|
||||
expect(ctx.project.specs).to.have.length(2)
|
||||
expect(ctx.project.specs).toHaveLength(2)
|
||||
|
||||
const hasNonExampleSpec = await actions.hasNonExampleSpec()
|
||||
|
||||
expect(hasNonExampleSpec).to.be.true
|
||||
expect(hasNonExampleSpec).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
context('testing type is component', () => {
|
||||
describe('testing type is component', () => {
|
||||
it('should indicate there are NO non example spec files with no specs', async () => {
|
||||
const mockSpecs = [] as SpecWithRelativeRoot[]
|
||||
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
ctx.project.setSpecs(mockSpecs)
|
||||
|
||||
expect(ctx.project.specs).to.have.length(0)
|
||||
expect(ctx.project.specs).toHaveLength(0)
|
||||
|
||||
const hasNonExampleSpec = await actions.hasNonExampleSpec()
|
||||
|
||||
expect(hasNonExampleSpec).to.be.false
|
||||
expect(hasNonExampleSpec).toBe(false)
|
||||
})
|
||||
|
||||
// there are no examples for component tests, so any component spec file should be a non-example
|
||||
@@ -95,28 +92,28 @@ describe('ProjectActions', () => {
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
ctx.project.setSpecs(mockSpecs)
|
||||
|
||||
expect(ctx.project.specs).to.have.length(1)
|
||||
expect(ctx.project.specs).toHaveLength(1)
|
||||
|
||||
const hasNonExampleSpec = await actions.hasNonExampleSpec()
|
||||
|
||||
expect(hasNonExampleSpec).to.be.true
|
||||
expect(hasNonExampleSpec).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('runSpec', () => {
|
||||
context('no project', () => {
|
||||
describe('no project', () => {
|
||||
it('should fail with `NO_PROJECT`', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/cypress/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
code: 'NO_PROJECT',
|
||||
detailMessage: sinon.match.string,
|
||||
detailMessage: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('empty specPath', () => {
|
||||
describe('empty specPath', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
})
|
||||
@@ -124,125 +121,166 @@ describe('ProjectActions', () => {
|
||||
it('should fail with `NO_SPEC_PATH`', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
code: 'NO_SPEC_PATH',
|
||||
detailMessage: sinon.match.string,
|
||||
detailMessage: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('no specPattern match', () => {
|
||||
describe('no specPattern match', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
sinon.stub(ctx.project, 'matchesSpecPattern').resolves(false)
|
||||
jest.spyOn(ctx.project, 'matchesSpecPattern').mockResolvedValue(false)
|
||||
})
|
||||
|
||||
it('should fail with `NO_SPEC_PATTERN_MATCH`', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
code: 'NO_SPEC_PATTERN_MATCH',
|
||||
detailMessage: sinon.match.string,
|
||||
detailMessage: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('spec file not found', () => {
|
||||
describe('spec file not found', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true)
|
||||
sinon.stub(ctx.fs, 'existsSync').returns(false)
|
||||
jest.spyOn(ctx.project, 'matchesSpecPattern').mockImplementation((specFile: string) => {
|
||||
if (specFile.includes('e2e')) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
jest.spyOn(ctx.fs, 'existsSync').mockReturnValue(false)
|
||||
})
|
||||
|
||||
it('should fail with `SPEC_NOT_FOUND`', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
code: 'SPEC_NOT_FOUND',
|
||||
detailMessage: sinon.match.string,
|
||||
detailMessage: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('matched testing type not configured', () => {
|
||||
describe('matched testing type not configured', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentTestingType = null
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true)
|
||||
sinon.stub(ctx.fs, 'existsSync').returns(true)
|
||||
sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(false)
|
||||
jest.spyOn(ctx.project, 'matchesSpecPattern').mockImplementation((specFile: string) => {
|
||||
if (specFile.includes('e2e')) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
jest.spyOn(ctx.fs, 'existsSync').mockReturnValue(true)
|
||||
jest.spyOn(ctx.lifecycleManager, 'isTestingTypeConfigured').mockImplementation((testingType: TestingType) => {
|
||||
if (testingType === 'e2e') {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
it('should fail with `TESTING_TYPE_NOT_CONFIGURED`', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
code: 'TESTING_TYPE_NOT_CONFIGURED',
|
||||
detailMessage: sinon.match.string,
|
||||
detailMessage: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('spec can be executed', () => {
|
||||
describe('spec can be executed', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentProject = '/cy-project'
|
||||
sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true)
|
||||
sinon.stub(ctx.fs, 'existsSync').returns(true)
|
||||
sinon.stub(ctx.project, 'getCurrentSpecByAbsolute').returns({ id: 'xyz' } as any)
|
||||
sinon.stub(ctx.lifecycleManager, 'setInitialActiveBrowser')
|
||||
jest.spyOn(ctx.project, 'matchesSpecPattern').mockImplementation((specFile: string) => {
|
||||
if (specFile.includes('e2e')) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
})
|
||||
|
||||
jest.spyOn(ctx.fs, 'existsSync').mockReturnValue(true)
|
||||
jest.spyOn(ctx.project, 'getCurrentSpecByAbsolute').mockReturnValue({ id: 'xyz' } as any)
|
||||
jest.spyOn(ctx.lifecycleManager, 'setInitialActiveBrowser')
|
||||
ctx.coreData.activeBrowser = { id: 'abc' } as any
|
||||
sinon.stub(ctx.lifecycleManager, 'setCurrentTestingType')
|
||||
sinon.stub(ctx.actions.project, 'switchTestingTypesAndRelaunch')
|
||||
jest.spyOn(ctx.lifecycleManager, 'setCurrentTestingType').mockReturnValue(undefined)
|
||||
jest.spyOn(ctx.lifecycleManager, 'setAndLoadCurrentTestingType').mockReturnValue(undefined)
|
||||
jest.spyOn(ctx.actions.project, 'switchTestingTypesAndRelaunch')
|
||||
jest.spyOn(ctx.actions.browser, 'closeBrowser').mockReturnValue(undefined)
|
||||
ctx.coreData.app.browserStatus = 'open'
|
||||
sinon.stub(ctx.emitter, 'subscribeTo').returns({
|
||||
jest.spyOn(ctx.emitter, 'subscribeTo').mockReturnValue({
|
||||
next: () => {},
|
||||
return: () => {},
|
||||
} as any)
|
||||
})
|
||||
|
||||
context('no current testing type', () => {
|
||||
describe('no current testing type', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentTestingType = null
|
||||
sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(true)
|
||||
jest.spyOn(ctx.lifecycleManager, 'isTestingTypeConfigured').mockImplementation((testingType: TestingType) => {
|
||||
if (testingType === 'e2e') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
it('should succeed', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
testingType: 'e2e',
|
||||
browser: sinon.match.object,
|
||||
spec: sinon.match.object,
|
||||
browser: expect.any(Object),
|
||||
spec: expect.any(Object),
|
||||
})
|
||||
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).to.have.been.calledWith('e2e')
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).to.have.been.calledWith('e2e')
|
||||
expect(ctx._apis.projectApi.runSpec).to.have.been.called
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).toHaveBeenCalledWith('e2e')
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).toHaveBeenCalledWith('e2e')
|
||||
expect(ctx._apis.projectApi.runSpec).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
context('testing type needs to change', () => {
|
||||
describe('testing type needs to change', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(true)
|
||||
jest.spyOn(ctx.lifecycleManager, 'isTestingTypeConfigured').mockImplementation((testingType: TestingType) => {
|
||||
if (testingType === 'e2e') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
it('should succeed', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
testingType: 'e2e',
|
||||
browser: sinon.match.object,
|
||||
spec: sinon.match.object,
|
||||
browser: expect.any(Object),
|
||||
spec: expect.any(Object),
|
||||
})
|
||||
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).to.have.been.calledWith('e2e')
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).to.have.been.calledWith('e2e')
|
||||
expect(ctx._apis.projectApi.runSpec).to.have.been.called
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).toHaveBeenCalledWith('e2e')
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).toHaveBeenCalledWith('e2e')
|
||||
expect(ctx._apis.projectApi.runSpec).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
context('testing type does not need to change', () => {
|
||||
describe('testing type does not need to change', () => {
|
||||
beforeEach(() => {
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
})
|
||||
@@ -250,16 +288,16 @@ describe('ProjectActions', () => {
|
||||
it('should succeed', async () => {
|
||||
const result = await ctx.actions.project.runSpec({ specPath: '/Users/blah/Desktop/application/e2e/abc.cy.ts' })
|
||||
|
||||
sinon.assert.match(result, {
|
||||
expect(result).toMatchObject({
|
||||
testingType: 'e2e',
|
||||
browser: sinon.match.object,
|
||||
spec: sinon.match.object,
|
||||
browser: expect.any(Object),
|
||||
spec: expect.any(Object),
|
||||
})
|
||||
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).not.to.have.been.called
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).not.to.have.been.called
|
||||
expect(ctx.lifecycleManager.setCurrentTestingType).not.toHaveBeenCalled()
|
||||
expect(ctx.actions.project.switchTestingTypesAndRelaunch).not.toHaveBeenCalled()
|
||||
|
||||
expect(ctx._apis.projectApi.runSpec).to.have.been.called
|
||||
expect(ctx._apis.projectApi.runSpec).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -267,14 +305,14 @@ describe('ProjectActions', () => {
|
||||
|
||||
describe('debugCloudRun', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(ctx.relevantRuns, 'moveToRun')
|
||||
jest.spyOn(ctx.relevantRuns, 'moveToRun')
|
||||
})
|
||||
|
||||
it('should call moveToRun and routeToDebug', async () => {
|
||||
await ctx.actions.project.debugCloudRun(123)
|
||||
|
||||
expect(ctx.relevantRuns.moveToRun).to.have.been.calledWith(123)
|
||||
expect(ctx._apis.projectApi.routeToDebug).to.have.been.called
|
||||
expect(ctx.relevantRuns.moveToRun).toHaveBeenCalledWith(123, [])
|
||||
expect(ctx._apis.projectApi.routeToDebug).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, beforeEach } from '@jest/globals'
|
||||
import { parse } from '@babel/parser'
|
||||
import { expect } from 'chai'
|
||||
import dedent from 'dedent'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
@@ -73,7 +73,7 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const skippedExpected = {
|
||||
...expected,
|
||||
@@ -81,7 +81,7 @@ describe('code-generator', () => {
|
||||
}
|
||||
const skippedCodeGenResults = await codeGenerator(action, codeGenArgs)
|
||||
|
||||
expect(skippedCodeGenResults).deep.eq(skippedExpected)
|
||||
expect(skippedCodeGenResults).toEqual(skippedExpected)
|
||||
|
||||
const getMTimes = (files: Array<CodeGenResult>) => {
|
||||
return Promise.all(
|
||||
@@ -91,7 +91,7 @@ describe('code-generator', () => {
|
||||
const mTimesBefore = await getMTimes(codeGenResults.files)
|
||||
let mTimesAfter = await getMTimes(skippedCodeGenResults.files)
|
||||
|
||||
expect(mTimesBefore).deep.eq(mTimesAfter)
|
||||
expect(mTimesBefore).toEqual(mTimesAfter)
|
||||
|
||||
const overwriteAction: Action = { ...action, overwrite: true }
|
||||
const overwriteExpected: CodeGenResults = {
|
||||
@@ -103,11 +103,11 @@ describe('code-generator', () => {
|
||||
codeGenArgs,
|
||||
)
|
||||
|
||||
expect(overwriteCodeGenResults).deep.eq(overwriteExpected)
|
||||
expect(overwriteCodeGenResults).toEqual(overwriteExpected)
|
||||
|
||||
mTimesAfter = await getMTimes(overwriteCodeGenResults.files)
|
||||
|
||||
expect(mTimesBefore).not.deep.eq(mTimesAfter)
|
||||
expect(mTimesBefore).not.toEqual(mTimesAfter)
|
||||
mTimesAfter.forEach((time, i) => expect(time > mTimesBefore[i]))
|
||||
})
|
||||
|
||||
@@ -139,13 +139,13 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const fileContent = (await fs.readFile(fileAbsolute)).toString()
|
||||
|
||||
expect(fileContent).eq(expected.files[0].content)
|
||||
expect(fileContent).toEqual(expected.files[0].content)
|
||||
|
||||
expect(() => babelParse(fileContent)).not.throw()
|
||||
expect(() => babelParse(fileContent)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should generate from empty component template', async () => {
|
||||
@@ -178,13 +178,13 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const fileContent = (await fs.readFile(fileAbsolute)).toString()
|
||||
|
||||
expect(fileContent).eq(expected.files[0].content)
|
||||
expect(fileContent).toEqual(expected.files[0].content)
|
||||
|
||||
expect(() => babelParse(fileContent)).not.throw()
|
||||
expect(() => babelParse(fileContent)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should generate from Vue component template', async () => {
|
||||
@@ -221,13 +221,13 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const fileContent = (await fs.readFile(fileAbsolute)).toString()
|
||||
|
||||
expect(fileContent).eq(expected.files[0].content)
|
||||
expect(fileContent).toEqual(expected.files[0].content)
|
||||
|
||||
expect(() => babelParse(fileContent)).not.throw()
|
||||
expect(() => babelParse(fileContent)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should generate from React component template', async () => {
|
||||
@@ -268,13 +268,13 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const fileContent = (await fs.readFile(fileAbsolute)).toString()
|
||||
|
||||
expect(fileContent).eq(expected.files[0].content)
|
||||
expect(fileContent).toEqual(expected.files[0].content)
|
||||
|
||||
expect(() => babelParse(fileContent)).not.throw()
|
||||
expect(() => babelParse(fileContent)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should generate from React component template with default export', async () => {
|
||||
@@ -315,13 +315,13 @@ describe('code-generator', () => {
|
||||
failed: [],
|
||||
}
|
||||
|
||||
expect(codeGenResults).deep.eq(expected)
|
||||
expect(codeGenResults).toEqual(expected)
|
||||
|
||||
const fileContent = (await fs.readFile(fileAbsolute)).toString()
|
||||
|
||||
expect(fileContent).eq(expected.files[0].content)
|
||||
expect(fileContent).toEqual(expected.files[0].content)
|
||||
|
||||
expect(() => babelParse(fileContent)).not.throw()
|
||||
expect(() => babelParse(fileContent)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should generate from e2eExamples', async () => {
|
||||
@@ -335,13 +335,13 @@ describe('code-generator', () => {
|
||||
// https://github.com/cypress-io/cypress-example-kitchensink
|
||||
const codeGenResult = await codeGenerator(action, {})
|
||||
|
||||
expect(codeGenResult.files.length).gt(0)
|
||||
expect(codeGenResult.files.length).toBeGreaterThan(0)
|
||||
for (const res of codeGenResult.files) {
|
||||
expect(async () => await fs.access(res.file, fs.constants.F_OK)).not.throw()
|
||||
expect(fs.access(res.file, fs.constants.F_OK)).resolves.not.toThrow()
|
||||
const shouldParse = ['js', 'ts'].some((ext) => res.file.endsWith(ext))
|
||||
|
||||
if (shouldParse) {
|
||||
expect(() => babelParse(res.content)).not.throw()
|
||||
expect(() => babelParse(res.content)).not.toThrow()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -357,6 +357,7 @@ describe('code-generator', () => {
|
||||
currentProject: 'path/to/myProject',
|
||||
codeGenPath: path.join(__dirname, 'files', 'react', 'Button.jsx'),
|
||||
codeGenType: 'component',
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[1],
|
||||
isDefaultSpecPattern: true,
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
@@ -366,16 +367,17 @@ describe('code-generator', () => {
|
||||
|
||||
const codeGenResult = await codeGenerator(action, codeGenOptions)
|
||||
|
||||
expect(() => babelParse(codeGenResult.files[0].content)).not.throw()
|
||||
expect(() => babelParse(codeGenResult.files[0].content)).not.toThrow()
|
||||
})
|
||||
|
||||
context('nonExampleSpecfile', () => {
|
||||
describe('nonExampleSpecfile', () => {
|
||||
it('should return true after adding new spec file', async () => {
|
||||
const target = path.join(tmpPath, 'spec-check')
|
||||
|
||||
const checkBeforeScaffolding = await hasNonExampleSpec(templates.e2eExamples, [])
|
||||
|
||||
expect(checkBeforeScaffolding, 'expected having no spec files to show no non-example specs').to.be.false
|
||||
// expected having no spec files to show no non-example specs
|
||||
expect(checkBeforeScaffolding).toBe(false)
|
||||
|
||||
const scaffoldExamplesAction: Action = {
|
||||
templateDir: templates.e2eExamples,
|
||||
@@ -390,13 +392,15 @@ describe('code-generator', () => {
|
||||
|
||||
const scaffoldResults = await codeGenerator(scaffoldExamplesAction, {})
|
||||
|
||||
expect(scaffoldResults.files.length, 'expected scaffold files to be created').gt(0)
|
||||
// expected scaffold files to be created
|
||||
expect(scaffoldResults.files.length).toBeGreaterThan(0)
|
||||
|
||||
const specs = addTemplatesAsSpecs(scaffoldResults)
|
||||
|
||||
const checkAfterScaffolding = await hasNonExampleSpec(templates.e2eExamples, specs)
|
||||
|
||||
expect(checkAfterScaffolding, 'expected only having template files to show no non-example specs').to.be.false
|
||||
// expected only having template files to show no non-example specs
|
||||
expect(checkAfterScaffolding).toBe(false)
|
||||
|
||||
const fileName = 'my-test-file.js'
|
||||
const scaffoldTemplateAction: Action = {
|
||||
@@ -411,30 +415,31 @@ describe('code-generator', () => {
|
||||
|
||||
const checkAfterTemplate = await hasNonExampleSpec(templates.e2eExamples, specsWithGenerated)
|
||||
|
||||
expect(checkAfterTemplate, 'expected check after adding a new spec to indicate there are now non-example specs').to.be.true
|
||||
// expected check after adding a new spec to indicate there are now non-example specs
|
||||
expect(checkAfterTemplate).toBe(true)
|
||||
})
|
||||
|
||||
it('should error if template dir does not exist', async () => {
|
||||
it('should error if template dir does not exist', () => {
|
||||
const singleSpec = ['sample.spec.ts']
|
||||
|
||||
expect(async () => await hasNonExampleSpec('', singleSpec)).to.throw
|
||||
expect(hasNonExampleSpec('', singleSpec)).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
context('hasNonExampleSpec', async () => {
|
||||
describe('hasNonExampleSpec', () => {
|
||||
it('should error if template dir does not exist', () => {
|
||||
expect(async () => await getExampleSpecPaths('')).to.throw
|
||||
expect(getExampleSpecPaths('')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should return relative paths to example specs', async () => {
|
||||
const results = await getExampleSpecPaths(templates.e2eExamples)
|
||||
|
||||
expect(results.length).to.be.greaterThan(0)
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
|
||||
results.forEach((specPath) => {
|
||||
const fullPathToSpec = path.join(templates.e2eExamples, specPath)
|
||||
|
||||
expect(fs.pathExistsSync(fullPathToSpec), `expected to find file at ${fullPathToSpec}`).to.be.true
|
||||
expect(fs.pathExistsSync(fullPathToSpec)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import { defaultSpecPattern } from '@packages/config'
|
||||
import { CT_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { expect } from 'chai'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import { DataContext } from '../../../src'
|
||||
import { SpecOptions, expectedSpecExtensions } from '../../../src/codegen/spec-options'
|
||||
import { createTestDataContext } from '../helper'
|
||||
@@ -25,10 +24,10 @@ describe('spec-options', () => {
|
||||
|
||||
describe('getCodeGenOptions', () => {
|
||||
it('uses expected set of spec extensions', () => {
|
||||
expect(expectedSpecExtensions).to.deep.eq(['.cy', '.spec', '.test', '-spec', '-test', '_spec'])
|
||||
expect(expectedSpecExtensions).toEqual(['.cy', '.spec', '.test', '-spec', '-test', '_spec'])
|
||||
})
|
||||
|
||||
context('unique file names', () => {
|
||||
describe('unique file names', () => {
|
||||
for (const specExtension of expectedSpecExtensions) {
|
||||
it(`generates options for names with extension ${specExtension}`, async () => {
|
||||
const testSpecOptions = new SpecOptions({
|
||||
@@ -41,8 +40,8 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName${specExtension}.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName${specExtension}.js`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,8 +56,8 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName.js`)
|
||||
})
|
||||
|
||||
it('generates options for file name with multiple extensions', async () => {
|
||||
@@ -72,61 +71,60 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName.foo.bar.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName.foo.bar.js`)
|
||||
})
|
||||
})
|
||||
|
||||
context('create from component', () => {
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
context('Vue', () => {
|
||||
describe('create from component', () => {
|
||||
describe('Vue', () => {
|
||||
it('generates options for generating a Vue component spec', async () => {
|
||||
const testSpecOptions = new SpecOptions({
|
||||
currentProject: 'path/to/myProject',
|
||||
codeGenPath: `${tmpPath}/MyComponent.vue`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[1],
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
})
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('vueComponent')
|
||||
expect(result.fileName).to.eq('MyComponent.cy.js')
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('vueComponent')
|
||||
expect(result.fileName).toEqual('MyComponent.cy.js')
|
||||
})
|
||||
|
||||
it('creates copy file if spec already exists', async () => {
|
||||
sinon.stub(fs, 'access').onFirstCall().resolves().onSecondCall().rejects()
|
||||
jest.spyOn(fs, 'access').mockImplementationOnce(() => Promise.resolve()).mockImplementationOnce(() => Promise.reject())
|
||||
|
||||
const testSpecOptions = new SpecOptions({
|
||||
currentProject: 'path/to/myProject',
|
||||
codeGenPath: `${tmpPath}/MyComponent.vue`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[1],
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
})
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('vueComponent')
|
||||
expect(result.fileName).to.eq('MyComponent-copy-1.cy.js')
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('vueComponent')
|
||||
expect(result.fileName).toEqual('MyComponent-copy-1.cy.js')
|
||||
})
|
||||
})
|
||||
|
||||
context('React', () => {
|
||||
describe('React', () => {
|
||||
it('generates options for generating a React component spec', async () => {
|
||||
const testSpecOptions = new SpecOptions({
|
||||
currentProject: 'path/to/myProject',
|
||||
codeGenPath: `${tmpPath}/Counter.tsx`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[2],
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
componentName: 'Counter',
|
||||
@@ -135,9 +133,9 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('reactComponent')
|
||||
expect(result.fileName).to.eq('Counter.cy.tsx')
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('reactComponent')
|
||||
expect(result.fileName).toEqual('Counter.cy.tsx')
|
||||
})
|
||||
|
||||
it('creates a spec file with file and component names combined if they are different', async () => {
|
||||
@@ -146,6 +144,7 @@ describe('spec-options', () => {
|
||||
codeGenPath: `${tmpPath}/Counter.tsx`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[2],
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
componentName: 'View',
|
||||
@@ -153,19 +152,22 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('reactComponent')
|
||||
expect(result.fileName).to.eq('CounterView.cy.tsx')
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('reactComponent')
|
||||
expect(result.fileName).toEqual('CounterView.cy.tsx')
|
||||
})
|
||||
|
||||
it('creates copy file if spec already exists', async () => {
|
||||
sinon.stub(fs, 'access').onFirstCall().resolves().onSecondCall().rejects()
|
||||
jest.spyOn(fs, 'access')
|
||||
.mockImplementationOnce(() => Promise.resolve())
|
||||
.mockImplementationOnce(() => Promise.reject())
|
||||
|
||||
const testSpecOptions = new SpecOptions({
|
||||
currentProject: 'path/to/myProject',
|
||||
codeGenPath: `${tmpPath}/Counter.tsx`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[2],
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
componentName: 'View',
|
||||
@@ -173,13 +175,13 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('reactComponent')
|
||||
expect(result.fileName).to.eq('CounterView-copy-1.cy.tsx')
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('reactComponent')
|
||||
expect(result.fileName).toEqual('CounterView-copy-1.cy.tsx')
|
||||
})
|
||||
})
|
||||
|
||||
context('custom spec pattern', () => {
|
||||
describe('custom spec pattern', () => {
|
||||
[{ testName: 'src/specs-folder/*.cy.{js,jsx}', componentPath: 'ComponentName.vue', specs: [], pattern: 'src/specs-folder/*.cy.{js,jsx}', expectedPath: 'src/specs-folder/ComponentName.cy.js' },
|
||||
{ testName: 'src/**/*.{spec,cy}.{js,jsx,ts,tsx}', componentPath: 'MyComponent.vue', specs: [], pattern: 'src/**/*.{spec,cy}.{js,jsx,ts,tsx}', expectedPath: 'src/MyComponent.spec.ts', isTypescriptComponent: true },
|
||||
{ testName: '**/*.test.js', componentPath: 'src/Foo.vue', specs: [], pattern: '**/*.test.js', expectedPath: 'cypress/Foo.test.js' },
|
||||
@@ -195,13 +197,18 @@ describe('spec-options', () => {
|
||||
it(testName, async () => {
|
||||
// This stub simulates the spec file already existing the first time we try, which should cause a copy to be created
|
||||
if (makeCopy) {
|
||||
sinon.stub(fs, 'access').onFirstCall().resolves().onSecondCall().rejects()
|
||||
jest.spyOn(fs, 'access')
|
||||
.mockImplementationOnce(() => Promise.resolve())
|
||||
.mockImplementationOnce(() => Promise.reject())
|
||||
}
|
||||
|
||||
// This stub simulates that the component we are generating a spec from is using Typescript.
|
||||
if (isTypescriptComponent) {
|
||||
// @ts-ignore
|
||||
sinon.stub(fs, 'readFile').resolves('lang="ts"')
|
||||
jest.spyOn(fs, 'readFile').mockResolvedValue('lang="ts"')
|
||||
} else {
|
||||
// @ts-ignore
|
||||
jest.spyOn(fs, 'readFile').mockResolvedValue('lang="js"')
|
||||
}
|
||||
|
||||
const currentProject = 'path/to/myProject'
|
||||
@@ -212,6 +219,7 @@ describe('spec-options', () => {
|
||||
codeGenPath: `${tmpPath}/${componentPath}`,
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: false,
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[1],
|
||||
specPattern,
|
||||
specs,
|
||||
@@ -219,15 +227,15 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.templateKey).to.eq('vueComponent')
|
||||
expect(`${result.overrideCodeGenDir}/${result.fileName}`).to.eq(expectedPath)
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.templateKey).toEqual('vueComponent')
|
||||
expect(`${result.overrideCodeGenDir}/${result.fileName}`).toEqual(expectedPath)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('duplicate files names', () => {
|
||||
describe('duplicate files names', () => {
|
||||
for (const specExtension of expectedSpecExtensions) {
|
||||
it(`generates options for file name with extension ${specExtension}`, async () => {
|
||||
const testSpecOptions = new SpecOptions({
|
||||
@@ -242,16 +250,16 @@ describe('spec-options', () => {
|
||||
|
||||
let result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName-copy-1${specExtension}.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName-copy-1${specExtension}.js`)
|
||||
|
||||
// Add copy to file system and generate options again, index should increment
|
||||
await fs.outputFile(`${tmpPath}/TestName-copy-1${specExtension}.js`, '// foo')
|
||||
|
||||
result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName-copy-2${specExtension}.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName-copy-2${specExtension}.js`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -268,16 +276,16 @@ describe('spec-options', () => {
|
||||
|
||||
let result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName-copy-1.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName-copy-1.js`)
|
||||
|
||||
// Add copy to file system and generate options again, index should increment
|
||||
await fs.outputFile(`${tmpPath}/TestName-copy-1.js`, '// foo')
|
||||
|
||||
result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName-copy-2.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName-copy-2.js`)
|
||||
})
|
||||
|
||||
it('generates options for file name with multiple extensions', async () => {
|
||||
@@ -293,19 +301,19 @@ describe('spec-options', () => {
|
||||
|
||||
let result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName.foo.bar-copy-1.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName.foo.bar-copy-1.js`)
|
||||
|
||||
// Add copy to file system and generate options again, index should increment
|
||||
await fs.outputFile(`${tmpPath}/TestName.foo.bar-copy-1.js`, '// foo')
|
||||
|
||||
result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('e2e')
|
||||
expect(result.fileName).to.eq(`TestName.foo.bar-copy-2.js`)
|
||||
expect(result.codeGenType).toEqual('e2e')
|
||||
expect(result.fileName).toEqual(`TestName.foo.bar-copy-2.js`)
|
||||
})
|
||||
|
||||
context('file name contains special characters', async () => {
|
||||
describe('file name contains special characters', () => {
|
||||
[
|
||||
{ condition: 'braces', fileName: '[...MyComponent].vue', expectedFileName: '[...MyComponent].cy.js', expectedComponentName: 'MyComponent' },
|
||||
{ condition: 'hyphens', fileName: 'my-component.vue', expectedFileName: 'my-component.cy.js', expectedComponentName: 'MyComponent' },
|
||||
@@ -322,6 +330,7 @@ describe('spec-options', () => {
|
||||
codeGenType: 'component',
|
||||
isDefaultSpecPattern: true,
|
||||
specPattern: [defaultSpecPattern.component],
|
||||
// @ts-expect-error
|
||||
framework: CT_FRAMEWORKS[1],
|
||||
})
|
||||
|
||||
@@ -329,9 +338,9 @@ describe('spec-options', () => {
|
||||
|
||||
const result = await testSpecOptions.getCodeGenOptions()
|
||||
|
||||
expect(result.codeGenType).to.eq('component')
|
||||
expect(result.fileName).to.eq(expectedFileName)
|
||||
expect(result['componentName']).to.eq(expectedComponentName)
|
||||
expect(result.codeGenType).toEqual('component')
|
||||
expect(result.fileName).toEqual(expectedFileName)
|
||||
expect(result['componentName']).toEqual(expectedComponentName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import { insertValueInJSString } from '../../src/util/config-file-updater'
|
||||
|
||||
@@ -13,7 +13,7 @@ const errors = {
|
||||
}
|
||||
|
||||
describe('lib/util/config-file-updater', () => {
|
||||
context('with js files', () => {
|
||||
describe('with js files', () => {
|
||||
describe('#insertValueInJSString', () => {
|
||||
describe('es6 vs es5', () => {
|
||||
it('finds the object literal and adds the values to it es6', async () => {
|
||||
@@ -33,7 +33,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('finds the object literal and adds the values to it es5', async () => {
|
||||
@@ -53,7 +53,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('finds the object literal by string literal and updates the values (es5)', async () => {
|
||||
@@ -67,7 +67,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234' }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('works with and without the quotes around keys', async () => {
|
||||
@@ -87,7 +87,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('skips defineConfig even if it renamed in an import (es6)', async () => {
|
||||
@@ -133,7 +133,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('skips defineConfig even if it renamed in a require (es5)', async () => {
|
||||
@@ -155,7 +155,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('accepts inline comments', async () => {
|
||||
@@ -193,7 +193,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates a value even when this value is explicitely undefined', async () => {
|
||||
@@ -212,7 +212,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates values and inserts config', async () => {
|
||||
@@ -243,7 +243,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000, bar: 3000, projectId: 'id1234' }, errors)
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -266,7 +266,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
}
|
||||
`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('inserts nested values into existing keys', async () => {
|
||||
@@ -291,7 +291,7 @@ describe('lib/util/config-file-updater', () => {
|
||||
}
|
||||
`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates nested values', async () => {
|
||||
@@ -315,27 +315,27 @@ describe('lib/util/config-file-updater', () => {
|
||||
}
|
||||
}`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
expect(output).toEqual(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
describe('failures', () => {
|
||||
it('fails if not an object literal', () => {
|
||||
it('fails if not an object literal', async () => {
|
||||
const src = [
|
||||
'const foo = {}',
|
||||
'export default foo',
|
||||
].join('\n')
|
||||
|
||||
return insertValueInJSString(src, { bar: 10 }, errors)
|
||||
.then(() => {
|
||||
try {
|
||||
await insertValueInJSString(src, { bar: 10 }, errors)
|
||||
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err).toHaveProperty('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
}
|
||||
})
|
||||
|
||||
it('fails if one of the values to update is not a literal', () => {
|
||||
it('fails if one of the values to update is not a literal', async () => {
|
||||
const src = [
|
||||
'const bar = 12',
|
||||
'export default {',
|
||||
@@ -343,16 +343,16 @@ describe('lib/util/config-file-updater', () => {
|
||||
'}',
|
||||
].join('\n')
|
||||
|
||||
return insertValueInJSString(src, { foo: 10 }, errors)
|
||||
.then(() => {
|
||||
try {
|
||||
await insertValueInJSString(src, { foo: 10 }, errors)
|
||||
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err).toHaveProperty('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
}
|
||||
})
|
||||
|
||||
it('fails with inlined values', () => {
|
||||
it('fails with inlined values', async () => {
|
||||
const src = stripIndent`\
|
||||
const foo = 12
|
||||
export default {
|
||||
@@ -360,16 +360,16 @@ describe('lib/util/config-file-updater', () => {
|
||||
}
|
||||
`
|
||||
|
||||
return insertValueInJSString(src, { foo: 10 }, errors)
|
||||
.then(() => {
|
||||
try {
|
||||
await insertValueInJSString(src, { foo: 10 }, errors)
|
||||
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err).toHaveProperty('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
}
|
||||
})
|
||||
|
||||
it('fails if there is a spread', () => {
|
||||
it('fails if there is a spread', async () => {
|
||||
const src = stripIndent`\
|
||||
const foo = { bar: 12 }
|
||||
export default {
|
||||
@@ -378,13 +378,13 @@ describe('lib/util/config-file-updater', () => {
|
||||
}
|
||||
`
|
||||
|
||||
return insertValueInJSString(src, { bar: 10 }, errors)
|
||||
.then(() => {
|
||||
try {
|
||||
await insertValueInJSString(src, { bar: 10 }, errors)
|
||||
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
})
|
||||
} catch (err) {
|
||||
expect(err).toHaveProperty('type', 'COULD_NOT_UPDATE_CONFIG_FILE')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import childProcess from 'child_process'
|
||||
import { expect } from 'chai'
|
||||
import semver from 'semver'
|
||||
import sinon from 'sinon'
|
||||
import { scaffoldMigrationProject as scaffoldProject } from '../helper'
|
||||
import { ProjectConfigIpc } from '../../../src/data/ProjectConfigIpc'
|
||||
|
||||
jest.mock('child_process')
|
||||
|
||||
describe('ProjectConfigIpc', () => {
|
||||
context('#eventProcessPid', () => {
|
||||
describe('#eventProcessPid', () => {
|
||||
let projectConfigIpc
|
||||
|
||||
beforeEach(async () => {
|
||||
const projectPath = await scaffoldProject('e2e')
|
||||
|
||||
// @ts-expect-error - mock
|
||||
childProcess.fork.mockImplementation(() => {
|
||||
return {
|
||||
on: jest.fn(),
|
||||
once: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
kill: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
projectConfigIpc = new ProjectConfigIpc(
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -26,16 +38,17 @@ describe('ProjectConfigIpc', () => {
|
||||
|
||||
afterEach(() => {
|
||||
projectConfigIpc.cleanupIpc()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('returns id for child process', () => {
|
||||
const expectedId = projectConfigIpc._childProcess.pid
|
||||
|
||||
expect(projectConfigIpc.childProcessPid).to.eq(expectedId)
|
||||
expect(projectConfigIpc.childProcessPid).toEqual(expectedId)
|
||||
})
|
||||
})
|
||||
|
||||
context('forkChildProcess', () => {
|
||||
describe('forkChildProcess', () => {
|
||||
// some of these node versions may not exist, but we want to verify
|
||||
// the experimental flags are correctly disabled for future versions
|
||||
const NODE_VERSIONS = ['20.5.1', '20.6.0', '20.19.1', '22.15.0']
|
||||
@@ -43,26 +56,28 @@ describe('ProjectConfigIpc', () => {
|
||||
const lastVersionWithDeprecatedLoaderOption = '20.5.1'
|
||||
|
||||
let projectConfigIpc
|
||||
let forkSpy
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.CYPRESS_INTERNAL_MOCK_TYPESCRIPT_INSTALL = 'true'
|
||||
forkSpy = sinon.spy(childProcess, 'fork')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.CYPRESS_INTERNAL_MOCK_TYPESCRIPT_INSTALL
|
||||
forkSpy.restore()
|
||||
projectConfigIpc.cleanupIpc()
|
||||
})
|
||||
|
||||
context('typescript', () => {
|
||||
describe('typescript', () => {
|
||||
[...NODE_VERSIONS].forEach((nodeVersion) => {
|
||||
const MOCK_NODE_PATH = `/Users/foo/.nvm/versions/node/v${nodeVersion}/bin/node`
|
||||
const MOCK_NODE_VERSION = nodeVersion
|
||||
|
||||
context(`node v${nodeVersion}`, () => {
|
||||
const PROJECTS = ['config-cjs-and-esm/config-with-ts-module', 'config-cjs-and-esm/config-with-module-resolution-bundler', 'config-cjs-and-esm/config-with-js-module', 'config-cjs-and-esm/config-with-cjs']
|
||||
describe(`node v${nodeVersion}`, () => {
|
||||
const PROJECTS = [
|
||||
'config-cjs-and-esm/config-with-ts-module',
|
||||
'config-cjs-and-esm/config-with-module-resolution-bundler',
|
||||
'config-cjs-and-esm/config-with-js-module',
|
||||
'config-cjs-and-esm/config-with-cjs',
|
||||
]
|
||||
|
||||
PROJECTS.forEach((project) => {
|
||||
it(`${project}: tsx generic loader (esm/commonjs/typescript)`, async () => {
|
||||
@@ -82,36 +97,36 @@ describe('ProjectConfigIpc', () => {
|
||||
// make sure that we use tsx for every file, regardless of typescript, esm, or commonjs
|
||||
if (semver.lte(nodeVersion, lastVersionWithDeprecatedLoaderOption)) {
|
||||
// For node 20.5.1 and down, we need use the --loader flag
|
||||
expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({
|
||||
env: {
|
||||
NODE_OPTIONS: sinon.match(/--loader ".*cypress\/node_modules\/tsx\/dist\/loader.mjs"/),
|
||||
},
|
||||
expect(childProcess.fork).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
||||
env: expect.objectContaining({
|
||||
NODE_OPTIONS: expect.stringMatching(/--loader ".*cypress\/node_modules\/tsx\/dist\/loader.mjs"/),
|
||||
}),
|
||||
}))
|
||||
} else {
|
||||
// For node 20.6.0 and up, we need use the --import flag
|
||||
expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({
|
||||
env: {
|
||||
NODE_OPTIONS: sinon.match(/--import ".*cypress\/node_modules\/tsx\/dist\/loader.mjs"/),
|
||||
},
|
||||
expect(childProcess.fork).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
||||
env: expect.objectContaining({
|
||||
NODE_OPTIONS: expect.stringMatching(/--import ".*cypress\/node_modules\/tsx\/dist\/loader.mjs"/),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
if (project.includes('config-with-ts-module') || project.includes('config-with-module-resolution-bundler')) {
|
||||
// these projects have typescript installed and have a tsconfig, so the TSX_TSCONFIG_PATH should be set to the project path
|
||||
expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({
|
||||
env: {
|
||||
TSX_TSCONFIG_PATH: sinon.match(`/cy-projects/${project}/tsconfig.json`),
|
||||
},
|
||||
expect(childProcess.fork).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
||||
env: expect.objectContaining({
|
||||
TSX_TSCONFIG_PATH: expect.stringMatching(`/cy-projects/${project}/tsconfig.json`),
|
||||
}),
|
||||
}))
|
||||
} else {
|
||||
// non typescript projects that do NOT have a tsconfig, so the TSX_TSCONFIG_PATH should be undefined
|
||||
expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({
|
||||
env: {
|
||||
TSX_TSCONFIG_PATH: undefined,
|
||||
},
|
||||
expect(childProcess.fork).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
||||
env: expect.not.objectContaining({
|
||||
TSX_TSCONFIG_PATH: expect.any(String),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
})
|
||||
}, 30000)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach } from '@jest/globals'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { ProjectConfigManager } from '../../../src/data/ProjectConfigManager'
|
||||
import { EventRegistrar } from '../../../src/data/EventRegistrar'
|
||||
@@ -23,20 +23,20 @@ describe('ProjectConfigManager', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('#eventProcessPid', () => {
|
||||
describe('#eventProcessPid', () => {
|
||||
it('returns process id from events ipc', () => {
|
||||
// @ts-expect-error
|
||||
configManager._eventsIpc = {
|
||||
childProcessPid: 45699,
|
||||
}
|
||||
|
||||
expect(configManager.eventProcessPid).to.eq(45699)
|
||||
expect(configManager.eventProcessPid).toEqual(45699)
|
||||
})
|
||||
|
||||
it('does not throw if config manager is not initialized', () => {
|
||||
// @ts-expect-error
|
||||
configManager._eventsIpc = undefined
|
||||
expect(configManager.eventProcessPid).to.eq(undefined)
|
||||
expect(configManager.eventProcessPid).toEqual(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import type { DataContext } from '../../../src'
|
||||
import path from 'path'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import sinon from 'sinon'
|
||||
import { FoundBrowser, FullConfig } from '@packages/types'
|
||||
|
||||
const browsers = [
|
||||
{ name: 'electron', family: 'chromium', channel: 'stable', displayName: 'Electron' },
|
||||
{ name: 'chrome', family: 'chromium', channel: 'stable', displayName: 'Chrome' },
|
||||
{ name: 'chrome', family: 'chromium', channel: 'beta', displayName: 'Chrome Beta' },
|
||||
{ name: 'firefox', family: 'firefox', channel: 'stable', displayName: 'Firefox' },
|
||||
{ name: 'electron', family: 'chromium', channel: 'stable', displayName: 'Electron', path: '', version: '' },
|
||||
{ name: 'chrome', family: 'chromium', channel: 'stable', displayName: 'Chrome', path: '', version: '' },
|
||||
{ name: 'chrome', family: 'chromium', channel: 'beta', displayName: 'Chrome Beta', path: '', version: '' },
|
||||
{ name: 'firefox', family: 'firefox', channel: 'stable', displayName: 'Firefox', path: '', version: '' },
|
||||
]
|
||||
|
||||
let ctx: DataContext
|
||||
@@ -16,10 +16,10 @@ let ctx: DataContext
|
||||
function createDataContext (modeOptions?: Parameters<typeof createTestDataContext>[1]) {
|
||||
const context = createTestDataContext('open', modeOptions)
|
||||
|
||||
context._apis.browserApi.getBrowsers = sinon.stub().resolves(browsers)
|
||||
context._apis.projectApi.insertProjectPreferencesToCache = sinon.stub()
|
||||
context.actions.project.launchProject = sinon.stub().resolves()
|
||||
context.project.getProjectPreferences = sinon.stub().resolves(null)
|
||||
jest.spyOn(context._apis.browserApi, 'getBrowsers').mockResolvedValue(browsers)
|
||||
context._apis.projectApi.insertProjectPreferencesToCache = jest.fn()
|
||||
jest.spyOn(context.actions.project, 'launchProject').mockResolvedValue(undefined)
|
||||
jest.spyOn(context.project, 'getProjectPreferences').mockResolvedValue(null)
|
||||
|
||||
// @ts-expect-error
|
||||
context.lifecycleManager._projectRoot = 'foo'
|
||||
@@ -35,31 +35,42 @@ const fullConfig: FullConfig = {
|
||||
describe('ProjectLifecycleManager', () => {
|
||||
beforeEach(() => {
|
||||
ctx = createDataContext()
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
})
|
||||
|
||||
context('#setInitialActiveBrowser', () => {
|
||||
afterEach(() => {
|
||||
// reset the working directory to the root of @packages/data-context
|
||||
process.chdir(path.join(__dirname, '../../../'))
|
||||
})
|
||||
|
||||
describe('#setInitialActiveBrowser', () => {
|
||||
it('falls back to browsers[0] if preferences and cliBrowser do not exist', async () => {
|
||||
ctx.coreData.activeBrowser = null
|
||||
ctx.coreData.cliBrowser = null
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
|
||||
expect(ctx.actions.project.launchProject).to.not.be.called
|
||||
expect(ctx.coreData.activeBrowser).toEqual(expect.objectContaining({ name: 'electron' }))
|
||||
expect(ctx.actions.project.launchProject).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cli --browser option if one is set', async () => {
|
||||
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('electron').resolves(browsers[0])
|
||||
jest.spyOn(ctx._apis.browserApi, 'ensureAndGetByNameOrPath').mockImplementation((name) => {
|
||||
if (name === 'electron') {
|
||||
return Promise.resolve(browsers[0])
|
||||
}
|
||||
|
||||
throw new Error('Browser not found')
|
||||
})
|
||||
|
||||
ctx.coreData.activeBrowser = null
|
||||
ctx.coreData.cliBrowser = 'electron'
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.coreData.cliBrowser).to.eq('electron')
|
||||
expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
|
||||
expect(ctx.actions.project.launchProject).to.not.be.called
|
||||
expect(ctx.coreData.cliBrowser).toEqual('electron')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(expect.objectContaining({ name: 'electron' }))
|
||||
expect(ctx.actions.project.launchProject).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cli --browser option and launches project if `--project --testingType` were used', async () => {
|
||||
@@ -68,38 +79,44 @@ describe('ProjectLifecycleManager', () => {
|
||||
testingType: 'e2e',
|
||||
})
|
||||
|
||||
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('electron').resolves(browsers[0])
|
||||
jest.spyOn(ctx._apis.browserApi, 'ensureAndGetByNameOrPath').mockImplementation((name) => {
|
||||
if (name === 'electron') {
|
||||
return Promise.resolve(browsers[0])
|
||||
}
|
||||
|
||||
throw new Error('Browser not found')
|
||||
})
|
||||
|
||||
ctx.coreData.activeBrowser = null
|
||||
ctx.coreData.cliBrowser = 'electron'
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.coreData.cliBrowser).to.eq('electron')
|
||||
expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
|
||||
expect(ctx.actions.project.launchProject).to.be.calledOnce
|
||||
expect(ctx.coreData.cliBrowser).toEqual('electron')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(expect.objectContaining({ name: 'electron' }))
|
||||
expect(ctx.actions.project.launchProject).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('uses lastBrowser if available', async () => {
|
||||
ctx.project.getProjectPreferences = sinon.stub().resolves({ lastBrowser: { name: 'chrome', channel: 'beta' } })
|
||||
jest.spyOn(ctx.project, 'getProjectPreferences').mockResolvedValue({ lastBrowser: { name: 'chrome', channel: 'beta' } })
|
||||
ctx.coreData.activeBrowser = null
|
||||
ctx.coreData.cliBrowser = null
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.coreData.activeBrowser).to.include({ name: 'chrome', displayName: 'Chrome Beta' })
|
||||
expect(ctx.actions.project.launchProject).to.not.be.called
|
||||
expect(ctx.coreData.activeBrowser).toEqual(expect.objectContaining({ name: 'chrome', displayName: 'Chrome Beta' }))
|
||||
expect(ctx.actions.project.launchProject).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('falls back to browsers[0] if lastBrowser does not exist', async () => {
|
||||
ctx.project.getProjectPreferences = sinon.stub().resolves({ lastBrowser: { name: 'chrome', channel: 'dev' } })
|
||||
jest.spyOn(ctx.project, 'getProjectPreferences').mockResolvedValue({ lastBrowser: { name: 'chrome', channel: 'dev' } })
|
||||
ctx.coreData.activeBrowser = null
|
||||
ctx.coreData.cliBrowser = null
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
|
||||
expect(ctx.actions.project.launchProject).to.not.be.called
|
||||
expect(ctx.coreData.activeBrowser).toEqual(expect.objectContaining({ name: 'electron' }))
|
||||
expect(ctx.actions.project.launchProject).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses config defaultBrowser option if --browser is not given', async () => {
|
||||
@@ -109,18 +126,25 @@ describe('ProjectLifecycleManager', () => {
|
||||
isBrowserGivenByCli: false,
|
||||
})
|
||||
|
||||
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('chrome').resolves(browsers[1])
|
||||
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'chrome' }))
|
||||
jest.spyOn(ctx._apis.browserApi, 'ensureAndGetByNameOrPath').mockImplementation((name) => {
|
||||
if (name === 'chrome') {
|
||||
return Promise.resolve(browsers[1])
|
||||
}
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq(undefined)
|
||||
expect(ctx.coreData.cliBrowser).to.eq(null)
|
||||
expect(ctx.coreData.activeBrowser).to.eq(null)
|
||||
throw new Error('Browser not found')
|
||||
})
|
||||
|
||||
jest.spyOn(ctx.lifecycleManager, 'loadedFullConfig', 'get').mockReturnValue({ defaultBrowser: 'chrome' } as unknown as FullConfig)
|
||||
|
||||
expect(ctx.modeOptions.browser).toEqual(undefined)
|
||||
expect(ctx.coreData.cliBrowser).toEqual(null)
|
||||
expect(ctx.coreData.activeBrowser).toEqual(null)
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq('chrome')
|
||||
expect(ctx.coreData.cliBrowser).to.eq('chrome')
|
||||
expect(ctx.coreData.activeBrowser).to.eq(browsers[1])
|
||||
expect(ctx.modeOptions.browser).toEqual('chrome')
|
||||
expect(ctx.coreData.cliBrowser).toEqual('chrome')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(browsers[1])
|
||||
})
|
||||
|
||||
it('doesn\'t use config defaultBrowser option if --browser is given', async () => {
|
||||
@@ -131,19 +155,26 @@ describe('ProjectLifecycleManager', () => {
|
||||
isBrowserGivenByCli: true,
|
||||
})
|
||||
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('firefox').resolves(browsers[3])
|
||||
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'chrome' }))
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
jest.spyOn(ctx._apis.browserApi, 'ensureAndGetByNameOrPath').mockImplementation((name) => {
|
||||
if (name === 'firefox') {
|
||||
return Promise.resolve(browsers[3])
|
||||
}
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq('firefox')
|
||||
expect(ctx.coreData.cliBrowser).to.eq('firefox')
|
||||
expect(ctx.coreData.activeBrowser).to.eq(null)
|
||||
throw new Error('Browser not found')
|
||||
})
|
||||
|
||||
jest.spyOn(ctx.lifecycleManager, 'loadedFullConfig', 'get').mockReturnValue({ defaultBrowser: 'chrome' } as unknown as FullConfig)
|
||||
|
||||
expect(ctx.modeOptions.browser).toEqual('firefox')
|
||||
expect(ctx.coreData.cliBrowser).toEqual('firefox')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(null)
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq('firefox')
|
||||
expect(ctx.coreData.cliBrowser).to.eq('firefox')
|
||||
expect(ctx.coreData.activeBrowser).to.eq(browsers[3])
|
||||
expect(ctx.modeOptions.browser).toEqual('firefox')
|
||||
expect(ctx.coreData.cliBrowser).toEqual('firefox')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(browsers[3])
|
||||
})
|
||||
|
||||
it('ignores the defaultBrowser if there is an active browser and updates the CLI browser to the active browser', async () => {
|
||||
@@ -153,27 +184,34 @@ describe('ProjectLifecycleManager', () => {
|
||||
isBrowserGivenByCli: false,
|
||||
})
|
||||
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('chrome:beta').resolves(browsers[2])
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
jest.spyOn(ctx._apis.browserApi, 'ensureAndGetByNameOrPath').mockImplementation((name) => {
|
||||
if (name === 'chrome:beta') {
|
||||
return Promise.resolve(browsers[2])
|
||||
}
|
||||
|
||||
throw new Error('Browser not found')
|
||||
})
|
||||
|
||||
// the default browser will be ignored since we have an active browser
|
||||
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'firefox' }))
|
||||
jest.spyOn(ctx.lifecycleManager, 'loadedFullConfig', 'get').mockReturnValue({ defaultBrowser: 'firefox' } as unknown as FullConfig)
|
||||
|
||||
// set the active browser to chrome:beta
|
||||
ctx.actions.browser.setActiveBrowser(browsers[2] as FoundBrowser)
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq(undefined)
|
||||
expect(ctx.coreData.cliBrowser).to.eq(null)
|
||||
expect(ctx.coreData.activeBrowser).to.eq(browsers[2])
|
||||
expect(ctx.modeOptions.browser).toEqual(undefined)
|
||||
expect(ctx.coreData.cliBrowser).toBeNull()
|
||||
expect(ctx.coreData.activeBrowser).toEqual(browsers[2])
|
||||
|
||||
await ctx.lifecycleManager.setInitialActiveBrowser()
|
||||
|
||||
expect(ctx.modeOptions.browser).to.eq('chrome:beta')
|
||||
expect(ctx.coreData.cliBrowser).to.eq('chrome:beta')
|
||||
expect(ctx.coreData.activeBrowser).to.eq(browsers[2])
|
||||
expect(ctx.modeOptions.browser).toEqual('chrome:beta')
|
||||
expect(ctx.coreData.cliBrowser).toEqual('chrome:beta')
|
||||
expect(ctx.coreData.activeBrowser).toEqual(browsers[2])
|
||||
})
|
||||
})
|
||||
|
||||
context('#eventProcessPid', () => {
|
||||
describe('#eventProcessPid', () => {
|
||||
it('returns process id from config manager', () => {
|
||||
// @ts-expect-error
|
||||
ctx.lifecycleManager._configManager = {
|
||||
@@ -181,13 +219,13 @@ describe('ProjectLifecycleManager', () => {
|
||||
destroy: () => {},
|
||||
}
|
||||
|
||||
expect(ctx.lifecycleManager.eventProcessPid).to.eq(12399)
|
||||
expect(ctx.lifecycleManager.eventProcessPid).toEqual(12399)
|
||||
})
|
||||
|
||||
it('does not throw if config manager is not initialized', () => {
|
||||
// @ts-expect-error
|
||||
ctx.lifecycleManager._configManager = undefined
|
||||
expect(ctx.lifecycleManager.eventProcessPid).to.eq(undefined)
|
||||
expect(ctx.lifecycleManager.eventProcessPid).toEqual(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// necessary to have mocha types working correctly
|
||||
import 'mocha'
|
||||
import { jest } from '@jest/globals'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { Response } from 'cross-fetch'
|
||||
@@ -9,14 +9,13 @@ import { graphqlSchema } from '../../graphql/schema'
|
||||
import { remoteSchemaWrapped as schemaCloud } from '../../graphql/stitching/remoteSchemaWrapped'
|
||||
import type { BrowserApiShape } from '../../src/sources/BrowserDataSource'
|
||||
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape, CohortsApiShape } from '../../src/actions'
|
||||
import sinon from 'sinon'
|
||||
import { execute, parse } from 'graphql'
|
||||
import { getOperationName } from '@urql/core'
|
||||
import { CloudQuery } from '../../test/graphql/stubCloudTypes'
|
||||
import { remoteSchema } from '../../graphql/stitching/remoteSchema'
|
||||
import type { OpenModeOptions, RunModeOptions } from '@packages/types'
|
||||
import { GET_MAJOR_VERSION_FOR_CONTENT } from '@packages/types'
|
||||
import { RelevantRunInfo } from '../../src/gen/graphcache-config.gen'
|
||||
import type { RelevantRunInfo } from '../../src/gen/graphcache-config.gen'
|
||||
|
||||
type SystemTestProject = typeof fixtureDirs[number]
|
||||
type SystemTestProjectPath<T extends SystemTestProject> = `${string}/system-tests/projects/${T}`
|
||||
@@ -47,38 +46,41 @@ export function createTestDataContext (mode: DataContextConfig['mode'] = 'run',
|
||||
modeOptions,
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {
|
||||
getPreferences: sinon.stub().resolves({
|
||||
getPreferences: jest.fn().mockResolvedValue({
|
||||
majorVersionWelcomeDismissed: { [GET_MAJOR_VERSION_FOR_CONTENT()]: 123456 },
|
||||
notifyWhenRunCompletes: ['failed'],
|
||||
}),
|
||||
getAvailableEditors: sinon.stub(),
|
||||
setPreferences: sinon.stub(),
|
||||
getAvailableEditors: jest.fn(),
|
||||
setPreferences: jest.fn(),
|
||||
} as unknown as LocalSettingsApiShape,
|
||||
authApi: {
|
||||
logIn: sinon.stub().throws('not stubbed'),
|
||||
resetAuthState: sinon.stub(),
|
||||
logIn: jest.fn().mockImplementation(() => {
|
||||
throw new Error('not stubbed')
|
||||
}),
|
||||
resetAuthState: jest.fn(),
|
||||
} as unknown as AuthApiShape,
|
||||
projectApi: {
|
||||
closeActiveProject: sinon.stub(),
|
||||
insertProjectToCache: sinon.stub().resolves(),
|
||||
getProjectRootsFromCache: sinon.stub().resolves([]),
|
||||
runSpec: sinon.stub(),
|
||||
routeToDebug: sinon.stub(),
|
||||
closeActiveProject: jest.fn(),
|
||||
insertProjectToCache: jest.fn().mockResolvedValue(undefined),
|
||||
getProjectRootsFromCache: jest.fn().mockResolvedValue([]),
|
||||
runSpec: jest.fn(),
|
||||
routeToDebug: jest.fn(),
|
||||
} as unknown as ProjectApiShape,
|
||||
electronApi: {
|
||||
isMainWindowFocused: sinon.stub().returns(false),
|
||||
focusMainWindow: sinon.stub(),
|
||||
copyTextToClipboard: (text) => {},
|
||||
isMainWindowFocused: jest.fn().mockReturnValue(false),
|
||||
focusMainWindow: jest.fn(),
|
||||
copyTextToClipboard: (text: string) => {},
|
||||
} as unknown as ElectronApiShape,
|
||||
browserApi: {
|
||||
focusActiveBrowserWindow: sinon.stub(),
|
||||
getBrowsers: sinon.stub().resolves([]),
|
||||
ensureAndGetByNameOrPath: jest.fn(),
|
||||
focusActiveBrowserWindow: jest.fn(),
|
||||
getBrowsers: jest.fn().mockResolvedValue([]),
|
||||
} as unknown as BrowserApiShape,
|
||||
cohortsApi: {
|
||||
getCohorts: sinon.stub().resolves(),
|
||||
getCohort: sinon.stub().resolves(),
|
||||
insertCohort: sinon.stub(),
|
||||
determineCohort: sinon.stub().resolves(),
|
||||
getCohorts: jest.fn().mockResolvedValue(undefined),
|
||||
getCohort: jest.fn().mockResolvedValue(undefined),
|
||||
insertCohort: jest.fn(),
|
||||
determineCohort: jest.fn().mockResolvedValue(undefined),
|
||||
} as unknown as CohortsApiShape,
|
||||
})
|
||||
|
||||
@@ -115,6 +117,7 @@ export function createTestDataContext (mode: DataContextConfig['mode'] = 'run',
|
||||
export function createRelevantRun (runNumber: number): RelevantRunInfo {
|
||||
return {
|
||||
runNumber,
|
||||
// @ts-expect-error - ciBuildNumber is not in the type
|
||||
ciBuildNumber: '123',
|
||||
branch: 'feature/branch',
|
||||
organizationId: 'org-id',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import { DataContext } from '../../../src'
|
||||
|
||||
import { Poller } from '../../../src/polling'
|
||||
@@ -7,92 +6,94 @@ import { createTestDataContext } from '../helper'
|
||||
|
||||
describe('Poller', () => {
|
||||
let ctx: DataContext
|
||||
let clock: sinon.SinonFakeTimers
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
clock = sinon.useFakeTimers()
|
||||
jest.useFakeTimers()
|
||||
ctx = createTestDataContext('open')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clock.restore()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('polls', async () => {
|
||||
const callback = sinon.stub()
|
||||
const callback = jest.fn()
|
||||
const interval = 5
|
||||
|
||||
const poller = new Poller(ctx, 'relevantRunChange', interval, callback)
|
||||
|
||||
const iterator = poller.start()
|
||||
|
||||
expect(callback).to.have.been.calledOnce
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback).to.have.been.calledTwice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
expect(callback).toHaveBeenCalledTimes(2)
|
||||
|
||||
//stop iterator
|
||||
iterator.return(undefined)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback, 'should not be called again after iterator stopped').to.have.been.calledTwice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
// should not be called again after iterator stopped
|
||||
expect(callback).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('can change interval', async () => {
|
||||
const callback = sinon.stub()
|
||||
const callback = jest.fn()
|
||||
const interval = 5
|
||||
|
||||
const poller = new Poller(ctx, 'relevantRunChange', interval, callback)
|
||||
|
||||
const iterator = poller.start()
|
||||
|
||||
expect(callback).to.have.been.calledOnce
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback).to.have.been.calledTwice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
expect(callback).toHaveBeenCalledTimes(2)
|
||||
|
||||
poller.interval = 10
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback, 'should be called at one original interval after interval change').to.have.been.calledThrice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
// should be called at one original interval after interval change'
|
||||
expect(callback).toHaveBeenCalledTimes(3)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback, 'should not be called yet with longer interval').to.have.been.calledThrice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
// should not be called yet with longer interval
|
||||
expect(callback).toHaveBeenCalledTimes(3)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback, 'should be called after longer interval').to.have.callCount(4)
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
// should be called after longer interval
|
||||
expect(callback).toHaveBeenCalledTimes(4)
|
||||
|
||||
//stop iterator
|
||||
iterator.return(undefined)
|
||||
})
|
||||
|
||||
it('handles multiple pollers for the same event', async () => {
|
||||
const callback = sinon.stub()
|
||||
const callback = jest.fn()
|
||||
const interval = 5
|
||||
|
||||
const poller = new Poller(ctx, 'relevantRunChange', interval, callback)
|
||||
const iterator1 = poller.start()
|
||||
|
||||
expect(callback).to.have.been.calledOnce
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback).to.have.been.calledTwice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
expect(callback).toHaveBeenCalledTimes(2)
|
||||
|
||||
const iterator2 = poller.start()
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback).to.have.been.calledThrice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
expect(callback).toHaveBeenCalledTimes(3)
|
||||
|
||||
iterator1.return(undefined)
|
||||
iterator2.return(undefined)
|
||||
|
||||
await clock.tickAsync(interval * 1000)
|
||||
expect(callback).to.have.been.calledThrice
|
||||
await jest.advanceTimersByTimeAsync(interval * 1000)
|
||||
expect(callback).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('returns initial value', async () => {
|
||||
const callback = sinon.stub()
|
||||
const callback = jest.fn()
|
||||
const interval = 5
|
||||
|
||||
const initialValue = { foo: true }
|
||||
@@ -100,38 +101,38 @@ describe('Poller', () => {
|
||||
const poller = new Poller<any, any>(ctx, 'relevantRunChange', interval, callback)
|
||||
const iterator1 = poller.start({ initialValue })
|
||||
|
||||
expect(callback).to.have.been.calledOnce
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
|
||||
const result1 = await iterator1.next()
|
||||
|
||||
expect(result1.value).to.eq(initialValue)
|
||||
expect(result1.value).toEqual(initialValue)
|
||||
})
|
||||
|
||||
it('stores and returns meta values for each subscription', async () => {
|
||||
const callback = sinon.stub()
|
||||
const callback = jest.fn()
|
||||
const interval = 5
|
||||
|
||||
const poller = new Poller<'relevantRunChange', { name: string }, { name: string}>(ctx, 'relevantRunChange', interval, callback)
|
||||
|
||||
expect(poller.subscriptions).to.have.length(0)
|
||||
expect(poller.subscriptions).toHaveLength(0)
|
||||
|
||||
const iterator1 = poller.start({ meta: { name: 'one' } })
|
||||
|
||||
expect(poller.subscriptions).to.have.length(1)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).to.eql(['one'])
|
||||
expect(poller.subscriptions).toHaveLength(1)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).toEqual(['one'])
|
||||
|
||||
const iterator2 = poller.start({ meta: { name: 'two' } })
|
||||
|
||||
expect(poller.subscriptions).to.have.length(2)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).to.eql(['one', 'two'])
|
||||
expect(poller.subscriptions).toHaveLength(2)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).toEqual(['one', 'two'])
|
||||
|
||||
iterator1.return(undefined)
|
||||
|
||||
expect(poller.subscriptions).to.have.length(1)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).to.eql(['two'])
|
||||
expect(poller.subscriptions).toHaveLength(1)
|
||||
expect(poller.subscriptions.map((sub) => sub.meta.name)).toEqual(['two'])
|
||||
|
||||
iterator2.return(undefined)
|
||||
|
||||
expect(poller.subscriptions).to.have.length(0)
|
||||
expect(poller.subscriptions).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import chai from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import { FullConfig } from '@packages/types'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { userBrowser, foundBrowserChrome } from '../../fixtures/browsers'
|
||||
|
||||
chai.use(sinonChai)
|
||||
const { expect } = chai
|
||||
|
||||
describe('BrowserDataSource', () => {
|
||||
describe('#allBrowsers', () => {
|
||||
it('returns machine browser if no user custom browsers resolved in config', async () => {
|
||||
@@ -18,12 +13,12 @@ describe('BrowserDataSource', () => {
|
||||
|
||||
const ctx = createTestDataContext('run')
|
||||
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
ctx.coreData.machineBrowsers = Promise.resolve([foundBrowserChrome])
|
||||
|
||||
const result = await ctx.browser.allBrowsers()
|
||||
|
||||
expect(result).to.eql([foundBrowserChrome])
|
||||
expect(result).toEqual([foundBrowserChrome])
|
||||
})
|
||||
|
||||
it('populates coreData.allBrowsers is not populated', async () => {
|
||||
@@ -34,13 +29,13 @@ describe('BrowserDataSource', () => {
|
||||
|
||||
const ctx = createTestDataContext('run')
|
||||
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
ctx.coreData.machineBrowsers = Promise.resolve([foundBrowserChrome])
|
||||
|
||||
const result = await ctx.browser.allBrowsers()
|
||||
|
||||
expect(result.length).to.eq(2)
|
||||
expect(result[1].custom).to.be.true
|
||||
expect(result.length).toEqual(2)
|
||||
expect(result[1].custom).toEqual(true)
|
||||
})
|
||||
|
||||
it('does not add user custom browser if name and version matches a machine browser', async () => {
|
||||
@@ -54,12 +49,12 @@ describe('BrowserDataSource', () => {
|
||||
|
||||
const ctx = createTestDataContext('run')
|
||||
|
||||
sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
|
||||
jest.spyOn(ctx.lifecycleManager, 'getFullInitialConfig').mockResolvedValue(fullConfig)
|
||||
ctx.coreData.machineBrowsers = Promise.resolve([machineBrowser])
|
||||
|
||||
const result = await ctx.browser.allBrowsers()
|
||||
|
||||
expect(result).to.eql([machineBrowser])
|
||||
expect(result).toEqual([machineBrowser])
|
||||
})
|
||||
|
||||
it('returns coreData.allBrowsers if populated', async () => {
|
||||
@@ -70,7 +65,7 @@ describe('BrowserDataSource', () => {
|
||||
|
||||
const result = await ctx.browser.allBrowsers()
|
||||
|
||||
expect(result).to.eql(allBrowsers)
|
||||
expect(result).toEqual(allBrowsers)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import { execute } from 'graphql'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import { Response } from 'cross-fetch'
|
||||
|
||||
import { DataContext } from '../../../src/DataContext'
|
||||
import { CloudDataResponse, CloudDataSource } from '../../../src/sources'
|
||||
import { createTestDataContext, scaffoldProject } from '../helper'
|
||||
import chai, { expect } from 'chai'
|
||||
import { ExecutionResult } from '@urql/core'
|
||||
import {
|
||||
CLOUD_PROJECT_QUERY,
|
||||
@@ -20,24 +18,22 @@ import {
|
||||
FAKE_USER_WITH_REQUIRED_RESOLVED_RESPONSE,
|
||||
} from './fixtures/graphqlFixtures'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
describe('CloudDataSource', () => {
|
||||
let cloudDataSource: CloudDataSource
|
||||
let fetchStub: sinon.SinonStub
|
||||
let getUserStub: sinon.SinonStub
|
||||
let logoutStub: sinon.SinonStub
|
||||
let invalidateCacheStub: sinon.SinonStub
|
||||
let fetchStub: jest.Mock<() => Promise<Response>>
|
||||
let getUserStub: jest.Mock<() => { authToken: string } | null>
|
||||
let logoutStub: jest.Mock<() => void>
|
||||
let invalidateCacheStub: jest.Mock<() => void>
|
||||
let ctx: DataContext
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore()
|
||||
fetchStub = sinon.stub()
|
||||
fetchStub.resolves(new Response(JSON.stringify(FAKE_USER_RESPONSE), { status: 200 }))
|
||||
getUserStub = sinon.stub()
|
||||
getUserStub.returns({ authToken: '1234' })
|
||||
logoutStub = sinon.stub()
|
||||
invalidateCacheStub = sinon.stub()
|
||||
jest.restoreAllMocks()
|
||||
fetchStub = jest.fn()
|
||||
fetchStub.mockResolvedValue(new Response(JSON.stringify(FAKE_USER_RESPONSE), { status: 200 }))
|
||||
getUserStub = jest.fn()
|
||||
getUserStub.mockReturnValue({ authToken: '1234' })
|
||||
logoutStub = jest.fn()
|
||||
invalidateCacheStub = jest.fn()
|
||||
ctx = createTestDataContext('open')
|
||||
cloudDataSource = new CloudDataSource({
|
||||
fetch: fetchStub,
|
||||
@@ -53,7 +49,7 @@ describe('CloudDataSource', () => {
|
||||
|
||||
describe('excecuteRemoteGraphQL', () => {
|
||||
it('returns immediately with { data: null } when no user is defined', () => {
|
||||
getUserStub.returns(null)
|
||||
getUserStub.mockReturnValue(null)
|
||||
const result = cloudDataSource.executeRemoteGraphQL({
|
||||
fieldName: 'cloudViewer',
|
||||
operationDoc: FAKE_USER_QUERY,
|
||||
@@ -61,8 +57,8 @@ describe('CloudDataSource', () => {
|
||||
operationType: 'query',
|
||||
})
|
||||
|
||||
expect(result).to.eql({ data: null })
|
||||
expect(fetchStub).not.to.be.called
|
||||
expect(result).toEqual({ data: null })
|
||||
expect(fetchStub).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('issues a fetch request for the data when the user is defined', async () => {
|
||||
@@ -75,7 +71,7 @@ describe('CloudDataSource', () => {
|
||||
|
||||
const resolved = await result
|
||||
|
||||
expect(resolved.data).to.eql(FAKE_USER_RESPONSE.data)
|
||||
expect(resolved.data).toEqual(FAKE_USER_RESPONSE.data)
|
||||
})
|
||||
|
||||
it('only issues a single fetch if the operation is called twice', async () => {
|
||||
@@ -92,12 +88,12 @@ describe('CloudDataSource', () => {
|
||||
operationType: 'query',
|
||||
})
|
||||
|
||||
expect(result1).to.eq(result2)
|
||||
expect(result1).toEqual(result2)
|
||||
|
||||
const resolved = await result1
|
||||
|
||||
expect(resolved.data).to.eql(FAKE_USER_RESPONSE.data)
|
||||
expect(fetchStub).to.have.been.calledOnce
|
||||
expect(resolved.data).toEqual(FAKE_USER_RESPONSE.data)
|
||||
expect(fetchStub).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('resolves eagerly with the cached data if the data has already been resolved', async () => {
|
||||
@@ -117,8 +113,8 @@ describe('CloudDataSource', () => {
|
||||
operationType: 'query',
|
||||
})
|
||||
|
||||
expect((immediateResult as ExecutionResult).data).to.eql(FAKE_USER_RESPONSE.data)
|
||||
expect(fetchStub).to.have.been.calledOnce
|
||||
expect((immediateResult as ExecutionResult).data).toEqual(FAKE_USER_RESPONSE.data)
|
||||
expect(fetchStub).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('when there is a nullable field missing, resolves with the eager result & fetches for the rest', async () => {
|
||||
@@ -131,7 +127,7 @@ describe('CloudDataSource', () => {
|
||||
|
||||
await result
|
||||
|
||||
fetchStub.resolves(new Response(JSON.stringify(FAKE_USER_WITH_OPTIONAL_RESOLVED_RESPONSE), { status: 200 }))
|
||||
fetchStub.mockResolvedValue(new Response(JSON.stringify(FAKE_USER_WITH_OPTIONAL_RESOLVED_RESPONSE), { status: 200 }))
|
||||
|
||||
const immediateResult = cloudDataSource.executeRemoteGraphQL({
|
||||
fieldName: 'cloudViewer',
|
||||
@@ -140,14 +136,14 @@ describe('CloudDataSource', () => {
|
||||
operationType: 'query',
|
||||
})
|
||||
|
||||
expect((immediateResult as CloudDataResponse).data).to.eql(FAKE_USER_WITH_OPTIONAL_MISSING_RESPONSE.data)
|
||||
expect((immediateResult as CloudDataResponse).stale).to.eql(true)
|
||||
expect((immediateResult as CloudDataResponse).data).toEqual(FAKE_USER_WITH_OPTIONAL_MISSING_RESPONSE.data)
|
||||
expect((immediateResult as CloudDataResponse).stale).toEqual(true)
|
||||
|
||||
const executingResponse = await (immediateResult as CloudDataResponse).executing
|
||||
|
||||
expect(executingResponse.data).to.eql(FAKE_USER_WITH_OPTIONAL_RESOLVED_RESPONSE.data)
|
||||
expect(executingResponse.data).toEqual(FAKE_USER_WITH_OPTIONAL_RESOLVED_RESPONSE.data)
|
||||
|
||||
expect(fetchStub).to.have.been.calledTwice
|
||||
expect(fetchStub).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('when there is a non-nullable field missing, issues the remote query immediately', async () => {
|
||||
@@ -160,7 +156,7 @@ describe('CloudDataSource', () => {
|
||||
|
||||
await result
|
||||
|
||||
fetchStub.resolves(new Response(JSON.stringify(FAKE_USER_WITH_REQUIRED_RESOLVED_RESPONSE), { status: 200 }))
|
||||
fetchStub.mockResolvedValue(new Response(JSON.stringify(FAKE_USER_WITH_REQUIRED_RESOLVED_RESPONSE), { status: 200 }))
|
||||
|
||||
const requiredResult = cloudDataSource.executeRemoteGraphQL({
|
||||
fieldName: 'cloudViewer',
|
||||
@@ -169,15 +165,15 @@ describe('CloudDataSource', () => {
|
||||
operationType: 'query',
|
||||
})
|
||||
|
||||
expect(requiredResult).to.be.instanceOf(Promise)
|
||||
expect(requiredResult).toBeInstanceOf(Promise)
|
||||
|
||||
expect((await requiredResult).data).to.eql(FAKE_USER_WITH_REQUIRED_RESOLVED_RESPONSE.data)
|
||||
expect((await requiredResult).data).toEqual(FAKE_USER_WITH_REQUIRED_RESOLVED_RESPONSE.data)
|
||||
|
||||
expect(fetchStub).to.have.been.calledTwice
|
||||
expect(fetchStub).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('returns error property on response', async () => {
|
||||
fetchStub.resolves(new Response(JSON.stringify(new Error('Unauthorized')), { status: 200 }))
|
||||
fetchStub.mockResolvedValue(new Response(JSON.stringify(new Error('Unauthorized')), { status: 200 }))
|
||||
|
||||
const result = cloudDataSource.executeRemoteGraphQL({
|
||||
fieldName: 'cloudViewer',
|
||||
@@ -188,13 +184,13 @@ describe('CloudDataSource', () => {
|
||||
|
||||
const resolved = await result
|
||||
|
||||
expect(resolved.data).to.eql(undefined)
|
||||
expect(resolved.errors).to.exist
|
||||
expect(resolved.error?.networkError?.message).to.eql('No Content')
|
||||
expect(resolved.data).toEqual(undefined)
|
||||
expect(resolved.errors).toBeDefined()
|
||||
expect(resolved.error?.networkError?.message).toEqual('No Content')
|
||||
})
|
||||
|
||||
it('logout user on 401 response', async () => {
|
||||
fetchStub.resolves(new Response(JSON.stringify(new Error('Unauthorized')), { status: 401 }))
|
||||
fetchStub.mockResolvedValue(new Response(JSON.stringify(new Error('Unauthorized')), { status: 401 }))
|
||||
|
||||
const result = cloudDataSource.executeRemoteGraphQL({
|
||||
fieldName: 'cloudViewer',
|
||||
@@ -205,11 +201,11 @@ describe('CloudDataSource', () => {
|
||||
|
||||
const resolved = await result
|
||||
|
||||
expect(resolved.data).to.eql(undefined)
|
||||
expect(resolved.errors).to.exist
|
||||
expect(resolved.error?.networkError?.message).to.eql('Unauthorized')
|
||||
expect(resolved.data).toEqual(undefined)
|
||||
expect(resolved.errors).toBeDefined()
|
||||
expect(resolved.error?.networkError?.message).toEqual('Unauthorized')
|
||||
|
||||
expect(logoutStub).to.have.been.calledOnce
|
||||
expect(logoutStub).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -220,7 +216,7 @@ describe('CloudDataSource', () => {
|
||||
operationVariables: {},
|
||||
})
|
||||
|
||||
expect(result).to.eql(false)
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns true if we are currently resolving the request', () => {
|
||||
@@ -236,7 +232,7 @@ describe('CloudDataSource', () => {
|
||||
operationVariables: {},
|
||||
})
|
||||
|
||||
expect(result).to.eql(true)
|
||||
expect(result).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -247,7 +243,7 @@ describe('CloudDataSource', () => {
|
||||
operationVariables: {},
|
||||
})
|
||||
|
||||
expect(result).to.eql(false)
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
|
||||
it('returns true if we have resolved the data for the query', async () => {
|
||||
@@ -263,7 +259,7 @@ describe('CloudDataSource', () => {
|
||||
operationVariables: {},
|
||||
})
|
||||
|
||||
expect(result).to.eql(true)
|
||||
expect(result).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -279,24 +275,26 @@ describe('CloudDataSource', () => {
|
||||
expect(cloudDataSource.hasResolved({
|
||||
operationDoc: FAKE_USER_QUERY,
|
||||
operationVariables: {},
|
||||
})).to.eq(true)
|
||||
})).toEqual(true)
|
||||
|
||||
await cloudDataSource.invalidate('Query', 'cloudViewer')
|
||||
|
||||
expect(cloudDataSource.hasResolved({
|
||||
operationDoc: FAKE_USER_QUERY,
|
||||
operationVariables: {},
|
||||
})).to.eq(false)
|
||||
})).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('delegateCloudField', () => {
|
||||
it('delegates a field to the remote schema, which calls executeRemoteGraphQL', async () => {
|
||||
fetchStub.resolves(new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(new Response(JSON.stringify(CLOUD_PROJECT_RESPONSE), { status: 200 }))
|
||||
}, 200)
|
||||
}))
|
||||
fetchStub.mockImplementation(() => {
|
||||
return new Promise<Response>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(new Response(JSON.stringify(CLOUD_PROJECT_RESPONSE), { status: 200 }))
|
||||
}, 200)
|
||||
})
|
||||
})
|
||||
|
||||
Object.defineProperty(ctx, 'cloud', { value: cloudDataSource })
|
||||
|
||||
@@ -304,13 +302,13 @@ describe('CloudDataSource', () => {
|
||||
|
||||
const delegateCloudField = cloudDataSource.delegateCloudField
|
||||
|
||||
const delegateCloudSpy = sinon.stub(cloudDataSource, 'delegateCloudField').callsFake(async function (...args) {
|
||||
const delegateCloudSpy = jest.spyOn(cloudDataSource, 'delegateCloudField').mockImplementation(async function (...args) {
|
||||
return delegateCloudField.apply(this, args)
|
||||
})
|
||||
|
||||
await ctx.actions.project.setCurrentProject(dir)
|
||||
|
||||
sinon.stub(ctx.project, 'projectId').resolves('abc1234')
|
||||
jest.spyOn(ctx.project, 'projectId').mockResolvedValue('abc1234')
|
||||
|
||||
const result = await execute({
|
||||
rootValue: {},
|
||||
@@ -319,16 +317,16 @@ describe('CloudDataSource', () => {
|
||||
contextValue: ctx,
|
||||
})
|
||||
|
||||
expect(delegateCloudSpy).to.have.been.calledOnce
|
||||
expect(delegateCloudSpy).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(result.data).to.eql({
|
||||
expect(result.data).toEqual({
|
||||
currentProject: {
|
||||
cloudProject: null,
|
||||
id: Buffer.from(`CurrentProject:${dir}`, 'utf8').toString('base64'),
|
||||
},
|
||||
})
|
||||
|
||||
expect(await delegateCloudSpy.firstCall.returnValue)
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
const result2 = await execute({
|
||||
rootValue: {},
|
||||
@@ -337,7 +335,7 @@ describe('CloudDataSource', () => {
|
||||
contextValue: ctx,
|
||||
})
|
||||
|
||||
expect(result2.data).to.eql({
|
||||
expect(result2.data).toEqual({
|
||||
currentProject: {
|
||||
cloudProject: {
|
||||
__typename: 'CloudProject',
|
||||
@@ -347,7 +345,7 @@ describe('CloudDataSource', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(fetchStub).to.have.been.calledOnce
|
||||
expect(fetchStub).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import fs from 'fs-extra'
|
||||
import { expect } from 'chai'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
|
||||
@@ -31,7 +30,6 @@ describe('FileDataSource', () => {
|
||||
|
||||
afterEach(() => {
|
||||
removeProject('globby-test-bed')
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
describe('#getFilesByGlob', () => {
|
||||
@@ -41,9 +39,9 @@ describe('FileDataSource', () => {
|
||||
'root-script-*.js',
|
||||
)
|
||||
|
||||
expect(files).to.have.length(2)
|
||||
expect(files[0]).to.eq(path.join(projectPath, 'root-script-1.js'))
|
||||
expect(files[1]).to.eq(path.join(projectPath, 'root-script-2.js'))
|
||||
expect(files).toHaveLength(2)
|
||||
expect(files[0]).toEqual(path.join(projectPath, 'root-script-1.js'))
|
||||
expect(files[1]).toEqual(path.join(projectPath, 'root-script-2.js'))
|
||||
})
|
||||
|
||||
it('finds files matching relative patterns in working dir', async () => {
|
||||
@@ -52,7 +50,7 @@ describe('FileDataSource', () => {
|
||||
'./root-script-*.js',
|
||||
)
|
||||
|
||||
expect(files).to.have.length(2)
|
||||
expect(files).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('finds files matching patterns that include working dir', async () => {
|
||||
@@ -61,7 +59,7 @@ describe('FileDataSource', () => {
|
||||
`${projectPath}/root-script-*.js`,
|
||||
)
|
||||
|
||||
expect(files).to.have.length(2)
|
||||
expect(files).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('does not replace working directory in glob pattern if it is not leading', async () => {
|
||||
@@ -79,7 +77,7 @@ describe('FileDataSource', () => {
|
||||
`./cypress${projectPath}/nested-script.js`,
|
||||
)
|
||||
|
||||
expect(files).to.have.length(1)
|
||||
expect(files).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('finds files matching multiple patterns', async () => {
|
||||
@@ -88,7 +86,7 @@ describe('FileDataSource', () => {
|
||||
['root-script-*.js', 'scripts/**/*.js'],
|
||||
)
|
||||
|
||||
expect(files).to.have.length(5)
|
||||
expect(files).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('does not find files outside of working dir', async () => {
|
||||
@@ -97,7 +95,7 @@ describe('FileDataSource', () => {
|
||||
['root-script-*.js', './**/*.js'],
|
||||
)
|
||||
|
||||
expect(files).to.have.length(3)
|
||||
expect(files).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('by default ignores files within node_modules', async () => {
|
||||
@@ -115,7 +113,7 @@ describe('FileDataSource', () => {
|
||||
|
||||
// only scripts at root should be found, as node_modules is implicitly ignored
|
||||
// and ./scripts is explicitly ignored
|
||||
expect(files).to.have.length(2)
|
||||
expect(files).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('does not ignores files within node_modules, if node_modules is in the glob path', async () => {
|
||||
@@ -132,7 +130,7 @@ describe('FileDataSource', () => {
|
||||
|
||||
// scripts at root (2 of them) and scripts at node_modules should be found
|
||||
// and ./scripts is explicitly ignored
|
||||
expect(files).to.have.length(4)
|
||||
expect(files).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('does not ignores files within node_modules, if node_modules is in the project path', async () => {
|
||||
@@ -149,15 +147,18 @@ describe('FileDataSource', () => {
|
||||
)
|
||||
|
||||
// only scripts at node_modules should be found, since it is the project path
|
||||
expect(files).to.have.length(3)
|
||||
expect(files).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('converts globs to POSIX paths on windows', async () => {
|
||||
const windowsSeperator = '\\'
|
||||
|
||||
sinon.stub(os, 'platform').returns('win32')
|
||||
const toPosixStub = sinon.stub(fileUtil, 'toPosix').callsFake((path) => {
|
||||
return toPosixStub.wrappedMethod(path, windowsSeperator)
|
||||
jest.spyOn(os, 'platform').mockReturnValue('win32')
|
||||
|
||||
const { toPosix: toPosixActual } = jest.requireActual<typeof import('../../../src/util/file')>('../../../src/util/file')
|
||||
|
||||
jest.spyOn(fileUtil, 'toPosix').mockImplementation((path) => {
|
||||
return toPosixActual(path, windowsSeperator)
|
||||
})
|
||||
|
||||
const files = await fileDataSource.getFilesByGlob(
|
||||
@@ -165,7 +166,7 @@ describe('FileDataSource', () => {
|
||||
`**${windowsSeperator}*script-*.js`,
|
||||
)
|
||||
|
||||
expect(files).to.have.length(5)
|
||||
expect(files).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('finds files using given globby options', async () => {
|
||||
@@ -175,9 +176,9 @@ describe('FileDataSource', () => {
|
||||
{ absolute: false },
|
||||
)
|
||||
|
||||
expect(files).to.have.length(2)
|
||||
expect(files[0]).to.eq('root-script-1.js')
|
||||
expect(files[1]).to.eq('root-script-2.js')
|
||||
expect(files).toHaveLength(2)
|
||||
expect(files[0]).toEqual('root-script-1.js')
|
||||
expect(files[1]).toEqual('root-script-2.js')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -195,14 +196,8 @@ describe('FileDataSource', () => {
|
||||
ignore: ['**/node_modules/**'],
|
||||
}
|
||||
|
||||
let matchGlobsStub: sinon.SinonStub
|
||||
|
||||
beforeEach(() => {
|
||||
matchGlobsStub = sinon.stub(FileDataSourceModule, 'matchGlobs').resolves(mockMatches)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
jest.spyOn(FileDataSourceModule, 'matchGlobs').mockResolvedValue(mockMatches)
|
||||
})
|
||||
|
||||
it('matches absolute patterns when working directory is root', async () => {
|
||||
@@ -211,8 +206,8 @@ describe('FileDataSource', () => {
|
||||
'/cypress/e2e/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['cypress/e2e/**.cy.js'],
|
||||
{ ...defaultGlobbyOptions, cwd: '/' },
|
||||
)
|
||||
@@ -224,8 +219,8 @@ describe('FileDataSource', () => {
|
||||
'./project/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['./project/**.cy.js'],
|
||||
{ ...defaultGlobbyOptions, cwd: '/' },
|
||||
)
|
||||
@@ -237,8 +232,8 @@ describe('FileDataSource', () => {
|
||||
'project/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['project/**.cy.js'],
|
||||
{ ...defaultGlobbyOptions, cwd: '/' },
|
||||
)
|
||||
@@ -250,8 +245,8 @@ describe('FileDataSource', () => {
|
||||
'/my/project/cypress/e2e/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['cypress/e2e/**.cy.js'],
|
||||
{ ...defaultGlobbyOptions, cwd: '/my/project' },
|
||||
)
|
||||
@@ -263,8 +258,8 @@ describe('FileDataSource', () => {
|
||||
'/my/project/cypress/my/project/e2e/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['cypress/my/project/e2e/**.cy.js'],
|
||||
{ ...defaultGlobbyOptions, cwd: '/my/project' },
|
||||
)
|
||||
@@ -277,8 +272,8 @@ describe('FileDataSource', () => {
|
||||
{ ignore: ['ignore/foo.*', '/ignore/bar.*'] },
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['cypress/e2e/**.cy.js'],
|
||||
{
|
||||
...defaultGlobbyOptions,
|
||||
@@ -294,8 +289,8 @@ describe('FileDataSource', () => {
|
||||
'/cypress/e2e/**.cy.js',
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['/cypress/e2e/**.cy.js'],
|
||||
{
|
||||
...defaultGlobbyOptions,
|
||||
@@ -314,8 +309,8 @@ describe('FileDataSource', () => {
|
||||
],
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
[
|
||||
'node_modules/cypress/e2e/**.cy.js',
|
||||
'cypress/e2e/**.cy.js',
|
||||
@@ -335,8 +330,8 @@ describe('FileDataSource', () => {
|
||||
{ ignore: ['ignore/foo.*', '/ignore/bar.*'] },
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['/node_modules/test_package/e2e/**.cy.js'],
|
||||
{
|
||||
...defaultGlobbyOptions,
|
||||
@@ -353,8 +348,8 @@ describe('FileDataSource', () => {
|
||||
{ absolute: false, objectMode: true },
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.been.calledWith(
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledWith(
|
||||
['cypress/e2e/**.cy.js'],
|
||||
{
|
||||
...defaultGlobbyOptions,
|
||||
@@ -366,8 +361,13 @@ describe('FileDataSource', () => {
|
||||
})
|
||||
|
||||
it('should retry search with `suppressErrors` if non-suppressed attempt fails', async () => {
|
||||
matchGlobsStub.onFirstCall().rejects(new Error('mocked filesystem error'))
|
||||
matchGlobsStub.onSecondCall().resolves(mockMatches)
|
||||
jest.spyOn(FileDataSourceModule, 'matchGlobs')
|
||||
.mockReset()
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.reject(new Error('mocked filesystem error'))
|
||||
}).mockImplementationOnce(() => {
|
||||
return Promise.resolve(mockMatches)
|
||||
})
|
||||
|
||||
const files = await fileDataSource.getFilesByGlob(
|
||||
'/',
|
||||
@@ -375,14 +375,18 @@ describe('FileDataSource', () => {
|
||||
{ absolute: false, objectMode: true },
|
||||
)
|
||||
|
||||
expect(files).to.eq(mockMatches)
|
||||
expect(matchGlobsStub).to.have.callCount(2)
|
||||
expect(matchGlobsStub.getCall(0).args[1].suppressErrors).to.be.undefined
|
||||
expect(matchGlobsStub.getCall(1).args[1].suppressErrors).to.equal(true)
|
||||
expect(files).toEqual(mockMatches)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledTimes(2)
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenNthCalledWith(1, expect.any(Array), expect.not.objectContaining({ suppressErrors: expect.any(Boolean) }))
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenNthCalledWith(2, expect.any(Array), expect.objectContaining({ suppressErrors: true }))
|
||||
})
|
||||
|
||||
it('should return empty array if retry with suppression fails', async () => {
|
||||
matchGlobsStub.rejects(new Error('mocked filesystem error'))
|
||||
jest.spyOn(FileDataSourceModule, 'matchGlobs')
|
||||
.mockReset()
|
||||
.mockImplementation(() => {
|
||||
return Promise.reject(new Error('mocked filesystem error'))
|
||||
})
|
||||
|
||||
const files = await fileDataSource.getFilesByGlob(
|
||||
'/',
|
||||
@@ -390,8 +394,8 @@ describe('FileDataSource', () => {
|
||||
{ absolute: false, objectMode: true },
|
||||
)
|
||||
|
||||
expect(files).to.eql([])
|
||||
expect(matchGlobsStub).to.have.callCount(2)
|
||||
expect(files).toEqual([])
|
||||
expect(FileDataSourceModule.matchGlobs).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { assert, expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import simpleGit from 'simple-git'
|
||||
import fs from 'fs-extra'
|
||||
import sinon from 'sinon'
|
||||
import pDefer from 'p-defer'
|
||||
import chokidar from 'chokidar'
|
||||
|
||||
@@ -42,12 +41,10 @@ describe('GitDataSource', () => {
|
||||
}
|
||||
|
||||
gitInfo = undefined
|
||||
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it(`gets correct status for files on ${os.platform()}`, async function () {
|
||||
const onBranchChange = sinon.stub()
|
||||
const onBranchChange = jest.fn()
|
||||
const dfd = pDefer()
|
||||
|
||||
// create a file and modify a file to express all
|
||||
@@ -73,29 +70,29 @@ describe('GitDataSource', () => {
|
||||
|
||||
const gitInfoChangeResolve = await dfd.promise
|
||||
|
||||
expect(gitInfoChangeResolve).to.eql([fooSpec, aRecordSpec, xhrSpec])
|
||||
expect(gitInfoChangeResolve).toEqual([fooSpec, aRecordSpec, xhrSpec])
|
||||
|
||||
const created = gitInfo.gitInfoFor(fooSpec)!
|
||||
const unmodified = gitInfo.gitInfoFor(aRecordSpec)!
|
||||
const modified = gitInfo.gitInfoFor(xhrSpec)!
|
||||
|
||||
expect(created.lastModifiedHumanReadable).to.match(/(a few|[0-9]) seconds? ago/)
|
||||
expect(created.statusType).to.eql('created')
|
||||
expect(created.lastModifiedHumanReadable).toMatch(/(a few|[0-9]) seconds? ago/)
|
||||
expect(created.statusType).toEqual('created')
|
||||
// do not want to set this explicitly in the test, since it can mess up your local git instance
|
||||
expect(created.author).not.to.be.undefined
|
||||
expect(created.lastModifiedTimestamp).not.to.be.undefined
|
||||
expect(created.author).not.toBeUndefined()
|
||||
expect(created.lastModifiedTimestamp).not.toBeUndefined()
|
||||
|
||||
expect(unmodified.lastModifiedHumanReadable).to.match(/(a few|[0-9]) seconds? ago/)
|
||||
expect(unmodified.statusType).to.eql('unmodified')
|
||||
expect(unmodified.lastModifiedHumanReadable).toMatch(/(a few|[0-9]) seconds? ago/)
|
||||
expect(unmodified.statusType).toEqual('unmodified')
|
||||
// do not want to set this explicitly in the test, since it can mess up your local git instance
|
||||
expect(unmodified.author).not.to.be.undefined
|
||||
expect(unmodified.lastModifiedTimestamp).not.to.be.undefined
|
||||
expect(unmodified.author).not.toBeUndefined()
|
||||
expect(unmodified.lastModifiedTimestamp).not.toBeUndefined()
|
||||
|
||||
expect(modified.lastModifiedHumanReadable).to.match(/(a few|[0-9]) seconds? ago/)
|
||||
expect(modified.statusType).to.eql('modified')
|
||||
expect(modified.lastModifiedHumanReadable).toMatch(/(a few|[0-9]) seconds? ago/)
|
||||
expect(modified.statusType).toEqual('modified')
|
||||
// do not want to set this explicitly in the test, since it can mess up your local git instance
|
||||
expect(modified.author).not.to.be.undefined
|
||||
expect(modified.lastModifiedTimestamp).not.to.be.undefined
|
||||
expect(modified.author).not.toBeUndefined()
|
||||
expect(modified.lastModifiedTimestamp).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it(`handles files with special characters on ${os.platform()}`, async () => {
|
||||
@@ -128,9 +125,9 @@ describe('GitDataSource', () => {
|
||||
gitInfo = new GitDataSource({
|
||||
isRunMode: false,
|
||||
projectRoot: projectPath,
|
||||
onBranchChange: sinon.stub(),
|
||||
onBranchChange: jest.fn(),
|
||||
onGitInfoChange: dfd.resolve,
|
||||
onError: sinon.stub(),
|
||||
onError: jest.fn(),
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
@@ -145,44 +142,44 @@ describe('GitDataSource', () => {
|
||||
return gitInfo.gitInfoFor(filepath)
|
||||
})
|
||||
|
||||
expect(results).to.have.lengthOf(filepaths.length)
|
||||
expect(results).toHaveLength(filepaths.length)
|
||||
|
||||
filepaths.forEach((filepath, index) => {
|
||||
const result = results[index]
|
||||
|
||||
expect(result?.lastModifiedHumanReadable).to.match(/(a few|[0-9]) seconds? ago/)
|
||||
expect(result?.statusType).to.eql('created')
|
||||
expect(result?.lastModifiedHumanReadable).toMatch(/(a few|[0-9]) seconds? ago/)
|
||||
expect(result?.statusType).toEqual('created')
|
||||
})
|
||||
})
|
||||
|
||||
it(`watches switching branches on ${os.platform()}`, async () => {
|
||||
const stub = sinon.stub()
|
||||
const stub = jest.fn()
|
||||
const dfd = pDefer()
|
||||
|
||||
stub.onFirstCall().callsFake(dfd.resolve)
|
||||
stub.mockImplementationOnce(dfd.resolve)
|
||||
|
||||
gitInfo = new GitDataSource({
|
||||
isRunMode: false,
|
||||
projectRoot: projectPath,
|
||||
onBranchChange: stub,
|
||||
onGitInfoChange: sinon.stub(),
|
||||
onError: sinon.stub(),
|
||||
onGitInfoChange: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
})
|
||||
|
||||
const result = await dfd.promise
|
||||
|
||||
expect(result).to.eq((await git.branch()).current)
|
||||
expect(result).toEqual((await git.branch()).current)
|
||||
|
||||
const switchBranch = pDefer()
|
||||
|
||||
stub.onSecondCall().callsFake(switchBranch.resolve)
|
||||
stub.mockImplementationOnce(switchBranch.resolve)
|
||||
|
||||
git.checkoutLocalBranch('testing123')
|
||||
expect(await switchBranch.promise).to.eq('testing123')
|
||||
expect(await switchBranch.promise).toEqual('testing123')
|
||||
})
|
||||
|
||||
it(`handles error while watching .git on ${os.platform()}`, async () => {
|
||||
sinon.stub(chokidar, 'watch').callsFake(() => {
|
||||
jest.spyOn(chokidar, 'watch').mockImplementation(() => {
|
||||
const mockWatcher = {
|
||||
on: (event, fn) => {
|
||||
if (event === 'error') {
|
||||
@@ -195,34 +192,34 @@ describe('GitDataSource', () => {
|
||||
return mockWatcher as chokidar.FSWatcher
|
||||
})
|
||||
|
||||
const errorStub = sinon.stub()
|
||||
const stub = sinon.stub()
|
||||
const errorStub = jest.fn()
|
||||
const stub = jest.fn()
|
||||
const dfd = pDefer()
|
||||
|
||||
stub.onFirstCall().callsFake(dfd.resolve)
|
||||
stub.mockImplementationOnce(dfd.resolve)
|
||||
|
||||
gitInfo = new GitDataSource({
|
||||
isRunMode: false,
|
||||
projectRoot: projectPath,
|
||||
onBranchChange: stub,
|
||||
onGitInfoChange: sinon.stub(),
|
||||
onGitInfoChange: jest.fn(),
|
||||
onError: errorStub,
|
||||
})
|
||||
|
||||
const result = await dfd.promise
|
||||
|
||||
expect(result).to.eq((await git.branch()).current)
|
||||
expect(result).toEqual((await git.branch()).current)
|
||||
|
||||
expect(errorStub).to.be.callCount(1)
|
||||
expect(errorStub).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
context('Git Hashes - no fake timers', () => {
|
||||
describe('Git Hashes - no fake timers', () => {
|
||||
it('does not include commits that are part of the Git tree from a merge', async () => {
|
||||
const dfd = pDefer()
|
||||
|
||||
const logCallback = sinon.stub()
|
||||
const logCallback = jest.fn()
|
||||
|
||||
logCallback.onFirstCall().callsFake(dfd.resolve)
|
||||
logCallback.mockImplementationOnce(dfd.resolve)
|
||||
|
||||
const mainBranch = (await git.branch()).current
|
||||
|
||||
@@ -254,16 +251,16 @@ describe('GitDataSource', () => {
|
||||
gitInfo = new GitDataSource({
|
||||
isRunMode: false,
|
||||
projectRoot: projectPath,
|
||||
onBranchChange: sinon.stub(),
|
||||
onGitInfoChange: sinon.stub(),
|
||||
onError: sinon.stub(),
|
||||
onBranchChange: jest.fn(),
|
||||
onGitInfoChange: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
onGitLogChange: logCallback,
|
||||
})
|
||||
|
||||
await dfd.promise
|
||||
|
||||
expect(gitInfo.currentHashes).to.have.length(3)
|
||||
expect(gitInfo.currentHashes).not.to.contain(hashFromMerge)
|
||||
expect(gitInfo.currentHashes).toHaveLength(3)
|
||||
expect(gitInfo.currentHashes).not.toContain(hashFromMerge)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,64 +1,52 @@
|
||||
import { expect, use } from 'chai'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import sinon from 'sinon'
|
||||
import proxyquire from 'proxyquire'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import pDefer, { DeferredPromise } from 'p-defer'
|
||||
import EventEmitter from 'events'
|
||||
|
||||
import { SimpleGit } from 'simple-git'
|
||||
import type { GitDataSource, GitDataSourceConfig } from '../../../src/sources/GitDataSource'
|
||||
import type { SimpleGit } from 'simple-git'
|
||||
import { GitDataSource } from '../../../src/sources/GitDataSource'
|
||||
import Chokidar from 'chokidar'
|
||||
|
||||
use(sinonChai)
|
||||
const stubbedSimpleGit: {
|
||||
// Parameters<> only gets the last overload defined, which is
|
||||
// supposed to be the most permissive. However, SimpleGit defines
|
||||
// overloads in the opposite order, and we need the one that takes
|
||||
// a string.
|
||||
revparse: jest.Mock<(option: string) => R<'revparse'>>
|
||||
branch: jest.Mock<(options: P<'branch'>) => R<'branch'>>
|
||||
status: jest.Mock<(options: P<'status'>) => R<'status'>>
|
||||
log: jest.Mock<(options: P<'log'>) => R<'log'>>
|
||||
} = {
|
||||
revparse: jest.fn(),
|
||||
branch: jest.fn(),
|
||||
status: jest.fn(),
|
||||
log: jest.fn(),
|
||||
}
|
||||
|
||||
jest.mock('simple-git', () => {
|
||||
// use a module factory to return the stubbed SimpleGit instance
|
||||
// @see https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return stubbedSimpleGit
|
||||
})
|
||||
})
|
||||
|
||||
type P<F extends keyof SimpleGit> = Parameters<SimpleGit[F]>
|
||||
type R<F extends keyof SimpleGit> = ReturnType<SimpleGit[F]>
|
||||
|
||||
interface GitDataSourceConstructor {
|
||||
new (config: GitDataSourceConfig): GitDataSource
|
||||
}
|
||||
|
||||
type GDSImport = {
|
||||
GitDataSource: GitDataSourceConstructor
|
||||
}
|
||||
|
||||
describe('GitDataSource', () => {
|
||||
let stubbedSimpleGit: {
|
||||
// Parameters<> only gets the last overload defined, which is
|
||||
// supposed to be the most permissive. However, SimpleGit defines
|
||||
// overloads in the opposite order, and we need the one that takes
|
||||
// a string.
|
||||
revparse: sinon.SinonStub<[option: string], R<'revparse'>>
|
||||
branch: sinon.SinonStub<P<'branch'>, R<'branch'>>
|
||||
status: sinon.SinonStub<P<'status'>, R<'status'>>
|
||||
log: sinon.SinonStub<P<'log'>, R<'log'>>
|
||||
}
|
||||
let stubbedWatchInstance: sinon.SinonStubbedInstance<Chokidar.FSWatcher>
|
||||
|
||||
let gitDataSourceImport: GDSImport
|
||||
let fakeTimers: sinon.SinonFakeTimers
|
||||
|
||||
beforeEach(() => {
|
||||
fakeTimers = sinon.useFakeTimers()
|
||||
stubbedSimpleGit = {
|
||||
revparse: sinon.stub<[option: string], R<'revparse'>>(),
|
||||
branch: sinon.stub<P<'branch'>, R<'branch'>>(),
|
||||
status: sinon.stub<P<'status'>, R<'status'>>(),
|
||||
log: sinon.stub<P<'log'>, R<'log'>>(),
|
||||
}
|
||||
jest.useFakeTimers()
|
||||
|
||||
stubbedWatchInstance = sinon.createStubInstance(Chokidar.FSWatcher)
|
||||
sinon.stub(Chokidar, 'watch').returns(stubbedWatchInstance)
|
||||
|
||||
gitDataSourceImport = proxyquire.noCallThru()('../../../src/sources/GitDataSource', {
|
||||
'simple-git' () {
|
||||
return stubbedSimpleGit
|
||||
},
|
||||
})
|
||||
// @ts-expect-error - incorrect type to stub
|
||||
jest.spyOn(Chokidar, 'watch').mockReturnValue(new EventEmitter())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
fakeTimers.restore()
|
||||
stubbedSimpleGit.log.mockReset()
|
||||
stubbedSimpleGit.revparse.mockReset()
|
||||
stubbedSimpleGit.branch.mockReset()
|
||||
stubbedSimpleGit.status.mockReset()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
describe('Unit', () => {
|
||||
@@ -66,10 +54,10 @@ describe('GitDataSource', () => {
|
||||
let gds: GitDataSource
|
||||
let projectRoot: string
|
||||
let branchName: string
|
||||
let onBranchChange: sinon.SinonStub<[branch: string | null], void>
|
||||
let onGitInfoChange: sinon.SinonStub<[specPath: string[]], void>
|
||||
let onError: sinon.SinonStub<[err: any], void>
|
||||
let onGitLogChange: sinon.SinonStub<[shas: string[]], void>
|
||||
let onBranchChange: jest.Mock<(branch: string | null) => void>
|
||||
let onGitInfoChange: jest.Mock<(specPath: string[]) => void>
|
||||
let onError: jest.Mock<(err: any) => void>
|
||||
let onGitLogChange: jest.Mock<(shas: string[]) => void>
|
||||
const firstHashes = [
|
||||
{ hash: 'abc' },
|
||||
]
|
||||
@@ -83,27 +71,21 @@ describe('GitDataSource', () => {
|
||||
firstGitLogCall = pDefer()
|
||||
secondGitLogCall = pDefer()
|
||||
branchName = 'main'
|
||||
onBranchChange = sinon.stub()
|
||||
onGitInfoChange = sinon.stub()
|
||||
onError = sinon.stub()
|
||||
onGitLogChange = sinon.stub()
|
||||
onBranchChange = jest.fn()
|
||||
onGitInfoChange = jest.fn()
|
||||
onError = jest.fn()
|
||||
onGitLogChange = jest.fn()
|
||||
|
||||
projectRoot = '/root'
|
||||
|
||||
// @ts-ignore
|
||||
stubbedSimpleGit.log.onFirstCall()
|
||||
// @ts-expect-error
|
||||
.callsFake(() => {
|
||||
stubbedSimpleGit.log.mockImplementationOnce((opts: P<'log'>) => {
|
||||
firstGitLogCall.resolve()
|
||||
|
||||
return { all: firstHashes }
|
||||
})
|
||||
.onSecondCall()
|
||||
// @ts-expect-error
|
||||
.callsFake(() => {
|
||||
return { all: firstHashes } as unknown as R<'log'>
|
||||
}).mockImplementationOnce((opts: P<'log'>) => {
|
||||
secondGitLogCall.resolve()
|
||||
|
||||
return { all: secondHashes }
|
||||
return { all: secondHashes } as unknown as R<'log'>
|
||||
})
|
||||
|
||||
// #verifyGitRepo
|
||||
@@ -111,12 +93,10 @@ describe('GitDataSource', () => {
|
||||
// constructor verifies the repo in open mode via #refreshAllGitData, but does not wait for it :womp:
|
||||
const revparseP = pDefer<void>()
|
||||
|
||||
// SimpleGit returns a chainable, but we only care about the promise
|
||||
// @ts-expect-error
|
||||
stubbedSimpleGit.revparse.callsFake(() => {
|
||||
stubbedSimpleGit.revparse.mockImplementationOnce((opts: string) => {
|
||||
revparseP.resolve()
|
||||
|
||||
return Promise.resolve(projectRoot)
|
||||
return Promise.resolve(projectRoot) as unknown as R<'revparse'>
|
||||
})
|
||||
|
||||
// wait for revparse to be called, so we can be assured that GitDataSource has initialized
|
||||
@@ -128,18 +108,17 @@ describe('GitDataSource', () => {
|
||||
const branchP = pDefer<void>()
|
||||
|
||||
// again, ignoring type warning re: chaining
|
||||
// @ts-expect-error
|
||||
stubbedSimpleGit.branch.callsFake(() => {
|
||||
stubbedSimpleGit.branch.mockImplementationOnce((opts: P<'branch'>) => {
|
||||
branchP.resolve()
|
||||
|
||||
return Promise.resolve({ current: branchName })
|
||||
return Promise.resolve({ current: branchName }) as unknown as R<'branch'>
|
||||
})
|
||||
|
||||
const onBranchChangeP = pDefer<void>()
|
||||
|
||||
onBranchChange.callsFake(() => onBranchChangeP.resolve())
|
||||
onBranchChange.mockImplementationOnce(() => onBranchChangeP.resolve())
|
||||
|
||||
gds = new gitDataSourceImport.GitDataSource({
|
||||
gds = new GitDataSource({
|
||||
isRunMode: false,
|
||||
projectRoot,
|
||||
onBranchChange,
|
||||
@@ -151,7 +130,7 @@ describe('GitDataSource', () => {
|
||||
await revparseP.promise
|
||||
await branchP.promise
|
||||
await onBranchChangeP.promise
|
||||
expect(onBranchChange).to.be.calledWith(branchName)
|
||||
expect(onBranchChange).toHaveBeenCalledWith(branchName)
|
||||
})
|
||||
|
||||
describe('.get currentHashes', () => {
|
||||
@@ -161,15 +140,15 @@ describe('GitDataSource', () => {
|
||||
})
|
||||
|
||||
it('returns the current hashes', () => {
|
||||
expect(gds.currentHashes).to.have.same.members(firstHashesReturnValue)
|
||||
expect(gds.currentHashes).toEqual(firstHashesReturnValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('after sixty seconds, when there are additional hashes', () => {
|
||||
it('returns the current hashes', async () => {
|
||||
await fakeTimers.tickAsync(60001)
|
||||
await jest.advanceTimersByTimeAsync(60001)
|
||||
await secondGitLogCall.promise
|
||||
expect(gds.currentHashes).to.have.same.members(secondHashesReturnValue)
|
||||
expect(gds.currentHashes).toEqual(secondHashesReturnValue)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach, afterEach } from '@jest/globals'
|
||||
import dedent from 'dedent'
|
||||
import path from 'path'
|
||||
import { execute, ExecutionResult, parse, subscribe } from 'graphql'
|
||||
import { DataContext } from '../../../src'
|
||||
import { createTestDataContext, scaffoldProject } from '../helper'
|
||||
@@ -40,6 +41,7 @@ describe('GraphQLDataSource', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(path.join(__dirname, '../../../'))
|
||||
pushFragmentIterator.return()
|
||||
ctx.destroy()
|
||||
})
|
||||
@@ -57,18 +59,18 @@ describe('GraphQLDataSource', () => {
|
||||
const result = await executeQuery(`{ cloudViewer { id } }`)
|
||||
|
||||
// Initial cloudViewer result returns null
|
||||
expect(result.data.cloudViewer).to.eq(null)
|
||||
expect(result.data.cloudViewer).toEqual(null)
|
||||
|
||||
const { target, data, fragment } = (await pushFragmentNextVal).data.pushFragment[0]
|
||||
|
||||
expect(target).to.eq('Query')
|
||||
expect(data).to.eql({
|
||||
expect(target).toEqual('Query')
|
||||
expect(data).toEqual({
|
||||
cloudViewer: {
|
||||
id: 'Q2xvdWRVc2VyOjE=',
|
||||
},
|
||||
})
|
||||
|
||||
expect(fragment.trim()).to.eq(dedent`
|
||||
expect(fragment.trim()).toEqual(dedent`
|
||||
fragment GeneratedFragment on Query {
|
||||
cloudViewer {
|
||||
id
|
||||
@@ -83,12 +85,12 @@ describe('GraphQLDataSource', () => {
|
||||
const result = await executeQuery(`{ currentProject { id cloudProject { __typename ... on CloudProject { id name } } } }`)
|
||||
|
||||
// Initial cloudProject result returns null
|
||||
expect(result.data.currentProject.cloudProject).to.eq(null)
|
||||
expect(result.data.currentProject.cloudProject).toBeNull()
|
||||
|
||||
const { target, data, fragment } = (await pushFragmentNextVal).data.pushFragment[0]
|
||||
|
||||
expect(target).to.eq('CurrentProject')
|
||||
expect(data).to.eql({
|
||||
expect(target).toEqual('CurrentProject')
|
||||
expect(data).toEqual({
|
||||
__typename: 'CurrentProject',
|
||||
cloudProject: {
|
||||
__typename: 'CloudProject',
|
||||
@@ -98,7 +100,7 @@ describe('GraphQLDataSource', () => {
|
||||
id: Buffer.from(`CurrentProject:${projectPath}`, 'utf8').toString('base64'),
|
||||
})
|
||||
|
||||
expect(fragment.trim()).to.eq(dedent`
|
||||
expect(fragment.trim()).toEqual(dedent`
|
||||
fragment GeneratedFragment on CurrentProject {
|
||||
id
|
||||
cloudProject {
|
||||
@@ -116,7 +118,7 @@ describe('GraphQLDataSource', () => {
|
||||
const result = await executeQuery(`{ cloudViewer { id } }`)
|
||||
|
||||
// Initial cloudProject result returns null
|
||||
expect(result.data.cloudViewer).to.eql(null)
|
||||
expect(result.data.cloudViewer).toBeNull()
|
||||
await pushFragmentNextVal
|
||||
|
||||
pushFragmentNextVal = pushFragmentIterator.next().then(({ value }) => value)
|
||||
@@ -124,19 +126,19 @@ describe('GraphQLDataSource', () => {
|
||||
const result2 = await executeQuery(`{ cloudViewer { id cloudOrganizationsUrl } }`)
|
||||
|
||||
// Initial cloudProject result returns null
|
||||
expect(result2.data.cloudViewer).to.eql({ id: 'Q2xvdWRVc2VyOjE=', cloudOrganizationsUrl: null })
|
||||
expect(result2.data.cloudViewer).toEqual({ id: 'Q2xvdWRVc2VyOjE=', cloudOrganizationsUrl: null })
|
||||
|
||||
const { target, data, fragment } = (await pushFragmentNextVal).data.pushFragment[0]
|
||||
|
||||
expect(target).to.eq('Query')
|
||||
expect(data).to.eql({
|
||||
expect(target).toEqual('Query')
|
||||
expect(data).toEqual({
|
||||
cloudViewer: {
|
||||
id: 'Q2xvdWRVc2VyOjE=',
|
||||
cloudOrganizationsUrl: 'http://dummy.cypress.io/organizations',
|
||||
},
|
||||
})
|
||||
|
||||
expect(fragment.trim()).to.eq(dedent`
|
||||
expect(fragment.trim()).toEqual(dedent`
|
||||
fragment GeneratedFragment on Query {
|
||||
cloudViewer {
|
||||
id
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import chai from 'chai'
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import os from 'os'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import { matchedSpecs, transformSpec, SpecWithRelativeRoot, getLongestCommonPrefixFromPaths, getPathFromSpecPattern } from '../../../src/sources'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import chokidar from 'chokidar'
|
||||
import _ from 'lodash'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import { FoundSpec } from '@packages/types'
|
||||
import { DataContext } from '../../../src'
|
||||
import type { FindSpecs } from '../../../src/actions'
|
||||
@@ -15,9 +13,6 @@ import { createTestDataContext } from '../helper'
|
||||
import { defaultExcludeSpecPattern, defaultSpecPattern } from '@packages/config'
|
||||
import FixturesHelper from '@tooling/system-tests'
|
||||
|
||||
chai.use(sinonChai)
|
||||
const { expect } = chai
|
||||
|
||||
function delay (ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
@@ -25,7 +20,7 @@ function delay (ms) {
|
||||
}
|
||||
|
||||
describe('matchedSpecs', () => {
|
||||
context('got a single spec pattern from --spec via cli', () => {
|
||||
describe('got a single spec pattern from --spec via cli', () => {
|
||||
it('returns spec name only', () => {
|
||||
const result = matchedSpecs({
|
||||
projectRoot: '/var/folders/T/cy-projects/e2e',
|
||||
@@ -48,11 +43,11 @@ describe('matchedSpecs', () => {
|
||||
specType: 'integration',
|
||||
}]
|
||||
|
||||
expect(result).to.eql(actual)
|
||||
expect(result).toEqual(actual)
|
||||
})
|
||||
})
|
||||
|
||||
context('got a multi spec pattern from --spec via cli', () => {
|
||||
describe('got a multi spec pattern from --spec via cli', () => {
|
||||
it('removes all common path', () => {
|
||||
const result = matchedSpecs({
|
||||
projectRoot: '/var/folders/T/cy-projects/e2e',
|
||||
@@ -71,14 +66,14 @@ describe('matchedSpecs', () => {
|
||||
],
|
||||
})
|
||||
|
||||
expect(result[0].relativeToCommonRoot).to.eq('simple_passing_spec.js')
|
||||
expect(result[1].relativeToCommonRoot).to.eq('simple_hooks_spec.js')
|
||||
expect(result[2].relativeToCommonRoot).to.eq('simple_failing_spec.js')
|
||||
expect(result[3].relativeToCommonRoot).to.eq('simple_failing_hook_spec.js')
|
||||
expect(result[0].relativeToCommonRoot).toEqual('simple_passing_spec.js')
|
||||
expect(result[1].relativeToCommonRoot).toEqual('simple_hooks_spec.js')
|
||||
expect(result[2].relativeToCommonRoot).toEqual('simple_failing_spec.js')
|
||||
expect(result[3].relativeToCommonRoot).toEqual('simple_failing_hook_spec.js')
|
||||
})
|
||||
})
|
||||
|
||||
context('generic glob from config', () => {
|
||||
describe('generic glob from config', () => {
|
||||
it('infers common path from glob and returns spec name', () => {
|
||||
const result = matchedSpecs({
|
||||
projectRoot: '/Users/lachlan/code/work/cypress6/packages/app',
|
||||
@@ -90,12 +85,12 @@ describe('matchedSpecs', () => {
|
||||
specPattern: 'cypress/e2e/integration/**/*.spec.ts',
|
||||
})
|
||||
|
||||
expect(result[0].relativeToCommonRoot).to.eq('files.spec.ts')
|
||||
expect(result[1].relativeToCommonRoot).to.eq('index.spec.ts')
|
||||
expect(result[0].relativeToCommonRoot).toEqual('files.spec.ts')
|
||||
expect(result[1].relativeToCommonRoot).toEqual('index.spec.ts')
|
||||
})
|
||||
})
|
||||
|
||||
context('deeply nested test', () => {
|
||||
describe('deeply nested test', () => {
|
||||
it('removes superfluous leading directories', () => {
|
||||
const result = matchedSpecs({
|
||||
projectRoot: '/var/folders/y5/T/cy-projects/e2e',
|
||||
@@ -106,7 +101,7 @@ describe('matchedSpecs', () => {
|
||||
specPattern: '/var/folders/y5/T/cy-projects/e2e/cypress/integration/nested-1/nested-2/screenshot_nested_file_spec.js',
|
||||
})
|
||||
|
||||
expect(result[0].relativeToCommonRoot).to.eq('screenshot_nested_file_spec.js')
|
||||
expect(result[0].relativeToCommonRoot).toEqual('screenshot_nested_file_spec.js')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -134,7 +129,7 @@ describe('transformSpec', () => {
|
||||
relativeToCommonRoot: 'C:/Windows/Project/src/spec.cy.js',
|
||||
}
|
||||
|
||||
expect(result).to.eql(actual)
|
||||
expect(result).toEqual(actual)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -176,7 +171,7 @@ describe('findSpecs', () => {
|
||||
additionalIgnorePattern: [],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(3)
|
||||
expect(specs).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('find all the *.cy.{ts,js} excluding the e2e', async () => {
|
||||
@@ -189,7 +184,7 @@ describe('findSpecs', () => {
|
||||
additionalIgnorePattern: ['e2e/*.{spec,cy}.{ts,js}'],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(2)
|
||||
expect(specs).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('find all the *.{cy,spec}.{ts,js} excluding the e2e', async () => {
|
||||
@@ -202,7 +197,7 @@ describe('findSpecs', () => {
|
||||
additionalIgnorePattern: ['e2e/*.{spec,cy}.{ts,js}'],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(3)
|
||||
expect(specs).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('find all the e2e specs', async () => {
|
||||
@@ -215,7 +210,7 @@ describe('findSpecs', () => {
|
||||
additionalIgnorePattern: [],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(3)
|
||||
expect(specs).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('ignores node_modules if excludeSpecPattern is empty array', async () => {
|
||||
@@ -228,7 +223,7 @@ describe('findSpecs', () => {
|
||||
additionalIgnorePattern: [],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(6)
|
||||
expect(specs).toHaveLength(6)
|
||||
})
|
||||
|
||||
it('ignores e2e tests if additionalIgnorePattern is set', async () => {
|
||||
@@ -241,7 +236,7 @@ describe('findSpecs', () => {
|
||||
excludeSpecPattern: [],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(3)
|
||||
expect(specs).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('respects excludeSpecPattern', async () => {
|
||||
@@ -254,7 +249,7 @@ describe('findSpecs', () => {
|
||||
excludeSpecPattern: ['**/*'],
|
||||
})
|
||||
|
||||
expect(specs).to.have.length(0)
|
||||
expect(specs).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -265,7 +260,7 @@ describe('getLongestCommonPrefixFromPaths', () => {
|
||||
'cypress/component/bar/meta-component-test.cy.ts',
|
||||
])
|
||||
|
||||
expect(lcp).to.equal('cypress/component')
|
||||
expect(lcp).toEqual('cypress/component')
|
||||
})
|
||||
|
||||
it('with src and cypress', () => {
|
||||
@@ -275,7 +270,7 @@ describe('getLongestCommonPrefixFromPaths', () => {
|
||||
'src/frontend/MyComponent.cy.ts',
|
||||
])
|
||||
|
||||
expect(lcp).to.equal('')
|
||||
expect(lcp).toEqual('')
|
||||
})
|
||||
|
||||
it('with src', () => {
|
||||
@@ -284,7 +279,7 @@ describe('getLongestCommonPrefixFromPaths', () => {
|
||||
'src/MyComponent.cy.ts',
|
||||
])
|
||||
|
||||
expect(lcp).to.equal('src')
|
||||
expect(lcp).toEqual('src')
|
||||
})
|
||||
|
||||
it('with 1 path', () => {
|
||||
@@ -292,133 +287,133 @@ describe('getLongestCommonPrefixFromPaths', () => {
|
||||
'src/frontend/MyComponent.cy.ts',
|
||||
])
|
||||
|
||||
expect(lcp).to.equal('src/frontend')
|
||||
expect(lcp).toEqual('src/frontend')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getPathFromSpecPattern', () => {
|
||||
context('dirname', () => {
|
||||
describe('dirname', () => {
|
||||
it('returns pattern without change if it is do not a glob', () => {
|
||||
const specPattern = 'cypress/e2e/foo.spec.ts'
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern, testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq(specPattern)
|
||||
expect(defaultFileName).toEqual(specPattern)
|
||||
})
|
||||
|
||||
it('remove ** from glob if it is not in the beginning', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/**/foo.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/foo.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/foo.spec.ts')
|
||||
})
|
||||
|
||||
it('replace ** for cypress if it starts with **', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: '**/e2e/foo.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/foo.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/foo.spec.ts')
|
||||
})
|
||||
|
||||
it('replace ** for cypress if it starts with ** and omit extra **', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: '**/**/foo.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/foo.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/foo.spec.ts')
|
||||
})
|
||||
|
||||
it('selects first option if there are multiples possibilities of values', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: '{cypress,tests}/{integration,e2e}/foo.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/integration/foo.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/integration/foo.spec.ts')
|
||||
})
|
||||
})
|
||||
|
||||
context('filename', () => {
|
||||
describe('filename', () => {
|
||||
it('replace * for filename', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/*.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/spec.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/spec.spec.ts')
|
||||
})
|
||||
|
||||
it('selects first option if there are multiples possibilities of values', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/{foo,filename}.spec.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/foo.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/foo.spec.ts')
|
||||
})
|
||||
})
|
||||
|
||||
context('test extension', () => {
|
||||
describe('test extension', () => {
|
||||
it('replace * for filename', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.*.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.cy.ts')
|
||||
})
|
||||
|
||||
it('selects first option if there are multiples possibilities of values', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.{spec,cy}.ts', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.spec.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.spec.ts')
|
||||
})
|
||||
})
|
||||
|
||||
context('lang extension', () => {
|
||||
describe('lang extension', () => {
|
||||
it('if project use TS, set TS as extension if it exists in the glob', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.ts', testingType: 'e2e', fileExtensionToUse: 'ts' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.cy.ts')
|
||||
})
|
||||
|
||||
it('if project use TS, set TS as extension if it exists in the options of extensions', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{js,ts,tsx}', testingType: 'e2e', fileExtensionToUse: 'ts' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.cy.ts')
|
||||
})
|
||||
|
||||
it('if project use TS, do not set TS as extension if it do not exists in the options of extensions', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{js,jsx}', testingType: 'e2e', fileExtensionToUse: 'ts' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.cy.js')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.cy.js')
|
||||
})
|
||||
|
||||
it('selects first option if there are multiples possibilities of values', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{ts,js}', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts')
|
||||
expect(defaultFileName).toEqual('cypress/e2e/filename.cy.ts')
|
||||
})
|
||||
})
|
||||
|
||||
context('extra cases', () => {
|
||||
describe('extra cases', () => {
|
||||
it('creates specName for tests/*.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'tests/*.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('tests/spec.js')
|
||||
expect(defaultFileName).toEqual('tests/spec.js')
|
||||
})
|
||||
|
||||
it('creates specName for src/*-test.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*-test.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('src/spec-test.js')
|
||||
expect(defaultFileName).toEqual('src/spec-test.js')
|
||||
})
|
||||
|
||||
it('creates specName for src/*.foo.bar.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*.foo.bar.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('src/spec.foo.bar.js')
|
||||
expect(defaultFileName).toEqual('src/spec.foo.bar.js')
|
||||
})
|
||||
|
||||
it('creates specName for src/prefix.*.test.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/prefix.*.test.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('src/prefix.cy.test.js')
|
||||
expect(defaultFileName).toEqual('src/prefix.cy.test.js')
|
||||
})
|
||||
|
||||
it('creates specName for src/*/*.test.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*/*.test.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('src/e2e/spec.test.js')
|
||||
expect(defaultFileName).toEqual('src/e2e/spec.test.js')
|
||||
})
|
||||
|
||||
it('creates specName for src-*/**/*.test.js', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'src-*/**/*.test.js', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('src-e2e/spec.test.js')
|
||||
expect(defaultFileName).toEqual('src-e2e/spec.test.js')
|
||||
})
|
||||
|
||||
it('creates specName for src/*.test.(js|jsx)', () => {
|
||||
@@ -426,7 +421,7 @@ describe('getPathFromSpecPattern', () => {
|
||||
|
||||
const possiblesFileNames = ['src/ComponentName.test.jsx', 'src/ComponentName.test.js']
|
||||
|
||||
expect(possiblesFileNames.includes(defaultFileName)).to.eq(true)
|
||||
expect(possiblesFileNames.includes(defaultFileName)).toEqual(true)
|
||||
})
|
||||
|
||||
it('creates specName for (src|components)/**/*.test.js', () => {
|
||||
@@ -434,19 +429,19 @@ describe('getPathFromSpecPattern', () => {
|
||||
|
||||
const possiblesFileNames = ['src/ComponentName.test.js', 'components/ComponentName.test.js']
|
||||
|
||||
expect(possiblesFileNames.includes(defaultFileName)).to.eq(true)
|
||||
expect(possiblesFileNames.includes(defaultFileName)).toEqual(true)
|
||||
})
|
||||
|
||||
it('creates specName for e2e/**/*.cy.{js,jsx,ts,tsx}', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'e2e/**/*.cy.{js,jsx,ts,tsx}', testingType: 'e2e' })
|
||||
|
||||
expect(defaultFileName).to.eq('e2e/spec.cy.js')
|
||||
expect(defaultFileName).toEqual('e2e/spec.cy.js')
|
||||
})
|
||||
|
||||
it('creates specName for cypress/component-tests/**/*', () => {
|
||||
const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/component-tests/**/*', testingType: 'component', fileExtensionToUse: 'ts' })
|
||||
|
||||
expect(defaultFileName).to.eq('cypress/component-tests/ComponentName.cy.ts')
|
||||
expect(defaultFileName).toEqual('cypress/component-tests/ComponentName.cy.ts')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -454,18 +449,17 @@ describe('getPathFromSpecPattern', () => {
|
||||
describe('_makeSpecWatcher', () => {
|
||||
let ctx: DataContext
|
||||
let specWatcher: chokidar.FSWatcher
|
||||
let specWatcherPath: string
|
||||
|
||||
beforeEach(async function () {
|
||||
this.timeout(20000) // fixture cleanup can take awhile
|
||||
FixturesHelper.remove()
|
||||
|
||||
this.specWatcherPath = await FixturesHelper.scaffoldProject('spec-watcher')
|
||||
specWatcherPath = await FixturesHelper.scaffoldProject('spec-watcher')
|
||||
|
||||
ctx = createTestDataContext('open', { projectRoot: this.specWatcherPath })
|
||||
})
|
||||
ctx = createTestDataContext('open', { projectRoot: specWatcherPath })
|
||||
}, 20000)
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore()
|
||||
await specWatcher.close()
|
||||
ctx.destroy()
|
||||
})
|
||||
@@ -488,7 +482,7 @@ describe('_makeSpecWatcher', () => {
|
||||
|
||||
it('watch for changes on files based on the specPattern', async function () {
|
||||
specWatcher = ctx.project._makeSpecWatcher({
|
||||
projectRoot: this.specWatcherPath,
|
||||
projectRoot: specWatcherPath,
|
||||
specPattern: ['**/*.{cy,spec}.{ts,js}'],
|
||||
excludeSpecPattern: ['**/ignore.spec.ts'],
|
||||
additionalIgnorePattern: ['additional.ignore.cy.js'],
|
||||
@@ -509,18 +503,18 @@ describe('_makeSpecWatcher', () => {
|
||||
await delay(10)
|
||||
}
|
||||
|
||||
expect(Array.from(allFiles).sort()).to.eql([
|
||||
expect(Array.from(allFiles).sort()).toEqual([
|
||||
SPEC_FILE1,
|
||||
SPEC_FILE2,
|
||||
SPEC_FILE3,
|
||||
])
|
||||
|
||||
expect(Array.from(allFiles)).to.not.include(SUPPORT_FILE)
|
||||
expect(Array.from(allFiles)).not.toContain(SUPPORT_FILE)
|
||||
})
|
||||
|
||||
it('watch for changes on files with multiple specPatterns', async function () {
|
||||
specWatcher = ctx.project._makeSpecWatcher({
|
||||
projectRoot: this.specWatcherPath,
|
||||
projectRoot: specWatcherPath,
|
||||
specPattern: ['**/*.{cy,spec}.{ts,js}', '**/abc.ts'],
|
||||
excludeSpecPattern: ['**/ignore.spec.ts'],
|
||||
additionalIgnorePattern: ['additional.ignore.cy.js'],
|
||||
@@ -541,19 +535,19 @@ describe('_makeSpecWatcher', () => {
|
||||
await delay(10)
|
||||
}
|
||||
|
||||
expect(Array.from(allFiles).sort()).to.eql([
|
||||
expect(Array.from(allFiles).sort()).toEqual([
|
||||
SPEC_FILE1,
|
||||
SPEC_FILE_ABC,
|
||||
SPEC_FILE2,
|
||||
SPEC_FILE3,
|
||||
])
|
||||
|
||||
expect(Array.from(allFiles)).to.not.include(SUPPORT_FILE)
|
||||
expect(Array.from(allFiles)).not.toContain(SUPPORT_FILE)
|
||||
})
|
||||
|
||||
it('do not throw if file/folder is deleted while ignoring files', async function () {
|
||||
specWatcher = ctx.project._makeSpecWatcher({
|
||||
projectRoot: this.specWatcherPath,
|
||||
projectRoot: specWatcherPath,
|
||||
specPattern: ['**/*.{cy,spec}.{ts,js}', '**/abc.ts'],
|
||||
excludeSpecPattern: ['**/ignore.spec.ts'],
|
||||
additionalIgnorePattern: ['additional.ignore.cy.js'],
|
||||
@@ -571,14 +565,14 @@ describe('_makeSpecWatcher', () => {
|
||||
await ctx.actions.file.removeFileInProject(SPEC_FILE1)
|
||||
await delay(1000)
|
||||
|
||||
expect(Array.from(allFiles).sort()).to.eql([
|
||||
expect(Array.from(allFiles).sort()).toEqual([
|
||||
SPEC_FILE_ABC,
|
||||
SPEC_FILE2,
|
||||
SPEC_FILE3,
|
||||
])
|
||||
|
||||
expect(Array.from(allFiles)).to.not.include(SPEC_FILE1)
|
||||
expect(Array.from(allFiles)).to.not.include(SUPPORT_FILE)
|
||||
expect(Array.from(allFiles)).not.toContain(SPEC_FILE1)
|
||||
expect(Array.from(allFiles)).not.toContain(SUPPORT_FILE)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -587,10 +581,6 @@ describe('startSpecWatcher', () => {
|
||||
|
||||
let ctx: DataContext
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
describe('run mode', () => {
|
||||
beforeEach(async () => {
|
||||
ctx = createTestDataContext('run')
|
||||
@@ -599,9 +589,9 @@ describe('startSpecWatcher', () => {
|
||||
})
|
||||
|
||||
it('early return specWatcher', () => {
|
||||
const onStub = sinon.stub()
|
||||
const onStub = jest.fn()
|
||||
|
||||
sinon.stub(chokidar, 'watch').callsFake(() => {
|
||||
jest.spyOn(chokidar, 'watch').mockImplementation(() => {
|
||||
const mockWatcher = {
|
||||
on: onStub,
|
||||
close: () => ({ catch: () => {} }),
|
||||
@@ -612,7 +602,7 @@ describe('startSpecWatcher', () => {
|
||||
|
||||
let handleFsChange
|
||||
|
||||
sinon.stub(_, 'debounce').callsFake((funcToDebounce) => {
|
||||
jest.spyOn(_, 'debounce').mockImplementation((funcToDebounce) => {
|
||||
handleFsChange = (() => funcToDebounce())
|
||||
|
||||
return handleFsChange as _.DebouncedFunc<any>
|
||||
@@ -627,11 +617,11 @@ describe('startSpecWatcher', () => {
|
||||
additionalIgnorePattern: ['additional.ignore.cy.js'],
|
||||
})
|
||||
|
||||
expect(_.debounce).to.have.not.been.called
|
||||
expect(_.debounce).not.toHaveBeenCalled()
|
||||
|
||||
expect(chokidar.watch).to.have.not.been.called
|
||||
expect(chokidar.watch).not.toHaveBeenCalled()
|
||||
|
||||
expect(onStub).to.have.not.been.called
|
||||
expect(onStub).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -662,9 +652,9 @@ describe('startSpecWatcher', () => {
|
||||
})
|
||||
|
||||
it('creates file watcher based on given config properties', async () => {
|
||||
const onStub = sinon.stub()
|
||||
const onStub = jest.fn()
|
||||
|
||||
sinon.stub(chokidar, 'watch').callsFake(() => {
|
||||
jest.spyOn(chokidar, 'watch').mockImplementation(() => {
|
||||
const mockWatcher = {
|
||||
on: onStub,
|
||||
close: () => ({ catch: () => {} }),
|
||||
@@ -675,7 +665,7 @@ describe('startSpecWatcher', () => {
|
||||
|
||||
let handleFsChange
|
||||
|
||||
sinon.stub(_, 'debounce').callsFake((funcToDebounce) => {
|
||||
jest.spyOn(_, 'debounce').mockImplementation((funcToDebounce) => {
|
||||
handleFsChange = (() => funcToDebounce())
|
||||
|
||||
return handleFsChange as _.DebouncedFunc<any>
|
||||
@@ -690,16 +680,16 @@ describe('startSpecWatcher', () => {
|
||||
additionalIgnorePattern: ['additional.ignore.cy.js'],
|
||||
})
|
||||
|
||||
expect(_.debounce).to.have.been.calledWith(sinon.match.func, 250)
|
||||
expect(_.debounce).toHaveBeenCalledWith(expect.any(Function), 250)
|
||||
|
||||
expect(chokidar.watch).to.have.been.calledWith('.', {
|
||||
expect(chokidar.watch).toHaveBeenCalledWith('.', {
|
||||
ignoreInitial: true,
|
||||
cwd: projectRoot,
|
||||
ignored: ['**/node_modules/**', '**/ignore.spec.ts', 'additional.ignore.cy.js', sinon.match.func],
|
||||
ignored: ['**/node_modules/**', '**/ignore.spec.ts', 'additional.ignore.cy.js', expect.any(Function)],
|
||||
ignorePermissionErrors: true,
|
||||
})
|
||||
|
||||
expect(onStub).to.have.been.calledWith('all', handleFsChange)
|
||||
expect(onStub).toHaveBeenCalledWith('all', handleFsChange)
|
||||
})
|
||||
|
||||
it('implements change handler with duplicate result handling', async () => {
|
||||
@@ -709,10 +699,11 @@ describe('startSpecWatcher', () => {
|
||||
{ name: 'test-3.cy.js' },
|
||||
] as FoundSpec[]
|
||||
|
||||
sinon.stub(ctx.project, 'findSpecs').resolves(mockFoundSpecs)
|
||||
sinon.stub(ctx.actions.project, 'setSpecs')
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.project, 'findSpecs').mockResolvedValue(mockFoundSpecs)
|
||||
jest.spyOn(ctx.actions.project, 'setSpecs')
|
||||
|
||||
sinon.stub(chokidar, 'watch').callsFake(() => {
|
||||
jest.spyOn(chokidar, 'watch').mockImplementation(() => {
|
||||
const mockWatcher = {
|
||||
on: () => {},
|
||||
close: () => ({ catch: () => {} }),
|
||||
@@ -723,7 +714,7 @@ describe('startSpecWatcher', () => {
|
||||
|
||||
let handleFsChange
|
||||
|
||||
sinon.stub(_, 'debounce').callsFake((funcToDebounce) => {
|
||||
jest.spyOn(_, 'debounce').mockImplementation((funcToDebounce) => {
|
||||
handleFsChange = (() => funcToDebounce())
|
||||
|
||||
return handleFsChange as _.DebouncedFunc<any>
|
||||
@@ -741,22 +732,25 @@ describe('startSpecWatcher', () => {
|
||||
await ctx.project.startSpecWatcher(watchOptions)
|
||||
|
||||
// Set internal specs state to the stubbed found value to simulate irrelevant FS changes
|
||||
// @ts-expect-error
|
||||
ctx.project.setSpecs(mockFoundSpecs)
|
||||
|
||||
await handleFsChange()
|
||||
|
||||
expect(ctx.project.findSpecs).to.have.been.calledWith(watchOptions)
|
||||
expect(ctx.actions.project.setSpecs).not.to.have.been.called
|
||||
expect(ctx.project.findSpecs).toHaveBeenCalledWith(watchOptions)
|
||||
expect(ctx.actions.project.setSpecs).not.toHaveBeenCalled()
|
||||
|
||||
// Update internal specs state so that a change will be detected on next FS event
|
||||
const updatedSpecs = [...mockFoundSpecs, { name: 'test-4.cy.js' }] as FoundSpec[]
|
||||
|
||||
// @ts-expect-error
|
||||
ctx.project.setSpecs(updatedSpecs)
|
||||
|
||||
await handleFsChange()
|
||||
|
||||
expect(ctx.project.findSpecs).to.have.been.calledWith(watchOptions)
|
||||
expect(ctx.actions.project.setSpecs).to.have.been.calledWith(mockFoundSpecs)
|
||||
expect(ctx.project.findSpecs).toHaveBeenCalledWith(watchOptions)
|
||||
// @ts-expect-error
|
||||
expect(ctx.actions.project.setSpecs).toHaveBeenCalledWith(mockFoundSpecs)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -770,24 +764,25 @@ describe('ProjectDataSource', () => {
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
})
|
||||
|
||||
context('#defaultSpecFilename', () => {
|
||||
describe('#defaultSpecFilename', () => {
|
||||
it('yields default if no spec pattern is set', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: [] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: [] })
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/e2e/spec.cy.js')
|
||||
expect(defaultSpecFileName).toEqual('cypress/e2e/spec.cy.js')
|
||||
})
|
||||
|
||||
it('yields default if the spec pattern is default', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: [defaultSpecPattern.e2e] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: [defaultSpecPattern.e2e] })
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/e2e/spec.cy.js')
|
||||
expect(defaultSpecFileName).toEqual('cypress/e2e/spec.cy.js')
|
||||
})
|
||||
|
||||
it('yields common prefix if there are existing specs', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: ['cypress/e2e/**/*'] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: ['cypress/e2e/**/*'] })
|
||||
// @ts-expect-error
|
||||
ctx.project.setSpecs([
|
||||
{ relative: 'cypress/e2e/foo/spec.js' },
|
||||
{ relative: 'cypress/e2e/foo/bar/spec.js' },
|
||||
@@ -795,20 +790,23 @@ describe('ProjectDataSource', () => {
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/e2e/foo/spec.cy.js')
|
||||
expect(defaultSpecFileName).toEqual('cypress/e2e/foo/spec.cy.js')
|
||||
})
|
||||
|
||||
it('yields spec pattern guess if there are no existing specs', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: ['cypress/integration/**/*'] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: ['cypress/integration/**/*'] })
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/integration/spec.cy.js')
|
||||
expect(defaultSpecFileName).toEqual('cypress/integration/spec.cy.js')
|
||||
})
|
||||
|
||||
it('yields correct filename from specpattern if there are existing specs', async () => {
|
||||
jest.spyOn(ctx.lifecycleManager, 'getConfigFileContents').mockResolvedValue({})
|
||||
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: ['cypress/component-tests/*.spec.js'] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: ['cypress/component-tests/*.spec.js'] })
|
||||
// @ts-expect-error
|
||||
ctx.project.setSpecs([
|
||||
{ relative: 'cypress/component-tests/foo/spec.spec.js' },
|
||||
{ relative: 'cypress/component-tests/foo/spec2.spec.js' },
|
||||
@@ -816,7 +814,7 @@ describe('ProjectDataSource', () => {
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/component-tests/foo/ComponentName.spec.js')
|
||||
expect(defaultSpecFileName).toEqual('cypress/component-tests/foo/ComponentName.spec.js')
|
||||
})
|
||||
|
||||
describe('jsx/tsx handling', () => {
|
||||
@@ -826,30 +824,30 @@ describe('ProjectDataSource', () => {
|
||||
})
|
||||
|
||||
it('yields correct jsx extension if there are jsx files and specPattern allows', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: [defaultSpecPattern.component] })
|
||||
sinon.stub(ctx.project, 'specPatternsByTestingType').resolves({ specPattern: [defaultSpecPattern.component] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: [defaultSpecPattern.component] })
|
||||
jest.spyOn(ctx.project, 'specPatternsByTestingType').mockResolvedValue({ specPattern: [defaultSpecPattern.component] })
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
expect(defaultSpecFileName).to.equal('cypress/component/ComponentName.cy.jsx', defaultSpecFileName)
|
||||
expect(defaultSpecFileName).toEqual('cypress/component/ComponentName.cy.jsx', defaultSpecFileName)
|
||||
})
|
||||
|
||||
it('yields non-jsx extension if there are jsx files but specPattern disallows', async () => {
|
||||
sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: ['cypress/component/*.cy.js'] })
|
||||
sinon.stub(ctx.project, 'specPatternsByTestingType').resolves({ specPattern: ['cypress/component/*.cy.js'] })
|
||||
jest.spyOn(ctx.project, 'specPatterns').mockResolvedValue({ specPattern: ['cypress/component/*.cy.js'] })
|
||||
jest.spyOn(ctx.project, 'specPatternsByTestingType').mockResolvedValue({ specPattern: ['cypress/component/*.cy.js'] })
|
||||
|
||||
const defaultSpecFileName = await ctx.project.defaultSpecFileName()
|
||||
|
||||
// specPattern does not allow for jsx, so generated spec name should not use jsx extension
|
||||
expect(defaultSpecFileName).to.equal('cypress/component/ComponentName.cy.js', defaultSpecFileName)
|
||||
expect(defaultSpecFileName).toEqual('cypress/component/ComponentName.cy.js', defaultSpecFileName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('specPatternsByTestingType', () => {
|
||||
context('when custom patterns configured', () => {
|
||||
describe('when custom patterns configured', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(ctx.lifecycleManager, 'getConfigFileContents').resolves({
|
||||
jest.spyOn(ctx.lifecycleManager, 'getConfigFileContents').mockResolvedValue({
|
||||
e2e: {
|
||||
specPattern: 'abc',
|
||||
excludeSpecPattern: 'def',
|
||||
@@ -862,38 +860,38 @@ describe('ProjectDataSource', () => {
|
||||
})
|
||||
|
||||
it('should return custom e2e patterns', async () => {
|
||||
expect(await ctx.project.specPatternsByTestingType('e2e')).to.eql({
|
||||
expect(await ctx.project.specPatternsByTestingType('e2e')).toEqual({
|
||||
specPattern: ['abc'],
|
||||
excludeSpecPattern: ['def'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return custom component patterns', async () => {
|
||||
expect(await ctx.project.specPatternsByTestingType('component')).to.eql({
|
||||
expect(await ctx.project.specPatternsByTestingType('component')).toEqual({
|
||||
specPattern: ['uvw'],
|
||||
excludeSpecPattern: ['xyz'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('when no custom patterns configured', () => {
|
||||
describe('when no custom patterns configured', () => {
|
||||
const wrapInArray = (value: string | string[]): string[] => {
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(ctx.lifecycleManager, 'getConfigFileContents').resolves({})
|
||||
jest.spyOn(ctx.lifecycleManager, 'getConfigFileContents').mockResolvedValue({})
|
||||
})
|
||||
|
||||
it('should return default e2e patterns', async () => {
|
||||
expect(await ctx.project.specPatternsByTestingType('e2e')).to.eql({
|
||||
expect(await ctx.project.specPatternsByTestingType('e2e')).toEqual({
|
||||
specPattern: wrapInArray(defaultSpecPattern.e2e),
|
||||
excludeSpecPattern: wrapInArray(defaultExcludeSpecPattern.e2e),
|
||||
})
|
||||
})
|
||||
|
||||
it('should return default component patterns', async () => {
|
||||
expect(await ctx.project.specPatternsByTestingType('component')).to.eql({
|
||||
expect(await ctx.project.specPatternsByTestingType('component')).toEqual({
|
||||
specPattern: wrapInArray(defaultSpecPattern.component),
|
||||
excludeSpecPattern: wrapInArray(defaultExcludeSpecPattern.component),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import chai from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import debugLib from 'debug'
|
||||
import { GraphQLInt, GraphQLString, print } from 'graphql'
|
||||
|
||||
@@ -11,9 +9,6 @@ import { FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC } from './fixtures/graphqlFixture
|
||||
import { createGraphQL } from '../helper-graphql'
|
||||
import dedent from 'dedent'
|
||||
|
||||
chai.use(sinonChai)
|
||||
|
||||
const { expect } = chai
|
||||
const debug = debugLib('cypress:data-context:test:sources:RelevantRunSpecsDataSource')
|
||||
|
||||
describe('RelevantRunSpecsDataSource', () => {
|
||||
@@ -23,38 +18,37 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
beforeEach(() => {
|
||||
ctx = createTestDataContext('open')
|
||||
dataSource = new RelevantRunSpecsDataSource(ctx)
|
||||
sinon.stub(ctx.project, 'projectId').resolves('test123')
|
||||
jest.spyOn(ctx.project, 'projectId').mockResolvedValue('test123')
|
||||
})
|
||||
|
||||
describe('getRelevantRunSpecs()', () => {
|
||||
it('returns no specs or statuses when no specs found for run', async () => {
|
||||
const result = await dataSource.getRelevantRunSpecs([])
|
||||
|
||||
expect(result).to.eql([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('returns the runs the cloud sends and sets the polling interval', async () => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC)
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockResolvedValue(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC)
|
||||
|
||||
expect(dataSource.pollingInterval).to.eql(15)
|
||||
expect(dataSource.pollingInterval).toEqual(15)
|
||||
|
||||
const result = await dataSource.getRelevantRunSpecs(['fake-id'])
|
||||
|
||||
expect(result).to.eql(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC.data.cloudNodesByIds)
|
||||
expect(result).toEqual(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC.data.cloudNodesByIds)
|
||||
|
||||
expect(dataSource.pollingInterval).to.eql(20)
|
||||
expect(dataSource.pollingInterval).toEqual(20)
|
||||
})
|
||||
})
|
||||
|
||||
describe('polling', () => {
|
||||
let clock: sinon.SinonFakeTimers
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers()
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clock.restore()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('polls and emits changes', async () => {
|
||||
@@ -65,7 +59,8 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
|
||||
const runId = testData.data.cloudNodesByIds[0].id
|
||||
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT)
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockResolvedValue(FAKE_PROJECT)
|
||||
|
||||
const query = `
|
||||
query Test {
|
||||
@@ -101,72 +96,78 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
},
|
||||
}
|
||||
|
||||
return createGraphQL(query, fields, async (source, args, context, info) => {
|
||||
const result = await createGraphQL(query, fields, async (source, args, context, info) => {
|
||||
const subscriptionIterator = dataSource.pollForSpecs(runId, info)
|
||||
|
||||
const firstEmit = await subscriptionIterator.next()
|
||||
|
||||
expect(firstEmit, 'should emit because of first value').to.eql({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
// should emit because of first value
|
||||
expect(firstEmit).toEqual({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
|
||||
FAKE_PROJECT.data.cloudNodesByIds[0].totalInstanceCount++
|
||||
debug('**** tick after total instance count increase')
|
||||
await clock.nextAsync()
|
||||
await jest.runOnlyPendingTimers()
|
||||
|
||||
const secondEmit = await subscriptionIterator.next()
|
||||
|
||||
expect(secondEmit, 'should emit because of updated "totalInstanceCount"').to.eql({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
// should emit because of updated "totalInstanceCount"
|
||||
expect(secondEmit).toEqual({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
|
||||
FAKE_PROJECT.data.cloudNodesByIds[0].scheduledToCompleteAt = (new Date()).toISOString()
|
||||
debug('**** tick after adding scheduledToCompleteAt')
|
||||
await clock.nextAsync()
|
||||
await jest.runOnlyPendingTimers()
|
||||
|
||||
const thirdEmit = await subscriptionIterator.next()
|
||||
|
||||
expect(thirdEmit, 'should emit again because of updated "scheduledToCompleteAt"').to.eql({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
// should emit again because of updated "scheduledToCompleteAt"
|
||||
expect(thirdEmit).toEqual({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
|
||||
FAKE_PROJECT.data.cloudNodesByIds[0].totalTests++
|
||||
debug('**** tick after testCounts increase')
|
||||
await clock.nextAsync()
|
||||
await jest.runOnlyPendingTimers()
|
||||
|
||||
const forthEmit = await subscriptionIterator.next()
|
||||
|
||||
expect(forthEmit, 'should emit again because of updated "testCounts"').to.eql({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
// should emit again because of updated "testCounts"
|
||||
expect(forthEmit).toEqual({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
|
||||
FAKE_PROJECT.data.cloudNodesByIds[0].status = 'FAILED'
|
||||
debug('**** tick after setting status Failed')
|
||||
await clock.nextAsync()
|
||||
await jest.runOnlyPendingTimers()
|
||||
|
||||
const finalEmit = await subscriptionIterator.next()
|
||||
|
||||
expect(finalEmit, 'should emit again because of updated "status"').to.eql({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
// should emit again because of updated "status"
|
||||
expect(finalEmit).toEqual({ done: false, value: FAKE_PROJECT.data.cloudNodesByIds[0] })
|
||||
|
||||
subscriptionIterator.return(undefined)
|
||||
|
||||
return {}
|
||||
}).then((result) => {
|
||||
if (result.errors) {
|
||||
throw result.errors[0]
|
||||
}
|
||||
|
||||
const expected = {
|
||||
data: {
|
||||
test: {
|
||||
completedInstanceCount: null,
|
||||
id: null,
|
||||
runNumber: null,
|
||||
status: null,
|
||||
totalInstanceCount: null,
|
||||
totalTests: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(result).to.eql(expected)
|
||||
})
|
||||
|
||||
if (result.errors) {
|
||||
throw result.errors[0]
|
||||
}
|
||||
|
||||
const expected = {
|
||||
data: {
|
||||
test: {
|
||||
completedInstanceCount: null,
|
||||
id: null,
|
||||
runNumber: null,
|
||||
status: null,
|
||||
totalInstanceCount: null,
|
||||
totalTests: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(result).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should create query', async () => {
|
||||
const gqlStub = sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves({ data: {} })
|
||||
// @ts-expect-error
|
||||
const gqlStub = jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockResolvedValue({ data: {} })
|
||||
|
||||
const fields = {
|
||||
value: {
|
||||
@@ -201,11 +202,11 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
let iterator1: ReturnType<RelevantRunSpecsDataSource['pollForSpecs']>
|
||||
let iterator2: ReturnType<RelevantRunSpecsDataSource['pollForSpecs']>
|
||||
|
||||
return createGraphQL(query, fields, async (source, args, context, info) => {
|
||||
await createGraphQL(query, fields, async (source, args, context, info) => {
|
||||
iterator1 = dataSource.pollForSpecs('runId', info)
|
||||
})
|
||||
.then(() => {
|
||||
const expected =
|
||||
|
||||
const firstExpected =
|
||||
dedent`query RelevantRunSpecsDataSource_Specs($ids: [ID!]!) {
|
||||
cloudNodesByIds(ids: $ids) {
|
||||
id
|
||||
@@ -227,19 +228,20 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
value2
|
||||
}`
|
||||
|
||||
expect(gqlStub).to.have.been.called
|
||||
expect(gqlStub.firstCall.args[0]).to.haveOwnProperty('operationDoc')
|
||||
expect(print(gqlStub.firstCall.args[0].operationDoc), 'should match initial query with one fragment').to.eql(`${expected }\n`)
|
||||
})
|
||||
.then(() => {
|
||||
return createGraphQL(query2, fields, async (source, args, context, info) => {
|
||||
iterator2 = dataSource.pollForSpecs('runId', info)
|
||||
expect(gqlStub).toHaveBeenCalled()
|
||||
const gqlStubFirstCallFirstArg = gqlStub.mock.calls[0][0]
|
||||
|
||||
await clock.nextAsync()
|
||||
})
|
||||
expect(gqlStubFirstCallFirstArg).toHaveProperty('operationDoc')
|
||||
// should match initial query with one fragment
|
||||
expect(print(gqlStubFirstCallFirstArg.operationDoc)).toEqual(`${firstExpected }\n`)
|
||||
|
||||
await createGraphQL(query2, fields, async (source, args, context, info) => {
|
||||
iterator2 = dataSource.pollForSpecs('runId', info)
|
||||
|
||||
await jest.runOnlyPendingTimers()
|
||||
})
|
||||
.then(() => {
|
||||
const expected =
|
||||
|
||||
const secondExpected =
|
||||
dedent`query RelevantRunSpecsDataSource_Specs($ids: [ID!]!) {
|
||||
cloudNodesByIds(ids: $ids) {
|
||||
id
|
||||
@@ -267,14 +269,15 @@ describe('RelevantRunSpecsDataSource', () => {
|
||||
value3
|
||||
}`
|
||||
|
||||
expect(gqlStub).to.have.been.calledTwice
|
||||
expect(gqlStub.secondCall.args[0]).to.haveOwnProperty('operationDoc')
|
||||
expect(print(gqlStub.secondCall.args[0].operationDoc), 'should match second query with two fragments').to.eql(`${expected }\n`)
|
||||
})
|
||||
.then(() => {
|
||||
iterator1.return(undefined)
|
||||
iterator2.return(undefined)
|
||||
})
|
||||
expect(gqlStub).toHaveBeenCalledTimes(2)
|
||||
const gqlStubSecondCallFirstArg = gqlStub.mock.calls[1][0]
|
||||
|
||||
expect(gqlStubSecondCallFirstArg).toHaveProperty('operationDoc')
|
||||
// should match second query with two fragments
|
||||
expect(print(gqlStubSecondCallFirstArg.operationDoc)).toEqual(`${secondExpected }\n`)
|
||||
|
||||
iterator1.return(undefined)
|
||||
iterator2.return(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { describe, expect, it, beforeEach, jest } from '@jest/globals'
|
||||
import debugLib from 'debug'
|
||||
|
||||
import { DataContext } from '../../../src'
|
||||
@@ -29,32 +28,33 @@ describe('RelevantRunsDataSource', () => {
|
||||
beforeEach(() => {
|
||||
ctx = createTestDataContext('open')
|
||||
dataSource = new RelevantRunsDataSource(ctx)
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockReset()
|
||||
})
|
||||
|
||||
it('returns empty with no shas', async () => {
|
||||
const result = await dataSource.getRelevantRuns([])
|
||||
|
||||
expect(result).to.eql([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty with no project set', async () => {
|
||||
sinon.stub(ctx.project, 'projectId').resolves(undefined)
|
||||
jest.spyOn(ctx.project, 'projectId').mockResolvedValue(undefined)
|
||||
|
||||
const result = await dataSource.getRelevantRuns([FAKE_SHAS[0]])
|
||||
|
||||
expect(result).to.eql([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty if error', async () => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_WITH_ERROR)
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockResolvedValue(FAKE_PROJECT_WITH_ERROR)
|
||||
const result = await dataSource.getRelevantRuns([])
|
||||
|
||||
expect(result).to.eql([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
context('cloud responses', () => {
|
||||
describe('cloud responses', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(ctx.project, 'projectId').resolves('test123')
|
||||
jest.spyOn(ctx.project, 'projectId').mockResolvedValue('test123')
|
||||
})
|
||||
|
||||
const getShasForTestData = (testData: TestProject) => {
|
||||
@@ -62,13 +62,14 @@ describe('RelevantRunsDataSource', () => {
|
||||
}
|
||||
|
||||
const testScenario = async (testData: TestProject, expectedResult: RelevantRunInfo[]) => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(testData)
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL').mockResolvedValue(testData)
|
||||
|
||||
const testShas: string[] = getShasForTestData(testData)
|
||||
|
||||
const result = await dataSource.getRelevantRuns(testShas)
|
||||
|
||||
expect(result).to.eql(expectedResult)
|
||||
expect(result).toEqual(expectedResult)
|
||||
}
|
||||
|
||||
it('returns empty if cloud project not loaded', async () => {
|
||||
@@ -104,30 +105,37 @@ describe('RelevantRunsDataSource', () => {
|
||||
})
|
||||
|
||||
it('returns the same current if current already set only one running', async () => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL')
|
||||
.onFirstCall().resolves(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
.onSecondCall().resolves(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL')
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
|
||||
const firstResult = await dataSource.getRelevantRuns([FAKE_SHAS[0]])
|
||||
|
||||
expect(firstResult, 'running should be current after first check').to.eql(
|
||||
// running should be current after first check
|
||||
expect(firstResult).toEqual(
|
||||
[formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
)
|
||||
|
||||
const secondResult = await dataSource.getRelevantRuns([FAKE_SHAS[0]])
|
||||
|
||||
expect(secondResult, 'running should be current after second check').to.eql(
|
||||
// running should be current after second check
|
||||
expect(secondResult).toEqual(
|
||||
[formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the same current if current already set and updates after movesToNext is called', async () => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL')
|
||||
.onFirstCall().resolves(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
.onSecondCall().resolves(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
.onThirdCall().resolves(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL')
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
|
||||
const maybeSendRunNotificationStub = sinon.stub(ctx.actions.notification, 'maybeSendRunNotification')
|
||||
const maybeSendRunNotificationStub = jest.spyOn(ctx.actions.notification, 'maybeSendRunNotification')
|
||||
|
||||
const subscription = ctx.emitter.subscribeTo('relevantRunChange')
|
||||
const subValues: any[] = []
|
||||
@@ -145,12 +153,13 @@ describe('RelevantRunsDataSource', () => {
|
||||
debug('first check with only one running run')
|
||||
await dataSource.checkRelevantRuns([FAKE_SHAS[0]], true)
|
||||
|
||||
expect(maybeSendRunNotificationStub).not.to.have.been.called
|
||||
expect(maybeSendRunNotificationStub).not.toHaveBeenCalled()
|
||||
|
||||
debug('second check with the running run completing, but should stay selected')
|
||||
await dataSource.checkRelevantRuns([FAKE_SHAS[1], FAKE_SHAS[0]], true)
|
||||
|
||||
expect(maybeSendRunNotificationStub).to.have.been.calledWithMatch(
|
||||
expect(maybeSendRunNotificationStub).toHaveBeenCalledWith(
|
||||
// @ts-expect-error
|
||||
{ runNumber: 1, status: 'RUNNING', sha: 'fcb90f', totalFailed: 0 },
|
||||
{ runNumber: 4, status: 'FAILED', sha: 'fc753a', totalFailed: 1 },
|
||||
)
|
||||
@@ -158,7 +167,7 @@ describe('RelevantRunsDataSource', () => {
|
||||
debug('moving runs will cause another check')
|
||||
await dataSource.moveToRun(4, [FAKE_SHAS[1], FAKE_SHAS[0]])
|
||||
|
||||
expect(maybeSendRunNotificationStub).to.have.been.calledOnce
|
||||
expect(maybeSendRunNotificationStub).toHaveBeenCalledTimes(1)
|
||||
|
||||
setImmediate(() => {
|
||||
subscription.return(undefined)
|
||||
@@ -166,16 +175,18 @@ describe('RelevantRunsDataSource', () => {
|
||||
|
||||
await watchSubscription()
|
||||
|
||||
expect(subValues).to.have.lengthOf(3)
|
||||
expect(subValues).toHaveLength(3)
|
||||
|
||||
expect(subValues[0], 'should emit first result of running').to.eql({
|
||||
expect(subValues[0]).toEqual({
|
||||
// should emit first result of running
|
||||
all: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
commitsAhead: 0,
|
||||
latest: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
expect(subValues[1], 'should keep run if selected but no longer in all').to.eql({
|
||||
expect(subValues[1]).toEqual({
|
||||
// should keep run if selected but no longer in all
|
||||
all: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 1),
|
||||
@@ -188,7 +199,8 @@ describe('RelevantRunsDataSource', () => {
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
expect(subValues[2], 'should emit selected run after moving').to.eql({
|
||||
expect(subValues[2]).toEqual({
|
||||
// should emit selected run after moving
|
||||
all: [formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0)],
|
||||
latest: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
@@ -200,9 +212,11 @@ describe('RelevantRunsDataSource', () => {
|
||||
})
|
||||
|
||||
it('moves to new sha once completed', async () => {
|
||||
sinon.stub(ctx.cloud, 'executeRemoteGraphQL')
|
||||
.onFirstCall().resolves(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
.onSecondCall().resolves(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
jest.spyOn(ctx.cloud, 'executeRemoteGraphQL')
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_ONE_RUNNING_RUN)
|
||||
// @ts-expect-error
|
||||
.mockResolvedValueOnce(FAKE_PROJECT_MULTIPLE_COMPLETED)
|
||||
|
||||
const subscription = ctx.emitter.subscribeTo('relevantRunChange')
|
||||
const subValues: any[] = []
|
||||
@@ -229,16 +243,18 @@ describe('RelevantRunsDataSource', () => {
|
||||
|
||||
await watchSubscription()
|
||||
|
||||
expect(subValues).to.have.lengthOf(2)
|
||||
expect(subValues).toHaveLength(2)
|
||||
|
||||
expect(subValues[0], 'should emit first result of running').to.eql({
|
||||
expect(subValues[0]).toEqual({
|
||||
// should emit first result of running
|
||||
all: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
latest: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
commitsAhead: 0,
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
expect(subValues[1], 'should emit newer completed run on different sha').to.eql({
|
||||
expect(subValues[1]).toEqual({
|
||||
// should emit newer completed run on different sha
|
||||
all: [formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0)],
|
||||
latest: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach, afterEach } from '@jest/globals'
|
||||
import crypto from 'crypto'
|
||||
|
||||
import { DataContext } from '../../../src'
|
||||
@@ -26,8 +26,8 @@ describe('RemoteRequestDataSource', () => {
|
||||
{ b: '2', a: 1 },
|
||||
)
|
||||
|
||||
expect(id).to.eql('UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo1Y2MyNWQ4YTM5YTY1NGViMjNiNTI1NzM0NWFiYmY0MmJlNDBjOGQxOmV5SmhJam94TENKaUlqb2lNaUo5')
|
||||
expect(Buffer.from(id, 'base64').toString('utf-8')).to.eql('RemoteFetchableCloudProjectSpecResult:5cc25d8a39a654eb23b5257345abbf42be40c8d1:eyJhIjoxLCJiIjoiMiJ9')
|
||||
expect(id).toEqual('UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo1Y2MyNWQ4YTM5YTY1NGViMjNiNTI1NzM0NWFiYmY0MmJlNDBjOGQxOmV5SmhJam94TENKaUlqb2lNaUo5')
|
||||
expect(Buffer.from(id, 'base64').toString('utf-8')).toEqual('RemoteFetchableCloudProjectSpecResult:5cc25d8a39a654eb23b5257345abbf42be40c8d1:eyJhIjoxLCJiIjoiMiJ9')
|
||||
})
|
||||
|
||||
it('stable stringifies via stringifyVariables', () => {
|
||||
@@ -42,13 +42,13 @@ describe('RemoteRequestDataSource', () => {
|
||||
{ a: 1, b: '2' },
|
||||
)
|
||||
|
||||
expect(id).to.eql(id2)
|
||||
expect(id).toEqual(id2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unpackFetchableNodeId', () => {
|
||||
it('takes the identifier created from makeRefetchableId and decodes the variables', () => {
|
||||
expect(remoteRequestSource.unpackFetchableNodeId('UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo1Y2MyNWQ4YTM5YTY1NGViMjNiNTI1NzM0NWFiYmY0MmJlNDBjOGQxOmV5SmhJam94TENKaUlqb2lNaUo5')).to.eql({
|
||||
expect(remoteRequestSource.unpackFetchableNodeId('UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo1Y2MyNWQ4YTM5YTY1NGViMjNiNTI1NzM0NWFiYmY0MmJlNDBjOGQxOmV5SmhJam94TENKaUlqb2lNaUo5')).toEqual({
|
||||
name: 'RemoteFetchableCloudProjectSpecResult',
|
||||
operationHash: '5cc25d8a39a654eb23b5257345abbf42be40c8d1',
|
||||
operationVariables: { a: 1, b: '2' },
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import chai, { expect } from 'chai'
|
||||
import { describe, expect, it, beforeEach, afterEach, jest } from '@jest/globals'
|
||||
import os from 'os'
|
||||
import sinon from 'sinon'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import { Response } from 'cross-fetch'
|
||||
|
||||
import { DataContext } from '../../../src'
|
||||
import { VersionsDataSource } from '../../../src/sources'
|
||||
import { createTestDataContext } from '../helper'
|
||||
import { CYPRESS_REMOTE_MANIFEST_URL, NPM_CYPRESS_REGISTRY_URL } from '@packages/types'
|
||||
|
||||
const pkg = require('@packages/root')
|
||||
|
||||
chai.use(sinonChai)
|
||||
import { AllowedState, CYPRESS_REMOTE_MANIFEST_URL, NPM_CYPRESS_REGISTRY_URL } from '@packages/types'
|
||||
import pkg from '@packages/root'
|
||||
|
||||
describe('VersionsDataSource', () => {
|
||||
context('.versions', () => {
|
||||
describe('.versions', () => {
|
||||
let ctx: DataContext
|
||||
let fetchStub: sinon.SinonStub
|
||||
let isDependencyInstalledByNameStub: sinon.SinonStub
|
||||
let fetchMock: jest.Mock
|
||||
let isDependencyInstalledByNameStub: jest.Mock
|
||||
let mockNow: Date = new Date()
|
||||
let currentCypressVersion: string = pkg.version
|
||||
|
||||
beforeEach(() => {
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
;(ctx.lifecycleManager as any)._cachedInitialConfig = {
|
||||
// @ts-expect-error
|
||||
ctx.lifecycleManager._cachedInitialConfig = {
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'react',
|
||||
@@ -37,59 +33,66 @@ describe('VersionsDataSource', () => {
|
||||
ctx.coreData.currentProject = '/abc'
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
|
||||
fetchStub = sinon.stub()
|
||||
fetchMock = jest.fn()
|
||||
|
||||
fetchStub
|
||||
.withArgs(NPM_CYPRESS_REGISTRY_URL)
|
||||
.resolves({
|
||||
json: sinon.stub().resolves({
|
||||
'time': {
|
||||
modified: '2022-01-31T21:14:41.593Z',
|
||||
created: '2014-03-09T01:07:35.219Z',
|
||||
[currentCypressVersion]: '2014-03-09T01:07:37.369Z',
|
||||
'18.0.0': '2015-05-07T00:09:41.109Z',
|
||||
},
|
||||
}),
|
||||
})
|
||||
isDependencyInstalledByNameStub = jest.fn()
|
||||
|
||||
isDependencyInstalledByNameStub = sinon.stub()
|
||||
|
||||
sinon.stub(ctx.util, 'fetch').callsFake(fetchStub)
|
||||
sinon.stub(ctx.util, 'isDependencyInstalledByName').callsFake(isDependencyInstalledByNameStub)
|
||||
sinon.stub(os, 'platform').returns('darwin')
|
||||
sinon.stub(os, 'arch').returns('x64')
|
||||
sinon.useFakeTimers({ now: mockNow })
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.util, 'fetch').mockImplementation(fetchMock)
|
||||
// @ts-expect-error
|
||||
jest.spyOn(ctx.util, 'isDependencyInstalledByName').mockImplementation(isDependencyInstalledByNameStub)
|
||||
jest.spyOn(os, 'platform').mockReturnValue('darwin')
|
||||
jest.spyOn(os, 'arch').mockReturnValue('x64')
|
||||
jest.useFakeTimers({ now: mockNow })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('loads the manifest for the latest version with all headers and queries npm for release dates', async () => {
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
'x-arch': 'x64',
|
||||
'x-initial-launch': String(true),
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
}),
|
||||
}).resolves({
|
||||
json: sinon.stub().resolves({
|
||||
name: 'Cypress',
|
||||
version: '18.0.0',
|
||||
}),
|
||||
fetchMock.mockImplementation((url: string, options: { headers: Record<string, string> }) => {
|
||||
if (url === NPM_CYPRESS_REGISTRY_URL) {
|
||||
return Promise.resolve({
|
||||
// @ts-expect-error
|
||||
json: jest.fn().mockResolvedValue({
|
||||
'time': {
|
||||
modified: '2022-01-31T21:14:41.593Z',
|
||||
created: '2014-03-09T01:07:35.219Z',
|
||||
[currentCypressVersion]: '2014-03-09T01:07:37.369Z',
|
||||
'18.0.0': '2015-05-07T00:09:41.109Z',
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
url === CYPRESS_REMOTE_MANIFEST_URL &&
|
||||
options.headers['Content-Type'] === 'application/json' &&
|
||||
options.headers['x-cypress-version'] === currentCypressVersion &&
|
||||
options.headers['x-os-name'] === 'darwin' &&
|
||||
options.headers['x-arch'] === 'x64' &&
|
||||
options.headers['x-initial-launch'] === String(true) &&
|
||||
options.headers['x-machine-id'] === 'abcd123' &&
|
||||
options.headers['x-testing-type'] === 'e2e' &&
|
||||
options.headers['x-logged-in'] === 'false') {
|
||||
return Promise.resolve({
|
||||
// @ts-expect-error
|
||||
json: jest.fn().mockResolvedValue({
|
||||
name: 'Cypress',
|
||||
version: '18.0.0',
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
const versionsDataSource = new VersionsDataSource(ctx)
|
||||
|
||||
const versionInfo = await versionsDataSource.versionData()
|
||||
|
||||
expect(versionInfo).to.eql({
|
||||
expect(versionInfo).toEqual({
|
||||
current: {
|
||||
id: currentCypressVersion,
|
||||
version: currentCypressVersion,
|
||||
@@ -107,97 +110,125 @@ describe('VersionsDataSource', () => {
|
||||
ctx.coreData.machineId = Promise.resolve(null)
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
|
||||
const mockRequest = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
'x-arch': 'x64',
|
||||
'x-initial-launch': String(true),
|
||||
'x-testing-type': 'component',
|
||||
'x-logged-in': 'false',
|
||||
}
|
||||
fetchMock.mockImplementation((url: string, options: { headers: Record<string, string> }) => {
|
||||
if (url === NPM_CYPRESS_REGISTRY_URL) {
|
||||
return Promise.resolve({
|
||||
// @ts-expect-error
|
||||
json: jest.fn().mockResolvedValue({
|
||||
'time': {
|
||||
modified: '2022-01-31T21:14:41.593Z',
|
||||
created: '2014-03-09T01:07:35.219Z',
|
||||
[currentCypressVersion]: '2014-03-09T01:07:37.369Z',
|
||||
'18.0.0': '2015-05-07T00:09:41.109Z',
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: sinon.match(mockRequest),
|
||||
}).resolves({
|
||||
json: sinon.stub().resolves({
|
||||
name: 'Cypress',
|
||||
version: '15.0.0',
|
||||
}),
|
||||
})
|
||||
// first mocked response
|
||||
if (
|
||||
url === CYPRESS_REMOTE_MANIFEST_URL &&
|
||||
options.headers['Content-Type'] === 'application/json' &&
|
||||
options.headers['x-cypress-version'] === currentCypressVersion &&
|
||||
options.headers['x-os-name'] === 'darwin' &&
|
||||
options.headers['x-arch'] === 'x64' &&
|
||||
options.headers['x-initial-launch'] === String(true) &&
|
||||
options.headers['x-testing-type'] === 'component' &&
|
||||
options.headers['x-logged-in'] === 'false') {
|
||||
return Promise.resolve({
|
||||
// @ts-expect-error
|
||||
json: jest.fn().mockResolvedValue({
|
||||
name: 'Cypress',
|
||||
version: '15.0.0',
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const mockRequest2 = {
|
||||
...mockRequest,
|
||||
'x-initial-launch': String(false),
|
||||
'x-testing-type': 'e2e',
|
||||
}
|
||||
// second mocked response
|
||||
if (
|
||||
url === CYPRESS_REMOTE_MANIFEST_URL &&
|
||||
options.headers['Content-Type'] === 'application/json' &&
|
||||
options.headers['x-cypress-version'] === currentCypressVersion &&
|
||||
options.headers['x-os-name'] === 'darwin' &&
|
||||
options.headers['x-arch'] === 'x64' &&
|
||||
options.headers['x-initial-launch'] === String(false) &&
|
||||
options.headers['x-testing-type'] === 'e2e' &&
|
||||
options.headers['x-logged-in'] === 'false' &&
|
||||
options.headers['x-initial-launch'] === String(false)) {
|
||||
return Promise.resolve({
|
||||
// @ts-expect-error
|
||||
json: jest.fn().mockResolvedValue({
|
||||
name: 'Cypress',
|
||||
version: '16.0.0',
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: sinon.match(mockRequest2),
|
||||
}).resolves({
|
||||
json: sinon.stub().resolves({
|
||||
name: 'Cypress',
|
||||
version: '16.0.0',
|
||||
}),
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
const versionsDataSource = new VersionsDataSource(ctx)
|
||||
|
||||
await versionsDataSource.versionData()
|
||||
|
||||
expect(await ctx.coreData.versionData?.latestVersion).to.eql('15.0.0')
|
||||
expect(await ctx.coreData.versionData?.latestVersion).toEqual('15.0.0')
|
||||
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
|
||||
versionsDataSource.resetLatestVersionTelemetry()
|
||||
|
||||
expect(await ctx.coreData.versionData?.latestVersion).to.eql('16.0.0')
|
||||
expect(await ctx.coreData.versionData?.latestVersion).toEqual('16.0.0')
|
||||
})
|
||||
|
||||
it('handles errors fetching version data', async () => {
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
'x-arch': 'x64',
|
||||
'x-initial-launch': String(true),
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
}),
|
||||
fetchMock.mockImplementation((url: string, options: { headers: Record<string, string> }) => {
|
||||
if (url === NPM_CYPRESS_REGISTRY_URL) {
|
||||
return Promise.reject(new Error('NPM_CYPRESS_REGISTRY_URL mocked response failed'))
|
||||
}
|
||||
|
||||
if (
|
||||
url === CYPRESS_REMOTE_MANIFEST_URL &&
|
||||
options.headers['Content-Type'] === 'application/json' &&
|
||||
options.headers['x-cypress-version'] === currentCypressVersion &&
|
||||
options.headers['x-os-name'] === 'darwin' &&
|
||||
options.headers['x-arch'] === 'x64' &&
|
||||
options.headers['x-initial-launch'] === String(true) &&
|
||||
options.headers['x-testing-type'] === 'e2e' &&
|
||||
options.headers['x-logged-in'] === 'false') {
|
||||
return Promise.reject(new Error('CYPRESS_REMOTE_MANIFEST_URL mocked response failed'))
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
.rejects()
|
||||
.withArgs(NPM_CYPRESS_REGISTRY_URL)
|
||||
.rejects()
|
||||
|
||||
const versionsDataSource = new VersionsDataSource(ctx)
|
||||
|
||||
const versionInfo = await versionsDataSource.versionData()
|
||||
|
||||
expect(versionInfo.current.version).to.eql(currentCypressVersion)
|
||||
expect(versionInfo.current.version).toEqual(currentCypressVersion)
|
||||
})
|
||||
|
||||
it('handles invalid response errors', async () => {
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
'x-arch': 'x64',
|
||||
'x-initial-launch': String(true),
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
}),
|
||||
fetchMock.mockImplementation((url: string, options: { headers: Record<string, string> }) => {
|
||||
if (url === NPM_CYPRESS_REGISTRY_URL) {
|
||||
return Promise.reject(new Response('Error'))
|
||||
}
|
||||
|
||||
if (
|
||||
url === CYPRESS_REMOTE_MANIFEST_URL &&
|
||||
options.headers['Content-Type'] === 'application/json' &&
|
||||
options.headers['x-cypress-version'] === currentCypressVersion &&
|
||||
options.headers['x-os-name'] === 'darwin' &&
|
||||
options.headers['x-arch'] === 'x64' &&
|
||||
options.headers['x-initial-launch'] === String(true) &&
|
||||
options.headers['x-machine-id'] === 'abcd123' &&
|
||||
options.headers['x-testing-type'] === 'e2e' &&
|
||||
options.headers['x-logged-in'] === 'false') {
|
||||
return Promise.reject(new Response('Error'))
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
.callsFake(async () => new Response('Error'))
|
||||
.withArgs(NPM_CYPRESS_REGISTRY_URL)
|
||||
.callsFake(async () => new Response('Error'))
|
||||
|
||||
const versionsDataSource = new VersionsDataSource(ctx)
|
||||
|
||||
@@ -211,17 +242,17 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
await ctx.coreData.versionData?.latestVersion
|
||||
|
||||
expect(versionInfo.current.version).to.eql(currentCypressVersion)
|
||||
expect(versionInfo.current.version).toEqual(currentCypressVersion)
|
||||
})
|
||||
|
||||
it('generates x-framework, x-bundler, and x-dependencies headers', async () => {
|
||||
isDependencyInstalledByNameStub.callsFake(async (packageName) => {
|
||||
isDependencyInstalledByNameStub.mockImplementation(async (packageName) => {
|
||||
// Should include any resolved dependency with a valid version
|
||||
if (packageName === 'react') {
|
||||
return {
|
||||
dependency: packageName,
|
||||
detectedVersion: '1.2.3',
|
||||
} as Cypress.DependencyToInstall
|
||||
} as unknown as Cypress.DependencyToInstall
|
||||
}
|
||||
|
||||
if (packageName === 'vue') {
|
||||
@@ -264,10 +295,10 @@ describe('VersionsDataSource', () => {
|
||||
versionsDataSource.resetLatestVersionTelemetry()
|
||||
await versionsDataSource.versionData()
|
||||
|
||||
expect(fetchStub).to.have.been.calledWith(
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
CYPRESS_REMOTE_MANIFEST_URL,
|
||||
{
|
||||
headers: sinon.match({
|
||||
headers: expect.objectContaining({
|
||||
'x-framework': 'react',
|
||||
'x-dev-server': 'vite',
|
||||
'x-dependencies': 'react@1.2.3,vue@4.5.6,@builder.io/qwik@1.1.4,@playwright/experimental-ct-core@1.33.0',
|
||||
@@ -277,22 +308,22 @@ describe('VersionsDataSource', () => {
|
||||
})
|
||||
|
||||
it('generates x-notifications header', async () => {
|
||||
(ctx.config.localSettingsApi.getPreferences as sinon.SinonStub).callsFake(() => {
|
||||
return {
|
||||
(ctx.config.localSettingsApi.getPreferences as jest.Mock<typeof ctx.config.localSettingsApi.getPreferences>).mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
notifyWhenRunCompletes: ['errored'],
|
||||
notifyWhenRunStarts: true,
|
||||
notifyWhenRunStartsFailing: true,
|
||||
}
|
||||
}as unknown as AllowedState)
|
||||
})
|
||||
|
||||
const versionsDataSource = new VersionsDataSource(ctx)
|
||||
|
||||
await versionsDataSource.versionData()
|
||||
|
||||
expect(fetchStub).to.have.been.calledWith(
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
CYPRESS_REMOTE_MANIFEST_URL,
|
||||
{
|
||||
headers: sinon.match({
|
||||
headers: expect.objectContaining({
|
||||
'x-notifications': 'errored,started,failing',
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, beforeAll } from '@jest/globals'
|
||||
import { WizardBundler, WIZARD_BUNDLERS, CT_FRAMEWORKS, resolveComponentFrameworkDefinition } from '@packages/scaffold-config'
|
||||
import { expect } from 'chai'
|
||||
import { createTestDataContext, scaffoldMigrationProject, removeCommonNodeModules } from '../helper'
|
||||
|
||||
function findFramework (type: Cypress.ResolvedComponentFrameworkDefinition['type']) {
|
||||
@@ -11,7 +11,7 @@ function findBundler (type: WizardBundler['type']) {
|
||||
}
|
||||
|
||||
describe('packagesToInstall', () => {
|
||||
before(() => {
|
||||
beforeAll(() => {
|
||||
removeCommonNodeModules()
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D webpack react react-dom`)
|
||||
expect(actual).toEqual(`npm install -D webpack react react-dom`)
|
||||
})
|
||||
|
||||
it('regular vue project with webpack', async () => {
|
||||
@@ -44,7 +44,7 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D webpack vue`)
|
||||
expect(actual).toEqual(`npm install -D webpack vue`)
|
||||
})
|
||||
|
||||
it('regular react project with vite', async () => {
|
||||
@@ -60,7 +60,7 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D vite react react-dom`)
|
||||
expect(actual).toEqual(`npm install -D vite react react-dom`)
|
||||
})
|
||||
|
||||
it('regular vue project with vite', async () => {
|
||||
@@ -76,7 +76,7 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D vite vue`)
|
||||
expect(actual).toEqual(`npm install -D vite vue`)
|
||||
})
|
||||
|
||||
it('nextjs-unconfigured', async () => {
|
||||
@@ -92,7 +92,7 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D next react react-dom`)
|
||||
expect(actual).toEqual(`npm install -D next react react-dom`)
|
||||
})
|
||||
|
||||
it('framework and bundler are undefined', async () => {
|
||||
@@ -108,6 +108,6 @@ describe('packagesToInstall', () => {
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq('')
|
||||
expect(actual).toEqual('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
import { graphqlSchema } from '../../../graphql/schema'
|
||||
import { expect } from 'chai'
|
||||
import dedent from 'dedent'
|
||||
import { FieldNode, GraphQLObjectType, OperationDefinitionNode, parse, print } from 'graphql'
|
||||
import { DocumentNodeBuilder } from '../../../src'
|
||||
@@ -41,7 +41,7 @@ describe('DocumentNodeBuilder', () => {
|
||||
operationName: 'CLOUD_VIEWER_QUERY',
|
||||
})
|
||||
|
||||
expect(print(docNodeBuilder.frag)).to.eql(dedent`
|
||||
expect(print(docNodeBuilder.frag)).toEqual(dedent`
|
||||
fragment GeneratedFragment on Query {
|
||||
cloudViewer {
|
||||
id
|
||||
@@ -60,7 +60,7 @@ describe('DocumentNodeBuilder', () => {
|
||||
operationName: 'CLOUD_VIEWER_QUERY',
|
||||
})
|
||||
|
||||
expect(print(docNodeBuilder.query).trimEnd()).to.eql(dedent`
|
||||
expect(print(docNodeBuilder.query).trimEnd()).toEqual(dedent`
|
||||
fragment GeneratedFragment on Query {
|
||||
cloudViewer {
|
||||
id
|
||||
@@ -86,7 +86,7 @@ describe('DocumentNodeBuilder', () => {
|
||||
operationName: 'CLOUD_PROJECT_QUERY',
|
||||
})
|
||||
|
||||
expect(print(docNodeBuilder.queryNode).trimRight()).to.eql(dedent`
|
||||
expect(print(docNodeBuilder.queryNode).trimRight()).toEqual(dedent`
|
||||
fragment GeneratedFragment on CloudProject {
|
||||
id
|
||||
cloudProject {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
import path from 'path'
|
||||
import { hasTypeScriptInstalled } from '../../../src/util'
|
||||
import { scaffoldMigrationProject } from '../helper'
|
||||
@@ -7,12 +7,12 @@ describe('hasTypeScript', () => {
|
||||
it('returns true when installed', async () => {
|
||||
const monorepoRoot = path.join(__dirname, '..', '..', '..', '..', '..')
|
||||
|
||||
expect(hasTypeScriptInstalled(monorepoRoot)).to.be.true
|
||||
expect(hasTypeScriptInstalled(monorepoRoot)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when not installed', async () => {
|
||||
const projectRoot = await scaffoldMigrationProject('config-with-js')
|
||||
|
||||
expect(hasTypeScriptInstalled(projectRoot)).to.be.false
|
||||
expect(hasTypeScriptInstalled(projectRoot)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
import { SpecWithRelativeRoot } from '@packages/types'
|
||||
import { expect } from 'chai'
|
||||
|
||||
import fs from 'fs-extra'
|
||||
import { scaffoldMigrationProject } from '../helper'
|
||||
import { getTestCounts } from '../../../src/util/testCounts'
|
||||
@@ -12,7 +11,7 @@ describe('getTestCounts', () => {
|
||||
|
||||
const counts = await getTestCounts(specs)
|
||||
|
||||
expect(counts).to.deep.equal({
|
||||
expect(counts).toEqual({
|
||||
totalSpecs: 0,
|
||||
totalTests: 0,
|
||||
exampleSpecs: 0,
|
||||
@@ -20,7 +19,7 @@ describe('getTestCounts', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('with e2e project', () => {
|
||||
describe('with e2e project', () => {
|
||||
let specs: SpecWithRelativeRoot[]
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -44,11 +43,11 @@ describe('getTestCounts', () => {
|
||||
it('should return counts for tests e2e migration project', async () => {
|
||||
const counts = await getTestCounts(specs)
|
||||
|
||||
expect(counts.totalSpecs).to.equal(specs.length)
|
||||
expect(counts.totalSpecs).toEqual(specs.length)
|
||||
// don't test for exact number since tests in sample project might change
|
||||
expect(counts.totalTests).to.be.greaterThan(0)
|
||||
expect(counts.exampleSpecs).to.eq(0)
|
||||
expect(counts.exampleTests).to.eq(0)
|
||||
expect(counts.totalTests).toBeGreaterThan(0)
|
||||
expect(counts.exampleSpecs).toEqual(0)
|
||||
expect(counts.exampleTests).toEqual(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
|
||||
import { WEIGHTED, WEIGHTED_EVEN } from '../../../src/util/weightedChoice'
|
||||
|
||||
describe('weightedChoice', () => {
|
||||
context('WeightedAlgorithm', () => {
|
||||
describe('WeightedAlgorithm', () => {
|
||||
it('should error if invalid arguments', () => {
|
||||
const weights = [25, 75, 45]
|
||||
const options = ['A', 'B']
|
||||
@@ -12,7 +12,7 @@ describe('weightedChoice', () => {
|
||||
WEIGHTED(weights).pick(options)
|
||||
}
|
||||
|
||||
expect(func).to.throw()
|
||||
expect(func).toThrow()
|
||||
})
|
||||
|
||||
it('should error if weights is empty', () => {
|
||||
@@ -23,7 +23,7 @@ describe('weightedChoice', () => {
|
||||
WEIGHTED(weights).pick(options)
|
||||
}
|
||||
|
||||
expect(func).to.throw()
|
||||
expect(func).toThrow()
|
||||
})
|
||||
|
||||
it('should error if options is empty', () => {
|
||||
@@ -34,7 +34,7 @@ describe('weightedChoice', () => {
|
||||
WEIGHTED(weights).pick(options)
|
||||
}
|
||||
|
||||
expect(func).to.throw()
|
||||
expect(func).toThrow()
|
||||
})
|
||||
|
||||
it('should return an option', () => {
|
||||
@@ -42,20 +42,20 @@ describe('weightedChoice', () => {
|
||||
const options = ['A', 'B']
|
||||
const selected = WEIGHTED(weights).pick(options)
|
||||
|
||||
expect(options.includes(selected)).to.be.true
|
||||
expect(options.includes(selected)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
context('WEIGHTED_EVEN', () => {
|
||||
describe('WEIGHTED_EVEN', () => {
|
||||
it('should return an option', () => {
|
||||
const options = ['A', 'B']
|
||||
const selected = WEIGHTED_EVEN(options).pick(options)
|
||||
|
||||
expect(options.includes(selected)).to.be.true
|
||||
expect(options.includes(selected)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
context('randomness', () => {
|
||||
describe('randomness', () => {
|
||||
it('should return values close to supplied weights', () => {
|
||||
const results = {}
|
||||
const options = ['A', 'B']
|
||||
@@ -68,7 +68,7 @@ describe('weightedChoice', () => {
|
||||
}
|
||||
|
||||
Object.keys(results).forEach((key) => {
|
||||
expect(Math.round(results[key] / 100)).to.equal(5)
|
||||
expect(Math.round(results[key] / 100)).toEqual(5)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"isolatedModules": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// necessary to have mocha types working correctly
|
||||
// NOTE: this is the sinon version of @packages/data-context/test/unit/helper.ts and will eventually be replaced with the a different version
|
||||
|
||||
import 'mocha'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { Response } from 'cross-fetch'
|
||||
import Fixtures, { fixtureDirs, scaffoldProject, removeProject } from '@tooling/system-tests'
|
||||
import { DataContext, DataContextConfig } from '@packages/data-context/src'
|
||||
import { graphqlSchema } from '@packages/data-context/graphql/schema'
|
||||
import { remoteSchemaWrapped as schemaCloud } from '@packages/data-context/graphql/stitching/remoteSchemaWrapped'
|
||||
import type { BrowserApiShape } from '@packages/data-context/src/sources/BrowserDataSource'
|
||||
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape, CohortsApiShape } from '@packages/data-context/src/actions'
|
||||
import sinon from 'sinon'
|
||||
import { execute, parse } from 'graphql'
|
||||
import { getOperationName } from '@urql/core'
|
||||
import { CloudQuery } from '@packages/data-context/test/graphql/stubCloudTypes'
|
||||
import { remoteSchema } from '@packages/data-context/graphql/stitching/remoteSchema'
|
||||
import type { OpenModeOptions, RunModeOptions } from '@packages/types'
|
||||
import { GET_MAJOR_VERSION_FOR_CONTENT } from '@packages/types'
|
||||
import { RelevantRunInfo } from '@packages/data-context/src/gen/graphcache-config.gen'
|
||||
|
||||
type SystemTestProject = typeof fixtureDirs[number]
|
||||
type SystemTestProjectPath<T extends SystemTestProject> = `${string}/system-tests/projects/${T}`
|
||||
|
||||
export { scaffoldProject, removeProject }
|
||||
|
||||
export function getSystemTestProject<T extends typeof fixtureDirs[number]> (project: T): SystemTestProjectPath<T> {
|
||||
return path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', project) as SystemTestProjectPath<T>
|
||||
}
|
||||
|
||||
export function removeCommonNodeModules () {
|
||||
fs.rmSync(path.join(Fixtures.cyTmpDir, 'node_modules'), { recursive: true, force: true })
|
||||
}
|
||||
|
||||
export async function scaffoldMigrationProject (project: typeof fixtureDirs[number]): Promise<string> {
|
||||
Fixtures.removeProject(project)
|
||||
|
||||
await Fixtures.scaffoldProject(project)
|
||||
|
||||
return Fixtures.projectPath(project)
|
||||
}
|
||||
|
||||
export function createTestDataContext (mode: DataContextConfig['mode'] = 'run', modeOptions: Partial<RunModeOptions | OpenModeOptions> = {}) {
|
||||
const ctx = new DataContext({
|
||||
schema: graphqlSchema,
|
||||
schemaCloud,
|
||||
mode,
|
||||
modeOptions,
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {
|
||||
getPreferences: sinon.stub().resolves({
|
||||
majorVersionWelcomeDismissed: { [GET_MAJOR_VERSION_FOR_CONTENT()]: 123456 },
|
||||
notifyWhenRunCompletes: ['failed'],
|
||||
}),
|
||||
getAvailableEditors: sinon.stub(),
|
||||
setPreferences: sinon.stub(),
|
||||
} as unknown as LocalSettingsApiShape,
|
||||
authApi: {
|
||||
logIn: sinon.stub().throws('not stubbed'),
|
||||
resetAuthState: sinon.stub(),
|
||||
} as unknown as AuthApiShape,
|
||||
projectApi: {
|
||||
closeActiveProject: sinon.stub(),
|
||||
insertProjectToCache: sinon.stub().resolves(),
|
||||
getProjectRootsFromCache: sinon.stub().resolves([]),
|
||||
runSpec: sinon.stub(),
|
||||
routeToDebug: sinon.stub(),
|
||||
} as unknown as ProjectApiShape,
|
||||
electronApi: {
|
||||
isMainWindowFocused: sinon.stub().returns(false),
|
||||
focusMainWindow: sinon.stub(),
|
||||
copyTextToClipboard: (text) => {},
|
||||
} as unknown as ElectronApiShape,
|
||||
browserApi: {
|
||||
focusActiveBrowserWindow: sinon.stub(),
|
||||
getBrowsers: sinon.stub().resolves([]),
|
||||
} as unknown as BrowserApiShape,
|
||||
cohortsApi: {
|
||||
getCohorts: sinon.stub().resolves(),
|
||||
getCohort: sinon.stub().resolves(),
|
||||
insertCohort: sinon.stub(),
|
||||
determineCohort: sinon.stub().resolves(),
|
||||
} as unknown as CohortsApiShape,
|
||||
})
|
||||
|
||||
const origFetch = ctx.util.fetch
|
||||
|
||||
ctx.util.fetch = async function (url, init) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
|
||||
if (String(url).endsWith('/test-runner-graphql')) {
|
||||
const { query, variables } = JSON.parse(String(init?.body))
|
||||
const document = parse(query)
|
||||
const operationName = getOperationName(document)
|
||||
|
||||
const result = await Promise.resolve(execute({
|
||||
operationName,
|
||||
variableValues: variables,
|
||||
rootValue: CloudQuery,
|
||||
contextValue: {
|
||||
__server__: ctx,
|
||||
},
|
||||
schema: remoteSchema,
|
||||
document,
|
||||
}))
|
||||
|
||||
return new Response(JSON.stringify(result), { status: 200 })
|
||||
}
|
||||
|
||||
return origFetch.call(this, url, init)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function createRelevantRun (runNumber: number): RelevantRunInfo {
|
||||
return {
|
||||
runNumber,
|
||||
ciBuildNumber: '123',
|
||||
branch: 'feature/branch',
|
||||
organizationId: 'org-id',
|
||||
sha: 'sha-123',
|
||||
totalFailed: 0,
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import snapshot from 'snap-shot-it'
|
||||
import { EventEmitter } from 'events'
|
||||
import { exec } from 'child_process'
|
||||
import util from 'util'
|
||||
import { createTestDataContext } from '@packages/data-context/test/unit/helper'
|
||||
import { createTestDataContext } from '../../support/helpers/data-context-helper'
|
||||
import electron from '../../../lib/browsers/electron'
|
||||
import chrome from '../../../lib/browsers/chrome'
|
||||
import Promise from 'bluebird'
|
||||
|
||||
Reference in New Issue
Block a user