diff --git a/src/renderer/components/layout/TabBar.tsx b/src/renderer/components/layout/TabBar.tsx
index 2500374..29f00e9 100644
--- a/src/renderer/components/layout/TabBar.tsx
+++ b/src/renderer/components/layout/TabBar.tsx
@@ -1,7 +1,58 @@
+import { X } from 'lucide-react';
+import { useFileStore } from '@/stores/file-store';
+import { cn } from '@/lib/utils';
+
export function TabBar() {
+ const openTabs = useFileStore((s) => s.openTabs);
+ const activeTabId = useFileStore((s) => s.activeTabId);
+ const setActiveTab = useFileStore((s) => s.setActiveTab);
+ const closeTab = useFileStore((s) => s.closeTab);
+
+ if (openTabs.length === 0) {
+ return (
+
-
No files open
+
+
+ {openTabs.map((tab) => {
+ const isActive = tab.id === activeTabId;
+ return (
+
setActiveTab(tab.id)}
+ >
+ {tab.dirty && (
+
+ )}
+ {tab.title}
+
+
+ );
+ })}
+
);
-}
\ No newline at end of file
+}
diff --git a/tests/component/layout/TabBar.test.tsx b/tests/component/layout/TabBar.test.tsx
index 072ef70..94ef08b 100644
--- a/tests/component/layout/TabBar.test.tsx
+++ b/tests/component/layout/TabBar.test.tsx
@@ -1,10 +1,93 @@
-import { describe, it, expect } from 'vitest';
-import { render, screen } from '@testing-library/react';
+import { describe, it, expect, beforeEach } from 'vitest';
+import { render, screen, act } from '@testing-library/react';
import { TabBar } from '@/components/layout/TabBar';
+import { useFileStore } from '@/stores/file-store';
describe('TabBar', () => {
- it('renders an empty state when no tabs are open', () => {
+ beforeEach(() => {
+ useFileStore.setState({ openTabs: [], activeTabId: null });
+ });
+
+ it('renders "No files open" when openTabs is empty', () => {
render(
);
expect(screen.getByText(/no files open/i)).toBeInTheDocument();
});
-});
\ No newline at end of file
+
+ it('renders a tab for each OpenTab', () => {
+ useFileStore.setState({
+ openTabs: [
+ { id: '/tmp/foo.md', path: '/tmp/foo.md', title: 'foo.md', dirty: false },
+ { id: '/tmp/bar.md', path: '/tmp/bar.md', title: 'bar.md', dirty: false },
+ ],
+ activeTabId: '/tmp/foo.md',
+ });
+ render(
);
+ expect(screen.getByText('foo.md')).toBeInTheDocument();
+ expect(screen.getByText('bar.md')).toBeInTheDocument();
+ });
+
+ it('highlights the active tab with aria-current', () => {
+ useFileStore.setState({
+ openTabs: [
+ { id: '/tmp/foo.md', path: '/tmp/foo.md', title: 'foo.md', dirty: false },
+ { id: '/tmp/bar.md', path: '/tmp/bar.md', title: 'bar.md', dirty: false },
+ ],
+ activeTabId: '/tmp/foo.md',
+ });
+ render(
);
+ const fooTab = screen.getByText('foo.md').closest('[role="tab"]');
+ expect(fooTab).toHaveAttribute('aria-current', 'page');
+ const barTab = screen.getByText('bar.md').closest('[role="tab"]');
+ expect(barTab).not.toHaveAttribute('aria-current');
+ });
+
+ it('clicking a tab calls setActiveTab', async () => {
+ useFileStore.setState({
+ openTabs: [
+ { id: '/tmp/foo.md', path: '/tmp/foo.md', title: 'foo.md', dirty: false },
+ { id: '/tmp/bar.md', path: '/tmp/bar.md', title: 'bar.md', dirty: false },
+ ],
+ activeTabId: '/tmp/foo.md',
+ });
+ render(
);
+ await act(async () => {
+ screen.getByText('bar.md').closest('[role="tab"]')!.click();
+ });
+ expect(useFileStore.getState().activeTabId).toBe('/tmp/bar.md');
+ });
+
+ it('clicking the close button calls closeTab and does not call setActiveTab separately', async () => {
+ // Use a non-active tab to test that closeTab doesn't change activeTabId
+ useFileStore.setState({
+ openTabs: [
+ { id: '/tmp/foo.md', path: '/tmp/foo.md', title: 'foo.md', dirty: false },
+ { id: '/tmp/bar.md', path: '/tmp/bar.md', title: 'bar.md', dirty: false },
+ ],
+ activeTabId: '/tmp/bar.md',
+ });
+ render(
);
+
+ const fooTab = screen.getByText('foo.md');
+ const closeBtn = fooTab.closest('[role="tab"]')!.querySelector('button')!;
+ await act(async () => {
+ closeBtn.click();
+ });
+
+ const state = useFileStore.getState();
+ expect(state.openTabs.find((t) => t.id === '/tmp/foo.md')).toBeUndefined();
+ // activeTabId stays on bar since we closed a non-active tab
+ expect(state.activeTabId).toBe('/tmp/bar.md');
+ });
+
+ it('renders a dirty indicator when dirty === true', () => {
+ useFileStore.setState({
+ openTabs: [
+ { id: '/tmp/foo.md', path: '/tmp/foo.md', title: 'foo.md', dirty: true },
+ ],
+ activeTabId: '/tmp/foo.md',
+ });
+ render(
);
+ const dirtyDot = screen.getByLabelText('Unsaved changes');
+ expect(dirtyDot).toBeInTheDocument();
+ });
+});