Files
FEDEO/frontend/tests/email-actions.test.cjs
flfeders 1c39b69513
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 46s
Build and Push Docker Images / build-frontend (push) Successful in 1m15s
Build and Push Docker Images / build-website (push) Successful in 23s
Build and Push Docker Images / build-central-services-api (push) Successful in 21s
Build and Push Docker Images / build-central-services-admin (push) Successful in 21s
Build and Push Docker Images / build-docs (push) Successful in 1m21s
perf(email): update message actions optimistically
2026-09-08 10:18:00 +02:00

98 lines
4.5 KiB
JavaScript

const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const vm = require('node:vm')
const ts = require('typescript')
// Exercise the page's real handlers with delayed API responses and Vue ref stubs.
function setup() {
const requests = []
const notices = []
const context = vm.createContext({
ref: value => ({ value }),
computed: getter => ({ get value() { return getter() } }),
watch() {}, onMounted() {},
useRoute: () => ({ query: {} }), useRuntimeConfig: () => ({}),
useToast: () => ({ add: notice => notices.push(notice) }),
window: { confirm: () => true },
useNuxtApp: () => ({ $api: (url, options) => new Promise((resolve, reject) => requests.push({ url, options, resolve, reject })) }),
})
const source = fs.readFileSync(`${__dirname}/../pages/email/index.vue`, 'utf8')
.split('<script setup lang="ts">')[1].split('</script>')[0].replace(/^import .*$/gm, '')
vm.runInContext(ts.transpile(source, { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.None }), context)
const run = code => vm.runInContext(code, context)
run(`selectedAccountId.value='account'; messages.value=[{id:'a',mailboxPath:'INBOX',seen:false},{id:'b',mailboxPath:'INBOX',seen:false}]; mailboxes.value=[{path:'INBOX',name:'INBOX',exists:2,unseen:2},{path:'Archive',name:'Archive',exists:0,unseen:0}];`)
return { run, requests, notices }
}
test('whole bulk selection disappears before response; partial failure restores only failed mail and counters', async () => {
const { run, requests } = setup()
run(`toggleAllMessages(true); bulkMoveTarget.value='Archive'`)
const pending = run(`runBulkAction('move')`)
assert.equal(run('messages.value.length'), 0)
assert.equal(run('mailboxes.value[1].exists'), 2)
assert.equal(requests.length, 1)
requests[0].resolve({ destinationMailboxPath: 'Archive' })
await new Promise(setImmediate)
requests[1].reject(Error('offline'))
await pending
assert.equal(run('messages.value.map(m=>m.id).join()'), 'b')
assert.equal(run('checkedMessageIds.value.join()'), 'b')
assert.equal(run('mailboxes.value[0].unseen'), 1)
assert.equal(run('mailboxes.value[1].exists'), 1)
})
test('bulk read updates every mail immediately and rolls back individual failures', async () => {
const { run, requests } = setup()
run('toggleAllMessages(true)')
const pending = run(`runBulkAction('read')`)
assert.equal(run('messages.value.every(m=>m.seen)'), true)
assert.equal(run('mailboxes.value[0].unseen'), 0)
requests[0].reject(Error('offline'))
await new Promise(setImmediate)
requests[1].resolve({})
await pending
assert.equal(run('messages.value[0].seen'), false)
assert.equal(run('messages.value[1].seen'), true)
assert.equal(run('mailboxes.value[0].unseen'), 1)
})
test('single deletion is immediate and restores details on failure', async () => {
const { run, requests } = setup()
run(`selectedMessage.value={...messages.value[0],body:{text:'Body'}}`)
const pending = run('deleteSelectedMessage()')
assert.equal(run('messages.value.length'), 1)
assert.equal(run('selectedMessage.value'), null)
requests[0].reject(Error('offline'))
await pending
assert.equal(run('messages.value.map(m=>m.id).join()'), 'a,b')
assert.equal(run('selectedMessage.value.body.text'), 'Body')
})
test('late message response cannot replace newer selection; header and read state are immediate', async () => {
const { run, requests } = setup()
const first = run('selectMessage(messages.value[0])')
assert.equal(run('selectedMessage.value.id'), 'a')
assert.equal(run('messages.value[0].seen'), true)
const second = run('selectMessage(messages.value[1])')
const detailRequests = requests.filter(request => !request.options)
detailRequests[1].resolve({ id: 'b', body: { text: 'B' }, seen: false })
await second
detailRequests[0].resolve({ id: 'a', body: { text: 'A' }, seen: false })
await first
assert.equal(run('selectedMessage.value.id'), 'b')
assert.equal(run('selectedMessage.value.seen'), true)
assert.equal(run('loadingMessage.value'), false)
requests.filter(request => request.options).forEach(request => request.resolve({}))
})
test('archiving in archive leaves the message in place', async () => {
const { run, requests } = setup()
run(`messages.value[0].mailboxPath='Archive';selectedMessage.value=messages.value[0]`)
const pending = run('archiveSelectedMessage()')
assert.equal(run('messages.value.length'), 2)
requests[0].resolve({ mailboxPath: 'Archive' })
await pending
assert.equal(run('messages.value.length'), 2)
})