@toptal/davinci-qa 8.0.1-alpha-fx-2755-codebase-specific-workflows.20 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 8.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1351](https://github.com/toptal/davinci/pull/1351) [`31a5c91e`](https://github.com/toptal/davinci/commit/31a5c91e792323b7674395f120f7ca2f648cf53d) Thanks [@TomasSlama](https://github.com/TomasSlama)! - Update cypress to ^9.7.0
8
+
9
+ - Updated dependencies [[`1212e098`](https://github.com/toptal/davinci/commit/1212e098c668fc1c87ee9b1824edf0bc80509bc4)]:
10
+ - @toptal/davinci-cli-shared@1.5.2
11
+
3
12
  ## 8.0.0
4
13
 
5
14
  ### Major Changes
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@toptal/davinci-qa",
3
+ "version": "8.0.1",
4
+ "description": "QA package to test your application",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "keywords": [
9
+ "qa",
10
+ "testing"
11
+ ],
12
+ "author": "Toptal",
13
+ "homepage": "https://github.com/toptal/davinci/tree/master/packages/qa#readme",
14
+ "license": "ISC",
15
+ "bin": {
16
+ "davinci-qa": "./bin/davinci-qa.js"
17
+ },
18
+ "main": "./src/index.js",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/toptal/davinci.git"
22
+ },
23
+ "scripts": {
24
+ "build:package": "../../bin/build-package.js",
25
+ "prepublishOnly": "../../bin/prepublish.js",
26
+ "test": "echo \"Error: run tests from root\" && exit 1"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/toptal/davinci/issues"
30
+ },
31
+ "dependencies": {
32
+ "@babel/core": "^7.18.0",
33
+ "@babel/preset-env": "^7.18.0",
34
+ "@babel/preset-react": "^7.17.12",
35
+ "@babel/preset-typescript": "^7.17.12",
36
+ "@bahmutov/cypress-extends": "^1.1.0",
37
+ "@cypress/code-coverage": "^3.9.12",
38
+ "@cypress/webpack-preprocessor": "^5.11.0",
39
+ "@testing-library/jest-dom": "^5.14.1",
40
+ "@testing-library/react": "^12.1.2",
41
+ "@toptal/davinci-cli-shared": "1.5.2",
42
+ "@types/jest": "^27.4.1",
43
+ "babel-jest": "^27.5.1",
44
+ "cypress": "^9.7.0",
45
+ "enhanced-resolve": "^5.9.2",
46
+ "fs-extra": "^10.0.0",
47
+ "glob": "^8.0.3",
48
+ "jest": "^27.5.1",
49
+ "jest-html-reporters": "^3.0.6",
50
+ "jest-junit": "^13.1.0",
51
+ "jest-silent-reporter": "^0.5.0",
52
+ "jest-styled-components": "^7.0.8",
53
+ "jsdom": "^19.0.0",
54
+ "matchmedia-polyfill": "^0.3.2",
55
+ "semver": "^7.3.2"
56
+ },
57
+ "devDependencies": {
58
+ "json5": "^2.2.1",
59
+ "mocha": "^10.0.0"
60
+ }
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/davinci-qa",
3
- "version": "8.0.1-alpha-fx-2755-codebase-specific-workflows.20+b2bc74b6",
3
+ "version": "8.0.1",
4
4
  "description": "QA package to test your application",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -38,7 +38,7 @@
38
38
  "@cypress/webpack-preprocessor": "^5.11.0",
39
39
  "@testing-library/jest-dom": "^5.14.1",
40
40
  "@testing-library/react": "^12.1.2",
41
- "@toptal/davinci-cli-shared": "1.5.2-alpha-fx-2755-codebase-specific-workflows.131+b2bc74b6",
41
+ "@toptal/davinci-cli-shared": "1.5.2",
42
42
  "@types/jest": "^27.4.1",
43
43
  "babel-jest": "^27.5.1",
44
44
  "cypress": "^9.7.0",
@@ -57,6 +57,5 @@
57
57
  "devDependencies": {
58
58
  "json5": "^2.2.1",
59
59
  "mocha": "^10.0.0"
60
- },
61
- "gitHead": "b2bc74b6947156d1489a42f820be1832f53b870d"
60
+ }
62
61
  }
@@ -0,0 +1,69 @@
1
+ const convertToCLIParametersMock = jest.fn()
2
+ const getPackageFilePathMock = jest.fn()
3
+
4
+ jest.mock('@toptal/davinci-cli-shared', () => ({
5
+ runSync: jest.fn(),
6
+ print: {
7
+ green: jest.fn()
8
+ },
9
+ convertToCLIParameters: convertToCLIParametersMock,
10
+ files: {
11
+ getPackageFilePath: getPackageFilePathMock
12
+ }
13
+ }))
14
+
15
+ const { runSync } = require('@toptal/davinci-cli-shared')
16
+
17
+ const { action } = require('./integration-tests')
18
+
19
+ describe('integrationTestCommand', () => {
20
+ it('runs cypress with default options', () => {
21
+ convertToCLIParametersMock.mockReturnValueOnce([])
22
+ getPackageFilePathMock.mockReturnValueOnce(
23
+ 'packages/qa/src/configs/cypress.config.json'
24
+ )
25
+
26
+ action({ options: {} })
27
+
28
+ expect(runSync).toHaveBeenCalledWith('yarn', [
29
+ 'cypress',
30
+ 'run',
31
+ '--config-file',
32
+ 'packages/qa/src/configs/cypress.config.json'
33
+ ])
34
+
35
+ expect(convertToCLIParametersMock).toHaveBeenCalledWith({})
36
+ expect(getPackageFilePathMock).toHaveBeenCalledWith(
37
+ '@toptal/davinci-qa',
38
+ 'src/configs/cypress.config.json'
39
+ )
40
+ })
41
+
42
+ it('runs cypress with custom options', () => {
43
+ const spec = 'cypress/integration/activation_flow.spec.ts'
44
+
45
+ convertToCLIParametersMock.mockReturnValueOnce(['--spec', spec])
46
+ getPackageFilePathMock.mockReturnValueOnce(
47
+ 'packages/qa/src/configs/cypress.config.json'
48
+ )
49
+
50
+ action({ options: { spec } })
51
+
52
+ expect(runSync).toHaveBeenCalledWith('yarn', [
53
+ 'cypress',
54
+ 'run',
55
+ '--config-file',
56
+ 'packages/qa/src/configs/cypress.config.json',
57
+ '--spec',
58
+ spec
59
+ ])
60
+
61
+ expect(convertToCLIParametersMock).toHaveBeenCalledWith({
62
+ spec
63
+ })
64
+ expect(getPackageFilePathMock).toHaveBeenCalledWith(
65
+ '@toptal/davinci-qa',
66
+ 'src/configs/cypress.config.json'
67
+ )
68
+ })
69
+ })
@@ -0,0 +1,213 @@
1
+ const jestRunCLIMock = jest
2
+ .fn()
3
+ .mockResolvedValue({ results: { success: true } })
4
+ const getPackageFilePathMock = jest.fn()
5
+ const getProjectRootFilePathMock = jest.fn()
6
+ const getProjectRootFileContentMock = jest.fn().mockReturnValue({
7
+ devDependencies: {},
8
+ dependencies: {}
9
+ })
10
+
11
+ jest.mock('@toptal/davinci-cli-shared', () => ({
12
+ runSync: jest.fn(),
13
+ print: {
14
+ green: jest.fn(),
15
+ grey: jest.fn(),
16
+ red: jest.fn()
17
+ },
18
+ files: {
19
+ getPackageFilePath: getPackageFilePathMock,
20
+ getProjectRootFileContent: getProjectRootFileContentMock,
21
+ getProjectRootFilePath: getProjectRootFilePathMock
22
+ }
23
+ }))
24
+
25
+ jest.mock('jest', () => ({
26
+ runCLI: jestRunCLIMock
27
+ }))
28
+
29
+ const { action } = require('./unit-tests')
30
+ const originalEnv = process.env
31
+
32
+ describe('unitTestsCommand', () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks()
35
+ })
36
+
37
+ afterEach(() => {
38
+ process.env = originalEnv
39
+ })
40
+
41
+ describe('when running locally', () => {
42
+ beforeEach(() => {
43
+ delete process.env.CI
44
+ })
45
+
46
+ it('calls the jest CLI without additional reporters', async () => {
47
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
48
+
49
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
50
+
51
+ await action({})
52
+
53
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
54
+ { config: davinciJestConfigPath, testPathPattern: [] },
55
+ expect.anything()
56
+ )
57
+ })
58
+ })
59
+
60
+ describe('when running on CI', () => {
61
+ beforeEach(() => {
62
+ process.env.CI = true
63
+ })
64
+
65
+ it('calls the jest CLI with default Davinci reporters', async () => {
66
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
67
+
68
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
69
+
70
+ await action({})
71
+
72
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ reporters: [
75
+ [
76
+ 'jest-silent-reporter',
77
+ {
78
+ useDots: true,
79
+ showPaths: true,
80
+ showWarnings: true
81
+ }
82
+ ],
83
+ [
84
+ 'jest-junit',
85
+ {
86
+ outputDirectory: 'reports'
87
+ }
88
+ ],
89
+ [
90
+ 'jest-html-reporters',
91
+ {
92
+ publicPath: './reports'
93
+ }
94
+ ]
95
+ ]
96
+ }),
97
+ expect.anything()
98
+ )
99
+ })
100
+
101
+ it('returns the anvil reporter when anvilTag is set', async () => {
102
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
103
+
104
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
105
+
106
+ await action({ options: { anvilTag: 'platform' } })
107
+
108
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
109
+ expect.objectContaining({
110
+ reporters: [
111
+ [
112
+ 'jest-silent-reporter',
113
+ {
114
+ useDots: true,
115
+ showPaths: true,
116
+ showWarnings: true
117
+ }
118
+ ],
119
+ [
120
+ 'jest-junit',
121
+ {
122
+ outputDirectory: 'reports'
123
+ }
124
+ ],
125
+ [
126
+ 'jest-html-reporters',
127
+ {
128
+ publicPath: './reports'
129
+ }
130
+ ],
131
+ [
132
+ expect.stringContaining(
133
+ 'davinci/packages/qa/src/reporters/jest-anvil-reporter.js'
134
+ ),
135
+ {
136
+ anvilTag: 'platform'
137
+ }
138
+ ]
139
+ ]
140
+ }),
141
+ expect.anything()
142
+ )
143
+ })
144
+
145
+ it('allows overriding of the default Davinci reporters via custom configuration', async () => {
146
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
147
+
148
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
149
+ const customCLIReporter = 'My-CLI-Reporter'
150
+
151
+ await action({
152
+ options: {
153
+ anvilTag: 'MyTag',
154
+ reporters: customCLIReporter
155
+ }
156
+ })
157
+
158
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
159
+ expect.objectContaining({
160
+ reporters: [customCLIReporter]
161
+ }),
162
+ expect.anything()
163
+ )
164
+ })
165
+
166
+ it('allows overriding the default Davinci reporters via custom jest config', async () => {
167
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
168
+
169
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
170
+ const customJestConfigReporter = 'My-Jest-Config-Reporter'
171
+
172
+ getProjectRootFileContentMock.mockReturnValue({
173
+ reporters: customJestConfigReporter
174
+ })
175
+
176
+ await action({ options: { config: {} } })
177
+
178
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
179
+ expect.objectContaining({
180
+ reporters: [customJestConfigReporter]
181
+ }),
182
+ expect.anything()
183
+ )
184
+ })
185
+
186
+ it('prioritizes the CLI reporters over the custom jest config or the default Davinci reporters', async () => {
187
+ const davinciJestConfigPath = 'packages/qa/src/configs/jest.config.js'
188
+
189
+ getPackageFilePathMock.mockReturnValueOnce(davinciJestConfigPath)
190
+ const customCLIReporter = 'My-CLI-Reporter'
191
+ const customJestConfigReporter = 'My-Jest-Config-Reporter'
192
+
193
+ getProjectRootFileContentMock.mockReturnValue({
194
+ reporters: customJestConfigReporter
195
+ })
196
+
197
+ await action({
198
+ options: {
199
+ anvilTag: 'MyTag',
200
+ reporters: customCLIReporter,
201
+ config: {}
202
+ }
203
+ })
204
+
205
+ expect(jestRunCLIMock).toHaveBeenCalledWith(
206
+ expect.objectContaining({
207
+ reporters: [customCLIReporter]
208
+ }),
209
+ expect.anything()
210
+ )
211
+ })
212
+ })
213
+ })
@@ -0,0 +1,28 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`cypress extends tests when "extends" key exists in the source config file merges extended config into source config 1`] = `
4
+ "{
5
+ configFile: '@toptal/davinci-qa/src/configs/cypress/plugins/config-with-extends.test.json',
6
+ fixturesFolder: './cypress/fixtures',
7
+ integrationFolder: '.',
8
+ screenshotsFolder: './cypress/screenshots',
9
+ testFiles: [
10
+ 'cypress/**/spec.{js,ts}',
11
+ 'cypress/**/*.spec.{js,ts}',
12
+ ],
13
+ ignoreTestFiles: '**/node_modules/**/*',
14
+ pluginsFile: './cypress/plugins/index.js',
15
+ baseUrl: 'http://0.0.0.0:3000',
16
+ chromeWebSecurity: false,
17
+ defaultCommandTimeout: 1000,
18
+ video: false,
19
+ extends: '@toptal/davinci-qa/src/configs/cypress.config.json',
20
+ }"
21
+ `;
22
+
23
+ exports[`cypress extends tests when no "extends" exist in the source config file returns source config file without any change 1`] = `
24
+ "{
25
+ configFile: '@toptal/davinci-qa/src/configs/cypress/plugins/config-without-extends.test.json',
26
+ defaultCommandTimeout: 1000,
27
+ }"
28
+ `;
@@ -0,0 +1,43 @@
1
+ const json5 = require('json5')
2
+
3
+ const plugins = require('./index')
4
+
5
+ jest.mock('@cypress/code-coverage/task', () => jest.fn())
6
+ jest.mock('./parallelization', () => jest.fn())
7
+
8
+ const configWithExtendsPath =
9
+ '@toptal/davinci-qa/src/configs/cypress/plugins/config-with-extends.test.json'
10
+ const configWithoutExtendsPath =
11
+ '@toptal/davinci-qa/src/configs/cypress/plugins/config-without-extends.test.json'
12
+
13
+ describe('cypress extends tests', () => {
14
+ const onFunction = jest.fn()
15
+
16
+ describe('when no "extends" exist in the source config file', () => {
17
+ it('returns source config file without any change', () => {
18
+ const config = {
19
+ configFile: configWithoutExtendsPath
20
+ }
21
+
22
+ plugins(onFunction, config)
23
+
24
+ const serializedConfig = json5.stringify(config, null, 2)
25
+
26
+ expect(serializedConfig).toMatchSnapshot()
27
+ })
28
+ })
29
+
30
+ describe('when "extends" key exists in the source config file', () => {
31
+ it('merges extended config into source config', () => {
32
+ const config = {
33
+ configFile: configWithExtendsPath
34
+ }
35
+
36
+ plugins(onFunction, config)
37
+
38
+ const serializedConfig = json5.stringify(config, null, 2)
39
+
40
+ expect(serializedConfig).toMatchSnapshot()
41
+ })
42
+ })
43
+ })
@@ -0,0 +1,87 @@
1
+ const fs = require('fs-extra')
2
+ const { print } = require('@toptal/davinci-cli-shared')
3
+
4
+ const { anvilMapper, generateAnvilResults } = require('./anvil-results-helper')
5
+
6
+ describe('Anvil results helper', () => {
7
+ beforeEach(() => {
8
+ jest.resetModules()
9
+ jest.resetAllMocks()
10
+ })
11
+
12
+ describe('anvilMapper', () => {
13
+ it('outputs an Anvil-compatible object', () => {
14
+ const mappedValue = anvilMapper({
15
+ fileName:
16
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
17
+ backtrace: null,
18
+ description: '1 + 1 returns 2',
19
+ durationInSeconds: 0.003,
20
+ error: null,
21
+ lineNumber: 2,
22
+ pendingMessage: null,
23
+ scenarioName: 'Example test suite/1 + 1 returns 2',
24
+ status: 'passed',
25
+ tag: 'platform',
26
+ testType: 'unit'
27
+ })
28
+
29
+ expect(mappedValue).toEqual({
30
+ file_name:
31
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
32
+ line_number: 2,
33
+ status: 'passed',
34
+ duration: 0.003,
35
+ scenario_name: 'Example test suite/1 + 1 returns 2',
36
+ description: '1 + 1 returns 2',
37
+ error: null,
38
+ backtrace: null,
39
+ test_type: 'unit',
40
+ pending_message: null,
41
+ tag: 'platform'
42
+ })
43
+ })
44
+
45
+ it('converts "todo" status to pending', () => {
46
+ const mappedValue = anvilMapper({ status: 'todo' })
47
+
48
+ expect(mappedValue.status).toBe('pending')
49
+ })
50
+ })
51
+
52
+ describe('generateAnvilResults', () => {
53
+ it('creates a report with the data passed in', () => {
54
+ const writeJsonMock = jest.spyOn(fs, 'writeJsonSync')
55
+ const printMock = jest.spyOn(print, 'green')
56
+
57
+ generateAnvilResults('anvil_cypress_test_results.json', { test: 123 })
58
+
59
+ expect(writeJsonMock).toHaveBeenCalledWith(
60
+ './reports/anvil_cypress_test_results.json',
61
+ { test: 123 }
62
+ )
63
+ expect(printMock).toHaveBeenCalledWith(
64
+ '[anvil-reporter] Anvil reports generated successfully!'
65
+ )
66
+ })
67
+
68
+ it('throws an error when unable to create report', () => {
69
+ const mockError = new Error('My error!')
70
+
71
+ jest.spyOn(fs, 'ensureDirSync').mockImplementation(() => {
72
+ throw mockError
73
+ })
74
+ const printMock = jest.spyOn(print, 'red')
75
+
76
+ expect(() =>
77
+ generateAnvilResults('anvil_cypress_test_results.json', { test: 123 })
78
+ ).toThrow()
79
+
80
+ expect(printMock).toHaveBeenCalledTimes(1)
81
+ expect(printMock).toHaveBeenCalledWith(
82
+ '[anvil-reporter] Failed to create Anvil test results!',
83
+ mockError
84
+ )
85
+ })
86
+ })
87
+ })
@@ -0,0 +1,269 @@
1
+ /* eslint-disable jest/no-done-callback */
2
+ // If you need to extend these tests, have a look at these other reporters for inspiration:
3
+ // - https://github.com/adamgruber/mochawesome/blob/963f073fe3f4522070788a702329dd6b61014df3/test/reporter.test.js
4
+ // - https://github.com/michaelleeallen/mocha-junit-reporter/blob/3b65764449f6f3a0ec6f54da5842a2a21d788382/test/mocha-junit-reporter-spec.js#L176
5
+ const generateAnvilResultsMock = jest.fn()
6
+
7
+ jest.mock('./anvil-results-helper', () => ({
8
+ ...jest.requireActual('./anvil-results-helper'),
9
+ generateAnvilResults: generateAnvilResultsMock
10
+ }))
11
+ const { print } = require('@toptal/davinci-cli-shared')
12
+ const Mocha = require('mocha')
13
+ const { AssertionError } = require('assert')
14
+
15
+ const CypressAnvilReporter = require('./cypress-anvil-reporter')
16
+
17
+ const { Runner, Suite, Test } = Mocha
18
+
19
+ const createRunner = suite => {
20
+ const runner = new Runner(suite)
21
+
22
+ runner.stats = {
23
+ passes: 0,
24
+ duration: 0
25
+ }
26
+
27
+ return runner
28
+ }
29
+
30
+ const configureReporter = (runner, reporter, options = {}) => {
31
+ const mocha = new Mocha({
32
+ reporter: reporter,
33
+ allowUncaught: true
34
+ })
35
+
36
+ new mocha._reporter(runner, {
37
+ reporterOptions: options
38
+ })
39
+ }
40
+
41
+ describe('Cypress Anvil reporter', () => {
42
+ const rootSuiteName = 'my root suite'
43
+ const testName = 'my test'
44
+ const anvilTag = 'talent_portal'
45
+
46
+ beforeEach(() => {
47
+ jest.resetModules()
48
+ jest.resetAllMocks()
49
+ })
50
+
51
+ it('generates an Anvil-compatible report when anvilTag is set', done => {
52
+ const suite = new Suite(rootSuiteName, 'root').addTest(
53
+ new Test(testName, () => {})
54
+ )
55
+ const runner = createRunner(suite)
56
+
57
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
58
+
59
+ runner.run(failures => {
60
+ expect(failures).toBe(0)
61
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
62
+ 'anvil_cypress_test_results.json',
63
+ {
64
+ test_results: [
65
+ {
66
+ status: 'passed',
67
+ duration: expect.any(Number),
68
+ scenario_name: `${rootSuiteName}/${testName}`,
69
+ description: testName,
70
+ error: null,
71
+ backtrace: null,
72
+ test_type: 'integration',
73
+ pending_message: null,
74
+ tag: anvilTag
75
+ }
76
+ ]
77
+ }
78
+ )
79
+
80
+ done()
81
+ })
82
+ })
83
+
84
+ it('does not generate a report if anvilTag is not specified', done => {
85
+ const printSpy = jest.spyOn(print, 'yellow')
86
+ const suite = new Suite(rootSuiteName, 'root').addTest(
87
+ new Test(testName, () => {})
88
+ )
89
+ const runner = createRunner(suite)
90
+
91
+ configureReporter(runner, CypressAnvilReporter)
92
+
93
+ runner.run(() => {
94
+ expect(printSpy).toHaveBeenCalledWith(
95
+ expect.stringContaining(
96
+ "[anvil-reporter] --anvilTag option is missing! anvil_cypress_test_results.json won't be generated."
97
+ )
98
+ )
99
+ expect(generateAnvilResultsMock).not.toHaveBeenCalled()
100
+
101
+ done()
102
+ })
103
+ })
104
+
105
+ it('returns the scenario name with all parents listed', done => {
106
+ const grandchildSuiteName = 'my grandchild suite'
107
+ const childSuiteName = 'my child suite'
108
+
109
+ const grandchildSuite = new Suite(grandchildSuiteName, 'root').addTest(
110
+ new Test(testName, () => {})
111
+ )
112
+ const childSuite = new Suite(childSuiteName, 'root').addSuite(
113
+ grandchildSuite
114
+ )
115
+
116
+ new Suite(rootSuiteName, 'root').addSuite(childSuite)
117
+
118
+ const runner = createRunner(grandchildSuite)
119
+
120
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
121
+
122
+ runner.run(failures => {
123
+ expect(failures).toBe(0)
124
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
125
+ 'anvil_cypress_test_results.json',
126
+ {
127
+ test_results: [
128
+ {
129
+ status: 'passed',
130
+ duration: expect.any(Number),
131
+ scenario_name: `${rootSuiteName}/${childSuiteName}/${grandchildSuiteName}/${testName}`,
132
+ description: testName,
133
+ error: null,
134
+ backtrace: null,
135
+ test_type: 'integration',
136
+ pending_message: null,
137
+ tag: anvilTag
138
+ }
139
+ ]
140
+ }
141
+ )
142
+
143
+ done()
144
+ })
145
+ })
146
+
147
+ it('returns errors and backtrace when tests fail', done => {
148
+ const errorMessage = 'Expected foo, got bar'
149
+ const suite = new Suite(rootSuiteName, 'root').addTest(
150
+ new Test(testName, callback => {
151
+ callback(new AssertionError({ message: errorMessage }))
152
+ })
153
+ )
154
+ const runner = createRunner(suite)
155
+
156
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
157
+
158
+ runner.run(failures => {
159
+ expect(failures).toBe(1)
160
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
161
+ 'anvil_cypress_test_results.json',
162
+ {
163
+ test_results: [
164
+ {
165
+ status: 'failed',
166
+ duration: expect.any(Number),
167
+ scenario_name: `${rootSuiteName}/${testName}`,
168
+ description: testName,
169
+ error: errorMessage,
170
+ backtrace: expect.stringMatching(
171
+ new RegExp(
172
+ `AssertionError.*${errorMessage}.*at new AssertionError`,
173
+ 'gms'
174
+ )
175
+ ),
176
+ test_type: 'integration',
177
+ pending_message: null,
178
+ tag: anvilTag
179
+ }
180
+ ]
181
+ }
182
+ )
183
+
184
+ done()
185
+ })
186
+ })
187
+
188
+ it('supports retried tests', done => {
189
+ const suite = new Suite(rootSuiteName, 'root').retries(2).addTest(
190
+ new Test(testName, callback => {
191
+ expect(1).toBe(2)
192
+
193
+ callback()
194
+ })
195
+ )
196
+ const runner = createRunner(suite)
197
+
198
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
199
+
200
+ runner.run(failures => {
201
+ expect(failures).toBe(1)
202
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
203
+ 'anvil_cypress_test_results.json',
204
+ {
205
+ test_results: [
206
+ {
207
+ status: 'retried',
208
+ duration: expect.any(Number),
209
+ scenario_name: `${rootSuiteName}/${testName}`,
210
+ description: testName,
211
+ error: null,
212
+ backtrace: null,
213
+ test_type: 'integration',
214
+ pending_message: null,
215
+ tag: anvilTag
216
+ },
217
+ {
218
+ status: 'retried',
219
+ duration: expect.any(Number),
220
+ scenario_name: `${rootSuiteName}/${testName}`,
221
+ description: testName,
222
+ error: null,
223
+ backtrace: null,
224
+ test_type: 'integration',
225
+ pending_message: null,
226
+ tag: anvilTag
227
+ },
228
+ {
229
+ status: 'failed',
230
+ duration: expect.any(Number),
231
+ scenario_name: `${rootSuiteName}/${testName}`,
232
+ description: testName,
233
+ error: expect.anything(),
234
+ backtrace: expect.anything(),
235
+ test_type: 'integration',
236
+ pending_message: null,
237
+ tag: anvilTag
238
+ }
239
+ ]
240
+ }
241
+ )
242
+
243
+ done()
244
+ })
245
+ })
246
+
247
+ it('calls the anvil test result mapper', done => {
248
+ const anvilMapperSpy = jest.fn()
249
+
250
+ jest.mock('./anvil-results-helper', () => ({
251
+ anvilMapper: anvilMapperSpy,
252
+ generateAnvilResults: jest.fn()
253
+ }))
254
+ const newCypressAnvilReporter = require('./cypress-anvil-reporter')
255
+
256
+ const suite = new Suite('My suite', 'root').addTest(
257
+ new Test('My test', () => {})
258
+ )
259
+ const runner = createRunner(suite)
260
+
261
+ configureReporter(runner, newCypressAnvilReporter, { anvilTag })
262
+
263
+ runner.run(() => {
264
+ expect(anvilMapperSpy).toHaveBeenCalledTimes(1)
265
+
266
+ done()
267
+ })
268
+ })
269
+ })
@@ -0,0 +1,223 @@
1
+ const generateAnvilResultsMock = jest.fn()
2
+
3
+ jest.mock('./anvil-results-helper', () => ({
4
+ ...jest.requireActual('./anvil-results-helper'),
5
+ generateAnvilResults: generateAnvilResultsMock
6
+ }))
7
+
8
+ const { print } = require('@toptal/davinci-cli-shared')
9
+
10
+ const JestAnvilReporter = require('./jest-anvil-reporter')
11
+ const {
12
+ passedTestResults,
13
+ failedTestResults,
14
+ skippedTestResults,
15
+ combinedTestResults,
16
+ toDoTestResults
17
+ } = require('./test-result-mocks')
18
+
19
+ describe('Jest Anvil reporter', () => {
20
+ const expectedPassedTestResults = {
21
+ test_results: [
22
+ {
23
+ file_name:
24
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
25
+ line_number: 2,
26
+ status: 'passed',
27
+ duration: 0.003,
28
+ scenario_name: 'Example test suite/1 + 1 returns 2',
29
+ description: '1 + 1 returns 2',
30
+ error: null,
31
+ backtrace: null,
32
+ test_type: 'unit',
33
+ pending_message: null,
34
+ tag: 'platform'
35
+ }
36
+ ]
37
+ }
38
+
39
+ const expectedFailedTestResults = {
40
+ test_results: [
41
+ {
42
+ file_name:
43
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
44
+ line_number: 2,
45
+ status: 'failed',
46
+ duration: 0.006,
47
+ scenario_name: 'Example test suite/1 + 1 returns 3',
48
+ description: '1 + 1 returns 3',
49
+ error:
50
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m',
51
+ backtrace: [
52
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:3:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41'
53
+ ],
54
+ test_type: 'unit',
55
+ pending_message: null,
56
+ tag: 'platform'
57
+ }
58
+ ]
59
+ }
60
+
61
+ const expectedSkippedTestResults = {
62
+ test_results: [
63
+ {
64
+ file_name:
65
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
66
+ line_number: 2,
67
+ status: 'pending',
68
+ duration: 0,
69
+ scenario_name: 'Example test suite/1 + 1 returns 2',
70
+ description: '1 + 1 returns 2',
71
+ error: null,
72
+ backtrace: null,
73
+ test_type: 'unit',
74
+ pending_message: null,
75
+ tag: 'platform'
76
+ }
77
+ ]
78
+ }
79
+
80
+ const expectedToDoTestResults = {
81
+ test_results: [
82
+ {
83
+ file_name:
84
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
85
+ line_number: null,
86
+ status: 'pending',
87
+ duration: 0,
88
+ scenario_name: 'Example test suite/1+1 returns 2',
89
+ description: '1+1 returns 2',
90
+ error: null,
91
+ backtrace: null,
92
+ test_type: 'unit',
93
+ pending_message: null,
94
+ tag: 'platform'
95
+ }
96
+ ]
97
+ }
98
+
99
+ const expectedCombinedTestResults = {
100
+ test_results: [
101
+ {
102
+ file_name:
103
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
104
+ line_number: 2,
105
+ status: 'passed',
106
+ duration: 0.005,
107
+ scenario_name: 'Example test suite/1 + 1 returns 2 (passed)',
108
+ description: '1 + 1 returns 2 (passed)',
109
+ error: null,
110
+ backtrace: null,
111
+ test_type: 'unit',
112
+ pending_message: null,
113
+ tag: 'platform'
114
+ },
115
+ {
116
+ file_name:
117
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
118
+ line_number: 6,
119
+ status: 'failed',
120
+ duration: 0.007,
121
+ scenario_name: 'Example test suite/1 + 1 returns 3 (failed)',
122
+ description: '1 + 1 returns 3 (failed)',
123
+ error:
124
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m',
125
+ backtrace: [
126
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:7:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41'
127
+ ],
128
+ test_type: 'unit',
129
+ pending_message: null,
130
+ tag: 'platform'
131
+ },
132
+ {
133
+ file_name:
134
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
135
+ line_number: 10,
136
+ status: 'pending',
137
+ duration: 0,
138
+ scenario_name: 'Example test suite/1 + 1 returns 2 (skipped)',
139
+ description: '1 + 1 returns 2 (skipped)',
140
+ error: null,
141
+ backtrace: null,
142
+ test_type: 'unit',
143
+ pending_message: null,
144
+ tag: 'platform'
145
+ }
146
+ ]
147
+ }
148
+
149
+ beforeEach(() => {
150
+ jest.resetModules()
151
+ jest.resetAllMocks()
152
+ })
153
+
154
+ it.each`
155
+ input | expectedOutput | testType
156
+ ${passedTestResults} | ${expectedPassedTestResults} | ${'passing tests'}
157
+ ${failedTestResults} | ${expectedFailedTestResults} | ${'failing tests'}
158
+ ${skippedTestResults} | ${expectedSkippedTestResults} | ${'skipped tests'}
159
+ ${toDoTestResults} | ${expectedToDoTestResults} | ${'todo tests'}
160
+ ${combinedTestResults} | ${expectedCombinedTestResults} | ${'a combination of test results'}
161
+ `(
162
+ 'calls generateAnvilResults with a payload matching Anvil for $testType',
163
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
164
+ ({ input, expectedOutput, testType }) => {
165
+ const jestAnvilReporter = new JestAnvilReporter(undefined, {
166
+ anvilTag: 'platform'
167
+ })
168
+
169
+ jestAnvilReporter.onRunComplete(undefined, input)
170
+
171
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
172
+ 'anvil_jest_test_results.json',
173
+ expectedOutput
174
+ )
175
+ }
176
+ )
177
+
178
+ it('prints warning and exits when anvilTag is not defined', () => {
179
+ const printSpy = jest.spyOn(print, 'yellow')
180
+ const jestAnvilReporter = new JestAnvilReporter(undefined, {
181
+ anvilTag: undefined
182
+ })
183
+
184
+ jestAnvilReporter.onRunComplete(undefined, passedTestResults)
185
+
186
+ expect(printSpy).toHaveBeenCalledWith(
187
+ 'Missing --anvilTag value! Anvil test results will be skipped.'
188
+ )
189
+ expect(generateAnvilResultsMock).not.toHaveBeenCalled()
190
+ })
191
+
192
+ it('calls the anvil mapper', () => {
193
+ const anvilMapperSpy = jest.fn()
194
+
195
+ jest.mock('./anvil-results-helper', () => ({
196
+ anvilMapper: anvilMapperSpy,
197
+ generateAnvilResults: jest.fn()
198
+ }))
199
+ const newJestAnvilReporter = require('./jest-anvil-reporter')
200
+
201
+ const jestAnvilReporter = new newJestAnvilReporter(undefined, {
202
+ anvilTag: 'platform'
203
+ })
204
+
205
+ jestAnvilReporter.onRunComplete(undefined, passedTestResults)
206
+
207
+ expect(anvilMapperSpy).toHaveBeenCalledWith({
208
+ backtrace: null,
209
+ description: '1 + 1 returns 2',
210
+ durationInSeconds: expect.any(Number),
211
+ error: null,
212
+ fileName: expect.stringContaining(
213
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js'
214
+ ),
215
+ lineNumber: expect.any(Number),
216
+ pendingMessage: null,
217
+ scenarioName: 'Example test suite/1 + 1 returns 2',
218
+ status: 'passed',
219
+ tag: 'platform',
220
+ testType: 'unit'
221
+ })
222
+ })
223
+ })
@@ -0,0 +1,59 @@
1
+ const getPackageFileContentMock = jest.fn()
2
+
3
+ jest.mock('@toptal/davinci-cli-shared', () => ({
4
+ files: {
5
+ getPackageFileContent: getPackageFileContentMock
6
+ }
7
+ }))
8
+
9
+ const jestArgsToCLIRules = require('./jest-args-to-cli-converters')
10
+
11
+ describe('jestArgsToCLIRules "reporters"', () => {
12
+ const reportersRule = jestArgsToCLIRules['reporters']
13
+
14
+ it('converts correctly arrays', () => {
15
+ expect(reportersRule(['reporter1', 'reporter2'])).toEqual([
16
+ 'reporter1',
17
+ 'reporter2'
18
+ ])
19
+ })
20
+
21
+ it('converts correctly string', () => {
22
+ expect(reportersRule('reporter1')).toEqual(['reporter1'])
23
+ })
24
+ })
25
+
26
+ describe('jestArgsToCLIRules "setupFilesAfterEnv"', () => {
27
+ const setupFilesAfterEnvRule = jestArgsToCLIRules['setupFilesAfterEnv']
28
+
29
+ it('takes default values from the davinci jest.config.js file', () => {
30
+ getPackageFileContentMock.mockReturnValueOnce({
31
+ setupFilesAfterEnv: ['davinci-setup-files']
32
+ })
33
+
34
+ expect(setupFilesAfterEnvRule()).toEqual(['davinci-setup-files'])
35
+ })
36
+
37
+ it('merges default values with passed setup files', () => {
38
+ getPackageFileContentMock.mockReturnValueOnce({
39
+ setupFilesAfterEnv: ['davinci-setup-files']
40
+ })
41
+
42
+ expect(setupFilesAfterEnvRule(['file1', 'file2'])).toEqual([
43
+ 'davinci-setup-files',
44
+ 'file1',
45
+ 'file2'
46
+ ])
47
+ })
48
+
49
+ it('merges default values with single passed setup file', () => {
50
+ getPackageFileContentMock.mockReturnValueOnce({
51
+ setupFilesAfterEnv: ['davinci-setup-files']
52
+ })
53
+
54
+ expect(setupFilesAfterEnvRule('file1')).toEqual([
55
+ 'davinci-setup-files',
56
+ 'file1'
57
+ ])
58
+ })
59
+ })
@@ -0,0 +1,81 @@
1
+ /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "errorTest", "successTest"] }] */
2
+ const styledComponentsVersionCheck = require('./styled-components-version-check')
3
+
4
+ const successTest = (styledComponentsVersion, dependenciesKeys) => {
5
+ const packageJson = {}
6
+
7
+ dependenciesKeys.forEach(dependenciesKey => {
8
+ packageJson[dependenciesKey] = {
9
+ 'styled-components': styledComponentsVersion
10
+ }
11
+ })
12
+
13
+ let error = null
14
+
15
+ try {
16
+ styledComponentsVersionCheck(packageJson)
17
+ } catch (e) {
18
+ error = e
19
+ }
20
+
21
+ expect(error).toBeNull()
22
+ }
23
+
24
+ const errorTest = (styledComponentsVersion, dependenciesKeys) => {
25
+ const packageJson = {}
26
+
27
+ dependenciesKeys.forEach(dependenciesKey => {
28
+ packageJson[dependenciesKey] = {
29
+ 'styled-components': styledComponentsVersion
30
+ }
31
+ })
32
+
33
+ let error = null
34
+
35
+ try {
36
+ styledComponentsVersionCheck(packageJson)
37
+ } catch (e) {
38
+ error = e
39
+ }
40
+
41
+ expect(error).not.toBeNull()
42
+ expect(error.message).toContain(styledComponentsVersion)
43
+ }
44
+
45
+ describe('styled-components version check', () => {
46
+ describe('has wrong (^4.4.1) SC version in dependencies', () => {
47
+ it('should throw an error', () => {
48
+ errorTest('^4.4.1', ['dependencies'])
49
+ })
50
+ })
51
+
52
+ describe('has correct (^5.0.1) SC version in dependencies', () => {
53
+ it('should NOT throw an error', () => {
54
+ successTest('^5.0.1', ['dependencies'])
55
+ })
56
+ })
57
+
58
+ describe('has wrong (^4.4.1) SC version in devDependencies and peerDependencies', () => {
59
+ it('should throw an error', () => {
60
+ errorTest('^4.4.1', ['devDependencies', 'peerDependencies'])
61
+ })
62
+ })
63
+
64
+ describe('has correct (^5.0.1) SC version in devDependencies and peerDependencies', () => {
65
+ it('should NOT throw an error', () => {
66
+ successTest('^5.0.1', ['devDependencies', 'peerDependencies'])
67
+ })
68
+ })
69
+
70
+ describe('has wrong (^4.4.1) SC version in devDependencies', () => {
71
+ it('should throw an error', () => {
72
+ errorTest('^4.4.1', ['devDependencies'])
73
+ })
74
+ })
75
+
76
+ describe('has correct (^5.0.1) SC version in devDependencies', () => {
77
+ it('should NOT throw an error', () => {
78
+ successTest('^5.0.1', ['devDependencies'])
79
+ })
80
+ })
81
+ })
@@ -0,0 +1,32 @@
1
+ const toJestCLIArguments = require('./to-jest-cli-arguments')
2
+
3
+ const EXAMPLE_REPORTER = 'some-jest-reporter'
4
+ const ARGS_TO_CLI_RESULT = [EXAMPLE_REPORTER]
5
+
6
+ jest.mock('./jest-args-to-cli-converters.js', () => ({
7
+ reporters: () => ARGS_TO_CLI_RESULT
8
+ }))
9
+
10
+ describe('toJestCLIArguments', () => {
11
+ it('converts boolean strings to booleans correctly', () => {
12
+ expect(
13
+ toJestCLIArguments({
14
+ cache: 'false',
15
+ ci: 'true'
16
+ })
17
+ ).toEqual({
18
+ cache: false,
19
+ ci: true
20
+ })
21
+ })
22
+
23
+ it('converts some rules by using args-to-cli rules', () => {
24
+ expect(
25
+ toJestCLIArguments({
26
+ reporters: EXAMPLE_REPORTER
27
+ })
28
+ ).toEqual({
29
+ reporters: ARGS_TO_CLI_RESULT
30
+ })
31
+ })
32
+ })