const { test } = require('node:test');
const assert = require('node:assert');
const { renderFolderTree, renderExecutionList, renderExecutionDetail, renderN8nPage } = require('../src/n8nView');
test('renderFolderTree renders nothing for an empty tree', () => {
assert.strictEqual(renderFolderTree([]), '');
});
test('renderFolderTree renders a workflow item with its id in a data attribute', () => {
const html = renderFolderTree([{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }]);
assert.ok(html.includes('data-workflow-id="w1"'));
assert.ok(html.includes('ワークフローA'));
});
test('renderFolderTree marks inactive workflows differently from active ones', () => {
const activeHtml = renderFolderTree([{ type: 'workflow', id: 'w1', name: 'A', active: true }]);
const inactiveHtml = renderFolderTree([{ type: 'workflow', id: 'w2', name: 'B', active: false }]);
assert.ok(activeHtml.includes('bg-success'));
assert.ok(inactiveHtml.includes('bg-secondary'));
});
test('renderFolderTree escapes HTML in workflow and folder names', () => {
const html = renderFolderTree([{ type: 'workflow', id: 'w1', name: '', active: true }]);
assert.ok(!html.includes(''));
assert.ok(html.includes('<script>'));
});
test('renderFolderTree nests folder children inside the folder element', () => {
const html = renderFolderTree([
{
type: 'folder',
id: 'f1',
name: 'フォルダA',
children: [{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }],
},
]);
assert.ok(html.includes('フォルダA'));
assert.ok(html.includes('data-workflow-id="w1"'));
});
test('renderExecutionList renders a row per execution with a status badge', () => {
const html = renderExecutionList([
{ id: 'e1', status: 'success', startedAt: '2026-09-19T00:00:00.000Z' },
{ id: 'e2', status: 'error', startedAt: '2026-09-18T00:00:00.000Z' },
]);
assert.ok(html.includes('data-execution-id="e1"'));
assert.ok(html.includes('data-execution-id="e2"'));
assert.ok(html.includes('bg-success'));
assert.ok(html.includes('bg-danger'));
});
test('renderExecutionList shows the started time converted to JST, not raw UTC', () => {
const html = renderExecutionList([{ id: 'e1', status: 'success', startedAt: '2026-09-19T00:00:00.000Z' }]);
assert.ok(html.includes('2026-09-19 09:00:00'));
assert.ok(!html.includes('2026-09-19T00:00:00.000Z'));
});
test('renderExecutionList shows a Japanese label for each status', () => {
const statuses = ['success', 'error', 'running', 'waiting', 'canceled'];
for (const status of statuses) {
const html = renderExecutionList([{ id: 'e1', status, startedAt: '2026-09-19T00:00:00.000Z' }]);
assert.ok(!html.includes(`>${status}<`), `expected status "${status}" to be translated`);
}
});
test('renderExecutionList shows a message when there are no executions', () => {
const html = renderExecutionList([]);
assert.ok(html.includes('実行履歴なし'));
});
test('renderExecutionDetail lists each node result with its status', () => {
const execution = {
id: 'e1',
status: 'error',
data: {
resultData: {
runData: {
'SSH: 対象wav一覧取得': [{ error: undefined }],
'IF: 未登録ファイルあり': [{ error: { message: 'timeout' } }],
},
},
},
};
const html = renderExecutionDetail(execution);
assert.ok(html.includes('SSH: 対象wav一覧取得'));
assert.ok(html.includes('IF: 未登録ファイルあり'));
assert.ok(html.includes('timeout'));
});
test('renderN8nPage embeds the folder tree and a container for execution details', () => {
const tree = [{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }];
const html = renderN8nPage(tree);
assert.ok(html.includes('data-workflow-id="w1"'));
assert.ok(html.includes('id="executionList"'));
assert.ok(html.includes('id="executionDetail"'));
assert.ok(html.includes('/n8n.js'));
});