find-cypress-specs 1.12.0 → 1.14.0

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # find-cypress-specs [![renovate-app badge][renovate-badge]][renovate-app] ![cypress version](https://img.shields.io/badge/cypress-9.4.1-brightgreen) [![ci](https://github.com/bahmutov/find-cypress-specs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bahmutov/find-cypress-specs/actions/workflows/ci.yml)
1
+ # find-cypress-specs [![renovate-app badge][renovate-badge]][renovate-app] ![cypress version](https://img.shields.io/badge/cypress-9.5.1-brightgreen) [![ci](https://github.com/bahmutov/find-cypress-specs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bahmutov/find-cypress-specs/actions/workflows/ci.yml)
2
2
 
3
3
  > Find Cypress spec files using the config settings
4
4
 
@@ -70,6 +70,21 @@ Each tag count includes the tests that use the tag directly, and the _effective_
70
70
 
71
71
  You can print the results in JSON format using `--json` or `-j` option.
72
72
 
73
+ ## Test names filtered by a tag
74
+
75
+ ```bash
76
+ $ npx find-cypress-specs --names --tagged <single tag>
77
+ # finds all specs and tests, then filters the output by a single tag
78
+ ```
79
+
80
+ ## Test names filtered by multiple tags
81
+
82
+ ```bash
83
+ $ npx find-cypress-specs --names --tagged <tag1>,<tag2>,<tag3>,...
84
+ # finds all specs and tests, then filters the output showing all tests
85
+ # tagged with tag1 or tag2 or tag3 or ...
86
+ ```
87
+
73
88
  ## Details
74
89
 
75
90
  Cypress uses the resolved [configuration values](https://on.cypress.io/configuration) to find the spec files to run. It searches the `integrationFolder` for all patterns listed in `testFiles` and removes any files matching the `ignoreTestFiles` patterns.
package/bin/find.js CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  const arg = require('arg')
4
4
  const { getSpecs, collectResults, findChangedFiles } = require('../src')
5
+ const { pickTaggedTestsFrom } = require('../src/tagged')
6
+ const { addCounts } = require('../src/count')
7
+ const { stringAllInfo } = require('../src/print')
8
+
5
9
  const fs = require('fs')
6
- const pluralize = require('pluralize')
7
- const { getTestNames, formatTestList, countTags } = require('find-test-names')
10
+ const { getTestNames, countTags } = require('find-test-names')
8
11
  const consoleTable = require('console.table')
9
12
  const debug = require('debug')('find-cypress-specs')
10
13
 
@@ -15,7 +18,9 @@ const args = arg({
15
18
  '--json': Boolean,
16
19
  // find the specs that have changed against this Git branch
17
20
  '--branch': String,
18
- '--count': Number,
21
+ '--count': Boolean,
22
+ // filter all tests to those that have the given tag
23
+ '--tagged': String,
19
24
 
20
25
  // aliases
21
26
  '-n': '--names',
@@ -34,43 +39,23 @@ if (args['--names'] || args['--tags']) {
34
39
  console.log('no specs found')
35
40
  } else {
36
41
  console.log('')
37
- let testsN = 0
38
- let pendingTestsN = 0
39
-
40
42
  // counts the number of tests for each tag across all specs
41
43
  const tagTestCounts = {}
42
-
43
44
  const jsonResults = {}
44
45
 
45
46
  specs.forEach((filename) => {
46
- jsonResults[filename] = []
47
+ jsonResults[filename] = {
48
+ counts: {
49
+ tests: 0,
50
+ pending: 0,
51
+ },
52
+ tests: [],
53
+ }
47
54
  const source = fs.readFileSync(filename, 'utf8')
48
55
  const result = getTestNames(source, true)
49
56
  // enable if need to debug the parsed test
50
57
  // console.dir(result.structure, { depth: null })
51
-
52
- testsN += result.testCount
53
- const testCount = pluralize('test', result.testNames.length, true)
54
- pendingTestsN += result.pendingTestCount
55
-
56
- if (args['--names']) {
57
- if (args['--json']) {
58
- collectResults(result.structure, jsonResults[filename])
59
- } else {
60
- if (result.pendingTestCount) {
61
- console.log(
62
- '%s (%s, %d pending)',
63
- filename,
64
- testCount,
65
- result.pendingTestCount,
66
- )
67
- } else {
68
- console.log('%s (%s)', filename, testCount)
69
- }
70
- console.log(formatTestList(result.structure))
71
- console.log('')
72
- }
73
- }
58
+ collectResults(result.structure, jsonResults[filename].tests)
74
59
 
75
60
  if (args['--tags']) {
76
61
  const specTagCounts = countTags(result.structure)
@@ -84,24 +69,26 @@ if (args['--names'] || args['--tags']) {
84
69
  }
85
70
  })
86
71
 
72
+ addCounts(jsonResults)
73
+
87
74
  if (args['--names']) {
75
+ if (args['--tagged']) {
76
+ // filter all collected tests to those that have the given tag(s)
77
+ const splitTags = args['--tagged']
78
+ .split(',')
79
+ .map((s) => s.trim())
80
+ .filter(Boolean)
81
+ debug('filtering all tests by tag "%o"', splitTags)
82
+ pickTaggedTestsFrom(jsonResults, splitTags)
83
+ // recompute the number of tests
84
+ addCounts(jsonResults)
85
+ }
86
+
88
87
  if (args['--json']) {
89
88
  console.log(JSON.stringify(jsonResults, null, 2))
90
89
  } else {
91
- if (pendingTestsN) {
92
- console.log(
93
- 'found %s (%s, %d pending)',
94
- pluralize('spec', specs.length, true),
95
- pluralize('test', testsN, true),
96
- pendingTestsN,
97
- )
98
- } else {
99
- console.log(
100
- 'found %s (%s)',
101
- pluralize('spec', specs.length, true),
102
- pluralize('test', testsN, true),
103
- )
104
- }
90
+ const str = stringAllInfo(jsonResults)
91
+ console.log(str)
105
92
  }
106
93
  console.log('')
107
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "find-cypress-specs",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Find Cypress spec files using the config settings",
5
5
  "main": "src",
6
6
  "files": [
@@ -20,7 +20,9 @@
20
20
  "demo-names-and-tags": "node ./bin/find --names --tags",
21
21
  "demo-names-and-tags-json": "node ./bin/find --names --tags --json",
22
22
  "demo-names-json": "node ./bin/find --names --json",
23
+ "demo-names-tagged": "node ./bin/find --names --tagged @user",
23
24
  "print-changed-specs": "node ./bin/find --branch main",
25
+ "count-changed-specs": "node ./bin/find --branch main --count",
24
26
  "semantic-release": "semantic-release"
25
27
  },
26
28
  "repository": {
@@ -38,7 +40,7 @@
38
40
  "homepage": "https://github.com/bahmutov/find-cypress-specs#readme",
39
41
  "devDependencies": {
40
42
  "ava": "^4.0.0",
41
- "cypress": "9.4.1",
43
+ "cypress": "9.5.1",
42
44
  "execa-wrap": "^1.4.0",
43
45
  "prettier": "^2.5.1",
44
46
  "semantic-release": "19.0.2"
package/src/count.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * For each file and each
3
+ */
4
+ function addCounts(json) {
5
+ Object.keys(json).forEach((filename) => {
6
+ const fileInfo = json[filename]
7
+ if (!fileInfo.counts) {
8
+ fileInfo.counts = {
9
+ tests: 0,
10
+ pending: 0,
11
+ }
12
+ }
13
+
14
+ fileInfo.counts.tests =
15
+ countTests(fileInfo.tests) + countTests(fileInfo.suites)
16
+ fileInfo.counts.pending =
17
+ countPendingTests(fileInfo.tests) + countPendingTests(fileInfo.suites)
18
+ })
19
+ }
20
+
21
+ function countTests(testsOrSuites) {
22
+ if (!testsOrSuites) {
23
+ return 0
24
+ }
25
+
26
+ return testsOrSuites.reduce((count, test) => {
27
+ if (test.type === 'test') {
28
+ return count + 1
29
+ } else if (test.type === 'suite') {
30
+ return count + countTests(test.tests) + countTests(test.suites)
31
+ }
32
+ }, 0)
33
+ }
34
+
35
+ function countPendingTests(testsOrSuites) {
36
+ if (!testsOrSuites) {
37
+ return 0
38
+ }
39
+
40
+ return testsOrSuites.reduce((count, test) => {
41
+ if (test.type === 'test') {
42
+ return test.pending ? count + 1 : count
43
+ } else if (test.type === 'suite') {
44
+ if (test.pending) {
45
+ // all tests inside should count as pending
46
+ return count + countTests(test.tests) + countTests(test.suites)
47
+ }
48
+
49
+ return (
50
+ count + countPendingTests(test.tests) + countPendingTests(test.suites)
51
+ )
52
+ }
53
+ }, 0)
54
+ }
55
+
56
+ module.exports = { addCounts }
package/src/print.js ADDED
@@ -0,0 +1,50 @@
1
+ const pluralize = require('pluralize')
2
+ const { formatTestList } = require('find-test-names')
3
+
4
+ /**
5
+ * Outputs a string representation of the json test results object,
6
+ * like a tree of suites and tests.
7
+ */
8
+ function stringFileTests(fileName, fileInfo) {
9
+ const testCount = pluralize('test', fileInfo.counts.tests, true)
10
+ const headerLine = fileInfo.counts.pending
11
+ ? `${fileName} (${testCount}, ${fileInfo.counts.pending} pending)`
12
+ : `${fileName} (${testCount})`
13
+
14
+ const body = formatTestList(fileInfo.tests)
15
+
16
+ return headerLine + '\n' + body + '\n'
17
+ }
18
+
19
+ function stringAllInfo(allInfo) {
20
+ let fileCount = 0
21
+ let testCount = 0
22
+ let pendingTestCount = 0
23
+
24
+ const allInfoString = Object.keys(allInfo)
25
+ .map((fileName) => {
26
+ const fileInfo = allInfo[fileName]
27
+ fileCount += 1
28
+ testCount += fileInfo.counts.tests
29
+ pendingTestCount += fileInfo.counts.pending
30
+ return stringFileTests(fileName, fileInfo)
31
+ })
32
+ .join('\n')
33
+
34
+ // footer line is something like
35
+ // found 2 specs (4 tests, 1 pending)
36
+ let footer = `found ${pluralize('spec', fileCount, true)}`
37
+ const testWord = pluralize('test', testCount, true)
38
+ if (pendingTestCount) {
39
+ footer += ` (${testWord}, ${pendingTestCount} pending)`
40
+ } else {
41
+ footer += ` (${testWord})`
42
+ }
43
+
44
+ return allInfoString + '\n' + footer
45
+ }
46
+
47
+ module.exports = {
48
+ stringFileTests,
49
+ stringAllInfo,
50
+ }
package/src/tagged.js ADDED
@@ -0,0 +1,72 @@
1
+ const { addCounts } = require('./count')
2
+
3
+ function arraysOverlap(a, b) {
4
+ if (!Array.isArray(a) || !Array.isArray(b)) {
5
+ return false
6
+ }
7
+
8
+ if (a.length < b.length) {
9
+ return a.some((item) => b.includes(item))
10
+ } else {
11
+ return b.some((item) => a.includes(item))
12
+ }
13
+ }
14
+
15
+ // note: modifies the tests in place
16
+ function pickTaggedTests(tests, tag) {
17
+ if (!Array.isArray(tests)) {
18
+ return false
19
+ }
20
+ const tags = Array.isArray(tag) ? tag : [tag]
21
+ const filteredTests = tests.filter((test) => {
22
+ if (test.type === 'test') {
23
+ return test.tags && arraysOverlap(test.tags, tags)
24
+ } else if (test.type === 'suite') {
25
+ if (test.tags && arraysOverlap(test.tags, tags)) {
26
+ return true
27
+ }
28
+
29
+ // maybe there is some test inside this suite
30
+ // with the tag? Filter all other tests
31
+ return (
32
+ pickTaggedTests(test.tests, tags) || pickTaggedTests(test.suites, tags)
33
+ )
34
+ }
35
+ })
36
+ tests.length = 0
37
+ tests.push(...filteredTests)
38
+ return filteredTests.length > 0
39
+ }
40
+
41
+ function removeEmptyNodes(json) {
42
+ Object.keys(json).forEach((filename) => {
43
+ const fileTests = json[filename].tests
44
+ if (!fileTests.length) {
45
+ delete json[filename]
46
+ }
47
+ })
48
+ return json
49
+ }
50
+
51
+ /**
52
+ * Takes an object of tests collected from all files,
53
+ * and removes all tests that do not have the given tag applied.
54
+ * Modifies the given object in place.
55
+ */
56
+ function pickTaggedTestsFrom(json, tag) {
57
+ Object.keys(json).forEach((filename) => {
58
+ const fileTests = json[filename].tests
59
+ pickTaggedTests(fileTests, tag)
60
+ })
61
+
62
+ const result = removeEmptyNodes(json)
63
+ addCounts(result)
64
+ return result
65
+ }
66
+
67
+ module.exports = {
68
+ arraysOverlap,
69
+ pickTaggedTestsFrom,
70
+ removeEmptyNodes,
71
+ pickTaggedTests,
72
+ }