Ctrl K
ComunidadGuías…

src/quality-mirror.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { readdirSync, readFileSync } from 'node:fs';
import { join, relative } from 'node:path';

function suiteName(relFile) {
  const stem = relFile.replace(/^test\//, '').replace(/\.test\.mjs$/, '');
  const words = stem.replace(/\//g, ': ').replace(/-/g, ' ');
  return words.charAt(0).toUpperCase() + words.slice(1);
}

export function scanSuites(rootDir) {
  const testDir = join(rootDir, 'test');
  const files = [];
  const walk = (dir) => {
    let entries;
    try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
    for (const e of entries) {
      if (e.isDirectory()) walk(join(dir, e.name));
      else if (e.name.endsWith('.test.mjs')) files.push(join(dir, e.name));
    }
  };
  walk(testDir);

  return files.sort().map((abs) => {
    const file = relative(rootDir, abs).split('\\').join('/');
    const src = readFileSync(abs, 'utf8');
    const cases = [];
    for (const m of src.matchAll(/^[ \t]*test\(\s*(['"`])((?:\\.|(?!\1)[\s\S])*?)\1/gm)) {
      cases.push(m[2].replace(/\\(['"`])/g, '$1'));
    }
    return { name: suiteName(file), file, cases: [...new Set(cases)] };
  }).filter((s) => s.cases.length);
}

export function caseResults(shards) {
  const newest = new Map();
  for (const shard of shards) {
    const held = newest.get(shard.id);
    if (!held || (shard.attempt ?? 1) > (held.attempt ?? 1)) newest.set(shard.id, shard);
  }
  const files = new Map();
  const entry = (file) => files.get(file) ?? files.set(file, { ok: false, runs: new Map() }).get(file);
  for (const shard of newest.values()) {
    for (const s of shard.summaries ?? []) if (s.success) entry(s.file).ok = true;
    for (const c of shard.cases ?? []) {
      const runs = entry(c.file).runs;
      runs.set(c.name, [...(runs.get(c.name) ?? []), c.pass]);
    }
  }
  return new Map([...files].map(([file, { ok, runs }]) => [file, {
    ok,
    cases: new Map([...runs].map(([name, passes]) => [name, passes.every(Boolean) ? 'Passing' : passes.some(Boolean) ? 'Flaky' : 'Failing'])),
  }]));
}

function statusFor(results, file, name) {
  const r = results?.get(file);
  if (!r) return null;
  return r.cases.get(name) ?? (r.ok ? 'Passing' : null);
}

export function planQualityMirror(rows, scanned, results = null) {
  const summary = { suites: scanned.length, createdSuites: 0, createdCases: 0, removedSuites: 0, removedCases: 0, renamedSuites: 0, statusChanges: 0, missingFiles: [] };
  const ops = [];
  const byFile = new Map();
  const doomed = [];
  for (const r of [...rows].sort((a, b) => a.publicId - b.publicId)) {
    if (byFile.has(r.file)) doomed.push(r);
    else byFile.set(r.file, r);
  }
  for (const s of scanned) {
    const row = byFile.get(s.file);
    if (!row) {
      summary.createdSuites += 1;
      summary.createdCases += s.cases.length;
      ops.push({ op: 'createSuite', name: s.name, file: s.file, cases: s.cases.map((name) => ({ name, status: statusFor(results, s.file, name) })) });
      continue;
    }
    if (row.name !== s.name) {
      summary.renamedSuites += 1;
      ops.push({ op: 'rename', id: row.id, name: s.name });
    }
    const have = new Map(row.cases.map((c) => [c.name, c]));
    for (const name of new Set(s.cases)) {
      const status = statusFor(results, s.file, name);
      const held = have.get(name);
      if (!held) {
        summary.createdCases += 1;
        ops.push({ op: 'createCase', suite: row.id, name, status });
      } else if (status && held.status !== status) {
        summary.statusChanges += 1;
        ops.push({ op: 'status', id: held.id, status });
      }
    }
    const want = new Set(s.cases);
    for (const c of row.cases) {
      if (want.has(c.name)) continue;
      summary.removedCases += 1;
      ops.push({ op: 'deleteCase', id: c.id });
    }
  }
  const scannedFiles = new Set(scanned.map((s) => s.file));
  for (const [file, row] of byFile) {
    if (scannedFiles.has(file)) continue;
    summary.missingFiles.push(file);
    doomed.push(row);
  }
  for (const row of doomed) {
    summary.removedSuites += 1;
    summary.removedCases += row.cases.length;
    for (const c of row.cases) ops.push({ op: 'deleteCase', id: c.id });
    ops.push({ op: 'deleteSuite', id: row.id });
  }
  return { ops, summary };
}

export function syncQualityMirror(w, scanned, { dryRun = false, results = null } = {}) {
  const suiteTable = w.getTable('Quality/Suite');
  const caseTable = w.getTable('Quality/Case');
  const fileField = Object.values(suiteTable.fields).find((f) => f.name === 'File');
  const rows = w.listEntities(suiteTable.id).map((r) => ({
    id: r.id,
    publicId: r.publicId,
    name: w.entityName(r),
    file: r.values[fileField.id],
    cases: w.readEntity(r.id).fields.Cases.map((c) => ({ id: c.id, name: c.name, status: w.readEntity(c.id).fields.Status })),
  }));
  const { ops, summary } = planQualityMirror(rows, scanned, results);
  if (dryRun) return summary;
  for (const o of ops) {
    if (o.op === 'createSuite') {
      const row = w.createEntity(suiteTable, { name: o.name, values: { File: o.file } });
      for (const c of o.cases) w.createEntity(caseTable, { name: c.name, values: { Suite: row.id, ...(c.status ? { Status: c.status } : {}) } });
    } else if (o.op === 'createCase') w.createEntity(caseTable, { name: o.name, values: { Suite: o.suite, ...(o.status ? { Status: o.status } : {}) } });
    else if (o.op === 'rename') w.updateEntity(o.id, { Name: o.name });
    else if (o.op === 'status') w.updateEntity(o.id, { Status: o.status });
    else w.deleteEntity(o.id, { hard: true });
  }
  return summary;
}