From c6b821e9953cf19f3ad3b93b56d4af06ac583abb Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 16:51:17 +0200 Subject: [PATCH 1/9] Check network connection before sending feedback --- .../core/src/js/feedback/FeedbackForm.tsx | 22 +++++++------- .../src/js/feedback/FeedbackForm.types.ts | 5 ++++ packages/core/src/js/feedback/defaults.ts | 2 ++ packages/core/src/js/feedback/utils.ts | 17 +++++++++++ .../core/test/feedback/FeedbackForm.test.tsx | 29 +++++++++++++++++++ 5 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/js/feedback/utils.ts diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index b79fe7935..2904e8021 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -7,6 +7,7 @@ import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { defaultConfiguration } from './defaults'; import defaultStyles from './FeedbackForm.styles'; import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types'; +import { checkInternetConnection, isValidEmail } from './utils'; /** * @beta @@ -40,7 +41,7 @@ export class FeedbackForm extends React.Component 0) && !this._isValidEmail(trimmedEmail)) { + if ((config.isEmailRequired || trimmedEmail.length > 0) && !isValidEmail(trimmedEmail)) { Alert.alert(text.errorTitle, text.emailError); return; } @@ -53,11 +54,15 @@ export class FeedbackForm extends React.Component { + onFormClose(); + this.setState({ isVisible: false }); + captureFeedback(userFeedback); + Alert.alert(text.successMessageText); + }, () => { + Alert.alert(text.errorTitle, text.networkError); + }); }; /** @@ -135,9 +140,4 @@ export class FeedbackForm extends React.Component ); } - - private _isValidEmail = (email: string): boolean => { - const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ - return emailRegex.test(email); - }; } diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts index aac7a1e1b..3710c42e7 100644 --- a/packages/core/src/js/feedback/FeedbackForm.types.ts +++ b/packages/core/src/js/feedback/FeedbackForm.types.ts @@ -111,6 +111,11 @@ export interface FeedbackTextConfiguration { * The error message when the email is invalid */ emailError?: string; + + /** + * Message when there is a network error + */ + networkError?: string; } /** diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index 5b0360ec4..714bfeaa5 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -17,6 +17,7 @@ const ERROR_TITLE = 'Error'; const FORM_ERROR = 'Please fill out all required fields.'; const EMAIL_ERROR = 'Please enter a valid email address.'; const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!'; +const CONNECTIONS_ERROR_TEXT = 'Unable to send Feedback due to network issues.'; export const defaultConfiguration: Partial = { // FeedbackCallbacks @@ -54,4 +55,5 @@ export const defaultConfiguration: Partial = { formError: FORM_ERROR, emailError: EMAIL_ERROR, successMessageText: SUCCESS_MESSAGE_TEXT, + networkError: CONNECTIONS_ERROR_TEXT, }; diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts new file mode 100644 index 000000000..3b8424906 --- /dev/null +++ b/packages/core/src/js/feedback/utils.ts @@ -0,0 +1,17 @@ +export const checkInternetConnection = async (onConnected: () => void, onDisconnected: () => void): Promise => { + try { + const response = await fetch('https://sentry.io', { method: 'HEAD' }); + if (response.ok) { + onConnected(); + } else { + onDisconnected(); + } + } catch (error) { + onDisconnected(); + } +}; + +export const isValidEmail = (email: string): boolean => { + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + return emailRegex.test(email); +}; diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 272072f41..77d6050bd 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -5,6 +5,7 @@ import { Alert } from 'react-native'; import { FeedbackForm } from '../../src/js/feedback/FeedbackForm'; import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types'; +import { checkInternetConnection } from '../../src/js/feedback/utils'; const mockOnFormClose = jest.fn(); @@ -20,6 +21,10 @@ jest.mock('@sentry/core', () => ({ })), lastEventId: jest.fn(), })); +jest.mock('../../src/js/feedback/utils', () => ({ + ...jest.requireActual('../../src/js/feedback/utils'), + checkInternetConnection: jest.fn(), +})); const defaultProps: FeedbackFormProps = { onFormClose: mockOnFormClose, @@ -37,9 +42,15 @@ const defaultProps: FeedbackFormProps = { formError: 'Please fill out all required fields.', emailError: 'The email address is not valid.', successMessageText: 'Feedback success', + networkError: 'Network error', }; describe('FeedbackForm', () => { + beforeEach(() => { + (checkInternetConnection as jest.Mock).mockImplementation((onConnected, _) => { + onConnected(); + }); + }); afterEach(() => { jest.clearAllMocks(); }); @@ -125,6 +136,24 @@ describe('FeedbackForm', () => { }); }); + it('shows an error message when the form when there is no network connection', async () => { + (checkInternetConnection as jest.Mock).mockImplementationOnce((_, onDisconnected) => { + onDisconnected(); + }); + + 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)); + + await waitFor(() => { + expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.networkError); + }); + }); + it('calls onFormClose when the form is submitted successfully', async () => { const { getByPlaceholderText, getByText } = render(); From a4a79e2a1b790825e1054f9afacaa54c75ebf881 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 18:10:11 +0200 Subject: [PATCH 2/9] Fixes test description typo --- packages/core/test/feedback/FeedbackForm.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 77d6050bd..b793fd893 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -136,7 +136,7 @@ describe('FeedbackForm', () => { }); }); - it('shows an error message when the form when there is no network connection', async () => { + it('shows an error message when there is no network connection', async () => { (checkInternetConnection as jest.Mock).mockImplementationOnce((_, onDisconnected) => { onDisconnected(); }); From fe7b14cd6901f944891a8e191f6709a2813d3e7a Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 18:56:28 +0200 Subject: [PATCH 3/9] Logs feedback submission error --- packages/core/src/js/feedback/FeedbackForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 2904e8021..6b3e31fad 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -1,4 +1,4 @@ -import { captureFeedback, lastEventId } from '@sentry/core'; +import { captureFeedback, lastEventId, logger } from '@sentry/core'; import type { SendFeedbackParams } from '@sentry/types'; import * as React from 'react'; import type { KeyboardTypeOptions } from 'react-native'; @@ -62,6 +62,7 @@ export class FeedbackForm extends React.Component { Alert.alert(text.errorTitle, text.networkError); + logger.error(`Feedback form submission failed: ${text.networkError}`); }); }; From b42e6f65b15213b04b751071a4340b1b36872057 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 19:19:44 +0200 Subject: [PATCH 4/9] Fixes test after logger addition --- packages/core/test/feedback/FeedbackForm.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index b793fd893..55b8acf11 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -12,6 +12,7 @@ const mockOnFormClose = jest.fn(); jest.spyOn(Alert, 'alert'); jest.mock('@sentry/core', () => ({ + ...jest.requireActual('@sentry/core'), captureFeedback: jest.fn(), getCurrentScope: jest.fn(() => ({ getUser: jest.fn(() => ({ From 40b4fd3be9d9f11bee401c972e1efa07aaad1dcf Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 19:57:01 +0200 Subject: [PATCH 5/9] Adds generic error handling --- .../core/src/js/feedback/FeedbackForm.tsx | 7 ++++-- .../src/js/feedback/FeedbackForm.types.ts | 5 ++++ packages/core/src/js/feedback/defaults.ts | 2 ++ packages/core/src/js/feedback/utils.ts | 8 +++++-- .../core/test/feedback/FeedbackForm.test.tsx | 23 +++++++++++++++++-- 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 6b3e31fad..b561dadd3 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -55,14 +55,17 @@ export class FeedbackForm extends React.Component { + checkInternetConnection(() => { // onConnected onFormClose(); this.setState({ isVisible: false }); captureFeedback(userFeedback); Alert.alert(text.successMessageText); - }, () => { + }, () => { // onDisconnected Alert.alert(text.errorTitle, text.networkError); logger.error(`Feedback form submission failed: ${text.networkError}`); + }, () => { // onError + Alert.alert(text.errorTitle, text.genericError); + logger.error(`Feedback form submission failed: ${text.genericError}`); }); }; diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts index 3710c42e7..3d7aeb117 100644 --- a/packages/core/src/js/feedback/FeedbackForm.types.ts +++ b/packages/core/src/js/feedback/FeedbackForm.types.ts @@ -116,6 +116,11 @@ export interface FeedbackTextConfiguration { * Message when there is a network error */ networkError?: string; + + /** + * Message when there is a network error + */ + genericError?: string; } /** diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index 714bfeaa5..9499cdd5b 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -18,6 +18,7 @@ const FORM_ERROR = 'Please fill out all required fields.'; const EMAIL_ERROR = 'Please enter a valid email address.'; const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!'; const CONNECTIONS_ERROR_TEXT = 'Unable to send Feedback due to network issues.'; +const GENERIC_ERROR_TEXT = 'Unable to send feedback due to an unexpected error.'; export const defaultConfiguration: Partial = { // FeedbackCallbacks @@ -56,4 +57,5 @@ export const defaultConfiguration: Partial = { emailError: EMAIL_ERROR, successMessageText: SUCCESS_MESSAGE_TEXT, networkError: CONNECTIONS_ERROR_TEXT, + genericError: GENERIC_ERROR_TEXT, }; diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts index 3b8424906..fb46b1f86 100644 --- a/packages/core/src/js/feedback/utils.ts +++ b/packages/core/src/js/feedback/utils.ts @@ -1,4 +1,8 @@ -export const checkInternetConnection = async (onConnected: () => void, onDisconnected: () => void): Promise => { +export const checkInternetConnection = async ( + onConnected: () => void, + onDisconnected: () => void, + onError: () => void, +): Promise => { try { const response = await fetch('https://sentry.io', { method: 'HEAD' }); if (response.ok) { @@ -7,7 +11,7 @@ export const checkInternetConnection = async (onConnected: () => void, onDisconn onDisconnected(); } } catch (error) { - onDisconnected(); + onError(); } }; diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 55b8acf11..ff6922b48 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -44,11 +44,12 @@ const defaultProps: FeedbackFormProps = { emailError: 'The email address is not valid.', successMessageText: 'Feedback success', networkError: 'Network error', + genericError: 'Generic error', }; describe('FeedbackForm', () => { beforeEach(() => { - (checkInternetConnection as jest.Mock).mockImplementation((onConnected, _) => { + (checkInternetConnection as jest.Mock).mockImplementation((onConnected, _onDisconnected, _onError) => { onConnected(); }); }); @@ -138,7 +139,7 @@ describe('FeedbackForm', () => { }); it('shows an error message when there is no network connection', async () => { - (checkInternetConnection as jest.Mock).mockImplementationOnce((_, onDisconnected) => { + (checkInternetConnection as jest.Mock).mockImplementationOnce((_onConnected, onDisconnected, _onError) => { onDisconnected(); }); @@ -155,6 +156,24 @@ describe('FeedbackForm', () => { }); }); + it('shows an error message when there is a generic connection', async () => { + (checkInternetConnection as jest.Mock).mockImplementationOnce((_onConnected, _onDisconnected, onError) => { + onError(); + }); + + 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)); + + await waitFor(() => { + expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.networkError); + }); + }); + it('calls onFormClose when the form is submitted successfully', async () => { const { getByPlaceholderText, getByText } = render(); From 9051509c169d1e6d040891511f6424a1a20371c9 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 12 Dec 2024 20:01:15 +0200 Subject: [PATCH 6/9] Fixes test --- packages/core/test/feedback/FeedbackForm.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index ff6922b48..5d1e2f3ab 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -170,7 +170,7 @@ describe('FeedbackForm', () => { fireEvent.press(getByText(defaultProps.submitButtonLabel)); await waitFor(() => { - expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.networkError); + expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.genericError); }); }); From c295df6bf8ee926dcfd46ba7bbcf1988a0c40aff Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Dec 2024 19:04:07 +0200 Subject: [PATCH 7/9] Adds missing callback methods --- .../core/src/js/feedback/FeedbackForm.tsx | 8 +++++-- .../src/js/feedback/FeedbackForm.types.ts | 23 +++++++++++++++++++ packages/core/src/js/feedback/defaults.ts | 12 ++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index b561dadd3..1bead2692 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -28,7 +28,7 @@ export class FeedbackForm extends React.Component void = () => { const { name, email, description } = this.state; - const { onFormClose } = { ...defaultConfiguration, ...this.props }; + const { onFormClose, onSubmitSuccess, onSubmitError, onFormSubmitted } = { ...defaultConfiguration, ...this.props }; const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props }; const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props }; @@ -59,13 +59,17 @@ export class FeedbackForm extends React.Component { // onDisconnected Alert.alert(text.errorTitle, text.networkError); logger.error(`Feedback form submission failed: ${text.networkError}`); }, () => { // onError + const errorString = `Feedback form submission failed: ${text.genericError}`; + onSubmitError(new Error(errorString)); Alert.alert(text.errorTitle, text.genericError); - logger.error(`Feedback form submission failed: ${text.genericError}`); + logger.error(errorString); }); }; diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts index 3d7aeb117..2b7b1064e 100644 --- a/packages/core/src/js/feedback/FeedbackForm.types.ts +++ b/packages/core/src/js/feedback/FeedbackForm.types.ts @@ -1,3 +1,4 @@ +import type { FeedbackFormData } from '@sentry/core'; import type { TextStyle, ViewStyle } from 'react-native'; export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks { @@ -127,10 +128,32 @@ export interface FeedbackTextConfiguration { * The public callbacks available for the feedback integration */ export interface FeedbackCallbacks { + /** + * Callback when form is opened + */ + onFormOpen?: () => void; + /** * Callback when form is closed and not submitted */ onFormClose?: () => void; + + /** + * Callback when feedback is successfully submitted + * + * After this you'll see a SuccessMessage on the screen for a moment. + */ + onSubmitSuccess?: (data: FeedbackFormData) => void; + + /** + * Callback when feedback is unsuccessfully submitted + */ + onSubmitError?: (error: Error) => void; + + /** + * Callback when the feedback form is submitted successfully, and the SuccessMessage is complete, or dismissed + */ + onFormSubmitted?: () => void; } export interface FeedbackFormStyles { diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index 9499cdd5b..7f3c05465 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -22,6 +22,9 @@ const GENERIC_ERROR_TEXT = 'Unable to send feedback due to an unexpected error.' export const defaultConfiguration: Partial = { // FeedbackCallbacks + onFormOpen: () => { + // Does nothing by default + }, onFormClose: () => { if (__DEV__) { Alert.alert( @@ -30,6 +33,15 @@ export const defaultConfiguration: Partial = { ); } }, + onSubmitSuccess: () => { + // Does nothing by default + }, + onSubmitError: () => { + // Does nothing by default + }, + onFormSubmitted: () => { + // Does nothing by default + }, // FeedbackGeneralConfiguration isEmailRequired: false, From 12b8ede2fd455367be7d3149095de77270b252b7 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Dec 2024 19:12:07 +0200 Subject: [PATCH 8/9] Removes onFormClose from the succesful submission flow --- packages/core/src/js/feedback/FeedbackForm.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 1bead2692..affe3e78f 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -28,7 +28,7 @@ export class FeedbackForm extends React.Component void = () => { const { name, email, description } = this.state; - const { onFormClose, onSubmitSuccess, onSubmitError, onFormSubmitted } = { ...defaultConfiguration, ...this.props }; + const { onSubmitSuccess, onSubmitError, onFormSubmitted } = { ...defaultConfiguration, ...this.props }; const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props }; const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props }; @@ -56,7 +56,6 @@ export class FeedbackForm extends React.Component { // onConnected - onFormClose(); this.setState({ isVisible: false }); captureFeedback(userFeedback); onSubmitSuccess({ name: trimmedName, email: trimmedEmail, message: trimmedDescription, attachments: undefined }); From 5a09da4c5eb9f162e23c78e4161583377b2d81f6 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Dec 2024 19:13:59 +0200 Subject: [PATCH 9/9] Updates tests --- .../core/test/feedback/FeedbackForm.test.tsx | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 5d1e2f3ab..2d19f5402 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -8,6 +8,9 @@ import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types import { checkInternetConnection } from '../../src/js/feedback/utils'; const mockOnFormClose = jest.fn(); +const mockOnSubmitSuccess = jest.fn(); +const mockOnFormSubmitted = jest.fn(); +const mockOnSubmitError = jest.fn(); jest.spyOn(Alert, 'alert'); @@ -29,6 +32,9 @@ jest.mock('../../src/js/feedback/utils', () => ({ const defaultProps: FeedbackFormProps = { onFormClose: mockOnFormClose, + onSubmitSuccess: mockOnSubmitSuccess, + onFormSubmitted: mockOnFormSubmitted, + onSubmitError: mockOnSubmitError, formTitle: 'Feedback Form', nameLabel: 'Name', namePlaceholder: 'Name Placeholder', @@ -174,7 +180,39 @@ describe('FeedbackForm', () => { }); }); - it('calls onFormClose when the form is submitted successfully', async () => { + it('calls onSubmitError when there is an error', async () => { + (checkInternetConnection as jest.Mock).mockImplementationOnce((_onConnected, _onDisconnected, onError) => { + onError(); + }); + + 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)); + + await waitFor(() => { + expect(mockOnSubmitError).toHaveBeenCalled(); + }); + }); + + it('calls onSubmitSuccess when the form is submitted successfully', 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)); + + await waitFor(() => { + expect(mockOnSubmitSuccess).toHaveBeenCalled(); + }); + }); + + it('calls onFormSubmitted when the form is submitted successfully', async () => { const { getByPlaceholderText, getByText } = render(); fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); @@ -184,7 +222,7 @@ describe('FeedbackForm', () => { fireEvent.press(getByText(defaultProps.submitButtonLabel)); await waitFor(() => { - expect(mockOnFormClose).toHaveBeenCalled(); + expect(mockOnFormSubmitted).toHaveBeenCalled(); }); });