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('')[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) })