Code To Learn logo

Code To Learn

M7: Forms & Authentication

L10: Test Authentication Flow

Verify complete sign-in functionality

Let's thoroughly test the authentication system and ensure everything works perfectly! ✅

Complete Authentication Flow

Full user journey:

1. User visits app (not signed in)

2. Navbar hidden, loading spinner shows

3. No valid session found

4. Loading completes, Navbar stays hidden

5. User navigates to /sign-in

6. Fills form and submits

7. API validates credentials

8. Success: Token + user data returned

9. AuthContext state updates

10. Navigate to home (/)

11. Navbar appears, user is signed in!

Testing Checklist

Test Initial Load (No Session)

  1. Clear browser storage:

    // Open DevTools Console
    localStorage.clear();
    sessionStorage.clear();
    document.cookie.split(";").forEach(c => {
      document.cookie = c.trim().split("=")[0] + '=;expires=' + new Date(0).toUTCString();
    });
  2. Refresh page

  3. Observe:

    • ✅ Loading spinner appears
    • ✅ No Navbar visible
    • ✅ Loading completes
    • ✅ HomePage shows (public content)

Test Sign-In Navigation

  1. Click sign-in link/button
  2. Should navigate to /sign-in
  3. Observe:
    • ✅ Sign-in page appears
    • ✅ Form is visible
    • ✅ Fields are empty
    • ✅ No errors shown

Test Form Validation

  1. Leave fields empty, click "Sign In"

  2. Should see:

    • ✅ "Email is required"
    • ✅ "Password is required"
  3. Enter invalid email: bad-email

  4. Should see:

    • ✅ "Please enter a valid email address"
  5. Enter short password: 123

  6. Should see:

    • ✅ "Password must be at least 8 characters"

Test Invalid Credentials

  1. Enter valid format but wrong credentials:

    • Email: wrong@example.com
    • Password: wrongpassword123
  2. Click "Sign In"

  3. Should see:

    • ✅ Button shows "Signing in..."
    • ✅ Button is disabled
    • ✅ API error appears: "Invalid email or password"
    • ✅ Still on sign-in page
    • ✅ Form is still functional

Test Successful Sign-In

  1. Enter valid credentials

  2. Click "Sign In"

  3. Should see:

    • ✅ Button shows "Signing in..."
    • ✅ Page redirects to /
    • ✅ Navbar appears
    • ✅ User name/avatar in Navbar
    • ✅ Can navigate to protected pages
  4. Check React DevTools:

    • user has data
    • token has JWT
    • isLoading is false

Test Session Persistence

  1. After signing in, refresh page (F5)
  2. Should see:
    • ✅ Brief loading spinner
    • ✅ Stays signed in
    • ✅ Navbar visible
    • ✅ User data preserved
    • ✅ Still on same page

Test Navigation While Signed In

  1. Navigate to different pages:

    • / → HomePage
    • /favorites → FavoritesPage
    • /sign-in → SignInPage (should redirect)
  2. All pages should:

    • ✅ Show Navbar
    • ✅ Have user data
    • ✅ Allow sign-out

Test with Different Scenarios

New user signing in for first time:

Timeline:
  0:00 - Visit site
  0:01 - See homepage (no Navbar)
  0:02 - Click "Sign In" button
  0:03 - Fill credentials
  0:04 - Submit form
  0:05 - Redirect to homepage
  0:06 - Navbar appears
  0:07 - Explore site (signed in)

Expected behavior:

// Initial state
user: null
token: null
isLoading: true

// After loading check
user: null
token: null
isLoading: false

// After sign-in
user: { name: "John", email: "...", ... }
token: "eyJhbG..."
isLoading: false

Test steps:

  1. Clear all storage (new user simulation)
  2. Visit homepage
  3. Navigate to /sign-in
  4. Enter valid credentials
  5. Submit form
  6. Verify redirect and Navbar appears

First-time sign-in successful!

User who signed in before (has refresh token cookie):

Timeline:
  0:00 - Visit site
  0:01 - Loading spinner
  0:02 - API fetches access token
  0:03 - User data loaded
  0:04 - Navbar appears
  0:05 - User is signed in automatically!

Expected behavior:

// Initial state
user: null
token: null
isLoading: true

// After token refresh
user: { name: "John", ... }
token: "eyJhbG..."
isLoading: false

Test steps:

  1. Sign in successfully
  2. Close browser (don't sign out)
  3. Reopen browser
  4. Visit homepage
  5. Should automatically sign in

Session restored!

How long does session last?

Access token: 15 minutes (in memory)
Refresh token: 7-30 days (HTTP-only cookie)

After 15 min:
  - Access token expired
  - Refresh token still valid
  - Next request triggers refresh
  - New access token issued

User returns after refresh token expired:

Timeline:
  Day 30 - Last signed in
  Day 31 - Refresh token expires
  Day 32 - User visits site
  0:01 - Loading spinner
  0:02 - API returns 401 (token invalid)
  0:03 - Loading completes
  0:04 - No Navbar (not signed in)
  0:05 - User must sign in again

Expected behavior:

// Initial state
isLoading: true

// After failed token refresh
user: null
token: null
isLoading: false

// User sees:
- No Navbar
- Public content only
- "Sign In" button/link

Test steps:

  1. Manually delete refresh token cookie
  2. Refresh page
  3. Should see loading, then no Navbar
  4. Navigate to /sign-in
  5. Sign in again

Expired session handled!

User experience:

User perspective:
"I haven't visited in a while, so I need to sign in again."

Not:
"The app is broken! I can't access anything!"

Clear communication is key!

User has poor/no internet connection:

Scenario 1: Network error during sign-in

User submits form

API call fails (network error)

Catch block handles error

Show error: "Network error. Please try again."

Form remains functional

Test steps:

  1. Turn off internet
  2. Fill sign-in form
  3. Submit
  4. Should see network error message
  5. Turn on internet
  6. Submit again
  7. Should work

Network error handled!

Scenario 2: Slow connection

User submits form

Button shows "Signing in..."

Request takes 5-10 seconds

Button stays disabled

Finally responds

User signed in (or error shown)

Test steps:

  1. Throttle network in DevTools:
    • Network tab → Throttling → Slow 3G
  2. Submit form
  3. Observe button stays "Signing in..."
  4. Wait for response

Slow connection handled!

Scenario 3: API timeout

const api = axios.create({
  baseURL: 'https://v2.api.noroff.dev',
  timeout: 10000, // 10 seconds
  withCredentials: true,
});

If request takes >10s, Axios throws timeout error.

Debugging Tools

Performance Testing

Count component renders:

function Navbar() {
  const { user } = useAuth();
  const renderCount = useRef(0);
  
  useEffect(() => {
    renderCount.current++;
    console.log(`Navbar rendered ${renderCount.current} times`);
  });

  return <nav>...</nav>;
}

Expected renders:

Initial: 1 render (user = null)
After sign-in: 1 render (user = data)
Total: 2 renders ✅

Too many renders:

Initial: 1 render
After sign-in: 1 render
Random: 1 render (why?)
Random: 1 render (why?)
Total: 4 renders ❌

Causes:

// ❌ New object every render
const value = { user, token, signIn: async () => {} };

// ✅ Memoize function
const signIn = useCallback(async (email, password) => {
  // ...
}, []);

const value = useMemo(
  () => ({ user, token, signIn }),
  [user, token, signIn]
);

Measure API performance:

const signIn = async (email, password) => {
  const startTime = performance.now();
  
  try {
    const response = await api.post('/auth/login', { email, password });
    
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    console.log(`Sign-in took ${duration.toFixed(2)}ms`);
    
    // ...
  } catch (error) {
    // ...
  }
};

Expected timings:

Fast connection: 100-500ms ✅
Normal connection: 500-2000ms ✅
Slow connection: 2000-5000ms ⚠️
Timeout: >10000ms ❌

Optimization tips:

// 1. Add timeout
const api = axios.create({
  timeout: 10000, // 10 seconds
});

// 2. Add retry logic
const signIn = async (email, password, retries = 3) => {
  try {
    return await api.post('/auth/login', { email, password });
  } catch (error) {
    if (retries > 0 && error.code === 'ECONNABORTED') {
      console.log(`Retrying... (${retries} left)`);
      return signIn(email, password, retries - 1);
    }
    throw error;
  }
};

Check for memory leaks:

// ❌ Potential leak: Missing cleanup
useEffect(() => {
  const interval = setInterval(() => {
    checkTokenExpiry();
  }, 60000);
  
  // Missing: return () => clearInterval(interval);
}, []);

// ✅ Proper cleanup
useEffect(() => {
  const interval = setInterval(() => {
    checkTokenExpiry();
  }, 60000);
  
  return () => clearInterval(interval);
}, []);

Test for leaks:

  1. Open DevTools → Memory tab
  2. Take heap snapshot
  3. Sign in/out 10 times
  4. Take another heap snapshot
  5. Compare: Should be similar size

Common leak sources:

// Event listeners
window.addEventListener('resize', handler);
// Fix: window.removeEventListener('resize', handler);

// Timers
setTimeout(() => {}, 1000);
setInterval(() => {}, 1000);
// Fix: clearTimeout, clearInterval

// Subscriptions
const subscription = api.subscribe();
// Fix: subscription.unsubscribe();

// Async operations
const fetchData = async () => {
  const data = await api.get('/data');
  setState(data); // Component might be unmounted!
};
// Fix: Use AbortController or isMounted flag

Check impact on bundle size:

npm run build

Check output:

dist/assets/index-abc123.js    142.34 kB
dist/assets/vendor-def456.js   456.78 kB

React Hook Form + Zod impact:

Before: ~120 kB
After: ~145 kB (+25 kB)

Worth it for the functionality!

Optimize if needed:

// 1. Code splitting
const SignInForm = lazy(() => import('./SignInForm'));

// 2. Tree shaking
import { z } from 'zod'; // ✅ Imports only what you use

// 3. Dynamic imports
const loadZod = () => import('zod');

What's Next?

In Lesson 11, we'll:

  1. Add Axios request interceptors
  2. Automatically attach token to all requests
  3. Handle token in API calls
  4. Protect API endpoints

✅ Lesson Complete! Authentication flow thoroughly tested and working!

Key Takeaways

  • Test systematically through all user scenarios
  • Use DevTools to inspect state and network
  • Console logging helps debug issues
  • Test edge cases (network errors, expired sessions)
  • Session persistence works via refresh tokens
  • Performance monitoring ensures smooth experience
  • Memory leak prevention with proper cleanup
  • First-time vs returning users have different flows
  • Error messages guide users when things go wrong
  • Loading states provide feedback during async operations