diff --git a/docs/CONFIG.md b/docs/CONFIG.md
index dea7d04a..455bd24b 100644
--- a/docs/CONFIG.md
+++ b/docs/CONFIG.md
@@ -38,6 +38,7 @@ interface ConfigModel {
openNewTab: string;
commandConfirmation: string;
pinContextHint: string;
+ contextSearchPlaceholder: string;
dragOverlayText: string;
};
// Options to show up on the overlay feedback form
@@ -217,6 +218,11 @@ Default tab title text if it is not set through store data for that tab.
+
+## contextSearchPlaceholder
+
+
+
---
## dragOverlayText
diff --git a/docs/img/texts/contextSearchPlaceholder.png b/docs/img/texts/contextSearchPlaceholder.png
new file mode 100644
index 00000000..ae0dd02d
Binary files /dev/null and b/docs/img/texts/contextSearchPlaceholder.png differ
diff --git a/src/components/chat-item/chat-item-form-items.ts b/src/components/chat-item/chat-item-form-items.ts
index d044735b..7d10a344 100644
--- a/src/components/chat-item/chat-item-form-items.ts
+++ b/src/components/chat-item/chat-item-form-items.ts
@@ -375,4 +375,16 @@ export class ChatItemFormItemsWrapper {
});
return valueMap;
};
+
+ clearAndFocusFirstTextInput = (): void => {
+ const firstOptionId = Object.keys(this.options)[0];
+ if (firstOptionId != null) {
+ const firstOption = this.options[firstOptionId];
+ // Clear first text input
+ if ('clear' in firstOption && typeof firstOption.clear === 'function') {
+ firstOption.clear();
+ firstOption.focus();
+ }
+ }
+ };
}
diff --git a/src/components/chat-item/chat-prompt-input.ts b/src/components/chat-item/chat-prompt-input.ts
index b90692ea..233479f2 100644
--- a/src/components/chat-item/chat-prompt-input.ts
+++ b/src/components/chat-item/chat-prompt-input.ts
@@ -671,7 +671,7 @@ export class ChatPromptInput {
type: 'textinput',
icon: MynahIcons.SEARCH,
id: 'search',
- placeholder: 'Search context',
+ placeholder: Config.getInstance().config.texts.contextSearchPlaceholder,
autoFocus: true,
},
],
@@ -717,7 +717,7 @@ export class ChatPromptInput {
} else {
this.quickPickItemsSelectorContainer.update({
list: detailedListItemsGroup
- });
+ }, false, true);
}
const headerInfo: QuickActionCommandsHeader = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('quickActionCommandsHeader');
diff --git a/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts b/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts
index 0eebff4e..b8ffec84 100644
--- a/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts
+++ b/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts
@@ -123,6 +123,7 @@ export class PromptTopBar {
}
if (this.titleButton == null) {
this.titleButton = new Button({
+ testId: testIds.prompt.topBarTitle,
onClick: () => {
this.props.onTopBarTitleClick?.();
},
diff --git a/src/components/detailed-list/detailed-list.ts b/src/components/detailed-list/detailed-list.ts
index 726b4358..927af5c9 100644
--- a/src/components/detailed-list/detailed-list.ts
+++ b/src/components/detailed-list/detailed-list.ts
@@ -304,7 +304,11 @@ export class DetailedListWrapper {
return this.allSelectableDetailedListElements[this.activeTargetElementIndex].getItem();
};
- public readonly update = (detailedList: DetailedList, preserveScrollPosition?: boolean): void => {
+ public readonly update = (detailedList: DetailedList, preserveScrollPosition?: boolean, clearFilter?: boolean): void => {
+ if (this.filterForm != null && clearFilter === true) {
+ this.filterForm.clearAndFocusFirstTextInput();
+ }
+
if (detailedList.header != null) {
this.props.detailedList.header = detailedList.header;
this.headerContainer.update({
diff --git a/src/components/form-items/text-input.ts b/src/components/form-items/text-input.ts
index f08f12c0..09a6aea1 100644
--- a/src/components/form-items/text-input.ts
+++ b/src/components/form-items/text-input.ts
@@ -36,6 +36,8 @@ export abstract class TextInputAbstract {
render: ExtendedHTMLElement;
setValue = (value: string): void => {};
getValue = (): string => '';
+ clear = (): void => {};
+ focus = (): void => {};
setEnabled = (enabled: boolean): void => {};
checkValidation = (): void => {};
}
@@ -125,11 +127,7 @@ export class TextInputInternal extends TextInputAbstract {
]
});
- if (this.props.autoFocus === true) {
- setTimeout(() => {
- this.inputElement.focus();
- }, 250);
- }
+ this.focus();
}
setValue = (value: string): void => {
@@ -140,6 +138,18 @@ export class TextInputInternal extends TextInputAbstract {
return this.inputElement.value;
};
+ focus = (): void => {
+ if (this.props.autoFocus === true) {
+ setTimeout(() => {
+ this.inputElement.focus();
+ }, 250);
+ }
+ };
+
+ clear = (): void => {
+ this.setValue('');
+ };
+
setEnabled = (enabled: boolean): void => {
if (enabled) {
this.inputElement.removeAttribute('disabled');
@@ -161,6 +171,8 @@ export class TextInput extends TextInputAbstract {
setValue = (value: string): void => {};
getValue = (): string => '';
+ clear = (): void => {};
+ focus = (): void => {};
setEnabled = (enabled: boolean): void => {};
checkValidation = (): void => {};
}
diff --git a/src/helper/config.ts b/src/helper/config.ts
index 17bc7449..10e39dd8 100644
--- a/src/helper/config.ts
+++ b/src/helper/config.ts
@@ -82,6 +82,7 @@ const configDefaults: ConfigFullModel = {
openNewTab: 'New tab',
commandConfirmation: 'Press enter to continue',
pinContextHint: 'Pin context with \u2325 Enter',
+ contextSearchPlaceholder: 'Search context',
dragOverlayText: 'Add image to context',
},
};
diff --git a/src/helper/test-ids.ts b/src/helper/test-ids.ts
index 78d0a536..7dd1d868 100644
--- a/src/helper/test-ids.ts
+++ b/src/helper/test-ids.ts
@@ -22,6 +22,7 @@ export default {
progress: 'prompt-input-progress-wrapper',
label: 'prompt-input-label',
topBar: 'prompt-input-top-bar',
+ topBarTitle: 'prompt-input-top-bar-title',
topBarButton: 'prompt-input-top-bar-button',
topBarContextPill: 'prompt-input-top-bar-context-pill',
topBarContextTooltip: 'prompt-input-top-bar-context-tooltip',
diff --git a/src/static.ts b/src/static.ts
index 22bcb669..ca2ce9b0 100644
--- a/src/static.ts
+++ b/src/static.ts
@@ -761,6 +761,7 @@ export interface ConfigTexts {
openNewTab: string;
commandConfirmation: string;
pinContextHint: string;
+ contextSearchPlaceholder: string;
dragOverlayText: string;
}
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png
new file mode 100644
index 00000000..ffbb66fa
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png differ
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png
new file mode 100644
index 00000000..a2b1ed28
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png differ
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png
new file mode 100644
index 00000000..b337751b
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png differ
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png
new file mode 100644
index 00000000..745e6d4b
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png differ
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png
new file mode 100644
index 00000000..cea1bd71
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png differ
diff --git a/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png
new file mode 100644
index 00000000..7fe6fd33
Binary files /dev/null and b/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png
new file mode 100644
index 00000000..86395dc0
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-children-shown.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png
new file mode 100644
index 00000000..db84e0bd
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-filtered.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png
new file mode 100644
index 00000000..02be72bc
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-navigation.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png
new file mode 100644
index 00000000..fa274e7d
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-keyboard.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png
new file mode 100644
index 00000000..ecd0e1f9
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-nested-children-shown.png differ
diff --git a/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png
new file mode 100644
index 00000000..d4e1f48b
Binary files /dev/null and b/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-context-menu-when-clicking-top-bar-title/context-selector-opened.png differ
diff --git a/ui-tests/__test__/flows/prompt-top-bar/index.ts b/ui-tests/__test__/flows/prompt-top-bar/index.ts
index 67c7d387..800ba88c 100644
--- a/ui-tests/__test__/flows/prompt-top-bar/index.ts
+++ b/ui-tests/__test__/flows/prompt-top-bar/index.ts
@@ -1,9 +1,11 @@
import { renderPromptTopBar } from './render-prompt-top-bar';
import { promptTopBarTooltip } from './prompt-top-bar-tooltip';
import { promptTopBarButtonOverlay } from './prompt-top-bar-button-overlay';
+import { promptTopBarTitle } from './prompt-top-bar-title';
export {
renderPromptTopBar,
promptTopBarTooltip,
promptTopBarButtonOverlay,
+ promptTopBarTitle,
};
diff --git a/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-title.ts b/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-title.ts
new file mode 100644
index 00000000..74f6149a
--- /dev/null
+++ b/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-title.ts
@@ -0,0 +1,367 @@
+import { expect, Page } from 'playwright/test';
+import { getSelector, waitForAnimationEnd, justWait } from '../../helpers';
+import testIds from '../../../../src/helper/test-ids';
+
+export const promptTopBarTitle = async (page: Page): Promise => {
+ // Set up the prompt top bar with a title and context commands
+ await page.evaluate(() => {
+ const selectedTabId = window.mynahUI.getSelectedTabId();
+ if (selectedTabId != null) {
+ window.mynahUI.updateStore(selectedTabId, {
+ promptTopBarTitle: '@Pin Context',
+ contextCommands: [
+ {
+ commands: [
+ {
+ command: '@workspace',
+ placeholder: 'Yes, you selected workspace :P',
+ description: 'Reference all code in workspace.',
+ },
+ {
+ command: 'image',
+ icon: 'image',
+ description: 'Add an image to the context',
+ placeholder: 'Select an image file'
+ },
+ {
+ command: 'folder',
+ icon: 'folder',
+ children: [
+ {
+ groupName: 'Folders',
+ commands: [
+ {
+ command: 'src',
+ icon: 'folder',
+ children: [
+ {
+ groupName: 'src/',
+ commands: [
+ {
+ command: 'index.ts',
+ icon: 'file',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ command: 'main',
+ description: './src/',
+ icon: 'folder',
+ },
+ {
+ command: 'components',
+ description: './src/',
+ icon: 'folder',
+ },
+ {
+ command: 'helper',
+ description: './src/',
+ icon: 'folder',
+ },
+ {
+ command: 'src',
+ description: './example/',
+ icon: 'folder',
+ },
+ ],
+ },
+ ],
+ placeholder: 'Mention a specific folder',
+ description: 'Include entire folder as context',
+ },
+ {
+ command: 'file',
+ icon: 'file',
+ children: [
+ {
+ groupName: 'Files',
+ commands: [
+ {
+ command: 'monarch.ts',
+ description: 'spring-boot-template/.github/workflows/p-openapi.yaml',
+ icon: 'file',
+ },
+ {
+ command: 'main.ts',
+ description: './src/',
+ icon: 'file',
+ },
+ {
+ command: 'button.ts',
+ description: './src/components/',
+ icon: 'file',
+ },
+ {
+ command: 'ex-dom.ts',
+ description: './src/helper/',
+ icon: 'file',
+ },
+ {
+ command: 'dom.ts',
+ description: './src/helper/',
+ icon: 'file',
+ },
+ {
+ command: '_dark.scss',
+ description: './src/styles/',
+ icon: 'file',
+ // add route just to check if it returns back
+ route: [ 'src', 'styles' ],
+ },
+ ],
+ },
+ ],
+ placeholder: 'Mention a specific file',
+ description: 'Add a file to context',
+ },
+ {
+ command: 'symbols',
+ icon: 'code-block',
+ children: [
+ {
+ groupName: 'Symbols',
+ commands: [
+ {
+ command: 'DomBuilder',
+ icon: 'code-block',
+ description: 'The DomGeneration function in dom.ts file',
+ },
+ ...Array(10)
+ .fill(null)
+ .map((_, i) => ({
+ command: `item${i}`,
+ description: `./src/${i}`,
+ icon: 'code-block' as any,
+ })),
+ ],
+ },
+ ],
+ placeholder: 'Select a symbol',
+ description: 'After that mention a specific file/folder, or leave blank for full project!',
+ },
+ {
+ command: 'prompts',
+ icon: 'flash',
+ description: 'Saved prompts, to reuse them in your current prompt',
+ children: [
+ {
+ groupName: 'Prompts',
+ actions: [
+ {
+ id: 'add-new-prompt',
+ icon: 'plus',
+ text: 'Add',
+ description: 'Add new prompt',
+ },
+ ],
+ commands: [
+ {
+ command: 'python_expert',
+ icon: 'chat',
+ description: 'Expert on python stuff',
+ },
+ {
+ command: 'javascript_expert',
+ icon: 'chat',
+ description: 'Expert on Javascript and typescript',
+ },
+ {
+ command: 'Add Prompt',
+ icon: 'plus',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ }
+ });
+
+ await waitForAnimationEnd(page);
+
+ // Verify the top bar title is visible
+ const topBarTitle = page.locator(getSelector(testIds.prompt.topBarTitle));
+ expect(await topBarTitle.isVisible()).toBeTruthy();
+
+ // Click on the top bar title to open the context selector
+ await topBarTitle.click();
+ await waitForAnimationEnd(page);
+
+ // Wait for the overlay to appear
+ await justWait(500);
+
+ // Verify the quick picks overlay is visible
+ const quickPicksWrapper = page.locator(getSelector(testIds.prompt.quickPicksWrapper));
+ expect(await quickPicksWrapper.isVisible()).toBeTruthy();
+
+ // Take screenshot of the opened context selector
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-opened.png');
+
+ // Verify that we can see the groups and items
+ const quickPickItems = page.locator(getSelector(testIds.prompt.quickPickItem));
+ expect(await quickPickItems.count()).toBeGreaterThan(0);
+
+ // Verify the search filter input is present and focused
+ const searchInput = page.getByRole('textbox', { name: 'Search context' });
+ await expect(searchInput).toBeFocused();
+ expect(await searchInput.isVisible()).toBeTruthy();
+ expect(await searchInput.getAttribute('placeholder')).toBe('Search context');
+
+ // Type in the search filter to filter items
+ await searchInput.fill('folder');
+ await waitForAnimationEnd(page);
+
+ // Take screenshot after filtering
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-filtered.png');
+
+ // Verify that only folder-related items are shown
+ const filteredItems = await quickPickItems.allInnerTexts();
+ expect(filteredItems.some(text => text.includes('folder'))).toBeTruthy();
+ expect(filteredItems.some(text => text.includes('file'))).toBeFalsy();
+
+ // Click on the folder item (which has children)
+ const folderItem = page.locator(getSelector(testIds.prompt.quickPickItem)).filter({ hasText: 'folder' });
+ await folderItem.click();
+ await page.mouse.move(0, 0); // move cursor away so hover effect is removed
+ await waitForAnimationEnd(page);
+
+ // Verify that the children are now shown (Folders group)
+ const childItems = page.locator(getSelector(testIds.prompt.quickPickItem));
+ const childTexts = await childItems.allInnerTexts();
+ expect(childTexts.some(text => text.includes('src'))).toBeTruthy();
+ expect(childTexts.some(text => text.includes('main'))).toBeTruthy();
+ expect(childTexts.some(text => text.includes('components'))).toBeTruthy();
+
+ // Take screenshot showing the children
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-children-shown.png');
+
+ // Select the 'src' item which has further children
+ const srcItem = page.locator(getSelector(testIds.prompt.quickPickItem)).filter({ hasText: 'src' }).first();
+ await srcItem.click();
+ await waitForAnimationEnd(page);
+
+ // Verify that the nested children are now shown (src/ group with index.ts)
+ const nestedChildItems = page.locator(getSelector(testIds.prompt.quickPickItem));
+ const nestedChildTexts = await nestedChildItems.allInnerTexts();
+ expect(nestedChildTexts.some(text => text.includes('index.ts'))).toBeTruthy();
+
+ // Hover over index.ts item to verify hover effect in screenshot
+ const indexTsItem = page.locator(getSelector(testIds.prompt.quickPickItem)).filter({ hasText: 'index.ts' }).first();
+ await indexTsItem.hover();
+ await waitForAnimationEnd(page);
+
+ // Take screenshot showing the nested children
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-nested-children-shown.png');
+
+ // Select index.ts context item
+ await indexTsItem.click();
+ await waitForAnimationEnd(page);
+
+ // Verify the overlay is closed after selection
+ expect(await page.locator(getSelector(testIds.prompt.quickPicksWrapper)).count()).toBe(0);
+
+ // Verify that the selected context item appears in the top bar
+ const contextPills = page.locator(getSelector(testIds.prompt.topBarContextPill));
+ expect(await contextPills.count()).toBe(1);
+
+ const pillText = await contextPills.first().innerText();
+ expect(pillText).toContain('index.ts');
+
+ // Test keyboard navigation to select nested children
+ await topBarTitle.click();
+ await waitForAnimationEnd(page);
+
+ // Clear search and navigate to folder item using arrow keys
+ const searchInputKeyboard = page.getByRole('textbox', { name: 'Search context' });
+ await searchInputKeyboard.clear();
+ await searchInputKeyboard.fill('folder');
+ await waitForAnimationEnd(page);
+
+ // Use arrow keys to navigate to the folder item and press Enter to select it
+ await page.keyboard.press('ArrowDown'); // Navigate to first item (folder)
+ await waitForAnimationEnd(page);
+ await page.keyboard.press('Enter'); // Select folder item to view its children
+ await waitForAnimationEnd(page);
+
+ // Verify that the children are now shown (Folders group)
+ const keyboardChildItems = page.locator(getSelector(testIds.prompt.quickPickItem));
+ const keyboardChildTexts = await keyboardChildItems.allInnerTexts();
+ expect(keyboardChildTexts.some(text => text.includes('src'))).toBeTruthy();
+ expect(keyboardChildTexts.some(text => text.includes('main'))).toBeTruthy();
+ expect(keyboardChildTexts.some(text => text.includes('components'))).toBeTruthy();
+
+ // Navigate to 'src' item using arrow keys and select it to view nested children
+ await page.keyboard.press('ArrowDown'); // Navigate to src item
+ await waitForAnimationEnd(page);
+ await page.keyboard.press('Enter'); // Select src item to view its nested children
+ await waitForAnimationEnd(page);
+
+ // Verify that the nested children are now shown using keyboard navigation
+ const keyboardNestedChildItems = page.locator(getSelector(testIds.prompt.quickPickItem));
+ const keyboardNestedChildTexts = await keyboardNestedChildItems.allInnerTexts();
+ expect(keyboardNestedChildTexts.some(text => text.includes('index.ts'))).toBeTruthy();
+
+ // Take screenshot showing the nested children selected via keyboard
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-nested-children-keyboard.png');
+
+ // Navigate to index.ts and select it using keyboard
+ await page.keyboard.press('ArrowDown'); // Navigate to index.ts
+ await waitForAnimationEnd(page);
+ await page.keyboard.press('Enter'); // Select index.ts
+ await waitForAnimationEnd(page);
+
+ // Verify the overlay is closed after keyboard selection
+ expect(await page.locator(getSelector(testIds.prompt.quickPicksWrapper)).count()).toBe(0);
+
+ // Verify that another context item was added to the top bar (should now have 2 items total)
+ expect(await contextPills.count()).toBe(2);
+
+ const keyboardPillText = await contextPills.last().innerText();
+ expect(keyboardPillText).toContain('index.ts');
+
+ // Test selecting a different type of item - open context selector again
+ await topBarTitle.click();
+ await waitForAnimationEnd(page);
+
+ // Search for and select a file item
+ const searchInput2 = page.getByRole('textbox', { name: 'Search context' });
+ await expect(searchInput2).toBeFocused();
+ await searchInput2.fill('file');
+ await waitForAnimationEnd(page);
+
+ // Click on the file item
+ const fileItem = page.locator(getSelector(testIds.prompt.quickPickItem)).filter({ hasText: 'file' });
+ await fileItem.click();
+ await waitForAnimationEnd(page);
+
+ // Select one of the file items (main.ts)
+ const mainTsItem = page.locator(getSelector(testIds.prompt.quickPickItem)).filter({ hasText: 'main.ts' });
+ await mainTsItem.click();
+ await waitForAnimationEnd(page);
+
+ // Verify another context item was added (should now have 3 total: first index.ts, keyboard index.ts, and main.ts)
+ expect(await contextPills.count()).toBe(3);
+
+ // Test keyboard navigation by opening the context selector again
+ await topBarTitle.click();
+ await waitForAnimationEnd(page);
+
+ // Use arrow keys to navigate
+ await page.keyboard.press('ArrowDown');
+ await waitForAnimationEnd(page);
+
+ // Take screenshot showing keyboard navigation
+ expect(await quickPicksWrapper.screenshot()).toMatchSnapshot('context-selector-navigation.png');
+
+ // Press Escape to close without selecting
+ await page.keyboard.press('Escape');
+ await waitForAnimationEnd(page);
+
+ // Verify the overlay is closed
+ expect(await page.locator(getSelector(testIds.prompt.quickPicksWrapper)).count()).toBe(0);
+};
diff --git a/ui-tests/__test__/main.spec.ts b/ui-tests/__test__/main.spec.ts
index 6f8e9a1b..3b72599f 100644
--- a/ui-tests/__test__/main.spec.ts
+++ b/ui-tests/__test__/main.spec.ts
@@ -55,6 +55,7 @@ import {
renderPromptTopBar,
promptTopBarTooltip,
promptTopBarButtonOverlay,
+ promptTopBarTitle,
} from './flows/prompt-top-bar';
test.describe('Open MynahUI', () => {
@@ -200,6 +201,11 @@ test.describe('Open MynahUI', () => {
test('should show overlay when clicking top bar button', async ({ page }) => {
await promptTopBarButtonOverlay(page);
});
+
+ test('should show context menu when clicking top bar title', async ({ page }) => {
+ test.setTimeout(40_000);
+ await promptTopBarTitle(page);
+ });
});
test.describe('File tree', () => {