diff --git a/src/renderer/components/modals/AboutDialog.tsx b/src/renderer/components/modals/AboutDialog.tsx new file mode 100644 index 0000000..bd7c9ed --- /dev/null +++ b/src/renderer/components/modals/AboutDialog.tsx @@ -0,0 +1,49 @@ +import { useEffect, useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { ipc } from '@/lib/ipc'; +import { useAppStore } from '@/stores/app-store'; + +export function AboutDialog() { + const closeModal = useAppStore((s) => s.closeModal); + const isOpen = useAppStore((s) => s.modal.kind) === 'about'; + const [version, setVersion] = useState('…'); + + useEffect(() => { + ipc.app.getVersion().then((r) => { + if (r.ok && typeof r.data === 'string') setVersion(r.data); + }); + }, []); + + return ( + !o && closeModal()}> + + + About MarkdownConverter + + Professional Markdown editor and universal file converter. + + +
+

Version: {version}

+

+ { + e.preventDefault(); + ipc.app.openExternal('https://github.com/amitwh/markdown-converter'); + }} + > + GitHub repository + +

+

© ConcreteInfo. Licensed under MIT.

+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/tests/component/modals/AboutDialog.test.tsx b/tests/component/modals/AboutDialog.test.tsx new file mode 100644 index 0000000..8c0b752 --- /dev/null +++ b/tests/component/modals/AboutDialog.test.tsx @@ -0,0 +1,34 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { AboutDialog } from '@/components/modals/AboutDialog'; +import { useAppStore } from '@/stores/app-store'; + +describe('AboutDialog', () => { + beforeEach(() => { + window.electronAPI = { + app: { + getVersion: vi.fn().mockResolvedValue('5.0.0'), + openExternal: vi.fn().mockResolvedValue({ ok: true }), + }, + } as any; + // Reset store to about modal so dialog renders + useAppStore.setState({ modal: { kind: 'about' } } as any); + }); + + it('renders title and version', async () => { + render(); + expect(screen.getByText(/about markdownconverter/i)).toBeInTheDocument(); + expect(await screen.findByText(/5\.0\.0/)).toBeInTheDocument(); + }); + + it('closes when the close button is clicked', async () => { + render(); + // dialog-footer has the visible Close button; the X button also has sr-only "Close" + const closeButtons = await screen.findAllByRole('button', { name: /close/i }); + const close = closeButtons[0]; + await userEvent.click(close); + // closeModal sets modal.kind = null, so isOpen=false, Dialog unmounts + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); +}); \ No newline at end of file