65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
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"/);
|
|
});
|
|
});
|