| 1 | /** |
| 2 | * @vitest-environment happy-dom |
| 3 | */ |
| 4 | import React, { act } from 'react' |
| 5 | import { afterEach, describe, expect, it, vi } from 'vitest' |
| 6 | import { createRoot, type Root } from 'react-dom/client' |
| 7 | import { TooltipProvider } from '../../../src/renderer/src/components/ui/Tooltip' |
| 8 | import { SessionToolbar } from '../../../src/renderer/src/components/session-detail/toolbar/SessionToolbar' |
| 9 | import { useSessionDetailRuntimeStore } from '../../../src/renderer/src/store' |
| 10 | |
| 11 | vi.mock('../../../src/renderer/src/i18n', () => ({ |
| 12 | useT: () => (key: string) => key |
| 13 | })) |
| 14 | |
| 15 | vi.mock('../../../src/renderer/src/components/session-detail/toolbar/useSessionToolbarController', () => ({ |
| 16 | useSessionToolbarController: () => ({ |
| 17 | hasPages: true, |
| 18 | isGenerating: false, |
| 19 | historyDisabled: false, |
| 20 | selectedPageHasPendingEdits: false, |
| 21 | canPreview: true, |
| 22 | canRevealFile: true, |
| 23 | sessionTitle: 'Quarterly Plan', |
| 24 | saveTemplateOpen: false, |
| 25 | savingTemplate: false, |
| 26 | saveAsNewSessionOpen: false, |
| 27 | savingAsNewSession: false, |
| 28 | saveAsNewSessionDisabled: false, |
| 29 | defaultTemplateName: 'Quarterly Plan', |
| 30 | defaultSaveAsNewSessionName: 'Quarterly Plan copy', |
| 31 | setSaveTemplateOpen: vi.fn(), |
| 32 | setSaveAsNewSessionOpen: vi.fn(), |
| 33 | handleSaveTemplate: vi.fn(), |
| 34 | handleSaveAsNewSession: vi.fn(), |
| 35 | exportActions: { |
| 36 | canExportPptx: true, |
| 37 | exportPptx: vi.fn(), |
| 38 | exportPng: vi.fn(), |
| 39 | exportVideo: vi.fn(), |
| 40 | exportPdf: vi.fn(), |
| 41 | exportSlidePack: vi.fn(), |
| 42 | exportSessionZip: vi.fn(), |
| 43 | openProjectPreview: vi.fn(), |
| 44 | openPresentation: vi.fn(), |
| 45 | revealSelectedPageFile: vi.fn() |
| 46 | }, |
| 47 | openHistory: vi.fn() |
| 48 | }) |
| 49 | })) |
| 50 | |
| 51 | async function renderToolbar(): Promise<{ container: HTMLDivElement; root: Root }> { |
| 52 | const container = document.createElement('div') |
| 53 | document.body.appendChild(container) |
| 54 | const root = createRoot(container) |
| 55 | |
| 56 | await act(async () => { |
| 57 | root.render( |
| 58 | React.createElement( |
| 59 | TooltipProvider, |
| 60 | { delayDuration: 0 }, |
| 61 | React.createElement( |
| 62 | 'header', |
| 63 | { className: 'app-drag-region app-titlebar' }, |
| 64 | React.createElement( |
| 65 | 'div', |
| 66 | { className: 'flex-1' }, |
| 67 | React.createElement(SessionToolbar, { sessionId: 'session-1' }) |
| 68 | ) |
| 69 | ) |
| 70 | ) |
| 71 | ) |
| 72 | }) |
| 73 | |
| 74 | return { container, root } |
| 75 | } |
| 76 | |
| 77 | async function cleanupRoot(root: Root, container: HTMLDivElement): Promise<void> { |
| 78 | await act(async () => { |
| 79 | root.unmount() |
| 80 | }) |
| 81 | container.remove() |
| 82 | } |
| 83 | |
| 84 | describe('SessionToolbar titlebar drag region', () => { |
| 85 | afterEach(() => { |
| 86 | useSessionDetailRuntimeStore.getState().setWorkspaceRibbonActions(null) |
| 87 | document.body.innerHTML = '' |
| 88 | }) |
| 89 | |
| 90 | it('keeps title and empty toolbar space draggable while controls opt out', async () => { |
| 91 | useSessionDetailRuntimeStore.getState().setWorkspaceRibbonActions({ |
| 92 | onUndo: vi.fn(), |
| 93 | onRedo: vi.fn(), |
| 94 | onSaveCurrentPage: vi.fn(), |
| 95 | onDiscardAllEdits: vi.fn(), |
| 96 | onBackToSessions: vi.fn(), |
| 97 | onAddFromLibrary: vi.fn(), |
| 98 | onAddFromLocal: vi.fn(), |
| 99 | onAddText: vi.fn(), |
| 100 | onAddArtText: vi.fn() |
| 101 | }) |
| 102 | const { container, root } = await renderToolbar() |
| 103 | |
| 104 | try { |
| 105 | const title = container.querySelector('.truncate') |
| 106 | expect(title?.textContent).toBe('Quarterly Plan') |
| 107 | expect(title?.closest('.app-no-drag')).toBeNull() |
| 108 | |
| 109 | const toolbarHost = title?.closest('.flex-1') |
| 110 | expect(toolbarHost).toBeTruthy() |
| 111 | expect(toolbarHost?.classList.contains('app-no-drag')).toBe(false) |
| 112 | |
| 113 | const buttons = container.querySelectorAll('button') |
| 114 | expect(buttons.length).toBeGreaterThan(0) |
| 115 | for (const button of buttons) { |
| 116 | expect(button.classList.contains('app-no-drag')).toBe(true) |
| 117 | } |
| 118 | } finally { |
| 119 | await cleanupRoot(root, container) |
| 120 | } |
| 121 | }) |
| 122 | }) |
| 123 |