forked from cardosofelipe/fast-next-template
Refactor to enforce AuthContext usage over useAuthStore and improve test stability
- Replaced `useAuthStore` with `useAuth` from `AuthContext` across frontend components and tests to ensure dependency injection compliance. - Enhanced E2E test stability by delaying navigation until the auth context is fully initialized. - Updated Playwright configuration to use a single worker to prevent mock conflicts. - Refactored test setup to consistently inject `AuthProvider` for improved isolation and mocking. - Adjusted comments and documentation to clarify dependency injection and testability patterns.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useAuthStore } from '@/lib/stores/authStore';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
|
||||
/**
|
||||
* AuthInitializer - Initializes auth state from encrypted storage on mount
|
||||
@@ -15,6 +15,9 @@ import { useAuthStore } from '@/lib/stores/authStore';
|
||||
* This component should be included in the app's Providers to ensure
|
||||
* authentication state is restored from storage when the app loads.
|
||||
*
|
||||
* IMPORTANT: Uses useAuth() to respect dependency injection for testability.
|
||||
* Do NOT import useAuthStore directly - it bypasses the Context wrapper.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // In app/providers.tsx
|
||||
@@ -29,9 +32,14 @@ import { useAuthStore } from '@/lib/stores/authStore';
|
||||
* ```
|
||||
*/
|
||||
export function AuthInitializer() {
|
||||
const loadAuthFromStorage = useAuthStore((state) => state.loadAuthFromStorage);
|
||||
const loadAuthFromStorage = useAuth((state) => state.loadAuthFromStorage);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip loading from storage in E2E tests - test store is already injected
|
||||
if (typeof window !== 'undefined' && (window as any).__E2E_TEST__) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load auth state from encrypted storage on mount
|
||||
loadAuthFromStorage();
|
||||
}, [loadAuthFromStorage]);
|
||||
|
||||
@@ -35,13 +35,16 @@ let refreshPromise: Promise<string> | null = null;
|
||||
/* istanbul ignore next */
|
||||
const getAuthStore = async () => {
|
||||
// Check for E2E test store injection (same pattern as AuthProvider)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (typeof window !== 'undefined' && (window as any).__TEST_AUTH_STORE__) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const testStore = (window as any).__TEST_AUTH_STORE__;
|
||||
// Test store must have getState() method for non-React contexts
|
||||
return testStore.getState();
|
||||
}
|
||||
|
||||
// Production: use real Zustand store
|
||||
// Note: Dynamic import is acceptable here (non-React context, checks __TEST_AUTH_STORE__ first)
|
||||
const { useAuthStore } = await import('@/lib/stores/authStore');
|
||||
return useAuthStore.getState();
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
confirmPasswordReset,
|
||||
changeCurrentUserPassword,
|
||||
} from '../client';
|
||||
import { useAuthStore } from '@/lib/stores/authStore';
|
||||
import type { User } from '@/lib/stores/authStore';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { parseAPIError, getGeneralError } from '../errors';
|
||||
@@ -50,8 +49,8 @@ export const authKeys = {
|
||||
* @returns React Query result with user data
|
||||
*/
|
||||
export function useMe() {
|
||||
const { isAuthenticated, accessToken } = useAuthStore();
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
const { isAuthenticated, accessToken } = useAuth();
|
||||
const setUser = useAuth((state) => state.setUser);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: authKeys.me,
|
||||
@@ -95,7 +94,7 @@ export function useMe() {
|
||||
export function useLogin(onSuccess?: () => void) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const setAuth = useAuthStore((state) => state.setAuth);
|
||||
const setAuth = useAuth((state) => state.setAuth);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (credentials: { email: string; password: string }) => {
|
||||
@@ -163,7 +162,7 @@ export function useLogin(onSuccess?: () => void) {
|
||||
export function useRegister(onSuccess?: () => void) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const setAuth = useAuthStore((state) => state.setAuth);
|
||||
const setAuth = useAuth((state) => state.setAuth);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: {
|
||||
@@ -240,8 +239,8 @@ export function useRegister(onSuccess?: () => void) {
|
||||
export function useLogout() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const clearAuth = useAuthStore((state) => state.clearAuth);
|
||||
const refreshToken = useAuthStore((state) => state.refreshToken);
|
||||
const clearAuth = useAuth((state) => state.clearAuth);
|
||||
const refreshToken = useAuth((state) => state.refreshToken);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
@@ -296,7 +295,7 @@ export function useLogout() {
|
||||
export function useLogoutAll() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const clearAuth = useAuthStore((state) => state.clearAuth);
|
||||
const clearAuth = useAuth((state) => state.clearAuth);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { updateCurrentUser } from '../client';
|
||||
import { useAuthStore } from '@/lib/stores/authStore';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import type { User } from '@/lib/stores/authStore';
|
||||
import { parseAPIError, getGeneralError } from '../errors';
|
||||
import { authKeys } from './useAuth';
|
||||
@@ -31,7 +31,7 @@ import { authKeys } from './useAuth';
|
||||
*/
|
||||
export function useUpdateProfile(onSuccess?: (message: string) => void) {
|
||||
const queryClient = useQueryClient();
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
const setUser = useAuth((state) => state.setUser);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
// eslint-disable-next-line no-restricted-imports -- This is the DI boundary, needs real store for production
|
||||
import { useAuthStore as useAuthStoreImpl } from "@/lib/stores/authStore";
|
||||
import type { User } from "@/lib/stores/authStore";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user