tahamajs/RR / Docs /visualizer /tests /button_interactions.test.js
tahamajs's picture
download
raw
11.6 kB
/**
* tests/button_interactions.test.js
* =================================
* Comprehensive verification for Visualizer UI Buttons & Controls:
* - HTML audit of all 61 button elements (checking IDs, roles, labels, classes)
* - setupControls event handler wiring for playback, camera, perspective, speeds, LOD, and models
* - Pin module comparator state toggling
* - Color palette legend dynamic DOM generation
* - Keyboard shortcuts modal activation and hotkeys registry
*/
import assert from 'node:assert';
import test, { describe, it } from 'node:test';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
describe('Visualizer UI Buttons & Controls Audit', () => {
const htmlPath = path.resolve(__dirname, '../index.html');
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
it('contains all expected control and navigation buttons in index.html', () => {
const buttonRegex = /<button([^>]*)>(.*?)<\/button>/gis;
const matches = [...htmlContent.matchAll(buttonRegex)];
assert.ok(matches.length >= 60, `Expected at least 60 buttons in index.html, found ${matches.length}`);
const expectedIds = [
'btn-present-mode',
'btn-view-3d',
'btn-view-2d',
'btn-open-learn',
'btn-open-lessons',
'btn-open-model-matrix',
'btn-open-heatmap',
'btn-open-prediction',
'btn-open-compression',
'btn-open-race',
'btn-open-catalog',
'btn-toggle-inspector',
'btn-share',
'btn-record-video',
'btn-keyboard-shortcuts',
'btn-detail-inspect',
'btn-play-pause',
'btn-auto-tour',
'btn-legend-toggle',
'btn-pin-module',
'btn-reset-cam',
'btn-browse-all-modules',
'btn-zoom-out',
'btn-zoom-in',
'btn-close-insp',
'btn-tree-clear',
'btn-close-modal',
'btn-apply-from-doc',
'btn-close-doc-modal'
];
expectedIds.forEach(id => {
assert.ok(htmlContent.includes(`id="${id}"`), `index.html must contain button with id="${id}"`);
});
});
it('contains all mechanism navigation tabs and categories', () => {
assert.ok(htmlContent.includes('class="mechanism-button active"'));
assert.ok(htmlContent.includes('data-model="distilbert"'));
assert.ok(htmlContent.includes('data-model="roberta"'));
assert.ok(htmlContent.includes('data-model="gpt2"'));
assert.ok(htmlContent.includes('data-model="qwen"'));
});
it('contains playback speeds, LOD modes, and timeline scrubber', () => {
assert.ok(htmlContent.includes('id="timeline-scrubber"'));
assert.ok(htmlContent.includes('id="timeline-step"'));
assert.ok(htmlContent.includes('id="timeline-label"'));
assert.ok(htmlContent.includes('class="btn-speed"'));
assert.ok(htmlContent.includes('class="btn-lod"'));
});
it('verifies anti-overlap UI layout structure in index.html and style.css', () => {
// Assert dock-actions wrapper wraps secondary dock buttons
assert.ok(htmlContent.includes('class="dock-actions"'), 'index.html must group dock action buttons in .dock-actions');
const cssPath = path.resolve(__dirname, '../style.css');
const cssContent = fs.readFileSync(cssPath, 'utf-8');
// Assert no unclosed braces / syntax errors around .view-panels
assert.ok(cssContent.includes('.view-panels {'), 'style.css must have valid .view-panels selector');
// Assert body.inspector-open hides compare-card to prevent overlap
assert.ok(cssContent.includes('body.inspector-open .compare-card'), 'style.css must hide .compare-card when inspector is open');
// Assert .mechanism-nav does not overlap cards
assert.ok(cssContent.includes('.mechanism-nav {'), 'style.css must style .mechanism-nav');
assert.ok(cssContent.includes('top: 68px;'), 'mechanism-nav top position must be aligned below topbar');
assert.ok(cssContent.includes('top: 118px;'), 'stage cards top position must be below mechanism nav');
});
});
describe('UI Controls Logic & Mock DOM Binding', () => {
// Lightweight mock DOM environment for Node.js unit tests
function createMockElement(tag = 'div', id = '', classes = []) {
const classList = new Set(classes);
const children = [];
const listeners = {};
const attrs = {};
const style = {};
return {
tagName: tag.toUpperCase(),
id,
children,
style,
classList: {
add: (c) => classList.add(c),
remove: (c) => classList.delete(c),
contains: (c) => classList.has(c),
toggle: (c, force) => {
if (force === undefined) {
if (classList.has(c)) { classList.delete(c); return false; }
else { classList.add(c); return true; }
} else if (force) {
classList.add(c); return true;
} else {
classList.delete(c); return false;
}
}
},
get className() { return [...classList].join(' '); },
set className(val) {
classList.clear();
val.split(/\s+/).filter(Boolean).forEach(c => classList.add(c));
},
getAttribute: (attr) => attrs[attr] || null,
setAttribute: (attr, val) => { attrs[attr] = String(val); },
addEventListener: (evt, cb) => {
if (!listeners[evt]) listeners[evt] = [];
listeners[evt].push(cb);
},
click: function() {
if (listeners['click']) listeners['click'].forEach(cb => cb({ target: this }));
},
trigger: function(evt, eventObj = {}) {
if (listeners[evt]) listeners[evt].forEach(cb => cb({ target: this, ...eventObj }));
},
appendChild: (child) => { children.push(child); return child; },
hasChildNodes: () => children.length > 0,
dataset: {},
innerText: '',
textContent: '',
innerHTML: ''
};
}
it('verifies setupControls correctly wires playback and camera handlers', async () => {
// Setup global mock document
const elementsById = {
'btn-play-pause': createMockElement('button', 'btn-play-pause', ['primary']),
'btn-reset-cam': createMockElement('button', 'btn-reset-cam', ['secondary']),
'btn-view-3d': createMockElement('button', 'btn-view-3d', ['active']),
'btn-view-2d': createMockElement('button', 'btn-view-2d', []),
'btn-toggle-inspector': createMockElement('button', 'btn-toggle-inspector', []),
'btn-close-insp': createMockElement('button', 'btn-close-insp', []),
'inspector': createMockElement('div', 'inspector', ['hidden']),
'color-legend': createMockElement('div', 'color-legend', []),
'btn-legend-toggle': createMockElement('button', 'btn-legend-toggle', []),
'btn-pin-module': createMockElement('button', 'btn-pin-module', []),
'cmp-selected-node': createMockElement('strong', 'cmp-selected-node', [])
};
const originalDoc = global.document;
global.document = {
getElementById: (id) => elementsById[id] || null,
querySelectorAll: (sel) => {
if (sel === '.btn-speed') return [];
if (sel === '.model-tab') return [];
if (sel === '.comp-tab') return [];
if (sel === '.btn-lod') return [];
if (sel === '.mechanism-button') return [];
if (sel === '.modal-overlay') return [];
return [];
},
querySelector: () => null,
createElement: (tag) => createMockElement(tag),
body: createMockElement('body')
};
global.window = {
addEventListener: () => {}
};
const { setupControls } = await import('../src/ui/controls.js');
let playPauseCalled = false;
let resetCamCalled = false;
let viewSwitched = null;
setupControls({
onPlayPause: () => {
playPauseCalled = true;
return true;
},
onResetCamera: () => {
resetCamCalled = true;
},
onSwitchView: (v) => {
viewSwitched = v;
}
});
// Test play/pause
elementsById['btn-play-pause'].click();
assert.strictEqual(playPauseCalled, true);
assert.strictEqual(elementsById['btn-play-pause'].innerText, '⏸ Pause');
assert.ok(elementsById['btn-play-pause'].classList.contains('active'));
// Test reset camera
elementsById['btn-reset-cam'].click();
assert.strictEqual(resetCamCalled, true);
// Test 2D perspective switch
elementsById['btn-view-2d'].click();
assert.strictEqual(viewSwitched, '2d');
assert.ok(elementsById['btn-view-2d'].classList.contains('active'));
assert.strictEqual(elementsById['btn-view-3d'].classList.contains('active'), false);
// Test inspector toggle
elementsById['btn-toggle-inspector'].click();
assert.strictEqual(elementsById['inspector'].classList.contains('hidden'), false);
assert.strictEqual(global.document.body.classList.contains('inspector-open'), true, 'body must receive inspector-open class');
// Test inspector toggle off
elementsById['btn-toggle-inspector'].click();
assert.strictEqual(elementsById['inspector'].classList.contains('hidden'), true);
assert.strictEqual(global.document.body.classList.contains('inspector-open'), false, 'body must remove inspector-open class');
// Restore globals
global.document = originalDoc;
});
it('verifies openKeyboardShortcutsModal creates cheatsheet modal', async () => {
let createdModal = null;
const mockBody = {
appendChild: (child) => { createdModal = child; return child; }
};
const originalDoc = global.document;
global.document = {
getElementById: (id) => (id === 'keyboard-shortcuts-modal' ? createdModal : null),
createElement: (tag) => {
const el = createMockElement(tag);
return el;
},
body: mockBody
};
const { openKeyboardShortcutsModal } = await import('../src/ui/controls.js');
openKeyboardShortcutsModal();
assert.ok(createdModal !== null, 'Modal should be appended to body');
assert.strictEqual(createdModal.id, 'keyboard-shortcuts-modal');
assert.strictEqual(createdModal.classList.contains('hidden'), false);
assert.ok(createdModal.innerHTML.includes('Space'));
assert.ok(createdModal.innerHTML.includes('Play / Pause'));
assert.ok(createdModal.innerHTML.includes('Auto-Fit Camera'));
global.document = originalDoc;
});
});

Xet Storage Details

Size:
11.6 kB
·
Xet hash:
c426ad33da743175fe9b6b49d3333a4721254fb3da86ffdcfbf8621902ce5a8f

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.