diff --git a/src/renderer/components/modals/ExportDocxDialog.tsx b/src/renderer/components/modals/ExportDocxDialog.tsx new file mode 100644 index 0000000..9ba069a --- /dev/null +++ b/src/renderer/components/modals/ExportDocxDialog.tsx @@ -0,0 +1,75 @@ +import { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { useAppStore } from '@/stores/app-store'; +import { useSettingsStore } from '@/stores/settings-store'; +import { useExportSource } from '@/hooks/use-export-source'; +import { ipc } from '@/lib/ipc'; +import { ExportDialogFooter } from './ExportDialogFooter'; + +export function ExportDocxDialog({ sourcePath }: { sourcePath: string }) { + const closeModal = useAppStore((s) => s.closeModal); + const { docxTemplate, renderTablesAsAscii } = useSettingsStore(); + const source = useExportSource(); + const [template, setTemplate] = useState<'standard' | 'minimal' | 'modern'>(docxTemplate); + const [ascii, setAscii] = useState(renderTablesAsAscii); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async () => { + if (!source) { setError('No file open.'); return; } + setSubmitting(true); + setError(null); + const result = await ipc.export.docx({ + inputPath: source.path, + outputPath: source.path.replace(/\.md$/, '.docx'), + template, + renderTablesAsAscii: ascii, + } as any); + if (!result.ok) { + setError(result.error.message); + setSubmitting(false); + } else { + closeModal(); + } + }; + + return ( + !o && closeModal()}> + + + Export to DOCX + {sourcePath} + +
+
+ + +

+ Bundled with the app. Standard is the default; Modern adds a colored cover page. +

+
+ + {error && ( +
+ {error} +
+ )} +
+ +
+
+ ); +} \ No newline at end of file diff --git a/tests/component/modals/ExportDocxDialog.test.tsx b/tests/component/modals/ExportDocxDialog.test.tsx new file mode 100644 index 0000000..a376715 --- /dev/null +++ b/tests/component/modals/ExportDocxDialog.test.tsx @@ -0,0 +1,41 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ExportDocxDialog } from '@/components/modals/ExportDocxDialog'; +import { useFileStore } from '@/stores/file-store'; +import { useEditorStore } from '@/stores/editor-store'; +import { useSettingsStore } from '@/stores/settings-store'; + +describe('ExportDocxDialog', () => { + beforeEach(() => { + localStorage.clear(); + window.electronAPI = { + export: { docx: vi.fn().mockResolvedValue({ ok: true, data: { outputPath: '/out.docx' } }) }, + } as any; + useSettingsStore.setState(useSettingsStore.getInitialState()); + useFileStore.setState({ activeTabId: '/test.md', openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }] } as any); + useEditorStore.setState({ buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: false }]]) } as any); + }); + + it('renders with standard template selected by default', () => { + render(); + expect(screen.getByText(/export to docx/i)).toBeInTheDocument(); + expect(screen.getByRole('combobox', { name: /template/i })).toBeInTheDocument(); + }); + + it('submitting with default options sends template=standard', async () => { + render(); + await userEvent.click(screen.getByRole('button', { name: /^export$/i })); + const call = (window.electronAPI.export.docx as any).mock.calls[0][0]; + expect(call.template).toBe('standard'); + }); + + it('selecting "modern" template sends template=modern', async () => { + render(); + await userEvent.click(screen.getByRole('combobox', { name: /template/i })); + await userEvent.click(screen.getByRole('option', { name: /modern/i })); + await userEvent.click(screen.getByRole('button', { name: /^export$/i })); + const call = (window.electronAPI.export.docx as any).mock.calls[0][0]; + expect(call.template).toBe('modern'); + }); +}); \ No newline at end of file