mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): AboutDialog (version + GitHub link)
This commit is contained in:
@@ -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<string>('…');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ipc.app.getVersion().then((r) => {
|
||||||
|
if (r.ok && typeof r.data === 'string') setVersion(r.data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={(o) => !o && closeModal()}>
|
||||||
|
<DialogContent aria-describedby="about-desc">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>About MarkdownConverter</DialogTitle>
|
||||||
|
<DialogDescription id="about-desc">
|
||||||
|
Professional Markdown editor and universal file converter.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-2 text-sm text-muted-foreground">
|
||||||
|
<p>Version: {version}</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/amitwh/markdown-converter"
|
||||||
|
className="text-brand hover:underline"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
ipc.app.openExternal('https://github.com/amitwh/markdown-converter');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
GitHub repository
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className="text-xs">© ConcreteInfo. Licensed under MIT.</p>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={closeModal}>Close</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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(<AboutDialog />);
|
||||||
|
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(<AboutDialog />);
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user