Skip to content

Commit 330ff09

Browse files
committed
tests(amazonq): Add inline completion e2e tests
1 parent 30ea80a commit 330ff09

File tree

2 files changed

+176
-1
lines changed

2 files changed

+176
-1
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import assert from 'assert'
8+
import {
9+
assertTelemetry,
10+
closeAllEditors,
11+
getTestWindow,
12+
registerAuthHook,
13+
TestFolder,
14+
toTextEditor,
15+
using,
16+
} from 'aws-core-vscode/test'
17+
import { RecommendationHandler, RecommendationService } from 'aws-core-vscode/codewhisperer'
18+
import { Commands, globals, sleep, waitUntil } from 'aws-core-vscode/shared'
19+
import { loginToIdC } from '../amazonq/utils/setup'
20+
21+
describe('Amazon Q Inline', async function () {
22+
let tempFolder: string
23+
const waitOptions = {
24+
interval: 500,
25+
timeout: 10000,
26+
retryOnFail: false,
27+
}
28+
29+
before(async function () {
30+
await using(registerAuthHook('amazonq-test-account'), async () => {
31+
await loginToIdC()
32+
})
33+
})
34+
35+
beforeEach(async function () {
36+
registerAuthHook('amazonq-test-account')
37+
const folder = await TestFolder.create()
38+
tempFolder = folder.path
39+
await closeAllEditors()
40+
})
41+
42+
afterEach(async function () {
43+
await closeAllEditors()
44+
})
45+
46+
async function setupEditor({ name, contents }: { name?: string; contents?: string } = {}) {
47+
const fileName = name ?? 'test.ts'
48+
const textContents =
49+
contents ??
50+
`function fib() {
51+
52+
53+
}`
54+
await toTextEditor(textContents, fileName, tempFolder, {
55+
selection: new vscode.Range(new vscode.Position(1, 4), new vscode.Position(1, 4)),
56+
})
57+
}
58+
59+
async function waitForRecommendations() {
60+
const ok = await waitUntil(async () => RecommendationHandler.instance.isSuggestionVisible(), waitOptions)
61+
if (!ok) {
62+
assert.fail('Suggestions failed to become visible')
63+
}
64+
}
65+
66+
async function waitForTelemetry() {
67+
const ok = await waitUntil(
68+
async () =>
69+
globals.telemetry.logger.query({
70+
metricName: 'codewhisperer_userTriggerDecision',
71+
}).length > 0,
72+
waitOptions
73+
)
74+
if (!ok) {
75+
assert.fail('Telemetry failed to be emitted')
76+
}
77+
}
78+
79+
for (const [name, invokeCompletion] of [
80+
['automatic', async () => await vscode.commands.executeCommand('type', { text: '\n' })],
81+
['manual', async () => Commands.tryExecute('aws.amazonq.invokeInlineCompletion')],
82+
] as const) {
83+
describe(`${name} invoke`, async function () {
84+
let originalEditorContents: string | undefined
85+
86+
describe('supported filetypes', () => {
87+
beforeEach(async () => {
88+
await setupEditor()
89+
90+
/**
91+
* Allow some time between when the editor is opened and when we start typing.
92+
* If we don't do this then the time between the initial editor selection
93+
* and invoking the "type" command is too low, causing completion to never
94+
* activate. AFAICT there isn't anything we can use waitUntil on here
95+
**/
96+
await sleep(1000)
97+
98+
await invokeCompletion()
99+
originalEditorContents = vscode.window.activeTextEditor?.document.getText()
100+
101+
// wait until the ghost text appears
102+
await waitForRecommendations()
103+
})
104+
105+
it(`${name} invoke accept`, async function () {
106+
/**
107+
* keep accepting the suggestion until the text contents change
108+
* this is required because we have no access to the inlineSuggest panel
109+
**/
110+
const suggestionAccepted = await waitUntil(async () => {
111+
// Accept the suggestion
112+
await vscode.commands.executeCommand('editor.action.inlineSuggest.commit')
113+
return vscode.window.activeTextEditor?.document.getText() !== originalEditorContents
114+
}, waitOptions)
115+
116+
assert.ok(suggestionAccepted, 'Editor contents should have changed')
117+
118+
await waitForTelemetry()
119+
assertTelemetry('codewhisperer_userTriggerDecision', {
120+
codewhispererSuggestionState: 'Accept',
121+
})
122+
})
123+
124+
it(`${name} invoke reject`, async function () {
125+
// Reject the suggestion
126+
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')
127+
128+
// Contents haven't changed
129+
assert.deepStrictEqual(vscode.window.activeTextEditor?.document.getText(), originalEditorContents)
130+
131+
await waitForTelemetry()
132+
assertTelemetry('codewhisperer_userTriggerDecision', {
133+
codewhispererSuggestionState: 'Reject',
134+
})
135+
})
136+
137+
it(`${name} invoke discard`, async function () {
138+
// Discard the suggestion by moving it back to the original position
139+
const position = new vscode.Position(1, 4)
140+
const editor = vscode.window.activeTextEditor
141+
if (!editor) {
142+
assert.fail('Could not find text editor')
143+
}
144+
editor.selection = new vscode.Selection(position, position)
145+
146+
// Contents are the same
147+
assert.deepStrictEqual(vscode.window.activeTextEditor?.document.getText(), originalEditorContents)
148+
})
149+
})
150+
151+
it(`${name} invoke on unsupported filetype`, async function () {
152+
await setupEditor({
153+
name: 'test.zig',
154+
contents: `fn doSomething() void {
155+
156+
}`,
157+
})
158+
159+
/**
160+
* Add delay between editor loading and invoking completion
161+
* @see beforeEach in supported filetypes for more information
162+
*/
163+
await sleep(1000)
164+
await invokeCompletion()
165+
166+
if (name === 'automatic') {
167+
// It should never get triggered since its not a supported file type
168+
assert.deepStrictEqual(RecommendationService.instance.isRunning, false)
169+
} else {
170+
await getTestWindow().waitForMessage('currently not supported by Amazon Q inline suggestions')
171+
}
172+
})
173+
})
174+
}
175+
})

packages/core/src/test/setupUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ function maskArns(text: string) {
211211
*/
212212
export function registerAuthHook(secret: string, lambdaId = process.env['AUTH_UTIL_LAMBDA_ARN']) {
213213
return getTestWindow().onDidShowMessage((message) => {
214-
if (message.items[0].title.match(new RegExp(proceedToBrowser))) {
214+
if (message.items.length > 0 && message.items[0].title.match(new RegExp(proceedToBrowser))) {
215215
if (!lambdaId) {
216216
const baseMessage = 'Browser login flow was shown during testing without an authorizer function'
217217
if (process.env['AWS_TOOLKIT_AUTOMATION'] === 'local') {

0 commit comments

Comments
 (0)