// Comportamento do gerenciador de arquivos: ações, filtros, busca e preview. 'use strict'; const test = require('node:test'); const assert = require('node:assert'); const { createApp } = require('./harness'); function actionButton(row, label) { return [...row.querySelectorAll('.fitem-acts button')].find((b) => b.textContent.trim() === label); } test('a lista mostra os arquivos da pasta selecionada', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); assert.deepStrictEqual(app.listRows().map((r) => r.querySelector('.fitem-name').textContent), ['raiz.txt']); app.treeRowByName('Documentos').click(); await app.settle(); assert.deepStrictEqual(app.listRows().map((r) => r.querySelector('.fitem-name').textContent), ['nota.txt']); }); test('renomear envia o novo nome e mantém o id', async (t) => { const app = createApp({ promptAnswer: 'renomeado.txt' }); t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('raiz.txt'), '✎').click(); await app.settle(); const req = app.lastRequest('PUT', '/api/files/file2/move'); assert.ok(req, 'deve chamar PUT /api/files/{id}/move'); assert.strictEqual(req.url, '/api/files/file2/move'); assert.deepStrictEqual(req.body, { name: 'renomeado.txt' }); }); test('mover pelo botão de pasta escolhe o destino pelo índice informado', async (t) => { const app = createApp({ promptAnswer: '1' }); // 0 = Raiz, 1 = Documentos t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('raiz.txt'), '📂').click(); await app.settle(); const req = app.lastRequest('PUT', '/api/files/file2/move'); assert.ok(req, 'deve chamar PUT /api/files/{id}/move'); assert.deepStrictEqual(req.body, { folder: 'folder1' }); }); test('associar o arquivo à nota atual envia o id da nota', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); // abre a nota para que exista uma aba com id (clicar no item da lista, // nunca no botão de excluir que fica dentro dele) const noteItem = [...app.document.querySelectorAll('#notes-list li')] .find((li) => li.querySelector('.n-title')); assert.ok(noteItem, 'a nota deve aparecer na lista lateral'); noteItem.click(); await app.settle(); const row = app.rowByName('raiz.txt'); const pin = actionButton(row, '📌'); assert.ok(pin, 'o botão de associar deve existir'); pin.click(); await app.settle(); const req = app.lastRequest('PUT', '/api/files/file2/move'); assert.ok(req, 'deve chamar PUT /api/files/{id}/move'); assert.deepStrictEqual(req.body, { note: 'note1' }); }); test('excluir pede confirmação e só apaga depois de confirmado', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('raiz.txt'), '✕').click(); await app.settle(); const modal = app.document.querySelector('#modal-overlay'); assert.ok(!modal.classList.contains('hidden'), 'o modal de confirmação deve abrir'); assert.strictEqual(app.requests('DELETE', '/api/files/file2').length, 0, 'não exclui antes de confirmar'); app.document.querySelector('#modal-ok').click(); await app.settle(); assert.strictEqual(app.requests('DELETE', '/api/files/file2').length, 1, 'exclui depois de confirmar'); }); test('cancelar a confirmação de exclusão não apaga nada', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('raiz.txt'), '✕').click(); await app.settle(); app.document.querySelector('#modal-cancel').click(); await app.settle(); assert.strictEqual(app.requests('DELETE', '/api/files/file2').length, 0, 'não deve excluir'); }); test('os filtros separam anexos de arquivos avulsos', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); const chip = (label) => [...app.document.querySelectorAll('#files-filters button')] .find((b) => b.textContent.trim() === label); // raiz.txt está vinculado a note1 → some no filtro "Avulsos" chip('Avulsos').click(); await app.settle(); assert.deepStrictEqual(app.listRows(), [], 'nenhum avulso na raiz'); chip('Anexos').click(); await app.settle(); assert.deepStrictEqual(app.listRows().map((r) => r.querySelector('.fitem-name').textContent), ['raiz.txt']); }); test('a busca filtra os arquivos por nome', async (t) => { const app = createApp(); t.after(() => app.close()); await app.openManager(); const search = app.document.querySelector('#files-search'); search.value = 'raiz'; search.dispatchEvent(new app.window.Event('input', { bubbles: true })); await app.settle(400); assert.deepStrictEqual(app.listRows().map((r) => r.querySelector('.fitem-name').textContent), ['raiz.txt']); search.value = 'nada-encontrado'; search.dispatchEvent(new app.window.Event('input', { bubbles: true })); await app.settle(400); assert.deepStrictEqual(app.listRows(), [], 'busca sem resultado limpa a lista'); }); test('criar pasta envia nome e pai', async (t) => { const app = createApp({ promptAnswer: 'Novos' }); t.after(() => app.close()); await app.openManager(); app.document.querySelector('#files-newfolder').click(); await app.settle(); const req = app.lastRequest('POST', '/api/folders'); assert.ok(req, 'deve chamar POST /api/folders'); assert.deepStrictEqual(req.body, { name: 'Novos', parent: '' }); }); test('o preview de imagem abre o lightbox com a url do arquivo', async (t) => { const app = createApp({ fixtures: { rootFiles: [{ id: 'img1', name: 'foto.png', folder: '', note: '', size: 10, mime: 'image/png', sha256: 'cc', created_at: '2026-01-02T00:00:00Z', url: '/api/files/static/img1' }], }, }); t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('foto.png'), '👁').click(); await app.settle(); const lightbox = app.document.querySelector('#lightbox'); assert.ok(!lightbox.classList.contains('hidden'), 'o lightbox deve abrir'); const img = lightbox.querySelector('img'); assert.ok(img, 'a imagem deve ser exibida'); assert.strictEqual(img.getAttribute('src'), '/api/files/static/img1'); }); test('o preview de compactado lista o conteúdo do arquivo', async (t) => { const app = createApp({ fixtures: { rootFiles: [{ id: 'zip1', name: 'backup.zip', folder: '', note: '', size: 10, mime: 'application/zip', sha256: 'dd', created_at: '2026-01-02T00:00:00Z', url: '/api/files/static/zip1' }], }, routes: { '/api/files/archive/zip1': ({ json }) => json({ entries: [{ name: 'docs/nota.txt', size: 16, dir: false }, { name: 'vazia/', size: 0, dir: true }], total: 2, truncated: false, }), }, }); t.after(() => app.close()); await app.openManager(); actionButton(app.rowByName('backup.zip'), '👁').click(); await app.settle(); const items = [...app.document.querySelectorAll('.archive-list li')]; assert.strictEqual(items.length, 2, 'o compactado lista os itens'); assert.match(items[0].textContent, /docs\/nota\.txt/); assert.match(items[1].textContent, /vazia\//); });