diff --git a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts index 9cd87003f36..644b6209452 100644 --- a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts +++ b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts @@ -215,5 +215,87 @@ describe('Amazon Q Feature Dev', function () { tab.clickButton(FollowUpTypes.InsertCode) await waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) }) + + describe('file-level accepts', async () => { + beforeEach(async () => { + await retryIfRequired(async () => { + await Promise.any([ + waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), + waitForButtons([FollowUpTypes.Retry]), + ]) + }) + }) + + describe('fileList', async () => { + it('has both accept-change and reject-change action buttons for file', async () => { + const filePath = tab.getFilePaths()[0] + assert.ok(tab.getActionsByFilePath(filePath).length === 2) + assert.ok(tab.hasAction(filePath, 'accept-change')) + assert.ok(tab.hasAction(filePath, 'reject-change')) + }) + + it('has only revert-rejection action buttons for rejected file', async () => { + const filePath = tab.getFilePaths()[0] + tab.clickFileActionButton(filePath, 'reject-change') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'reject-change')) + + assert.ok(tab.getActionsByFilePath(filePath).length === 1) + assert.ok(tab.hasAction(filePath, 'revert-rejection')) + }) + + it('does not have any action buttons for accepted file', async () => { + const filePath = tab.getFilePaths()[0] + tab.clickFileActionButton(filePath, 'accept-change') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'accept-change')) + + assert.ok(tab.getActionsByFilePath(filePath).length === 0) + }) + }) + + describe('accept changes button', async () => { + describe('button text', async () => { + it('shows Accept all changes when all files are neither accepted nor rejected', async () => { + const insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept all changes') + }) + + it('shows Accept remaining changes when one or more file is rejected', async () => { + const filePath = tab.getFilePaths()[0] + tab.clickFileActionButton(filePath, 'reject-change') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'reject-change')) + + const insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept remaining changes') + }) + + it('shows Accept remaining changes when one or more file is accepted', async () => { + const filePath = tab.getFilePaths()[0] + tab.clickFileActionButton(filePath, 'accept-change') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'accept-change')) + + const insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept remaining changes') + }) + + it('shows Accept remaining changes when a file is rejected, then shows Accept all changes again when the rejection is reverted', async () => { + let insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept all changes') + + const filePath = tab.getFilePaths()[0] + tab.clickFileActionButton(filePath, 'reject-change') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'reject-change')) + + insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept remaining changes') + + tab.clickFileActionButton(filePath, 'revert-rejection') + await tab.waitForEvent(() => !tab.hasAction(filePath, 'revert-rejection')) + + insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept all changes') + }) + }) + }) + }) }) }) diff --git a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts index ce612bd41c5..f026e4366b4 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts @@ -59,6 +59,14 @@ export class Messenger { this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', option[0]) } + clickFileActionButton(filePath: string, actionName: string) { + if (!this.mynahUIProps.onFileActionClick) { + assert.fail('onFileActionClick must be defined to use it in the tests') + } + + this.mynahUIProps.onFileActionClick(this.tabID, this.getFileListMessageId(), filePath, actionName) + } + findCommand(command: string) { return this.getCommands() .map((groups) => groups.commands) @@ -78,6 +86,55 @@ export class Messenger { return this.getStore().promptInputPlaceholder } + getFollowUpButton(type: FollowUpTypes) { + const followUpButton = this.getChatItems() + .pop() + ?.followUp?.options?.find((action) => action.type === type) + if (!followUpButton) { + assert.fail(`Could not find follow up button with type ${type}`) + } + return followUpButton + } + + getFileList() { + const chatItems = this.getChatItems() + const fileList = chatItems.find((item) => 'fileList' in item) + if (!fileList) { + assert.fail('Could not find file list') + } + return fileList + } + + getFileListMessageId() { + const fileList = this.getFileList() + const messageId = fileList?.messageId + if (!messageId) { + assert.fail('Could not find file list message id') + } + return messageId + } + + getFilePaths() { + const fileList = this.getFileList() + const filePaths = fileList?.fileList?.filePaths + if (!filePaths) { + assert.fail('Could not find file paths') + } + if (filePaths.length === 0) { + assert.fail('File paths list is empty') + } + return filePaths + } + + getActionsByFilePath(filePath: string) { + const fileList = this.getFileList() + const actions = fileList?.fileList?.actions + if (!actions) { + assert.fail('Could not find file list actions') + } + return actions[filePath] + } + hasButton(type: FollowUpTypes) { return ( this.getChatItems() @@ -87,6 +144,10 @@ export class Messenger { ) } + hasAction(filePath: string, actionName: string) { + return this.getActionsByFilePath(filePath).some((action) => action.name === actionName) + } + async waitForChatFinishesLoading() { return this.waitForEvent(() => this.getStore().loadingChat === false || this.hasButton(FollowUpTypes.Retry)) }