@autotests/playwright-impact 0.1.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.
@@ -0,0 +1,316 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const { filterSpecsByImpactedMethods } = require('../src/modules/method-filter-helpers');
6
+ const { createTempDir, writeFile } = require('./_test-helpers');
7
+
8
+ test('filterSpecsByImpactedMethods returns empty result for empty input', () => {
9
+ const result = filterSpecsByImpactedMethods({
10
+ selectedSpecs: [],
11
+ directChangedSpecsAbs: [],
12
+ fixtureKeyToClass: new Map(),
13
+ fixtureKeys: new Set(),
14
+ impactedMethodsByClass: new Map(),
15
+ });
16
+
17
+ assert.equal(result.filteredSpecs.length, 0);
18
+ assert.equal(result.droppedByMethodFilter, 0);
19
+ });
20
+
21
+ test('filter keeps all specs when impacted methods are empty', () => {
22
+ const dir = createTempDir();
23
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.open(); });');
24
+
25
+ const result = filterSpecsByImpactedMethods({
26
+ selectedSpecs: [spec],
27
+ directChangedSpecsAbs: [],
28
+ fixtureKeyToClass: new Map([['page', 'Page']]),
29
+ fixtureKeys: new Set(['page']),
30
+ impactedMethodsByClass: new Map(),
31
+ });
32
+
33
+ assert.deepEqual(result.filteredSpecs, [spec]);
34
+ assert.equal(result.selectionReasons.get(spec), 'retained-no-impacted-methods');
35
+ });
36
+
37
+ test('filter always keeps direct changed specs', () => {
38
+ const dir = createTempDir();
39
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.unrelated(); });');
40
+
41
+ const result = filterSpecsByImpactedMethods({
42
+ selectedSpecs: [spec],
43
+ directChangedSpecsAbs: [spec],
44
+ fixtureKeyToClass: new Map([['page', 'Page']]),
45
+ fixtureKeys: new Set(['page']),
46
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
47
+ });
48
+
49
+ assert.deepEqual(result.filteredSpecs, [spec]);
50
+ assert.equal(result.selectionReasons.get(spec), 'direct-changed-spec');
51
+ });
52
+
53
+ test('filter always keeps import-graph matched specs', () => {
54
+ const dir = createTempDir();
55
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.unrelated(); });');
56
+
57
+ const result = filterSpecsByImpactedMethods({
58
+ selectedSpecs: [spec],
59
+ directChangedSpecsAbs: [],
60
+ alwaysIncludeSpecsAbs: [spec],
61
+ fixtureKeyToClass: new Map([['page', 'Page']]),
62
+ fixtureKeys: new Set(['page']),
63
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
64
+ });
65
+
66
+ assert.deepEqual(result.filteredSpecs, [spec]);
67
+ assert.equal(result.selectionReasons.get(spec), 'matched-import-graph');
68
+ });
69
+
70
+ test('filter drops non-matching specs by impacted method', () => {
71
+ const dir = createTempDir();
72
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.unrelated(); });');
73
+
74
+ const result = filterSpecsByImpactedMethods({
75
+ selectedSpecs: [spec],
76
+ directChangedSpecsAbs: [],
77
+ fixtureKeyToClass: new Map([['page', 'Page']]),
78
+ fixtureKeys: new Set(['page']),
79
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
80
+ });
81
+
82
+ assert.equal(result.filteredSpecs.length, 0);
83
+ assert.equal(result.droppedByMethodFilter, 1);
84
+ });
85
+
86
+ test('filter keeps spec when direct fixture method call matches', () => {
87
+ const dir = createTempDir();
88
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.open(); });');
89
+
90
+ const result = filterSpecsByImpactedMethods({
91
+ selectedSpecs: [spec],
92
+ directChangedSpecsAbs: [],
93
+ fixtureKeyToClass: new Map([['page', 'Page']]),
94
+ fixtureKeys: new Set(['page']),
95
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
96
+ });
97
+
98
+ assert.deepEqual(result.filteredSpecs, [spec]);
99
+ assert.equal(result.selectionReasons.get(spec), 'matched-precise');
100
+ });
101
+
102
+ test('filter keeps spec for literal element access call page["open"]()', () => {
103
+ const dir = createTempDir();
104
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page["open"](); });');
105
+
106
+ const result = filterSpecsByImpactedMethods({
107
+ selectedSpecs: [spec],
108
+ directChangedSpecsAbs: [],
109
+ fixtureKeyToClass: new Map([['page', 'Page']]),
110
+ fixtureKeys: new Set(['page']),
111
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
112
+ });
113
+
114
+ assert.deepEqual(result.filteredSpecs, [spec]);
115
+ assert.equal(result.selectionReasons.get(spec), 'matched-precise');
116
+ });
117
+
118
+ test('filter keeps spec for dynamic element access in fail-open mode', () => {
119
+ const dir = createTempDir();
120
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { const key = "open"; await page[key](); });');
121
+
122
+ const result = filterSpecsByImpactedMethods({
123
+ selectedSpecs: [spec],
124
+ directChangedSpecsAbs: [],
125
+ fixtureKeyToClass: new Map([['page', 'Page']]),
126
+ fixtureKeys: new Set(['page']),
127
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
128
+ selectionBias: 'fail-open',
129
+ });
130
+
131
+ assert.deepEqual(result.filteredSpecs, [spec]);
132
+ assert.equal(result.selectionReasons.get(spec), 'matched-uncertain-fail-open');
133
+ assert.equal(result.uncertainCallSites, 1);
134
+ });
135
+
136
+ test('filter drops dynamic element access in fail-closed mode', () => {
137
+ const dir = createTempDir();
138
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { const key = "open"; await page[key](); });');
139
+
140
+ const result = filterSpecsByImpactedMethods({
141
+ selectedSpecs: [spec],
142
+ directChangedSpecsAbs: [],
143
+ fixtureKeyToClass: new Map([['page', 'Page']]),
144
+ fixtureKeys: new Set(['page']),
145
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
146
+ selectionBias: 'fail-closed',
147
+ });
148
+
149
+ assert.equal(result.filteredSpecs.length, 0);
150
+ assert.equal(result.uncertainCallSites, 1);
151
+ });
152
+
153
+ test('filter matches optional chain page?.open?.() as precise', () => {
154
+ const dir = createTempDir();
155
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page?.open?.(); });');
156
+
157
+ const result = filterSpecsByImpactedMethods({
158
+ selectedSpecs: [spec],
159
+ directChangedSpecsAbs: [],
160
+ fixtureKeyToClass: new Map([['page', 'Page']]),
161
+ fixtureKeys: new Set(['page']),
162
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
163
+ });
164
+
165
+ assert.deepEqual(result.filteredSpecs, [spec]);
166
+ assert.equal(result.selectionReasons.get(spec), 'matched-precise');
167
+ });
168
+
169
+ test('CallChain node is processed without crash', () => {
170
+ const dir = createTempDir();
171
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page?.open(); });');
172
+
173
+ const result = filterSpecsByImpactedMethods({
174
+ selectedSpecs: [spec],
175
+ directChangedSpecsAbs: [],
176
+ fixtureKeyToClass: new Map([['page', 'Page']]),
177
+ fixtureKeys: new Set(['page']),
178
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
179
+ });
180
+
181
+ assert.deepEqual(result.filteredSpecs, [spec]);
182
+ });
183
+
184
+ test('this.a.b.m() deep chain within depth limit is handled as precise', () => {
185
+ const dir = createTempDir();
186
+ const spec = writeFile(dir, 'a.spec.ts', 'test(\"x\", async ({ page }) => { await page.a.b.open(); });');
187
+
188
+ const result = filterSpecsByImpactedMethods({
189
+ selectedSpecs: [spec],
190
+ directChangedSpecsAbs: [],
191
+ fixtureKeyToClass: new Map([['page', 'Page']]),
192
+ fixtureKeys: new Set(['page']),
193
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
194
+ selectionBias: 'fail-open',
195
+ });
196
+
197
+ assert.deepEqual(result.filteredSpecs, [spec]);
198
+ assert.equal(result.selectionReasons.get(spec), 'matched-precise');
199
+ });
200
+
201
+ test('Deep chain beyond depth limit is marked uncertain', () => {
202
+ const dir = createTempDir();
203
+ const spec = writeFile(dir, 'a.spec.ts', 'test(\"x\", async ({ page }) => { await page.a.b.c.open(); });');
204
+
205
+ const result = filterSpecsByImpactedMethods({
206
+ selectedSpecs: [spec],
207
+ directChangedSpecsAbs: [],
208
+ fixtureKeyToClass: new Map([['page', 'Page']]),
209
+ fixtureKeys: new Set(['page']),
210
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
211
+ selectionBias: 'fail-open',
212
+ });
213
+
214
+ assert.deepEqual(result.filteredSpecs, [spec]);
215
+ assert.equal(result.selectionReasons.get(spec), 'matched-uncertain-fail-open');
216
+ });
217
+
218
+ test('filter does not produce false precise match for optional chain with another method', () => {
219
+ const dir = createTempDir();
220
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page?.close?.(); });');
221
+
222
+ const result = filterSpecsByImpactedMethods({
223
+ selectedSpecs: [spec],
224
+ directChangedSpecsAbs: [],
225
+ fixtureKeyToClass: new Map([['page', 'Page']]),
226
+ fixtureKeys: new Set(['page']),
227
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
228
+ });
229
+
230
+ assert.equal(result.filteredSpecs.length, 0);
231
+ });
232
+
233
+ test('optional element access marks uncertain in fail-open mode', () => {
234
+ const dir = createTempDir();
235
+ const spec = writeFile(dir, 'a.spec.ts', 'test(\"x\", async ({ page }) => { const k = \"open\"; await page?.[k]?.(); });');
236
+
237
+ const result = filterSpecsByImpactedMethods({
238
+ selectedSpecs: [spec],
239
+ directChangedSpecsAbs: [],
240
+ fixtureKeyToClass: new Map([['page', 'Page']]),
241
+ fixtureKeys: new Set(['page']),
242
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
243
+ selectionBias: 'fail-open',
244
+ });
245
+
246
+ assert.deepEqual(result.filteredSpecs, [spec]);
247
+ assert.equal(result.selectionReasons.get(spec), 'matched-uncertain-fail-open');
248
+ });
249
+
250
+ test('Alias pattern const fn = page.open; fn() is marked uncertain', () => {
251
+ const dir = createTempDir();
252
+ const spec = writeFile(dir, 'a.spec.ts', 'test(\"x\", async ({ page }) => { const fn = page.open; await fn(); });');
253
+
254
+ const result = filterSpecsByImpactedMethods({
255
+ selectedSpecs: [spec],
256
+ directChangedSpecsAbs: [],
257
+ fixtureKeyToClass: new Map([['page', 'Page']]),
258
+ fixtureKeys: new Set(['page']),
259
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
260
+ selectionBias: 'fail-open',
261
+ });
262
+
263
+ assert.deepEqual(result.filteredSpecs, [spec]);
264
+ assert.equal(result.selectionReasons.get(spec), 'matched-uncertain-fail-open');
265
+ });
266
+
267
+ test('Destructuring alias const { open } = page; open() is marked uncertain', () => {
268
+ const dir = createTempDir();
269
+ const spec = writeFile(dir, 'a.spec.ts', 'test(\"x\", async ({ page }) => { const { open } = page; await open(); });');
270
+
271
+ const result = filterSpecsByImpactedMethods({
272
+ selectedSpecs: [spec],
273
+ directChangedSpecsAbs: [],
274
+ fixtureKeyToClass: new Map([['page', 'Page']]),
275
+ fixtureKeys: new Set(['page']),
276
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
277
+ selectionBias: 'fail-open',
278
+ });
279
+
280
+ assert.deepEqual(result.filteredSpecs, [spec]);
281
+ assert.equal(result.selectionReasons.get(spec), 'matched-uncertain-fail-open');
282
+ });
283
+
284
+ test('filter keeps spec without fixture bindings in callback params', () => {
285
+ const dir = createTempDir();
286
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async () => { expect(1).toBe(1); });');
287
+
288
+ const result = filterSpecsByImpactedMethods({
289
+ selectedSpecs: [spec],
290
+ directChangedSpecsAbs: [],
291
+ fixtureKeyToClass: new Map([['page', 'Page']]),
292
+ fixtureKeys: new Set(['page']),
293
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
294
+ });
295
+
296
+ assert.deepEqual(result.filteredSpecs, [spec]);
297
+ assert.equal(result.selectionReasons.get(spec), 'retained-no-bindings');
298
+ });
299
+
300
+ test('filter keeps spec on read error', () => {
301
+ const dir = createTempDir();
302
+ const spec = writeFile(dir, 'a.spec.ts', 'test("x", async ({ page }) => { await page.open(); });');
303
+ const fs = require('fs');
304
+ fs.unlinkSync(spec);
305
+
306
+ const result = filterSpecsByImpactedMethods({
307
+ selectedSpecs: [spec],
308
+ directChangedSpecsAbs: [],
309
+ fixtureKeyToClass: new Map([['page', 'Page']]),
310
+ fixtureKeys: new Set(['page']),
311
+ impactedMethodsByClass: new Map([['Page', new Set(['open'])]]),
312
+ });
313
+
314
+ assert.deepEqual(result.filteredSpecs, [spec]);
315
+ assert.equal(result.selectionReasons.get(spec), 'retained-read-error');
316
+ });
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const { collectChangedMethodsByClass, buildImpactedMethodsByClass } = require('../src/modules/method-impact-helpers');
6
+ const { createTempDir, writeFile } = require('./_test-helpers');
7
+
8
+ const changedEntry = { status: 'M', effectivePath: 'src/pages/A.ts', oldPath: 'src/pages/A.ts', newPath: 'src/pages/A.ts' };
9
+
10
+ const mapToObject = (map) => {
11
+ const result = {};
12
+ for (const [k, v] of map.entries()) result[k] = Array.from(v).sort();
13
+ return result;
14
+ };
15
+
16
+ test('collectChangedMethodsByClass detects changed method body', () => {
17
+ const result = collectChangedMethodsByClass({
18
+ changedPomEntries: [changedEntry],
19
+ baseRef: 'HEAD',
20
+ readChangeContents: () => ({
21
+ basePath: 'src/pages/A.ts',
22
+ headPath: 'src/pages/A.ts',
23
+ baseContent: 'export class A { run(){ return 1; } }',
24
+ headContent: 'export class A { run(){ return 2; } }',
25
+ }),
26
+ });
27
+
28
+ assert.equal(result.changedMethodsByClass.get('A').has('run'), true);
29
+ assert.equal(result.stats.semanticChangedMethodsCount >= 1, true);
30
+ });
31
+
32
+ test('collectChangedMethodsByClass ignores formatting-only changes', () => {
33
+ const result = collectChangedMethodsByClass({
34
+ changedPomEntries: [changedEntry],
35
+ baseRef: 'HEAD',
36
+ readChangeContents: () => ({
37
+ basePath: 'src/pages/A.ts',
38
+ headPath: 'src/pages/A.ts',
39
+ baseContent: 'export class A { run(){ return 1; } }',
40
+ headContent: 'export class A{\nrun(){\nreturn 1;\n}\n}',
41
+ }),
42
+ });
43
+
44
+ assert.equal(result.stats.semanticChangedMethodsCount, 0);
45
+ });
46
+
47
+ test('collectChangedMethodsByClass marks callable members when field changes', () => {
48
+ const result = collectChangedMethodsByClass({
49
+ changedPomEntries: [changedEntry],
50
+ baseRef: 'HEAD',
51
+ readChangeContents: () => ({
52
+ basePath: 'src/pages/A.ts',
53
+ headPath: 'src/pages/A.ts',
54
+ baseContent: 'export class A { private a: string; one(){return 1;} two(){return 2;} }',
55
+ headContent: 'export class A { private a: number; one(){return 1;} two(){return 2;} }',
56
+ }),
57
+ });
58
+
59
+ const methods = Array.from(result.changedMethodsByClass.get('A') || []);
60
+ assert.equal(methods.includes('one'), true);
61
+ assert.equal(methods.includes('two'), true);
62
+ });
63
+
64
+ test('collectChangedMethodsByClass marks all callables on top-level runtime change', () => {
65
+ const result = collectChangedMethodsByClass({
66
+ changedPomEntries: [changedEntry],
67
+ baseRef: 'HEAD',
68
+ readChangeContents: () => ({
69
+ basePath: 'src/pages/A.ts',
70
+ headPath: 'src/pages/A.ts',
71
+ baseContent: 'const FLAG = 1; export class A { one(){return 1;} two(){return 2;} }',
72
+ headContent: 'const FLAG = 2; export class A { one(){return 1;} two(){return 2;} }',
73
+ }),
74
+ });
75
+
76
+ assert.equal(result.stats.topLevelRuntimeChangedFiles, 1);
77
+ const methods = Array.from(result.changedMethodsByClass.get('A') || []);
78
+ assert.equal(methods.includes('one'), true);
79
+ assert.equal(methods.includes('two'), true);
80
+ });
81
+
82
+ test('collectChangedMethodsByClass handles class rename between base and head', () => {
83
+ const result = collectChangedMethodsByClass({
84
+ changedPomEntries: [changedEntry],
85
+ baseRef: 'HEAD',
86
+ readChangeContents: () => ({
87
+ basePath: 'src/pages/A.ts',
88
+ headPath: 'src/pages/B.ts',
89
+ baseContent: 'export class OldA { run(){ return 1; } }',
90
+ headContent: 'export class NewA { run(){ return 2; } }',
91
+ }),
92
+ });
93
+
94
+ const obj = mapToObject(result.changedMethodsByClass);
95
+ assert.equal(Object.keys(obj).length > 0, true);
96
+ });
97
+
98
+ test('buildImpactedMethodsByClass propagates through this-call chain', () => {
99
+ const dir = createTempDir();
100
+ const file = writeFile(dir, 'A.ts', 'export class A { leaf(){return 1;} mid(){ return this.leaf(); } top(){ return this.mid(); } }');
101
+
102
+ const result = buildImpactedMethodsByClass({
103
+ impactedClasses: new Set(['A']),
104
+ changedMethodsByClass: new Map([['A', new Set(['leaf'])]]),
105
+ parentsByChild: new Map(),
106
+ pageFiles: [file],
107
+ });
108
+
109
+ const methods = Array.from(result.impactedMethodsByClass.get('A') || []);
110
+ assert.equal(methods.includes('leaf'), true);
111
+ assert.equal(methods.includes('mid'), true);
112
+ assert.equal(methods.includes('top'), true);
113
+ });
114
+
115
+ test('buildImpactedMethodsByClass supports this["method"] call', () => {
116
+ const dir = createTempDir();
117
+ const file = writeFile(dir, 'A.ts', 'export class A { leaf(){return 1;} call(){ return this["leaf"](); } }');
118
+
119
+ const result = buildImpactedMethodsByClass({
120
+ impactedClasses: new Set(['A']),
121
+ changedMethodsByClass: new Map([['A', new Set(['leaf'])]]),
122
+ parentsByChild: new Map(),
123
+ pageFiles: [file],
124
+ });
125
+
126
+ const methods = Array.from(result.impactedMethodsByClass.get('A') || []);
127
+ assert.equal(methods.includes('call'), true);
128
+ });
129
+
130
+ test('buildImpactedMethodsByClass supports this[key] as fail-open by including class call sites', () => {
131
+ const dir = createTempDir();
132
+ const file = writeFile(
133
+ dir,
134
+ 'A.ts',
135
+ 'export class A { leaf(){return 1;} safe(){return 2;} dyn(){ const key = "leaf"; return this[key](); } caller(){ return this.dyn(); } }'
136
+ );
137
+
138
+ const result = buildImpactedMethodsByClass({
139
+ impactedClasses: new Set(['A']),
140
+ changedMethodsByClass: new Map([['A', new Set(['leaf'])]]),
141
+ parentsByChild: new Map(),
142
+ pageFiles: [file],
143
+ });
144
+
145
+ const methods = Array.from(result.impactedMethodsByClass.get('A') || []);
146
+ assert.equal(methods.includes('dyn'), true);
147
+ assert.equal(methods.includes('caller'), true);
148
+ });
149
+
150
+ test('buildImpactedMethodsByClass propagates through super calls', () => {
151
+ const dir = createTempDir();
152
+ const base = writeFile(dir, 'Base.ts', 'export class Base { changed(){ return 1; } }');
153
+ const child = writeFile(dir, 'Child.ts', 'export class Child extends Base { use(){ return super.changed(); } }');
154
+
155
+ const result = buildImpactedMethodsByClass({
156
+ impactedClasses: new Set(['Base']),
157
+ changedMethodsByClass: new Map([['Base', new Set(['changed'])]]),
158
+ parentsByChild: new Map([['Child', 'Base']]),
159
+ pageFiles: [base, child],
160
+ });
161
+
162
+ const childMethods = Array.from(result.impactedMethodsByClass.get('Child') || []);
163
+ assert.equal(childMethods.includes('use'), true);
164
+ });
165
+
166
+ test('buildImpactedMethodsByClass projects composition owner classes', () => {
167
+ const dir = createTempDir();
168
+ const widget = writeFile(dir, 'Widget.ts', 'export class Widget { click(){ return 1; } }');
169
+ const page = writeFile(dir, 'Page.ts', 'export class Page { widget: Widget; open(){ return this.widget.click(); } }');
170
+
171
+ const result = buildImpactedMethodsByClass({
172
+ impactedClasses: new Set(['Widget']),
173
+ changedMethodsByClass: new Map([['Widget', new Set(['click'])]]),
174
+ parentsByChild: new Map(),
175
+ pageFiles: [widget, page],
176
+ });
177
+
178
+ const pageMethods = Array.from(result.impactedMethodsByClass.get('Page') || []);
179
+ assert.equal(pageMethods.includes('open'), true);
180
+ });
181
+
182
+ test('buildImpactedMethodsByClass handles empty changed methods map', () => {
183
+ const dir = createTempDir();
184
+ const file = writeFile(dir, 'A.ts', 'export class A { run(){ return 1; } }');
185
+
186
+ const result = buildImpactedMethodsByClass({
187
+ impactedClasses: new Set(['A']),
188
+ changedMethodsByClass: new Map(),
189
+ parentsByChild: new Map(),
190
+ pageFiles: [file],
191
+ });
192
+
193
+ assert.equal(result.impactedMethodsByClass.size, 0);
194
+ assert.equal(result.stats.impactedMethodsTotal, 0);
195
+ });