richie-education 2.25.0-b2.dev102 → 2.25.0-b2.dev110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/api/joanie.ts +13 -0
- package/js/api/lms/dummy.ts +2 -2
- package/js/hooks/useCourseOrders/index.ts +32 -0
- package/js/{pages/TeacherDashboardContractsLayout/hooks → hooks}/useDefaultOrganizationId/index.spec.tsx +6 -42
- package/js/hooks/useOrganizations/index.ts +4 -4
- package/js/hooks/useResources/index.tsx +2 -0
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +68 -83
- package/js/pages/TeacherDashboardContractsLayout/components/ContractFiltersBar/index.spec.tsx +21 -62
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +1 -1
- package/js/pages/TeacherDashboardContractsLayout/styles.scss +0 -4
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +62 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +123 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnersFiltersBar/index.spec.tsx +70 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnersFiltersBar/index.tsx +31 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +158 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +44 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +385 -0
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +141 -0
- package/js/settings/index.ts +38 -0
- package/js/settings/settings.dev.dist.ts +25 -0
- package/js/{settings.ts → settings/settings.prod.ts} +2 -13
- package/js/settings/settings.test.ts +16 -0
- package/js/types/Joanie.ts +47 -0
- package/js/utils/OrderHelper/index.ts +5 -1
- package/js/utils/test/factories/cunningham.ts +13 -0
- package/js/utils/test/factories/joanie.ts +44 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +8 -2
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +1 -1
- package/js/widgets/Dashboard/components/FilterOrganization/index.tsx +27 -10
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +36 -76
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +6 -1
- package/js/widgets/Dashboard/utils/teacherRouteMessages.tsx +23 -0
- package/js/widgets/Dashboard/utils/teacherRoutes.tsx +31 -0
- package/package.json +1 -1
- package/scss/objects/_dashboard.scss +7 -0
- package/js/settings.dev.dist.ts +0 -3
- /package/js/{pages/TeacherDashboardContractsLayout/hooks → hooks}/useDefaultOrganizationId/index.tsx +0 -0
package/js/api/joanie.ts
CHANGED
|
@@ -190,6 +190,9 @@ export const getRoutes = () => {
|
|
|
190
190
|
products: {
|
|
191
191
|
get: `${baseUrl}/courses/:course_id/products/:id/`,
|
|
192
192
|
},
|
|
193
|
+
orders: {
|
|
194
|
+
get: `${baseUrl}/courses/:course_id/orders/:id/`,
|
|
195
|
+
},
|
|
193
196
|
},
|
|
194
197
|
courseRuns: {
|
|
195
198
|
get: `${baseUrl}/course-runs/:id/`,
|
|
@@ -464,6 +467,16 @@ const API = (): Joanie.API => {
|
|
|
464
467
|
return fetchWithJWT(buildApiUrl(ROUTES.courses.products.get, filters)).then(checkStatus);
|
|
465
468
|
},
|
|
466
469
|
},
|
|
470
|
+
orders: {
|
|
471
|
+
get: async (filters?: Joanie.CourseOrderResourceQuery) => {
|
|
472
|
+
if (!filters || !filters.course_id) {
|
|
473
|
+
throw new Error('A course_id is required to fetch a course order');
|
|
474
|
+
}
|
|
475
|
+
return fetchWithJWT(buildApiUrl(ROUTES.courses.orders.get, filters), {
|
|
476
|
+
method: 'GET',
|
|
477
|
+
}).then(checkStatus);
|
|
478
|
+
},
|
|
479
|
+
},
|
|
467
480
|
},
|
|
468
481
|
courseRuns: {
|
|
469
482
|
get: (filters: Joanie.CourseRunFilters) => {
|
package/js/api/lms/dummy.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type DevDemoUser = keyof typeof JOANIE_DEV_DEMO_USER_JWT_TOKENS;
|
|
|
42
42
|
|
|
43
43
|
export const RICHIE_DUMMY_IS_LOGGED_IN = 'RICHIE_DUMMY_IS_LOGGED_IN';
|
|
44
44
|
|
|
45
|
-
function getUserInfo(username: DevDemoUser):
|
|
45
|
+
function getUserInfo(username: DevDemoUser): User {
|
|
46
46
|
const accessToken = JOANIE_DEV_DEMO_USER_JWT_TOKENS[username];
|
|
47
47
|
const JWTPayload: JWTPayload = JSON.parse(base64Decode(accessToken.split('.')[1]));
|
|
48
48
|
|
|
@@ -78,7 +78,7 @@ const API = (APIConf: LMSBackend | AuthenticationBackend): APILms => {
|
|
|
78
78
|
if (!localStorage.getItem(RICHIE_DUMMY_IS_LOGGED_IN)) {
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
-
return getUserInfo(CURRENT_JOANIE_DEV_DEMO_USER)
|
|
81
|
+
return CURRENT_JOANIE_DEV_DEMO_USER ? getUserInfo(CURRENT_JOANIE_DEV_DEMO_USER) : null;
|
|
82
82
|
},
|
|
83
83
|
login: () => {
|
|
84
84
|
localStorage.setItem(RICHIE_DUMMY_IS_LOGGED_IN, 'true');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl';
|
|
2
|
+
import { useJoanieApi } from 'contexts/JoanieApiContext';
|
|
3
|
+
import { useResource, useResources, UseResourcesProps } from 'hooks/useResources';
|
|
4
|
+
import { API, CourseOrderResourceQuery, NestedCourseOrder } from 'types/Joanie';
|
|
5
|
+
|
|
6
|
+
const messages = defineMessages({
|
|
7
|
+
errorGet: {
|
|
8
|
+
id: 'hooks.useCourseOrders.errorSelect',
|
|
9
|
+
description: 'Error message shown to the user when orders fetch request fails.',
|
|
10
|
+
defaultMessage: 'An error occurred while fetching orders. Please retry later.',
|
|
11
|
+
},
|
|
12
|
+
errorNotFound: {
|
|
13
|
+
id: 'hooks.useCourseOrders.errorNotFound',
|
|
14
|
+
description: 'Error message shown to the user when no orders matches.',
|
|
15
|
+
defaultMessage: 'Cannot find orders',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const props: UseResourcesProps<
|
|
20
|
+
NestedCourseOrder,
|
|
21
|
+
CourseOrderResourceQuery,
|
|
22
|
+
API['courses']['orders']
|
|
23
|
+
> = {
|
|
24
|
+
queryKey: ['courses', 'orders'],
|
|
25
|
+
apiInterface: () => useJoanieApi().courses.orders,
|
|
26
|
+
session: true,
|
|
27
|
+
messages,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const useCourseOrder = useResource(props);
|
|
31
|
+
|
|
32
|
+
export const useCourseOrders = useResources(props);
|
|
@@ -1,30 +1,21 @@
|
|
|
1
|
-
import { QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
-
import { PropsWithChildren } from 'react';
|
|
3
|
-
import { IntlProvider } from 'react-intl';
|
|
4
|
-
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
5
1
|
import { renderHook, waitFor } from '@testing-library/react';
|
|
6
2
|
import fetchMock from 'fetch-mock';
|
|
7
3
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
8
|
-
import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
|
|
9
|
-
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
10
4
|
import { OrganizationFactory } from 'utils/test/factories/joanie';
|
|
11
5
|
import { Organization } from 'types/Joanie';
|
|
6
|
+
import { JoanieAppWrapper, setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
12
7
|
import useDefaultOrganizationId from '.';
|
|
13
8
|
|
|
14
9
|
jest.mock('utils/context', () => ({
|
|
15
10
|
__esModule: true,
|
|
16
11
|
default: mockRichieContextFactory({
|
|
17
12
|
authentication: { backend: 'fonzie', endpoint: 'https://demo.test' },
|
|
18
|
-
joanie_backend: { endpoint: 'https://joanie.
|
|
13
|
+
joanie_backend: { endpoint: 'https://joanie.endpoint' },
|
|
19
14
|
}).one(),
|
|
20
15
|
}));
|
|
21
16
|
|
|
22
|
-
interface WrapperProps {
|
|
23
|
-
routePath: string;
|
|
24
|
-
initialEntry: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
describe('useDefaultOrganizationId', () => {
|
|
18
|
+
setupJoanieSession();
|
|
28
19
|
const organizations: {
|
|
29
20
|
routeOrganization: Organization;
|
|
30
21
|
queryOrganization: Organization;
|
|
@@ -35,33 +26,6 @@ describe('useDefaultOrganizationId', () => {
|
|
|
35
26
|
userOrganizationList: OrganizationFactory().many(2),
|
|
36
27
|
};
|
|
37
28
|
|
|
38
|
-
const Wrapper = ({ children, routePath, initialEntry }: PropsWithChildren<WrapperProps>) => {
|
|
39
|
-
return (
|
|
40
|
-
<IntlProvider locale="en">
|
|
41
|
-
<QueryClientProvider client={createTestQueryClient({ user: true })}>
|
|
42
|
-
<JoanieSessionProvider>
|
|
43
|
-
<MemoryRouter initialEntries={[initialEntry]}>
|
|
44
|
-
<Routes>
|
|
45
|
-
<Route path={routePath} element={children} />
|
|
46
|
-
</Routes>
|
|
47
|
-
</MemoryRouter>
|
|
48
|
-
</JoanieSessionProvider>
|
|
49
|
-
</QueryClientProvider>
|
|
50
|
-
</IntlProvider>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
beforeEach(() => {
|
|
55
|
-
// Joanie provider's calls
|
|
56
|
-
fetchMock.get('https://joanie.test/api/v1.0/orders/', []);
|
|
57
|
-
fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
|
|
58
|
-
fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
afterEach(() => {
|
|
62
|
-
fetchMock.restore();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
29
|
it.each([
|
|
66
30
|
{
|
|
67
31
|
testLabel: 'route organization before query',
|
|
@@ -112,16 +76,16 @@ describe('useDefaultOrganizationId', () => {
|
|
|
112
76
|
}
|
|
113
77
|
|
|
114
78
|
fetchMock.get(
|
|
115
|
-
'https://joanie.
|
|
79
|
+
'https://joanie.endpoint/api/v1.0/organizations/',
|
|
116
80
|
[...userOrganizationList, routeOrganization, queryOrganization].filter(
|
|
117
81
|
(organization) => organization !== undefined,
|
|
118
82
|
),
|
|
119
83
|
);
|
|
120
84
|
const { result } = renderHook(useDefaultOrganizationId, {
|
|
121
85
|
wrapper: ({ children }) => (
|
|
122
|
-
<
|
|
86
|
+
<JoanieAppWrapper routerOptions={{ path: routePath, initialEntries: [initialEntry] }}>
|
|
123
87
|
{children}
|
|
124
|
-
</
|
|
88
|
+
</JoanieAppWrapper>
|
|
125
89
|
),
|
|
126
90
|
});
|
|
127
91
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineMessages } from 'react-intl';
|
|
2
2
|
|
|
3
|
-
import { Organization } from 'types/Joanie';
|
|
3
|
+
import { API, Organization, OrganizationResourceQuery } from 'types/Joanie';
|
|
4
4
|
import { useJoanieApi } from 'contexts/JoanieApiContext';
|
|
5
5
|
import { useResource, useResources, UseResourcesProps } from '../useResources';
|
|
6
6
|
|
|
@@ -21,11 +21,11 @@ const messages = defineMessages({
|
|
|
21
21
|
* Joanie Api hook to retrieve organizations
|
|
22
22
|
* owned by the authenticated user.
|
|
23
23
|
*/
|
|
24
|
-
const props: UseResourcesProps<Organization> = {
|
|
24
|
+
const props: UseResourcesProps<Organization, OrganizationResourceQuery, API['organizations']> = {
|
|
25
25
|
queryKey: ['organizations'],
|
|
26
26
|
apiInterface: () => useJoanieApi().organizations,
|
|
27
27
|
session: true,
|
|
28
28
|
messages,
|
|
29
29
|
};
|
|
30
|
-
export const useOrganizations = useResources<Organization>(props);
|
|
31
|
-
export const useOrganization = useResource<Organization>(props);
|
|
30
|
+
export const useOrganizations = useResources<Organization, OrganizationResourceQuery>(props);
|
|
31
|
+
export const useOrganization = useResource<Organization, OrganizationResourceQuery>(props);
|
|
@@ -40,6 +40,8 @@ export interface UseResourcesCallbackProps<
|
|
|
40
40
|
TResourceQuery extends ResourcesQuery = ResourcesQuery,
|
|
41
41
|
TApiResource extends ApiResourceInterface<TData> = ApiResourceInterface<TData>,
|
|
42
42
|
> extends UseResourcesProps<TData, TResourceQuery, TApiResource> {
|
|
43
|
+
// TODO(rlecellier): we've cases where fitlers should be required.
|
|
44
|
+
// like in useCourseOrder where the course_id filter must be set.
|
|
43
45
|
filters?: TResourceQuery;
|
|
44
46
|
queryOptions?: Omit<QueryOptions<TData>, 'queryFn' | 'queryKey'>;
|
|
45
47
|
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import { IntlProvider } from 'react-intl';
|
|
2
|
-
import { QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
-
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
4
1
|
import fetchMock from 'fetch-mock';
|
|
5
|
-
import {
|
|
6
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { screen } from '@testing-library/react';
|
|
7
3
|
import { getAllByRole } from '@testing-library/dom';
|
|
8
4
|
import userEvent from '@testing-library/user-event';
|
|
9
5
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
10
|
-
import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
|
|
11
|
-
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
12
6
|
import { ContractFactory, OrganizationFactory } from 'utils/test/factories/joanie';
|
|
13
7
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
14
8
|
import { expectBannerError } from 'utils/test/expectBanner';
|
|
15
9
|
import { HttpStatusCode } from 'utils/errors/HttpError';
|
|
10
|
+
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
11
|
+
import { render } from 'utils/test/render';
|
|
16
12
|
import TeacherDashboardContracts from '.';
|
|
17
13
|
|
|
18
14
|
jest.mock('utils/context', () => ({
|
|
19
15
|
__esModule: true,
|
|
20
16
|
default: mockRichieContextFactory({
|
|
21
17
|
authentication: { backend: 'fonzie', endpoint: 'https://auth.endpoint.test' },
|
|
22
|
-
joanie_backend: { endpoint: 'https://joanie.
|
|
18
|
+
joanie_backend: { endpoint: 'https://joanie.endpoint' },
|
|
23
19
|
}).one(),
|
|
24
20
|
}));
|
|
25
21
|
|
|
@@ -34,36 +30,8 @@ jest.mock('components/ContractFrame', () => ({
|
|
|
34
30
|
}) => isOpen && <p>ContractFrame opened for {organizationId}</p>,
|
|
35
31
|
}));
|
|
36
32
|
|
|
37
|
-
const Wrapper = ({ path, initialEntry }: { path: string; initialEntry: string }) => {
|
|
38
|
-
return (
|
|
39
|
-
<IntlProvider locale="en">
|
|
40
|
-
<QueryClientProvider client={createTestQueryClient({ user: true })}>
|
|
41
|
-
<JoanieSessionProvider>
|
|
42
|
-
<CunninghamProvider>
|
|
43
|
-
<MemoryRouter initialEntries={[initialEntry]}>
|
|
44
|
-
<Routes>
|
|
45
|
-
<Route path={path} element={<TeacherDashboardContracts />} />
|
|
46
|
-
</Routes>
|
|
47
|
-
</MemoryRouter>
|
|
48
|
-
</CunninghamProvider>
|
|
49
|
-
</JoanieSessionProvider>
|
|
50
|
-
</QueryClientProvider>
|
|
51
|
-
</IntlProvider>
|
|
52
|
-
);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
33
|
describe('pages/TeacherDashboardContracts', () => {
|
|
56
|
-
|
|
57
|
-
// Joanie providers calls
|
|
58
|
-
fetchMock.get('https://joanie.test/api/v1.0/orders/', []);
|
|
59
|
-
fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
|
|
60
|
-
fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
fetchMock.restore();
|
|
65
|
-
jest.resetAllMocks();
|
|
66
|
-
});
|
|
34
|
+
setupJoanieSession();
|
|
67
35
|
|
|
68
36
|
it('should render a list of contracts for a course product relation', async () => {
|
|
69
37
|
const contracts = ContractFactory({
|
|
@@ -74,24 +42,24 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
74
42
|
const defaultOrganization = organizations[0];
|
|
75
43
|
|
|
76
44
|
// OrganizationContractFilter request all organizations forwho the user have access
|
|
77
|
-
fetchMock.get(`https://joanie.
|
|
45
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, organizations);
|
|
78
46
|
// TeacherDashboardContracts request a paginated list of contracts to display
|
|
79
47
|
fetchMock.get(
|
|
80
|
-
`https://joanie.
|
|
48
|
+
`https://joanie.endpoint/api/v1.0/organizations/${defaultOrganization.id}/contracts/?course_product_relation_id=2&signature_state=signed&page=1&page_size=25`,
|
|
81
49
|
{ results: contracts, count: 0, previous: null, next: null },
|
|
82
50
|
);
|
|
83
51
|
// useTeacherContractsToSign request all contract to sign, without pagination
|
|
84
52
|
fetchMock.get(
|
|
85
|
-
`https://joanie.
|
|
53
|
+
`https://joanie.endpoint/api/v1.0/organizations/${defaultOrganization.id}/contracts/?signature_state=half_signed&course_product_relation_id=2`,
|
|
86
54
|
{ results: [], count: 0, previous: null, next: null },
|
|
87
55
|
);
|
|
88
56
|
|
|
89
|
-
render(
|
|
90
|
-
|
|
91
|
-
path
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
57
|
+
render(<TeacherDashboardContracts />, {
|
|
58
|
+
routerOptions: {
|
|
59
|
+
path: '/courses/:courseId/products/:courseProductRelationId/contracts',
|
|
60
|
+
initialEntries: ['/courses/1/products/2/contracts'],
|
|
61
|
+
},
|
|
62
|
+
});
|
|
95
63
|
|
|
96
64
|
await expectNoSpinner();
|
|
97
65
|
|
|
@@ -139,21 +107,24 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
139
107
|
organization_signed_on: Date.toString(),
|
|
140
108
|
}).many(3);
|
|
141
109
|
|
|
110
|
+
// OrganizationContractFilter request all organizations forwho the user have access
|
|
111
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
112
|
+
|
|
142
113
|
fetchMock.get(
|
|
143
|
-
`https://joanie.
|
|
114
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=signed&page=1&page_size=25`,
|
|
144
115
|
{ results: contracts, count: 0, previous: null, next: null },
|
|
145
116
|
);
|
|
146
117
|
fetchMock.get(
|
|
147
|
-
`https://joanie.
|
|
118
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=half_signed`,
|
|
148
119
|
{ results: [], count: 0, previous: null, next: null },
|
|
149
120
|
);
|
|
150
121
|
|
|
151
|
-
render(
|
|
152
|
-
|
|
153
|
-
path
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
122
|
+
render(<TeacherDashboardContracts />, {
|
|
123
|
+
routerOptions: {
|
|
124
|
+
path: '/organizations/:organizationId/contracts',
|
|
125
|
+
initialEntries: ['/organizations/1/contracts'],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
157
128
|
|
|
158
129
|
await expectNoSpinner();
|
|
159
130
|
|
|
@@ -193,21 +164,23 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
193
164
|
});
|
|
194
165
|
|
|
195
166
|
it('should render an empty table if there are no contracts', async () => {
|
|
167
|
+
// OrganizationContractFilter request all organizations forwho the user have access
|
|
168
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
196
169
|
fetchMock.get(
|
|
197
|
-
`https://joanie.
|
|
170
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=signed&page=1&page_size=25`,
|
|
198
171
|
{ results: [], count: 0, previous: null, next: null },
|
|
199
172
|
);
|
|
200
173
|
fetchMock.get(
|
|
201
|
-
`https://joanie.
|
|
174
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=half_signed`,
|
|
202
175
|
{ results: [], count: 0, previous: null, next: null },
|
|
203
176
|
);
|
|
204
177
|
|
|
205
|
-
render(
|
|
206
|
-
|
|
207
|
-
path
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
178
|
+
render(<TeacherDashboardContracts />, {
|
|
179
|
+
routerOptions: {
|
|
180
|
+
path: '/organizations/:organizationId/contracts',
|
|
181
|
+
initialEntries: ['/organizations/1/contracts'],
|
|
182
|
+
},
|
|
183
|
+
});
|
|
211
184
|
|
|
212
185
|
await expectNoSpinner();
|
|
213
186
|
|
|
@@ -239,25 +212,27 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
239
212
|
abilities: { sign: true },
|
|
240
213
|
}).many(3);
|
|
241
214
|
|
|
215
|
+
// OrganizationContractFilter request all organizations forwho the user have access
|
|
216
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
242
217
|
fetchMock.get(
|
|
243
|
-
`https://joanie.
|
|
218
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=signed&page=1&page_size=25`,
|
|
244
219
|
{ results: [], count: 0, previous: null, next: null },
|
|
245
220
|
);
|
|
246
221
|
fetchMock.get(
|
|
247
|
-
`https://joanie.
|
|
222
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=half_signed&page=1&page_size=25`,
|
|
248
223
|
{ results: contracts, count: 3, previous: null, next: null },
|
|
249
224
|
);
|
|
250
225
|
fetchMock.get(
|
|
251
|
-
`https://joanie.
|
|
226
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=half_signed`,
|
|
252
227
|
{ results: contracts, count: 3, previous: null, next: null },
|
|
253
228
|
);
|
|
254
229
|
|
|
255
|
-
render(
|
|
256
|
-
|
|
257
|
-
path
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
230
|
+
render(<TeacherDashboardContracts />, {
|
|
231
|
+
routerOptions: {
|
|
232
|
+
path: '/organizations/:organizationId/contracts',
|
|
233
|
+
initialEntries: ['/organizations/1/contracts?signature_state=half_signed'],
|
|
234
|
+
},
|
|
235
|
+
});
|
|
261
236
|
|
|
262
237
|
await expectNoSpinner();
|
|
263
238
|
|
|
@@ -306,21 +281,24 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
306
281
|
});
|
|
307
282
|
|
|
308
283
|
it('should render an error banner if an error occured during contracts fetching', async () => {
|
|
284
|
+
// OrganizationContractFilter request all organizations forwho the user have access
|
|
285
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
286
|
+
|
|
309
287
|
fetchMock.get(
|
|
310
|
-
`https://joanie.
|
|
288
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=signed&page=1&page_size=25`,
|
|
311
289
|
HttpStatusCode.NOT_FOUND,
|
|
312
290
|
);
|
|
313
291
|
fetchMock.get(
|
|
314
|
-
`https://joanie.
|
|
292
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/contracts/?signature_state=half_signed`,
|
|
315
293
|
HttpStatusCode.NOT_FOUND,
|
|
316
294
|
);
|
|
317
295
|
|
|
318
|
-
render(
|
|
319
|
-
|
|
320
|
-
path
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
);
|
|
296
|
+
render(<TeacherDashboardContracts />, {
|
|
297
|
+
routerOptions: {
|
|
298
|
+
path: '/organizations/:organizationId/contracts',
|
|
299
|
+
initialEntries: ['/organizations/1/contracts'],
|
|
300
|
+
},
|
|
301
|
+
});
|
|
324
302
|
|
|
325
303
|
await expectNoSpinner();
|
|
326
304
|
await expectBannerError('An error occurred while fetching contracts. Please retry later.');
|
|
@@ -328,21 +306,28 @@ describe('pages/TeacherDashboardContracts', () => {
|
|
|
328
306
|
|
|
329
307
|
it('should hide organization filter when user only have one organization', async () => {
|
|
330
308
|
const defaultOrganization = OrganizationFactory().one();
|
|
331
|
-
fetchMock.get('https://joanie.
|
|
309
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', [defaultOrganization]);
|
|
332
310
|
|
|
333
311
|
const contracts = ContractFactory({
|
|
334
312
|
student_signed_on: Date.toString(),
|
|
335
313
|
abilities: { sign: true },
|
|
336
314
|
}).many(3);
|
|
337
315
|
fetchMock.get(
|
|
338
|
-
`https://joanie.
|
|
316
|
+
`https://joanie.endpoint/api/v1.0/organizations/${defaultOrganization.id}/contracts/?signature_state=signed&page=1&page_size=25`,
|
|
339
317
|
{ results: [], count: 0, previous: null, next: null },
|
|
340
318
|
);
|
|
341
319
|
fetchMock.get(
|
|
342
|
-
`https://joanie.
|
|
320
|
+
`https://joanie.endpoint/api/v1.0/organizations/${defaultOrganization.id}/contracts/?signature_state=half_signed`,
|
|
343
321
|
{ results: contracts, count: 3, previous: null, next: null },
|
|
344
322
|
);
|
|
345
|
-
|
|
323
|
+
|
|
324
|
+
render(<TeacherDashboardContracts />, {
|
|
325
|
+
routerOptions: {
|
|
326
|
+
path: '/',
|
|
327
|
+
initialEntries: ['/'],
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
346
331
|
await expectNoSpinner();
|
|
347
332
|
|
|
348
333
|
// Signature state filter should have been rendered
|
package/js/pages/TeacherDashboardContractsLayout/components/ContractFiltersBar/index.spec.tsx
CHANGED
|
@@ -1,50 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
-
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
4
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
-
import { PropsWithChildren } from 'react';
|
|
1
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
6
2
|
import fetchMock from 'fetch-mock';
|
|
7
3
|
import { userEvent } from '@testing-library/user-event';
|
|
8
4
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
9
|
-
import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
|
|
10
|
-
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
11
5
|
import { ContractState } from 'types/Joanie';
|
|
12
6
|
import { OrganizationFactory } from 'utils/test/factories/joanie';
|
|
13
7
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
14
8
|
import { noop } from 'utils';
|
|
9
|
+
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
10
|
+
import { render } from 'utils/test/render';
|
|
15
11
|
import ContractFiltersBar from '.';
|
|
16
12
|
|
|
17
13
|
jest.mock('utils/context', () => ({
|
|
18
14
|
__esModule: true,
|
|
19
15
|
default: mockRichieContextFactory({
|
|
20
16
|
authentication: { backend: 'fonzie', endpoint: 'https://auth.endpoint.test' },
|
|
21
|
-
joanie_backend: { endpoint: 'https://joanie.
|
|
17
|
+
joanie_backend: { endpoint: 'https://joanie.endpoint' },
|
|
22
18
|
}).one(),
|
|
23
19
|
}));
|
|
24
20
|
|
|
25
21
|
describe('<ContractFiltersBar/>', () => {
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<IntlProvider locale="en">
|
|
29
|
-
<QueryClientProvider client={createTestQueryClient({ user: true })}>
|
|
30
|
-
<JoanieSessionProvider>
|
|
31
|
-
<CunninghamProvider>{children}</CunninghamProvider>
|
|
32
|
-
</JoanieSessionProvider>
|
|
33
|
-
</QueryClientProvider>
|
|
34
|
-
</IntlProvider>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
fetchMock.get('https://joanie.test/api/v1.0/orders/', []);
|
|
40
|
-
fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
|
|
41
|
-
fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
afterEach(() => {
|
|
45
|
-
fetchMock.restore();
|
|
46
|
-
jest.resetAllMocks();
|
|
47
|
-
});
|
|
22
|
+
setupJoanieSession();
|
|
48
23
|
|
|
49
24
|
it('should render SignatureState and Organization filters with default value', async () => {
|
|
50
25
|
const filterChange = jest.fn();
|
|
@@ -54,13 +29,9 @@ describe('<ContractFiltersBar/>', () => {
|
|
|
54
29
|
organization_id: organizations[1].id,
|
|
55
30
|
};
|
|
56
31
|
|
|
57
|
-
fetchMock.get('https://joanie.
|
|
32
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', organizations);
|
|
58
33
|
|
|
59
|
-
render(
|
|
60
|
-
<Wrapper>
|
|
61
|
-
<ContractFiltersBar onFiltersChange={filterChange} defaultValues={defaultValues} />
|
|
62
|
-
</Wrapper>,
|
|
63
|
-
);
|
|
34
|
+
render(<ContractFiltersBar onFiltersChange={filterChange} defaultValues={defaultValues} />);
|
|
64
35
|
|
|
65
36
|
await waitFor(() => expectNoSpinner());
|
|
66
37
|
|
|
@@ -101,16 +72,14 @@ describe('<ContractFiltersBar/>', () => {
|
|
|
101
72
|
organization_id: organizations[0].id,
|
|
102
73
|
};
|
|
103
74
|
|
|
104
|
-
fetchMock.get('https://joanie.
|
|
75
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', organizations);
|
|
105
76
|
|
|
106
77
|
render(
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/>
|
|
113
|
-
</Wrapper>,
|
|
78
|
+
<ContractFiltersBar
|
|
79
|
+
onFiltersChange={noop}
|
|
80
|
+
defaultValues={defaultValues}
|
|
81
|
+
hideFilterSignatureState={true}
|
|
82
|
+
/>,
|
|
114
83
|
);
|
|
115
84
|
|
|
116
85
|
await waitFor(() => expectNoSpinner());
|
|
@@ -132,16 +101,14 @@ describe('<ContractFiltersBar/>', () => {
|
|
|
132
101
|
organization_id: organizations[0].id,
|
|
133
102
|
};
|
|
134
103
|
|
|
135
|
-
fetchMock.get('https://joanie.
|
|
104
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', organizations);
|
|
136
105
|
|
|
137
106
|
render(
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/>
|
|
144
|
-
</Wrapper>,
|
|
107
|
+
<ContractFiltersBar
|
|
108
|
+
onFiltersChange={noop}
|
|
109
|
+
defaultValues={defaultValues}
|
|
110
|
+
hideFilterOrganization={true}
|
|
111
|
+
/>,
|
|
145
112
|
);
|
|
146
113
|
|
|
147
114
|
await waitFor(() => expectNoSpinner());
|
|
@@ -161,19 +128,11 @@ describe('<ContractFiltersBar/>', () => {
|
|
|
161
128
|
const organizations = OrganizationFactory().many(2);
|
|
162
129
|
const handleChange = jest.fn();
|
|
163
130
|
|
|
164
|
-
fetchMock.get('https://joanie.
|
|
131
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', organizations);
|
|
165
132
|
|
|
166
|
-
render(
|
|
167
|
-
<Wrapper>
|
|
168
|
-
<ContractFiltersBar onFiltersChange={handleChange} />
|
|
169
|
-
</Wrapper>,
|
|
170
|
-
);
|
|
133
|
+
render(<ContractFiltersBar onFiltersChange={handleChange} />);
|
|
171
134
|
|
|
172
135
|
await waitFor(() => expectNoSpinner());
|
|
173
|
-
|
|
174
|
-
// Organization filter should have been rendered and the first organization should be selected
|
|
175
|
-
const organizationFilter = screen.getByRole('combobox', { name: 'Organization' });
|
|
176
|
-
expect(organizationFilter).toHaveAttribute('value', organizations[0].title);
|
|
177
136
|
expect(handleChange).toHaveBeenNthCalledWith(1, { organization_id: organizations[0].id });
|
|
178
137
|
});
|
|
179
138
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
3
|
+
import useDefaultOrganizationId from 'hooks/useDefaultOrganizationId';
|
|
3
4
|
import { ContractResourceQuery, ContractState } from 'types/Joanie';
|
|
4
|
-
import useDefaultOrganizationId from '../useDefaultOrganizationId';
|
|
5
5
|
|
|
6
6
|
export type TeacherDashboardContractsParams = {
|
|
7
7
|
organizationId?: string;
|