testing: exploratory UI for followup actions

This adds an API that extensions can use to contribute 'followup'
actions around test messages. Here just a dummy hello world using
copilot, but extensions could have any action here, such as actions to
update snapshots if a test failed:

![](https://memes.peet.io/img/24-05-c1f3e073-a2da-4f16-a033-c8f7e5cd4864.png)

Implemented using a simple provider API.
This commit is contained in:
Connor Peet
2024-05-21 16:09:08 -07:00
parent 71e25d9b3c
commit e1dfc911ce
12 changed files with 315 additions and 34 deletions

View File

@@ -25,7 +25,7 @@ const TEST_FILE_PATTERN = 'src/vs/**/*.{test,integrationTest}.ts';
const getWorkspaceFolderForTestFile = (uri: vscode.Uri) =>
(uri.path.endsWith('.test.ts') || uri.path.endsWith('.integrationTest.ts')) &&
uri.path.includes('/src/vs/')
uri.path.includes('/src/vs/')
? vscode.workspace.getWorkspaceFolder(uri)
: undefined;
@@ -41,6 +41,18 @@ export async function activate(context: vscode.ExtensionContext) {
const ctrl = vscode.tests.createTestController('selfhost-test-controller', 'VS Code Tests');
const fileChangedEmitter = new vscode.EventEmitter<FileChangeEvent>();
// todo@connor4312: tidy this up and make it work
// context.subscriptions.push(vscode.tests.registerTestFollowupProvider({
// async provideFollowup(result, test, taskIndex, messageIndex, token) {
// await new Promise(r => setTimeout(r, 2000));
// return [{
// title: '$(sparkle) Ask copilot for help',
// command: 'asdf'
// }];
// },
// }));
ctrl.resolveHandler = async test => {
if (!test) {
context.subscriptions.push(await startWatchingWorkspace(ctrl, fileChangedEmitter));
@@ -62,7 +74,7 @@ export async function activate(context: vscode.ExtensionContext) {
});
const createRunHandler = (
runnerCtor: { new (folder: vscode.WorkspaceFolder): VSCodeTestRunner },
runnerCtor: { new(folder: vscode.WorkspaceFolder): VSCodeTestRunner },
kind: vscode.TestRunProfileKind,
args: string[] = []
) => {

View File

@@ -71,8 +71,6 @@ export class FailingDeepStrictEqualAssertFixer {
},
})
);
tests.testResults;
}
dispose() {
@@ -99,15 +97,15 @@ const formatJsonValue = (value: unknown) => {
context => (node: ts.Node) => {
const visitor = (node: ts.Node): ts.Node =>
ts.isPropertyAssignment(node) &&
ts.isStringLiteralLike(node.name) &&
identifierLikeRe.test(node.name.text)
ts.isStringLiteralLike(node.name) &&
identifierLikeRe.test(node.name.text)
? ts.factory.createPropertyAssignment(
ts.factory.createIdentifier(node.name.text),
ts.visitNode(node.initializer, visitor) as ts.Expression
)
ts.factory.createIdentifier(node.name.text),
ts.visitNode(node.initializer, visitor) as ts.Expression
)
: ts.isStringLiteralLike(node) && node.text === '[undefined]'
? ts.factory.createIdentifier('undefined')
: ts.visitEachChild(node, visitor, context);
? ts.factory.createIdentifier('undefined')
: ts.visitEachChild(node, visitor, context);
return ts.visitNode(node, visitor);
},
@@ -190,7 +188,7 @@ class StrictEqualAssertion {
return undefined;
}
constructor(private readonly expression: ts.CallExpression) {}
constructor(private readonly expression: ts.CallExpression) { }
/** Gets the expected value */
public get expectedValue(): ts.Expression | undefined {