feat: Challenges Page and Challenge Solve Modal 1.05
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const sourcePath = resolve(
|
||||
__dirname,
|
||||
'../../frontend/src/app/features/challenges/challenge-card.component.ts',
|
||||
);
|
||||
const templatePath = resolve(
|
||||
__dirname,
|
||||
'../../frontend/src/app/features/challenges/challenge-card.component.ts',
|
||||
);
|
||||
const source = readFileSync(sourcePath, 'utf8');
|
||||
|
||||
describe('ChallengeCardComponent — per-card solved accessibility contract', () => {
|
||||
function extractTemplate(src: string): string {
|
||||
const match = src.match(/template:\s*`([\s\S]*?)`,?\s*\n\s*\}/);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
const template = extractTemplate(source);
|
||||
|
||||
it('wraps the card in a native <button type="button"> so it is keyboard-activatable', () => {
|
||||
expect(template).toMatch(/<button[^>]*type="button"[^>]*>/);
|
||||
expect(template).not.toMatch(/class="card"[^>]*role=/);
|
||||
});
|
||||
|
||||
it('binds the click output on the button root and preserves open.emit semantics', () => {
|
||||
expect(template).toMatch(/\(click\)\s*=\s*"open\.emit\(card\.id\)"/);
|
||||
});
|
||||
|
||||
it('preserves the challenge-card-<uuid> data-testid on the new button root', () => {
|
||||
expect(template).toMatch(/\[attr\.data-testid\]="'challenge-card-' \+ card\.id"/);
|
||||
});
|
||||
|
||||
it('exposes a player-specific solved state on the accessibility tree via aria-label', () => {
|
||||
expect(template).toMatch(
|
||||
/\[attr\.aria-label\]="'Challenge ' \+ card\.name \+ \(card\.solvedByMe \? ', solved' : ', not solved'\)"/,
|
||||
);
|
||||
});
|
||||
|
||||
it('exposes an aria-pressed value derived solely from card.solvedByMe', () => {
|
||||
const matches = template.match(/\[attr\.aria-pressed\]="card\.solvedByMe"/g) ?? [];
|
||||
expect(matches.length).toBe(1);
|
||||
});
|
||||
|
||||
it('keeps the conditional checkmark glyph tied to card.solvedByMe and hidden from assistive tech', () => {
|
||||
expect(template).toMatch(/\*ngIf="card\.solvedByMe"/);
|
||||
expect(template).toMatch(/class="check"/);
|
||||
expect(template).toMatch(/aria-hidden="true"/);
|
||||
});
|
||||
|
||||
it('does not derive solved state from solve count, points, or any global field', () => {
|
||||
expect(template).not.toMatch(/solvedByMe\s*\|\|/);
|
||||
expect(template).not.toMatch(/solvedByMe\s*&&/);
|
||||
expect(template).not.toMatch(/solveCount\s*>\s*0/);
|
||||
expect(template).not.toMatch(/livePoints\s*[<>]=?\s*0/);
|
||||
expect(template).toMatch(/card\.solvedByMe\s*\?\s*'true'\s*:\s*'false'/);
|
||||
expect(template).toMatch(/card\.solvedByMe\s*\?\s*', solved'\s*:\s*', not solved'/);
|
||||
});
|
||||
|
||||
it('still binds the solved CSS class solely to card.solvedByMe for visual feedback', () => {
|
||||
expect(template).toMatch(/\[class\.solved\]="card\.solvedByMe"/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user