This commit is contained in:
Philippe Torrel
2026-08-24 11:56:41 +02:00
parent a5e74fd729
commit 821c839574
71 changed files with 19772 additions and 248 deletions

View File

@@ -0,0 +1,125 @@
import { it, expect, describe, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import AccountInfo from '../../src/steps/AccountInfo';
import { validate } from '../../src/utils/validation';
describe('AccountInfo Component', () => {
afterEach(cleanup);
const step = 1;
it('renders all form fields', () => {
render(
<AccountInfo
formData={{ fullName: '', email: '', password: '' }}
handleChange={() => {}}
errors={{}}
/>
);
expect(screen.getByLabelText(/Full Name/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Password/i)).toBeInTheDocument();
});
it('displays validation errors for invalid data', () => {
const formData = { fullName: '', email: '', password: '' };
const errors = validate(formData, step);
render(
<AccountInfo
formData={formData}
handleChange={() => {}}
errors={errors}
/>
);
expect(screen.getByText(/Full Name is required/i)).toBeInTheDocument();
expect(screen.getByText(/Email is required/i)).toBeInTheDocument();
expect(screen.getByText(/Password is required/i)).toBeInTheDocument();
});
it('displays no validation errors for valid data', () => {
const formData = {
fullName: 'John Doe',
email: 'john.doe@example.com',
password: 'securepassword',
};
const errors = validate(formData, step);
render(
<AccountInfo
formData={formData}
handleChange={() => {}}
errors={errors}
/>
);
expect(screen.queryByText(/is required/i)).not.toBeInTheDocument();
expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument();
expect(
screen.queryByText(/must be at least 6 characters/i)
).not.toBeInTheDocument();
});
it('calls handleChange and updates error messages dynamically', async () => {
let formData = { fullName: '', email: '', password: '' };
let errors = validate(formData, step);
const handleChangeTest = (field, value) => {
formData = { ...formData, [field]: value };
errors = validate(formData, step);
};
const { rerender } = render(
<AccountInfo
formData={formData}
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
errors={errors}
/>
);
const fullNameInput = screen.getByLabelText(/Full Name/i);
await userEvent.type(fullNameInput, 'John');
rerender(
<AccountInfo
formData={formData}
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
errors={errors}
/>
);
expect(
screen.getByText(/Full Name must contain at least two words/i)
).toBeInTheDocument();
});
it('retains user input in form fields', () => {
const formData = {
fullName: 'Jane Doe',
email: 'jane.doe@example.com',
password: 'securepassword',
};
const errors = validate(formData, step);
render(
<AccountInfo
formData={formData}
handleChange={() => {}}
errors={errors}
/>
);
expect(screen.getByLabelText(/Full Name/i)).toHaveValue('Jane Doe');
expect(screen.getByLabelText(/Email/i)).toHaveValue('jane.doe@example.com');
expect(screen.getByLabelText(/Password/i)).toHaveValue('securepassword');
});
});

View File

@@ -0,0 +1,114 @@
import { it, expect, describe, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import ActivityInfo from '../../src/steps/ActivityInfo';
import { validate } from '../../src/utils/validation';
describe('ActivityInfo Component', () => {
afterEach(cleanup);
const step = 2;
it('renders all form fields', () => {
render(<ActivityInfo handleChange={() => {}} errors={{}} />);
expect(
screen.getByLabelText(/How often do you exercise\?/i)
).toBeInTheDocument();
expect(
screen.getByLabelText(
/How would you describe your average energy level\?/i
)
).toBeInTheDocument();
});
it('displays validation errors for invalid data', () => {
const formData = {
fullName: 'John Doe',
email: 'john@example.com',
password: 'password123',
activityLevel: '',
energyLevel: '',
goals: [],
timeCommitment: '15',
};
const errors = validate(formData, step);
render(<ActivityInfo handleChange={() => {}} errors={errors} />);
expect(screen.getByText(/Activity Level is required/i)).toBeInTheDocument();
expect(screen.getByText(/Energy Level is required/i)).toBeInTheDocument();
});
it('displays no validation errors for valid data', () => {
const formData = {
fullName: 'John Doe',
email: 'john@example.com',
password: 'password123',
activityLevel: 'moderate',
energyLevel: 'high',
goals: [],
timeCommitment: '15',
};
const errors = validate(formData, step);
render(<ActivityInfo handleChange={() => {}} errors={errors} />);
expect(screen.queryByText(/is required/i)).not.toBeInTheDocument();
});
it('calls handleChange and updates error messages dynamically', async () => {
let formData = {
fullName: 'John Doe',
email: 'john@example.com',
password: 'password123',
activityLevel: '',
energyLevel: '',
goals: [],
timeCommitment: '15',
};
let errors = validate(formData, step);
const handleChangeTest = (field, value) => {
formData = { ...formData, [field]: value };
errors = validate(formData, step);
};
const { rerender } = render(
<ActivityInfo
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
errors={errors}
/>
);
const activitySelect = screen.getByLabelText(
/How often do you exercise\?/i
);
const energySelect = screen.getByLabelText(
/How would you describe your average energy level\?/i
);
await userEvent.selectOptions(activitySelect, '3+ times a week');
await userEvent.selectOptions(energySelect, 'medium');
rerender(
<ActivityInfo
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
errors={errors}
/>
);
expect(
screen.queryByText(/Activity Level is required/i)
).not.toBeInTheDocument();
expect(
screen.queryByText(/Energy Level is required/i)
).not.toBeInTheDocument();
});
});

View File

@@ -0,0 +1,173 @@
import { it, expect, describe, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import GoalSetting from '../../src/steps/GoalSetting';
import { validate } from '../../src/utils/validation';
describe('GoalSetting Component', () => {
afterEach(cleanup);
const step = 3;
it('renders all form fields', () => {
render(
<GoalSetting
formData={{ goals: [], timeCommitment: '' }}
handleChange={() => {}}
handleCheckboxChange={() => {}}
errors={{}}
/>
);
// Use getByRole to target the fieldset for "What are your goals?"
expect(
screen.getByRole('group', { name: /What are your goals\?/i })
).toBeInTheDocument();
expect(
screen.getByLabelText(/How much time can you commit each day\?/i)
).toBeInTheDocument();
});
it('displays validation errors for empty fields', () => {
const formData = {
goals: [],
timeCommitment: '',
};
const errors = validate(formData, step);
render(
<GoalSetting
formData={formData}
handleChange={() => {}}
handleCheckboxChange={() => {}}
errors={errors}
/>
);
expect(
screen.getByText(/At least one goal is required/i)
).toBeInTheDocument();
expect(
screen.getByText(/Time Commitment is required/i)
).toBeInTheDocument();
});
it('displays no validation errors for valid data', () => {
const formData = {
goals: ['weightLoss', 'muscleGain'],
timeCommitment: '30',
};
const errors = validate(formData, step);
render(
<GoalSetting
formData={formData}
handleChange={() => {}}
handleCheckboxChange={() => {}}
errors={errors}
/>
);
expect(screen.queryByText(/is required/i)).not.toBeInTheDocument();
});
it('handles checkbox interactions and updates goals dynamically', async () => {
let formData = { goals: [], timeCommitment: '' };
let errors = validate(formData, step);
const handleCheckboxChangeTest = (field, id, isChecked) => {
const updatedGoals = isChecked
? [...formData[field], id]
: formData[field].filter((goal) => goal !== id);
formData = { ...formData, [field]: updatedGoals };
errors = validate(formData, step);
};
const { rerender } = render(
<GoalSetting
formData={formData}
handleChange={() => {}}
handleCheckboxChange={handleCheckboxChangeTest}
errors={errors}
/>
);
const weightLossCheckbox = screen.getByLabelText(/Weight Loss/i);
const muscleGainCheckbox = screen.getByLabelText(/Muscle Gain/i);
await userEvent.click(weightLossCheckbox);
rerender(
<GoalSetting
formData={formData}
handleChange={() => {}}
handleCheckboxChange={handleCheckboxChangeTest}
errors={errors}
/>
);
expect(weightLossCheckbox).toBeChecked();
expect(
screen.queryByText(/At least one goal is required/i)
).not.toBeInTheDocument();
await userEvent.click(muscleGainCheckbox);
rerender(
<GoalSetting
formData={formData}
handleChange={() => {}}
handleCheckboxChange={handleCheckboxChangeTest}
errors={errors}
/>
);
expect(muscleGainCheckbox).toBeChecked();
expect(formData.goals).toContain('weightLoss');
expect(formData.goals).toContain('muscleGain');
});
it('handles select interactions and updates time commitment dynamically', async () => {
let formData = { goals: ['weightLoss'], timeCommitment: '' };
let errors = validate(formData, step);
const handleChangeTest = (field, value) => {
formData = { ...formData, [field]: value };
errors = validate(formData, step);
};
const { rerender } = render(
<GoalSetting
formData={formData}
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
handleCheckboxChange={() => {}}
errors={errors}
/>
);
const timeCommitmentSelect = screen.getByLabelText(
/How much time can you commit each day\?/i
);
await userEvent.selectOptions(timeCommitmentSelect, '45');
rerender(
<GoalSetting
formData={formData}
handleChange={({ target }) =>
handleChangeTest(target.name, target.value)
}
handleCheckboxChange={() => {}}
errors={errors}
/>
);
expect(timeCommitmentSelect).toHaveValue('30');
expect(
screen.queryByText(/Time Commitment is required/i)
).not.toBeInTheDocument();
});
});

View File

@@ -0,0 +1,58 @@
import { describe, it, expect } from 'vitest';
import {
isEmpty,
isValidEmail,
hasMinLength,
hasTwoWords,
} from '../../src/utils/validation';
describe('Utility Functions', () => {
describe('isEmpty', () => {
it('should return true for empty strings', () => {
expect(isEmpty('')).toBe(true);
expect(isEmpty(' ')).toBe(true);
});
it('should return false for non-empty strings', () => {
expect(isEmpty('John')).toBe(false);
expect(isEmpty(' John Doe ')).toBe(false);
});
});
describe('isValidEmail', () => {
it('should validate correct email formats', () => {
expect(isValidEmail('john@example.com')).toBe(true);
expect(isValidEmail('jane.doe@sub.domain.co')).toBe(true);
});
it('should invalidate incorrect email formats', () => {
expect(isValidEmail('johnexample.com')).toBe(false);
expect(isValidEmail('jane@.com')).toBe(false);
expect(isValidEmail('jane@com')).toBe(false);
});
});
describe('hasMinLength', () => {
it('should return true if value meets minimum length', () => {
expect(hasMinLength('password', 6)).toBe(true);
expect(hasMinLength('123456', 6)).toBe(true);
});
it('should return false if value does not meet minimum length', () => {
expect(hasMinLength('pass', 6)).toBe(false);
expect(hasMinLength('123', 6)).toBe(false);
});
});
describe('hasTwoWords', () => {
it('should return true if fullName has at least two words', () => {
expect(hasTwoWords('John Doe')).toBe(true);
expect(hasTwoWords('Jane Mary Doe')).toBe(true);
});
it('should return false if fullName has less than two words', () => {
expect(hasTwoWords('John')).toBe(false);
expect(hasTwoWords(' Jane ')).toBe(false);
});
});
});