feat(chat): preserve message drafts in session temp store
This commit is contained in:
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const vm = require('node:vm')
|
||||
const vue = require('vue')
|
||||
const { createDraftHarness } = require('./helpers/chat-drafts.cjs')
|
||||
|
||||
const settle = () => new Promise(setImmediate)
|
||||
const deferred = () => {
|
||||
@@ -33,6 +34,7 @@ function fixture(override = () => undefined) {
|
||||
|
||||
function setup(api = fixture(), query = {}) {
|
||||
const auth = vue.reactive({ user: { id: 'user-1' }, activeTenant: '1' })
|
||||
const drafts = createDraftHarness({ auth })
|
||||
const route = vue.reactive({ query })
|
||||
const calls = [], toasts = [], timers = new Map()
|
||||
const listeners = new Map()
|
||||
@@ -47,6 +49,7 @@ function setup(api = fixture(), query = {}) {
|
||||
ref: vue.ref, reactive: vue.reactive, computed: vue.computed, watch: vue.watch, nextTick: vue.nextTick,
|
||||
useToast: () => ({ add: item => toasts.push(item) }),
|
||||
useAuthStore: () => auth, useRoute: () => route,
|
||||
useChatDraft: drafts.useChatDraft,
|
||||
useRouter: () => ({ replace: async location => { route.query = location.query } }),
|
||||
useNuxtApp: () => ({ $api: (url, options) => { calls.push({ url, options }); return api(url, options) } }),
|
||||
onMounted: handler => { mount = handler }, onBeforeUnmount: handler => { unmount = handler },
|
||||
@@ -54,7 +57,7 @@ function setup(api = fixture(), query = {}) {
|
||||
clearInterval: id => timers.delete(id), document,
|
||||
})
|
||||
scope.run(() => vm.runInContext(source, context))
|
||||
return { auth, route, calls, toasts, timers, listeners, document, mount, close: () => { unmount(); scope.stop() }, run: code => vm.runInContext(code, context) }
|
||||
return { auth, route, calls, toasts, timers, listeners, document, mount, drafts, close: () => { unmount(); scope.stop(); drafts.close() }, run: code => vm.runInContext(code, context) }
|
||||
}
|
||||
|
||||
test('only member rooms enter normal chat, with separate keys, groups and unread counts', async t => {
|
||||
@@ -377,3 +380,69 @@ test('native image-only sends retain the draft after an error and clear it after
|
||||
assert.equal(ui.run('draftImage.value'), null)
|
||||
assert.ok(ui.calls.some(c => c.options?.body?.image?.name === 'Bild'))
|
||||
})
|
||||
|
||||
test('native drafts survive switching chats, hiding a room and reloading its history', async t => {
|
||||
const ui = setup(fixture(url => url.endsWith('/project-rooms') ? { rooms: [{ key: 'project_1', exists: true, type: 'project' }] } : undefined))
|
||||
t.after(ui.close)
|
||||
ui.mount()
|
||||
await settle()
|
||||
ui.run('draft.value = " Hallo\\nWelt 👋 "; draftImage.value = { dataUrl: "data:image/webp;base64,AAAA", name: "Skizze" }')
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.key === "project_1"))')
|
||||
assert.equal(ui.run('draft.value'), '')
|
||||
assert.equal(ui.run('draftImage.value'), null)
|
||||
ui.run('draft.value = "Projektentwurf"')
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.key === "allgemein"))')
|
||||
assert.equal(ui.run('draft.value'), ' Hallo\nWelt 👋 ')
|
||||
assert.equal(ui.run('draftImage.value.name'), 'Skizze')
|
||||
await ui.run('loadActiveRoom()')
|
||||
assert.equal(ui.run('draft.value'), ' Hallo\nWelt 👋 ')
|
||||
await ui.run('setRoomHidden(activeRoom.value, true)')
|
||||
assert.equal(ui.run('draft.value'), 'Projektentwurf')
|
||||
await ui.run('setRoomHidden(hiddenRooms.value[0], false)')
|
||||
assert.equal(ui.run('draft.value'), ' Hallo\nWelt 👋 ')
|
||||
})
|
||||
|
||||
test('failed sends retain the exact draft after switching away, and late success clears only the sent room', async t => {
|
||||
const pending = deferred()
|
||||
let fail = true
|
||||
const ui = setup(fixture((url, options) => {
|
||||
if (url.endsWith('/messages') && options?.method === 'POST') {
|
||||
if (fail) throw { data: { message: 'Offline' } }
|
||||
return pending.promise
|
||||
}
|
||||
}))
|
||||
t.after(ui.close)
|
||||
ui.mount()
|
||||
await settle()
|
||||
ui.run('draft.value = " Noch nicht fertig\\n "')
|
||||
await ui.run('sendMessage()')
|
||||
assert.equal(ui.run('draft.value'), ' Noch nicht fertig\n ')
|
||||
fail = false
|
||||
const sending = ui.run('sendMessage()')
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.source === "collaboration"))')
|
||||
pending.resolve({ id: 2, body: 'Noch nicht fertig' })
|
||||
await sending
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.key === "allgemein"))')
|
||||
assert.equal(ui.run('draft.value'), '')
|
||||
assert.equal(Object.keys(ui.drafts.storage).length, 0)
|
||||
})
|
||||
|
||||
test('editing a room after returning to a pending send preserves the new draft', async t => {
|
||||
const pending = deferred()
|
||||
const ui = setup(fixture((url, options) => url.endsWith('/messages') && options?.method === 'POST' ? pending.promise : undefined))
|
||||
t.after(ui.close)
|
||||
ui.mount()
|
||||
await settle()
|
||||
ui.run('draft.value = "Vorheriger Entwurf"')
|
||||
const sending = ui.run('sendMessage()')
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.source === "collaboration"))')
|
||||
await ui.run('selectRoom(decoratedRooms.value.find(r => r.key === "allgemein"))')
|
||||
assert.equal(ui.run('draftSending.value'), true)
|
||||
await ui.run('sendMessage()')
|
||||
assert.equal(ui.calls.filter(({ url, options }) => url.endsWith('/messages') && options?.method === 'POST').length, 1)
|
||||
ui.run('draft.value = "Weitergeschrieben"')
|
||||
pending.resolve({ id: 2, body: 'Vorheriger Entwurf' })
|
||||
await sending
|
||||
assert.equal(ui.run('draft.value'), 'Weitergeschrieben')
|
||||
assert.equal(ui.run('draftSending.value'), false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user