mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
47157e7a55
* feat(editor): add Vim keyboard annotation controls * feat(ui): add optional live Vim HUD * feat(ui): finish Vim HUD experience * feat(ui): promote Vim to dedicated settings panel * feat(ui): make Vim document focus automatic * feat(ui): let Vim HUD hide its key panel * fix(ui): harden Vim selection UX
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import type { SemanticTarget } from './blockTargeting';
|
|
import { createVimHudCommand } from './vimHud';
|
|
import { getVimReticleLabel } from './vimReticle';
|
|
|
|
function target(
|
|
kind: SemanticTarget['kind'],
|
|
label: string,
|
|
): Pick<SemanticTarget, 'kind' | 'label'> {
|
|
return { kind, label };
|
|
}
|
|
|
|
describe('Vim HUD reticle labels', () => {
|
|
test('names semantic targets without leaking their full text', () => {
|
|
expect(getVimReticleLabel(
|
|
{ phase: 'block', targetKey: 'target' },
|
|
target('block', 'heading: "Implementation plan"'),
|
|
null,
|
|
)).toBe('BLOCK · HEADING');
|
|
expect(getVimReticleLabel(
|
|
{ phase: 'inline', targetKey: 'target' },
|
|
target('inline', 'bold: "production-grade"'),
|
|
null,
|
|
)).toBe('INLINE · BOLD');
|
|
});
|
|
|
|
test('describes caret and Visual motions using their handled context', () => {
|
|
expect(getVimReticleLabel(
|
|
{
|
|
phase: 'text',
|
|
targetKey: 'target',
|
|
cursor: { blockId: 'block', textOffset: 0 },
|
|
},
|
|
null,
|
|
createVimHudCommand(1, 'refine', 'l', 'block'),
|
|
)).toBe('CURSOR · INLINE TEXT');
|
|
expect(getVimReticleLabel(
|
|
{
|
|
phase: 'visual',
|
|
targetKey: 'target',
|
|
anchor: { blockId: 'block', textOffset: 0 },
|
|
cursor: { blockId: 'block', textOffset: 4 },
|
|
},
|
|
null,
|
|
createVimHudCommand(2, 'wordEnd', 'e', 'visual'),
|
|
)).toBe('VISUAL · EXACT TOKEN');
|
|
});
|
|
|
|
test('keeps motion compositor-only and disables it for reduced motion', () => {
|
|
const themeCss = readFileSync(resolve(import.meta.dir, '../theme.css'), 'utf8');
|
|
const reticleCss = themeCss.slice(themeCss.indexOf('.vim-target-reticle'));
|
|
expect(reticleCss).toContain('transition: transform');
|
|
expect(reticleCss).not.toMatch(/transition:\s*(top|left|width|height)/);
|
|
expect(reticleCss).toContain('@media (prefers-reduced-motion: reduce)');
|
|
expect(reticleCss).toContain('transition: none');
|
|
});
|
|
});
|