Files
notas-online/frontend/tests/files-dnd.test.js
T
lgcosta 1866463b3d
ci / check (push) Successful in 57s
test(frontend): add jsdom tests for the file manager
Sobe web/index.html + web/js/app.js de verdade em jsdom, intercepta a API
e cobre arrastar-e-soltar entre pastas, mover/renomear/excluir, filtros,
busca e preview. CI passa a rodar npm ci && npm test.
2026-11-07 19:17:50 -03:00

162 lines
5.6 KiB
JavaScript

// Arrastar-e-soltar de arquivos entre pastas no gerenciador.
'use strict';
const test = require('node:test');
const assert = require('node:assert');
const { createApp, FakeDataTransfer } = require('./harness');
test('arrastar um arquivo para uma pasta move o arquivo', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
const source = app.rowByName('raiz.txt');
const target = app.treeRowByName('Documentos');
assert.ok(source, 'arquivo na raiz deve aparecer na lista');
assert.ok(target, 'pasta deve aparecer na árvore');
app.dragAndDrop(source, target);
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('o arrasto leva o id do arquivo no payload interno', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
const dt = app.dragAndDrop(app.rowByName('raiz.txt'), app.treeRowByName('Documentos'));
assert.strictEqual(dt.getData('application/x-notas-file'), 'file2');
assert.strictEqual(dt.getData('text/plain'), 'raiz.txt');
});
test('soltar na pasta em que o arquivo já está não chama a API', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
app.treeRowByName('Documentos').click();
await app.settle();
const source = app.rowByName('nota.txt');
assert.ok(source, 'nota.txt deve estar na lista da pasta');
app.dragAndDrop(source, app.treeRowByName('Documentos'));
assert.strictEqual(app.requests('PUT', '/move').length, 0, 'nenhuma chamada de movementação');
});
test('soltar na raiz move o arquivo para a raiz', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
app.treeRowByName('Documentos').click();
await app.settle();
app.dragAndDrop(app.rowByName('nota.txt'), app.treeRowByName('Raiz'));
const req = app.lastRequest('PUT', '/api/files/file1/move');
assert.ok(req, 'deve chamar PUT /api/files/{id}/move');
assert.deepStrictEqual(req.body, { folder: '' });
});
test('drop de arquivo do sistema é ignorado', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
const dt = new FakeDataTransfer(); // sem mime interno: é um arquivo do disco
app.fireDrag(app.rowByName('raiz.txt'), 'dragover', dt);
app.fireDrag(app.treeRowByName('Documentos'), 'drop', dt);
assert.strictEqual(app.requests('PUT', '/move').length, 0, 'não deve mover nada');
});
test('a pasta alvo é destacada durante o arrasto', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
const source = app.rowByName('raiz.txt');
const target = app.treeRowByName('Documentos');
const dt = new FakeDataTransfer();
app.fireDrag(source, 'dragstart', dt);
assert.ok(source.classList.contains('dragging'), 'origem fica marcada como arrastando');
app.fireDrag(target, 'dragover', dt);
assert.ok(target.classList.contains('drop-hl'), 'destino fica destacado');
app.fireDrag(source, 'dragend', dt);
assert.ok(!target.classList.contains('drop-hl'), 'destaque é limpo ao fim do arrasto');
assert.ok(!source.classList.contains('dragging'), 'origem volta ao normal');
});
test('o destino da movimentação é informado ao usuário', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
app.dragAndDrop(app.rowByName('raiz.txt'), app.treeRowByName('Documentos'));
await app.settle();
assert.match(app.toastText(), /Documentos/);
});
test('após mover, o gerenciador recarrega a árvore', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
const before = app.requests('GET', '/api/files/tree').length;
app.dragAndDrop(app.rowByName('raiz.txt'), app.treeRowByName('Documentos'));
await app.settle();
assert.ok(app.requests('GET', '/api/files/tree').length > before, 'a árvore deve ser recarregada');
});
test('a falha da API não deixa a UI afirmar sucesso', async (t) => {
const app = createApp({
routes: {
'/api/files/file2/move': ({ json }) => json({ error: 'pasta inexistente' }, 400),
},
});
t.after(() => app.close());
await app.openManager();
app.dragAndDrop(app.rowByName('raiz.txt'), app.treeRowByName('Documentos'));
await app.settle();
assert.match(app.toastText(), /pasta inexistente/, 'o erro da API aparece para o usuário');
assert.doesNotMatch(app.toastText(), /Movido para/, 'não pode anunciar sucesso');
});
test('no modo seletor as linhas não são arrastáveis', async (t) => {
const app = createApp();
t.after(() => app.close());
// abre o gerenciador como seletor (botão 📎 Anexo)
app.document.querySelector('#btn-attach').click();
await app.waitFor(() => app.listRows().length > 0);
for (const row of app.listRows()) {
assert.strictEqual(row.draggable, false, 'linha do seletor não deve ser arrastável');
}
});
test('o arquivo continua vinculado à nota após mudar de pasta', async (t) => {
const app = createApp();
t.after(() => app.close());
await app.openManager();
app.dragAndDrop(app.rowByName('raiz.txt'), app.treeRowByName('Documentos'));
await app.settle();
// a movimentação não mexe no vínculo com a nota: só o id e a pasta mudam
const req = app.lastRequest('PUT', '/api/files/file2/move');
assert.deepStrictEqual(Object.keys(req.body), ['folder']);
assert.strictEqual(req.url, '/api/files/file2/move', 'o id do arquivo é o mesmo, então a referência da nota continua válida');
});