This commit is contained in:
@@ -12,15 +12,22 @@ vi.mock('next/navigation', () => ({
|
||||
|
||||
// Mock ResizeObserver
|
||||
global.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
observe(): void {
|
||||
/* noop */
|
||||
}
|
||||
unobserve(): void {
|
||||
/* noop */
|
||||
}
|
||||
disconnect(): void {
|
||||
/* noop */
|
||||
}
|
||||
};
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
const createTestQueryClient = () => new QueryClient({
|
||||
const createTestQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
@@ -33,16 +40,17 @@ describe('Dashboard', () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock Auth Response
|
||||
(global.fetch as any).mockImplementation((url: string) => {
|
||||
vi.mocked(global.fetch).mockImplementation((url: string | URL | Request) => {
|
||||
if (url === '/api/auth') {
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve({ authenticated: true, token: 'test-token' }),
|
||||
});
|
||||
} as Response);
|
||||
}
|
||||
if (url === '/api/habits') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
habits: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -53,12 +61,12 @@ describe('Dashboard', () => {
|
||||
logsLastMonth: 5,
|
||||
createdAt: new Date().toISOString(),
|
||||
lastLoggedAt: new Date().toISOString(),
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
} as Response);
|
||||
}
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) } as Response);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +74,7 @@ describe('Dashboard', () => {
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<Dashboard />
|
||||
</QueryClientProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -78,7 +86,7 @@ describe('Dashboard', () => {
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<Dashboard />
|
||||
</QueryClientProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -99,7 +107,7 @@ describe('Dashboard', () => {
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<Dashboard />
|
||||
</QueryClientProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -109,7 +109,9 @@ export default function Dashboard() {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(Date.now());
|
||||
}, 60000);
|
||||
return () => clearInterval(interval);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Fetch habits
|
||||
@@ -140,9 +142,9 @@ export default function Dashboard() {
|
||||
});
|
||||
|
||||
// Undo log mutation
|
||||
const undoLogMutation = useMutation<void, Error, number>({
|
||||
const undoLogMutation = useMutation<unknown, Error, number>({
|
||||
mutationFn: async (habitId: number): Promise<void> => {
|
||||
const res = await fetch(`/api/habits/${habitId}/log`, {
|
||||
const res = await fetch(`/api/habits/${String(habitId)}/log`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to undo log');
|
||||
@@ -172,9 +174,13 @@ export default function Dashboard() {
|
||||
});
|
||||
|
||||
// Update habit mutation
|
||||
const updateHabitMutation = useMutation<HabitResponse, Error, { id: number; name: string; type: string }>({
|
||||
const updateHabitMutation = useMutation<
|
||||
HabitResponse,
|
||||
Error,
|
||||
{ id: number; name: string; type: string }
|
||||
>({
|
||||
mutationFn: async ({ id, name, type }): Promise<HabitResponse> => {
|
||||
const res = await fetch(`/api/habits/${id}`, {
|
||||
const res = await fetch(`/api/habits/${String(id)}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, type }),
|
||||
@@ -189,9 +195,9 @@ export default function Dashboard() {
|
||||
});
|
||||
|
||||
// Delete habit mutation
|
||||
const deleteHabitMutation = useMutation<void, Error, number>({
|
||||
const deleteHabitMutation = useMutation<unknown, Error, number>({
|
||||
mutationFn: async (id: number): Promise<void> => {
|
||||
const res = await fetch(`/api/habits/${id}`, {
|
||||
const res = await fetch(`/api/habits/${String(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to delete habit');
|
||||
@@ -450,8 +456,10 @@ export default function Dashboard() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-black/20"
|
||||
onClick={(e) => openEditDialog(e, habit)}
|
||||
data-testid={`edit-habit-${habit.id}`}
|
||||
onClick={(e) => {
|
||||
openEditDialog(e, habit);
|
||||
}}
|
||||
data-testid={`edit-habit-${String(habit.id)}`}
|
||||
>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -477,7 +485,7 @@ export default function Dashboard() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 text-zinc-500 hover:text-red-400 hover:bg-red-950/30"
|
||||
className="h-6 w-6 text-zinc-500 hover:bg-red-950/30 hover:text-red-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
undoLogMutation.mutate(habit.id);
|
||||
@@ -555,7 +563,12 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* Edit Habit Dialog */}
|
||||
<Dialog open={!!editingHabit} onOpenChange={(open) => !open && setEditingHabit(null)}>
|
||||
<Dialog
|
||||
open={!!editingHabit}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setEditingHabit(null);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="border-zinc-800 bg-zinc-950">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Habit</DialogTitle>
|
||||
@@ -569,7 +582,9 @@ export default function Dashboard() {
|
||||
<Input
|
||||
id="edit-name"
|
||||
value={editHabitName}
|
||||
onChange={(e) => setEditHabitName(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setEditHabitName(e.target.value);
|
||||
}}
|
||||
className="border-zinc-800 bg-zinc-900"
|
||||
/>
|
||||
</div>
|
||||
@@ -618,7 +633,12 @@ export default function Dashboard() {
|
||||
>
|
||||
{deleteHabitMutation.isPending ? 'Deleting...' : 'Confirm Delete'}
|
||||
</Button>
|
||||
<Button variant="ghost" onClick={() => setShowDeleteConfirm(false)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
@@ -626,17 +646,21 @@ export default function Dashboard() {
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-red-900 text-red-500 hover:bg-red-950 hover:text-red-400"
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Archive Habit
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setEditingHabit(null)}
|
||||
onClick={() => {
|
||||
setEditingHabit(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user