Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NODE_ENV='development'
PORT=8080
PORT=1998
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='http://localhost:8080'
BASE_URL='http://localhost:1998'
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
Expand Down
3 changes: 2 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='http://localhost:1995'
BASE_URL='http://localhost:1998'
CREDENTIALS_BASE_URL='http://localhost:18150'
SUPPORT_URL='http://support.example.com'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
Expand Down
3 changes: 2 additions & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"property-no-unknown": [true, {
"ignoreProperties": ["xs", "sm", "md", "lg", "xl", "xxl"]
}],
"alpha-value-notation": "number"
"alpha-value-notation": "number",
"string-quotes": "double"
}
}
9 changes: 8 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ frontend-app-catalog
Purpose
*******

This is the Catalog micro-frontend application, currently under development.
This is the Catalog micro-frontend application, currently under development by `2U <https://2u.com>`_.

**What is the domain of this MFE?**

Expand Down Expand Up @@ -70,6 +70,13 @@ The dev server is running at `http://apps.local.openedx.io:1998/catalog/ <http:/
`Tutor <https://github.com/overhangio/tutor>`_. If you start Tutor with ``tutor dev start catalog``
that should give you everything you need as a companion to this frontend.

.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-app-catalog.svg?branch=master
:target: https://travis-ci.com/edx/frontend-app-catalog
.. |Codecov| image:: https://codecov.io/gh/edx/frontend-app-catalog/branch/master/graph/badge.svg
:target: https://codecov.io/gh/edx/frontend-app-catalog
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-catalog.svg
:target: @edx/frontend-app-catalog

Internationalization
====================

Expand Down
118 changes: 118 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useCourseDiscovery } from './data/course-discovery/hooks';
import { useHomeSettingsQuery } from './home/data/hooks';
import { mockHomeSettingsResponse } from './home/__mocks__';
import { mockCourseDiscoveryResponse } from './__mocks__';
import {
render, within, waitFor, screen,
} from './setupTest';
import { ROUTES } from './routes';
import App from './App';

import messages from './сatalog/messages';

jest.mock('@edx/frontend-platform', () => ({
getAuthenticatedUser: jest.fn(() => ({ username: 'test-user', roles: [] })),
getConfig: jest.fn(() => ({
LMS_BASE_URL: '',
ENABLE_PROGRAMS: true,
ENABLE_COURSE_DISCOVERY: true,
})),
}));

jest.mock('./home/data/hooks', () => ({
useHomeSettingsQuery: jest.fn(),
}));

jest.mock('./сatalog/data/hooks', () => ({
useCourseDiscovery: jest.fn(),
}));

const mockHomeSettings = useHomeSettingsQuery as jest.Mock;
const mockCourseDiscovery = useCourseDiscovery as jest.Mock;

jest.mock('@edx/frontend-platform/react', () => ({
AppProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="app-provider">{children}</div>,
}));

jest.mock('@edx/frontend-component-header', () => function getHeader() {
return <div data-testid="header" />;
});

jest.mock('@edx/frontend-component-footer', () => ({
FooterSlot: () => <div data-testid="footer" />,
}));

describe('App', () => {
beforeEach(() => {
document.body.innerHTML = '';
});

mockHomeSettings.mockReturnValue({
data: mockHomeSettingsResponse,
isLoading: false,
isError: false,
});

mockCourseDiscovery.mockReturnValue({
data: mockCourseDiscoveryResponse,
isLoading: false,
isError: false,
});

it('renders HomePage on "/" route', async () => {
window.testHistory = [ROUTES.HOME];

render(<App />);

await waitFor(() => {
expect(screen.queryByTestId('spinner')).not.toBeInTheDocument();
});

expect(screen.getByTestId('home-banner')).toBeInTheDocument();
});

it('renders CatalogPage with course cards at /courses route', async () => {
window.testHistory = [ROUTES.COURSES];

render(<App />);

await waitFor(() => {
expect(screen.queryByTestId('spinner')).not.toBeInTheDocument();
});

expect(
screen.getByText(
messages.totalCoursesHeading.defaultMessage.replace(
'{totalCourses}',
mockCourseDiscoveryResponse.results.length,
),
),
).toBeInTheDocument();

const courseCards = screen.getAllByRole('link');
expect(courseCards.length).toBe(mockCourseDiscoveryResponse.results.length);

courseCards.forEach((card, index) => {
const course = mockCourseDiscoveryResponse.results[index];
const cardContent = within(card);

expect(card).toHaveAttribute('href', `/courses/${course.id}/about`);
expect(cardContent.getByText(course.data.content.displayName)).toBeInTheDocument();
expect(cardContent.getByText(course.data.org)).toBeInTheDocument();
});
});

it('renders CourseAboutPage on "/courses/some-course-id/about"', () => {
window.testHistory = [ROUTES.COURSE_ABOUT];

render(<App />);
expect(screen.getByTestId('course-about-page')).toBeInTheDocument();
});

it('renders NotFoundPage on unknown route', () => {
window.testHistory = ['/some-unknown-path'];

render(<App />);
expect(screen.getByTestId('not-found-page')).toBeInTheDocument();
});
});
32 changes: 32 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AppProvider } from '@edx/frontend-platform/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Route, Routes } from 'react-router-dom';
import { FooterSlot } from '@edx/frontend-component-footer';
import Header from '@edx/frontend-component-header';

import HomePage from './home/HomePage';
import CatalogPage from './сatalog/CatalogPage';
import CourseAboutPage from './course-about/CourseAboutPage';
import NotFoundPage from './not-found-page/NotFoundPage';
import { ROUTES } from './routes';

const queryClient = new QueryClient();

const App = () => (
<AppProvider>
<QueryClientProvider client={queryClient}>
<Header />
<main className="d-flex flex-column flex-grow-1">
<Routes>
<Route path={ROUTES.HOME} element={<HomePage />} />
<Route path={ROUTES.COURSES} element={<CatalogPage />} />
<Route path={ROUTES.COURSE_ABOUT} element={<CourseAboutPage />} />
<Route path={ROUTES.NOT_FOUND} element={<NotFoundPage />} />
</Routes>
</main>
<FooterSlot />
</QueryClientProvider>
</AppProvider>
);

export default App;
20 changes: 20 additions & 0 deletions src/__mocks__/course.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const mockCourseResponse = {
id: 'course-v1:edX+DemoX+Demo_Course',
data: {
id: 'course-v1:edX+DemoX+Demo_Course',
course: 'Demo Course',
start: '2024-04-01T00:00:00Z',
imageUrl: '/asset-v1:edX+DemoX+Demo_Course+type@asset+block@course_image.jpg',
org: 'edX',
orgImg: '/asset-v1:edX+DemoX+Demo_Course+type@asset+block@org_image.jpg',
content: {
displayName: 'Demonstration Course',
overview: 'Course overview',
number: 'DemoX',
},
number: 'DemoX',
modes: ['audit', 'verified'],
language: 'en',
catalogVisibility: 'both',
},
};
100 changes: 100 additions & 0 deletions src/__mocks__/courseDiscovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
export const mockCourseDiscoveryResponse = {
took: 1,
total: 3,
results: [
{
id: 'course-v1:OpenEdx+123+2023',
index: 'course_info',
type: '_doc',
data: {
id: 'course-v1:OpenEdx+123+2023',
course: 'course-v1:OpenEdx+123+2023',
content: {
displayName: 'Test course 1',
overview: 'About This Course Include your long course description here. The long course description should contain 150-400 words. This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags. Requirements Add information about the skills and knowledge students need to take this course. Course Staff Staff Member #1 Biography of instructor/staff member #1 Staff Member #2 Biography of instructor/staff member #2 Frequently Asked Questions What web browser should I use? The Open edX platform works best with current versions of Chrome, Edge, Firefox, or Safari. See our list of supported browsers for the most up-to-date information. Question #2 Your answer would be displayed here. ',
number: '123',
},
imageUrl: '/asset-v1:OpenEdx+123+2023+type@asset+block@images_course_image.jpg',
start: '2030-01-01T00:00:00',
number: '123',
org: 'OpenEdx',
modes: [
'audit',
],
language: 'en',
catalogVisibility: 'both',
},
},
{
id: 'course-v1:OpenEdx+123+2024',
index: 'course_info',
type: '_doc',
data: {
id: 'course-v1:OpenEdx+123+2024',
course: 'course-v1:OpenEdx+123+2024',
content: {
displayName: 'Course test 2',
overview: 'About This Course Include your long course description here. The long course description should contain 150-400 words. This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags. Requirements Add information about the skills and knowledge students need to take this course. Course Staff Staff Member #1 Biography of instructor/staff member #1 Staff Member #2 Biography of instructor/staff member #2 Frequently Asked Questions What web browser should I use? The Open edX platform works best with current versions of Chrome, Edge, Firefox, or Safari. See our list of supported browsers for the most up-to-date information. Question #2 Your answer would be displayed here. ',
number: '312',
},
imageUrl: '/asset-v1:OpenEdx+123+2024+type@asset+block@images_course_image.jpg',
start: '2030-01-01T00:00:00',
number: '312',
org: 'OpenEdx',
modes: [
'audit',
],
language: 'en',
catalogVisibility: 'both',
},
},
{
id: 'course-v1:dev+654+2024',
index: 'course_info',
type: '_doc',
data: {
id: 'course-v1:dev+654+2024',
course: 'course-v1:dev+654+2024',
content: {
displayName: 'Course test 3',
overview: 'About This Course Include your long course description here. The long course description should contain 150-400 words. This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags. Requirements Add information about the skills and knowledge students need to take this course. Course Staff Staff Member #1 Biography of instructor/staff member #1 Staff Member #2 Biography of instructor/staff member #2 Frequently Asked Questions What web browser should I use? The Open edX platform works best with current versions of Chrome, Edge, Firefox, or Safari. See our list of supported browsers for the most up-to-date information. Question #2 Your answer would be displayed here. ',
number: '654',
},
imageUrl: '/asset-v1:dev+654+2024+type@asset+block@images_course_image.jpg',
start: '2030-01-01T00:00:00',
number: '654',
org: 'dev',
modes: [
'audit',
],
language: 'en',
catalogVisibility: 'both',
},
},
],
aggs: {
language: {
terms: {
en: 3,
},
total: 3,
other: 0,
},
modes: {
terms: {
audit: 3,
},
total: 3,
other: 0,
},
org: {
terms: {
dev: 1,
openedx: 1,
},
total: 3,
other: 0,
},
},
maxScore: 1.0,
};
2 changes: 2 additions & 0 deletions src/__mocks__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { mockCourseResponse } from './course';
export { mockCourseDiscoveryResponse } from './courseDiscovery';
Binary file added src/assets/images/home-banner.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/images/no-course-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/images/no-org-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/assets/scss/_animations.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@keyframes home-banner-info {
0% {
opacity: 0;
transform: translateY(18.75rem);
}

45% {
opacity: 1;
}

65% {
transform: translateY(-2.5rem);
}

85% {
transform: translateY(.625rem);
}

100% {
top: 0;
transform: translateY(0);
}
}
15 changes: 15 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Feature policy for iframe, allowing access to certain courseware-related media.
*
* We must use the wildcard (*) origin for each feature, as courseware content
* may be embedded in external iframes. Notably, xblock-lti-consumer is a popular
* block that iframes external course content.

* This policy was selected in conference with the edX Security Working Group.
* Changes to it should be vetted by them ([email protected]).
*/
export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *'
);

export const DEFAULT_VIDEO_MODAL_HEIGHT = 500;
Loading