Skip to content

Commit

Permalink
chore: filter test tree by selected project
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Feb 28, 2024
1 parent 6bae596 commit 41ae646
Show file tree
Hide file tree
Showing 15 changed files with 294 additions and 219 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"url": "https://github.com/microsoft/playwright-vscode/issues"
},
"engines": {
"vscode": "^1.78.0"
"vscode": "^1.86.0"
},
"categories": [
"Testing"
Expand Down Expand Up @@ -128,7 +128,7 @@
"@types/glob": "^8.0.0",
"@types/node": "^18.11.9",
"@types/stack-utils": "^2.0.1",
"@types/vscode": "1.78.0",
"@types/vscode": "1.86.0",
"@types/which": "^2.0.1",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
Expand Down
53 changes: 35 additions & 18 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,17 +292,30 @@ export class Extension implements RunHooks {
}

const model = new TestModel(this._vscode, this._playwrightTest, workspaceFolderPath, configFileUri.fsPath, playwrightInfo, this._envProvider.bind(this));
this._models.push(model);
this._testTree.addModel(model);
const configError = await model.listFiles();
if (configError) {
configErrors.set(configError.location?.file ?? configFilePath, configError);
continue;
}

for (const project of model.projects.values()) {
await this._createRunProfile(project);
this._workspaceObserver.addWatchFolder(project.testDir);
this._models.push(model);
this._testTree.addModel(model);
}

// Update list of run profiles.
{
// rebuildModel can run concurrently, swapping run profiles needs to be sync.
const existingProfiles = this._runProfiles;
this._runProfiles = new Map();
for (const model of this._models) {
for (const project of model.allProjects().values()) {
this._createRunProfile(project, existingProfiles);
this._workspaceObserver.addWatchFolder(project.testDir);
}
}
for (const [id, profile] of existingProfiles) {
if (!this._runProfiles.has(id))
profile.dispose();
}
}

Expand Down Expand Up @@ -332,7 +345,7 @@ export class Extension implements RunHooks {
if (error?.location) {
const range = new this._vscode.Range(
new this._vscode.Position(Math.max(error.location.line - 4, 0), 0),
new this._vscode.Position(error.location.line - 1, error.location.column - 1),
new this._vscode.Position(Math.max(error.location.line - 1, 0), Math.max(error.location.column - 1, 0)),
);
this._vscode.window.activeTextEditor?.revealRange(range);
}
Expand All @@ -349,25 +362,26 @@ export class Extension implements RunHooks {
})) as NodeJS.ProcessEnv;
}

private async _createRunProfile(project: TestProject) {
private _createRunProfile(project: TestProject, existingProfiles: Map<string, vscodeTypes.TestRunProfile>) {
const configFile = project.model.config.configFile;
const configName = path.basename(configFile);
const folderName = path.basename(path.dirname(configFile));
const projectPrefix = project.name ? `${project.name} — ` : '';
const keyPrefix = configFile + ':' + project.name;
let runProfile = this._runProfiles.get(keyPrefix + ':run');
let runProfile = existingProfiles.get(keyPrefix + ':run');
const projectTag = this._testTree.projectTag(project);
const isDefault = false;
const supportsContinuousRun = this._settingsModel.allowWatchingFiles.get();
if (!runProfile) {
if (!runProfile)
runProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Run, this._scheduleTestRunRequest.bind(this, configFile, project.name, false), isDefault, projectTag, supportsContinuousRun);
this._runProfiles.set(keyPrefix + ':run', runProfile);
}
let debugProfile = this._runProfiles.get(keyPrefix + ':debug');
if (!debugProfile) {
this._runProfiles.set(keyPrefix + ':run', runProfile);
let debugProfile = existingProfiles.get(keyPrefix + ':debug');
if (!debugProfile)
debugProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Debug, this._scheduleTestRunRequest.bind(this, configFile, project.name, true), isDefault, projectTag, supportsContinuousRun);
this._runProfiles.set(keyPrefix + ':debug', debugProfile);
}
this._runProfiles.set(keyPrefix + ':debug', debugProfile);

runProfile.onDidChangeDefault(enabled => project.model.setProjectEnabled(project, enabled));
debugProfile.onDidChangeDefault(enabled => project.model.setProjectEnabled(project, enabled));
}

private _scheduleTestRunRequest(configFile: string, projectName: string, isDebug: boolean, request: vscodeTypes.TestRunRequest, cancellationToken?: vscodeTypes.CancellationToken) {
Expand All @@ -380,7 +394,7 @@ export class Extension implements RunHooks {
const model = this._models.find(m => m.config.configFile === configFile);
if (!model)
return;
const project = model.projects.get(projectName);
const project = model.allProjects().get(projectName);
if (!project)
return;

Expand Down Expand Up @@ -690,7 +704,10 @@ located next to Run / Debug Tests toolbar buttons.`);
const allErrors = new Set<string>();
const errorsByFile = new MultiMap<string, TestError>();
for (const model of this._models.slice()) {
const errors = await model.listTests([...files]).catch(e => console.log(e)) || [];
const filteredFiles = model.narrowDownFilesToEnabledProjects(files);
if (!filteredFiles.size)
continue;
const errors = await model.listTests([...filteredFiles]).catch(e => console.log(e)) || [];
for (const error of errors) {
if (!error.location || !error.message)
continue;
Expand Down Expand Up @@ -728,7 +745,7 @@ located next to Run / Debug Tests toolbar buttons.`);
diagnostics.push({
severity: this._vscode.DiagnosticSeverity.Error,
source: 'playwright',
range: new this._vscode.Range(error.location!.line - 1, error.location!.column - 1, error.location!.line, 0),
range: new this._vscode.Range(Math.max(error.location!.line - 1, 0), Math.max(error.location!.column - 1, 0), error.location!.line, 0),
message: error.message!,
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/reusedBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { TestConfig } from './playwrightTest';
import { TestModel, TestProject } from './testModel';
import type { TestModel } from './testModel';
import { createGuid } from './utils';
import * as vscodeTypes from './vscodeTypes';
import path from 'path';
Expand Down Expand Up @@ -324,7 +324,7 @@ export class ReusedBrowser implements vscodeTypes.Disposable {
}

private async _createFileForNewTest(model: TestModel) {
const project = model.projects.values().next().value as TestProject;
const project = model.enabledProjects()[0];
if (!project)
return;
let file;
Expand Down
82 changes: 49 additions & 33 deletions src/testModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,16 @@ export type TestProject = {
name: string;
testDir: string;
model: TestModel;
isFirst: boolean;
files: Map<string, TestFile>;
isEnabled: boolean;
};

export class TestModel {
private _vscode: vscodeTypes.VSCode;
readonly config: TestConfig;
readonly projects = new Map<string, TestProject>();
private _projects = new Map<string, TestProject>();
private _didUpdate: vscodeTypes.EventEmitter<void>;
readonly onUpdated: vscodeTypes.Event<void>;
readonly allFiles = new Set<string>();
private _playwrightTest: PlaywrightTest;
private _fileToSources: Map<string, string[]> = new Map();
private _sourceToFile: Map<string, string> = new Map();
Expand All @@ -85,6 +84,28 @@ export class TestModel {
this.onUpdated = this._didUpdate.event;
}

setProjectEnabled(project: TestProject, enabled: boolean) {
project.isEnabled = enabled;
this._didUpdate.fire();
}

allProjects(): Map<string, TestProject> {
return this._projects;
}

enabledProjects(): TestProject[] {
return [...this._projects.values()].filter(p => p.isEnabled);
}

enabledFiles(): string[] {
const result: string[] = [];
for (const project of this.enabledProjects()) {
for (const file of project.files.keys())
result.push(file);
}
return result;
}

async listFiles(): Promise<TestError | undefined> {
const report = await this._playwrightTest.listFiles(this.config);
if (report.error)
Expand All @@ -102,29 +123,28 @@ export class TestModel {
const projectsToKeep = new Set<string>();
for (const projectReport of report.projects) {
projectsToKeep.add(projectReport.name);
let project = this.projects.get(projectReport.name);
let project = this._projects.get(projectReport.name);
if (!project)
project = this._createProject(projectReport, projectReport === report.projects[0]);
project = this._createProject(projectReport);
this._updateProject(project, projectReport);
}

for (const projectName of this.projects.keys()) {
for (const projectName of this._projects.keys()) {
if (!projectsToKeep.has(projectName))
this.projects.delete(projectName);
this._projects.delete(projectName);
}

this._recalculateAllFiles();
this._didUpdate.fire();
}

private _createProject(projectReport: ProjectConfigWithFiles, isFirst: boolean): TestProject {
private _createProject(projectReport: ProjectConfigWithFiles): TestProject {
const project: TestProject = {
model: this,
...projectReport,
isFirst,
files: new Map(),
isEnabled: true,
};
this.projects.set(project.name, project);
this._projects.set(project.name, project);
return project;
}

Expand Down Expand Up @@ -157,7 +177,7 @@ export class TestModel {
change.deleted = this._mapFilesToSources(change.deleted);

if (change.deleted.size) {
for (const project of this.projects.values()) {
for (const project of this._projects.values()) {
for (const file of change.deleted) {
if (project.files.has(file)) {
project.files.delete(file);
Expand All @@ -169,7 +189,7 @@ export class TestModel {

if (change.created.size) {
let hasMatchingFiles = false;
for (const project of this.projects.values()) {
for (const project of this._projects.values()) {
for (const file of change.created) {
if (file.startsWith(project.testDir))
hasMatchingFiles = true;
Expand All @@ -179,12 +199,9 @@ export class TestModel {
await this.listFiles();
}

if (change.created.size || change.deleted.size)
this._recalculateAllFiles();

if (change.changed.size) {
const filesToLoad = new Set<string>();
for (const project of this.projects.values()) {
for (const project of this._projects.values()) {
for (const file of change.changed) {
const testFile = project.files.get(file);
if (!testFile || !testFile.entries())
Expand All @@ -200,17 +217,13 @@ export class TestModel {
}

async listTests(files: string[]): Promise<TestError[]> {
const sourcesToLoad = files.filter(f => this.allFiles.has(f));
if (!sourcesToLoad.length)
return [];

const { entries, errors } = await this._playwrightTest.listTests(this.config, sourcesToLoad);
this._updateProjects(entries, sourcesToLoad);
const { entries, errors } = await this._playwrightTest.listTests(this.config, files);
this._updateProjects(entries, files);
return errors;
}

private _updateProjects(projectEntries: Entry[], requestedFiles: string[]) {
for (const [projectName, project] of this.projects) {
for (const [projectName, project] of this._projects) {
const projectEntry = projectEntries.find(e => e.title === projectName);
const filesToDelete = new Set(requestedFiles);
for (const fileEntry of projectEntry?.children || []) {
Expand All @@ -232,7 +245,7 @@ export class TestModel {

updateFromRunningProjects(projectEntries: Entry[]) {
for (const projectEntry of projectEntries) {
const project = this.projects.get(projectEntry.title);
const project = this._projects.get(projectEntry.title);
if (project)
this._updateFromRunningProject(project, projectEntry);
}
Expand All @@ -252,14 +265,6 @@ export class TestModel {
this._didUpdate.fire();
}

private _recalculateAllFiles() {
this.allFiles.clear();
for (const project of this.projects.values()) {
for (const file of project.files.values())
this.allFiles.add(file.file);
}
}

async runTests(projects: TestProject[], locations: string[] | null, testListener: TestListener, parametrizedTestTitle: string | undefined, token: vscodeTypes.CancellationToken) {
locations = locations || [];
await this._playwrightTest.runTests(this.config, projects.map(p => p.name), locations, testListener, parametrizedTestTitle, token);
Expand All @@ -281,4 +286,15 @@ export class TestModel {
}
return result;
}

narrowDownFilesToEnabledProjects(fileNames: Set<string>) {
const result = new Set<string>();
for (const project of this.enabledProjects()) {
for (const fileName of fileNames) {
if (project.files.has(fileName))
result.add(fileName);
}
}
return result;
}
}
Loading

0 comments on commit 41ae646

Please sign in to comment.