From b2f3ec8f25f4bb421557b08e446130079d2bfc73 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Mon, 10 Nov 2025 10:57:43 +0100 Subject: [PATCH] Refactor ESLint configuration and update test rules for clarity and consistency - Consolidated and modularized `eslint.config.mjs` with defined rules for source, test, E2E, and scripts. - Improved test and E2E rules with relaxed settings for flexibility and enhanced mocking. - Standardized variable naming and removed redundant imports in unit and E2E tests. - Updated error handling and comments to align with modern TypeScript best practices (e.g., `@ts-expect-error`). --- frontend/e2e/admin-access.spec.ts | 1 - frontend/e2e/admin-dashboard.spec.ts | 2 +- .../e2e/admin-organization-members.spec.ts | 2 +- frontend/e2e/admin-organizations.spec.ts | 2 +- frontend/e2e/admin-users.spec.ts | 2 +- frontend/e2e/auth.setup.ts | 4 +- frontend/e2e/helpers/coverage.ts | 2 +- frontend/e2e/homepage.spec.ts | 2 +- frontend/e2e/settings-navigation.spec.ts | 2 +- frontend/e2e/settings-password.spec.ts | 2 +- frontend/e2e/settings-profile.spec.ts | 2 +- frontend/eslint.config.mjs | 57 ++++++++++++++++++- frontend/scripts/convert-v8-to-istanbul.ts | 2 +- frontend/tests/app/(auth)/login/page.test.tsx | 2 +- .../PasswordResetConfirmContent.test.tsx | 2 +- .../app/(auth)/password-reset/page.test.tsx | 2 +- .../tests/app/(auth)/register/page.test.tsx | 2 +- .../settings/sessions/page.test.tsx | 2 +- .../admin/users/UserActionMenu.test.tsx | 3 - .../admin/users/UserFormDialog.test.tsx | 6 +- .../components/layout/Skeletons.test.tsx | 6 +- .../settings/PasswordChangeForm.test.tsx | 2 +- .../components/theme/ThemeToggle.test.tsx | 2 +- .../hooks/usePrefersReducedMotion.test.ts | 2 +- frontend/tests/lib/api/errors.test.ts | 4 +- frontend/tests/lib/api/hooks/useAuth.test.tsx | 2 +- 26 files changed, 85 insertions(+), 34 deletions(-) diff --git a/frontend/e2e/admin-access.spec.ts b/frontend/e2e/admin-access.spec.ts index f0371ed..1ac75fc 100644 --- a/frontend/e2e/admin-access.spec.ts +++ b/frontend/e2e/admin-access.spec.ts @@ -7,7 +7,6 @@ import { test, expect } from '@playwright/test'; import { setupAuthenticatedMocks, setupSuperuserMocks, - loginViaUI, } from './helpers/auth'; test.describe('Admin Access Control', () => { diff --git a/frontend/e2e/admin-dashboard.spec.ts b/frontend/e2e/admin-dashboard.spec.ts index 0094228..035e8b6 100644 --- a/frontend/e2e/admin-dashboard.spec.ts +++ b/frontend/e2e/admin-dashboard.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupSuperuserMocks, loginViaUI } from './helpers/auth'; +import { setupSuperuserMocks } from './helpers/auth'; test.describe('Admin Dashboard - Page Load', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/admin-organization-members.spec.ts b/frontend/e2e/admin-organization-members.spec.ts index 6afed3a..91799f1 100644 --- a/frontend/e2e/admin-organization-members.spec.ts +++ b/frontend/e2e/admin-organization-members.spec.ts @@ -5,7 +5,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupSuperuserMocks, loginViaUI } from './helpers/auth'; +import { setupSuperuserMocks } from './helpers/auth'; test.describe('Admin Organization Members - Navigation from Organizations List', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/admin-organizations.spec.ts b/frontend/e2e/admin-organizations.spec.ts index e86f125..675ca21 100644 --- a/frontend/e2e/admin-organizations.spec.ts +++ b/frontend/e2e/admin-organizations.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupSuperuserMocks, loginViaUI } from './helpers/auth'; +import { setupSuperuserMocks } from './helpers/auth'; test.describe('Admin Organization Management - Page Load', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/admin-users.spec.ts b/frontend/e2e/admin-users.spec.ts index 9cd8798..50e0c4c 100644 --- a/frontend/e2e/admin-users.spec.ts +++ b/frontend/e2e/admin-users.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupSuperuserMocks, loginViaUI } from './helpers/auth'; +import { setupSuperuserMocks } from './helpers/auth'; test.describe('Admin User Management - Page Load', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/auth.setup.ts b/frontend/e2e/auth.setup.ts index 464f7e2..a46d667 100644 --- a/frontend/e2e/auth.setup.ts +++ b/frontend/e2e/auth.setup.ts @@ -11,9 +11,9 @@ * - Savings: ~690s (~11 minutes) per test run */ -import { test as setup, expect } from '@playwright/test'; +import { test as setup } from '@playwright/test'; import path from 'path'; -import { setupAuthenticatedMocks, setupSuperuserMocks, loginViaUI } from './helpers/auth'; +import { setupAuthenticatedMocks, setupSuperuserMocks } from './helpers/auth'; // Use absolute paths to ensure correct file location const ADMIN_STORAGE_STATE = path.join(__dirname, '.auth', 'admin.json'); diff --git a/frontend/e2e/helpers/coverage.ts b/frontend/e2e/helpers/coverage.ts index a402c63..c2e96a5 100644 --- a/frontend/e2e/helpers/coverage.ts +++ b/frontend/e2e/helpers/coverage.ts @@ -57,7 +57,7 @@ export async function startCoverage( try { await page.coverage.startJSCoverage({ resetOnNavigation: options?.resetOnNavigation ?? false, - // @ts-ignore + // @ts-expect-error - includeRawScriptCoverage is not in official types but supported by Playwright includeRawScriptCoverage: options?.includeRawScriptCoverage ?? false, }); } catch (error) { diff --git a/frontend/e2e/homepage.spec.ts b/frontend/e2e/homepage.spec.ts index f40066a..62eee41 100644 --- a/frontend/e2e/homepage.spec.ts +++ b/frontend/e2e/homepage.spec.ts @@ -214,7 +214,7 @@ test.describe('Homepage - Mobile Menu Interactions', () => { test.skip('should close mobile menu when clicking outside', async ({ page }) => { // Open mobile menu - const mobileMenu = await openMobileMenu(page); + const _mobileMenu = await openMobileMenu(page); // Press Escape key to close menu (more reliable than clicking overlay) await page.keyboard.press('Escape'); diff --git a/frontend/e2e/settings-navigation.spec.ts b/frontend/e2e/settings-navigation.spec.ts index 169a9a3..00ea0c5 100644 --- a/frontend/e2e/settings-navigation.spec.ts +++ b/frontend/e2e/settings-navigation.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupAuthenticatedMocks, loginViaUI } from './helpers/auth'; +import { setupAuthenticatedMocks } from './helpers/auth'; test.describe('Settings Navigation', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/settings-password.spec.ts b/frontend/e2e/settings-password.spec.ts index 322ceb0..313dfa3 100644 --- a/frontend/e2e/settings-password.spec.ts +++ b/frontend/e2e/settings-password.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupAuthenticatedMocks, loginViaUI } from './helpers/auth'; +import { setupAuthenticatedMocks } from './helpers/auth'; test.describe('Password Change', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/e2e/settings-profile.spec.ts b/frontend/e2e/settings-profile.spec.ts index f5ffa36..2f57320 100644 --- a/frontend/e2e/settings-profile.spec.ts +++ b/frontend/e2e/settings-profile.spec.ts @@ -4,7 +4,7 @@ */ import { test, expect } from '@playwright/test'; -import { setupAuthenticatedMocks, loginViaUI, MOCK_USER } from './helpers/auth'; +import { setupAuthenticatedMocks, MOCK_USER } from './helpers/auth'; test.describe('Profile Settings', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 1a2257d..30d717d 100755 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -9,7 +9,7 @@ const compat = new FlatCompat({ baseDirectory: __dirname, }); -export default [ +const eslintConfig = [ ...compat.extends('next/core-web-vitals'), ...compat.extends('next/typescript'), { @@ -23,9 +23,12 @@ export default [ 'src/lib/api/generated/**', '*.gen.ts', '*.gen.tsx', + 'next-env.d.ts', // Auto-generated by Next.js ], }, + // Base rules for source code { + files: ['src/**/*.{ts,tsx}'], rules: { // Enforce Dependency Injection pattern for auth store // Components/hooks must use useAuth() from AuthContext, not useAuthStore directly @@ -46,4 +49,56 @@ export default [ ], }, }, + // Relaxed rules for test files + { + files: ['tests/**/*.{ts,tsx}', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', // Test mocks often need any + '@typescript-eslint/no-require-imports': 'off', // Jest sometimes needs require + 'react/display-name': 'off', // Mock components don't need display names + 'no-restricted-imports': 'off', // Tests can import store directly + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + }, + ], + }, + }, + // Relaxed rules for E2E tests + { + files: ['e2e/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', // Playwright helpers need flexibility + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + }, + ], + }, + }, + // Relaxed rules for scripts and config files + { + files: ['scripts/**/*.{ts,js}', '*.config.{ts,js,mjs}', 'jest.setup.js'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', // Build scripts need flexibility + '@typescript-eslint/no-require-imports': 'off', // CommonJS configs + '@next/next/no-assign-module-variable': 'off', // Scripts may need module.exports + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + }, + ], + }, + }, ]; + +export default eslintConfig; diff --git a/frontend/scripts/convert-v8-to-istanbul.ts b/frontend/scripts/convert-v8-to-istanbul.ts index ccb11cf..4e4d1ed 100644 --- a/frontend/scripts/convert-v8-to-istanbul.ts +++ b/frontend/scripts/convert-v8-to-istanbul.ts @@ -83,7 +83,7 @@ async function convertV8ToIstanbul() { // Dynamic import to handle both scenarios (installed vs not installed) const module = await import('v8-to-istanbul'); v8toIstanbul = module.default || module; - } catch (error) { + } catch { console.log('❌ v8-to-istanbul not installed\n'); console.log('📦 Install it with:'); console.log(' npm install -D v8-to-istanbul\n'); diff --git a/frontend/tests/app/(auth)/login/page.test.tsx b/frontend/tests/app/(auth)/login/page.test.tsx index 486b60f..05bc3a4 100644 --- a/frontend/tests/app/(auth)/login/page.test.tsx +++ b/frontend/tests/app/(auth)/login/page.test.tsx @@ -9,7 +9,7 @@ import LoginPage from '@/app/(auth)/login/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ __esModule: true, - default: (importFn: () => Promise, options?: any) => { + default: (_importFn: () => Promise, _options?: any) => { const Component = () =>
Mocked LoginForm
; Component.displayName = 'LoginForm'; return Component; diff --git a/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx b/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx index bf6e08d..8d72fc5 100644 --- a/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx +++ b/frontend/tests/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.test.tsx @@ -25,7 +25,7 @@ jest.mock('next/link', () => ({ // Mock dynamic import jest.mock('next/dynamic', () => ({ __esModule: true, - default: (importFn: () => Promise, options?: any) => { + default: (_importFn: () => Promise, _options?: any) => { const Component = ({ onSuccess }: { onSuccess?: () => void }) => (
diff --git a/frontend/tests/app/(auth)/password-reset/page.test.tsx b/frontend/tests/app/(auth)/password-reset/page.test.tsx index 041a504..c26d3af 100644 --- a/frontend/tests/app/(auth)/password-reset/page.test.tsx +++ b/frontend/tests/app/(auth)/password-reset/page.test.tsx @@ -9,7 +9,7 @@ import PasswordResetPage from '@/app/(auth)/password-reset/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ __esModule: true, - default: (importFn: () => Promise, options?: any) => { + default: (_importFn: () => Promise, _options?: any) => { const Component = () =>
Mocked PasswordResetRequestForm
; Component.displayName = 'PasswordResetRequestForm'; return Component; diff --git a/frontend/tests/app/(auth)/register/page.test.tsx b/frontend/tests/app/(auth)/register/page.test.tsx index e0e212a..3d39622 100644 --- a/frontend/tests/app/(auth)/register/page.test.tsx +++ b/frontend/tests/app/(auth)/register/page.test.tsx @@ -9,7 +9,7 @@ import RegisterPage from '@/app/(auth)/register/page'; // Mock dynamic import jest.mock('next/dynamic', () => ({ __esModule: true, - default: (importFn: () => Promise, options?: any) => { + default: (_importFn: () => Promise, _options?: any) => { const Component = () =>
Mocked RegisterForm
; Component.displayName = 'RegisterForm'; return Component; diff --git a/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx b/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx index 39a1cf0..4ae74aa 100644 --- a/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx +++ b/frontend/tests/app/(authenticated)/settings/sessions/page.test.tsx @@ -3,7 +3,7 @@ * Smoke tests for page rendering */ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import SessionsPage from '@/app/(authenticated)/settings/sessions/page'; diff --git a/frontend/tests/components/admin/users/UserActionMenu.test.tsx b/frontend/tests/components/admin/users/UserActionMenu.test.tsx index 8f58aac..ed284c2 100644 --- a/frontend/tests/components/admin/users/UserActionMenu.test.tsx +++ b/frontend/tests/components/admin/users/UserActionMenu.test.tsx @@ -510,8 +510,6 @@ describe('UserActionMenu', () => { describe('User Name Display', () => { it('displays full name when last name is provided', async () => { - const user = userEvent.setup(); - render( { }); it('displays first name only when last name is null', async () => { - const user = userEvent.setup(); const userWithoutLastName = { ...mockUser, last_name: null }; render( diff --git a/frontend/tests/components/admin/users/UserFormDialog.test.tsx b/frontend/tests/components/admin/users/UserFormDialog.test.tsx index da3973b..cf13322 100644 --- a/frontend/tests/components/admin/users/UserFormDialog.test.tsx +++ b/frontend/tests/components/admin/users/UserFormDialog.test.tsx @@ -52,9 +52,9 @@ describe('UserFormDialog', () => { describe('Module Exports', () => { it('exports UserFormDialog component', () => { - const module = require('@/components/admin/users/UserFormDialog'); - expect(module.UserFormDialog).toBeDefined(); - expect(typeof module.UserFormDialog).toBe('function'); + const moduleExports = require('@/components/admin/users/UserFormDialog'); + expect(moduleExports.UserFormDialog).toBeDefined(); + expect(typeof moduleExports.UserFormDialog).toBe('function'); }); it('component is a valid React component', () => { diff --git a/frontend/tests/components/layout/Skeletons.test.tsx b/frontend/tests/components/layout/Skeletons.test.tsx index c9fb966..7e2a614 100644 --- a/frontend/tests/components/layout/Skeletons.test.tsx +++ b/frontend/tests/components/layout/Skeletons.test.tsx @@ -30,7 +30,7 @@ describe('HeaderSkeleton', () => { }); it('has proper styling classes', () => { - const { container } = render(); + render(); // Verify backdrop blur and background const header = screen.getByRole('banner'); @@ -60,7 +60,7 @@ describe('AuthLoadingSkeleton', () => { }); it('renders main content with container', () => { - const { container } = render(); + render(); const main = screen.getByRole('main'); expect(main).toHaveClass('flex-1'); @@ -71,7 +71,7 @@ describe('AuthLoadingSkeleton', () => { }); it('renders skeleton placeholders in main content', () => { - const { container } = render(); + render(); const main = screen.getByRole('main'); diff --git a/frontend/tests/components/settings/PasswordChangeForm.test.tsx b/frontend/tests/components/settings/PasswordChangeForm.test.tsx index d60b449..6a306c6 100644 --- a/frontend/tests/components/settings/PasswordChangeForm.test.tsx +++ b/frontend/tests/components/settings/PasswordChangeForm.test.tsx @@ -2,7 +2,7 @@ * Tests for PasswordChangeForm Component */ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { PasswordChangeForm } from '@/components/settings/PasswordChangeForm'; diff --git a/frontend/tests/components/theme/ThemeToggle.test.tsx b/frontend/tests/components/theme/ThemeToggle.test.tsx index e63d9db..3bbc65f 100644 --- a/frontend/tests/components/theme/ThemeToggle.test.tsx +++ b/frontend/tests/components/theme/ThemeToggle.test.tsx @@ -6,7 +6,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ThemeToggle } from '@/components/theme/ThemeToggle'; -import { ThemeProvider, useTheme } from '@/components/theme/ThemeProvider'; +import { useTheme } from '@/components/theme/ThemeProvider'; // Mock theme provider for controlled testing jest.mock('@/components/theme/ThemeProvider', () => { diff --git a/frontend/tests/hooks/usePrefersReducedMotion.test.ts b/frontend/tests/hooks/usePrefersReducedMotion.test.ts index b5dc5eb..78c802f 100644 --- a/frontend/tests/hooks/usePrefersReducedMotion.test.ts +++ b/frontend/tests/hooks/usePrefersReducedMotion.test.ts @@ -164,7 +164,7 @@ describe('usePrefersReducedMotion', () => { it('handles SSR environment safely', () => { const originalWindow = global.window; - // @ts-ignore - Simulating SSR + // @ts-expect-error - Simulating SSR delete global.window; const { result } = renderHook(() => usePrefersReducedMotion()); diff --git a/frontend/tests/lib/api/errors.test.ts b/frontend/tests/lib/api/errors.test.ts index 7aecd7f..d5996ad 100644 --- a/frontend/tests/lib/api/errors.test.ts +++ b/frontend/tests/lib/api/errors.test.ts @@ -630,7 +630,7 @@ describe('API Error Handling', () => { describe('Error message completeness', () => { it('should have non-empty messages for all error codes', () => { - Object.entries(ERROR_MESSAGES).forEach(([code, message]) => { + Object.entries(ERROR_MESSAGES).forEach(([_code, message]) => { expect(message).toBeTruthy(); expect(message.length).toBeGreaterThan(0); expect(message).not.toBe(''); @@ -639,7 +639,7 @@ describe('API Error Handling', () => { it('should have user-friendly messages', () => { // Check that messages don't contain technical jargon or error codes - Object.entries(ERROR_MESSAGES).forEach(([code, message]) => { + Object.entries(ERROR_MESSAGES).forEach(([_code, message]) => { expect(message).not.toContain('null'); expect(message).not.toContain('undefined'); expect(message).not.toContain('Error:'); diff --git a/frontend/tests/lib/api/hooks/useAuth.test.tsx b/frontend/tests/lib/api/hooks/useAuth.test.tsx index 4113bf0..67cbc97 100644 --- a/frontend/tests/lib/api/hooks/useAuth.test.tsx +++ b/frontend/tests/lib/api/hooks/useAuth.test.tsx @@ -4,7 +4,7 @@ * These tests cover hook setup, types, and basic integration */ -import { renderHook, waitFor } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useIsAuthenticated,