* Initial plan * fix: add loading state to AuthContext to prevent premature route resolution Agent-Logs-Url: https://github.com/RMCampos/tasknote/sessions/8b02c4fd-b37e-4d34-8910-6d075bde6b4b Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com> * restore: revert package-lock.json to original state Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com> * fix: add catch before finally in initial auth check useEffect Agent-Logs-Url: https://github.com/RMCampos/tasknote/sessions/8b02c4fd-b37e-4d34-8910-6d075bde6b4b Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com> * test: address test warning missing act --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RMCampos <2219519+RMCampos@users.noreply.github.com> Co-authored-by: Ricardo Campos <ricardompcampos@gmail.com>
291 lines
7.9 KiB
TypeScript
291 lines
7.9 KiB
TypeScript
// AuthProvider.test.tsx
|
|
import React, { useContext, act } from 'react';
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { render, waitFor, cleanup } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import AuthProvider from '../../context/AuthProvider';
|
|
import AuthContext, { AuthContextData } from '../../context/AuthContext';
|
|
import api from '../../api-service/api';
|
|
import { API_TOKEN, USER_DATA } from '../../app-constants/app-constants';
|
|
|
|
// Mock the API service methods.
|
|
vi.mock('../../api-service/api');
|
|
|
|
// Create a helper component to consume AuthContext for testing.
|
|
const ConsumerComponent: React.FC = () => {
|
|
const {
|
|
signed,
|
|
loading,
|
|
user,
|
|
signIn,
|
|
register,
|
|
signOut,
|
|
updateUser,
|
|
checkCurrentAuthUser
|
|
} = useContext<AuthContextData>(AuthContext);
|
|
|
|
return (
|
|
<div>
|
|
<div data-testid="signed">{signed ? 'true' : 'false'}</div>
|
|
<div data-testid="loading">{loading ? 'true' : 'false'}</div>
|
|
<div data-testid="user">{user ? user.name : 'none'}</div>
|
|
<button
|
|
data-testid="signIn"
|
|
onClick={async() => {
|
|
await signIn('test@example.com', 'password123');
|
|
}}
|
|
>
|
|
Sign In
|
|
</button>
|
|
<button
|
|
data-testid="register"
|
|
onClick={() => {
|
|
register('new@example.com', 'password123', 'password123');
|
|
}}
|
|
>
|
|
Register
|
|
</button>
|
|
<button
|
|
data-testid="sign-out-btn"
|
|
onClick={() => {
|
|
signOut();
|
|
}}
|
|
>
|
|
Sign Out
|
|
</button>
|
|
<button
|
|
data-testid="updateUser"
|
|
onClick={() => {
|
|
updateUser({
|
|
userId: 1,
|
|
name: 'Updated User',
|
|
email: 'updated@example.com',
|
|
admin: false,
|
|
createdAt: new Date(),
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
});
|
|
}}
|
|
>
|
|
Update User
|
|
</button>
|
|
<button
|
|
data-testid="checkCurrentAuthUser"
|
|
onClick={() => {
|
|
checkCurrentAuthUser('/some-path');
|
|
}}
|
|
>
|
|
Check Current Auth User
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
describe('AuthProvider', () => {
|
|
// Reset DOM and mocks for each test.
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it('should render the default context values', async () => {
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(getByTestId('signed').textContent).toBe('false');
|
|
expect(getByTestId('user').textContent).toBe('none');
|
|
});
|
|
});
|
|
|
|
it('should set loading to false after initial auth check with no token', async () => {
|
|
// No token in localStorage, so fetchCurrentSession returns immediately.
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() =>
|
|
expect(getByTestId('loading').textContent).toBe('false')
|
|
);
|
|
expect(getByTestId('signed').textContent).toBe('false');
|
|
});
|
|
|
|
it('should set loading to false and signed to true after successful initial auth check', async () => {
|
|
const fakeResponse = {
|
|
token: 'refresh-token',
|
|
userId: '789',
|
|
name: 'Refreshed User',
|
|
email: 'refreshed@example.com',
|
|
admin: false,
|
|
createdAt: new Date().toISOString(),
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
};
|
|
|
|
vi.spyOn(api, 'getJSON').mockResolvedValue(fakeResponse);
|
|
localStorage.setItem(API_TOKEN, 'dummy');
|
|
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() =>
|
|
expect(getByTestId('loading').textContent).toBe('false')
|
|
);
|
|
expect(getByTestId('signed').textContent).toBe('true');
|
|
});
|
|
|
|
it('should sign in a user successfully', async () => {
|
|
// Create a fake sign-in response.
|
|
const fakeResponse = {
|
|
userId: '123',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
admin: false,
|
|
createdAt: new Date(),
|
|
token: 'dummy-token',
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
};
|
|
|
|
vi.spyOn(api, 'postJSON').mockResolvedValue(fakeResponse);
|
|
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await userEvent.click(getByTestId('signIn'));
|
|
|
|
await waitFor(() =>
|
|
expect(getByTestId('signed').textContent).toBe('true')
|
|
);
|
|
expect(getByTestId('user').textContent).toBe('Test User');
|
|
// LocalStorage should have API_TOKEN and USER_DATA set.
|
|
expect(localStorage.getItem(API_TOKEN)).toBe('dummy-token');
|
|
expect(localStorage.getItem(USER_DATA)).not.toBeNull();
|
|
});
|
|
|
|
|
|
|
|
it('should sign out a user', async () => {
|
|
// Pre-populate localStorage to simulate a signed-in state.
|
|
localStorage.setItem(API_TOKEN, 'dummy-token');
|
|
localStorage.setItem(
|
|
USER_DATA,
|
|
JSON.stringify({
|
|
userId: '123',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
admin: false,
|
|
createdAt: new Date(),
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
})
|
|
);
|
|
|
|
const fakeResponse = {
|
|
userId: '123',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
admin: false,
|
|
createdAt: new Date(),
|
|
token: 'dummy-token',
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
};
|
|
|
|
vi.spyOn(api, 'postJSON').mockResolvedValue(fakeResponse);
|
|
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
// Make sure that the context initially renders with the signed user by triggering a signIn.
|
|
await userEvent.click(getByTestId('signIn'));
|
|
|
|
await waitFor(() =>
|
|
expect(getByTestId('signed').textContent).toBe('true')
|
|
);
|
|
|
|
// Now sign out
|
|
await userEvent.click(getByTestId('sign-out-btn'));
|
|
|
|
await waitFor(() =>
|
|
expect(getByTestId('signed').textContent).toBe('false')
|
|
);
|
|
expect(getByTestId('user').textContent).toBe('none');
|
|
// LocalStorage items should be removed.
|
|
expect(localStorage.getItem(API_TOKEN)).toBeNull();
|
|
expect(localStorage.getItem(USER_DATA)).toBeNull();
|
|
});
|
|
|
|
it('should update user in context and localStorage', async () => {
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await userEvent.click(getByTestId('updateUser'));
|
|
|
|
await waitFor(() => {
|
|
expect(getByTestId('user').textContent).toBe('Updated User')
|
|
const savedUser = localStorage.getItem(USER_DATA);
|
|
expect(savedUser).not.toBeNull();
|
|
|
|
if (savedUser) {
|
|
const parsedUser = JSON.parse(savedUser);
|
|
expect(parsedUser.name).toBe('Updated User');
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should call fetchCurrentSession when checking current auth user', async () => {
|
|
const fakeResponse = {
|
|
token: 'refresh-token',
|
|
userId: '789',
|
|
name: 'Refreshed User',
|
|
email: 'refreshed@example.com',
|
|
admin: false,
|
|
createdAt: new Date().toISOString(),
|
|
gravatarImageUrl: 'http://dummyimage.com'
|
|
};
|
|
|
|
vi.spyOn(api, 'getJSON').mockResolvedValue(fakeResponse);
|
|
|
|
// Store API_TOKEN so that fetchCurrentSession runs the refresh logic.
|
|
localStorage.setItem(API_TOKEN, 'dummy');
|
|
|
|
const user = userEvent.setup();
|
|
|
|
let getByTestIdFunction;
|
|
await act(async () => {
|
|
const { getByTestId } = render(
|
|
<AuthProvider>
|
|
<ConsumerComponent />
|
|
</AuthProvider>
|
|
);
|
|
getByTestIdFunction = getByTestId;
|
|
});
|
|
|
|
// Wait for any initial renders to complete
|
|
await waitFor(() => expect(getByTestIdFunction('checkCurrentAuthUser')).toBeDefined());
|
|
|
|
await user.click(getByTestIdFunction('checkCurrentAuthUser'));
|
|
|
|
await waitFor(() =>
|
|
expect(localStorage.getItem(API_TOKEN)).toBe('refresh-token')
|
|
);
|
|
});
|
|
});
|