hub-dashboard-ia-mobile.test.mjs
156 lines 6.5 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 12 days ago
1 /**
2 * Seven-tier contracts for HUB-DASH-IA-d (mobile bottom nav + How-to copy sync).
3 * Ground truth: docs/reviews/2026-07-29-hub-dashboard-ia.md (expert item 20 + copy/BV).
4 */
5 import { describe, it } from 'node:test';
6 import assert from 'node:assert/strict';
7 import fs from 'node:fs';
8 import path from 'node:path';
9 import { fileURLToPath } from 'node:url';
10 import {
11 hubMobileBottomNavOrder,
12 hubMobileMoreActions,
13 hubPrimaryRailOrder,
14 hubCriticalDataTabs,
15 clampProposedBadgeCount,
16 formatProposedBadgeText,
17 shouldPulseReviewBadge,
18 hubShellLabelForTab,
19 } from '../web/hub/hub-shell-ia.mjs';
20
21 const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
22 const hubHtml = fs.readFileSync(path.join(root, 'web/hub/index.html'), 'utf8');
23 const hubJs = fs.readFileSync(path.join(root, 'web/hub/hub.js'), 'utf8');
24 const hubCss = fs.readFileSync(path.join(root, 'web/hub/hub.css'), 'utf8');
25 const onboarding = fs.readFileSync(path.join(root, 'web/hub/onboarding-wizard.mjs'), 'utf8');
26
27 describe('HUB-DASH-IA-d unit', () => {
28 it('mobile bottom nav order is Vault | Review | History | More', () => {
29 assert.deepEqual(hubMobileBottomNavOrder(), ['Vault', 'Review', 'History', 'More']);
30 assert.deepEqual(hubPrimaryRailOrder(), ['Vault', 'Review', 'History']);
31 assert.equal(hubShellLabelForTab('suggested'), 'Review');
32 });
33
34 it('More sheet lists Import/Connect/Settings/Help (Insights relocated for mobile)', () => {
35 const more = hubMobileMoreActions();
36 assert.ok(more.includes('Import'));
37 assert.ok(more.includes('Connect'));
38 assert.ok(more.includes('Settings'));
39 assert.ok(more.includes('Help'));
40 assert.ok(more.includes('Insights'));
41 assert.deepEqual(more, ['Insights', 'Import', 'Connect', 'Settings', 'Help']);
42 });
43 });
44
45 describe('HUB-DASH-IA-d integration (source wiring)', () => {
46 it('bottom Review uses data-tab=suggested; History opens History mode; More actions share secondary runner', () => {
47 assert.match(hubHtml, /id="hub-bottom-review"[^>]*data-tab="suggested"/);
48 assert.match(hubHtml, /id="hub-bottom-vault"[^>]*data-tab="notes"/);
49 assert.match(hubHtml, /id="hub-bottom-history"[^>]*data-hub-mode="history"/);
50 assert.match(hubJs, /hub-bottom-history[\s\S]{0,200}openHistoryMode\s*\(/);
51 assert.match(hubJs, /function\s+runHubSecondaryAction\s*\(/);
52 assert.match(hubJs, /data-hub-more-action[\s\S]{0,300}runHubSecondaryAction/);
53 assert.match(
54 hubJs,
55 /key\s*===\s*['"]connect['"][\s\S]{0,200}openSettingsIntegrationsTab\s*\(/,
56 );
57 });
58
59 it('badge updates include hub-bottom-review-badge with rail/header', () => {
60 assert.match(
61 hubJs,
62 /\[['"]hub-review-badge['"],\s*['"]hub-header-review-badge['"],\s*['"]hub-bottom-review-badge['"]\]/,
63 );
64 assert.match(hubHtml, /id="hub-bottom-review-badge"/);
65 });
66 });
67
68 describe('HUB-DASH-IA-d e2e static contract', () => {
69 it('bottom nav markup order Vault → Review → History → More', () => {
70 const nav = hubHtml.match(/id="hub-bottom-nav"[\s\S]*?<\/nav>/);
71 assert.ok(nav, 'hub-bottom-nav present');
72 const chunk = nav[0];
73 const vault = chunk.indexOf('>Vault<');
74 const review = chunk.indexOf('>Review');
75 const history = chunk.indexOf('>History<');
76 const more = chunk.indexOf('>More<');
77 assert.ok(vault >= 0 && review > vault && history > review && more > history);
78 });
79
80 it('More sheet has Import, Connect, Settings, Help actions', () => {
81 assert.match(hubHtml, /id="hub-more-sheet"/);
82 for (const action of ['import', 'connect', 'settings', 'help']) {
83 assert.match(hubHtml, new RegExp('data-hub-more-action="' + action + '"'));
84 }
85 });
86
87 it('How-to / onboarding copy uses Review glossary and History segments (no Suggested queue label)', () => {
88 assert.match(onboarding, /Proposals and the Review queue/);
89 assert.match(onboarding, /mobile bottom nav/);
90 assert.match(hubHtml, /History → Discarded/);
91 assert.match(hubHtml, /History → Activity/);
92 assert.match(hubHtml, /Vault browse \+ Insights/);
93 assert.doesNotMatch(hubHtml, /Discarded tab:/);
94 assert.doesNotMatch(hubHtml, /Activity tab:/);
95 assert.doesNotMatch(hubHtml, />Suggested</);
96 assert.doesNotMatch(onboarding, /Suggested queue/);
97 assert.doesNotMatch(onboarding, /the Suggested/);
98 });
99 });
100
101 describe('HUB-DASH-IA-d stress', () => {
102 it('bottom badge helpers safe for proposed count 0–100', () => {
103 for (let i = 0; i <= 100; i++) {
104 assert.equal(clampProposedBadgeCount(i), i);
105 const t = formatProposedBadgeText(i);
106 if (i === 0) assert.equal(t, '');
107 else assert.equal(t, String(i));
108 }
109 assert.equal(shouldPulseReviewBadge(0, 1), true);
110 });
111 });
112
113 describe('HUB-DASH-IA-d data-integrity', () => {
114 it('critical data-tab values unchanged; bottom nav does not rename hooks', () => {
115 assert.deepEqual(hubCriticalDataTabs(), ['notes', 'suggested', 'activity', 'problem']);
116 for (const tab of hubCriticalDataTabs()) {
117 assert.match(hubHtml, new RegExp('data-tab="' + tab + '"'));
118 assert.match(hubHtml, new RegExp('id="tab-' + tab + '"'));
119 }
120 for (const id of [
121 'btn-header-suggested',
122 'proposals-suggested',
123 'btn-import',
124 'btn-settings',
125 'btn-how-to-use',
126 'modal-import',
127 'modal-settings',
128 'modal-how-to-use',
129 'modal-onboarding',
130 'hub-rail-connect',
131 'hub-bottom-nav',
132 ]) {
133 assert.match(hubHtml, new RegExp('id="' + id + '"'));
134 }
135 });
136 });
137
138 describe('HUB-DASH-IA-d performance (mobile chrome disclosure)', () => {
139 it('CSS hides left rail and shows fixed bottom nav at ≤768px', () => {
140 assert.match(hubCss, /@media\s*\(\s*max-width:\s*768px\s*\)/);
141 assert.match(hubCss, /\.hub-rail\s*\{\s*display:\s*none/);
142 assert.match(hubCss, /\.hub-bottom-nav\s*\{[^}]*display:\s*flex/s);
143 assert.match(hubCss, /\.hub-bottom-nav\s*\{[^}]*position:\s*fixed/s);
144 assert.match(hubCss, /\.hub-bottom-nav\s*\{\s*display:\s*none/);
145 });
146 });
147
148 describe('HUB-DASH-IA-d security', () => {
149 it('More actions use text labels; badge textContent; approve/discard contracts unchanged', () => {
150 assert.match(hubJs, /badge\.textContent\s*=/);
151 assert.doesNotMatch(hubJs, /hub-bottom-review-badge[\s\S]{0,80}innerHTML\s*=/);
152 assert.doesNotMatch(hubJs, /data-hub-more-action[\s\S]{0,120}innerHTML\s*=/);
153 assert.match(hubJs, /\/api\/v1\/proposals\/['"]\s*\+\s*encodeURIComponent\(id\)\s*\+\s*['"]\/approve/);
154 assert.match(hubJs, /\/api\/v1\/proposals\/['"]\s*\+\s*encodeURIComponent\(id\)\s*\+\s*['"]\/discard/);
155 });
156 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 12 days ago