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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Current automated scenarios (`e2e/specs/system.e2e.mjs`):

- Smoke flow (create board + open/close settings)
- Board lifecycle (create, rename, duplicate)
- Sidebar search filtering (case-insensitive match + clear)
- Board persistence across app restart (`browser.reloadSession`)
- Settings persistence across app restart (`browser.reloadSession`)

Expand Down
54 changes: 54 additions & 0 deletions e2e/helpers/board-ui.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const SELECTORS = {
createBoardInput: '[data-testid="create-board-input"]',
createBoardSubmit: '[data-testid="create-board-submit"]',
boardSearchInput: '[data-testid="board-search-input"]',
boardSearchClear: '[data-testid="board-search-clear"]',
toggleSearchButton: '[data-testid="toggle-search-btn"]',
settingsOpenButton: '[data-testid="open-settings-btn"]',
settingsModal: '[data-testid="settings-modal"]',
settingsCloseButton: '[data-testid="close-settings-btn"]',
Expand All @@ -11,6 +14,7 @@ const SELECTORS = {
commandPaletteInput: '[data-testid="command-palette-input"]',
commandPaletteCreateBoardItem: '[data-testid="command-palette-item-create-board"]',
commandPaletteOpenBoardItem: '[data-testid="command-palette-item-open-board"]',
commandPaletteSearchBoardsItem: '[data-testid="command-palette-item-search-boards"]',
commandPaletteOpenSettingsItem: '[data-testid="command-palette-item-open-settings"]',
};

Expand Down Expand Up @@ -68,6 +72,37 @@ export async function waitForBoardVisible(name) {
await boardName.waitForDisplayed({ timeout: 10000 });
}

export async function openBoardSearch() {
const searchInput = await $(SELECTORS.boardSearchInput);
if (await searchInput.isDisplayed().catch(() => false)) {
return;
}

const toggleButton = await $(SELECTORS.toggleSearchButton);
await toggleButton.waitForClickable({ timeout: 10000 });
await toggleButton.click();
await searchInput.waitForDisplayed({ timeout: 10000 });
}

export async function filterBoards(query) {
await openBoardSearch();

const searchInput = await $(SELECTORS.boardSearchInput);
await searchInput.waitForDisplayed({ timeout: 10000 });
await searchInput.setValue(query);
}

export async function clearBoardFilter() {
const clearButton = await $(SELECTORS.boardSearchClear);
await clearButton.waitForClickable({ timeout: 10000 });
await clearButton.click();
}

export async function assertBoardHidden(name) {
const boardName = await $(boardNameXpath(name));
await boardName.waitForExist({ reverse: true, timeout: 10000 });
}

export async function selectBoard(name) {
const boardItem = await $(boardItemXpath(name));
await boardItem.waitForDisplayed({ timeout: 10000 });
Expand Down Expand Up @@ -225,6 +260,25 @@ export async function openSettingsFromCommandPalette() {
await settingsModal.waitForDisplayed({ timeout: 10000 });
}

export async function openSearchFromCommandPalette() {
await openCommandPalette();

const paletteInput = await $(SELECTORS.commandPaletteInput);
await paletteInput.waitForDisplayed({ timeout: 10000 });
await paletteInput.setValue('search boards');

const searchBoardsItem = await $(SELECTORS.commandPaletteSearchBoardsItem);
await searchBoardsItem.waitForDisplayed({ timeout: 10000 });
await browser.keys('Enter');

const searchInput = await $(SELECTORS.boardSearchInput);
await searchInput.waitForDisplayed({ timeout: 10000 });
await browser.waitUntil(async () => searchInput.isFocused(), {
timeout: 10000,
timeoutMsg: 'Expected the sidebar board search to receive focus.',
});
}

export async function openBoardFromCommandPalette(name) {
await openCommandPalette();

Expand Down
22 changes: 22 additions & 0 deletions e2e/specs/system.e2e.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {
assertActiveBoard,
assertEditorInstancePreserved,
assertBoardHidden,
assertSidebarCollapsed,
assertExportRowHidden,
closeSettings,
closeSettingsWithEscape,
clearBoardFilter,
createBoard,
createBoardFromCommandPalette,
duplicateBoard,
filterBoards,
openBoardFromCommandPalette,
openSearchFromCommandPalette,
openSettings,
openSettingsFromCommandPalette,
rememberEditorInstance,
Expand Down Expand Up @@ -58,6 +62,21 @@ describe('System suite', () => {
await assertEditorInstancePreserved();
});

it('sidebar search: filters board names case-insensitively and clears the filter', async () => {
const matchingBoard = uniqueBoardName('Search Match');
const otherBoard = uniqueBoardName('Search Other');

await createBoard(matchingBoard);
await createBoard(otherBoard);
await filterBoards(matchingBoard.toUpperCase());

await waitForBoardVisible(matchingBoard);
await assertBoardHidden(otherBoard);

await clearBoardFilter();
await waitForBoardVisible(otherBoard);
});

it('persistence: keeps created boards after app restart', async () => {
const boardName = uniqueBoardName('Persistence Board');
const editedName = `${boardName} Edited`;
Expand Down Expand Up @@ -106,5 +125,8 @@ describe('System suite', () => {
await openBoardFromCommandPalette(secondBoardName);
await openSettingsFromCommandPalette();
await closeSettings();
await setSidebarCollapsed(true);
await openSearchFromCommandPalette();
await setSidebarCollapsed(false);
});
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "excastoneboard",
"private": true,
"version": "0.3.1",
"packageManager": "pnpm@11.15.0+sha512.266f8957a30d2be6e9468e5e66bcdedd35a794175f71b067ba8504d686cce1d0c0f429b33c323c3c569ad4891e667574a49ff71d1b89a22cc66f13c65818c578",
"packageManager": "pnpm@12.4.2+sha512.08adc6613180275c7c9edada39dcf08c9c61ad4e7eaf330a4f3461f102b0f907423454d117f98e72d47fef0616070644d7bffc973a6a57f5090a6d7c368b07c9",
"description": "Multi-board manager for Excalidraw - manage multiple drawing boards without losing access to old ones",
"type": "module",
"scripts": {
Expand All @@ -14,6 +14,7 @@
"preview": "vite preview",
"test:system": "pnpm --dir e2e install && pnpm --dir e2e test",
"tauri": "tauri",
"tauri:dev": "tauri dev",
"tauri:windows:build": "tauri-windows-bundle build"
},
"dependencies": {
Expand Down
158 changes: 158 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ interface CommandPaletteCommandsConfig {
renameBoard: AppController['renameBoard'];
deleteBoard: AppController['deleteBoard'];
duplicateBoard: AppController['duplicateBoard'];
requestOpenBoardSearch: () => void;
requestOpenSettings: () => void;
requestOpenImportBoards: () => void;
sidebarCollapsed: AppController['sidebarCollapsed'];
Expand Down Expand Up @@ -254,6 +255,14 @@ const createBoardCommandGroupCommands = (
}),
];

const createSearchBoardsCommand = (requestOpenBoardSearch: () => void): CommandPaletteItem => ({
id: 'search-boards',
label: 'Search boards',
description: 'Open and focus the sidebar board filter',
keywords: 'search boards filter find sidebar',
action: requestOpenBoardSearch,
});

const createCommandPaletteCommands = ({
activeBoardId,
boardDataLoading,
Expand All @@ -264,6 +273,7 @@ const createCommandPaletteCommands = ({
renameBoard,
deleteBoard,
duplicateBoard,
requestOpenBoardSearch,
requestOpenSettings,
requestOpenImportBoards,
sidebarCollapsed,
Expand Down Expand Up @@ -305,6 +315,7 @@ const createCommandPaletteCommands = ({
action: createCreateBoardAction(createBoard),
},
...createBoardCommandGroupCommands(hasBoards, boardSearchEmptyState, boardCommands),
createSearchBoardsCommand(requestOpenBoardSearch),
{
id: 'export-boards',
label: 'Export boards',
Expand Down Expand Up @@ -432,6 +443,13 @@ const useCommandPaletteController = ({
window.dispatchEvent(new CustomEvent('boardlist:open-settings'));
}, []);

const requestOpenBoardSearch = useCallback(() => {
if (sidebarCollapsed) {
toggleSidebar();
}
window.dispatchEvent(new CustomEvent('boardlist:open-search'));
}, [sidebarCollapsed, toggleSidebar]);

const requestOpenImportBoards = useCallback(() => {
window.dispatchEvent(new CustomEvent('boardlist:import-boards'));
}, []);
Expand All @@ -448,6 +466,7 @@ const useCommandPaletteController = ({
renameBoard,
deleteBoard,
duplicateBoard,
requestOpenBoardSearch,
requestOpenSettings,
requestOpenImportBoards,
sidebarCollapsed,
Expand Down Expand Up @@ -477,6 +496,7 @@ const useCommandPaletteController = ({
handleExportSvg,
handleSelectBoard,
renameBoard,
requestOpenBoardSearch,
requestOpenImportBoards,
requestOpenSettings,
sidebarCollapsed,
Expand Down
Loading
Loading