diff --git a/README.md b/README.md index 7f1d4f8e..108d35db 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ const fields = [ You can transform and validate data with custom hooks. There are hooks after each step: +- **fileSelectedHook** - runs only once after the file has been selected. - **uploadStepHook** - runs only once after uploading the file. - **selectHeaderStepHook** - runs only once after selecting the header row in spreadsheet. - **matchColumnsStepHook** - runs only once after column matching. Operations on data that are expensive should be done here. diff --git a/src/ReactSpreadsheetImport.tsx b/src/ReactSpreadsheetImport.tsx index 987bca69..f54eae6e 100644 --- a/src/ReactSpreadsheetImport.tsx +++ b/src/ReactSpreadsheetImport.tsx @@ -16,6 +16,7 @@ export const defaultRSIProps: Partial> = { autoMapDistance: 2, isNavigationEnabled: false, translations: translations, + fileSelectedHook: async (file) => {}, uploadStepHook: async (value) => value, selectHeaderStepHook: async (headerValues, data) => ({ headerValues, data }), matchColumnsStepHook: async (table) => table, diff --git a/src/steps/SelectSheetStep/tests/SelectSheetStep.test.tsx b/src/steps/SelectSheetStep/tests/SelectSheetStep.test.tsx index 84bfcb5c..cbd07c73 100644 --- a/src/steps/SelectSheetStep/tests/SelectSheetStep.test.tsx +++ b/src/steps/SelectSheetStep/tests/SelectSheetStep.test.tsx @@ -116,3 +116,35 @@ test("Should show error toast if error is thrown in uploadStepHook", async () => const errorToast = await screen.findAllByText(ERROR_MESSAGE, undefined, { timeout: 5000 }) expect(errorToast?.[0]).toBeInTheDocument() }) + +test("Should show error toast if error is thrown in fileSelectedHook", async () => { + const fileSelectedHook = jest.fn(async () => { + throw new Error(ERROR_MESSAGE) + return undefined as any + }) + render() + const uploader = screen.getByTestId("rsi-dropzone") + const data = readFileSync(__dirname + "/../../../../static/Workbook1.xlsx") + fireEvent.drop(uploader, { + target: { + files: [ + new File([data], "testFile.xlsx", { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }), + ], + }, + }) + + const nextButton = await screen.findByRole( + "button", + { + name: "Next", + }, + { timeout: 5000 }, + ) + + await userEvent.click(nextButton) + + const errorToast = await screen.findAllByText(ERROR_MESSAGE, undefined, { timeout: 5000 }) + expect(errorToast?.[0]).toBeInTheDocument() +}) diff --git a/src/steps/UploadFlow.tsx b/src/steps/UploadFlow.tsx index b70fc5dc..34fafc6c 100644 --- a/src/steps/UploadFlow.tsx +++ b/src/steps/UploadFlow.tsx @@ -51,6 +51,7 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => { const { maxRecords, translations, + fileSelectedHook, uploadStepHook, selectHeaderStepHook, matchColumnsStepHook, @@ -87,6 +88,7 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => { return } try { + await fileSelectedHook(file) const mappedWorkbook = await uploadStepHook(mapWorkbook(workbook)) onNext({ type: StepType.selectHeader, @@ -111,6 +113,10 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => { return } try { + if (uploadedFile) { + await fileSelectedHook(uploadedFile) + } + const mappedWorkbook = await uploadStepHook(mapWorkbook(state.workbook, sheetName)) onNext({ type: StepType.selectHeader, diff --git a/src/steps/UploadStep/tests/UploadStep.test.tsx b/src/steps/UploadStep/tests/UploadStep.test.tsx index 6717f751..27d64c93 100644 --- a/src/steps/UploadStep/tests/UploadStep.test.tsx +++ b/src/steps/UploadStep/tests/UploadStep.test.tsx @@ -84,3 +84,37 @@ test("Should show error toast if error is thrown in uploadStepHook", async () => const errorToast = await screen.findAllByText(ERROR_MESSAGE, undefined, { timeout: 5000 }) expect(errorToast?.[0]).toBeInTheDocument() }) + +test("Should call fileSelectedHook on file selection", async () => { + const file = new File(["Hello, Hello, Hello, Hello"], "test.csv", { type: "text/csv" }) + const fileSelectedHook = jest.fn(async (file) => {}) + render() + const uploader = screen.getByTestId("rsi-dropzone") + fireEvent.drop(uploader, { + target: { files: [file] }, + }) + + await waitFor( + () => { + expect(fileSelectedHook).toBeCalled() + }, + { timeout: 5000 }, + ) +}) + +test("Should show error toast if error is thrown in fileSelectedHook", async () => { + const file = new File(["Hello, Hello, Hello, Hello"], "test.csv", { type: "text/csv" }) + const fileSelectedHook = jest.fn(async () => { + throw new Error(ERROR_MESSAGE) + return undefined as any + }) + render() + + const uploader = screen.getByTestId("rsi-dropzone") + fireEvent.drop(uploader, { + target: { files: [file] }, + }) + + const errorToast = await screen.findAllByText(ERROR_MESSAGE, undefined, { timeout: 5000 }) + expect(errorToast?.[0]).toBeInTheDocument() +}) diff --git a/src/stories/mockRsiValues.ts b/src/stories/mockRsiValues.ts index 6bbb200b..c4c2c280 100644 --- a/src/stories/mockRsiValues.ts +++ b/src/stories/mockRsiValues.ts @@ -92,6 +92,14 @@ export const mockRsiValues = mockComponentBehaviourForTypes({ }, isOpen: true, onClose: () => {}, + // fileSelectedHook: async (file) => { + // await new Promise((resolve) => { + // setTimeout(() => { + // console.log(file); + // resolve(); + // }, 4000) + // }) + // }, // uploadStepHook: async (data) => { // await new Promise((resolve) => { // setTimeout(() => resolve(data), 4000) diff --git a/src/types.ts b/src/types.ts index 743dbe09..126fb550 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,8 @@ export type RsiProps = { onClose: () => void // Field description for requested data fields: Fields + // Runs after file selected for upload, receives the file data + fileSelectedHook?: (file: File) => Promise // Runs after file upload step, receives and returns raw sheet data uploadStepHook?: (data: RawData[]) => Promise // Runs after header selection step, receives and returns raw sheet data