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 (