From e770bb0838410560b76879b87974086a6a0f0065 Mon Sep 17 00:00:00 2001 From: YUHAO-corn Date: Sun, 17 May 2026 01:06:56 +0800 Subject: [PATCH 1/2] fix(ui): auto-add newly created boards --- .../Boards/BoardsList/AddBoardButton.test.tsx | 90 +++++++++++++++++++ .../Boards/BoardsList/AddBoardButton.tsx | 18 ++-- 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.test.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.test.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.test.tsx new file mode 100644 index 00000000000..51b42289871 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.test.tsx @@ -0,0 +1,90 @@ +import { autoAddBoardIdChanged, boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + autoAssignBoardOnClick: true, + createBoard: vi.fn(), + dispatch: vi.fn(), +})); + +vi.mock('react', async () => { + const actual = await vi.importActual('react'); + return { + ...actual, + memo: (component: unknown) => component, + useCallback: (callback: unknown) => callback, + }; +}); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => (key === 'boards.myBoard' ? 'My Board' : key), + }), +})); + +vi.mock('app/store/storeHooks', () => ({ + useAppDispatch: () => mocks.dispatch, + useAppSelector: () => mocks.autoAssignBoardOnClick, +})); + +vi.mock('services/api/endpoints/boards', () => ({ + useCreateBoardMutation: () => [mocks.createBoard, { isLoading: false }], +})); + +import AddBoardButton, { getCreatedBoardActions } from './AddBoardButton'; + +describe('getCreatedBoardActions', () => { + it('selects the created board and auto-adds it when auto assign is enabled', () => { + expect(getCreatedBoardActions('board-1', true)).toEqual([ + boardIdSelected({ boardId: 'board-1' }), + autoAddBoardIdChanged('board-1'), + boardSearchTextChanged(''), + ]); + }); + + it('does not change the auto-add board when auto assign is disabled', () => { + expect(getCreatedBoardActions('board-1', false)).toEqual([ + boardIdSelected({ boardId: 'board-1' }), + boardSearchTextChanged(''), + ]); + }); +}); + +describe('AddBoardButton', () => { + beforeEach(() => { + mocks.autoAssignBoardOnClick = true; + mocks.createBoard.mockReset(); + mocks.dispatch.mockReset(); + }); + + it('auto-adds the created board when auto assign is enabled', async () => { + mocks.createBoard.mockReturnValue({ + unwrap: () => Promise.resolve({ board_id: 'board-1' }), + }); + + const button = AddBoardButton({}) as { props: { onClick: () => Promise } }; + await button.props.onClick(); + + expect(mocks.createBoard).toHaveBeenCalledWith({ board_name: 'My Board' }); + expect(mocks.dispatch.mock.calls).toEqual([ + [boardIdSelected({ boardId: 'board-1' })], + [autoAddBoardIdChanged('board-1')], + [boardSearchTextChanged('')], + ]); + }); + + it('leaves the auto-add board unchanged when auto assign is disabled', async () => { + mocks.autoAssignBoardOnClick = false; + mocks.createBoard.mockReturnValue({ + unwrap: () => Promise.resolve({ board_id: 'board-1' }), + }); + + const button = AddBoardButton({}) as { props: { onClick: () => Promise } }; + await button.props.onClick(); + + expect(mocks.dispatch.mock.calls).toEqual([ + [boardIdSelected({ boardId: 'board-1' })], + [boardSearchTextChanged('')], + ]); + }); +}); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index 9f59e60fee8..fb832a1447f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -1,25 +1,33 @@ import { IconButton } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors'; +import { autoAddBoardIdChanged, boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; +import type { BoardId } from 'features/gallery/store/types'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; +export const getCreatedBoardActions = (boardId: BoardId, autoAssignBoardOnClick: boolean) => [ + boardIdSelected({ boardId }), + ...(autoAssignBoardOnClick ? [autoAddBoardIdChanged(boardId)] : []), + boardSearchTextChanged(''), +]; + const AddBoardButton = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick); const [createBoard, { isLoading }] = useCreateBoardMutation(); const handleCreateBoard = useCallback(async () => { try { const board = await createBoard({ board_name: t('boards.myBoard') }).unwrap(); - dispatch(boardIdSelected({ boardId: board.board_id })); - dispatch(boardSearchTextChanged('')); + getCreatedBoardActions(board.board_id, autoAssignBoardOnClick).forEach((action) => dispatch(action)); } catch { //no-op } - }, [t, createBoard, dispatch]); + }, [t, createBoard, dispatch, autoAssignBoardOnClick]); return ( Date: Tue, 19 May 2026 13:28:19 +0800 Subject: [PATCH 2/2] fix(ui): clarify auto-assign board label --- invokeai/frontend/web/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index a79eab2f70e..e232f611a3f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -518,7 +518,7 @@ "assets": "Assets", "alwaysShowImageSizeBadge": "Always Show Image Size Badge", "assetsTab": "Files you've uploaded for use in your projects.", - "autoAssignBoardOnClick": "Auto-Assign Board on Click", + "autoAssignBoardOnClick": "Auto-Assign Board", "autoSwitchNewImages": "Auto-Switch to New Images", "boardsSettings": "Boards Settings", "copy": "Copy",