/**
* Composed-cell test (#1189 R2 item 3): the grouped row's Observer cell must
* NOT render two adjacent unlabeled "+N +M" tokens.
*
* Before R2: `Name SJC SFO +1 +5` — `+1` was distinct-IATA overflow,
* `+5` was observer-count overflow. On mobile the two unlabeled tokens
* wrap/clip and operators can't tell what either number means. The
* eyeball-count badge `👁 N` in the col-rpt cell already conveys multi-
* reception, so we drop the observer-count `+N` from this composed cell
* and keep `+N` semantics SINGLE — distinct-IATA overflow only.
*
* Strategy: extract just the col-observer template-literal expression
* from buildGroupRowHtml in public/packets.js, evaluate it in a Node
* sandbox with mocked helpers, and assert the output contains exactly
* ONE `+N` token (or zero if all IATAs fit). Mutation test: revert the
* fix → this test must turn red.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');
let passed = 0, failed = 0;
function assert(cond, msg) {
if (cond) { passed++; console.log(' \u2705 ' + msg); }
else { failed++; console.error(' \u274c ' + msg); }
}
const src = fs.readFileSync(path.join(__dirname, 'public/packets.js'), 'utf8');
// ── Extract the composed col-observer cell expression. ──────────────────
// The cell is built inline in buildGroupRowHtml's template literal:
//
${ ... composed expression ... } |
// We capture the expression between the `>` and the closing `` of the
// col-observer cell when the row is a GROUP HEADER (isSingle false branch).
// The expression matches the pattern:
// isSingle ? :
// We want to verify the multi-branch.
const cellRe = /class="col-observer"[^<]*?>\$\{(isSingle\s*\?[\s\S]*?)\}<\/td>/m;
const m = src.match(cellRe);
assert(m != null,
'extracted composed col-observer cell expression from buildGroupRowHtml');
if (m) {
// The matched expression is `isSingle ? : `. Force the
// multi branch by binding isSingle=false in the sandbox and evaluating.
const expr = m[1];
// Sandbox mocks. `groupedObserverIataBadgesHtml` returns a deterministic
// multi-IATA overflow string so the composed cell would historically have
// shown ` +` adjacent to it.
const ctx = {
p: {
observer_count: 6, // would have rendered ' +5'
observer_id: 'obsA',
_children: [], // collapsed view — no children
distinct_iatas: ['SJC', 'SFO', 'OAK'],
},
headerObserverId: 'obsA',
isSingle: false,
truncate(s, n) { return String(s || '').slice(0, n); },
obsNameOnly(_id) { return 'NameA'; },
obsIataBadge(_p) { return ''; },
groupedObserverIataBadgesHtml(_p) {
return 'SJCSFO +1';
},
escapeHtml(s) {
return String(s).replace(/&/g,'&').replace(//g,'>')
.replace(/"/g,'"').replace(/'/g,''');
},
};
vm.createContext(ctx);
// Wrap in template literal so all ${} substitutions resolve.
ctx.__result__ = vm.runInContext('`${' + expr + '}`', ctx);
const cell = ctx.__result__;
// Cell must include the IATA badges from the helper.
assert(cell.includes('SJC') && cell.includes('SFO'),
'composed cell contains the distinct-IATA badges, got: ' + cell);
// CORE assertion: exactly ONE `+N` token in the cell. Before the fix the
// cell would carry `+1 +5` (distinct-IATA overflow + observer-count
// overflow); after the fix only `+1` remains.
const plusTokens = cell.match(/\+\d+/g) || [];
assert(plusTokens.length === 1,
'composed cell carries exactly ONE `+N` token (distinct-IATA only); got ' +
plusTokens.length + ' tokens: ' + JSON.stringify(plusTokens) +
'\n cell=' + cell);
// The `+5` (observer_count - 1 = 5) MUST NOT appear — that would mean
// the observer-count overflow was re-introduced.
assert(!cell.includes('+5'),
'composed cell must NOT contain observer-count overflow `+5`; got: ' + cell);
// And the cell must not contain ` +` adjacent twice (the failure mode is
// `+N +M` with whitespace between two `+` tokens).
assert(!/\+\d+\s+\+\d+/.test(cell),
'composed cell must NOT contain adjacent `+N +M` tokens; got: ' + cell);
// Edge case: even when the helper returns no overflow at all, the cell
// must still NOT append an observer-count `+N`.
const ctx2 = Object.assign({}, ctx);
ctx2.groupedObserverIataBadgesHtml = function () {
return 'SJC';
};
vm.createContext(ctx2);
ctx2.__result__ = vm.runInContext('`${' + expr + '}`', ctx2);
const cell2 = ctx2.__result__;
const plusTokens2 = cell2.match(/\+\d+/g) || [];
assert(plusTokens2.length === 0,
'composed cell with single-IATA helper output has ZERO `+N` tokens; got: ' +
JSON.stringify(plusTokens2) + '\n cell=' + cell2);
}
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`);
process.exit(failed > 0 ? 1 : 0);