Skip to content

Commit 2008dbd

Browse files
Merge master into feature/amazonqLSP
2 parents 0b32aff + 5213237 commit 2008dbd

File tree

5 files changed

+217
-22
lines changed

5 files changed

+217
-22
lines changed

packages/amazonq/test/e2e/amazonq/featureDev.test.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ import sinon from 'sinon'
99
import { registerAuthHook, using } from 'aws-core-vscode/test'
1010
import { loginToIdC } from './utils/setup'
1111
import { Messenger } from './framework/messenger'
12-
import { examples } from 'aws-core-vscode/amazonqFeatureDev'
1312
import { FollowUpTypes } from 'aws-core-vscode/amazonq'
1413
import { sleep } from 'aws-core-vscode/shared'
1514

1615
describe('Amazon Q Feature Dev', function () {
1716
let framework: qTestingFramework
1817
let tab: Messenger
1918

20-
const prompt = 'Add blank.txt file with empty content'
21-
const codegenApproachPrompt = `${prompt} and add a readme that describes the changes`
22-
const fileLevelAcceptPrompt = `${prompt} and add a license, and a contributing file`
19+
const prompt = 'Add current timestamp into blank.txt'
20+
const iteratePrompt = `Add a new section in readme to explain your change`
21+
const fileLevelAcceptPrompt = `${prompt} and ${iteratePrompt}`
22+
const informationCard =
23+
'After you provide a task, I will:\n1. Generate code based on your description and the code in your workspace\n2. Provide a list of suggestions for you to review and add to your workspace\n3. If needed, iterate based on your feedback\nTo learn more, visit the [user guide](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html)'
2324
const tooManyRequestsWaitTime = 100000
2425

2526
async function waitForText(text: string) {
@@ -144,34 +145,35 @@ describe('Amazon Q Feature Dev', function () {
144145
})
145146

146147
describe('/dev entry', () => {
147-
it('Clicks examples', async () => {
148-
const q = framework.createTab()
149-
q.addChatMessage({ command: '/dev' })
148+
before(async () => {
149+
tab = framework.createTab()
150+
tab.addChatMessage({ command: '/dev' }) // This would create a new tab for feature dev.
151+
tab = framework.getSelectedTab()
152+
})
153+
154+
it('should display information card', async () => {
150155
await retryIfRequired(
151156
async () => {
152-
await q.waitForChatFinishesLoading()
157+
await tab.waitForChatFinishesLoading()
153158
},
154159
() => {
155-
q.clickButton(FollowUpTypes.DevExamples)
156-
157-
const lastChatItems = q.getChatItems().pop()
158-
assert.deepStrictEqual(lastChatItems?.body, examples)
160+
const lastChatItems = tab.getChatItems().pop()
161+
assert.deepStrictEqual(lastChatItems?.body, informationCard)
159162
}
160163
)
161164
})
162165
})
163166

164-
// Disable failing tests while investigation. The tests are only failing in CI environments.
165-
describe.skip('/dev {msg} entry', async () => {
167+
describe('/dev {msg} entry', async () => {
166168
beforeEach(async function () {
169+
tab = framework.createTab()
167170
tab.addChatMessage({ command: '/dev', prompt })
171+
tab = framework.getSelectedTab()
168172
await retryIfRequired(
169173
async () => {
170174
await tab.waitForChatFinishesLoading()
171175
},
172-
() => {
173-
tab.addChatMessage({ prompt })
174-
}
176+
() => {}
175177
)
176178
})
177179

@@ -211,15 +213,17 @@ describe('Amazon Q Feature Dev', function () {
211213
})
212214
tab.clickButton(FollowUpTypes.ProvideFeedbackAndRegenerateCode)
213215
await tab.waitForChatFinishesLoading()
214-
await iterate(codegenApproachPrompt)
216+
await iterate(iteratePrompt)
215217
tab.clickButton(FollowUpTypes.InsertCode)
216218
await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession])
217219
})
218220
})
219221

220-
describe.skip('file-level accepts', async () => {
222+
describe('file-level accepts', async () => {
221223
beforeEach(async function () {
224+
tab = framework.createTab()
222225
tab.addChatMessage({ command: '/dev', prompt: fileLevelAcceptPrompt })
226+
tab = framework.getSelectedTab()
223227
await retryIfRequired(
224228
async () => {
225229
await tab.waitForChatFinishesLoading()

packages/amazonq/test/e2e/amazonq/framework/framework.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ export class qTestingFramework {
105105
return Object.entries(tabs).map(([tabId]) => new Messenger(tabId, this.mynahUIProps, this.mynahUI))
106106
}
107107

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

packages/core/src/test/codewhisperer/testUtil.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ import * as model from '../../codewhisperer/models/model'
2828
import { stub } from '../utilities/stubber'
2929
import { Dirent } from 'fs' // eslint-disable-line no-restricted-imports
3030

31-
export async function resetCodeWhispererGlobalVariables() {
31+
export async function resetCodeWhispererGlobalVariables(clearGlobalState: boolean = true) {
3232
vsCodeState.isIntelliSenseActive = false
3333
vsCodeState.isCodeWhispererEditing = false
3434
CodeWhispererCodeCoverageTracker.instances.clear()
3535
globals.telemetry.logger.clear()
3636
const session = CodeWhispererSessionState.instance.getSession()
3737
session.reset()
38-
await globals.globalState.clear()
38+
if (clearGlobalState) {
39+
await globals.globalState.clear()
40+
}
3941
await CodeSuggestionsState.instance.setSuggestionsEnabled(true)
4042
await RecommendationHandler.instance.clearInlineCompletionStates()
4143
}

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)