diff --git a/client/src/pages/PromptManager.jsx b/client/src/pages/PromptManager.jsx index 11b8ffd24c..0116ae7ba0 100644 --- a/client/src/pages/PromptManager.jsx +++ b/client/src/pages/PromptManager.jsx @@ -204,6 +204,7 @@ export default function PromptManager() { .catch((err) => { toast.error('Failed to save stage: ' + err.message); return false; }); if (!ok) { setSaving(false); return; } setSaving(false); + toast.success(`Stage "${stages[selectedStage]?.name || selectedStage}" saved`); await loadData(); }; @@ -283,6 +284,8 @@ export default function PromptManager() { variables: [], template: '' }); + toast.success(`Stage "${payload.name || payload.stageName}" created`); + setSelectedStage(payload.stageName); await loadData(); }; @@ -313,6 +316,7 @@ export default function PromptManager() { if (selectedStage === stageName) { setSelectedStage(null); } + toast.success(`Stage "${stageName}" deleted`); await loadData(); }; diff --git a/client/src/pages/PromptManager.test.jsx b/client/src/pages/PromptManager.test.jsx index 0cddea25d7..ec2c9ad4ac 100644 --- a/client/src/pages/PromptManager.test.jsx +++ b/client/src/pages/PromptManager.test.jsx @@ -11,6 +11,8 @@ const getPrompts = vi.fn(); const getPrompt = vi.fn(); const getPromptUsage = vi.fn(); const deletePrompt = vi.fn(); +const savePrompt = vi.fn(); +const createPrompt = vi.fn(); const getPromptVariables = vi.fn(); const deletePromptVariable = vi.fn(); const getJobSkills = vi.fn(() => Promise.resolve({ skills: [] })); @@ -21,8 +23,8 @@ const previewJobSkill = vi.fn(); vi.mock('../services/apiPrompts', () => ({ getPrompts: (...a) => getPrompts(...a), getPrompt: (...a) => getPrompt(...a), - createPrompt: vi.fn(), - savePrompt: vi.fn(), + createPrompt: (...a) => createPrompt(...a), + savePrompt: (...a) => savePrompt(...a), deletePrompt: (...a) => deletePrompt(...a), previewPrompt: vi.fn(), getPromptUsage: (...a) => getPromptUsage(...a), @@ -381,6 +383,56 @@ describe('PromptManager delete demotion', () => { // #3935: the row trash icon used to fire DELETE on the first click, so a // mis-click on a 14px target destroyed the variable with no undo. +describe('PromptManager stage save/create/delete feedback (#6022)', () => { + beforeEach(() => { + getPrompts.mockReset().mockResolvedValue({ stages: STAGES, systemStages: SYSTEM_STAGES }); + getPrompt.mockReset().mockResolvedValue({ name: 'Pipeline — Comic Book Script', template: 'body', variables: [] }); + getPromptUsage.mockReset().mockResolvedValue({ + isSystemStage: false, usedBy: [], referencedBy: [], canDelete: true, + }); + savePrompt.mockReset().mockResolvedValue({ success: true }); + createPrompt.mockReset().mockResolvedValue({ success: true, stageName: 'my-new-stage' }); + deletePrompt.mockReset().mockResolvedValue({ success: true }); + toast.error.mockReset(); + toast.success.mockReset(); + }); + + it('confirms a successful stage save with a named toast', async () => { + renderPage('/prompts?stage=pipeline-comic-script'); + await screen.findByText('Prompt Stages'); + await screen.findByRole('button', { name: /^save$/i }); + + fireEvent.click(screen.getByRole('button', { name: /^save$/i })); + + await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Stage "Pipeline — Comic Book Script" saved')); + expect(toast.error).not.toHaveBeenCalled(); + }); + + it('confirms stage creation with a named toast and deep-links straight to the new stage', async () => { + renderPage('/prompts'); + await screen.findByText('Prompt Stages'); + + fireEvent.click(screen.getByRole('button', { name: 'New Stage' })); + fireEvent.change(screen.getByPlaceholderText('my-stage'), { target: { value: 'my-new-stage' } }); + fireEvent.change(screen.getByPlaceholderText('Pipeline — My Stage'), { target: { value: 'My New Stage' } }); + fireEvent.click(screen.getByRole('button', { name: /create stage/i })); + + await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Stage "My New Stage" created')); + await waitFor(() => expect(currentSearch()).toContain('stage=my-new-stage')); + }); + + it('confirms a successful stage delete with a named toast', async () => { + renderPage('/prompts?stage=pipeline-comic-script'); + await screen.findByText('Prompt Stages'); + + fireEvent.click(await screen.findByRole('button', { name: /^delete$/i })); + expect(await screen.findByText('Delete Stage?')).toBeTruthy(); + fireEvent.click(screen.getAllByRole('button', { name: /^delete$/i }).at(-1)); + + await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Stage "pipeline-comic-script" deleted')); + }); +}); + describe('PromptManager variable deletion', () => { const VARIABLES = { 'tone-guide': { name: 'Tone Guide', category: 'style', content: 'stay wry' },