@systemfsoftware/stryker-js-typescript-checker 1.2.3 → 1.3.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +203 -21
  3. package/README.md +31 -0
  4. package/dist/index.d.mts +19 -19
  5. package/dist/index.mjs +131 -119
  6. package/package.json +22 -9
  7. package/.turbo/turbo-build.log +0 -21
  8. package/AGENTS.md +0 -7
  9. package/oxlint.config.ts +0 -9
  10. package/src/fs/hybrid-file-system.ts +0 -118
  11. package/src/fs/index.ts +0 -2
  12. package/src/fs/script-file.ts +0 -44
  13. package/src/grouping/create-groups.ts +0 -77
  14. package/src/grouping/ts-file-node.ts +0 -54
  15. package/src/index.ts +0 -18
  16. package/src/plugin-tokens.ts +0 -2
  17. package/src/tsconfig-helpers.ts +0 -187
  18. package/src/typescript-checker-options-with-stryker-options.ts +0 -9
  19. package/src/typescript-checker.ts +0 -223
  20. package/src/typescript-compiler.ts +0 -412
  21. package/test/integration/e2e-plugin-entry.it.spec.ts +0 -153
  22. package/test/integration/nodenext-project.it.spec.ts +0 -101
  23. package/test/integration/project-references.it.spec.ts +0 -146
  24. package/test/integration/project-with-ts-buildinfo.it.spec.ts +0 -92
  25. package/test/integration/single-project.it.spec.ts +0 -298
  26. package/test/integration/tsconfig-helpers.it.spec.ts +0 -218
  27. package/test/integration/typescript-checkers-errors.it.spec.ts +0 -112
  28. package/test/unit/fs/hybrid-file-system.spec.ts +0 -109
  29. package/test/unit/grouping/create-groups.spec.ts +0 -122
  30. package/test/unit/grouping/ts-file-node.spec.ts +0 -132
  31. package/testResources/errors/compile-error/add.ts +0 -3
  32. package/testResources/errors/compile-error/tsconfig.json +0 -5
  33. package/testResources/errors/empty-dir/.gitkeep +0 -0
  34. package/testResources/errors/invalid-tsconfig/tsconfig.json +0 -1
  35. package/testResources/nodenext-project/package.json +0 -6
  36. package/testResources/nodenext-project/src/index.ts +0 -5
  37. package/testResources/nodenext-project/src/util.ts +0 -3
  38. package/testResources/nodenext-project/tsconfig.json +0 -16
  39. package/testResources/project-references/src/index.ts +0 -5
  40. package/testResources/project-references/src/job.ts +0 -6
  41. package/testResources/project-references/src/tsconfig.json +0 -10
  42. package/testResources/project-references/tsconfig.root.json +0 -7
  43. package/testResources/project-references/tsconfig.settings.json +0 -15
  44. package/testResources/project-references/utils/math.ts +0 -3
  45. package/testResources/project-references/utils/text.ts +0 -3
  46. package/testResources/project-references/utils/tsconfig.json +0 -7
  47. package/testResources/project-with-ts-buildinfo/src/index.ts +0 -3
  48. package/testResources/project-with-ts-buildinfo/tsconfig.json +0 -15
  49. package/testResources/single-project/src/counter.ts +0 -9
  50. package/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts +0 -9
  51. package/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts +0 -7
  52. package/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts +0 -11
  53. package/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts +0 -22
  54. package/testResources/single-project/src/not-type-checked.js +0 -1
  55. package/testResources/single-project/src/todo.spec.ts +0 -11
  56. package/testResources/single-project/src/todo.ts +0 -22
  57. package/testResources/single-project/tsconfig.json +0 -15
  58. package/tsconfig.json +0 -26
  59. package/tsdown.config.ts +0 -16
  60. package/vitest.config.ts +0 -13
@@ -1,218 +0,0 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from 'fs'
2
- import { tmpdir } from 'os'
3
- import path from 'path'
4
-
5
- import { Either } from 'effect'
6
- import { afterEach, describe, expect, it } from 'vitest'
7
-
8
- import {
9
- determineBuildModeEnabled,
10
- overrideOptions,
11
- parseTsConfig,
12
- TsConfigParseError,
13
- } from '../../src/tsconfig-helpers.js'
14
-
15
- function expectRight<A, E>(either: Either.Either<A, E>): A {
16
- if (Either.isLeft(either)) {
17
- throw new Error(`Expected a Right result, got a Left: ${String(either.left)}`)
18
- }
19
- return either.right
20
- }
21
-
22
- describe('parseTsConfig', () => {
23
- it('should preserve a glob pattern in include exactly', () => {
24
- const config = expectRight(
25
- parseTsConfig('tsconfig.json', '{"include":["src/**/*.workflow.ts"]}'),
26
- )
27
- expect(config).toEqual({ include: ['src/**/*.workflow.ts'] })
28
- })
29
-
30
- it('should round-trip a $schema URL unchanged', () => {
31
- const config = expectRight(
32
- parseTsConfig('tsconfig.json', '{"$schema":"https://json.schemastore.org/tsconfig"}'),
33
- )
34
- expect(config).toEqual({
35
- $schema: 'https://json.schemastore.org/tsconfig',
36
- })
37
- })
38
-
39
- it('should round-trip a Windows path and an escaped quote unchanged', () => {
40
- const config = expectRight(
41
- parseTsConfig(
42
- 'tsconfig.json',
43
- '{"paths":{"a":["C:\\\\x//y"]},"description":"he said \\"hi\\""}',
44
- ),
45
- )
46
- expect(config).toEqual({
47
- paths: { a: ['C:\\x//y'] },
48
- description: 'he said "hi"',
49
- })
50
- })
51
-
52
- it('should accept trailing commas in nested objects and arrays', () => {
53
- const config = expectRight(
54
- parseTsConfig(
55
- 'tsconfig.json',
56
- '{"compilerOptions":{"strict":true,},"include":["a.ts","b.ts",]}',
57
- ),
58
- )
59
- expect(config).toEqual({
60
- compilerOptions: { strict: true },
61
- include: ['a.ts', 'b.ts'],
62
- })
63
- })
64
-
65
- it('should accept content prefixed with a BOM', () => {
66
- const withoutBom = expectRight(
67
- parseTsConfig('tsconfig.json', '{"compilerOptions":{"strict":true}}'),
68
- )
69
- const withBom = expectRight(
70
- parseTsConfig('tsconfig.json', '\uFEFF{"compilerOptions":{"strict":true}}'),
71
- )
72
- expect(withBom).toEqual(withoutBom)
73
- })
74
-
75
- it('should strip line and block comments', () => {
76
- const config = expectRight(
77
- parseTsConfig(
78
- 'tsconfig.json',
79
- '{\n// a line comment\n"a": 1,\n/* a block comment */\n"b": 2\n}',
80
- ),
81
- )
82
- expect(config).toEqual({ a: 1, b: 2 })
83
- })
84
-
85
- it('should preserve unknown nested keys inside compilerOptions', () => {
86
- const config = expectRight(
87
- parseTsConfig(
88
- 'tsconfig.json',
89
- '{"compilerOptions":{"strict":true,"experimentalDecorators":true}}',
90
- ),
91
- )
92
- expect(config.compilerOptions).toEqual({
93
- strict: true,
94
- experimentalDecorators: true,
95
- })
96
- })
97
-
98
- it('should return a Left for a malformed document', () => {
99
- const result = parseTsConfig('tsconfig.json', '{ "a": }')
100
- expect(Either.isLeft(result)).toBe(true)
101
- if (Either.isLeft(result)) {
102
- expect(result.left).toBeInstanceOf(TsConfigParseError)
103
- expect(result.left.file).toBe('tsconfig.json')
104
- expect(result.left.reason.length).toBeGreaterThan(0)
105
- }
106
- })
107
-
108
- it('should return a Left for truncated input', () => {
109
- for (const truncated of ['{', '{"a":']) {
110
- expect(Either.isLeft(parseTsConfig('tsconfig.json', truncated))).toBe(true)
111
- }
112
- })
113
-
114
- it('should return a Left for an unterminated block comment', () => {
115
- const result = parseTsConfig('tsconfig.json', '{"a":1,/* never closed')
116
- expect(Either.isLeft(result)).toBe(true)
117
- })
118
-
119
- it('should return a Left for a bare string root (not a config object)', () => {
120
- const result = parseTsConfig('tsconfig.json', '"just a string"')
121
- expect(Either.isLeft(result)).toBe(true)
122
- if (Either.isLeft(result)) {
123
- expect(result.left).toBeInstanceOf(TsConfigParseError)
124
- expect(result.left.file).toBe('tsconfig.json')
125
- expect(result.left.reason.length).toBeGreaterThan(0)
126
- }
127
- })
128
-
129
- it('should return a Left when references is not an array', () => {
130
- const result = parseTsConfig('tsconfig.json', '{"references":"nope"}')
131
- expect(Either.isLeft(result)).toBe(true)
132
- if (Either.isLeft(result)) {
133
- expect(result.left).toBeInstanceOf(TsConfigParseError)
134
- expect(result.left.reason.length).toBeGreaterThan(0)
135
- }
136
- })
137
- })
138
-
139
- describe('overrideOptions', () => {
140
- it('should apply the compiler-option overrides to an empty config', () => {
141
- const parsed = JSON.parse(overrideOptions({}, false))
142
- expect(parsed.compilerOptions).toMatchObject({
143
- allowUnreachableCode: true,
144
- noEmit: true,
145
- })
146
- })
147
-
148
- it('should not inject a moduleResolution that contradicts the consumer module', () => {
149
- const config = expectRight(
150
- parseTsConfig('tsconfig.json', '{"compilerOptions":{"module":"NodeNext"}}'),
151
- )
152
- const output = JSON.parse(overrideOptions(config, false))
153
- expect(output.compilerOptions.module).toBe('NodeNext')
154
- expect(output.compilerOptions.moduleResolution).toBeUndefined()
155
- })
156
-
157
- it('should preserve the consumer target and moduleResolution verbatim', () => {
158
- const config = expectRight(
159
- parseTsConfig(
160
- 'tsconfig.json',
161
- '{"compilerOptions":{"target":"es2024","moduleResolution":"NodeNext"}}',
162
- ),
163
- )
164
- const output = JSON.parse(overrideOptions(config, false))
165
- expect(output.compilerOptions.target).toBe('es2024')
166
- expect(output.compilerOptions.moduleResolution).toBe('NodeNext')
167
- })
168
-
169
- it('should preserve unknown top-level keys and unknown compilerOptions through to the output', () => {
170
- const config = expectRight(
171
- parseTsConfig(
172
- 'tsconfig.json',
173
- '{"include":["src/**/*.ts"],"compilerOptions":{"strict":true,"experimentalDecorators":true}}',
174
- ),
175
- )
176
- const output = JSON.parse(overrideOptions(config, false))
177
- expect(output.include).toEqual(['src/**/*.ts'])
178
- expect(output.compilerOptions.strict).toBe(true)
179
- expect(output.compilerOptions.experimentalDecorators).toBe(true)
180
- })
181
- })
182
-
183
- describe('determineBuildModeEnabled', () => {
184
- let tempDir: string | undefined
185
-
186
- afterEach(() => {
187
- if (tempDir !== undefined) {
188
- rmSync(tempDir, { recursive: true, force: true })
189
- }
190
- })
191
-
192
- function createTsconfig(content: string): string {
193
- tempDir = mkdtempSync(path.join(tmpdir(), 'tsconfig-helpers-'))
194
- const tsconfigPath = path.join(tempDir, 'tsconfig.json')
195
- writeFileSync(tsconfigPath, content)
196
- return tsconfigPath
197
- }
198
-
199
- it('should return true when the config has references', () => {
200
- const tsconfigPath = createTsconfig('{"references":[{"path":"./a"}]}')
201
- expect(determineBuildModeEnabled(tsconfigPath)).toBe(true)
202
- })
203
-
204
- it('should return false when the config has no references', () => {
205
- const tsconfigPath = createTsconfig('{"compilerOptions":{"strict":true}}')
206
- expect(determineBuildModeEnabled(tsconfigPath)).toBe(false)
207
- })
208
-
209
- it('should return false when the config is a bare string instead of throwing a TypeError', () => {
210
- const tsconfigPath = createTsconfig('"just a string"')
211
- expect(determineBuildModeEnabled(tsconfigPath)).toBe(false)
212
- })
213
-
214
- it('should return false when references is not an array', () => {
215
- const tsconfigPath = createTsconfig('{"references":"nope"}')
216
- expect(determineBuildModeEnabled(tsconfigPath)).toBe(false)
217
- })
218
- })
@@ -1,112 +0,0 @@
1
- import path from 'path'
2
- import { fileURLToPath } from 'url'
3
-
4
- import type { StrykerOptions } from '@stryker-mutator/api/core'
5
- import type { Logger } from '@stryker-mutator/api/logging'
6
- import { describe, expect, it, vi } from 'vitest'
7
-
8
- import { HybridFileSystem } from '../../src/fs/hybrid-file-system.js'
9
- import { TypescriptChecker } from '../../src/typescript-checker.js'
10
- import { TypescriptCompiler } from '../../src/typescript-compiler.js'
11
-
12
- const resolveTestResource = path.resolve.bind(
13
- path,
14
- path.dirname(fileURLToPath(import.meta.url)),
15
- '..',
16
- '..',
17
- 'testResources',
18
- 'errors',
19
- ) as unknown as typeof path.resolve
20
-
21
- function createLogger(warn: Logger['warn'] = () => {}): Logger {
22
- return {
23
- isTraceEnabled: () => false,
24
- isDebugEnabled: () => false,
25
- isInfoEnabled: () => false,
26
- isWarnEnabled: () => false,
27
- isErrorEnabled: () => false,
28
- isFatalEnabled: () => false,
29
- trace: () => {},
30
- debug: () => {},
31
- info: () => {},
32
- warn,
33
- error: () => {},
34
- fatal: () => {},
35
- }
36
- }
37
-
38
- function createChecker(
39
- tsconfigFile: string,
40
- logger: Logger = createLogger(),
41
- ): TypescriptChecker {
42
- const options = {
43
- tsconfigFile,
44
- typescriptChecker: { prioritizePerformanceOverAccuracy: true },
45
- } as unknown as StrykerOptions
46
- const fileSystem = new HybridFileSystem()
47
- const compiler = new TypescriptCompiler(logger, options, fileSystem)
48
- return new TypescriptChecker(logger, options, compiler)
49
- }
50
-
51
- describe('Typescript checker errors', () => {
52
- it('should reject initialization if initial compilation failed', async () => {
53
- const sut = createChecker(
54
- resolveTestResource('compile-error', 'tsconfig.json'),
55
- )
56
- await expect(sut.init()).rejects.toThrow(
57
- 'Typescript error(s) found in dry run compilation:',
58
- )
59
- await expect(sut.init()).rejects.toThrow(
60
- 'testResources/errors/compile-error/add.ts(2,3): error TS2322:',
61
- )
62
- }, 30_000)
63
-
64
- it('should reject initialization if tsconfig was invalid', async () => {
65
- const sut = createChecker(
66
- resolveTestResource('invalid-tsconfig', 'tsconfig.json'),
67
- )
68
- await expect(sut.init()).rejects.toThrow(
69
- 'Typescript error(s) found in dry run compilation:',
70
- )
71
- await expect(sut.init()).rejects.toThrow(
72
- 'testResources/errors/invalid-tsconfig/tsconfig.json(1,1): error TS1005:',
73
- )
74
- })
75
-
76
- it('should log a warning naming the tsconfig path and the skipped-overrides consequence when parsing falls back', async () => {
77
- const warn = vi.fn()
78
- const sut = createChecker(
79
- resolveTestResource('invalid-tsconfig', 'tsconfig.json'),
80
- createLogger(warn),
81
- )
82
-
83
- await expect(sut.init()).rejects.toThrow(
84
- 'testResources/errors/invalid-tsconfig/tsconfig.json(1,1): error TS1005:',
85
- )
86
-
87
- expect(warn).toHaveBeenCalled()
88
- const warnings = warn.mock.calls
89
- .map((call) => call.join(' '))
90
- .join('\n')
91
- expect(warnings).toContain(
92
- 'testResources/errors/invalid-tsconfig/tsconfig.json',
93
- )
94
- expect(warnings.toLowerCase()).toContain(
95
- 'compiler-option overrides and project-reference walking were skipped',
96
- )
97
- })
98
-
99
- it("should reject when tsconfig file doesn't exist", async () => {
100
- const sut = createChecker(
101
- resolveTestResource('empty-dir', 'tsconfig.json'),
102
- )
103
- await expect(sut.init()).rejects.toThrow(
104
- `The tsconfig file does not exist at: "${
105
- resolveTestResource(
106
- 'empty-dir',
107
- 'tsconfig.json',
108
- )
109
- }". Please configure the tsconfig file in your stryker.conf file using "tsconfigFile"`,
110
- )
111
- })
112
- })
@@ -1,109 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { HybridFileSystem } from '../../../src/fs/hybrid-file-system.js'
4
-
5
- describe('HybridFileSystem', () => {
6
- function createSut(): HybridFileSystem {
7
- return new HybridFileSystem()
8
- }
9
-
10
- describe('writeFile', () => {
11
- it('should create a new in-memory file', () => {
12
- const sut = createSut()
13
- sut.writeFile('add.js', 'a + b')
14
- const file = sut.getFile('add.js')
15
- expect(file).toBeTruthy()
16
- expect(file!.content).toBe('a + b')
17
- expect(file!.fileName).toBe('add.js')
18
- })
19
-
20
- it('should override an existing file', () => {
21
- const sut = createSut()
22
- sut.writeFile('add.js', 'a + b')
23
- sut.writeFile('add.js', 'a - b')
24
- const file = sut.getFile('add.js')
25
- expect(file!.content).toBe('a - b')
26
- })
27
-
28
- it('should convert path separator to forward slashes', () => {
29
- const sut = createSut()
30
- sut.writeFile('test\\foo\\a.js', 'a')
31
- const actual = sut.getFile('test/foo/a.js')
32
- expect(actual).toBeTruthy()
33
- expect(actual!.content).toBe('a')
34
- })
35
- })
36
-
37
- describe('readFile', () => {
38
- it('should return in-memory content', () => {
39
- const sut = createSut()
40
- sut.writeFile('foo.js', 'in-memory')
41
- expect(sut.readFile('foo.js')).toBe('in-memory')
42
- })
43
-
44
- it('should return undefined for non-mutated files to allow disk fallback', () => {
45
- const sut = createSut()
46
- expect(sut.readFile('some-file-that-is-not-tracked.js')).toBeUndefined()
47
- })
48
-
49
- it('should return null for buildinfo files', () => {
50
- const sut = createSut()
51
- expect(sut.readFile('cache.tsbuildinfo')).toBeNull()
52
- })
53
-
54
- it('should return the tsconfig override when present', () => {
55
- const sut = createSut()
56
- sut.tsConfigOverrides.set('/project/tsconfig.json', '{"compilerOptions":{}}')
57
- expect(sut.readFile('/project/tsconfig.json')).toBe('{"compilerOptions":{}}')
58
- })
59
- })
60
-
61
- describe('fileExists', () => {
62
- it('should return true for in-memory files', () => {
63
- const sut = createSut()
64
- sut.writeFile('foo.js', '')
65
- expect(sut.fileExists('foo.js')).toBe(true)
66
- })
67
-
68
- it('should return false for buildinfo files', () => {
69
- const sut = createSut()
70
- expect(sut.fileExists('cache.tsbuildinfo')).toBe(false)
71
- })
72
-
73
- it('should return undefined for unknown files', () => {
74
- const sut = createSut()
75
- expect(sut.fileExists('unknown.js')).toBeUndefined()
76
- })
77
- })
78
-
79
- describe('mutateFile / resetFile', () => {
80
- it('should mutate a file and reset it afterwards', () => {
81
- const sut = createSut()
82
- sut.writeFile('add.js', 'function add(a: number, b: number) { return a + b; }')
83
- sut.mutateFile('add.js', {
84
- location: {
85
- start: { line: 0, column: 42 },
86
- end: { line: 0, column: 49 },
87
- },
88
- replacement: 'a - b',
89
- })
90
- expect(sut.readFile('add.js')).toContain('a - b')
91
-
92
- sut.resetFile('add.js')
93
- expect(sut.readFile('add.js')).toContain('a + b')
94
- })
95
- })
96
-
97
- describe('existsInMemory', () => {
98
- it('should return true if file exists in memory', () => {
99
- const sut = createSut()
100
- sut.writeFile('test-file', '')
101
- expect(sut.existsInMemory('test-file')).toBe(true)
102
- })
103
-
104
- it('should return false if file does not exist in memory', () => {
105
- const sut = createSut()
106
- expect(sut.existsInMemory('test-file')).toBe(false)
107
- })
108
- })
109
- })
@@ -1,122 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { createGroups } from '../../../src/grouping/create-groups.js'
4
- import { TSFileNode } from '../../../src/grouping/ts-file-node.js'
5
-
6
- function factoryMutant(fileName: string, id: string) {
7
- return {
8
- id,
9
- fileName,
10
- mutatorName: 'foo-mutator',
11
- replacement: '',
12
- location: {
13
- start: { line: 0, column: 0 },
14
- end: { line: 0, column: 0 },
15
- },
16
- }
17
- }
18
-
19
- describe('createGroups', () => {
20
- it('single mutant should create single group', () => {
21
- const mutants = [factoryMutant('a.js', 'mutant-1')]
22
- const nodes = new Map<string, TSFileNode>([
23
- ['a.js', new TSFileNode('a.js', [], [])],
24
- ])
25
- const groups = createGroups(mutants, nodes)
26
- expect(groups).toHaveLength(1)
27
- expect(groups[0]!).toHaveLength(1)
28
- expect(groups[0]![0]).toBe('mutant-1')
29
- })
30
-
31
- it('two mutants in different files without reference to each other should create single group', () => {
32
- const mutants = [factoryMutant('a.js', '1'), factoryMutant('b.js', '2')]
33
- const nodes = new Map<string, TSFileNode>([
34
- ['a.js', new TSFileNode('a.js', [], [])],
35
- ['b.js', new TSFileNode('b.js', [], [])],
36
- ])
37
- const groups = createGroups(mutants, nodes)
38
- expect(groups).toHaveLength(1)
39
- expect(groups[0]![0]).toBe('1')
40
- expect(groups[0]![1]).toBe('2')
41
- })
42
-
43
- it('two mutants in different files with reference to each other should create 2 groups', () => {
44
- const mutants = [factoryMutant('a.js', '1'), factoryMutant('b.js', '2')]
45
- const nodeA = new TSFileNode('a.js', [], [])
46
- const nodeB = new TSFileNode('b.js', [nodeA], [])
47
- const nodes = new Map<string, TSFileNode>([
48
- [nodeA.fileName, nodeA],
49
- [nodeB.fileName, nodeB],
50
- ])
51
- const groups = createGroups(mutants, nodes)
52
- expect(groups).toHaveLength(2)
53
- expect(groups[0]![0]).toBe('1')
54
- expect(groups[1]![0]).toBe('2')
55
- })
56
-
57
- it('two mutants in different files with circular dependency to each other should create 2 groups', () => {
58
- const mutants = [factoryMutant('a.js', '1'), factoryMutant('b.js', '2')]
59
- const nodeA = new TSFileNode('a.js', [], [])
60
- const nodeB = new TSFileNode('b.js', [nodeA], [])
61
- nodeA.parents.push(nodeB)
62
- const nodes = new Map<string, TSFileNode>([
63
- [nodeA.fileName, nodeA],
64
- [nodeB.fileName, nodeB],
65
- ])
66
- const groups = createGroups(mutants, nodes)
67
- expect(groups).toHaveLength(2)
68
- expect(groups[0]![0]).toBe('1')
69
- expect(groups[1]![0]).toBe('2')
70
- })
71
-
72
- it('two mutants in same file should create 2 groups', () => {
73
- const mutants = [factoryMutant('a.js', '1'), factoryMutant('a.js', '2')]
74
- const nodeA = new TSFileNode('a.js', [], [])
75
- const nodes = new Map<string, TSFileNode>([[nodeA.fileName, nodeA]])
76
- const groups = createGroups(mutants, nodes)
77
- expect(groups).toHaveLength(2)
78
- expect(groups[0]![0]).toBe('1')
79
- expect(groups[1]![0]).toBe('2')
80
- })
81
-
82
- it('complex graph should contain multiple groups', () => {
83
- const mutants = [
84
- factoryMutant('a.js', '1'),
85
- factoryMutant('b.js', '2'),
86
- factoryMutant('c.js', '3'),
87
- factoryMutant('d.js', '4'),
88
- factoryMutant('e.js', '5'),
89
- factoryMutant('f.js', '6'),
90
- ]
91
- const nodeA = new TSFileNode('a.js', [], [])
92
- const nodeB = new TSFileNode('b.js', [nodeA], [])
93
- const nodeC = new TSFileNode('c.js', [nodeA], [])
94
- const nodeD = new TSFileNode('d.js', [nodeC], [])
95
- const nodeE = new TSFileNode('e.js', [nodeA], [])
96
- const nodeF = new TSFileNode('f.js', [nodeE, nodeD], [])
97
- const nodes = new Map<string, TSFileNode>([
98
- [nodeA.fileName, nodeA],
99
- [nodeB.fileName, nodeB],
100
- [nodeC.fileName, nodeC],
101
- [nodeD.fileName, nodeD],
102
- [nodeE.fileName, nodeE],
103
- [nodeF.fileName, nodeF],
104
- ])
105
- const groups = createGroups(mutants, nodes)
106
- expect(groups).toHaveLength(4)
107
- expect(groups[0]![0]).toBe('1')
108
- expect(groups[1]![0]).toBe('2')
109
- expect(groups[1]![1]).toBe('3')
110
- expect(groups[1]![2]).toBe('5')
111
- expect(groups[2]![0]).toBe('4')
112
- expect(groups[3]![0]).toBe('6')
113
- })
114
-
115
- it('should throw error when node is not in graph', () => {
116
- const mutants = [factoryMutant('a.js', '1')]
117
- const nodeA = new TSFileNode('.js', [], [])
118
- const nodes = new Map<string, TSFileNode>([[nodeA.fileName, nodeA]])
119
-
120
- expect(() => createGroups(mutants, nodes)).toThrow('Node not in graph: a.js')
121
- })
122
- })
@@ -1,132 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { TSFileNode } from '../../../src/grouping/ts-file-node.js'
4
-
5
- describe('TSFileNode', () => {
6
- describe('getAllParentReferencesIncludingSelf', () => {
7
- it('without parent should return array of 1 node', () => {
8
- const node = new TSFileNode('NodeA', [], [])
9
- expect(node.getAllParentReferencesIncludingSelf()).toHaveLength(1)
10
- })
11
-
12
- it('with 1 parent should return array of 2 nodes', () => {
13
- const node = new TSFileNode('NodeA', [new TSFileNode('', [], [])], [])
14
- expect(node.getAllParentReferencesIncludingSelf()).toHaveLength(2)
15
- })
16
-
17
- it('with recursive depth of 2 should return 3 nodes', () => {
18
- const node = new TSFileNode(
19
- 'NodeA',
20
- [new TSFileNode('', [new TSFileNode('', [], [])], [])],
21
- [],
22
- )
23
- expect(node.getAllParentReferencesIncludingSelf()).toHaveLength(3)
24
- })
25
-
26
- it('with recursive depth of 2 and multiple parents should return 4 nodes', () => {
27
- const node = new TSFileNode(
28
- 'NodeA',
29
- [
30
- new TSFileNode(
31
- '',
32
- [new TSFileNode('', [], []), new TSFileNode('', [], [])],
33
- [],
34
- ),
35
- ],
36
- [],
37
- )
38
- expect(node.getAllParentReferencesIncludingSelf()).toHaveLength(4)
39
- })
40
-
41
- it('with circular dependency should skip circular dependency node', () => {
42
- const nodeA = new TSFileNode('NodeA', [], [])
43
- const nodeC = new TSFileNode('NodeB', [nodeA], [])
44
- const nodeB = new TSFileNode('NodeB', [nodeC], [])
45
- nodeA.parents.push(nodeB)
46
- expect(nodeA.getAllParentReferencesIncludingSelf()).toHaveLength(3)
47
- })
48
- })
49
-
50
- describe('getAllChildReferencesIncludingSelf', () => {
51
- it('without child should return array of 1 node', () => {
52
- const node = new TSFileNode('NodeA', [], [])
53
- expect(node.getAllChildReferencesIncludingSelf()).toHaveLength(1)
54
- })
55
-
56
- it('with 1 child should return array of 2 nodes', () => {
57
- const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [])])
58
- expect(node.getAllChildReferencesIncludingSelf()).toHaveLength(2)
59
- })
60
-
61
- it('with recursive depth of 2 should return 3 nodes', () => {
62
- const node = new TSFileNode(
63
- 'NodeA',
64
- [],
65
- [new TSFileNode('', [], [new TSFileNode('', [], [])])],
66
- )
67
- expect(node.getAllChildReferencesIncludingSelf()).toHaveLength(3)
68
- })
69
-
70
- it('with recursive depth of 2 and multiple children should return 4 nodes', () => {
71
- const node = new TSFileNode(
72
- 'NodeA',
73
- [],
74
- [
75
- new TSFileNode(
76
- '',
77
- [],
78
- [new TSFileNode('', [], []), new TSFileNode('', [], [])],
79
- ),
80
- ],
81
- )
82
- expect(node.getAllChildReferencesIncludingSelf()).toHaveLength(4)
83
- })
84
- })
85
-
86
- describe('getMutantsWithReferenceToChildrenOrSelf', () => {
87
- it('with single mutant in file should return 1 mutant', () => {
88
- const node = new TSFileNode('NodeA.js', [], [])
89
- const mutants = [createMutant('NodeA.js')]
90
- expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).toHaveLength(1)
91
- })
92
-
93
- it('with single mutant in child should return 1 mutant', () => {
94
- const node = new TSFileNode('NodeA.js', [], [])
95
- const nodeB = new TSFileNode('NodeB.js', [], [])
96
- node.children.push(nodeB)
97
- const mutants = [createMutant('NodeB.js')]
98
- expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).toHaveLength(1)
99
- })
100
-
101
- it('should not create endless loop', () => {
102
- const node = new TSFileNode('NodeA.js', [], [])
103
- node.children = [node]
104
-
105
- const mutants = [createMutant('NodeA.js')]
106
-
107
- expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).toHaveLength(1)
108
- })
109
-
110
- it('should find mutant with backward slashes and forward slashes', () => {
111
- const node = new TSFileNode('path/NodeA.js', [], [])
112
- node.children = [node]
113
-
114
- const mutants = [createMutant('path/NodeA.js'), createMutant('path\\NodeA.js')]
115
-
116
- expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).toHaveLength(2)
117
- })
118
- })
119
- })
120
-
121
- function createMutant(fileName: string) {
122
- return {
123
- fileName,
124
- id: '0',
125
- replacement: '-',
126
- location: {
127
- start: { line: 1, column: 1 },
128
- end: { line: 1, column: 1 },
129
- },
130
- mutatorName: '',
131
- }
132
- }
@@ -1,3 +0,0 @@
1
- function add(a: number, b: number): string {
2
- return a + b
3
- }