import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createStore } from 'jotai'; import { UploadResponse, UploadProgress, MatrixError } from 'matrix-js-sdk'; import { createUploadAtom, UploadStatus, Upload } from './upload'; import { TUploadContent } from '../utils/matrix'; // We exercise the pure reducer inside `createUploadAtom` by driving it through a // plain jotai store. The write atom is a state machine over UploadStatus. // // SKIPPED (require a real MatrixClient and/or React render, not pure logic): // - useBindUploadAtom (React hook; calls mx.cancelUpload, uploadContent) // - createUploadAtomFamily / createUploadFamilyObserverAtom (thin atomFamily // wrappers whose behavior is just createUploadAtom + jotai plumbing) const makeFile = (size = 100): TUploadContent => ({ size }) as unknown as TUploadContent; test('createUploadAtom starts in the Idle state holding the file', () => { const store = createStore(); const file = makeFile(); const uploadAtom = createUploadAtom(file); const state = store.get(uploadAtom); assert.equal(state.status, UploadStatus.Idle); assert.equal(state.file, file); }); test('a promise update transitions Idle -> Loading with zeroed progress', () => { const store = createStore(); const file = makeFile(2048); const uploadAtom = createUploadAtom(file); const promise = Promise.resolve({} as UploadResponse); store.set(uploadAtom, { promise }); const state = store.get(uploadAtom); assert.equal(state.status, UploadStatus.Loading); if (state.status === UploadStatus.Loading) { assert.equal(state.promise, promise); assert.deepEqual(state.progress, { loaded: 0, total: 2048 }); } }); test('a progress update is applied only while Loading', () => { const store = createStore(); const uploadAtom = createUploadAtom(makeFile(2048)); const progress: UploadProgress = { loaded: 512, total: 2048 }; // Ignored while Idle (not Loading). store.set(uploadAtom, { progress }); assert.equal(store.get(uploadAtom).status, UploadStatus.Idle); // Enter Loading, then progress sticks. store.set(uploadAtom, { promise: Promise.resolve({} as UploadResponse) }); store.set(uploadAtom, { progress }); const state = store.get(uploadAtom); assert.equal(state.status, UploadStatus.Loading); if (state.status === UploadStatus.Loading) { assert.deepEqual(state.progress, progress); } }); test('an mxc update transitions to Success', () => { const store = createStore(); const uploadAtom = createUploadAtom(makeFile()); store.set(uploadAtom, { mxc: 'mxc://example.org/abc' }); const state = store.get(uploadAtom); assert.equal(state.status, UploadStatus.Success); if (state.status === UploadStatus.Success) { assert.equal(state.mxc, 'mxc://example.org/abc'); } }); test('an error update transitions to Error', () => { const store = createStore(); const uploadAtom = createUploadAtom(makeFile()); const error = new Error('boom') as unknown as MatrixError; store.set(uploadAtom, { error }); const state = store.get(uploadAtom); assert.equal(state.status, UploadStatus.Error); if (state.status === UploadStatus.Error) { assert.equal(state.error, error); } }); test('the file reference is preserved across transitions', () => { const store = createStore(); const file = makeFile(); const uploadAtom = createUploadAtom(file); const seenFiles: TUploadContent[] = []; const record = (s: Upload) => seenFiles.push(s.file); store.set(uploadAtom, { promise: Promise.resolve({} as UploadResponse) }); record(store.get(uploadAtom)); store.set(uploadAtom, { mxc: 'mxc://example.org/x' }); record(store.get(uploadAtom)); assert.ok(seenFiles.every((f) => f === file)); });