Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/slash-test/testGen.ts.stest.ts
13394 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { dirname, join } from 'path';
8
import { Intent } from '../../../src/extension/common/constants';
9
import { TestsIntent } from '../../../src/extension/intents/node/testIntent/testIntent';
10
import { ConfigKey } from '../../../src/platform/configuration/common/configurationService';
11
import { deserializeWorkbenchState } from '../../../src/platform/test/node/promptContextModel';
12
import { assertType } from '../../../src/util/vs/base/common/types';
13
import { ssuite, stest } from '../../base/stest';
14
import { generateScenarioTestRunner } from '../../e2e/scenarioTest';
15
import { forInline, simulateInlineChatWithStrategy } from '../inlineChatSimulator';
16
import { assertContainsAllSnippets, assertNoSyntacticDiagnosticsAsync, getFileContent } from '../outcomeValidators';
17
import { assertInlineEdit, assertInlineEditShape, assertNoStrings, assertSomeStrings, assertWorkspaceEdit, fromFixture } from '../stestUtil';
18
19
20
forInline((strategy, nonExtensionConfigurations, suffix) => {
21
22
ssuite({ title: `/tests${suffix}`, location: 'inline', language: 'typescript', nonExtensionConfigurations }, () => {
23
24
stest({ description: 'can add a test after an existing one', }, (testingServiceCollection) => {
25
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
26
files: [
27
fromFixture('tests/simple-ts-proj-with-test-file/src/index.ts'),
28
fromFixture('tests/simple-ts-proj-with-test-file/src/test/index.test.ts'),
29
],
30
queries: [
31
{
32
file: 'index.ts',
33
selection: [0, 17],
34
query: '/tests',
35
expectedIntent: Intent.Tests,
36
validate: async (outcome, workspace, accessor) => {
37
assertWorkspaceEdit(outcome);
38
39
const changedFile = outcome.files.at(0);
40
assert.ok(changedFile);
41
assert([...getFileContent(changedFile).matchAll(/\n\tit/g)].length > 1);
42
43
const sixthLine = getFileContent(changedFile).split(/\r\n|\r|\n/g).at(6);
44
45
assert(sixthLine !== '});', `new tests are inserted within the existing suite: expected NOT '});'`);
46
},
47
},
48
],
49
});
50
});
51
52
stest({ description: 'can add a test after an existing one with empty line', }, (testingServiceCollection) => {
53
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
54
files: [
55
fromFixture('tests/simple-ts-proj-with-test-file-1/src/index.ts'),
56
fromFixture('tests/simple-ts-proj-with-test-file-1/src/test/index.test.ts'),
57
],
58
queries: [
59
{
60
file: 'index.ts',
61
selection: [0, 17],
62
query: '/tests',
63
expectedIntent: Intent.Tests,
64
validate: async (outcome, workspace, accessor) => {
65
assertWorkspaceEdit(outcome);
66
assertType(outcome.files[0]);
67
assert([...getFileContent(outcome.files[0]).matchAll(/\n\tit/g)].length > 1);
68
},
69
},
70
],
71
});
72
});
73
74
stest({ description: 'supports chat variables', }, (testingServiceCollection) => {
75
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
76
files: [
77
fromFixture('tests/simple-ts-proj/src/index.ts'),
78
fromFixture('tests/simple-ts-proj/src/math.ts'),
79
],
80
queries: [
81
{
82
file: 'index.ts',
83
selection: [0, 17],
84
query: '/tests keep in mind #file:math.ts',
85
expectedIntent: Intent.Tests,
86
validate: async (outcome, workspace, accessor) => {
87
assertWorkspaceEdit(outcome);
88
assertType(outcome.files[0]);
89
assert(getFileContent(outcome.files[0]).match('subtract'));
90
},
91
},
92
],
93
});
94
});
95
96
stest({ description: 'BidiMap test generation (inside file)', }, (testingServiceCollection) => {
97
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
98
files: [
99
fromFixture('tests/generate-for-selection', 'base/test/common/map.test.ts'),
100
fromFixture('tests/generate-for-selection', 'base/common/map.ts'),
101
],
102
queries: [{
103
file: 'base/common/map.ts',
104
selection: [671, 0, 725, 1],
105
query: '/tests',
106
expectedIntent: Intent.Tests,
107
validate: async (outcome, workspace, accessor) => {
108
assertWorkspaceEdit(outcome);
109
110
assert.strictEqual(outcome.files.length, 1);
111
112
const [first] = outcome.files;
113
assertSomeStrings(getFileContent(first), ['suite', 'test', 'assert.strictEqual']);
114
assertNoStrings(getFileContent(first), ['import']);
115
}
116
}],
117
});
118
});
119
120
stest({ description: 'BidiMap test generation (inside test)', }, (testingServiceCollection) => {
121
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
122
files: [
123
fromFixture('tests/generate-for-selection', 'base/test/common/map.test.ts'),
124
fromFixture('tests/generate-for-selection', 'base/common/map.ts'),
125
],
126
queries: [{
127
file: 'base/test/common/map.test.ts',
128
selection: [470, 13, 470, 13],
129
query: '/tests Write tests for BidiMap',
130
expectedIntent: Intent.Tests,
131
validate: async (outcome, workspace, accessor) => {
132
assertInlineEdit(outcome);
133
134
assert.ok(outcome.appliedEdits.length >= 1);
135
136
assert.ok(outcome.appliedEdits.some(edit =>
137
edit.newText.includes('suite')
138
&& edit.newText.includes('test')
139
&& edit.newText.includes('assert.strictEqual')
140
));
141
}
142
}],
143
});
144
});
145
146
stest({ description: 'ts-new-test', }, (testingServiceCollection) => {
147
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
148
files: [
149
fromFixture('/tests/ts-another-test-4636/', 'stickyScroll.test.ts'),
150
],
151
queries: [{
152
file: 'stickyScroll.test.ts',
153
selection: [252, 0],
154
query: '/tests add one more test for testing findScrollWidgetState',
155
expectedIntent: Intent.Tests,
156
validate: async (outcome, workspace, accessor) => {
157
assertInlineEdit(outcome);
158
159
assert.ok(outcome.appliedEdits.length >= 1);
160
assert.ok(outcome.appliedEdits.some(edit => edit.newText.match(/test\(.*findScrollWidgetState/)));
161
}
162
}]
163
});
164
});
165
166
});
167
});
168
169
// the folloing tests test the intent-detection. Inline2 does not do intent-detection.
170
171
forInline((strategy, nonExtensionConfigurations) => {
172
173
ssuite({ title: `/tests`, subtitle: 'real world', location: 'inline', language: 'typescript', nonExtensionConfigurations }, () => {
174
175
stest({ description: 'generate a unit test', }, (testingServiceCollection) => {
176
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
177
files: [
178
fromFixture('tests/ts-leading-whitespace/charCode.ts'),
179
fromFixture('tests/ts-leading-whitespace/strings.ts'),
180
],
181
queries: [
182
{
183
file: 'strings.ts',
184
selection: [250, 3, 257, 4],
185
query: 'generate a unit test',
186
expectedIntent: Intent.Tests,
187
validate: async (outcome, workspace, accessor) => {
188
assert.strictEqual(outcome.type, 'workspaceEdit');
189
},
190
},
191
],
192
});
193
});
194
stest({ description: 'issue #3699: add test for function', }, (testingServiceCollection) => {
195
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
196
files: [
197
fromFixture('tests/for-method-issue-3699/foldingRanges.ts'),
198
],
199
queries: [
200
{
201
file: 'foldingRanges.ts',
202
selection: [419, 1, 421, 2],
203
query: 'add test for this function',
204
expectedIntent: Intent.Tests,
205
validate: async (outcome, workspace, accessor) => {
206
assert.strictEqual(outcome.type, 'workspaceEdit');
207
},
208
},
209
],
210
});
211
});
212
213
stest({ description: 'issue #3701: add some more tests for folding', }, (testingServiceCollection) => {
214
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
215
files: [
216
fromFixture('tests/in-suite-issue-3701/notebookFolding.test.ts'),
217
],
218
queries: [
219
{
220
file: 'notebookFolding.test.ts',
221
selection: [132, 2],
222
query: 'add some more tests for folding',
223
expectedIntent: Intent.Tests,
224
validate: async (outcome, workspace, accessor) => {
225
assert.strictEqual(outcome.type, 'inlineEdit');
226
const lines = outcome.fileContents.split(/\r\n|\r|\n/g);
227
assert.ok(lines.length >= 132 + 276);
228
// remove first 132 lines
229
lines.splice(0, 132);
230
// remove last 276 lines
231
lines.splice(lines.length - 276, 276);
232
const text = lines.join('\n');
233
return assertContainsAllSnippets(text, ['withTestNotebook', 'assert'], 'tests/in-suite-issue-3701');
234
},
235
},
236
],
237
});
238
});
239
240
241
stest('add another test for containsUppercaseCharacter with other non latin chars', (testingServiceCollection) => {
242
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
243
files: [
244
fromFixture('tests/another-unit-test/strings.test.ts'),
245
fromFixture('tests/another-unit-test/charCode.ts'),
246
fromFixture('tests/another-unit-test/strings.ts')],
247
queries: [
248
{
249
file: 'strings.test.ts',
250
selection: [344, 0],
251
query: 'add another test for containsUppercaseCharacter with other non latin chars',
252
expectedIntent: TestsIntent.ID,
253
validate: async (outcome, workspace, accessor) => {
254
assertInlineEdit(outcome);
255
await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');
256
const edit = assertInlineEditShape(outcome, [{
257
line: 344,
258
originalLength: 0,
259
modifiedLength: undefined
260
}, {
261
line: 344,
262
originalLength: 1,
263
modifiedLength: undefined
264
}]);
265
assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['containsUppercaseCharacter', 'assert.strictEqual']);
266
},
267
},
268
],
269
});
270
});
271
});
272
ssuite({ title: `/tests`, subtitle: 'custom instructions', location: 'inline', language: 'typescript', nonExtensionConfigurations }, function () {
273
const testGenConfigOnly = [
274
{
275
key: ConfigKey.TestGenerationInstructions,
276
value: [
277
{ 'text': `Add a comment: 'Generated by Copilot'` },
278
{ 'text': 'use TDD instead of BDD', 'language': 'typescript' },
279
{ 'text': 'use ssuite instead of suite and stest instead of test', 'language': 'typescript' },
280
]
281
}
282
];
283
stest({ description: '[test gen config] can add a test after an existing one with empty line', configurations: testGenConfigOnly }, (testingServiceCollection) => {
284
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
285
files: [
286
fromFixture('tests/simple-ts-proj-with-test-file-2/src/index.ts'),
287
fromFixture('tests/simple-ts-proj-with-test-file-2/src/test/index.test.ts'),
288
],
289
queries: [
290
{
291
file: 'index.ts',
292
selection: [0, 17],
293
query: '/tests',
294
expectedIntent: Intent.Tests,
295
validate: async (outcome, workspace, accessor) => {
296
assertWorkspaceEdit(outcome);
297
298
const fileContents = getFileContent(outcome.files[0]);
299
assertType(fileContents);
300
301
['ssuite', 'stest', 'Generated by Copilot'].forEach(needle => assert.ok(fileContents.includes(needle)));
302
},
303
},
304
],
305
});
306
});
307
308
const codeGenAndTestGenConfig = [
309
{
310
key: ConfigKey.CodeGenerationInstructions,
311
value: [
312
{ 'text': `Add a comment: 'Generated by Copilot'` },
313
]
314
},
315
{
316
key: ConfigKey.TestGenerationInstructions,
317
value: [
318
{ 'text': 'use TDD instead of BDD', 'language': 'typescript' },
319
{ 'text': 'use ssuite instead of suite and stest instead of test', 'language': 'typescript' },
320
]
321
}
322
];
323
stest({ description: '[code gen + test gen config] can add a test after an existing one with empty line', configurations: codeGenAndTestGenConfig }, (testingServiceCollection) => {
324
return simulateInlineChatWithStrategy(strategy, testingServiceCollection, {
325
files: [
326
fromFixture('tests/simple-ts-proj-with-test-file-2/src/index.ts'),
327
fromFixture('tests/simple-ts-proj-with-test-file-2/src/test/index.test.ts'),
328
],
329
queries: [
330
{
331
file: 'index.ts',
332
selection: [0, 17],
333
query: '/tests',
334
expectedIntent: Intent.Tests,
335
validate: async (outcome, workspace, accessor) => {
336
assertWorkspaceEdit(outcome);
337
338
const fileContents = getFileContent(outcome.files[0]);
339
assertType(fileContents);
340
341
['ssuite', 'stest', 'Generated by Copilot'].forEach(needle => assert.ok(fileContents.includes(needle)));
342
},
343
},
344
],
345
});
346
});
347
});
348
});
349
350
// the folloing tests are panel tests
351
352
ssuite({ title: `/tests`, location: 'panel', language: 'typescript' }, function () {
353
354
{
355
const root = join(__dirname, '../test/simulation/fixtures/tests/panel/tsq');
356
const path = join(root, 'workspaceState.state.json');
357
358
stest('can consume #file without active editor',
359
generateScenarioTestRunner([{
360
name: 'can consume #file without active editor',
361
question: '/tests test #file:foo.ts',
362
scenarioFolderPath: root,
363
stateFile: path,
364
getState: () => deserializeWorkbenchState(dirname(path), path),
365
}], async (accessor, question, answer) => {
366
367
try {
368
assert.ok(
369
['test', 'suite', 'describe', 'it'].some(x => answer.includes(x)),
370
'includes one of test, suite, describe, it with an opening parenthesis'
371
);
372
373
assert.ok(
374
(answer.includes('subtract') || answer.includes('add') || answer.includes('multiply')),
375
'includes one of subtract, add, multiply'
376
);
377
} catch (e) {
378
return { success: false, errorMessage: e.message };
379
}
380
381
return { success: true, };
382
}));
383
}
384
});
385
386