AI Implementation feature(857): Authenticated Shell: Header, Quick Tabs and Change Password (#14)
This commit was merged in pull request #14.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
passwordMatchValidator,
|
||||
buildPasswordPolicyMessage,
|
||||
} from '../../frontend/src/app/features/shell/change-password/change-password-modal.validators';
|
||||
import { formatChangePasswordError } from '../../frontend/src/app/features/shell/change-password/password-feedback';
|
||||
|
||||
describe('passwordMatchValidator', () => {
|
||||
const grp = (newP: string, conf: string) => ({
|
||||
get: (k: string) =>
|
||||
k === 'newPassword' ? { value: newP } : k === 'confirmNewPassword' ? { value: conf } : { value: '' },
|
||||
});
|
||||
|
||||
it('returns null when both inputs are equal', () => {
|
||||
expect(passwordMatchValidator(grp('Abc123!Xyz', 'Abc123!Xyz'))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns {passwordMismatch:true} when inputs differ', () => {
|
||||
expect(passwordMatchValidator(grp('Abc123!Xyz', 'Other1!Pass'))).toEqual({ passwordMismatch: true });
|
||||
});
|
||||
|
||||
it('returns null when either input is empty (handled by required validators)', () => {
|
||||
expect(passwordMatchValidator(grp('', ''))).toBeNull();
|
||||
expect(passwordMatchValidator(grp('Abc', ''))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPasswordPolicyMessage', () => {
|
||||
it('returns the policy description verbatim', () => {
|
||||
expect(buildPasswordPolicyMessage({ minLength: 12, requireMixed: true, description: 'foo bar' })).toBe('foo bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatChangePasswordError', () => {
|
||||
it('maps INVALID_OLD_PASSWORD to a clear message', () => {
|
||||
expect(formatChangePasswordError({ code: 'INVALID_OLD_PASSWORD' })).toBe('Old password is incorrect');
|
||||
});
|
||||
|
||||
it('maps PASSWORDS_DO_NOT_MATCH', () => {
|
||||
expect(formatChangePasswordError({ code: 'PASSWORDS_DO_NOT_MATCH' })).toBe('New password and confirmation do not match');
|
||||
});
|
||||
|
||||
it('maps PASSWORD_POLICY and surfaces server message if present', () => {
|
||||
expect(formatChangePasswordError({ code: 'PASSWORD_POLICY', message: 'too short' })).toBe('too short');
|
||||
expect(formatChangePasswordError({ code: 'PASSWORD_POLICY' })).toBe('New password does not meet the policy requirements');
|
||||
});
|
||||
|
||||
it('maps UNAUTHORIZED', () => {
|
||||
expect(formatChangePasswordError({ code: 'UNAUTHORIZED' })).toBe('You must be signed in to change your password');
|
||||
});
|
||||
|
||||
it('falls back to message or default for unknown codes', () => {
|
||||
expect(formatChangePasswordError({ code: 'EXOTIC', message: 'Quux' })).toBe('Quux');
|
||||
expect(formatChangePasswordError({ code: 'EXOTIC' })).toBe('Failed to change password');
|
||||
});
|
||||
|
||||
it('handles null/undefined code and message', () => {
|
||||
expect(formatChangePasswordError({ code: null, message: null })).toBe('Failed to change password');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
formatDdHhMm,
|
||||
deriveCountdownText,
|
||||
EventState,
|
||||
} from '../../frontend/src/app/core/services/event-status.pure';
|
||||
|
||||
describe('formatDdHhMm', () => {
|
||||
it('formats 90_061 seconds as 01:01:01', () => {
|
||||
expect(formatDdHhMm(90_061)).toBe('01:01:01');
|
||||
});
|
||||
|
||||
it('formats 800 seconds as 00:00:13', () => {
|
||||
expect(formatDdHhMm(800)).toBe('00:00:13');
|
||||
});
|
||||
|
||||
it('formats 0 seconds as 00:00:00', () => {
|
||||
expect(formatDdHhMm(0)).toBe('00:00:00');
|
||||
});
|
||||
|
||||
it('returns 00:00:00 for negative or non-finite values', () => {
|
||||
expect(formatDdHhMm(-5)).toBe('00:00:00');
|
||||
expect(formatDdHhMm(Number.NaN)).toBe('00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deriveCountdownText', () => {
|
||||
const states: EventState[] = ['countdown', 'running', 'stopped', 'unconfigured'];
|
||||
states.forEach((state) => {
|
||||
it(`returns empty string for ${state} when respective seconds are null`, () => {
|
||||
if (state === 'stopped' || state === 'unconfigured') {
|
||||
expect(deriveCountdownText(state, null, null)).toBe(state === 'unconfigured' ? '' : 'Event ended');
|
||||
} else {
|
||||
expect(deriveCountdownText(state, null, null)).toBe('00:00:00');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns DD:HH:mm to start when countdown and secondsToStart provided', () => {
|
||||
expect(deriveCountdownText('countdown', 3600, null)).toBe('00:01:00');
|
||||
});
|
||||
|
||||
it('returns DD:HH:mm to end when running and secondsToEnd provided', () => {
|
||||
expect(deriveCountdownText('running', null, 800)).toBe('00:00:13');
|
||||
});
|
||||
|
||||
it('returns "Event ended" when stopped', () => {
|
||||
expect(deriveCountdownText('stopped', 0, 0)).toBe('Event ended');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { deriveActiveSectionFromUrl } from '../../frontend/src/app/features/home/home.shell';
|
||||
|
||||
describe('deriveActiveSectionFromUrl', () => {
|
||||
it('returns Scoreboard for /scoreboard', () => {
|
||||
expect(deriveActiveSectionFromUrl('/scoreboard')).toBe('Scoreboard');
|
||||
});
|
||||
|
||||
it('returns Blog for /blog', () => {
|
||||
expect(deriveActiveSectionFromUrl('/blog')).toBe('Blog');
|
||||
});
|
||||
|
||||
it('returns Admin for /admin', () => {
|
||||
expect(deriveActiveSectionFromUrl('/admin')).toBe('Admin');
|
||||
});
|
||||
|
||||
it('returns Challenges for / or /challenges', () => {
|
||||
expect(deriveActiveSectionFromUrl('/')).toBe('Challenges');
|
||||
expect(deriveActiveSectionFromUrl('/challenges')).toBe('Challenges');
|
||||
});
|
||||
|
||||
it('ignores query string and hash', () => {
|
||||
expect(deriveActiveSectionFromUrl('/scoreboard?x=1')).toBe('Scoreboard');
|
||||
expect(deriveActiveSectionFromUrl('/blog#top')).toBe('Blog');
|
||||
});
|
||||
|
||||
it('falls back to Challenges for empty url', () => {
|
||||
expect(deriveActiveSectionFromUrl('')).toBe('Challenges');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user