v0.33.0 cf26365

rail.test.mjs RawHistoryFolder

1// Tests of the side panel keys of static/js/ermite.js (ermite.rail, and j / k
2// inside the panel) on a tiny hand-rolled DOM. Run with
3//   node --test 'internal/adapters/web/jstest/*.test.mjs'
4import assert from 'node:assert/strict';
5import { readFileSync } from 'node:fs';
6import { test } from 'node:test';
7import { el, installDom } from './dom.mjs';
8
9// A page: <main>, and a rail with a hidden input, a closed-details command
10// (not laid out), then the controls a key can reach.
11const doc = installDom();
12const main = el('main', { id: 'main' });
13const hidden = el('input', { attrs: { type: 'hidden' } });
14const folded = el('a', { href: '/x', attrs: { href: '/x' } });
15folded.getClientRects = () => []; // inside a closed <details>
16const markRead = el('button', { cls: 'btn' });
17const ready = el('button', { cls: 'btn primary' });
18const close = el('button', { cls: 'btn danger' });
19const rail = el('aside', { cls: 'rail' }, hidden, folded, markRead, ready, close);
20document.body.append(main, rail);
21let active = document.body;
22Object.defineProperty(doc, 'activeElement', { get: () => active });
23for (const e of [main, markRead, ready, close, folded]) e.focus = () => { active = e; };
24rail.contains = (e) => { for (let n = e; n; n = n.parent) if (n === rail) return true; return false; };
25
26// Only the ermiteRail block of the file is evaluated: the rest needs a page.
27const src = readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8');
28const start = src.indexOf('  window.ermiteRail = ');
29const end = src.indexOf('  })();', start) + '  })();'.length;
30new Function(src.slice(start, end))();
31const R = window.ermiteRail;
32const E = { rail: (into) => (into ? R.enter() : R.leave()), move: (d) => R.step(d) };
33
34test('l focuses the main action of the panel, j / k walk its shown controls, h goes back', () => {
35  active = main;
36  E.rail(true);
37  assert.equal(active, ready, 'the primary button first');
38  E.move(1);
39  assert.equal(active, close);
40  E.move(1);
41  assert.equal(active, close, 'stops at the last control');
42  E.move(-1);
43  E.move(-1);
44  assert.equal(active, markRead, 'the folded link is skipped');
45  E.move(-1);
46  assert.equal(active, markRead, 'stops at the first shown control');
47  E.rail(false);
48  assert.equal(active, main);
49});
50
51test('without a primary action, l takes the first shown control', () => {
52  ready.className = 'btn';
53  active = main;
54  E.rail(true);
55  assert.equal(active, markRead);
56  ready.className = 'btn primary';
57});
58
59test('h outside the panel leaves the focus alone; j / k outside it are not taken', () => {
60  active = main;
61  E.rail(false);
62  assert.equal(active, main);
63  assert.equal(R.step(1), false, 'the row cursor keeps j / k');
64});

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close