// 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'); });