Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/amazonq/test/e2e/amazonq/assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'assert'
import { Messenger } from './framework/messenger'

export function assertQuickActions(tab: Messenger, commands: string[]) {
const commandGroup = tab
.getCommands()
.map((groups) => groups.commands)
.flat()
if (!commandGroup) {
assert.fail(`Could not find commands for ${tab.tabID}`)
}

const commandNames = commandGroup.map((cmd) => cmd.command)

const missingCommands = []
for (const command of commands) {
if (!commandNames.includes(command)) {
missingCommands.push(command)
}
}

if (missingCommands.length > 0) {
assert.fail(`Could not find commands: ${missingCommands.join(', ')} for ${tab.tabID}`)
}
}
45 changes: 45 additions & 0 deletions packages/amazonq/test/e2e/amazonq/explore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'assert'
import sinon from 'sinon'
import { qTestingFramework } from './framework/framework'
import { Messenger } from './framework/messenger'

describe('Amazon Q Explore page', function () {
let framework: qTestingFramework
let tab: Messenger

beforeEach(() => {
framework = new qTestingFramework('agentWalkthrough', true, [], 0)
const welcomeTab = framework.getTabs()[0]
welcomeTab.clickInBodyButton('explore')

// Find the new explore tab
const exploreTab = framework.findTab('Explore')
if (!exploreTab) {
assert.fail('Explore tab not found')
}
tab = exploreTab
})

afterEach(() => {
framework.removeTab(tab.tabID)
framework.dispose()
sinon.restore()
})

// TODO refactor page objects so we can associate clicking user guides with actual urls
// TODO test that clicking quick start changes the tab title, etc
it('should have correct button IDs', async () => {
const features = ['featuredev', 'testgen', 'doc', 'review', 'gumby']

features.forEach((feature, index) => {
const buttons = (tab.getStore().chatItems ?? [])[index].buttons ?? []
assert.deepStrictEqual(buttons[0].id, `user-guide-${feature}`)
assert.deepStrictEqual(buttons[1].id, `quick-start-${feature}`)
})
})
})
32 changes: 28 additions & 4 deletions packages/amazonq/test/e2e/amazonq/framework/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { injectJSDOM } from './jsdomInjector'
// This needs to be ran before all other imports so that mynah ui gets loaded inside of jsdom
injectJSDOM()

import assert from 'assert'
import * as vscode from 'vscode'
import { MynahUI, MynahUIProps } from '@aws/mynah-ui'
import { DefaultAmazonQAppInitContext, TabType, createMynahUI } from 'aws-core-vscode/amazonq'
Expand All @@ -24,7 +25,12 @@ export class qTestingFramework {

lastEventId: string = ''

constructor(featureName: TabType, amazonQEnabled: boolean, featureConfigsSerialized: [string, FeatureContext][]) {
constructor(
featureName: TabType,
amazonQEnabled: boolean,
featureConfigsSerialized: [string, FeatureContext][],
welcomeCount = 0
) {
/**
* Instantiate the UI and override the postMessage to publish using the app message
* publishers directly.
Expand All @@ -44,7 +50,8 @@ export class qTestingFramework {
},
},
amazonQEnabled,
featureConfigsSerialized
featureConfigsSerialized,
welcomeCount
)
this.mynahUI = ui.mynahUI
this.mynahUIProps = (this.mynahUI as any).props
Expand Down Expand Up @@ -79,18 +86,35 @@ export class qTestingFramework {
* functionality against a specific tab
*/
public createTab(options?: MessengerOptions) {
const newTabID = this.mynahUI.updateStore('', {})
const oldTabs = Object.keys(this.mynahUI.getAllTabs())

// simulate pressing the new tab button
;(document.querySelectorAll('.mynah-nav-tabs-wrapper > button.mynah-button')[0] as HTMLButtonElement).click()
const newTabs = Object.keys(this.mynahUI.getAllTabs())

const newTabID = newTabs.find((tab) => !oldTabs.includes(tab))
if (!newTabID) {
throw new Error('Could not create tab id')
assert.fail('Could not find new tab')
}

return new Messenger(newTabID, this.mynahUIProps, this.mynahUI, options)
}

public getTabs() {
const tabs = this.mynahUI.getAllTabs()
return Object.entries(tabs).map(([tabId]) => new Messenger(tabId, this.mynahUIProps, this.mynahUI))
}

public findTab(title: string) {
return Object.values(this.getTabs()).find((tab) => tab.getStore().tabTitle === title)
}

public removeTab(tabId: string) {
this.mynahUI.removeTab(tabId, this.lastEventId)
}

public dispose() {
vscode.Disposable.from(...this.disposables).dispose()
this.mynahUI.destroy()
}
}
26 changes: 21 additions & 5 deletions packages/amazonq/test/e2e/amazonq/framework/messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,28 @@ export class Messenger {
}

const lastChatItem = this.getChatItems().pop()
const option = lastChatItem?.followUp?.options?.filter((option) => option.type === type)
if (!option?.length || option.length > 1) {
assert.fail('Could not find follow up option')
const followupOption = lastChatItem?.followUp?.options?.filter((option) => option.type === type)
if (followupOption && followupOption.length > 0) {
this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', followupOption[0])
return
}

this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', option[0])
assert.fail(`Could not find a button with id ${type} on tabID: ${this.tabID}`)
}

clickInBodyButton(type: string) {
if (!this.mynahUIProps.onInBodyButtonClicked) {
assert.fail('onInBodyButtonClicked must be defined to use it in the tests')
}

const lastChatItem = this.getChatItems().pop()
const followupButton = lastChatItem?.buttons?.filter((option) => option.id === type)
if (followupButton && followupButton.length > 0) {
this.mynahUIProps.onInBodyButtonClicked(this.tabID, lastChatItem?.messageId ?? '', followupButton[0])
return
}

assert.fail(`Could not find a button with id ${type} on tabID: ${this.tabID}`)
}

clickCustomFormButton(action: { id: string; text?: string; formItemValues?: Record<string, string> }) {
Expand Down Expand Up @@ -193,7 +209,7 @@ export class Messenger {
}
}

private getStore(): MynahUIDataModel {
getStore(): MynahUIDataModel {
const store = this.mynahUI.getAllTabs()[this.tabID].store
if (!store) {
assert.fail(`${this.tabID} does not have a store`)
Expand Down
101 changes: 101 additions & 0 deletions packages/amazonq/test/e2e/amazonq/welcome.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'assert'
import { qTestingFramework } from './framework/framework'
import sinon from 'sinon'
import { Messenger } from './framework/messenger'
import { MynahUIDataModel } from '@aws/mynah-ui'
import { assertQuickActions } from './assert'

describe('Amazon Q Welcome page', function () {
let framework: qTestingFramework
let tab: Messenger
let store: MynahUIDataModel

const availableCommands = ['/dev', '/test', '/review', '/doc', '/transform']

beforeEach(() => {
framework = new qTestingFramework('welcome', true, [], 0)
tab = framework.getTabs()[0] // use the default tab that gets created
store = tab.getStore()
})

afterEach(() => {
framework.removeTab(tab.tabID)
framework.dispose()
sinon.restore()
})

it(`Shows quick actions: ${availableCommands.join(', ')}`, async () => {
assertQuickActions(tab, availableCommands)
})

it('Shows @workspace', async () => {
assert.deepStrictEqual(
store.contextCommands
?.map((x) => x.commands)
.flat()
.map((x) => x.command),
['@workspace']
)
})

describe('shows 3 times', async () => {
it('new tabs', () => {
framework.createTab()
framework.createTab()
framework.createTab()
framework.createTab()

let welcomeCount = 0
framework.getTabs().forEach((tab) => {
if (tab.getStore().tabTitle === 'Welcome to Q') {
welcomeCount++
}
})
// 3 welcome tabs
assert.deepStrictEqual(welcomeCount, 3)

// 2 normal tabs
assert.deepStrictEqual(framework.getTabs().length - welcomeCount, 2)
})

it('new windows', () => {
// check the initial window
assert.deepStrictEqual(store.tabTitle, 'Welcome to Q')
framework.dispose()

// check when theres already been two welcome tabs shown
framework = new qTestingFramework('welcome', true, [], 2)
const secondStore = framework.getTabs()[0].getStore()
assert.deepStrictEqual(secondStore.tabTitle, 'Welcome to Q')
framework.dispose()

// check when theres already been three welcome tabs shown
framework = new qTestingFramework('welcome', true, [], 3)
const thirdStore = framework.getTabs()[0].getStore()
assert.deepStrictEqual(thirdStore.tabTitle, 'Chat')
framework.dispose()
})
})

describe('Welcome actions', () => {
it('explore', () => {
tab.clickInBodyButton('explore')

// explore opens in a new tab
const exploreTabStore = framework.findTab('Explore')?.getStore()
assert.strictEqual(exploreTabStore?.tabTitle, 'Explore')
})

it('quick-start', async () => {
tab.clickInBodyButton('quick-start')

// clicking quick start opens in the current tab and changes the compact mode
assert.deepStrictEqual(tab.getStore().compactMode, false)
})
})
})
3 changes: 2 additions & 1 deletion packages/core/src/amazonq/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ export function createMynahUI(
ideApi: any,
amazonQEnabled: boolean,
featureConfigsSerialized: [string, FeatureContext][],
welcomeCount: number,
disabledCommands?: string[]
) {
if (typeof window !== 'undefined') {
const mynahUI = require('./webview/ui/main')
return mynahUI.createMynahUI(ideApi, amazonQEnabled, featureConfigsSerialized, true, disabledCommands)
return mynahUI.createMynahUI(ideApi, amazonQEnabled, featureConfigsSerialized, welcomeCount, disabledCommands)
}
throw new Error('Not implemented for node')
}
Expand Down
Loading