Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
197 lines
6.2 KiB
JavaScript
197 lines
6.2 KiB
JavaScript
import { describe, it, afterEach, expect, $, click, pressKey, sleep, closeAll, csrf } from './runner.js?v=4';
|
|
import { state } from '../../js/modules/state.js';
|
|
|
|
function termToggle() {
|
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'T', shiftKey: true, bubbles: true }));
|
|
}
|
|
|
|
function termExec(cmd) {
|
|
const input = document.getElementById('hterm-input');
|
|
if (!input) return;
|
|
input.value = cmd;
|
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
}
|
|
|
|
function termOutput() {
|
|
return document.getElementById('hterm-output')?.textContent || '';
|
|
}
|
|
|
|
async function makeCard(title, body = '') {
|
|
return fetch('api/cards', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() },
|
|
body: JSON.stringify({ title, body, color: '#38bdf8' }),
|
|
}).then(r => r.json());
|
|
}
|
|
|
|
describe('Terminal: Toggle', () => {
|
|
afterEach(async () => {
|
|
document.getElementById('hterm')?.classList.add('hidden');
|
|
await closeAll();
|
|
});
|
|
|
|
it('panel exists but is hidden', () => {
|
|
const panel = document.getElementById('hterm');
|
|
expect(panel).toExist();
|
|
expect(panel.classList.contains('hidden')).toBe(true);
|
|
});
|
|
|
|
it('Shift+T opens the terminal', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
expect(document.getElementById('hterm').classList.contains('hidden')).toBe(false);
|
|
});
|
|
|
|
it('Shift+T again closes it', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termToggle();
|
|
await sleep(50);
|
|
expect(document.getElementById('hterm').classList.contains('hidden')).toBe(true);
|
|
});
|
|
|
|
it('ESC closes the terminal', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
const input = document.getElementById('hterm-input');
|
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
await sleep(50);
|
|
expect(document.getElementById('hterm').classList.contains('hidden')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Terminal: Commands', () => {
|
|
afterEach(async () => {
|
|
document.getElementById('hterm')?.classList.add('hidden');
|
|
document.getElementById('hterm-output').innerHTML = '';
|
|
await closeAll();
|
|
});
|
|
|
|
it('help lists commands', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('help');
|
|
await sleep(50);
|
|
const out = termOutput();
|
|
expect(out).toContain('help');
|
|
expect(out).toContain('new');
|
|
expect(out).toContain('search');
|
|
});
|
|
|
|
it('count shows numbers', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('count');
|
|
await sleep(50);
|
|
expect(termOutput()).toContain('cards');
|
|
expect(termOutput()).toContain('buckets');
|
|
});
|
|
|
|
it('version shows version string', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('version');
|
|
await sleep(50);
|
|
expect(termOutput()).toContain('v');
|
|
});
|
|
|
|
it('unknown command shows error', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('xyznonexistent');
|
|
await sleep(50);
|
|
expect(termOutput()).toContain('unknown command');
|
|
});
|
|
|
|
it('tags lists existing tags', async () => {
|
|
await makeCard('__term_tag_test__', '#alpha #beta');
|
|
await window._reloadAll?.();
|
|
await sleep(200);
|
|
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('tags');
|
|
await sleep(50);
|
|
expect(termOutput()).toContain('#alpha');
|
|
expect(termOutput()).toContain('#beta');
|
|
});
|
|
|
|
it('find matches cards by title', async () => {
|
|
await makeCard('__term_find_me__', 'findable body');
|
|
await window._reloadAll?.();
|
|
await sleep(200);
|
|
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('find __term_find_me__');
|
|
await sleep(50);
|
|
expect(termOutput()).toContain('__term_find_me__');
|
|
});
|
|
|
|
it('go switches to bucket', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('go all');
|
|
await sleep(100);
|
|
expect(String(state.activeBucket)).toBe('all');
|
|
});
|
|
|
|
it('new creates a card', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('new __term_new_card__');
|
|
// termExec fires off an async API call + full reload; poll rather
|
|
// than sleeping a fixed amount so slow machines don't flake.
|
|
const start = Date.now();
|
|
while (!termOutput().includes('created') && Date.now() - start < 3000) {
|
|
await sleep(50);
|
|
}
|
|
expect(termOutput()).toContain('created');
|
|
const cards = await fetch('api/cards').then(r => r.json());
|
|
expect(cards.some(c => c.title === '__term_new_card__')).toBe(true);
|
|
});
|
|
|
|
it('clear empties output', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
termExec('help');
|
|
await sleep(50);
|
|
expect(termOutput().length).toBeGreaterThan(10);
|
|
termExec('clear');
|
|
await sleep(50);
|
|
expect(document.getElementById('hterm-output').children.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Terminal: Tab Completion', () => {
|
|
afterEach(async () => {
|
|
document.getElementById('hterm')?.classList.add('hidden');
|
|
document.getElementById('hterm-output').innerHTML = '';
|
|
await closeAll();
|
|
});
|
|
|
|
it('Tab completes unique command', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
const input = document.getElementById('hterm-input');
|
|
input.value = 'vers';
|
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
await sleep(50);
|
|
expect(input.value).toBe('version ');
|
|
});
|
|
|
|
it('Tab shows multiple matches', async () => {
|
|
termToggle();
|
|
await sleep(50);
|
|
const input = document.getElementById('hterm-input');
|
|
input.value = 'c';
|
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
await sleep(50);
|
|
const out = termOutput();
|
|
expect(out).toContain('clear');
|
|
expect(out).toContain('count');
|
|
expect(out).toContain('calendar');
|
|
expect(out).toContain('color');
|
|
});
|
|
});
|