From 38a6085e0bb534ea1621d9a4d4c356a461887d1d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 14 Feb 2025 13:47:36 +0200 Subject: [PATCH 1/8] Save form state for unsubmitted data --- .../core/src/js/feedback/FeedbackForm.tsx | 45 +++++++++++++++---- .../core/test/feedback/FeedbackForm.test.tsx | 30 +++++++++++++ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index b6aac2f412..8f5103854c 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -33,6 +33,15 @@ export class FeedbackForm extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }); + this.setState({ filename, attachment: data }, this._saveFormState); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -142,11 +154,11 @@ export class FeedbackForm extends React.Component { - this.setState({ filename, attachment: attachement }); + this.setState({ filename, attachment: attachement }, this._saveFormState); }); } } else { - this.setState({ filename: undefined, attachment: undefined }); + this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); } } @@ -199,7 +211,7 @@ export class FeedbackForm extends React.Component this.setState({ name: value })} + onChangeText={(value) => this.setState({ name: value }, this._saveFormState)} /> )} @@ -215,7 +227,7 @@ export class FeedbackForm extends React.Component this.setState({ email: value })} + onChangeText={(value) => this.setState({ email: value }, this._saveFormState)} /> )} @@ -228,7 +240,7 @@ export class FeedbackForm extends React.Component this.setState({ description: value })} + onChangeText={(value) => this.setState({ description: value }, this._saveFormState)} multiline /> {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( @@ -254,4 +266,19 @@ export class FeedbackForm extends React.Component ); } + + private _saveFormState = (): void => { + FeedbackForm._savedState = { ...this.state }; + }; + + private _clearFormState = (): void => { + FeedbackForm._savedState = { + isVisible: false, + name: '', + email: '', + description: '', + filename: undefined, + attachment: undefined, + }; + }; } diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 92ed48fe4a..15918799ca 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -354,4 +354,34 @@ describe('FeedbackForm', () => { expect(mockOnFormClose).toHaveBeenCalled(); }); + + it('onCancel the input is saved and restored when the form reopens', async () => { + const { getByPlaceholderText, getByText } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + fireEvent.press(getByText(defaultProps.cancelButtonLabel)); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.'); + }); + + it('onSubmit the saved input is cleared and not restored when the form reopens', async () => { + const { getByPlaceholderText, getByText } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + fireEvent.press(getByText(defaultProps.submitButtonLabel)); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('Test User'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('test@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe(''); + }); }); From 56a113e15e7f1c25af6b982956b7e08ea7169d95 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 14 Feb 2025 17:55:15 +0200 Subject: [PATCH 2/8] Show selected screenshot --- .../src/js/feedback/FeedbackForm.styles.ts | 14 +- .../core/src/js/feedback/FeedbackForm.tsx | 32 ++- .../src/js/feedback/FeedbackForm.types.ts | 3 + .../__snapshots__/FeedbackForm.test.tsx.snap | 245 ++++++++++-------- 4 files changed, 177 insertions(+), 117 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts index 1e90af07c6..e1373f6c8c 100644 --- a/packages/core/src/js/feedback/FeedbackForm.styles.ts +++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts @@ -45,8 +45,20 @@ const defaultStyles: FeedbackFormStyles = { backgroundColor: '#eee', padding: 15, borderRadius: 5, - marginBottom: 20, alignItems: 'center', + flex: 1, + }, + screenshotContainer: { + flexDirection: 'row', + alignItems: 'center', + width: '100%', + marginBottom: 20, + }, + screenshotThumbnail: { + width: 50, + height: 50, + borderRadius: 5, + marginRight: 10, }, screenshotText: { color: '#333', diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 8f5103854c..19a3a96f8d 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -40,6 +40,7 @@ export class FeedbackForm extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }, this._saveFormState); + this.setState({ filename, attachment: data, attachmentUri: imageUri }, this._saveFormState); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -154,11 +156,12 @@ export class FeedbackForm extends React.Component { - this.setState({ filename, attachment: attachement }, this._saveFormState); + // TODO: Add support for image uri when using onAddScreenshot + this.setState({ filename, attachment: attachement, attachmentUri: undefined }, this._saveFormState); }); } } else { - this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); + this.setState({ filename: undefined, attachment: undefined, attachmentUri: undefined }, this._saveFormState); } } @@ -244,13 +247,21 @@ export class FeedbackForm extends React.Component {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( - - - {!this.state.filename && !this.state.attachment - ? text.addScreenshotButtonLabel - : text.removeScreenshotButtonLabel} - - + + {this.state.attachmentUri && ( + + )} + + + {!this.state.filename && !this.state.attachment + ? text.addScreenshotButtonLabel + : text.removeScreenshotButtonLabel} + + + )} {text.submitButtonLabel} @@ -279,6 +290,7 @@ export class FeedbackForm extends React.Component - - Add a screenshot - + + Add a screenshot + + - - Add Screenshot - + + Add Screenshot + + - - Add a screenshot - + + Add a screenshot + + Date: Mon, 17 Feb 2025 12:06:56 +0200 Subject: [PATCH 3/8] Omit isVisible from state --- packages/core/src/js/feedback/FeedbackWidget.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 839617beeb..e992f08d37 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,8 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', description: '', @@ -273,7 +272,6 @@ export class FeedbackWidget extends React.Component { FeedbackWidget._savedState = { - isVisible: false, name: '', email: '', description: '', From 59f2f570ae0e6bb6744bb59566cdc13e549add75 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 15:02:07 +0200 Subject: [PATCH 4/8] Save/clear form state on unmount --- .../core/src/js/feedback/FeedbackWidget.tsx | 27 ++++++++++++++----- .../test/feedback/FeedbackWidget.test.tsx | 21 +++++++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index e992f08d37..8ad7f85ded 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,6 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -103,7 +104,7 @@ export class FeedbackWidget extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }, this._saveFormState); + this.setState({ filename, attachment: data }); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -153,11 +154,23 @@ export class FeedbackWidget extends React.Component { - this.setState({ filename, attachment: attachement }, this._saveFormState); + this.setState({ filename, attachment: attachement }); }); } } else { - this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); + this.setState({ filename: undefined, attachment: undefined }); + } + } + + /** + * Save the state before unmounting the component. + */ + public componentWillUnmount(): void { + if (FeedbackWidget._didSubmitForm) { + this._clearFormState(); + FeedbackWidget._didSubmitForm = false; + } else { + this._saveFormState(); } } @@ -210,7 +223,7 @@ export class FeedbackWidget extends React.Component this.setState({ name: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ name: value })} /> )} @@ -226,7 +239,7 @@ export class FeedbackWidget extends React.Component this.setState({ email: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ email: value })} /> )} @@ -239,7 +252,7 @@ export class FeedbackWidget extends React.Component this.setState({ description: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ description: value })} multiline /> {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( diff --git a/packages/core/test/feedback/FeedbackWidget.test.tsx b/packages/core/test/feedback/FeedbackWidget.test.tsx index c3fcc8fefe..0274651a31 100644 --- a/packages/core/test/feedback/FeedbackWidget.test.tsx +++ b/packages/core/test/feedback/FeedbackWidget.test.tsx @@ -355,14 +355,30 @@ describe('FeedbackWidget', () => { expect(mockOnFormClose).toHaveBeenCalled(); }); + it('onUnmount the input is saved and restored when the form reopens', async () => { + const { getByPlaceholderText, unmount } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + unmount(); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.'); + }); + it('onCancel the input is saved and restored when the form reopens', async () => { - const { getByPlaceholderText, getByText } = render(); + const { getByPlaceholderText, getByText, unmount } = render(); fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); fireEvent.press(getByText(defaultProps.cancelButtonLabel)); + unmount(); const { queryByPlaceholderText } = render(); expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); @@ -371,13 +387,14 @@ describe('FeedbackWidget', () => { }); it('onSubmit the saved input is cleared and not restored when the form reopens', async () => { - const { getByPlaceholderText, getByText } = render(); + const { getByPlaceholderText, getByText, unmount } = render(); fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); fireEvent.press(getByText(defaultProps.submitButtonLabel)); + unmount(); const { queryByPlaceholderText } = render(); expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('Test User'); From 797611f898676086b4e9f3570244cf8783245055 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 15:02:42 +0200 Subject: [PATCH 5/8] Pass the missing attachment parameter in the onSubmitSuccess --- packages/core/src/js/feedback/FeedbackWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 8ad7f85ded..b1da634aef 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -101,7 +101,7 @@ export class FeedbackWidget extends React.Component Date: Tue, 18 Feb 2025 11:38:18 +0200 Subject: [PATCH 6/8] Use instance variable for _didSubmitForm --- packages/core/src/js/feedback/FeedbackWidget.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 92d2f6d3b8..c5415f4ddc 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,7 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -106,7 +106,7 @@ export class FeedbackWidget extends React.Component Date: Tue, 18 Feb 2025 11:45:18 +0200 Subject: [PATCH 7/8] Fixes lint issue --- packages/core/src/js/feedback/FeedbackWidget.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index c5415f4ddc..f0026751d3 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,7 +33,6 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -42,6 +41,8 @@ export class FeedbackWidget extends React.Component Date: Tue, 18 Feb 2025 15:55:16 +0200 Subject: [PATCH 8/8] Fix merge issue --- packages/core/src/js/feedback/FeedbackWidget.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 9af5099b81..317c48c3bd 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -180,18 +180,6 @@ export class FeedbackWidget extends React.Component