Skip to content

Commit d6af059

Browse files
feat: adding alerts and toasts
1 parent d5b0cbd commit d6af059

File tree

4 files changed

+43
-36
lines changed

4 files changed

+43
-36
lines changed

src/dateExtensions/DateExtensionsPage.test.tsx

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
3-
import { IntlProvider } from '@openedx/frontend-base';
4-
import { MemoryRouter, Route, Routes } from 'react-router-dom';
53
import DateExtensionsPage from './DateExtensionsPage';
64
import { useDateExtensions, useResetDateExtensionMutation } from './data/apiHook';
5+
import { renderWithAlertAndIntl } from '@src/testUtils';
6+
7+
jest.mock('react-router-dom', () => ({
8+
...jest.requireActual('react-router-dom'),
9+
useParams: () => ({
10+
courseId: 'course-v1:edX+DemoX+Demo_Course',
11+
}),
12+
}));
713

814
jest.mock('./data/apiHook', () => ({
915
useDateExtensions: jest.fn(),
@@ -34,28 +40,18 @@ describe('DateExtensionsPage', () => {
3440
});
3541
});
3642

37-
const RenderWithRouter = () => (
38-
<IntlProvider messages={{}}>
39-
<MemoryRouter initialEntries={['/course-v1:edX+DemoX+Demo_Course']}>
40-
<Routes>
41-
<Route path="/:courseId" element={<DateExtensionsPage />} />
42-
</Routes>
43-
</MemoryRouter>
44-
</IntlProvider>
45-
);
46-
4743
it('renders page title', () => {
48-
render(<RenderWithRouter />);
44+
renderWithAlertAndIntl(<DateExtensionsPage />);
4945
expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument();
5046
});
5147

5248
it('renders add extension button', () => {
53-
render(<RenderWithRouter />);
49+
renderWithAlertAndIntl(<DateExtensionsPage />);
5450
expect(screen.getByRole('button', { name: /add individual extension/i })).toBeInTheDocument();
5551
});
5652

5753
it('renders date extensions list', () => {
58-
render(<RenderWithRouter />);
54+
renderWithAlertAndIntl(<DateExtensionsPage />);
5955
expect(screen.getByText('Ed Byun')).toBeInTheDocument();
6056
expect(screen.getByText('Three body diagrams')).toBeInTheDocument();
6157
});
@@ -65,18 +61,18 @@ describe('DateExtensionsPage', () => {
6561
data: { count: 0, results: [] },
6662
isLoading: true,
6763
});
68-
render(<RenderWithRouter />);
64+
renderWithAlertAndIntl(<DateExtensionsPage />);
6965
expect(screen.getByRole('status')).toBeInTheDocument();
7066
});
7167

7268
it('renders reset link for each row', () => {
73-
render(<RenderWithRouter />);
69+
renderWithAlertAndIntl(<DateExtensionsPage />);
7470
const resetLinks = screen.getAllByRole('button', { name: 'Reset Extensions' });
7571
expect(resetLinks).toHaveLength(mockDateExtensions.length);
7672
});
7773

7874
it('opens reset modal when reset button is clicked', async () => {
79-
render(<RenderWithRouter />);
75+
renderWithAlertAndIntl(<DateExtensionsPage />);
8076
const user = userEvent.setup();
8177
const resetButton = screen.getByRole('button', { name: 'Reset Extensions' });
8278
await user.click(resetButton);
@@ -87,7 +83,7 @@ describe('DateExtensionsPage', () => {
8783
});
8884

8985
it('calls reset mutation when confirm reset is clicked', async () => {
90-
render(<RenderWithRouter />);
86+
renderWithAlertAndIntl(<DateExtensionsPage />);
9187
const user = userEvent.setup();
9288
const resetButton = screen.getByRole('button', { name: 'Reset Extensions' });
9389
await user.click(resetButton);
@@ -97,7 +93,7 @@ describe('DateExtensionsPage', () => {
9793
});
9894

9995
it('closes reset modal when cancel is clicked', async () => {
100-
render(<RenderWithRouter />);
96+
renderWithAlertAndIntl(<DateExtensionsPage />);
10197
const user = userEvent.setup();
10298
const resetButton = screen.getByRole('button', { name: 'Reset Extensions' });
10399
await user.click(resetButton);

src/dateExtensions/DateExtensionsPage.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import { useState } from 'react';
22
import { useParams } from 'react-router-dom';
33
import { useIntl } from '@openedx/frontend-base';
4-
import { AlertModal, Button, Toast } from '@openedx/paragon';
4+
import { Button } from '@openedx/paragon';
55
import messages from './messages';
66
import DateExtensionsList from './components/DateExtensionsList';
77
import ResetExtensionsModal from './components/ResetExtensionsModal';
88
import { LearnerDateExtension } from './types';
99
import { useResetDateExtensionMutation } from './data/apiHook';
10+
import { useAlert } from '@src/providers/AlertProvider';
1011

1112
const DateExtensionsPage = () => {
1213
const intl = useIntl();
1314
const { courseId } = useParams<{ courseId: string }>();
1415
const { mutate: resetMutation } = useResetDateExtensionMutation();
1516
const [isResetModalOpen, setIsResetModalOpen] = useState(false);
1617
const [selectedUser, setSelectedUser] = useState<LearnerDateExtension | null>(null);
17-
const [successMessage, setSuccessMessage] = useState<string>('');
18-
const [errorMessage, setErrorMessage] = useState<string>('');
18+
const { showToast, showModal, removeAlert, clearAlerts } = useAlert();
1919

2020
const handleResetExtensions = (user: LearnerDateExtension) => {
21+
clearAlerts();
2122
setIsResetModalOpen(true);
2223
setSelectedUser(user);
2324
};
@@ -28,11 +29,16 @@ const DateExtensionsPage = () => {
2829
};
2930

3031
const handleErrorOnReset = (error: any) => {
31-
setErrorMessage(error.message);
32+
showModal({
33+
confirmText: intl.formatMessage(messages.close),
34+
message: error.message,
35+
variant: 'default',
36+
onConfirm: (id) => removeAlert(id)
37+
});
3238
};
3339

3440
const handleSuccessOnReset = (response: string) => {
35-
setSuccessMessage(response);
41+
showToast(response);
3642
handleCloseModal();
3743
};
3844

@@ -67,12 +73,6 @@ const DateExtensionsPage = () => {
6773
onClose={handleCloseModal}
6874
onConfirmReset={handleConfirmReset}
6975
/>
70-
<Toast show={!!successMessage} onClose={() => setSuccessMessage('')} className="text-break">
71-
{successMessage}
72-
</Toast>
73-
<AlertModal title={errorMessage} isOpen={!!errorMessage} footerNode={<Button onClick={() => setErrorMessage('')}>{intl.formatMessage(messages.close)}</Button>}>
74-
{errorMessage}
75-
</AlertModal>
7676
</div>
7777
);
7878
};

src/providers/AlertProvider.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface ModalAlert {
2121
isOpen: boolean,
2222
confirmText?: string,
2323
cancelText?: string,
24-
onConfirm?: () => void,
24+
onConfirm?: (id: string) => void,
2525
onCancel?: () => void,
2626
}
2727

@@ -50,7 +50,7 @@ interface AlertContextType {
5050
variant?: 'default' | 'warning' | 'danger' | 'success',
5151
confirmText?: string,
5252
cancelText?: string,
53-
onConfirm?: () => void,
53+
onConfirm?: (id: string) => void,
5454
onCancel?: () => void,
5555
}) => void,
5656
showInlineAlert: (message: string, variant?: 'success' | 'danger' | 'warning' | 'info', dismissible?: boolean) => string,
@@ -103,7 +103,7 @@ export const AlertProvider: FC<AlertProviderProps> = ({ children }) => {
103103
variant?: 'default' | 'warning' | 'danger' | 'success',
104104
confirmText?: string,
105105
cancelText?: string,
106-
onConfirm?: () => void,
106+
onConfirm?: (id: string) => void,
107107
onCancel?: () => void,
108108
}) => {
109109
const id = `modal-${Date.now()}-${Math.random()}`;
@@ -136,7 +136,7 @@ export const AlertProvider: FC<AlertProviderProps> = ({ children }) => {
136136
setModals(prev => {
137137
const modal = prev.find(m => m.id === id);
138138
if (modal?.onConfirm) {
139-
modal.onConfirm();
139+
modal.onConfirm(id);
140140
}
141141
return prev.filter(m => m.id !== id);
142142
});

src/testUtils.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { render } from '@testing-library/react';
22
import { IntlProvider } from '@openedx/frontend-base';
3+
import { AlertProvider } from './providers/AlertProvider';
34

45
export const renderWithIntl = (component) => {
56
return render(<IntlProvider locale="en" messages={{}}>{ component }</IntlProvider>);
67
};
78

9+
export const renderWithAlertAndIntl = (component) => {
10+
return render(
11+
<AlertProvider>
12+
<IntlProvider locale="en" messages={{}}>
13+
{component}
14+
</IntlProvider>
15+
</AlertProvider>
16+
);
17+
};
18+
819
export const createQueryMock = (data: any = undefined, isLoading = false) => ({
920
data,
1021
isLoading,

0 commit comments

Comments
 (0)