AI linter fixes
All checks were successful
Lint / Lint and Check (push) Successful in 35s

This commit is contained in:
2025-12-14 17:50:08 +01:00
parent 907acc4fec
commit 2e3ba71148
2 changed files with 133 additions and 101 deletions

View File

@@ -12,53 +12,61 @@ vi.mock('next/navigation', () => ({
// Mock ResizeObserver // Mock ResizeObserver
global.ResizeObserver = class ResizeObserver { global.ResizeObserver = class ResizeObserver {
observe() {} observe(): void {
unobserve() {} /* noop */
disconnect() {} }
unobserve(): void {
/* noop */
}
disconnect(): void {
/* noop */
}
}; };
// Mock fetch // Mock fetch
global.fetch = vi.fn(); global.fetch = vi.fn();
const createTestQueryClient = () => new QueryClient({ const createTestQueryClient = () =>
defaultOptions: { new QueryClient({
queries: { defaultOptions: {
retry: false, queries: {
retry: false,
},
}, },
}, });
});
describe('Dashboard', () => { describe('Dashboard', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
// Mock Auth Response // Mock Auth Response
(global.fetch as any).mockImplementation((url: string) => { vi.mocked(global.fetch).mockImplementation((url: string | URL | Request) => {
if (url === '/api/auth') { if (url === '/api/auth') {
return Promise.resolve({ return Promise.resolve({
json: () => Promise.resolve({ authenticated: true, token: 'test-token' }), json: () => Promise.resolve({ authenticated: true, token: 'test-token' }),
}); } as Response);
} }
if (url === '/api/habits') { if (url === '/api/habits') {
return Promise.resolve({ return Promise.resolve({
ok: true, ok: true,
json: () => Promise.resolve({ json: () =>
habits: [ Promise.resolve({
{ habits: [
id: 1, {
name: 'Test Habit', id: 1,
type: 'neutral', name: 'Test Habit',
totalLogs: 5, type: 'neutral',
logsLastWeek: 2, totalLogs: 5,
logsLastMonth: 5, logsLastWeek: 2,
createdAt: new Date().toISOString(), logsLastMonth: 5,
lastLoggedAt: new Date().toISOString(), 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( render(
<QueryClientProvider client={createTestQueryClient()}> <QueryClientProvider client={createTestQueryClient()}>
<Dashboard /> <Dashboard />
</QueryClientProvider> </QueryClientProvider>,
); );
await waitFor(() => { await waitFor(() => {
@@ -78,7 +86,7 @@ describe('Dashboard', () => {
render( render(
<QueryClientProvider client={createTestQueryClient()}> <QueryClientProvider client={createTestQueryClient()}>
<Dashboard /> <Dashboard />
</QueryClientProvider> </QueryClientProvider>,
); );
await waitFor(() => { await waitFor(() => {
@@ -99,7 +107,7 @@ describe('Dashboard', () => {
render( render(
<QueryClientProvider client={createTestQueryClient()}> <QueryClientProvider client={createTestQueryClient()}>
<Dashboard /> <Dashboard />
</QueryClientProvider> </QueryClientProvider>,
); );
await waitFor(() => { await waitFor(() => {

View File

@@ -77,12 +77,12 @@ interface HabitResponse {
export default function Dashboard() { export default function Dashboard() {
const router = useRouter(); const router = useRouter();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// State // State
const [showNewHabitDialog, setShowNewHabitDialog] = useState(false); const [showNewHabitDialog, setShowNewHabitDialog] = useState(false);
const [newHabitName, setNewHabitName] = useState(''); const [newHabitName, setNewHabitName] = useState('');
const [newHabitType, setNewHabitType] = useState<'positive' | 'neutral' | 'negative'>('neutral'); const [newHabitType, setNewHabitType] = useState<'positive' | 'neutral' | 'negative'>('neutral');
const [editingHabit, setEditingHabit] = useState<Habit | null>(null); const [editingHabit, setEditingHabit] = useState<Habit | null>(null);
const [editHabitName, setEditHabitName] = useState(''); const [editHabitName, setEditHabitName] = useState('');
const [editHabitType, setEditHabitType] = useState<'positive' | 'neutral' | 'negative'>('neutral'); const [editHabitType, setEditHabitType] = useState<'positive' | 'neutral' | 'negative'>('neutral');
@@ -109,7 +109,9 @@ export default function Dashboard() {
const interval = setInterval(() => { const interval = setInterval(() => {
setCurrentTime(Date.now()); setCurrentTime(Date.now());
}, 60000); }, 60000);
return () => clearInterval(interval); return () => {
clearInterval(interval);
};
}, []); }, []);
// Fetch habits // Fetch habits
@@ -140,9 +142,9 @@ export default function Dashboard() {
}); });
// Undo log mutation // Undo log mutation
const undoLogMutation = useMutation<void, Error, number>({ const undoLogMutation = useMutation<unknown, Error, number>({
mutationFn: async (habitId: number): Promise<void> => { 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', method: 'DELETE',
}); });
if (!res.ok) throw new Error('Failed to undo log'); if (!res.ok) throw new Error('Failed to undo log');
@@ -172,9 +174,13 @@ export default function Dashboard() {
}); });
// Update habit mutation // 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> => { mutationFn: async ({ id, name, type }): Promise<HabitResponse> => {
const res = await fetch(`/api/habits/${id}`, { const res = await fetch(`/api/habits/${String(id)}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, type }), body: JSON.stringify({ name, type }),
@@ -189,9 +195,9 @@ export default function Dashboard() {
}); });
// Delete habit mutation // Delete habit mutation
const deleteHabitMutation = useMutation<void, Error, number>({ const deleteHabitMutation = useMutation<unknown, Error, number>({
mutationFn: async (id: number): Promise<void> => { mutationFn: async (id: number): Promise<void> => {
const res = await fetch(`/api/habits/${id}`, { const res = await fetch(`/api/habits/${String(id)}`, {
method: 'DELETE', method: 'DELETE',
}); });
if (!res.ok) throw new Error('Failed to delete habit'); if (!res.ok) throw new Error('Failed to delete habit');
@@ -450,8 +456,10 @@ export default function Dashboard() {
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-8 w-8 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-black/20" className="h-8 w-8 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-black/20"
onClick={(e) => openEditDialog(e, habit)} onClick={(e) => {
data-testid={`edit-habit-${habit.id}`} openEditDialog(e, habit);
}}
data-testid={`edit-habit-${String(habit.id)}`}
> >
<MoreVertical className="h-4 w-4" /> <MoreVertical className="h-4 w-4" />
</Button> </Button>
@@ -463,34 +471,34 @@ export default function Dashboard() {
{/* Last logged */} {/* Last logged */}
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-zinc-500" /> <Clock className="h-4 w-4 text-zinc-500" />
<span className="text-zinc-400"> <span className="text-zinc-400">
{habit.lastLoggedAt {habit.lastLoggedAt
? formatDistanceToNow(new Date(habit.lastLoggedAt), { addSuffix: true }) ? formatDistanceToNow(new Date(habit.lastLoggedAt), { addSuffix: true })
: 'Never logged'} : 'Never logged'}
</span> </span>
</div> </div>
{habit.lastLoggedAt && ( {habit.lastLoggedAt && (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="ghost" variant="ghost"
size="icon" 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) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
undoLogMutation.mutate(habit.id); undoLogMutation.mutate(habit.id);
}} }}
> >
<RotateCcw className="h-3 w-3" /> <RotateCcw className="h-3 w-3" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Undo last log</p> <p>Undo last log</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
</div> </div>
@@ -553,9 +561,14 @@ export default function Dashboard() {
</Card> </Card>
)} )}
</div> </div>
{/* Edit Habit Dialog */} {/* 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"> <DialogContent className="border-zinc-800 bg-zinc-950">
<DialogHeader> <DialogHeader>
<DialogTitle>Edit Habit</DialogTitle> <DialogTitle>Edit Habit</DialogTitle>
@@ -569,7 +582,9 @@ export default function Dashboard() {
<Input <Input
id="edit-name" id="edit-name"
value={editHabitName} value={editHabitName}
onChange={(e) => setEditHabitName(e.target.value)} onChange={(e) => {
setEditHabitName(e.target.value);
}}
className="border-zinc-800 bg-zinc-900" className="border-zinc-800 bg-zinc-900"
/> />
</div> </div>
@@ -608,47 +623,56 @@ export default function Dashboard() {
</div> </div>
</div> </div>
<DialogFooter className="flex-col items-stretch gap-2 sm:flex-row sm:justify-between"> <DialogFooter className="flex-col items-stretch gap-2 sm:flex-row sm:justify-between">
<div className="flex flex-1 justify-start"> <div className="flex flex-1 justify-start">
{showDeleteConfirm ? ( {showDeleteConfirm ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="destructive" variant="destructive"
onClick={handleDeleteHabit} onClick={handleDeleteHabit}
disabled={deleteHabitMutation.isPending} disabled={deleteHabitMutation.isPending}
> >
{deleteHabitMutation.isPending ? 'Deleting...' : 'Confirm Delete'} {deleteHabitMutation.isPending ? 'Deleting...' : 'Confirm Delete'}
</Button> </Button>
<Button variant="ghost" onClick={() => setShowDeleteConfirm(false)}> <Button
Cancel variant="ghost"
</Button> onClick={() => {
</div> setShowDeleteConfirm(false);
}}
>
Cancel
</Button>
</div>
) : ( ) : (
<Button <Button
variant="outline" variant="outline"
className="border-red-900 text-red-500 hover:bg-red-950 hover:text-red-400" 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" /> <Trash2 className="mr-2 h-4 w-4" />
Archive Habit Archive Habit
</Button> </Button>
)} )}
</div> </div>
<div className="flex items-center gap-2 justify-end"> <div className="flex items-center justify-end gap-2">
<Button <Button
variant="outline" variant="outline"
onClick={() => setEditingHabit(null)} onClick={() => {
> setEditingHabit(null);
Cancel }}
</Button> >
<Button Cancel
onClick={handleUpdateHabit} </Button>
disabled={!editHabitName.trim() || updateHabitMutation.isPending} <Button
className="bg-emerald-600 hover:bg-emerald-700" onClick={handleUpdateHabit}
> disabled={!editHabitName.trim() || updateHabitMutation.isPending}
<Save className="mr-2 h-4 w-4" /> className="bg-emerald-600 hover:bg-emerald-700"
{updateHabitMutation.isPending ? 'Saving...' : 'Save Changes'} >
</Button> <Save className="mr-2 h-4 w-4" />
</div> {updateHabitMutation.isPending ? 'Saving...' : 'Save Changes'}
</Button>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>