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