Skip to content

Commit 7023bdd

Browse files
committed
feat: add waitForPendingAttachments method to attachmentManager
1 parent 66ab699 commit 7023bdd

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

src/messageComposer/attachmentManager.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,28 @@ export class AttachmentManager {
208208
);
209209
}
210210

211+
/**
212+
* Resolves when no attachment is still in `pending` or `uploading` (each in-flight upload has
213+
* reached `finished`, `failed`, or `blocked`). Resolves immediately if there is nothing to wait for.
214+
*/
215+
waitForPendingAttachments = (): Promise<void> => {
216+
const hasPendingOrUploading = () =>
217+
this.pendingUploadsCount > 0 || this.uploadsInProgressCount > 0;
218+
219+
if (!hasPendingOrUploading()) {
220+
return Promise.resolve();
221+
}
222+
223+
return new Promise((resolve) => {
224+
const unsubscribe = this.state.subscribe(() => {
225+
if (!hasPendingOrUploading()) {
226+
unsubscribe();
227+
resolve();
228+
}
229+
});
230+
});
231+
};
232+
211233
initState = ({ message }: { message?: DraftMessage | LocalMessage } = {}) => {
212234
this.state.next(initState({ message }));
213235
};

test/unit/MessageComposer/attachmentManager.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,73 @@ describe('AttachmentManager', () => {
10021002
});
10031003
});
10041004

1005+
describe('waitForPendingAttachments', () => {
1006+
it('resolves immediately when there are no pending or uploading attachments', async () => {
1007+
const {
1008+
messageComposer: { attachmentManager },
1009+
} = setup();
1010+
1011+
await expect(
1012+
attachmentManager.waitForPendingAttachments(),
1013+
).resolves.toBeUndefined();
1014+
});
1015+
1016+
it('resolves when in-flight uploads finish', async () => {
1017+
const {
1018+
messageComposer: { attachmentManager },
1019+
mockChannel,
1020+
} = setup();
1021+
1022+
let resolveUpload!: (value: { file: string; thumb_url?: string }) => void;
1023+
const uploadPromise = new Promise<{ file: string; thumb_url?: string }>(
1024+
(resolve) => {
1025+
resolveUpload = resolve;
1026+
},
1027+
);
1028+
mockChannel.sendImage.mockImplementation(() => uploadPromise);
1029+
1030+
const file = new File([''], 'test.jpg', { type: 'image/jpeg' });
1031+
const local = await attachmentManager.fileToLocalUploadAttachment(file);
1032+
void attachmentManager.uploadAttachment(local);
1033+
1034+
await vi.waitFor(() => {
1035+
expect(attachmentManager.uploadsInProgressCount).toBe(1);
1036+
});
1037+
1038+
const settled = attachmentManager.waitForPendingAttachments();
1039+
resolveUpload({ file: 'done-url' });
1040+
await expect(settled).resolves.toBeUndefined();
1041+
expect(attachmentManager.successfulUploadsCount).toBe(1);
1042+
});
1043+
1044+
it('resolves when in-flight uploads fail', async () => {
1045+
const {
1046+
messageComposer: { attachmentManager },
1047+
mockChannel,
1048+
} = setup();
1049+
1050+
let rejectUpload!: (err: Error) => void;
1051+
const uploadPromise = new Promise<never>((_, reject) => {
1052+
rejectUpload = reject;
1053+
});
1054+
mockChannel.sendImage.mockImplementation(() => uploadPromise);
1055+
1056+
const file = new File([''], 'test.jpg', { type: 'image/jpeg' });
1057+
const local = await attachmentManager.fileToLocalUploadAttachment(file);
1058+
const uploadWork = attachmentManager.uploadAttachment(local);
1059+
1060+
await vi.waitFor(() => {
1061+
expect(attachmentManager.uploadsInProgressCount).toBe(1);
1062+
});
1063+
1064+
const settled = attachmentManager.waitForPendingAttachments();
1065+
rejectUpload(new Error('upload failed'));
1066+
await expect(settled).resolves.toBeUndefined();
1067+
await uploadWork;
1068+
expect(attachmentManager.failedUploadsCount).toBe(1);
1069+
});
1070+
});
1071+
10051072
describe('uploadAttachment', () => {
10061073
it('should upload files successfully', async () => {
10071074
const {

0 commit comments

Comments
 (0)