Anmelden
DE
apps/jumphp-ide-remote / ui / js / views / views.test.mjs · commit b81e652
// node ui/js/views/views.test.mjs  (docker run --rm -v $PWD:/w -w /w node:22-alpine node ui/js/views/views.test.mjs)import { readFileSync } from 'node:fs';import assert from 'node:assert/strict';import { fileURLToPath } from 'node:url';import { dirname, join } from 'node:path';const dir = dirname(fileURLToPath(import.meta.url));function load(name) {  const m = { exports: {} };  new Function('module', 'window', readFileSync(join(dir, name), 'utf8'))(m, undefined);  return m.exports;}const claude = load('claude.js'), terminal = load('terminal.js'), files = load('files.js');let n = 0;const test = (name, fn) => { fn(); n++; console.log('ok', name); };const ev = (event, id = 7) => ({ id, event });test('a streamed answer grows live, then the assistant message replaces it', () => {  const st = claude.newState();  claude.applyEvent(st, ev({ type: 'stream_event', event: { type: 'content_block_start', content_block: { type: 'text' } } }));  claude.applyEvent(st, ev({ type: 'stream_event', event: { type: 'content_block_delta', delta: { text: 'Hel' } } }));  claude.applyEvent(st, ev({ type: 'stream_event', event: { type: 'content_block_delta', delta: { text: 'lo' } } }));  assert.equal(st.live.buf, 'Hello'); assert.equal(st.working, true); assert.equal(st.items.length, 0);  assert.equal(claude.applyEvent(st, ev({ type: 'assistant', message: { content: [{ type: 'text', text: 'Hello' }] } })), 'items');  assert.equal(st.live, null); assert.deepEqual(st.items, [{ role: 'assistant', text: 'Hello' }]);});test('thinking, tool use, tool errors', () => {  const st = claude.newState();  claude.applyEvent(st, ev({ type: 'assistant', session_id: 'S1', message: { content: [{ type: 'thinking', thinking: 'hm' }, { type: 'tool_use', name: 'Bash', input: { command: 'ls' } }] } }));  claude.applyEvent(st, ev({ type: 'user', message: { content: [{ type: 'tool_result', is_error: true, content: [{ text: 'boom' }] }] } }));  assert.deepEqual(st.items.map((i) => i.role), ['thinking', 'tool', 'tool-error']);  assert.equal(st.items[2].text, 'boom'); assert.equal(st.session, 'S1'); assert.equal(st.tools, 1);});test('result adds done, stops working; errors and denials', () => {  const st = claude.newState();  claude.applyEvent(st, ev({ type: 'assistant', message: { content: [{ type: 'tool_use', name: 'Read', input: { file_path: 'a' } }] } }));  claude.applyEvent(st, ev({ type: 'result', is_error: true, result: 'oops', duration_ms: 3400, permission_denials: [{ tool_name: 'Bash' }] }));  assert.deepEqual(st.items.map((i) => i.role), ['tool', 'error', 'note', 'done']);  assert.match(st.items[3].text, /Stopped · 3 s · 1 action/); assert.equal(st.working, false);});test('exit with an error shows it', () => {  const st = claude.newState(); st.working = true;  claude.applyExit(st, { id: 1, code: 1, error: 'no claude' });  assert.equal(st.working, false); assert.equal(st.items[0].text, 'no claude');});test('snap replaces the conversation and keeps conv', () => {  const st = claude.newState(); st.items.push({ role: 'user', text: 'x' });  assert.equal(claude.applySnap(st, { panel: 'claude', conv: 'c1', items: [{ role: 'user', text: 'a' }, { role: 'assistant', text: 'b' }] }), 'reset');  assert.equal(st.conv, 'c1'); assert.equal(st.items.length, 2);});test('toolLabel like the IDE panel', () => {  assert.deepEqual(claude.toolLabel('Bash', { command: 'ls -la' }), { label: '$', what: 'ls -la' });  assert.equal(claude.toolLabel('Edit', { file_path: 'src/A.php' }).what, 'src/A.php');  assert.equal(claude.toolLabel('mcp__jumphp-ide__read_file', { path: 'x' }).label, 'read file');  assert.equal(claude.toolLabel('Mystery', { description: 'd' }).label, 'Mystery');});test('terminal base64 decoding (raw bytes, url-safe, no padding)', () => {  assert.deepEqual([...terminal.decode('aGk=')], [104, 105]);  assert.deepEqual([...terminal.decode('aGk')], [104, 105]);  assert.deepEqual([...terminal.decode(Buffer.from([0xff, 0xfe, 0x1b, 0x5b]).toString('base64url'))], [0xff, 0xfe, 0x1b, 0x5b]);  assert.equal(terminal.decode('').length, 0);});test('terminal messages are interpreted', () => {  const b = Buffer.from('\x1b[1mhi').toString('base64');  assert.deepEqual(terminal.interpret('snap', { panel: 'terminal', id: 3, data: b, cols: 80, rows: 24 }).cols, 80);  assert.equal(terminal.interpret('snap', { panel: 'claude', id: 3 }), null);  assert.equal(terminal.interpret('terminal.output', { id: 'a', data: b }).kind, 'output');  assert.equal(terminal.interpret('terminal.exit', { id: 1 }).kind, 'exit');  assert.equal(terminal.interpret('terminal.output', null), null);});test('ctrl turns letters into control codes', () => {  assert.equal(terminal.applyCtrl('c'), '\x03'); assert.equal(terminal.applyCtrl('D'), '\x04'); assert.equal(terminal.applyCtrl('|'), '|');});test('file tree: folders first, nested', () => {  const t = files.buildTree([{ path: 'b.php', size: 1 }, { path: 'src/Z.php', size: 2 }, { path: 'src/a/x.php', size: 3 }, { path: 'README.md', size: 4 }]);  assert.deepEqual(t.children.map((c) => c.name), ['src', 'b.php', 'README.md'].sort((a, b) => 0) && ['src', 'b.php', 'README.md']);  assert.deepEqual(t.children[0].children.map((c) => c.name), ['a', 'Z.php']);  assert.equal(t.children[0].children[0].children[0].path, 'src/a/x.php');});test('lines are cut at the maximum', () => {  const r = files.toLines('a\nb\nc\n', 2); assert.deepEqual(r, { lines: ['a', 'b'], cut: 1 });  assert.deepEqual(files.toLines('x\r\ny', 10).lines, ['x', 'y']);});console.log(n + ' passed');