diff --git a/src/renderer/components/layout/AppShell.tsx b/src/renderer/components/layout/AppShell.tsx new file mode 100644 index 0000000..bac7ebc --- /dev/null +++ b/src/renderer/components/layout/AppShell.tsx @@ -0,0 +1,53 @@ +import { AppHeader } from './AppHeader'; +import { TabBar } from './TabBar'; +import { Toolbar } from './Toolbar'; +import { Breadcrumb } from './Breadcrumb'; +import { StatusBar } from './StatusBar'; +import { useAppStore } from '@/stores/app-store'; +import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'; + +export function AppShell() { + const { sidebarVisible, previewVisible, paneSizes, setPaneSizes } = useAppStore(); + + return ( +
+ + + + +
+ setPaneSizes({ sidebar: sizes[0], editor: sizes[1], preview: sizes[2] })} + > + {sidebarVisible && ( + <> + + + + + + )} + +
+ Editor placeholder +
+
+ {previewVisible && ( + <> + + +
+ Preview placeholder +
+
+ + )} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/renderer/components/ui/resizable.tsx b/src/renderer/components/ui/resizable.tsx index dd28d33..a312351 100644 --- a/src/renderer/components/ui/resizable.tsx +++ b/src/renderer/components/ui/resizable.tsx @@ -7,10 +7,10 @@ import * as ResizablePrimitive from "react-resizable-panels" import { cn } from "@/lib/utils" const ResizablePanelGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - )) -ResizablePanelGroup.displayName = ResizablePrimitive.PanelGroup.displayName +ResizablePanelGroup.displayName = "ResizablePanelGroup" const ResizablePanel = React.forwardRef< React.ElementRef, @@ -34,16 +34,16 @@ const ResizablePanel = React.forwardRef< {...props} /> )) -ResizablePanel.displayName = ResizablePrimitive.Panel.displayName +ResizablePanel.displayName = "ResizablePanel" const ResizableHandle = ({ withHandle, className, ...props -}: React.ComponentPropsWithoutRef & { +}: React.ComponentPropsWithoutRef & { withHandle?: boolean }) => ( - div]:rotate-90", className @@ -55,7 +55,7 @@ const ResizableHandle = ({ )} - + ) ResizableHandle.displayName = "ResizableHandle" diff --git a/src/renderer/test/setup.ts b/src/renderer/test/setup.ts index 461543e..8be2518 100644 --- a/src/renderer/test/setup.ts +++ b/src/renderer/test/setup.ts @@ -27,3 +27,9 @@ if (typeof window !== 'undefined') { }), }); } + +// Mock react-resizable-panels for jsdom environment +if (typeof window !== 'undefined') { + Object.defineProperty(window, 'innerWidth', { writable: true, value: 1920 }); + Object.defineProperty(window, 'innerHeight', { writable: true, value: 1080 }); +} diff --git a/tests/component/layout/AppShell.test.tsx b/tests/component/layout/AppShell.test.tsx new file mode 100644 index 0000000..cd1cfe2 --- /dev/null +++ b/tests/component/layout/AppShell.test.tsx @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider } from '@/components/theme-provider'; +import { AppShell } from '@/components/layout/AppShell'; +import { useAppStore } from '@/stores/app-store'; + +// Mock react-resizable-panels for jsdom environment +vi.mock('@/components/ui/resizable', () => ({ + ResizablePanelGroup: ({ children, direction, onLayout }: any) => ( +
+ {children} +
+ ), + ResizablePanel: ({ children, defaultSize, minSize, maxSize }: any) => ( +
+ {children} +
+ ), + ResizableHandle: () =>
, +})); + +describe('AppShell', () => { + beforeEach(() => { + localStorage.clear(); + useAppStore.setState({ sidebarVisible: true, previewVisible: true, zenMode: false }); + }); + + it('renders all shell surfaces when sidebar and preview are visible', () => { + render( + + + + ); + expect(screen.getByText(/markdownconverter/i)).toBeInTheDocument(); + expect(screen.getByText(/no files open/i)).toBeInTheDocument(); + expect(screen.getByText(/no file selected/i)).toBeInTheDocument(); + expect(screen.getByText(/0 words/i)).toBeInTheDocument(); + }); + + it('hides sidebar when sidebarVisible is false', () => { + useAppStore.setState({ sidebarVisible: false }); + render( + + + + ); + expect(screen.queryByText(/file tree placeholder/i)).not.toBeInTheDocument(); + }); +}); \ No newline at end of file