fix(13-02): eliminate all ESLint violations — pnpm lint exits 0
- eslint.config.js: disable React Compiler rules (v7 flat.recommended enables
them; codebase does not use the Compiler); add e2e/ to disableTypeChecked
block; promote exhaustive-deps to error
- API broker: remove redundant as-casts (outboxWorker, poller, reminderScheduler,
expand, sync, vevent, spike); add targeted ical.js no-unsafe-assignment/argument
disables with justifying comments inside try blocks
- API routes/sse.ts: fix no-misused-promises on async writeSSE callback with
void+IIFE+catch pattern
- API routes/lists.ts: let → const for updateValues
- API tests: remove unused imports (beforeEach, eq, vi); rename unused vars
with _ prefix; remove unused lastActiveId assignment
- PWA components: void navigate() and void queryClient.invalidateQueries() on
all fire-and-forget call sites; fix CalendarShell explicit-type-casts;
Couldn't → HTML entity
- PWA test files: as unknown as Response for partial mock objects; string | null
type annotation on mockLastSyncedUid; remove async from test callbacks without
await; act(() => {}) not await act(async () => {}) for sync ops
- sw.ts: restructure Notification.data?.url access as let+if so disable
comments land on the exact violation lines; void self.skipWaiting()
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
|
||||
import React from 'react'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
// ── Module mocks ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -156,7 +156,7 @@ export function CalendarShell() {
|
||||
// IANATimezone (the config's timezone type) is declared but not exported by @schedule-x/calendar,
|
||||
// so derive it from useCalendarApp's config parameter rather than importing it.
|
||||
type SxTimeZone = NonNullable<Parameters<typeof useCalendarApp>[0]['timezone']>
|
||||
const displayTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone as SxTimeZone
|
||||
const displayTimeZone: SxTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
|
||||
// useCalendarApp — config is stable; plugins passed as second argument
|
||||
const calendar = useCalendarApp(
|
||||
@@ -169,7 +169,7 @@ export function CalendarShell() {
|
||||
],
|
||||
defaultView,
|
||||
timezone: displayTimeZone,
|
||||
firstDayOfWeek: SX_FIRST_DAY_OF_WEEK as 7,
|
||||
firstDayOfWeek: SX_FIRST_DAY_OF_WEEK,
|
||||
calendars: calendarsConfig,
|
||||
callbacks: {
|
||||
onRangeUpdate(range) {
|
||||
@@ -197,7 +197,7 @@ export function CalendarShell() {
|
||||
useEffect(() => {
|
||||
if (!eventsQuery.data) return
|
||||
const sxEvents = hydrateEvents(eventsQuery.data.occurrences)
|
||||
eventsService.set(sxEvents as Parameters<typeof eventsService.set>[0])
|
||||
eventsService.set(sxEvents)
|
||||
}, [eventsQuery.data, eventsService])
|
||||
|
||||
// Auth redirect — one-shot full-page nav to /api/login when /api/me fails.
|
||||
@@ -351,7 +351,7 @@ export function CalendarShell() {
|
||||
color: 'var(--color-text-primary)',
|
||||
}}
|
||||
>
|
||||
Couldn't load events
|
||||
Couldn't load events
|
||||
</h2>
|
||||
<p
|
||||
style={{
|
||||
@@ -363,7 +363,7 @@ export function CalendarShell() {
|
||||
Check your connection and try again.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => queryClient.refetchQueries({ queryKey: ['events'] })}
|
||||
onClick={() => { void queryClient.refetchQueries({ queryKey: ['events'] }) }}
|
||||
style={{
|
||||
background: 'var(--color-surface-dim)',
|
||||
border: '1px solid var(--color-border)',
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { createList } from '../api/listsClient.js'
|
||||
import type { List, ListsResponse } from '../api/listsClient.js'
|
||||
import type { ListsResponse } from '../api/listsClient.js'
|
||||
import { useListsStore } from '../store/listsStore.js'
|
||||
|
||||
export function CreateListSheet() {
|
||||
@@ -82,7 +82,7 @@ export function CreateListSheet() {
|
||||
ownerId: 0, // unknown until response
|
||||
activeCount: 0,
|
||||
doneCount: 0,
|
||||
} as List,
|
||||
},
|
||||
],
|
||||
}))
|
||||
return { previous }
|
||||
@@ -94,8 +94,8 @@ export function CreateListSheet() {
|
||||
}
|
||||
},
|
||||
onSettled: () => {
|
||||
// Always invalidate to get canonical server state
|
||||
queryClient.invalidateQueries({ queryKey: ['lists'] })
|
||||
// Always invalidate to get canonical server state; fire-and-forget (React Query handles cache update)
|
||||
void queryClient.invalidateQueries({ queryKey: ['lists'] })
|
||||
},
|
||||
onSuccess: () => {
|
||||
handleClose()
|
||||
|
||||
@@ -33,7 +33,7 @@ const {
|
||||
mockSetLastSyncedUid: vi.fn(),
|
||||
mockSetOpenEventId: vi.fn(),
|
||||
mockDeleteDialogOpen: { value: true },
|
||||
mockDeleteDialogUid: { value: 'event-uid-to-delete' as string | null },
|
||||
mockDeleteDialogUid: { value: 'event-uid-to-delete' },
|
||||
}))
|
||||
|
||||
vi.mock('../store/calendarStore.js', () => ({
|
||||
|
||||
@@ -332,7 +332,7 @@ describe('EventForm', () => {
|
||||
const tomorrow = new Date(today)
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
const todayStr = today.toISOString().slice(0, 10)
|
||||
const yesterdayStr = new Date(today.setDate(today.getDate() - 1)).toISOString().slice(0, 10)
|
||||
const _yesterdayStr = new Date(today.setDate(today.getDate() - 1)).toISOString().slice(0, 10)
|
||||
|
||||
if (dateInputs.length >= 2) {
|
||||
fireEvent.change(dateInputs[0], { target: { value: todayStr } })
|
||||
@@ -462,7 +462,7 @@ describe('EventForm', () => {
|
||||
eventOccurrence: EDIT_OCCURRENCE,
|
||||
})
|
||||
|
||||
const titleInput = screen.getByPlaceholderText('Event title') as HTMLInputElement
|
||||
const titleInput = screen.getByPlaceholderText<HTMLInputElement>('Event title')
|
||||
expect(titleInput.value).toBe('Existing Meeting')
|
||||
})
|
||||
})
|
||||
@@ -560,7 +560,7 @@ describe('EventForm — Plan 03-12 gap closures', () => {
|
||||
)
|
||||
|
||||
// Title should be empty (occurrence not yet in cache)
|
||||
const titleInput = screen.getByPlaceholderText('Event title') as HTMLInputElement
|
||||
const titleInput = screen.getByPlaceholderText<HTMLInputElement>('Event title')
|
||||
expect(titleInput.value).toBe('')
|
||||
|
||||
// Phase 2: occurrence arrives in cache (simulating TanStack Query resolving)
|
||||
@@ -576,7 +576,7 @@ describe('EventForm — Plan 03-12 gap closures', () => {
|
||||
// WR-03 fix: the reset effect must re-run because occurrence changed,
|
||||
// so the title should now be populated
|
||||
await waitFor(() => {
|
||||
const titleInputAfter = screen.getByPlaceholderText('Event title') as HTMLInputElement
|
||||
const titleInputAfter = screen.getByPlaceholderText<HTMLInputElement>('Event title')
|
||||
expect(titleInputAfter.value).toBe('Late-Arriving Meeting')
|
||||
})
|
||||
})
|
||||
@@ -676,7 +676,7 @@ describe('EventForm — Plan 03-12 gap closures', () => {
|
||||
|
||||
it('IN-03: todayIso is exported from calendarStore (single source of truth)', async () => {
|
||||
// Use importActual to bypass the vi.mock() and test the real module export
|
||||
const actualModule = await vi.importActual('../store/calendarStore.js') as Record<string, unknown>
|
||||
const actualModule = await vi.importActual('../store/calendarStore.js')
|
||||
expect(typeof actualModule.todayIso).toBe('function')
|
||||
const result = (actualModule.todayIso as () => string)()
|
||||
// Should return a YYYY-MM-DD string
|
||||
@@ -950,9 +950,11 @@ describe('EventForm — Plan 06-06 end-tracking + recurrence-bound', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCreateEvent).toHaveBeenCalledWith(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- expect.anything() returns 'any' (Vitest asymmetric matcher); safe in assertion context
|
||||
expect.not.objectContaining({ recurrenceUntil: expect.anything() }),
|
||||
)
|
||||
expect(mockCreateEvent).toHaveBeenCalledWith(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- expect.anything() returns 'any' (Vitest asymmetric matcher); safe in assertion context
|
||||
expect.not.objectContaining({ recurrenceCount: expect.anything() }),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -271,7 +271,7 @@ export function EventForm() {
|
||||
// read it if a future API version adds it, and default to 'none' when not present
|
||||
// (WR-03 v1 comment: occurrence edits whose recurrence is not in the cache default
|
||||
// to 'none'; this will be addressed when the occurrence/expand contract is extended).
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- occurrence.recurrence is not in CalendarOccurrence v1 type; cast to any to read a future API field, default 'none' when absent
|
||||
const derivedRecurrence = (occurrence as any)?.recurrence as RecurrencePreset | undefined
|
||||
setRecurrence(derivedRecurrence ?? 'none')
|
||||
// D-06: reset bound state to defaults on form open/occurrence change
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('useAndroidInstallPrompt', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sets canInstall=true when a beforeinstallprompt event is dispatched', async () => {
|
||||
it('sets canInstall=true when a beforeinstallprompt event is dispatched', () => {
|
||||
const { result } = renderHook(() => useAndroidInstallPrompt())
|
||||
expect(result.current.canInstall).toBe(false)
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('useAndroidInstallPrompt', () => {
|
||||
expect(result.current.canInstall).toBe(true)
|
||||
})
|
||||
|
||||
it('sets canInstall=false when appinstalled event fires', async () => {
|
||||
it('sets canInstall=false when appinstalled event fires', () => {
|
||||
const { result } = renderHook(() => useAndroidInstallPrompt())
|
||||
|
||||
// First fire beforeinstallprompt to set canInstall=true
|
||||
|
||||
@@ -14,7 +14,7 @@ import { render, screen, fireEvent } from '@testing-library/react'
|
||||
|
||||
vi.mock('../hooks/usePushSubscription.js', () => ({
|
||||
usePushSubscription: vi.fn(() => ({
|
||||
permission: 'denied' as NotificationPermission,
|
||||
permission: 'denied',
|
||||
isSubscribed: false,
|
||||
subscribe: vi.fn(),
|
||||
setEnabled: vi.fn(),
|
||||
|
||||
@@ -39,7 +39,7 @@ export function ListCard({ list, onDelete }: ListCardProps) {
|
||||
const [showDelete, setShowDelete] = useState(false)
|
||||
|
||||
const handleCardClick = () => {
|
||||
navigate(`/lists/${list.id}`)
|
||||
void navigate(`/lists/${list.id}`)
|
||||
}
|
||||
|
||||
const handleDeleteClick = (e: React.MouseEvent) => {
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('SyncStateToast', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('renders nothing when lastSyncedUid is null', async () => {
|
||||
it('renders nothing when lastSyncedUid is null', () => {
|
||||
mockLastSyncedUid.value = null
|
||||
const { container } = renderToast(queryClient)
|
||||
expect(container.firstChild).toBeNull()
|
||||
|
||||
Reference in New Issue
Block a user