@bongos/core 1.19.534 → 1.19.535

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -520,3 +520,156 @@ test('the vouched shapes still become links, exactly as before', () => {
520
520
  const degraded = MD.parseMarkdown('[t](../../CLAUDE.md)\n', MD.makeResolveLinkHref({ baseDir: 'docs/onboarding/' })).html;
521
521
  assert.ok(degraded.includes('<a href="../../CLAUDE.md"'), 'and with no bases a repo path still degrades to a link, as §10 pins above');
522
522
  });
523
+
524
+ // ---- the deck draws its dates (task 1003447, `legibility-series-are-drawn`) --
525
+ //
526
+ // /public/versions has always carried started_at and shipped_at per version, and
527
+ // the deck drew NEITHER: both dates reached the browser and came out as "Sep
528
+ // 2026" on a shipped pill. The SERIES lives in roadmap-timeline.js and
529
+ // tests/hall_kit_charts.mjs runs it against the real fixture; what belongs here
530
+ // is the deck's half — that the section is composed, gated and placed the way
531
+ // the reading rooms compose everything else.
532
+
533
+ test('the deck asks the kit to draw its timeline, and hand-rolls nothing', () => {
534
+ const js = read('roadmap.js');
535
+ assert.match(js, /window\.OTBRoadmapTimeline/, 'the series comes from its own module');
536
+ assert.match(js, /timelineCardHtml\(all, now\)/, 'over the merged versions');
537
+ assert.match(js, /html \+= timelineHtml\(all, Date\.now\(\)\)/, 'and lands in the deck');
538
+ // §6b holds this globally; pinned here because a Gantt is the shape most
539
+ // likely to be reached for as "just a few divs with a left offset".
540
+ for (const f of ['roadmap.js', 'roadmap-timeline.js']) {
541
+ assert.doesNotMatch(read(f), /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/,
542
+ `${f} builds no SVG geometry by hand`);
543
+ assert.doesNotMatch(read(f), /class="[^"]*(?:__|-)(?:fill|bar|track)[^"]*"[^>]*style="width:/,
544
+ `${f} draws no bar by hand`);
545
+ }
546
+ assert.match(read('roadmap-timeline.js'), /k\.barSeriesHtml\(/, 'the spans are the kit\'s bar series');
547
+ });
548
+
549
+ test('a missing timeline module leaves the deck exactly as it was', () => {
550
+ // The section is additive: the module is a <script> that can 404 behind a bad
551
+ // deploy, and when it does the deck must render its hero and its cards rather
552
+ // than an empty band or a thrown boot.
553
+ const js = read('roadmap.js');
554
+ const fn = js.slice(js.indexOf('function timelineHtml'), js.indexOf('function sectionHtml'));
555
+ assert.match(fn, /mod \? mod\.timelineCardHtml/, 'the module is checked before it is called');
556
+ assert.match(fn, /card \? sectionHtml\([\s\S]*?\) : ''/, 'and a null card renders no section at all');
557
+ });
558
+
559
+ // Boot roadmap.js in a stub DOM with the real dom-utils, the real kit and the
560
+ // real timeline module, answer /public/progress + /public/versions with the
561
+ // checked-in fixtures, and hand back what landed in #rm-root.
562
+ //
563
+ // This is the half the source pins and the kit's own RUN test both miss: the
564
+ // GLUE. `mergeVersions()` maps `m.started_at`/`m.shipped_at` onto `start`/`ship`
565
+ // and the timeline reads those names — a rename or a typo on either side leaves
566
+ // every span unparsable, the section renders nothing, and every other test in
567
+ // this file still passes because none of them run the mapping.
568
+ async function bootRoadmap({ progress, versions } = {}) {
569
+ const FIX = path.join(ROOT, 'scripts', 'hall-preview', 'fixtures');
570
+ const fx = (f) => JSON.parse(fs.readFileSync(path.join(FIX, f), 'utf8'));
571
+ const prog = progress || fx('public__progress.json');
572
+ // public__versions.json, not versions.json: the deck calls the PUBLIC route.
573
+ const vers = versions || fx('public__versions.json');
574
+
575
+ const els = new Map();
576
+ const makeEl = () => ({
577
+ dataset: {}, style: {}, textContent: '', hidden: false, _html: '',
578
+ classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } },
579
+ setAttribute() {}, getAttribute() { return null; },
580
+ addEventListener() {}, appendChild() {}, insertAdjacentHTML(_, h) { this._html += h; },
581
+ closest() { return null; }, querySelector() { return null; }, querySelectorAll() { return []; },
582
+ set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; },
583
+ });
584
+ const documentObj = {
585
+ getElementById(id) { if (!els.has(id)) els.set(id, makeEl()); return els.get(id); },
586
+ querySelector(sel) {
587
+ const s = String(sel);
588
+ return s.startsWith('#') ? this.getElementById(s.slice(1)) : makeEl();
589
+ },
590
+ querySelectorAll() { return []; },
591
+ createElement() { return makeEl(); },
592
+ addEventListener() {},
593
+ };
594
+ const client = {
595
+ request: async (_m, urlPath) => {
596
+ if (urlPath.includes('/public/progress')) return { ok: true, status: 200, data: prog };
597
+ if (urlPath.includes('/public/versions')) return { ok: true, status: 200, data: vers };
598
+ // No goals: goalSummary() swallows the failure by design, and the goal
599
+ // layer is not what this test is about.
600
+ return { ok: false, status: 500, data: {} };
601
+ },
602
+ };
603
+ const windowObj = {
604
+ BongosClient: { createClient: () => client },
605
+ __BRANDING__: {}, __MODULES__: { enabled: {} },
606
+ localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
607
+ location: { hash: '', pathname: '/roadmap', search: '', assign() {} },
608
+ addEventListener() {}, matchMedia: () => ({ matches: false, addEventListener() {} }),
609
+ };
610
+ const sandbox = {
611
+ window: windowObj, document: documentObj, location: windowObj.location,
612
+ localStorage: windowObj.localStorage,
613
+ CSS: { escape: (s) => String(s) }, history: { replaceState() {} },
614
+ console: { ...console, error() {}, warn() {} },
615
+ setTimeout, clearTimeout, URLSearchParams, Intl,
616
+ };
617
+ sandbox.globalThis = sandbox;
618
+ vm.createContext(sandbox);
619
+ for (const f of ['dom-utils.js', 'hall-kit.js', 'roadmap-timeline.js', 'roadmap.js']) {
620
+ vm.runInContext(read(f), sandbox, { filename: f });
621
+ }
622
+ for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
623
+ return { html: documentObj.getElementById('rm-root').innerHTML, prog, vers };
624
+ }
625
+
626
+ test('RUN: the deck really draws its timeline — mergeVersions\' field mapping included', async () => {
627
+ const { html, vers } = await bootRoadmap();
628
+ assert.ok(html.length > 0, 'the deck rendered');
629
+ assert.match(html, /class="rm-sec__t">In time</, 'the timeline section is on the page');
630
+ assert.match(html, /class="kit-bars"/, 'and it is the kit drawing it');
631
+ assert.doesNotMatch(html, /NaN|Infinity|undefined/, 'no broken geometry reaches the DOM');
632
+
633
+ // Every version the fixture gives a start date has a bar, labelled by its id.
634
+ // This is the assertion that fails if `start`/`ship` ever drift from
635
+ // `started_at`/`shipped_at`: the mapping breaks, no date parses, and the
636
+ // section quietly disappears.
637
+ const started = vers.versions.filter((v) => v.started_at);
638
+ assert.ok(started.length >= 2, 'the fixture has enough started versions to be a timeline');
639
+ for (const v of started) {
640
+ assert.ok(html.includes(`class="kit-bars__label">${v.id}<`), `${v.id} has a bar`);
641
+ }
642
+ // A version still running says "now" rather than naming a date nothing recorded.
643
+ if (vers.versions.some((v) => v.started_at && !v.shipped_at)) {
644
+ assert.match(html, /→ now</, 'an open span reads "now"');
645
+ }
646
+ // Later versions sit further along the shared axis — the whole point of a
647
+ // timeline, and the thing a plain bar series would NOT show.
648
+ const offsets = [...html.matchAll(/margin-left:(\d+)%/g)].map((m) => Number(m[1]));
649
+ assert.ok(offsets.length >= 1, 'spans are offset, not all flush left');
650
+ assert.deepEqual(offsets, [...offsets].sort((a, b) => a - b), 'offsets grow with start date');
651
+ });
652
+
653
+ test('RUN: a deck whose versions carry no dates renders every other section unharmed', async () => {
654
+ // The degradation that matters: /public/versions is fetched with a
655
+ // `.catch(() => [])`, so a 404 or a stripped payload must cost the timeline
656
+ // and nothing else. Before this task the whole page relied on that silently.
657
+ const { html } = await bootRoadmap({
658
+ versions: { versions: [{ id: 'BONGOS-V1', name: 'x', status: 'building', started_at: null, shipped_at: null }] },
659
+ });
660
+ assert.doesNotMatch(html, /In time</, 'no timeline section');
661
+ assert.match(html, /rm-hero/, 'but the hero still renders');
662
+ assert.doesNotMatch(html, /NaN|undefined/);
663
+ });
664
+
665
+ test('the timeline module is a pure builder, so it is testable and cannot boot itself', () => {
666
+ const src = read('roadmap-timeline.js');
667
+ // The node-require-safe tail (the hall-kit.js / dom-utils.js contract): the
668
+ // ONLY eval-time side effect is the assignment, and only when a window exists.
669
+ assert.match(src, /if \(typeof window !== 'undefined'\) window\.OTBRoadmapTimeline = api;/);
670
+ assert.match(src, /if \(typeof module !== 'undefined' && module\.exports\) module\.exports = api;/);
671
+ // It must not reach for the page: no document, no fetch, no client.
672
+ for (const forbidden of [/\bdocument\./, /\bfetch\(/, /BongosClient/]) {
673
+ assert.doesNotMatch(src, forbidden, `roadmap-timeline.js touches ${forbidden} — it is a builder, not a page`);
674
+ }
675
+ });