From 286221fd2da494a08e7cf2a7f28ca89e59dd612a Mon Sep 17 00:00:00 2001 From: Oleh Rybak Date: Tue, 9 Jun 2026 17:05:55 +0200 Subject: [PATCH 1/3] feat(content-uploader): allow host app to reset upload items state --- .../content-uploader/ContentUploader.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/elements/content-uploader/ContentUploader.tsx b/src/elements/content-uploader/ContentUploader.tsx index 9f733cdd1f..158ecb4cf4 100644 --- a/src/elements/content-uploader/ContentUploader.tsx +++ b/src/elements/content-uploader/ContentUploader.tsx @@ -110,6 +110,7 @@ export interface ContentUploaderProps { uploadHost: string; useUploadsManager?: boolean; enableModernizedUploads?: boolean; + setClearItemsCallback?: (callback: () => void) => void; } type State = { @@ -213,6 +214,7 @@ class ContentUploader extends Component { * @return {void} */ componentDidMount() { + const { setClearItemsCallback } = this.props; this.rootElement = document.getElementById(this.id); this.appElement = this.rootElement; const { files, isPrepopulateFilesEnabled } = this.props; @@ -220,6 +222,7 @@ class ContentUploader extends Component { if (isPrepopulateFilesEnabled && files && files.length > 0) { this.addFilesToUploadQueue(files, this.upload); } + setClearItemsCallback?.(this.resetUploadsManagerItemsWhenUploadsComplete); } /** @@ -1323,6 +1326,11 @@ class ContentUploader extends Component { * @return {void} */ checkClearUploadItems = () => { + const { enableModernizedUploads } = this.props; + if (enableModernizedUploads) { + return; + } + this.resetItemsTimeout = setTimeout( this.resetUploadsManagerItemsWhenUploadsComplete, HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT, @@ -1394,12 +1402,15 @@ class ContentUploader extends Component { * @return {void} */ resetUploadsManagerItemsWhenUploadsComplete = (): void => { - const { onCancel, useUploadsManager } = this.props; + const { onCancel, useUploadsManager, enableModernizedUploads } = this.props; const { isUploadsManagerExpanded, view } = this.state; - // Do not reset items when upload manger is expanded or there're uploads in progress + // Do not reset items when upload manger is expanded (for non-modernized uploader) or there're uploads in progress if ( - (isUploadsManagerExpanded && useUploadsManager && !!this.itemsRef.current.length) || + (isUploadsManagerExpanded && + useUploadsManager && + !!this.itemsRef.current.length && + !enableModernizedUploads) || view === VIEW_UPLOAD_IN_PROGRESS ) { return; From 5cdef38968ce964e1dd1a63caf623a1e18af76a9 Mon Sep 17 00:00:00 2001 From: Oleh Rybak Date: Wed, 10 Jun 2026 11:49:12 +0200 Subject: [PATCH 2/3] test: add test coverage --- .../__tests__/ContentUploader.test.js | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/src/elements/content-uploader/__tests__/ContentUploader.test.js b/src/elements/content-uploader/__tests__/ContentUploader.test.js index 931b53f4cc..cdc695df3a 100644 --- a/src/elements/content-uploader/__tests__/ContentUploader.test.js +++ b/src/elements/content-uploader/__tests__/ContentUploader.test.js @@ -728,6 +728,175 @@ describe('elements/content-uploader/ContentUploader', () => { // Assert that addFilesToUploadQueue is not called when isPrepopulateFilesEnabled is false expect(instance.addFilesToUploadQueue).not.toHaveBeenCalled(); }); + + test('calls setClearItemsCallback with the reset method when prop is provided', () => { + const setClearItemsCallback = jest.fn(); + const wrapper = getWrapper({ setClearItemsCallback }); + const instance = wrapper.instance(); + + instance.componentDidMount(); + + expect(setClearItemsCallback).toHaveBeenCalledWith(instance.resetUploadsManagerItemsWhenUploadsComplete); + }); + }); + + describe('checkClearUploadItems()', () => { + const HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT = 8000; + let setTimeoutSpy; + + beforeEach(() => { + jest.useFakeTimers(); + setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + }); + + afterEach(() => { + setTimeoutSpy.mockRestore(); + jest.useRealTimers(); + }); + + test('does not schedule reset when enableModernizedUploads is true', () => { + const wrapper = getWrapper({ enableModernizedUploads: true }); + const instance = wrapper.instance(); + + instance.checkClearUploadItems(); + + expect(setTimeoutSpy).not.toHaveBeenCalled(); + expect(instance.resetItemsTimeout).toBeUndefined(); + }); + + test('schedules reset via setTimeout when enableModernizedUploads is false', () => { + const wrapper = getWrapper({ enableModernizedUploads: false }); + const instance = wrapper.instance(); + const resetSpy = jest.spyOn(instance, 'resetUploadsManagerItemsWhenUploadsComplete'); + + instance.checkClearUploadItems(); + + expect(instance.resetItemsTimeout).toBeDefined(); + expect(setTimeoutSpy).toHaveBeenCalledWith( + instance.resetUploadsManagerItemsWhenUploadsComplete, + HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT, + ); + + jest.runAllTimers(); + + expect(resetSpy).toHaveBeenCalled(); + }); + + test('schedules reset via setTimeout when enableModernizedUploads is undefined', () => { + const wrapper = getWrapper(); + const instance = wrapper.instance(); + + instance.checkClearUploadItems(); + + expect(instance.resetItemsTimeout).toBeDefined(); + expect(setTimeoutSpy).toHaveBeenCalledWith( + instance.resetUploadsManagerItemsWhenUploadsComplete, + HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT, + ); + }); + }); + + describe('resetUploadsManagerItemsWhenUploadsComplete()', () => { + const createUploadItem = () => ({ + api: {}, + extension: '', + file: new File(['contents'], 'upload_file.txt', { type: 'text/plain' }), + name: 'upload_file.txt', + progress: 100, + size: 1000, + status: STATUS_COMPLETE, + }); + + const setupResetTest = ({ + props = {}, + isUploadsManagerExpanded = false, + view = VIEW_UPLOAD_SUCCESS, + items = [createUploadItem()], + } = {}) => { + const onCancel = jest.fn(); + const wrapper = getWrapper({ onCancel, ...props }); + const instance = wrapper.instance(); + + instance.itemsRef.current = items; + instance.itemIdsRef.current = { 'upload_file.txt': true }; + wrapper.setState({ isUploadsManagerExpanded, view, items, itemIds: { 'upload_file.txt': true } }); + + return { wrapper, instance, onCancel, items }; + }; + + test('clears items when manager is expanded with items present and enableModernizedUploads is true', () => { + const { wrapper, instance, onCancel, items } = setupResetTest({ + props: { useUploadsManager: true, enableModernizedUploads: true }, + isUploadsManagerExpanded: true, + }); + + instance.resetUploadsManagerItemsWhenUploadsComplete(); + + expect(onCancel).toHaveBeenCalledWith(items); + expect(instance.itemsRef.current).toEqual([]); + expect(instance.itemIdsRef.current).toEqual({}); + expect(wrapper.state().items).toEqual([]); + expect(wrapper.state().itemIds).toEqual({}); + }); + + test('does not clear items when manager is expanded with items present and enableModernizedUploads is false', () => { + const { wrapper, instance, onCancel, items } = setupResetTest({ + props: { useUploadsManager: true, enableModernizedUploads: false }, + isUploadsManagerExpanded: true, + }); + + instance.resetUploadsManagerItemsWhenUploadsComplete(); + + expect(onCancel).not.toHaveBeenCalled(); + expect(instance.itemsRef.current).toEqual(items); + expect(wrapper.state().items).toEqual(items); + }); + + test.each([true, false])( + 'does not clear items when view is VIEW_UPLOAD_IN_PROGRESS and enableModernizedUploads is %s', + enableModernizedUploads => { + const { wrapper, instance, onCancel, items } = setupResetTest({ + props: { useUploadsManager: true, enableModernizedUploads }, + isUploadsManagerExpanded: true, + view: VIEW_UPLOAD_IN_PROGRESS, + }); + + instance.resetUploadsManagerItemsWhenUploadsComplete(); + + expect(onCancel).not.toHaveBeenCalled(); + expect(instance.itemsRef.current).toEqual(items); + expect(wrapper.state().items).toEqual(items); + }, + ); + + test.each([true, false])( + 'clears items when manager is not expanded and enableModernizedUploads is %s', + enableModernizedUploads => { + const { wrapper, instance, onCancel, items } = setupResetTest({ + props: { useUploadsManager: true, enableModernizedUploads }, + isUploadsManagerExpanded: false, + }); + + instance.resetUploadsManagerItemsWhenUploadsComplete(); + + expect(onCancel).toHaveBeenCalledWith(items); + expect(instance.itemsRef.current).toEqual([]); + expect(wrapper.state().items).toEqual([]); + }, + ); + + test('clears items when useUploadsManager is false even if manager is expanded', () => { + const { wrapper, instance, onCancel, items } = setupResetTest({ + props: { useUploadsManager: false, enableModernizedUploads: false }, + isUploadsManagerExpanded: true, + }); + + instance.resetUploadsManagerItemsWhenUploadsComplete(); + + expect(onCancel).toHaveBeenCalledWith(items); + expect(instance.itemsRef.current).toEqual([]); + expect(wrapper.state().items).toEqual([]); + }); }); describe('addFileDataTransferItemsToUploadQueue()', () => { From c0650f00f9ed0cca34a6af8ccec8814b2359b288 Mon Sep 17 00:00:00 2001 From: Oleh Rybak Date: Wed, 10 Jun 2026 23:47:14 +0200 Subject: [PATCH 3/3] fix: set clear callback to noop on uploader unmount --- src/elements/content-uploader/ContentUploader.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/elements/content-uploader/ContentUploader.tsx b/src/elements/content-uploader/ContentUploader.tsx index 158ecb4cf4..4853d58ef2 100644 --- a/src/elements/content-uploader/ContentUploader.tsx +++ b/src/elements/content-uploader/ContentUploader.tsx @@ -233,6 +233,8 @@ class ContentUploader extends Component { * @return {void} */ componentWillUnmount() { + const { setClearItemsCallback } = this.props; + setClearItemsCallback?.(noop); this.cancel(); }