@mutineerjs/mutineer 0.2.2 → 0.2.4

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 (50) hide show
  1. package/README.md +22 -7
  2. package/dist/core/__tests__/module.spec.js +66 -3
  3. package/dist/core/__tests__/sfc.spec.d.ts +1 -0
  4. package/dist/core/__tests__/sfc.spec.js +76 -0
  5. package/dist/core/__tests__/variant-utils.spec.d.ts +1 -0
  6. package/dist/core/__tests__/variant-utils.spec.js +93 -0
  7. package/dist/runner/__tests__/args.spec.d.ts +1 -0
  8. package/dist/runner/__tests__/args.spec.js +225 -0
  9. package/dist/runner/__tests__/cache.spec.d.ts +1 -0
  10. package/dist/runner/__tests__/cache.spec.js +180 -0
  11. package/dist/runner/__tests__/changed.spec.d.ts +1 -0
  12. package/dist/runner/__tests__/changed.spec.js +227 -0
  13. package/dist/runner/__tests__/cleanup.spec.d.ts +1 -0
  14. package/dist/runner/__tests__/cleanup.spec.js +41 -0
  15. package/dist/runner/__tests__/config.spec.d.ts +1 -0
  16. package/dist/runner/__tests__/config.spec.js +71 -0
  17. package/dist/runner/__tests__/coverage-resolver.spec.d.ts +1 -0
  18. package/dist/runner/__tests__/coverage-resolver.spec.js +171 -0
  19. package/dist/runner/__tests__/pool-executor.spec.d.ts +1 -0
  20. package/dist/runner/__tests__/pool-executor.spec.js +213 -0
  21. package/dist/runner/__tests__/tasks.spec.d.ts +1 -0
  22. package/dist/runner/__tests__/tasks.spec.js +95 -0
  23. package/dist/runner/__tests__/variants.spec.d.ts +1 -0
  24. package/dist/runner/__tests__/variants.spec.js +259 -0
  25. package/dist/runner/args.d.ts +5 -0
  26. package/dist/runner/args.js +7 -0
  27. package/dist/runner/config.js +2 -2
  28. package/dist/runner/coverage-resolver.d.ts +21 -0
  29. package/dist/runner/coverage-resolver.js +96 -0
  30. package/dist/runner/jest/__tests__/pool.spec.d.ts +1 -0
  31. package/dist/runner/jest/__tests__/pool.spec.js +212 -0
  32. package/dist/runner/jest/__tests__/worker-runtime.spec.d.ts +1 -0
  33. package/dist/runner/jest/__tests__/worker-runtime.spec.js +148 -0
  34. package/dist/runner/orchestrator.js +43 -295
  35. package/dist/runner/pool-executor.d.ts +17 -0
  36. package/dist/runner/pool-executor.js +143 -0
  37. package/dist/runner/shared/__tests__/mutant-paths.spec.d.ts +1 -0
  38. package/dist/runner/shared/__tests__/mutant-paths.spec.js +66 -0
  39. package/dist/runner/shared/__tests__/redirect-state.spec.d.ts +1 -0
  40. package/dist/runner/shared/__tests__/redirect-state.spec.js +56 -0
  41. package/dist/runner/tasks.d.ts +12 -0
  42. package/dist/runner/tasks.js +25 -0
  43. package/dist/runner/variants.d.ts +17 -2
  44. package/dist/runner/variants.js +33 -0
  45. package/dist/runner/vitest/__tests__/redirect-loader.spec.js +4 -0
  46. package/dist/utils/__tests__/logger.spec.d.ts +1 -0
  47. package/dist/utils/__tests__/logger.spec.js +61 -0
  48. package/dist/utils/__tests__/normalizePath.spec.d.ts +1 -0
  49. package/dist/utils/__tests__/normalizePath.spec.js +22 -0
  50. package/package.json +3 -1
@@ -0,0 +1,171 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { resolveCoverageConfig, loadCoverageAfterBaseline, } from '../coverage-resolver.js';
6
+ function makeOpts(overrides = {}) {
7
+ return {
8
+ configPath: undefined,
9
+ wantsChanged: false,
10
+ wantsChangedWithDeps: false,
11
+ wantsOnlyCoveredLines: false,
12
+ wantsPerTestCoverage: false,
13
+ coverageFilePath: undefined,
14
+ concurrency: 1,
15
+ progressMode: 'bar',
16
+ minKillPercent: undefined,
17
+ runner: 'vitest',
18
+ ...overrides,
19
+ };
20
+ }
21
+ function makeAdapter(overrides = {}) {
22
+ return {
23
+ name: 'vitest',
24
+ init: vi.fn().mockResolvedValue(undefined),
25
+ runBaseline: vi.fn().mockResolvedValue(true),
26
+ runMutant: vi.fn().mockResolvedValue({ status: 'killed', durationMs: 0 }),
27
+ shutdown: vi.fn().mockResolvedValue(undefined),
28
+ hasCoverageProvider: vi.fn().mockReturnValue(false),
29
+ detectCoverageConfig: vi
30
+ .fn()
31
+ .mockResolvedValue({ perTestEnabled: false, coverageEnabled: false }),
32
+ ...overrides,
33
+ };
34
+ }
35
+ describe('resolveCoverageConfig', () => {
36
+ beforeEach(() => {
37
+ process.exitCode = undefined;
38
+ });
39
+ afterEach(() => {
40
+ process.exitCode = undefined;
41
+ });
42
+ it('returns baseline defaults when no coverage is requested', async () => {
43
+ const result = await resolveCoverageConfig(makeOpts(), {}, makeAdapter(), []);
44
+ expect(result.coverageData).toBeNull();
45
+ expect(result.perTestCoverage).toBeNull();
46
+ expect(result.enableCoverageForBaseline).toBe(false);
47
+ expect(result.wantsPerTestCoverage).toBe(false);
48
+ expect(result.needsCoverageFromBaseline).toBe(false);
49
+ });
50
+ it('enables coverage for baseline when config coverage is true', async () => {
51
+ const result = await resolveCoverageConfig(makeOpts(), { coverage: true }, makeAdapter(), []);
52
+ expect(result.enableCoverageForBaseline).toBe(true);
53
+ });
54
+ it('disables coverage for baseline when config coverage is false', async () => {
55
+ const adapter = makeAdapter({
56
+ detectCoverageConfig: vi
57
+ .fn()
58
+ .mockResolvedValue({ perTestEnabled: false, coverageEnabled: true }),
59
+ });
60
+ const result = await resolveCoverageConfig(makeOpts(), { coverage: false }, adapter, []);
61
+ expect(result.enableCoverageForBaseline).toBe(false);
62
+ });
63
+ it('sets exitCode when onlyCoveredLines is set but no coverage provider', async () => {
64
+ const opts = makeOpts({ wantsOnlyCoveredLines: true });
65
+ const adapter = makeAdapter({ hasCoverageProvider: vi.fn().mockReturnValue(false) });
66
+ await resolveCoverageConfig(opts, {}, adapter, []);
67
+ expect(process.exitCode).toBe(1);
68
+ });
69
+ it('does not set exitCode when onlyCoveredLines is set with coverage provider', async () => {
70
+ const opts = makeOpts({ wantsOnlyCoveredLines: true });
71
+ const adapter = makeAdapter({
72
+ hasCoverageProvider: vi.fn().mockReturnValue(true),
73
+ });
74
+ await resolveCoverageConfig(opts, {}, adapter, []);
75
+ expect(process.exitCode).toBeUndefined();
76
+ });
77
+ it('disables per-test coverage for jest runner', async () => {
78
+ const opts = makeOpts({ runner: 'jest', wantsPerTestCoverage: true });
79
+ const result = await resolveCoverageConfig(opts, {}, makeAdapter(), []);
80
+ expect(result.wantsPerTestCoverage).toBe(false);
81
+ });
82
+ it('enables per-test coverage for vitest runner', async () => {
83
+ const opts = makeOpts({ runner: 'vitest', wantsPerTestCoverage: true });
84
+ const result = await resolveCoverageConfig(opts, {}, makeAdapter(), []);
85
+ expect(result.wantsPerTestCoverage).toBe(true);
86
+ expect(result.enableCoverageForBaseline).toBe(true);
87
+ });
88
+ it('enables per-test coverage when adapter reports it enabled', async () => {
89
+ const adapter = makeAdapter({
90
+ detectCoverageConfig: vi
91
+ .fn()
92
+ .mockResolvedValue({ perTestEnabled: true, coverageEnabled: false }),
93
+ });
94
+ const result = await resolveCoverageConfig(makeOpts(), {}, adapter, []);
95
+ expect(result.wantsPerTestCoverage).toBe(true);
96
+ });
97
+ it('sets needsCoverageFromBaseline when onlyCoveredLines without coverageFile', async () => {
98
+ const opts = makeOpts({ wantsOnlyCoveredLines: true });
99
+ const adapter = makeAdapter({
100
+ hasCoverageProvider: vi.fn().mockReturnValue(true),
101
+ });
102
+ const result = await resolveCoverageConfig(opts, {}, adapter, []);
103
+ expect(result.needsCoverageFromBaseline).toBe(true);
104
+ expect(result.enableCoverageForBaseline).toBe(true);
105
+ });
106
+ });
107
+ describe('loadCoverageAfterBaseline', () => {
108
+ let tmpDir;
109
+ beforeEach(async () => {
110
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-cov-resolver-'));
111
+ });
112
+ afterEach(async () => {
113
+ await fs.rm(tmpDir, { recursive: true, force: true });
114
+ });
115
+ it('returns resolution unchanged when no coverage needed from baseline', async () => {
116
+ const resolution = {
117
+ coverageData: null,
118
+ perTestCoverage: null,
119
+ enableCoverageForBaseline: false,
120
+ wantsPerTestCoverage: false,
121
+ needsCoverageFromBaseline: false,
122
+ };
123
+ const result = await loadCoverageAfterBaseline(resolution, tmpDir);
124
+ expect(result).toEqual(resolution);
125
+ });
126
+ it('loads coverage data from default path when needsCoverageFromBaseline', async () => {
127
+ const coverageDir = path.join(tmpDir, 'coverage');
128
+ await fs.mkdir(coverageDir, { recursive: true });
129
+ const coverageJson = {
130
+ '/src/foo.ts': {
131
+ path: '/src/foo.ts',
132
+ statementMap: { '0': { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } } },
133
+ s: { '0': 1 },
134
+ },
135
+ };
136
+ await fs.writeFile(path.join(coverageDir, 'coverage-final.json'), JSON.stringify(coverageJson));
137
+ const resolution = {
138
+ coverageData: null,
139
+ perTestCoverage: null,
140
+ enableCoverageForBaseline: true,
141
+ wantsPerTestCoverage: false,
142
+ needsCoverageFromBaseline: true,
143
+ };
144
+ const result = await loadCoverageAfterBaseline(resolution, tmpDir);
145
+ expect(result.coverageData).not.toBeNull();
146
+ expect(result.coverageData.coveredLines.size).toBeGreaterThan(0);
147
+ });
148
+ it('continues gracefully when coverage file is missing', async () => {
149
+ const resolution = {
150
+ coverageData: null,
151
+ perTestCoverage: null,
152
+ enableCoverageForBaseline: true,
153
+ wantsPerTestCoverage: false,
154
+ needsCoverageFromBaseline: true,
155
+ };
156
+ const result = await loadCoverageAfterBaseline(resolution, tmpDir);
157
+ // Should not throw, coverageData stays null
158
+ expect(result.coverageData).toBeNull();
159
+ });
160
+ it('does not modify resolution when wantsPerTestCoverage is false', async () => {
161
+ const resolution = {
162
+ coverageData: null,
163
+ perTestCoverage: null,
164
+ enableCoverageForBaseline: false,
165
+ wantsPerTestCoverage: false,
166
+ needsCoverageFromBaseline: false,
167
+ };
168
+ const result = await loadCoverageAfterBaseline(resolution, tmpDir);
169
+ expect(result.perTestCoverage).toBeNull();
170
+ });
171
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,213 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { executePool } from '../pool-executor.js';
6
+ function makeAdapter(overrides = {}) {
7
+ return {
8
+ name: 'vitest',
9
+ init: vi.fn().mockResolvedValue(undefined),
10
+ runBaseline: vi.fn().mockResolvedValue(true),
11
+ runMutant: vi
12
+ .fn()
13
+ .mockResolvedValue({ status: 'killed', durationMs: 10 }),
14
+ shutdown: vi.fn().mockResolvedValue(undefined),
15
+ hasCoverageProvider: vi.fn().mockReturnValue(false),
16
+ detectCoverageConfig: vi
17
+ .fn()
18
+ .mockResolvedValue({ perTestEnabled: false, coverageEnabled: false }),
19
+ ...overrides,
20
+ };
21
+ }
22
+ function makeTask(overrides = {}) {
23
+ return {
24
+ v: {
25
+ id: 'file.ts#0',
26
+ name: 'flipStrictEQ',
27
+ file: '/src/file.ts',
28
+ code: 'const x = a !== b',
29
+ line: 1,
30
+ col: 10,
31
+ tests: ['/tests/file.test.ts'],
32
+ },
33
+ tests: ['/tests/file.test.ts'],
34
+ key: 'test-key-1',
35
+ ...overrides,
36
+ };
37
+ }
38
+ describe('executePool', () => {
39
+ let tmpDir;
40
+ beforeEach(async () => {
41
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-'));
42
+ process.exitCode = undefined;
43
+ });
44
+ afterEach(async () => {
45
+ process.exitCode = undefined;
46
+ await fs.rm(tmpDir, { recursive: true, force: true });
47
+ });
48
+ it('initializes adapter with correct worker count', async () => {
49
+ const adapter = makeAdapter();
50
+ const cache = {};
51
+ const tasks = [makeTask()];
52
+ await executePool({
53
+ tasks,
54
+ adapter,
55
+ cache,
56
+ concurrency: 4,
57
+ progressMode: 'list',
58
+ cwd: tmpDir,
59
+ });
60
+ // workerCount = min(concurrency, tasks.length) = min(4, 1) = 1
61
+ expect(adapter.init).toHaveBeenCalledWith(1);
62
+ });
63
+ it('runs mutants through the adapter and populates cache', async () => {
64
+ const adapter = makeAdapter();
65
+ const cache = {};
66
+ const task = makeTask({ key: 'unique-key' });
67
+ await executePool({
68
+ tasks: [task],
69
+ adapter,
70
+ cache,
71
+ concurrency: 1,
72
+ progressMode: 'list',
73
+ cwd: tmpDir,
74
+ });
75
+ expect(adapter.runMutant).toHaveBeenCalledTimes(1);
76
+ expect(cache['unique-key']).toBeDefined();
77
+ expect(cache['unique-key'].status).toBe('killed');
78
+ });
79
+ it('skips cached tasks without calling runMutant', async () => {
80
+ const adapter = makeAdapter();
81
+ const cache = {
82
+ 'cached-key': {
83
+ status: 'killed',
84
+ file: '/src/file.ts',
85
+ line: 1,
86
+ col: 10,
87
+ mutator: 'flipStrictEQ',
88
+ },
89
+ };
90
+ await executePool({
91
+ tasks: [makeTask({ key: 'cached-key' })],
92
+ adapter,
93
+ cache,
94
+ concurrency: 1,
95
+ progressMode: 'list',
96
+ cwd: tmpDir,
97
+ });
98
+ expect(adapter.runMutant).not.toHaveBeenCalled();
99
+ });
100
+ it('marks tasks with no tests as skipped', async () => {
101
+ const adapter = makeAdapter();
102
+ const cache = {};
103
+ const task = makeTask({ tests: [], key: 'no-tests-key' });
104
+ await executePool({
105
+ tasks: [task],
106
+ adapter,
107
+ cache,
108
+ concurrency: 1,
109
+ progressMode: 'list',
110
+ cwd: tmpDir,
111
+ });
112
+ expect(adapter.runMutant).not.toHaveBeenCalled();
113
+ expect(cache['no-tests-key'].status).toBe('skipped');
114
+ });
115
+ it('processes multiple tasks', async () => {
116
+ const adapter = makeAdapter();
117
+ const cache = {};
118
+ const tasks = [
119
+ makeTask({ key: 'key-1' }),
120
+ makeTask({ key: 'key-2' }),
121
+ makeTask({ key: 'key-3' }),
122
+ ];
123
+ await executePool({
124
+ tasks,
125
+ adapter,
126
+ cache,
127
+ concurrency: 2,
128
+ progressMode: 'list',
129
+ cwd: tmpDir,
130
+ });
131
+ expect(adapter.runMutant).toHaveBeenCalledTimes(3);
132
+ expect(Object.keys(cache)).toHaveLength(3);
133
+ });
134
+ it('shuts down adapter after completion', async () => {
135
+ const adapter = makeAdapter();
136
+ const cache = {};
137
+ await executePool({
138
+ tasks: [makeTask()],
139
+ adapter,
140
+ cache,
141
+ concurrency: 1,
142
+ progressMode: 'list',
143
+ cwd: tmpDir,
144
+ });
145
+ expect(adapter.shutdown).toHaveBeenCalledTimes(1);
146
+ });
147
+ it('sets exitCode when kill rate is below threshold', async () => {
148
+ const adapter = makeAdapter({
149
+ runMutant: vi
150
+ .fn()
151
+ .mockResolvedValue({ status: 'escaped', durationMs: 10 }),
152
+ });
153
+ const cache = {};
154
+ await executePool({
155
+ tasks: [makeTask()],
156
+ adapter,
157
+ cache,
158
+ concurrency: 1,
159
+ progressMode: 'list',
160
+ minKillPercent: 80,
161
+ cwd: tmpDir,
162
+ });
163
+ expect(process.exitCode).toBe(1);
164
+ });
165
+ it('does not set exitCode when kill rate meets threshold', async () => {
166
+ const adapter = makeAdapter({
167
+ runMutant: vi
168
+ .fn()
169
+ .mockResolvedValue({ status: 'killed', durationMs: 10 }),
170
+ });
171
+ const cache = {};
172
+ await executePool({
173
+ tasks: [makeTask()],
174
+ adapter,
175
+ cache,
176
+ concurrency: 1,
177
+ progressMode: 'list',
178
+ minKillPercent: 80,
179
+ cwd: tmpDir,
180
+ });
181
+ expect(process.exitCode).toBeUndefined();
182
+ });
183
+ it('saves cache to disk after completion', async () => {
184
+ const adapter = makeAdapter();
185
+ const cache = {};
186
+ await executePool({
187
+ tasks: [makeTask({ key: 'persist-key' })],
188
+ adapter,
189
+ cache,
190
+ concurrency: 1,
191
+ progressMode: 'list',
192
+ cwd: tmpDir,
193
+ });
194
+ const cacheFile = path.join(tmpDir, '.mutate-cache.json');
195
+ const content = JSON.parse(await fs.readFile(cacheFile, 'utf8'));
196
+ expect(content['persist-key']).toBeDefined();
197
+ });
198
+ it('handles adapter errors gracefully and still shuts down', async () => {
199
+ const adapter = makeAdapter({
200
+ runMutant: vi.fn().mockRejectedValue(new Error('adapter failure')),
201
+ });
202
+ const cache = {};
203
+ await expect(executePool({
204
+ tasks: [makeTask()],
205
+ adapter,
206
+ cache,
207
+ concurrency: 1,
208
+ progressMode: 'list',
209
+ cwd: tmpDir,
210
+ })).rejects.toThrow('adapter failure');
211
+ expect(adapter.shutdown).toHaveBeenCalledTimes(1);
212
+ });
213
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { prepareTasks } from '../tasks.js';
3
+ import { hash, keyForTests } from '../cache.js';
4
+ function makeVariant(overrides = {}) {
5
+ return {
6
+ id: 'file.ts#0',
7
+ name: 'flipStrictEQ',
8
+ file: '/src/file.ts',
9
+ code: 'const x = a !== b',
10
+ line: 1,
11
+ col: 10,
12
+ tests: ['/tests/file.test.ts'],
13
+ ...overrides,
14
+ };
15
+ }
16
+ describe('prepareTasks', () => {
17
+ it('creates tasks with sorted tests and computed cache keys', () => {
18
+ const v = makeVariant({
19
+ tests: ['/tests/b.test.ts', '/tests/a.test.ts'],
20
+ });
21
+ const tasks = prepareTasks([v], null);
22
+ expect(tasks).toHaveLength(1);
23
+ expect(tasks[0].tests).toEqual(['/tests/a.test.ts', '/tests/b.test.ts']);
24
+ expect(tasks[0].v).toBe(v);
25
+ // Key should be testSig:codeSig
26
+ const expectedTestSig = hash(keyForTests(['/tests/a.test.ts', '/tests/b.test.ts']));
27
+ const expectedCodeSig = hash(v.code);
28
+ expect(tasks[0].key).toBe(`${expectedTestSig}:${expectedCodeSig}`);
29
+ });
30
+ it('produces deterministic keys regardless of test order', () => {
31
+ const v1 = makeVariant({
32
+ tests: ['/tests/b.test.ts', '/tests/a.test.ts'],
33
+ });
34
+ const v2 = makeVariant({
35
+ tests: ['/tests/a.test.ts', '/tests/b.test.ts'],
36
+ });
37
+ const tasks1 = prepareTasks([v1], null);
38
+ const tasks2 = prepareTasks([v2], null);
39
+ expect(tasks1[0].key).toBe(tasks2[0].key);
40
+ });
41
+ it('produces different keys for different code', () => {
42
+ const v1 = makeVariant({ code: 'const x = a !== b' });
43
+ const v2 = makeVariant({ code: 'const x = a === b' });
44
+ const tasks = prepareTasks([v1, v2], null);
45
+ expect(tasks[0].key).not.toBe(tasks[1].key);
46
+ });
47
+ it('produces different keys for different test sets', () => {
48
+ const v1 = makeVariant({ tests: ['/tests/a.test.ts'] });
49
+ const v2 = makeVariant({ tests: ['/tests/b.test.ts'] });
50
+ const tasks = prepareTasks([v1, v2], null);
51
+ expect(tasks[0].key).not.toBe(tasks[1].key);
52
+ });
53
+ it('handles variants with no tests', () => {
54
+ const v = makeVariant({ tests: [] });
55
+ const tasks = prepareTasks([v], null);
56
+ expect(tasks).toHaveLength(1);
57
+ expect(tasks[0].tests).toEqual([]);
58
+ });
59
+ it('prunes tests via per-test coverage', () => {
60
+ const v = makeVariant({
61
+ file: '/src/file.ts',
62
+ line: 5,
63
+ tests: ['/tests/a.test.ts', '/tests/b.test.ts'],
64
+ });
65
+ // Only test-a covers line 5 of /src/file.ts
66
+ const perTestCoverage = new Map();
67
+ const aCoverage = new Map();
68
+ aCoverage.set('/src/file.ts', new Set([5, 6, 7]));
69
+ perTestCoverage.set('/tests/a.test.ts', aCoverage);
70
+ const bCoverage = new Map();
71
+ bCoverage.set('/src/file.ts', new Set([10, 11]));
72
+ perTestCoverage.set('/tests/b.test.ts', bCoverage);
73
+ const tasks = prepareTasks([v], perTestCoverage);
74
+ expect(tasks[0].tests).toEqual(['/tests/a.test.ts']);
75
+ });
76
+ it('does not prune when perTestCoverage is null', () => {
77
+ const v = makeVariant({
78
+ tests: ['/tests/a.test.ts', '/tests/b.test.ts'],
79
+ });
80
+ const tasks = prepareTasks([v], null);
81
+ expect(tasks[0].tests).toHaveLength(2);
82
+ });
83
+ it('handles multiple variants', () => {
84
+ const variants = [
85
+ makeVariant({ id: 'file.ts#0', code: 'code1' }),
86
+ makeVariant({ id: 'file.ts#1', code: 'code2' }),
87
+ makeVariant({ id: 'file.ts#2', code: 'code3' }),
88
+ ];
89
+ const tasks = prepareTasks(variants, null);
90
+ expect(tasks).toHaveLength(3);
91
+ expect(tasks[0].v.id).toBe('file.ts#0');
92
+ expect(tasks[1].v.id).toBe('file.ts#1');
93
+ expect(tasks[2].v.id).toBe('file.ts#2');
94
+ });
95
+ });
@@ -0,0 +1 @@
1
+ export {};