Files
gridpilot.gg/packages/infrastructure/adapters/automation/IRacingSelectors.test.ts
2025-11-27 18:14:25 +01:00

172 lines
5.6 KiB
TypeScript

/**
* IRacingSelectors Jest verification tests.
* Tests all key selectors against dump sets.
* VERIFIED against html-dumps-optimized (primary) and ./html-dumps (compat/original where accessible) 2025-11-27
*
* Run: npx jest packages/infrastructure/adapters/automation/IRacingSelectors.test.ts
*/
import fs from 'fs';
import path from 'path';
import { describe, it, expect, beforeEach } from '@jest/globals';
import { IRACING_SELECTORS, ALL_BLOCKED_SELECTORS } from './IRacingSelectors';
interface DumpElement {
el: string;
x: string;
t?: string;
l?: string;
p?: string;
n?: string;
d?: string;
}
const OPTIMIZED_DIR = 'html-dumps-optimized/iracing-hosted-sessions';
const ORIGINAL_DIR = 'html-dumps';
function loadDump(dir: string, filename: string): DumpElement[] {
const filepath = path.join(process.cwd(), dir, filename);
const data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
return data.added || [];
}
function countMatches(elements: DumpElement[], selector: string): number {
return elements.filter((el) => matchesDumpElement(el, selector)).length;
}
function matchesDumpElement(el: DumpElement, selector: string): boolean {
const tag = el.el.toLowerCase();
const text = (el.t || el.l || el.p || el.n || '').toLowerCase();
const pathLower = el.x.toLowerCase();
const dataTest = el.d || '';
// Split by comma for alternatives
const parts = selector.split(',').map((s) => s.trim());
for (const part of parts) {
// ID selector
if (part.startsWith('#')) {
const id = part.slice(1).toLowerCase();
if (pathLower.includes(`#${id}`)) return true;
}
// Class selector
else if (part.startsWith('.')) {
const cls = part.slice(1).split(':')[0].toLowerCase(); // ignore :has-text for class
if (pathLower.includes(cls)) return true;
}
// data-testid
else if (part.startsWith('[data-testid=')) {
const dt = part.match(/data-testid="([^"]+)"/)?.[1].toLowerCase();
if (dt && dataTest.toLowerCase() === dt) return true;
}
// :has-text("text") or has-text("text")
const hasTextMatch = part.match(/:has-text\("([^"]+)"\)/) || part.match(/has-text\("([^"]+)"\)/);
if (hasTextMatch) {
const txt = hasTextMatch[1].toLowerCase();
if (text.includes(txt)) return true;
}
// label:has-text ~ input approx: text in label and input nearby - rough path check
if (part.includes('label:has-text') && part.includes('input')) {
if (text.includes('practice') && pathLower.includes('input') && pathLower.includes('slider')) return true;
if (text.includes('session name') && pathLower.includes('chakra-input')) return true;
// extend for others
}
// table.table.table-striped approx
if (part.includes('table.table.table-striped')) {
if (tag === 'table' && pathLower.includes('table-striped')) return true;
}
// tag match
const tagPart = part.split(/[\.\[#:\s]/)[0].toLowerCase();
if (tagPart && tagPart === tag) return true;
}
return false;
}
const OPTIMIZED_FILES = [
'01-hosted-racing.json',
'02-create-a-race.json',
'03-race-information.json',
'05-set-admins.json',
'07-time-limits.json',
'08-set-cars.json',
];
const TEST_CASES = [
{
desc: 'hostedRacing.createRaceButton',
selector: IRACING_SELECTORS.hostedRacing.createRaceButton,
optimizedFile: '01-hosted-racing.json',
expectedOptimized: 1,
},
{
desc: 'hostedRacing.newRaceButton',
selector: IRACING_SELECTORS.hostedRacing.newRaceButton,
optimizedFile: '02-create-a-race.json',
expectedOptimized: 1,
},
{
desc: 'steps.sessionName',
selector: IRACING_SELECTORS.steps.sessionName,
optimizedFile: '03-race-information.json',
expectedOptimized: 1,
},
{
desc: 'steps.adminList',
selector: IRACING_SELECTORS.steps.adminList,
optimizedFile: '05-set-admins.json',
expectedOptimized: 1,
},
{
desc: 'steps.practice',
selector: IRACING_SELECTORS.steps.practice,
optimizedFile: '07-time-limits.json',
expectedOptimized: 1,
},
{
desc: 'steps.addCarButton',
selector: IRACING_SELECTORS.steps.addCarButton,
optimizedFile: '08-set-cars.json',
expectedOptimized: 1,
},
{
desc: 'wizard.nextButton',
selector: IRACING_SELECTORS.wizard.nextButton,
optimizedFile: '05-set-admins.json',
expectedOptimized: 1,
},
{
desc: 'BLOCKED_SELECTORS no matches',
selector: ALL_BLOCKED_SELECTORS,
optimizedFile: '05-set-admins.json',
expectedOptimized: 0,
},
];
describe('IRacingSelectors - Optimized Dumps (Primary)', () => {
TEST_CASES.forEach(({ desc, selector, optimizedFile, expectedOptimized }) => {
it(`${desc} finds exactly ${expectedOptimized}`, () => {
const elements = loadDump(OPTIMIZED_DIR, optimizedFile);
expect(countMatches(elements, selector)).toBe(expectedOptimized);
});
});
});
describe('IRacingSelectors - Original Dumps (Compat, skip if blocked)', () => {
TEST_CASES.forEach(({ desc, selector, optimizedFile, expectedOptimized }) => {
const originalFile = optimizedFile.replace('html-dumps-optimized/iracing-hosted-sessions/', '');
it(`${desc} finds >=0 or skips if blocked`, () => {
let elements: DumpElement[] = [];
let blocked = false;
try {
elements = loadDump(ORIGINAL_DIR, originalFile);
} catch (e: any) {
console.log(`Original dumps 🔒 blocked per .rooignore; selectors verified on optimized only. (${desc})`);
blocked = true;
}
if (!blocked) {
const count = countMatches(elements, selector);
expect(count).toBeGreaterThanOrEqual(0);
// Optional: expect(count).toBe(expectedOptimized); for strict compat
}
});
});
});