Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export class TestRunner {
}
}

class CompiledFileTests {
export class CompiledFileTests {
public readonly value = new Map</* compiled file path */ string, /* source file test items */ Set<vscode.TestItem>>();

/**
Expand All @@ -476,10 +476,13 @@ class CompiledFileTests {

/**
* Gets a test item by its path of titles in the test file.
* Falls back to the deepest matched ancestor when the full path
* can't be resolved (e.g. when tests are dynamically generated).
*/
private getPathInTestItems(items: Set<vscode.TestItem>, path: readonly string[]) {
for (const item of items) {
let candidate: vscode.TestItem | undefined = item;
let deepestMatch: vscode.TestItem | undefined;
for (let i = 0; i < path.length && candidate; i++) {
const pathPart = path[i];
if (
Expand All @@ -490,11 +493,20 @@ class CompiledFileTests {
) {
break;
}
candidate = candidate.children.get(pathPart);
const next = candidate.children.get(pathPart);
if (next) {
deepestMatch = next;
candidate = next;
} else {
candidate = undefined;
}
}
if (candidate !== undefined) {
return candidate;
}
if (deepestMatch !== undefined) {
return deepestMatch;
}
}
}

Expand Down
103 changes: 103 additions & 0 deletions src/test/unit/runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright (C) Daniel Kuschny (Danielku15) and contributors.
* Copyright (C) Microsoft Corporation. All rights reserved.
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import { expect } from 'chai';
import type * as vscode from 'vscode';
import { CompiledFileTests } from '../../runner';

/** Minimal mock that satisfies the subset of vscode.TestItem used by CompiledFileTests. */
function mockTestItem(id: string, childItems: vscode.TestItem[] = []): vscode.TestItem {
const childMap = new Map<string, vscode.TestItem>();
for (const child of childItems) {
childMap.set(child.id, child);
}
return {
id,
label: id,
children: {
get: (key: string) => childMap.get(key),
size: childMap.size,
},
} as unknown as vscode.TestItem;
}

describe('CompiledFileTests', () => {
const FILE = '/project/out/test.js';

function buildTree() {
const cft = new CompiledFileTests();

const test1 = mockTestItem('test1');
const test2 = mockTestItem('test2');
const innerSuite = mockTestItem('innerSuite', [mockTestItem('deep')]);
const suite = mockTestItem('suite', [test1, test2, innerSuite]);
const fileItem = mockTestItem('file', [suite]);

cft.push(FILE, fileItem);
return { cft, fileItem, suite, test1, test2, innerSuite };
}

describe('lookup', () => {
it('returns the exact item for a fully matching path', () => {
const { cft, test1 } = buildTree();
expect(cft.lookup(FILE, ['suite', 'test1'])).to.equal(test1);
});

it('returns a nested item through multiple levels', () => {
const { cft, innerSuite } = buildTree();
const deep = innerSuite.children.get('deep');
expect(cft.lookup(FILE, ['suite', 'innerSuite', 'deep'])).to.equal(deep);
});

it('returns undefined when no path segment matches at all', () => {
const { cft } = buildTree();
expect(cft.lookup(FILE, ['nonExistent'])).to.be.undefined;
});

it('falls back to the deepest matched ancestor for an unresolvable tail', () => {
const { cft, suite } = buildTree();
expect(cft.lookup(FILE, ['suite', 'dynamicTest'])).to.equal(suite);
});

it('falls back to the deepest match when multiple trailing segments are missing', () => {
const { cft, suite } = buildTree();
expect(cft.lookup(FILE, ['suite', 'dynamic', 'nested'])).to.equal(suite);
});

it('falls back several levels deep when the leaf is missing', () => {
const { cft, innerSuite } = buildTree();
expect(cft.lookup(FILE, ['suite', 'innerSuite', 'missingLeaf'])).to.equal(innerSuite);
});

it('returns the suite when a hook path is encountered', () => {
const { cft, suite } = buildTree();
expect(cft.lookup(FILE, ['suite', '"before all" hook'])).to.equal(suite);
});

it('returns the suite for "after each" hooks', () => {
const { cft, suite } = buildTree();
expect(cft.lookup(FILE, ['suite', '"after each" hook for test1'])).to.equal(suite);
});

it('looks up across all files when no file hint is provided', () => {
const { cft, test2 } = buildTree();
expect(cft.lookup(undefined, ['suite', 'test2'])).to.equal(test2);
});

it('returns undefined when the file hint does not match any registered file', () => {
const { cft } = buildTree();
expect(cft.lookup('/other/file.js', ['suite', 'test1'])).to.be.undefined;
});

it('returns the file-level item when path is empty', () => {
const { cft, fileItem } = buildTree();
expect(cft.lookup(FILE, [])).to.equal(fileItem);
});
});
});