diff --git a/.github/workflows/add-depr-ticket-to-depr-board.yml b/.github/workflows/add-depr-ticket-to-depr-board.yml index 73ca4c5c..250e394a 100644 --- a/.github/workflows/add-depr-ticket-to-depr-board.yml +++ b/.github/workflows/add-depr-ticket-to-depr-board.yml @@ -16,4 +16,4 @@ jobs: secrets: GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }} GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }} - SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }} \ No newline at end of file + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 711ac589..9b825b5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: Default CI on: push: branches: - - 'main' + - 'main' pull_request: branches: - '**' @@ -14,26 +14,26 @@ jobs: node: [16] npm: [8.5.x] steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup Nodejs - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node }} - - run: npm install -g npm@${{ matrix.npm }} - - name: Install dependencies - run: npm ci - - name: Validate package-lock.json changes - run: make validate-no-uncommitted-package-lock-changes - - name: Lint - run: npm run lint - - name: Test - run: npm run test - - name: Build - run: npm run build - - name: i18n_extract - run: npm run i18n_extract - - name: Coverage - uses: codecov/codecov-action@v1 + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Nodejs + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - run: npm install -g npm@${{ matrix.npm }} + - name: Install dependencies + run: npm ci + - name: Validate package-lock.json changes + run: make validate-no-uncommitted-package-lock-changes + - name: Lint + run: npm run lint + - name: Test + run: npm run test + - name: Build + run: npm run build + - name: i18n_extract + run: npm run i18n_extract + - name: Coverage + uses: codecov/codecov-action@v1 diff --git a/.github/workflows/lockfileversion-check.yml b/.github/workflows/lockfileversion-check.yml index 42312e8c..2bfd42e6 100644 --- a/.github/workflows/lockfileversion-check.yml +++ b/.github/workflows/lockfileversion-check.yml @@ -5,7 +5,7 @@ name: Lockfile Version check on: push: branches: - - master + - master pull_request: jobs: diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..0dd641bc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +# Ignore artifacts: +node_modules +.babelrc +.eslintignore +.eslintrc.json +.gitignore +.npmignore +commitlint.config.js \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..dc75c8a8 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/__mocks__/react-instantsearch-dom.jsx b/__mocks__/react-instantsearch-dom.jsx index 81659d40..dae2d508 100644 --- a/__mocks__/react-instantsearch-dom.jsx +++ b/__mocks__/react-instantsearch-dom.jsx @@ -12,14 +12,20 @@ const advertisedCourseRun = { const fakeHits = [ { - objectID: '1', title: 'bla', advertised_course_run: advertisedCourseRun, key: 'Bees101', + objectID: '1', + title: 'bla', + advertised_course_run: advertisedCourseRun, + key: 'Bees101', }, { - objectID: '2', title: 'blp', advertised_course_run: advertisedCourseRun, key: 'Wasps200', + objectID: '2', + title: 'blp', + advertised_course_run: advertisedCourseRun, + key: 'Wasps200', }, ]; -MockReactInstantSearch.connectStateResults = Component => function (props) { +MockReactInstantSearch.connectStateResults = (Component) => function (props) { return ( function (props) { ); }; -MockReactInstantSearch.connectPagination = Component => function (props) { +MockReactInstantSearch.connectPagination = (Component) => function (props) { return ; }; @@ -47,11 +53,11 @@ MockReactInstantSearch.InstantSearch = function ({ children }) { return
{children}
; }; -MockReactInstantSearch.connectCurrentRefinements = Component => function (props) { +MockReactInstantSearch.connectCurrentRefinements = (Component) => function (props) { return ; }; -MockReactInstantSearch.connectRefinementList = Component => function (props) { +MockReactInstantSearch.connectRefinementList = (Component) => function (props) { return ( function (props) { ); }; -MockReactInstantSearch.connectSearchBox = Component => function (props) { +MockReactInstantSearch.connectSearchBox = (Component) => function (props) { return ; }; -MockReactInstantSearch.connectPagination = Component => function (props) { +MockReactInstantSearch.connectPagination = (Component) => function (props) { return ; }; diff --git a/jest.config.js b/jest.config.js index 7c936b4c..da98d85b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,6 @@ const { createConfig } = require('@edx/frontend-build'); module.exports = createConfig('jest', { - setupFiles: [ - '/src/setupTest.js', - ], - coveragePathIgnorePatterns: [ - 'src/setupTest.js', - 'src/i18n', - ], + setupFiles: ['/src/setupTest.js'], + coveragePathIgnorePatterns: ['src/setupTest.js', 'src/i18n'], }); diff --git a/package-lock.json b/package-lock.json index 380b8e5d..37cc1c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "glob": "7.2.3", "husky": "6.0.0", "jest": "24.9.0", + "prettier": "2.7.1", "reactifex": "1.1.1" } }, @@ -21424,6 +21425,21 @@ "node": ">=0.10.0" } }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -42913,6 +42929,12 @@ "dev": true, "optional": true }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", diff --git a/package.json b/package.json index b8be5941..92d13ca1 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "glob": "7.2.3", "husky": "6.0.0", "jest": "24.9.0", + "prettier": "2.7.1", "reactifex": "1.1.1" } } diff --git a/public/index.html b/public/index.html index 4412e75d..c9565611 100644 --- a/public/index.html +++ b/public/index.html @@ -1,16 +1,18 @@ - + + + Enterprise Public Catalog + + + + + - - Enterprise Public Catalog - - - - - - - -
- - + +
+ diff --git a/renovate.json b/renovate.json index 3a7bedbd..710e502c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,5 @@ { - "extends": [ - "config:base" - ], + "extends": ["config:base"], "packageRules": [ { "matchUpdateTypes": ["minor", "patch"], diff --git a/src/components/PageWrapper.jsx b/src/components/PageWrapper.jsx index 11f299f0..992c95bd 100644 --- a/src/components/PageWrapper.jsx +++ b/src/components/PageWrapper.jsx @@ -11,9 +11,7 @@ function PageWrapper({ children, className }) { return ( -
- {children} -
+
{children}
); } diff --git a/src/components/app/tests/App.test.jsx b/src/components/app/tests/App.test.jsx index 34046279..c7dc2f83 100644 --- a/src/components/app/tests/App.test.jsx +++ b/src/components/app/tests/App.test.jsx @@ -17,20 +17,18 @@ getAuthenticatedUser.mockReturnValue({ username: 'test-username' }); // all we are testing is routes, we don't care what's rendered as long as it's the right page jest.mock('react-instantsearch-dom', () => ({ ...jest.requireActual('react-instantsearch-dom'), - InstantSearch: () => (
SEARCH
), - Index: () => (
SEARCH
), + InstantSearch: () =>
SEARCH
, + Index: () =>
SEARCH
, })); mockWindowLocations(); -const mockConfig = () => ( - { - HUBSPOT_MARKETING_URL: 'http://bobsdooremporium.com', - EDX_FOR_BUSINESS_TITLE: 'ayylmao', - EDX_FOR_ONLINE_EDU_TITLE: 'foo', - EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', - } -); +const mockConfig = () => ({ + HUBSPOT_MARKETING_URL: 'http://bobsdooremporium.com', + EDX_FOR_BUSINESS_TITLE: 'ayylmao', + EDX_FOR_ONLINE_EDU_TITLE: 'foo', + EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), diff --git a/src/components/catalogInfoModal/CatalogInfoModal.jsx b/src/components/catalogInfoModal/CatalogInfoModal.jsx index fa9253d5..cb1bac53 100644 --- a/src/components/catalogInfoModal/CatalogInfoModal.jsx +++ b/src/components/catalogInfoModal/CatalogInfoModal.jsx @@ -18,7 +18,9 @@ import CatalogProgramModalBanner from '../catalogModalBanner/CatalogProgramModal function SkillsListing({ skillNames }) { return (
    - {skillNames.slice(0, 5).map(s =>
  • {s}
  • )} + {skillNames.slice(0, 5).map((s) => ( +
  • {s}
  • + ))}
); } @@ -58,19 +60,26 @@ function CourseModal({ > - + - +
- { courseTitle && ( - - {courseTitle} - + {courseTitle && ( + + {courseTitle} + )} - { courseProvider && ( - - {courseProvider} - + {courseProvider && ( + + {courseProvider} + )}

- {intl.formatMessage(messages['catalogInfoModal.courseDescriptionTitle'])} + {intl.formatMessage( + messages['catalogInfoModal.courseDescriptionTitle'], + )}

{/* eslint-disable-next-line react/no-danger */}
- {(skillNames && skillNames.length > 0) && ( + {skillNames && skillNames.length > 0 && (

- {intl.formatMessage(messages['catalogInfoModal.relatedSkillsHeading'])} + {intl.formatMessage( + messages['catalogInfoModal.relatedSkillsHeading'], + )}

@@ -106,7 +119,9 @@ function CourseModal({ rel="noopener noreferrer" > @@ -142,7 +157,7 @@ CourseModal.propTypes = { function CourseDisplayForProgram({ course }) { const { image, title, short_description: desc } = course; // removing html tags in description - const regex = /(<([^>]+)>)/ig; + const regex = /(<([^>]+)>)/gi; const newDesc = desc.replace(regex, ''); // TODO: we can change it to just image once catalog server is updated // currently image is coming out as { src: 'url' }, instead we can just go with image: 'url' @@ -154,13 +169,15 @@ function CourseDisplayForProgram({ course }) { return (
- +

{title}

-

- {newDesc} -

+

{newDesc}

); @@ -190,11 +207,11 @@ function ProgramModal({ programCourses, } = selectedProgram; - const prices = programPrices?.filter(item => item.currency === 'USD'); + const prices = programPrices?.filter((item) => item.currency === 'USD'); const usdPrice = prices && prices.length > 0 ? `$${prices[0].total}` : '$0'; - const bulletedList = items => { - const itemsList = items.map(item =>
  • {item}
  • ); + const bulletedList = (items) => { + const itemsList = items.map((item) =>
  • {item}
  • ); return
      {itemsList}
    ; }; return ( @@ -210,38 +227,56 @@ function ProgramModal({ > - + - +
    - { programTitle && ( - - {programTitle} - + {programTitle && ( + + {programTitle} + )} - { programProvider && ( - - {programProvider} - + {programProvider && ( + + {programProvider} + )} - {(learningItems && learningItems.length > 0) && ( + {learningItems && learningItems.length > 0 && (
    -

    {intl.formatMessage(messages['catalogInfoModal.programLearningItemsHeader'])}

    +

    + {intl.formatMessage( + messages['catalogInfoModal.programLearningItemsHeader'], + )} +

    {bulletedList(learningItems)}
    )} - {(programCourses && programCourses.length > 0) && ( + {programCourses && programCourses.length > 0 && (
    -

    {intl.formatMessage(messages['catalogInfoModal.programCourseListingTitle'])}

    -
    - {(programCourses || []).map( - course => , +

    + {intl.formatMessage( + messages['catalogInfoModal.programCourseListingTitle'], )} +

    +
    + {(programCourses || []).map((course) => ( + + ))}
    )} @@ -261,12 +296,13 @@ function ProgramModal({ rel="noopener noreferrer" > - @@ -300,13 +336,31 @@ function CatalogCourseInfoModal({ selectedProgram, renderProgram, }) { - if (!selectedCourse && !renderProgram) { return null; } - if (!selectedProgram && renderProgram) { return null; } + if (!selectedCourse && !renderProgram) { + return null; + } + if (!selectedProgram && renderProgram) { + return null; + } if (!renderProgram) { - return ; + return ( + + ); } - return ; + return ( + + ); } CatalogCourseInfoModal.defaultProps = { diff --git a/src/components/catalogInfoModal/CatalogInfoModal.test.jsx b/src/components/catalogInfoModal/CatalogInfoModal.test.jsx index 404b69a1..febe1207 100644 --- a/src/components/catalogInfoModal/CatalogInfoModal.test.jsx +++ b/src/components/catalogInfoModal/CatalogInfoModal.test.jsx @@ -55,7 +55,9 @@ describe('Course info modal works as expected', () => { ); expect(screen.queryByText(selectedCourse.courseTitle)).toBeInTheDocument(); - expect(screen.queryByText(selectedCourse.courseProvider)).toBeInTheDocument(); + expect( + screen.queryByText(selectedCourse.courseProvider), + ).toBeInTheDocument(); expect(screen.queryByText(descriptionText)).toBeInTheDocument(); }); test('Course info modal is hidden when expected', () => { @@ -67,7 +69,9 @@ describe('Course info modal works as expected', () => { , ); - expect(screen.queryByText(selectedCourse.courseTitle)).not.toBeInTheDocument(); + expect( + screen.queryByText(selectedCourse.courseTitle), + ).not.toBeInTheDocument(); }); test('Renders Course info modal banner', () => { const defaultPropsCopy = {}; @@ -79,8 +83,12 @@ describe('Course info modal works as expected', () => { , ); expect(screen.queryByText('A la carte course price')).toBeInTheDocument(); - expect(screen.queryByText('Session ends Apr 6, 2040 • 2 additional session(s)')).toBeInTheDocument(); - expect(screen.queryByText('Included with subscription')).not.toBeInTheDocument(); + expect( + screen.queryByText('Session ends Apr 6, 2040 • 2 additional session(s)'), + ).toBeInTheDocument(); + expect( + screen.queryByText('Included with subscription'), + ).not.toBeInTheDocument(); }); test('Renders Course info modal with correct catalogs', () => { const defaultPropsCopy = {}; @@ -88,15 +96,21 @@ describe('Course info modal works as expected', () => { const educationQueryTitle = 'test-business-query-title'; process.env.EDX_FOR_ONLINE_EDU_TITLE = educationQueryTitle; - defaultPropsCopy.selectedCourse.courseAssociatedCatalogs = [educationQueryTitle]; + defaultPropsCopy.selectedCourse.courseAssociatedCatalogs = [ + educationQueryTitle, + ]; render( , ); - expect(screen.queryByText('Included in education catalog')).toBeInTheDocument(); - expect(screen.queryByText('Included in business catalog')).not.toBeInTheDocument(); + expect( + screen.queryByText('Included in education catalog'), + ).toBeInTheDocument(); + expect( + screen.queryByText('Included in business catalog'), + ).not.toBeInTheDocument(); }); test('Renders Course info modal with no catalogs', () => { const defaultPropsCopy = {}; @@ -107,14 +121,16 @@ describe('Course info modal works as expected', () => { , ); - expect(screen.queryByText('Included with subscription')).not.toBeInTheDocument(); + expect( + screen.queryByText('Included with subscription'), + ).not.toBeInTheDocument(); }); test('Course info modal displays up to 5 skills list', () => { const defaultPropsCopy = { ...courseTypeModalProps, selectedCourse: { ...courseTypeModalProps.selectedCourse, - skillNames: [...Array(20).keys()].map(i => `skill-${i}`), + skillNames: [...Array(20).keys()].map((i) => `skill-${i}`), }, }; @@ -181,9 +197,15 @@ describe('Program info modal works as expected', () => { ); const { selectedProgram } = programTypeModalProps; - expect(screen.queryByText(selectedProgram.programTitle)).toBeInTheDocument(); - expect(screen.queryByText(selectedProgram.programProvider)).toBeInTheDocument(); - expect(screen.queryByText(selectedProgram.programDescription)).toBeInTheDocument(); + expect( + screen.queryByText(selectedProgram.programTitle), + ).toBeInTheDocument(); + expect( + screen.queryByText(selectedProgram.programProvider), + ).toBeInTheDocument(); + expect( + screen.queryByText(selectedProgram.programDescription), + ).toBeInTheDocument(); }); test('renders learning items section if learningItems if non empty', () => { render( @@ -196,7 +218,10 @@ describe('Program info modal works as expected', () => { test('skipes render of learning items section if learningItems if empty', () => { const props = { ...programTypeModalProps, - selectedProgram: { ...programTypeModalProps.selectedProgram, learningItems: [] }, + selectedProgram: { + ...programTypeModalProps.selectedProgram, + learningItems: [], + }, }; render( @@ -212,6 +237,8 @@ describe('Program info modal works as expected', () => { , ); - expect(screen.queryByText('Courses in this program')).not.toBeInTheDocument(); + expect( + screen.queryByText('Courses in this program'), + ).not.toBeInTheDocument(); }); }); diff --git a/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx b/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx index 3f6082eb..2d10a4cf 100644 --- a/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx +++ b/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx @@ -1,17 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Icon, -} from '@edx/paragon'; +import { Icon } from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - BookOpen, - EventNote, - MoneyOutline, -} from '@edx/paragon/icons'; +import { BookOpen, EventNote, MoneyOutline } from '@edx/paragon/icons'; import messages from './CatalogCourseModalBanner.messages'; -import { checkAvailability, checkSubscriptions } from '../../utils/catalogUtils'; +import { + checkAvailability, + checkSubscriptions, +} from '../../utils/catalogUtils'; const nowDate = new Date(Date.now()); @@ -21,9 +18,15 @@ function availabilitySubtitle(start, end, upcomingRuns) { const startDate = new Date(start); const endDate = new Date(end); if (startDate < nowDate && endDate > nowDate) { - retString = `Session ends ${endDate.toLocaleDateString(undefined, options)}`; + retString = `Session ends ${endDate.toLocaleDateString( + undefined, + options, + )}`; } else if (startDate > nowDate) { - retString = `Session starts ${startDate.toLocaleDateString(undefined, options)}`; + retString = `Session starts ${startDate.toLocaleDateString( + undefined, + options, + )}`; } if (upcomingRuns !== undefined && upcomingRuns > 0) { retString += ` • ${upcomingRuns} additional session(s)`; @@ -47,29 +50,36 @@ function CatalogCourseModalBanner({ {coursePrice}
    - {intl.formatMessage(messages['CatalogCourseModalBanner.bannerPriceText'])} + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerPriceText'], + )}
    -
    /
    {checkSubscriptions(courseAssociatedCatalogs) && ( -
    -
    - - {intl.formatMessage(messages['CatalogCourseModalBanner.bannerCatalogText'])} +
    +
    + + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerCatalogText'], + )} +
    +
    + {checkSubscriptions(courseAssociatedCatalogs)} +
    -
    {checkSubscriptions(courseAssociatedCatalogs)}
    -
    )} {checkSubscriptions(courseAssociatedCatalogs) && ( -
    /
    +
    /
    )}
    {checkAvailability(startDate, endDate)}
    -
    {availabilitySubtitle(startDate, endDate, upcomingRuns)}
    +
    + {availabilitySubtitle(startDate, endDate, upcomingRuns)}{' '} +
    ); diff --git a/src/components/catalogModalBanner/CatalogProgramModalBanner.jsx b/src/components/catalogModalBanner/CatalogProgramModalBanner.jsx index 919d613b..70d8cd5f 100644 --- a/src/components/catalogModalBanner/CatalogProgramModalBanner.jsx +++ b/src/components/catalogModalBanner/CatalogProgramModalBanner.jsx @@ -1,15 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Icon, -} from '@edx/paragon'; +import { Icon } from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - Assignment, - BookOpen, - MoneyOutline, -} from '@edx/paragon/icons'; +import { Assignment, BookOpen, MoneyOutline } from '@edx/paragon/icons'; import messages from './CatalogCourseModalBanner.messages'; import { checkSubscriptions } from '../../utils/catalogUtils'; @@ -21,42 +15,50 @@ function CatalogProgramModalBanner({ }) { return (
    - { (coursePrice !== undefined) && ( - <> -
    -
    - - {coursePrice} + {coursePrice !== undefined && ( + <> +
    +
    + + {coursePrice} +
    +
    + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerPriceTextProgram'], + )} +
    -
    - {intl.formatMessage(messages['CatalogCourseModalBanner.bannerPriceTextProgram'])} +
    /
    + + )} + {courses && courses.length > 0 && ( + <> +
    +
    + + {courses.length} courses +
    +
    + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerCourseText'], + )} +
    -
    -
    /
    - +
    /
    + )} - { (courses && courses.length > 0) && ( - <> + {checkSubscriptions(courseAssociatedCatalogs) && (
    - - {courses.length} courses + + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerCatalogText'], + )}
    - {intl.formatMessage(messages['CatalogCourseModalBanner.bannerCourseText'])} + {checkSubscriptions(courseAssociatedCatalogs)}
    -
    /
    - - )} - {checkSubscriptions(courseAssociatedCatalogs) && ( -
    -
    - - {intl.formatMessage(messages['CatalogCourseModalBanner.bannerCatalogText'])} -
    -
    {checkSubscriptions(courseAssociatedCatalogs)}
    -
    )}
    ); diff --git a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx index a3f4639d..f30a806e 100644 --- a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx +++ b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx @@ -32,51 +32,76 @@ function CatalogNoResultsDeck({ const selectedCatalog = getSelectedCatalogFromURL(); let redirect; if (selectedCatalog) { - redirect = `${BASE_APP_URL}/?enterprise_catalog_query_titles=${encodeURIComponent(selectedCatalog)}`; + redirect = `${BASE_APP_URL}/?enterprise_catalog_query_titles=${encodeURIComponent( + selectedCatalog, + )}`; } else { redirect = BASE_APP_URL; } useEffect(() => { - const defaultCoursesRefinements = { enterprise_catalog_query_titles: selectedCatalog, content_type: contentType }; + const defaultCoursesRefinements = { + enterprise_catalog_query_titles: selectedCatalog, + content_type: contentType, + }; if (contentType === CONTENT_TYPE_COURSE) { // if a course type is not specified, default to edx course content defaultCoursesRefinements.course_type = courseType !== null ? [courseType] : EDX_COURSES_COURSE_TYPES; } - EnterpriseCatalogApiService.fetchDefaultCoursesInCatalogWithFacets(defaultCoursesRefinements).then(response => { - setDefaultData(response.default_content || []); - setApiError(false); - }).catch(err => { - setApiError(true); - logError(err); - }); + EnterpriseCatalogApiService.fetchDefaultCoursesInCatalogWithFacets( + defaultCoursesRefinements, + ) + .then((response) => { + setDefaultData(response.default_content || []); + setApiError(false); + }) + .catch((err) => { + setApiError(true); + logError(err); + }); }, [selectedCatalog, contentType, courseType]); let defaultDeckTitle; let alertText; if (contentType === CONTENT_TYPE_COURSE) { - alertText = intl.formatMessage(messages['catalogSearchResults.NoResultsCourseBannerText']); - defaultDeckTitle = intl.formatMessage(messages['catalogSearchResults.DefaultCourseDeckTitle']); + alertText = intl.formatMessage( + messages['catalogSearchResults.NoResultsCourseBannerText'], + ); + defaultDeckTitle = intl.formatMessage( + messages['catalogSearchResults.DefaultCourseDeckTitle'], + ); } else if (contentType === CONTENT_TYPE_PROGRAM) { - alertText = intl.formatMessage(messages['catalogSearchResults.NoResultsProgramBannerText']); - defaultDeckTitle = intl.formatMessage(messages['catalogSearchResults.DefaultProgramDeckTitle']); + alertText = intl.formatMessage( + messages['catalogSearchResults.NoResultsProgramBannerText'], + ); + defaultDeckTitle = intl.formatMessage( + messages['catalogSearchResults.DefaultProgramDeckTitle'], + ); } return ( <> - {intl.formatMessage(messages['catalogSearchResults.NoResultsBannerTitle'])} + + {intl.formatMessage( + messages['catalogSearchResults.NoResultsBannerTitle'], + )} + {alertText} - {intl.formatMessage(messages['catalogSearchResults.NoResultsBannerHyperlinkText'])} + {intl.formatMessage( + messages['catalogSearchResults.NoResultsBannerHyperlinkText'], + )} - { (!apiError) && ( -

    {defaultDeckTitle}

    + {!apiError && ( +

    + {defaultDeckTitle} +

    )} setCardView(val === 'card'), + onDataViewToggle: (val) => setCardView(val === 'card'), togglePlacement: 'left', defaultActiveStateValue: 'card', }} diff --git a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx index bcc62fbc..3df3a067 100644 --- a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx +++ b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx @@ -43,10 +43,13 @@ const csvData = { ], }; // Enterprise catalog API mock -const mockCatalogApiService = jest.spyOn(EnterpriseCatalogApiService, 'fetchDefaultCoursesInCatalogWithFacets'); +const mockCatalogApiService = jest.spyOn( + EnterpriseCatalogApiService, + 'fetchDefaultCoursesInCatalogWithFacets', +); // fetching catalog from query params mock jest.mock('../../utils/common', () => ({ - ...(jest.requireActual('../../utils/common')), + ...jest.requireActual('../../utils/common'), getSelectedCatalogFromURL: jest.fn(), })); @@ -87,7 +90,10 @@ describe('catalog no results deck works as expected', () => { , ); const hyperlinkthing = screen.getByText('removing filters'); - expect(hyperlinkthing).toHaveAttribute('href', `${process.env.BASE_URL}/?enterprise_catalog_query_titles=ayylmao`); + expect(hyperlinkthing).toHaveAttribute( + 'href', + `${process.env.BASE_URL}/?enterprise_catalog_query_titles=ayylmao`, + ); }); test('API error responses will hide content deck', async () => { mockCatalogApiService.mockRejectedValue(new Error('Async error')); @@ -96,7 +102,9 @@ describe('catalog no results deck works as expected', () => { , ); - expect(await screen.findByTestId('noResultsDeckTitleTestId')).not.toBeInTheDocument(); + expect( + await screen.findByTestId('noResultsDeckTitleTestId'), + ).not.toBeInTheDocument(); expect(logError).toBeCalled(); }); }); diff --git a/src/components/catalogPage/CatalogPage.jsx b/src/components/catalogPage/CatalogPage.jsx index d8dc43d9..09507b6b 100644 --- a/src/components/catalogPage/CatalogPage.jsx +++ b/src/components/catalogPage/CatalogPage.jsx @@ -1,6 +1,13 @@ import React from 'react'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { SearchData, SEARCH_FACET_FILTERS } from '@edx/frontend-enterprise-catalog-search'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import { + SearchData, + SEARCH_FACET_FILTERS, +} from '@edx/frontend-enterprise-catalog-search'; import { getConfig } from '@edx/frontend-platform'; import { CatalogSearch } from '../catalogs'; import Subheader from '../subheader/subheader'; @@ -8,8 +15,12 @@ import Hero from '../hero/Hero'; import messages from './CatalogPage.messages'; import CatalogSelectionDeck from '../catalogSelectionDeck/CatalogSelectionDeck'; import { - AVAILABILITY_REFINEMENT, AVAILABILITY_REFINEMENT_DEFAULTS, CONTENT_TYPE_REFINEMENT, - QUERY_TITLE_REFINEMENT, HIDE_CARDS_REFINEMENT, TRACKING_APP_NAME, + AVAILABILITY_REFINEMENT, + AVAILABILITY_REFINEMENT_DEFAULTS, + CONTENT_TYPE_REFINEMENT, + QUERY_TITLE_REFINEMENT, + HIDE_CARDS_REFINEMENT, + TRACKING_APP_NAME, } from '../../constants'; const contentType = { @@ -37,14 +48,22 @@ function CatalogPage({ intl }) { reloadPage = true; } - if (config.EDX_ENTERPRISE_ALACARTE_TITLE - && (!loadedSearchParams.get(CONTENT_TYPE_REFINEMENT)) - && (!loadedSearchParams.get(QUERY_TITLE_REFINEMENT))) { - loadedSearchParams.set(QUERY_TITLE_REFINEMENT, config.EDX_ENTERPRISE_ALACARTE_TITLE); + if ( + config.EDX_ENTERPRISE_ALACARTE_TITLE + && !loadedSearchParams.get(CONTENT_TYPE_REFINEMENT) + && !loadedSearchParams.get(QUERY_TITLE_REFINEMENT) + ) { + loadedSearchParams.set( + QUERY_TITLE_REFINEMENT, + config.EDX_ENTERPRISE_ALACARTE_TITLE, + ); reloadPage = true; } - if ((!loadedSearchParams.get(AVAILABILITY_REFINEMENT) && (!loadedSearchParams.get(CONTENT_TYPE_REFINEMENT)))) { - AVAILABILITY_REFINEMENT_DEFAULTS.map(a => loadedSearchParams.append(AVAILABILITY_REFINEMENT, a)); + if ( + !loadedSearchParams.get(AVAILABILITY_REFINEMENT) + && !loadedSearchParams.get(CONTENT_TYPE_REFINEMENT) + ) { + AVAILABILITY_REFINEMENT_DEFAULTS.map((a) => loadedSearchParams.append(AVAILABILITY_REFINEMENT, a)); reloadPage = true; } if (reloadPage) { @@ -71,11 +90,21 @@ function CatalogPage({ intl }) { - + diff --git a/src/components/catalogPage/CatalogPage.test.jsx b/src/components/catalogPage/CatalogPage.test.jsx index d53ed60c..b9af02fc 100644 --- a/src/components/catalogPage/CatalogPage.test.jsx +++ b/src/components/catalogPage/CatalogPage.test.jsx @@ -8,20 +8,18 @@ import selectionCardMessage from '../catalogSelectionDeck/CatalogSelectionDeck.m // all we are testing is routes, we don't need InstantSearch to work here jest.mock('react-instantsearch-dom', () => ({ ...jest.requireActual('react-instantsearch-dom'), - InstantSearch: () => (
    SEARCH
    ), - Index: () => (
    SEARCH
    ), + InstantSearch: () =>
    SEARCH
    , + Index: () =>
    SEARCH
    , })); // Catalog Page loads the CTA button link which expects a config value. // Thus we're mocking the config here. -const mockConfig = () => ( - { - HUBSPOT_MARKETING_URL: 'http://bobsdooremporium.com', - EDX_FOR_BUSINESS_TITLE: 'ayylmao', - EDX_FOR_ONLINE_EDU_TITLE: 'foo', - EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', - } -); +const mockConfig = () => ({ + HUBSPOT_MARKETING_URL: 'http://bobsdooremporium.com', + EDX_FOR_BUSINESS_TITLE: 'ayylmao', + EDX_FOR_ONLINE_EDU_TITLE: 'foo', + EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), @@ -41,7 +39,12 @@ describe('CatalogPage', () => { }); it('renders with catalog selection cards', () => { renderWithRouter(); - expect(screen.getByText(selectionCardMessage['catalogSelectionDeck.edxForBusiness.label'].defaultMessage)).toBeInTheDocument(); + expect( + screen.getByText( + selectionCardMessage['catalogSelectionDeck.edxForBusiness.label'] + .defaultMessage, + ), + ).toBeInTheDocument(); }); it('properly handles empty query params', () => { const location = { @@ -54,6 +57,8 @@ describe('CatalogPage', () => { }); expect(window.location.search).toEqual('?q='); renderWithRouter(); - expect(window.location.search).toEqual('enterprise_catalog_query_titles=baz&availability=Available+Now&availability=Starting+Soon&availability=Upcoming'); + expect(window.location.search).toEqual( + 'enterprise_catalog_query_titles=baz&availability=Available+Now&availability=Starting+Soon&availability=Upcoming', + ); }); }); diff --git a/src/components/catalogSearchResults/CatalogSearchResults.jsx b/src/components/catalogSearchResults/CatalogSearchResults.jsx index aa491e81..42334246 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.jsx @@ -1,7 +1,14 @@ import { - SearchContext, SearchPagination, setRefinementAction, useNbHitsFromSearchResults, + SearchContext, + SearchPagination, + setRefinementAction, + useNbHitsFromSearchResults, } from '@edx/frontend-enterprise-catalog-search'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; import { Alert, Button, CardView, DataTable, } from '@edx/paragon'; @@ -24,7 +31,10 @@ import { HIDE_PRICE_REFINEMENT, PROGRAM_TITLE, } from '../../constants'; -import { mapAlgoliaObjectToCourse, mapAlgoliaObjectToProgram } from '../../utils/algoliaUtils'; +import { + mapAlgoliaObjectToCourse, + mapAlgoliaObjectToProgram, +} from '../../utils/algoliaUtils'; import CatalogInfoModal from '../catalogInfoModal/CatalogInfoModal'; import { useSelectedCourse } from '../catalogs/data/hooks'; import CourseCard from '../courseCard/CourseCard'; @@ -54,7 +64,7 @@ export const SKELETON_DATA_TESTID = 'enterprise-catalog-skeleton'; * @param {object} args.paginationComponent Defaults to but can be injected * @param {object} args.contentType Whether the search is for courses or programs * @param {object} args.preview Whether we are on the split screen landing page or regular -*/ + */ export function BaseCatalogSearchResults({ intl, @@ -73,16 +83,33 @@ export function BaseCatalogSearchResults({ const isProgramType = contentType === CONTENT_TYPE_PROGRAM; const isCourseType = contentType === CONTENT_TYPE_COURSE; - const TABLE_HEADERS = useMemo(() => ({ - courseName: intl.formatMessage(messages['catalogSearchResults.table.courseName']), - partner: intl.formatMessage(messages['catalogSearchResults.table.partner']), - price: intl.formatMessage(messages['catalogSearchResults.table.price']), - availability: intl.formatMessage(messages['catalogSearchResults.table.availability']), - catalogs: intl.formatMessage(messages['catalogSearchResults.table.catalogs']), - programName: intl.formatMessage(messages['catalogSearchResults.table.programName']), - numCourses: intl.formatMessage(messages['catalogSearchResults.table.numCourses']), - programType: intl.formatMessage(messages['catalogSearchResults.table.programType']), - }), [intl]); + const TABLE_HEADERS = useMemo( + () => ({ + courseName: intl.formatMessage( + messages['catalogSearchResults.table.courseName'], + ), + partner: intl.formatMessage( + messages['catalogSearchResults.table.partner'], + ), + price: intl.formatMessage(messages['catalogSearchResults.table.price']), + availability: intl.formatMessage( + messages['catalogSearchResults.table.availability'], + ), + catalogs: intl.formatMessage( + messages['catalogSearchResults.table.catalogs'], + ), + programName: intl.formatMessage( + messages['catalogSearchResults.table.programName'], + ), + numCourses: intl.formatMessage( + messages['catalogSearchResults.table.numCourses'], + ), + programType: intl.formatMessage( + messages['catalogSearchResults.table.programType'], + ), + }), + [intl], + ); const { refinements, dispatch } = useContext(SearchContext); const nbHits = useNbHitsFromSearchResults(searchResults); @@ -92,34 +119,46 @@ export function BaseCatalogSearchResults({ const [cardView, setCardView] = useState(true); - const rowClicked = useCallback((row) => { - if (isProgramType) { - setSelectedCourse( - mapAlgoliaObjectToProgram(row.original), - ); - } else { - setSelectedCourse(mapAlgoliaObjectToCourse(row.original, intl, messages)); - } - }, [intl, isProgramType, setSelectedCourse]); + const rowClicked = useCallback( + (row) => { + if (isProgramType) { + setSelectedCourse(mapAlgoliaObjectToProgram(row.original)); + } else { + setSelectedCourse( + mapAlgoliaObjectToCourse(row.original, intl, messages), + ); + } + }, + [intl, isProgramType, setSelectedCourse], + ); - const cardClicked = useCallback((card) => { - if (isProgramType) { - setSelectedCourse(mapAlgoliaObjectToProgram(card)); - } else { - setSelectedCourse(mapAlgoliaObjectToCourse(card, intl, messages)); - } - }, [intl, isProgramType, setSelectedCourse]); + const cardClicked = useCallback( + (card) => { + if (isProgramType) { + setSelectedCourse(mapAlgoliaObjectToProgram(card)); + } else { + setSelectedCourse(mapAlgoliaObjectToCourse(card, intl, messages)); + } + }, + [intl, isProgramType, setSelectedCourse], + ); const refinementClick = (content) => { if (content === CONTENT_TYPE_COURSE) { - dispatch(setRefinementAction(CONTENT_TYPE_REFINEMENT, [CONTENT_TYPE_COURSE])); + dispatch( + setRefinementAction(CONTENT_TYPE_REFINEMENT, [CONTENT_TYPE_COURSE]), + ); } else { - dispatch(setRefinementAction(CONTENT_TYPE_REFINEMENT, [CONTENT_TYPE_PROGRAM])); + dispatch( + setRefinementAction(CONTENT_TYPE_REFINEMENT, [CONTENT_TYPE_PROGRAM]), + ); } }; const renderCardComponent = (props) => { - if (isCourseType) { return ; } + if (isCourseType) { + return ; + } return ; }; @@ -127,86 +166,106 @@ export function BaseCatalogSearchResults({ id: 'availability-column', Header: TABLE_HEADERS.availability, accessor: 'advertised_course_run', - Cell: ({ row }) => (formatDate(row.values.advertised_course_run)), + Cell: ({ row }) => formatDate(row.values.advertised_course_run), }; - const TitleButtonComponent = useCallback(({ row }) => ( - - ), [rowClicked]); + const TitleButtonComponent = useCallback( + ({ row }) => , + [rowClicked], + ); - const CatalogBadgeComponent = useCallback(({ row }) => ( - - ), []); + const CatalogBadgeComponent = useCallback( + ({ row }) => , + [], + ); // NOTE: Cell is not explicity supported in DataTable, which leads to lint errors regarding {row}. However, we needed // to use the accessor functionality instead of just adding in additionalColumns like the Paragon documentation. - const courseColumns = useMemo(() => [ - { - Header: TABLE_HEADERS.courseName, - accessor: 'title', - Cell: TitleButtonComponent, - }, - { - Header: TABLE_HEADERS.partner, - accessor: 'partners[0].name', - }, - { - Header: TABLE_HEADERS.price, - accessor: 'first_enrollable_paid_seat_price', - Cell: ({ row }) => (row.values.first_enrollable_paid_seat_price ? `$${row.values.first_enrollable_paid_seat_price}` : null), - }, - { - Header: TABLE_HEADERS.catalogs, - accessor: 'enterprise_catalog_query_titles', - Cell: CatalogBadgeComponent, - }, - ], [TABLE_HEADERS, TitleButtonComponent, CatalogBadgeComponent]); + const courseColumns = useMemo( + () => [ + { + Header: TABLE_HEADERS.courseName, + accessor: 'title', + Cell: TitleButtonComponent, + }, + { + Header: TABLE_HEADERS.partner, + accessor: 'partners[0].name', + }, + { + Header: TABLE_HEADERS.price, + accessor: 'first_enrollable_paid_seat_price', + Cell: ({ row }) => (row.values.first_enrollable_paid_seat_price + ? `$${row.values.first_enrollable_paid_seat_price}` + : null), + }, + { + Header: TABLE_HEADERS.catalogs, + accessor: 'enterprise_catalog_query_titles', + Cell: CatalogBadgeComponent, + }, + ], + [TABLE_HEADERS, TitleButtonComponent, CatalogBadgeComponent], + ); - const programColumns = useMemo(() => [ - { - Header: TABLE_HEADERS.programName, - accessor: 'title', - Cell: TitleButtonComponent, - }, - { - Header: TABLE_HEADERS.partner, - accessor: 'authoring_organizations[0].name', - }, - { - Header: TABLE_HEADERS.numCourses, - accessor: 'course_keys', - Cell: ({ row }) => (row.values.course_keys.length > 0 ? `${row.values.course_keys.length}` : 'Available upon request'), - }, - { - Header: TABLE_HEADERS.programType, - accessor: 'program_type', - }, + const programColumns = useMemo( + () => [ + { + Header: TABLE_HEADERS.programName, + accessor: 'title', + Cell: TitleButtonComponent, + }, + { + Header: TABLE_HEADERS.partner, + accessor: 'authoring_organizations[0].name', + }, + { + Header: TABLE_HEADERS.numCourses, + accessor: 'course_keys', + Cell: ({ row }) => (row.values.course_keys.length > 0 + ? `${row.values.course_keys.length}` + : 'Available upon request'), + }, + { + Header: TABLE_HEADERS.programType, + accessor: 'program_type', + }, - { - Header: TABLE_HEADERS.catalogs, - accessor: 'enterprise_catalog_query_titles', - Cell: CatalogBadgeComponent, - }, - ], [TABLE_HEADERS, TitleButtonComponent, CatalogBadgeComponent]); + { + Header: TABLE_HEADERS.catalogs, + accessor: 'enterprise_catalog_query_titles', + Cell: CatalogBadgeComponent, + }, + ], + [TABLE_HEADERS, TitleButtonComponent, CatalogBadgeComponent], + ); // substituting the price column with the availability dates per customer request ENT-5041 const page = refinements.page || (searchState ? searchState.page : 0); if (HIDE_PRICE_REFINEMENT in refinements) { courseColumns[2] = availabilityColumn; } - const tableData = useMemo(() => searchResults?.hits || [], [searchResults?.hits]); + const tableData = useMemo( + () => searchResults?.hits || [], + [searchResults?.hits], + ); const query = queryString.parse(window.location.search.substring(1)); - const toggleOptions = preview ? {} : { - isDataViewToggleEnabled: true, - onDataViewToggle: val => setCardView(val === 'card'), - togglePlacement: 'left', - defaultActiveStateValue: 'card', - }; + const toggleOptions = preview + ? {} + : { + isDataViewToggleEnabled: true, + onDataViewToggle: (val) => setCardView(val === 'card'), + togglePlacement: 'left', + defaultActiveStateValue: 'card', + }; function contentTitle() { - let subTitle = (contentType === CONTENT_TYPE_COURSE) ? COURSE_TITLE : PROGRAM_TITLE; + let subTitle = contentType === CONTENT_TYPE_COURSE ? COURSE_TITLE : PROGRAM_TITLE; if (refinements.q && refinements.q !== '') { - subTitle = `"${refinements.q}" ${subTitle} (${makePlural(nbHits, 'result')})`; + subTitle = `"${refinements.q}" ${subTitle} (${makePlural( + nbHits, + 'result', + )})`; } return subTitle; } @@ -227,21 +286,23 @@ export function BaseCatalogSearchResults({ const inputQuery = query.q; const dataTableActions = () => { - if (preview || (searchResults?.nbHits === 0)) { + if (preview || searchResults?.nbHits === 0) { return null; } - // eslint-disable-next-line no-underscore-dangle - return ; + + return ( + + ); }; if (isSearchStalled) { return (
    - +
    ); } @@ -260,14 +321,14 @@ export function BaseCatalogSearchResults({ return ( <> - { isCourseType && ( + {isCourseType && ( setSelectedCourse(null)} selectedCourse={selectedCourse} /> )} - { isProgramType && ( + {isProgramType && ( setSelectedCourse(null)} @@ -275,32 +336,38 @@ export function BaseCatalogSearchResults({ renderProgram /> )} - {preview && isCourseType && (searchResults?.nbHits !== 0) && ( + {preview && isCourseType && searchResults?.nbHits !== 0 && ( - { - // eslint-disable-next-line no-underscore-dangle - } + )}
    {preview && ( -
    -

    {contentTitle()}

    - { (searchResults?.nbHits !== 0) && ( - - )} -
    +
    +

    {contentTitle()}

    + {searchResults?.nbHits !== 0 && ( + + )} +
    )} - { (searchResults?.nbHits === 0) && ( + {searchResults?.nbHits === 0 && ( )} - {(searchResults?.nbHits !== 0) && ( + {searchResults?.nbHits !== 0 && ( - { cardView && ( + {cardView && ( renderCardComponent(props)} /> )} - { !cardView && } + {!cardView && } {!preview && ( - - - - + + + + )} )} diff --git a/src/components/catalogSearchResults/CatalogSearchResults.messages.js b/src/components/catalogSearchResults/CatalogSearchResults.messages.js index d328759d..c719170b 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.messages.js +++ b/src/components/catalogSearchResults/CatalogSearchResults.messages.js @@ -9,17 +9,20 @@ const messages = defineMessages({ 'catalogSearchResults.table.partner': { id: 'catalogSearchResults.table.partner', defaultMessage: 'Partner', - description: 'The partner institution providing/authoring the course (ie Harvard, MIT, etc.)', + description: + 'The partner institution providing/authoring the course (ie Harvard, MIT, etc.)', }, 'catalogSearchResults.table.price': { id: 'catalogSearchResults.table.price', defaultMessage: 'A la carte course price', - description: 'Table column A La Carte price for the course - optional column', + description: + 'Table column A La Carte price for the course - optional column', }, 'catalogSearchResults.table.availability': { id: 'catalogSearchResults.table.availability', defaultMessage: 'Course Availability', - description: 'Table column form course availability dates - optional column', + description: + 'Table column form course availability dates - optional column', }, 'catalogSearchResults.table.catalogs': { id: 'catalogSearchResults.table.catalogs', @@ -29,7 +32,8 @@ const messages = defineMessages({ 'catalogSearchResults.table.priceNotAvailable': { id: 'catalogSearchResults.table.priceNotAvailable', defaultMessage: 'Not Available', - description: 'When a course price is not available, notify learners that there is no data available to display.', + description: + 'When a course price is not available, notify learners that there is no data available to display.', }, 'catalogSearchResults.table.programName': { id: 'catalogSearchResults.table.programName', diff --git a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx index 73e5292d..05a255c2 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx @@ -5,11 +5,17 @@ import '@testing-library/jest-dom/extend-expect'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { BaseCatalogSearchResults, ERROR_MESSAGE, SKELETON_DATA_TESTID } from './CatalogSearchResults'; +import { + BaseCatalogSearchResults, + ERROR_MESSAGE, + SKELETON_DATA_TESTID, +} from './CatalogSearchResults'; import { renderWithRouter } from '../tests/testUtils'; import messages from './CatalogSearchResults.messages'; import { - CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, HIDE_PRICE_REFINEMENT, + CONTENT_TYPE_COURSE, + CONTENT_TYPE_PROGRAM, + HIDE_PRICE_REFINEMENT, } from '../../constants'; import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAPIService'; @@ -20,12 +26,18 @@ function PaginationComponent() { } const csvData = [{ csv_data: 'foobar' }]; -jest.spyOn(EnterpriseCatalogApiService, 'fetchDefaultCoursesInCatalogWithFacets').mockResolvedValue(csvData); +jest + .spyOn(EnterpriseCatalogApiService, 'fetchDefaultCoursesInCatalogWithFacets') + .mockResolvedValue(csvData); const DEFAULT_SEARCH_CONTEXT_VALUE = { refinements: {} }; -// eslint-disable-next-line react/prop-types -function SearchDataWrapper({ children, searchContextValue = DEFAULT_SEARCH_CONTEXT_VALUE }) { +function SearchDataWrapper({ + // eslint-disable-next-line react/prop-types + children, + // eslint-disable-next-line react/prop-types + searchContextValue = DEFAULT_SEARCH_CONTEXT_VALUE, +}) { return ( {children} @@ -33,14 +45,12 @@ function SearchDataWrapper({ children, searchContextValue = DEFAULT_SEARCH_CONTE ); } -const mockConfig = () => ( - { - EDX_FOR_BUSINESS_TITLE: 'ayylmao', - EDX_FOR_ONLINE_EDU_TITLE: 'foo', - EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', - FEATURE_CARD_VIEW_ENABLED: 'True', - } -); +const mockConfig = () => ({ + EDX_FOR_BUSINESS_TITLE: 'ayylmao', + EDX_FOR_ONLINE_EDU_TITLE: 'foo', + EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', + FEATURE_CARD_VIEW_ENABLED: 'True', +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), @@ -173,7 +183,8 @@ describe('Main Catalogs view works as expected', () => { - , + + , , ); @@ -198,7 +209,8 @@ describe('Main Catalogs view works as expected', () => { - , + + , , ); @@ -215,20 +227,20 @@ describe('Main Catalogs view works as expected', () => { expect(courseTitleInCard).toBeVisible(); // course 1 image with the correct alt text - expect(screen.getByAltText(`${TEST_COURSE_NAME} course image`)).toBeVisible(); + expect( + screen.getByAltText(`${TEST_COURSE_NAME} course image`), + ).toBeVisible(); }); test('pagination component renders', () => { renderWithRouter( - + , ); expect(screen.queryByText(PAGINATE_ME)).toBeInTheDocument(); }); test('error if present is rendered instead of table', () => { - const ERRMSG = 'something ain\'t right here'; + const ERRMSG = "something ain't right here"; renderWithRouter( { test('isSearchStalled leads to rendering skeleton and not content', () => { renderWithRouter( - + , ); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); @@ -262,9 +271,7 @@ describe('Main Catalogs view works as expected', () => { test('headers rendered correctly', async () => { renderWithRouter( - + , ); @@ -272,42 +279,75 @@ describe('Main Catalogs view works as expected', () => { const listViewToggleButton = screen.getByLabelText('List'); userEvent.click(listViewToggleButton); - expect(screen.queryByText(messages['catalogSearchResults.table.courseName'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.catalogs'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.partner'].defaultMessage)).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.courseName'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.catalogs'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.partner'].defaultMessage, + ), + ).toBeInTheDocument(); // fixes the act warnings by ensuring we await some UI state before returning from the test - await act(() => screen.findByText(messages['catalogSearchResults.table.price'].defaultMessage)); - expect(screen.queryByText(messages['catalogSearchResults.table.price'].defaultMessage)).toBeInTheDocument(); + await act(() => screen.findByText( + messages['catalogSearchResults.table.price'].defaultMessage, + )); + expect( + screen.queryByText( + messages['catalogSearchResults.table.price'].defaultMessage, + ), + ).toBeInTheDocument(); }); test('refinements hide price column and show availability', async () => { const refinements = { refinements: { [HIDE_PRICE_REFINEMENT]: 'true' }, }; renderWithRouter( - - + + , ); const listViewToggleButton = screen.getByLabelText('List'); userEvent.click(listViewToggleButton); - expect(screen.queryByText(messages['catalogSearchResults.table.courseName'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.catalogs'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.partner'].defaultMessage)).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.courseName'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.catalogs'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.partner'].defaultMessage, + ), + ).toBeInTheDocument(); // fixes the act warnings by ensuring we await some UI state before returning from the test - await act(() => screen.findByText(messages['catalogSearchResults.table.availability'].defaultMessage)); - expect(screen.queryByText(messages['catalogSearchResults.table.availability'].defaultMessage)).toBeInTheDocument(); + await act(() => screen.findByText( + messages['catalogSearchResults.table.availability'].defaultMessage, + )); + expect( + screen.queryByText( + messages['catalogSearchResults.table.availability'].defaultMessage, + ), + ).toBeInTheDocument(); }); test('testing list course modal pops up ', async () => { renderWithRouter( - , + + , , ); @@ -326,7 +366,8 @@ describe('Main Catalogs view works as expected', () => { - , + + , , ); @@ -355,7 +396,9 @@ describe('Main Catalogs view works as expected', () => { expect(screen.queryByText(TEST_PROGRAM_NAME)).toBeInTheDocument(); expect(screen.queryByText(TEST_PARTNER)).toBeInTheDocument(); - expect(screen.queryByText('Courses available upon enrollment')).toBeInTheDocument(); + expect( + screen.queryByText('Courses available upon enrollment'), + ).toBeInTheDocument(); // TODO: Badges commented out until Algolia bug is resolved (ENT-5338) // expect(screen.queryByText(TEST_CATALOGS[0])).toBeInTheDocument(); }); @@ -373,11 +416,29 @@ describe('Main Catalogs view works as expected', () => { const listViewToggleButton = screen.getByLabelText('List'); userEvent.click(listViewToggleButton); - expect(screen.queryByText(messages['catalogSearchResults.table.programName'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.numCourses'].defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(messages['catalogSearchResults.table.programType'].defaultMessage)).toBeInTheDocument(); - await act(() => screen.findByText(messages['catalogSearchResults.table.partner'].defaultMessage)); - expect(screen.queryByText(messages['catalogSearchResults.table.partner'].defaultMessage)).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.programName'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.numCourses'].defaultMessage, + ), + ).toBeInTheDocument(); + expect( + screen.queryByText( + messages['catalogSearchResults.table.programType'].defaultMessage, + ), + ).toBeInTheDocument(); + await act(() => screen.findByText( + messages['catalogSearchResults.table.partner'].defaultMessage, + )); + expect( + screen.queryByText( + messages['catalogSearchResults.table.partner'].defaultMessage, + ), + ).toBeInTheDocument(); }); test('no program search results displays popular programs text', async () => { const emptySearchResults = { ...searchResults, nbHits: 0 }; diff --git a/src/components/catalogSearchResults/associatedComponents/catalogBadges/CatalogBadges.jsx b/src/components/catalogSearchResults/associatedComponents/catalogBadges/CatalogBadges.jsx index 1e049e97..7282407c 100644 --- a/src/components/catalogSearchResults/associatedComponents/catalogBadges/CatalogBadges.jsx +++ b/src/components/catalogSearchResults/associatedComponents/catalogBadges/CatalogBadges.jsx @@ -29,7 +29,7 @@ function CatalogBadges({ row }) { {intl.formatMessage(messages['catalogSearchResults.educationBadge'])} - )}; + )}
    ); } diff --git a/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.jsx b/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.jsx index 3d648403..0759720e 100644 --- a/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.jsx +++ b/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.jsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { - Toast, Button, useToggle, -} from '@edx/paragon'; +import { Toast, Button, useToggle } from '@edx/paragon'; import { Download } from '@edx/paragon/icons'; import EnterpriseCatalogApiService from '../../../../data/services/EnterpriseCatalogAPIService'; @@ -14,7 +12,7 @@ function DownloadCsvButton({ facets, query }) { const formatFilterText = (filterObject) => { let filterString = ''; - Object.keys(filterObject).forEach(key => { + Object.keys(filterObject).forEach((key) => { const currentFilters = [...filterObject[key]]; currentFilters.unshift(filterString); filterString = currentFilters.join(', '); @@ -25,19 +23,25 @@ function DownloadCsvButton({ facets, query }) { const handleClick = () => { formatFilterText(facets); open(); - const downloadUrl = EnterpriseCatalogApiService.generateCsvDownloadLink(facets, query); + const downloadUrl = EnterpriseCatalogApiService.generateCsvDownloadLink( + facets, + query, + ); global.location.href = downloadUrl; }; const toastText = `Downloaded with filters: ${filters}. Check website for the most up-to-date information on courses.`; return ( <> - { isOpen - && ( - - {toastText} - - )} - diff --git a/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.test.jsx b/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.test.jsx index 3a4ecd40..b89417e5 100644 --- a/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.test.jsx +++ b/src/components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton.test.jsx @@ -9,7 +9,9 @@ import { renderWithRouter } from '../../../tests/testUtils'; // file-saver mocks jest.mock('file-saver', () => ({ saveAs: jest.fn() })); // eslint-disable-next-line func-names -global.Blob = function (content, options) { return ({ content, options }); }; +global.Blob = function (content, options) { + return { content, options }; +}; const facets = { skill_names: ['Research'], @@ -31,9 +33,7 @@ global.location = { href: assignMock }; describe('Download button', () => { test('button renders and is clickable', async () => { // Render the component - renderWithRouter( - , - ); + renderWithRouter(); // Expect to be in the default state expect(screen.queryByText('Download results')).toBeInTheDocument(); @@ -46,9 +46,7 @@ describe('Download button', () => { test('download button url encodes queries', async () => { process.env.CATALOG_SERVICE_BASE_URL = 'foobar.com'; // Render the component - renderWithRouter( - , - ); + renderWithRouter(); // Expect to be in the default state expect(screen.queryByText('Download results')).toBeInTheDocument(); @@ -60,7 +58,7 @@ describe('Download button', () => { // The query, query param should not have an `&` in it. // TODO: figure out why the process env for catalog base service can't be set in the test const expectedWindowLocation = 'undefined/api/v1/enterprise-catalogs/catalog_workbook?availability=Available' - + '%20Now&availability=Upcoming&query=math%20%26%20science'; + + '%20Now&availability=Upcoming&query=math%20%26%20science'; expect(window.location.href).toEqual(expectedWindowLocation); }); }); diff --git a/src/components/catalogSelectionCard/CatalogSelectionCard.jsx b/src/components/catalogSelectionCard/CatalogSelectionCard.jsx index c0072958..3ecf2cd0 100644 --- a/src/components/catalogSelectionCard/CatalogSelectionCard.jsx +++ b/src/components/catalogSelectionCard/CatalogSelectionCard.jsx @@ -1,7 +1,8 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { - SearchContext, setRefinementAction, + SearchContext, + setRefinementAction, } from '@edx/frontend-enterprise-catalog-search'; import { Badge, Card, Form } from '@edx/paragon'; import { QUERY_TITLE_REFINEMENT } from '../../constants'; @@ -16,11 +17,7 @@ export function CardCheckbox({ queryTitle }) { } }; return ( - + {/* span here because radio buttons require children */} @@ -50,7 +47,7 @@ function CatalogSelectionCard({
    {label}
    - )} + )} subtitle={labelDetail} actions={} /> diff --git a/src/components/catalogSelectionDeck/CatalogSelectionDeck.jsx b/src/components/catalogSelectionDeck/CatalogSelectionDeck.jsx index 0054d3c1..c4aa0d4d 100644 --- a/src/components/catalogSelectionDeck/CatalogSelectionDeck.jsx +++ b/src/components/catalogSelectionDeck/CatalogSelectionDeck.jsx @@ -14,34 +14,61 @@ const educationVariant = 'light'; function CatalogSelectionDeck({ intl, title, hide }) { const config = getConfig(); return ( -
    +

    {title}

    diff --git a/src/components/catalogSelectionDeck/CatalogSelectionDeck.messages.js b/src/components/catalogSelectionDeck/CatalogSelectionDeck.messages.js index c9327d2b..2096d582 100644 --- a/src/components/catalogSelectionDeck/CatalogSelectionDeck.messages.js +++ b/src/components/catalogSelectionDeck/CatalogSelectionDeck.messages.js @@ -18,7 +18,8 @@ const messages = defineMessages({ }, 'catalogSelectionDeck.edxForBusiness.body': { id: 'catalogSelectionDeck.edxForBusiness.body', - defaultMessage: '\u2713 Unlimited access to 1,000+ courses\n\u2713 Professional certificates and select programs\n\u2713 Self-paced courses', + defaultMessage: + '\u2713 Unlimited access to 1,000+ courses\n\u2713 Professional certificates and select programs\n\u2713 Self-paced courses', description: 'description of filter', }, 'catalogSelectionDeck.edxForOnlineEdu.badge': { @@ -38,7 +39,8 @@ const messages = defineMessages({ }, 'catalogSelectionDeck.edxForOnlineEdu.body': { id: 'catalogSelectionDeck.edxForOnlineEdu.body', - defaultMessage: '\u2713 Unlimited access to 1,000+ courses\n\u2713 Professional certificates and select programs\n\u2713 Self-paced courses', + defaultMessage: + '\u2713 Unlimited access to 1,000+ courses\n\u2713 Professional certificates and select programs\n\u2713 Self-paced courses', description: 'description of filter', }, 'catalogSelectionDeck.aLaCarte.badge': { @@ -58,7 +60,8 @@ const messages = defineMessages({ }, 'catalogSelectionDeck.aLaCarte.body': { id: 'aLaCarte.body', - defaultMessage: '\u2713 Select from all 3,000+ courses available on edX including all programs and certificates\n\u2713 Self-paced and instructor-paced courses', + defaultMessage: + '\u2713 Select from all 3,000+ courses available on edX including all programs and certificates\n\u2713 Self-paced and instructor-paced courses', description: 'description of filter', }, }); diff --git a/src/components/catalogSelectionDeck/CatalogSelectionDeck.scss b/src/components/catalogSelectionDeck/CatalogSelectionDeck.scss index 88f0ace0..317c107a 100644 --- a/src/components/catalogSelectionDeck/CatalogSelectionDeck.scss +++ b/src/components/catalogSelectionDeck/CatalogSelectionDeck.scss @@ -1,6 +1,6 @@ -@import "~@edx/brand/paragon/overrides.scss"; +@import '~@edx/brand/paragon/overrides.scss'; -.catalog-selection-deck{ +.catalog-selection-deck { @extend .mb-5; white-space: pre-line; padding: 0px 6px; diff --git a/src/components/catalogSelectionDeck/CatalogSelectionDeck.test.jsx b/src/components/catalogSelectionDeck/CatalogSelectionDeck.test.jsx index 08991a45..aa2881b7 100644 --- a/src/components/catalogSelectionDeck/CatalogSelectionDeck.test.jsx +++ b/src/components/catalogSelectionDeck/CatalogSelectionDeck.test.jsx @@ -1,7 +1,10 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import { screen, getDefaultNormalizer } from '@testing-library/react'; -import { SEARCH_FACET_FILTERS, SearchContext } from '@edx/frontend-enterprise-catalog-search'; +import { + SEARCH_FACET_FILTERS, + SearchContext, +} from '@edx/frontend-enterprise-catalog-search'; import CatalogSelectionDeck from './CatalogSelectionDeck'; import { renderWithRouter } from '../tests/testUtils'; import messages from './CatalogSelectionDeck.messages'; @@ -9,13 +12,11 @@ import QUERY_TITLE_REFINEMENT from '../../constants'; const DEFAULT_SEARCH_CONTEXT_VALUE = { refinements: {} }; -const mockConfig = () => ( - { - EDX_FOR_BUSINESS_TITLE: 'ayylmao', - EDX_FOR_ONLINE_EDU_TITLE: 'foo', - EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', - } -); +const mockConfig = () => ({ + EDX_FOR_BUSINESS_TITLE: 'ayylmao', + EDX_FOR_ONLINE_EDU_TITLE: 'foo', + EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), @@ -27,9 +28,10 @@ function SearchDataWrapper({ children, searchContextValue }) { return ( {children} @@ -41,35 +43,27 @@ const label = 'foo'; describe('CatalogSelectionDeck', () => { it('renders all cards', () => { renderWithRouter( - - + + , ); Object.keys(messages).forEach((key) => { // Note: we just pick out the first match for this basic test because some messages appear more than once // e.g. both cards for Education and Business have some identical text. - expect(screen.getAllByText( - messages[key].defaultMessage, - { - normalizer: - getDefaultNormalizer({ trim: false, collapseWhitespace: false }), - }, - )[0]) - .toBeInTheDocument(); + expect( + screen.getAllByText(messages[key].defaultMessage, { + normalizer: getDefaultNormalizer({ + trim: false, + collapseWhitespace: false, + }), + })[0], + ).toBeInTheDocument(); }); }); it('renders title', () => { renderWithRouter( - - + + , ); expect(screen.getByText(label)).toBeInTheDocument(); diff --git a/src/components/catalogs/CatalogSearch.jsx b/src/components/catalogs/CatalogSearch.jsx index a333c851..d378a34f 100644 --- a/src/components/catalogs/CatalogSearch.jsx +++ b/src/components/catalogs/CatalogSearch.jsx @@ -1,10 +1,17 @@ /* eslint eqeqeq: "off" */ import React, { useContext, useState, useMemo } from 'react'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform/config'; import { Configure, Index, InstantSearch } from 'react-instantsearch-dom'; -import { SearchHeader, SearchContext } from '@edx/frontend-enterprise-catalog-search'; +import { + SearchHeader, + SearchContext, +} from '@edx/frontend-enterprise-catalog-search'; import { useAlgoliaIndex } from './data/hooks'; import PageWrapper from '../PageWrapper'; @@ -18,11 +25,16 @@ import { } from '../../constants'; import CatalogSearchResults from '../catalogSearchResults/CatalogSearchResults'; import CatalogInfoModal from '../catalogInfoModal/CatalogInfoModal'; -import { mapAlgoliaObjectToProgram, mapAlgoliaObjectToCourse } from '../../utils/algoliaUtils'; +import { + mapAlgoliaObjectToProgram, + mapAlgoliaObjectToCourse, +} from '../../utils/algoliaUtils'; import messages from '../catalogSearchResults/CatalogSearchResults.messages'; function CatalogSearch(intl) { - const { refinements: { content_type: contentType } } = useContext(SearchContext); + const { + refinements: { content_type: contentType }, + } = useContext(SearchContext); const { algoliaIndexName, searchClient } = useAlgoliaIndex(); const courseFilter = `content_type:${CONTENT_TYPE_COURSE} AND NOT course_type:${EXECUTIVE_EDUCATION_2U_COURSE_TYPE}`; const programFilter = `content_type:${CONTENT_TYPE_PROGRAM}`; @@ -49,13 +61,10 @@ function CatalogSearch(intl) { } const config = getConfig(); - const courseIndex = useMemo( - () => { - const cIndex = searchClient.initIndex(config.ALGOLIA_INDEX_NAME); - return cIndex; - }, - [config.ALGOLIA_INDEX_NAME, searchClient], - ); + const courseIndex = useMemo(() => { + const cIndex = searchClient.initIndex(config.ALGOLIA_INDEX_NAME); + return cIndex; + }, [config.ALGOLIA_INDEX_NAME, searchClient]); const suggestedCourseOnClick = (hit) => { if (hit.program_type !== undefined) { @@ -76,10 +85,7 @@ function CatalogSearch(intl) { description="Search dialogue." tagName="h2" /> - +
    {(!contentType || contentType.length === 2) && (noCourseResults === noProgramResults || !noCourseResults) && ( - <> - - - - - - - - - + <> + + + + + + + + + )} - {(!contentType || contentType.length === 2) && (noCourseResults && !noProgramResults) && ( - <> + {(!contentType || contentType.length === 2) + && noCourseResults + && !noProgramResults && ( + <> + + + + + + + + + + )} + {specifiedContentType === CONTENT_TYPE_PROGRAM && ( + )} + {specifiedContentType === CONTENT_TYPE_COURSE && ( - - - )} - {(specifiedContentType === CONTENT_TYPE_PROGRAM) && ( - - - - - )} - {(specifiedContentType === CONTENT_TYPE_COURSE) && ( - - - )} diff --git a/src/components/catalogs/CatalogSearch.test.jsx b/src/components/catalogs/CatalogSearch.test.jsx index d261bf5e..ea8b6751 100644 --- a/src/components/catalogs/CatalogSearch.test.jsx +++ b/src/components/catalogs/CatalogSearch.test.jsx @@ -1,6 +1,9 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; -import { SEARCH_FACET_FILTERS, SearchContext } from '@edx/frontend-enterprise-catalog-search'; +import { + SEARCH_FACET_FILTERS, + SearchContext, +} from '@edx/frontend-enterprise-catalog-search'; import { screen } from '@testing-library/react'; import { renderWithRouter } from '../tests/testUtils'; import QUERY_TITLE_REFINEMENT from '../../constants'; @@ -9,22 +12,27 @@ import '../../../__mocks__/react-instantsearch-dom'; jest.mock('react-instantsearch-dom', () => ({ ...jest.requireActual('react-instantsearch-dom'), - InstantSearch: () => (
    SEARCH
    ), - Index: () => (
    SEARCH
    ), + InstantSearch: () =>
    SEARCH
    , + Index: () =>
    SEARCH
    , })); const DEFAULT_SEARCH_CONTEXT_VALUE = { refinements: {} }; -const COURSE_SEARCH_CONTEXT_VALUE = { refinements: { content_type: ['course'] } }; -const PROGRAM_SEARCH_CONTEXT_VALUE = { refinements: { content_type: ['program'] } }; +const COURSE_SEARCH_CONTEXT_VALUE = { + refinements: { content_type: ['course'] }, +}; +const PROGRAM_SEARCH_CONTEXT_VALUE = { + refinements: { content_type: ['program'] }, +}; // eslint-disable-next-line react/prop-types function SearchDataWrapper({ children, searchContextValue }) { return ( {children} @@ -34,9 +42,7 @@ function SearchDataWrapper({ children, searchContextValue }) { describe('Catalog Search component', () => { it('properly renders component', () => { renderWithRouter( - + , ); @@ -44,9 +50,7 @@ describe('Catalog Search component', () => { }); it('properly renders component with program content type context', () => { renderWithRouter( - + , ); @@ -54,9 +58,7 @@ describe('Catalog Search component', () => { }); it('properly renders component with course content type context', () => { renderWithRouter( - + , ); diff --git a/src/components/catalogs/data/hooks.jsx b/src/components/catalogs/data/hooks.jsx index 9695b2d5..b444b801 100644 --- a/src/components/catalogs/data/hooks.jsx +++ b/src/components/catalogs/data/hooks.jsx @@ -10,10 +10,10 @@ const useAlgoliaIndex = () => { // note: calling the getConfig outside of a hook/render function won't work // if using `mergeConfig` const config = getConfig(); - const searchClient = useMemo(() => algoliasearch( - config.ALGOLIA_APP_ID, - config.ALGOLIA_SEARCH_API_KEY, - ), [config]); + const searchClient = useMemo( + () => algoliasearch(config.ALGOLIA_APP_ID, config.ALGOLIA_SEARCH_API_KEY), + [config], + ); return { algoliaIndexName: config.ALGOLIA_INDEX_NAME, searchClient }; }; @@ -24,13 +24,15 @@ const useMarketingSite = () => { const useSelectedCourse = () => { const [course, setCourse] = useState(null); - const isProgram = useMemo(() => course && course.contentType === CONTENT_TYPE_PROGRAM, [course]); - const isCourse = useMemo(() => course && course.contentType === CONTENT_TYPE_COURSE, [course]); + const isProgram = useMemo( + () => course && course.contentType === CONTENT_TYPE_PROGRAM, + [course], + ); + const isCourse = useMemo( + () => course && course.contentType === CONTENT_TYPE_COURSE, + [course], + ); return [course, setCourse, isProgram, isCourse]; }; -export { - useAlgoliaIndex, - useMarketingSite, - useSelectedCourse, -}; +export { useAlgoliaIndex, useMarketingSite, useSelectedCourse }; diff --git a/src/components/catalogs/styles/_enterprise_catalogs.scss b/src/components/catalogs/styles/_enterprise_catalogs.scss index 988d3db3..97031a60 100644 --- a/src/components/catalogs/styles/_enterprise_catalogs.scss +++ b/src/components/catalogs/styles/_enterprise_catalogs.scss @@ -124,7 +124,7 @@ display: inline-block; } li:not(:last-child):after { - content: "\2022"; + content: '\2022'; margin: 0 10px; } } diff --git a/src/components/catalogs/styles/_index.scss b/src/components/catalogs/styles/_index.scss index defb56c2..9c8d795f 100644 --- a/src/components/catalogs/styles/_index.scss +++ b/src/components/catalogs/styles/_index.scss @@ -1,2 +1,2 @@ -@import "./enterprise_catalogs"; -@import "./skeleton"; +@import './enterprise_catalogs'; +@import './skeleton'; diff --git a/src/components/catalogs/tests/data/hooks.test.jsx b/src/components/catalogs/tests/data/hooks.test.jsx index 50e1b667..c8b2bf63 100644 --- a/src/components/catalogs/tests/data/hooks.test.jsx +++ b/src/components/catalogs/tests/data/hooks.test.jsx @@ -7,14 +7,12 @@ const appid = 'test app'; const key = 'test key'; const marketingSite = 'http://test.edx.org'; -const mockConfig = () => ( - { - ALGOLIA_INDEX_NAME: indexName, - ALGOLIA_APP_ID: appid, - ALGOLIA_SEARCH_API_KEY: key, - HUBSPOT_MARKETING_URL: marketingSite, - } -); +const mockConfig = () => ({ + ALGOLIA_INDEX_NAME: indexName, + ALGOLIA_APP_ID: appid, + ALGOLIA_SEARCH_API_KEY: key, + HUBSPOT_MARKETING_URL: marketingSite, +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), diff --git a/src/components/courseCard/CourseCard.jsx b/src/components/courseCard/CourseCard.jsx index e62e79ab..b0c09017 100644 --- a/src/components/courseCard/CourseCard.jsx +++ b/src/components/courseCard/CourseCard.jsx @@ -23,7 +23,12 @@ function CourseCard({ intl, onClick, original }) { const altText = `${title} course image`; return ( - onClick(original)}> + onClick(original)} + > { , ); expect(screen.queryByText(defaultProps.original.title)).toBeInTheDocument(); - expect(screen.queryByText(defaultProps.original.partners[0].name)).toBeInTheDocument(); + expect( + screen.queryByText(defaultProps.original.partners[0].name), + ).toBeInTheDocument(); expect(screen.queryByText('$100 • Available Now')).toBeInTheDocument(); expect(screen.queryByText('Business')).toBeInTheDocument(); }); @@ -48,6 +50,6 @@ describe('Course card works as expected', () => { ); const imageAltText = `${originalData.title} course image`; fireEvent.error(screen.getByAltText(imageAltText)); - await expect((screen.getByAltText(imageAltText).src)).not.toBeUndefined; + await expect(screen.getByAltText(imageAltText).src).not.toBeUndefined; }); }); diff --git a/src/components/helperComponents/Highlighted.jsx b/src/components/helperComponents/Highlighted.jsx index d1acb37f..2df6ad08 100644 --- a/src/components/helperComponents/Highlighted.jsx +++ b/src/components/helperComponents/Highlighted.jsx @@ -12,11 +12,19 @@ function Highlighted({ text, highlight, highlightClass }) { return ( <> - {parts.filter(part => part).map((part, i) => ( - regex.test(part) ? {part} + {parts + .filter((part) => part) + .map((part, i) => (regex.test(part) ? ( + + {part} + + ) : ( // eslint-disable-next-line react/no-array-index-key - : {part} - ))} + {part} + )))} ); } diff --git a/src/components/helperComponents/Highlighted.test.jsx b/src/components/helperComponents/Highlighted.test.jsx index 39db7c3c..bda184f1 100644 --- a/src/components/helperComponents/Highlighted.test.jsx +++ b/src/components/helperComponents/Highlighted.test.jsx @@ -20,7 +20,13 @@ describe('Highlighted', () => { const text = 'Bears r gr8'; const highlight = 'rs r gr'; const highlightClass = 'awesome'; - render(); + render( + , + ); const highlightedText = screen.getByText(highlight); expect(highlightedText).toHaveClass(highlightClass); expect(highlightedText).toHaveClass('highlighted'); diff --git a/src/components/hero/Hero.jsx b/src/components/hero/Hero.jsx index 29e7b414..0af6a614 100644 --- a/src/components/hero/Hero.jsx +++ b/src/components/hero/Hero.jsx @@ -55,7 +55,10 @@ Desktop.propTypes = { }).isRequired, }; function Tablet({ children }) { - const isTablet = useMediaQuery({ minWidth: breakpoints.medium.minWidth, maxWidth: breakpoints.medium.maxWidth }); + const isTablet = useMediaQuery({ + minWidth: breakpoints.medium.minWidth, + maxWidth: breakpoints.medium.maxWidth, + }); return isTablet ? children : null; } Tablet.propTypes = { @@ -72,7 +75,9 @@ function Hero({ intl, text, highlight }) { return (
    -

    +

    + +

    diff --git a/src/components/programCard/ProgramCard.jsx b/src/components/programCard/ProgramCard.jsx index e35aecab..31c6470e 100644 --- a/src/components/programCard/ProgramCard.jsx +++ b/src/components/programCard/ProgramCard.jsx @@ -12,9 +12,7 @@ import messages from './ProgramCard.messages'; import { getCourses } from '../../utils/common'; import defaultCardHeader from '../../static/default-card-header-dark.png'; -function ProgramCard({ - intl, onClick, original, -}) { +function ProgramCard({ intl, onClick, original }) { const { title, card_image_url, @@ -24,13 +22,24 @@ function ProgramCard({ program_type, } = original; - const alaCarteRequested = enterprise_catalog_query_titles?.includes(process.env.EDX_ENTERPRISE_ALACARTE_TITLE); - const businessCatalogRequested = enterprise_catalog_query_titles?.includes(process.env.EDX_FOR_BUSINESS_TITLE); - const eduCatalogRequested = enterprise_catalog_query_titles?.includes(process.env.EDX_FOR_ONLINE_EDU_TITLE); + const alaCarteRequested = enterprise_catalog_query_titles?.includes( + process.env.EDX_ENTERPRISE_ALACARTE_TITLE, + ); + const businessCatalogRequested = enterprise_catalog_query_titles?.includes( + process.env.EDX_FOR_BUSINESS_TITLE, + ); + const eduCatalogRequested = enterprise_catalog_query_titles?.includes( + process.env.EDX_FOR_ONLINE_EDU_TITLE, + ); const imageSrc = card_image_url || defaultCardHeader; return ( - onClick(original)}> + onClick(original)} + > { authoring_organizations[0].name }

    } + subtitle={ + authoring_organizations.length !== 0 && ( +

    {authoring_organizations[0].name}

    + ) + } /> - + {program_type} -

    {getCourses(course_keys.length, 'Course')}

    +

    + {getCourses(course_keys.length, 'Course')} +

    {alaCarteRequested && ( @@ -92,7 +101,10 @@ ProgramCard.propTypes = { card_image_url: PropTypes.string, course_keys: PropTypes.arrayOf(PropTypes.string), authoring_organizations: PropTypes.arrayOf( - PropTypes.shape({ name: PropTypes.string, logo_image_url: PropTypes.string }), + PropTypes.shape({ + name: PropTypes.string, + logo_image_url: PropTypes.string, + }), ), enterprise_catalog_query_titles: PropTypes.arrayOf(PropTypes.string), program_type: PropTypes.string, diff --git a/src/components/programCard/ProgramCard.messages.jsx b/src/components/programCard/ProgramCard.messages.jsx index fe6257c4..aaa1bba3 100644 --- a/src/components/programCard/ProgramCard.messages.jsx +++ b/src/components/programCard/ProgramCard.messages.jsx @@ -24,7 +24,8 @@ const messages = defineMessages({ 'ProgramCard.priceNotAvailable': { id: 'ProgramCard.priceNotAvailable', defaultMessage: ' Not Available', - description: 'When a course price is not available, notify learners that there is no data available to display.', + description: + 'When a course price is not available, notify learners that there is no data available to display.', }, }); diff --git a/src/components/programCard/ProgramCard.test.jsx b/src/components/programCard/ProgramCard.test.jsx index 53ab6b02..c2c8e595 100644 --- a/src/components/programCard/ProgramCard.test.jsx +++ b/src/components/programCard/ProgramCard.test.jsx @@ -5,14 +5,12 @@ import '@testing-library/jest-dom/extend-expect'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import ProgramCard from './ProgramCard'; -const mockConfig = () => ( - { - EDX_FOR_BUSINESS_TITLE: 'ayylmao', - EDX_FOR_ONLINE_EDU_TITLE: 'foo', - EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', - FEATURE_CARD_VIEW_ENABLED: 'True', - } -); +const mockConfig = () => ({ + EDX_FOR_BUSINESS_TITLE: 'ayylmao', + EDX_FOR_ONLINE_EDU_TITLE: 'foo', + EDX_ENTERPRISE_ALACARTE_TITLE: 'baz', + FEATURE_CARD_VIEW_ENABLED: 'True', +}); jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), @@ -45,8 +43,12 @@ describe('Program card works as expected', () => { , ); expect(screen.queryByText(defaultProps.original.title)).toBeInTheDocument(); - expect(screen.queryByText(defaultProps.original.authoring_organizations[0].name)).toBeInTheDocument(); - expect(screen.queryByText(defaultProps.original.program_type)).toBeInTheDocument(); + expect( + screen.queryByText(defaultProps.original.authoring_organizations[0].name), + ).toBeInTheDocument(); + expect( + screen.queryByText(defaultProps.original.program_type), + ).toBeInTheDocument(); expect(screen.queryByText('2 Courses')).toBeInTheDocument(); expect(screen.queryByText('Business')); }); @@ -57,6 +59,6 @@ describe('Program card works as expected', () => { , ); fireEvent.error(screen.getByAltText(originalData.title)); - await expect((screen.getByAltText(originalData.title)).src).not.toBeUndefined; + await expect(screen.getByAltText(originalData.title).src).not.toBeUndefined; }); }); diff --git a/src/components/subheader/subheader.jsx b/src/components/subheader/subheader.jsx index 261b3c5c..25805ef7 100644 --- a/src/components/subheader/subheader.jsx +++ b/src/components/subheader/subheader.jsx @@ -1,12 +1,8 @@ -import { - Container, -} from '@edx/paragon'; +import { Container } from '@edx/paragon'; import React from 'react'; import PropTypes from 'prop-types'; -function Subheader({ - title, children, -}) { +function Subheader({ title, children }) { return (
    diff --git a/src/components/subheader/subheader.test.jsx b/src/components/subheader/subheader.test.jsx index 99dead3f..0b148054 100644 --- a/src/components/subheader/subheader.test.jsx +++ b/src/components/subheader/subheader.test.jsx @@ -12,7 +12,11 @@ const props = { describe('callToAction', () => { it('renders the text passed to it', () => { - render(

    {text}

    ); + render( + +

    {text}

    +
    , + ); // getByText will error if title is not in the document. screen.getByText(props.title); screen.getByText(text); diff --git a/src/components/tests/PageWrapper.test.jsx b/src/components/tests/PageWrapper.test.jsx index 89fdb5a8..ffbed1db 100644 --- a/src/components/tests/PageWrapper.test.jsx +++ b/src/components/tests/PageWrapper.test.jsx @@ -6,7 +6,11 @@ import PageWrapper, { DATA_TEST_ID } from '../PageWrapper'; describe('PageWrapper rendering', () => { test('PageWrapper renders successfully', () => { - render(
    a child
    ); + render( + +
    a child
    +
    , + ); expect(screen.getByText('a child')).toBeInTheDocument(); expect(screen.getByTestId(DATA_TEST_ID)).toBeInTheDocument(); }); diff --git a/src/components/tests/testUtils.jsx b/src/components/tests/testUtils.jsx index 012622e1..a4d2af84 100644 --- a/src/components/tests/testUtils.jsx +++ b/src/components/tests/testUtils.jsx @@ -9,6 +9,7 @@ import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; const TEST_CONFIG = { + ALGOLIA_APP_ID: 'app', ALGOLIA_INDEX_NAME: 'index', ALGOLIA_SEARCH_API_KEY: 'key', @@ -16,15 +17,17 @@ const TEST_CONFIG = { export const renderWithRouter = (ui, { route } = {}) => { const history = createMemoryHistory(); - if (route) { history.push(route); } + if (route) { + history.push(route); + } const locale = 'en'; return render( - // eslint-disable-next-line react/jsx-no-constructed-context-values - + - - {ui} - + {ui} , ); diff --git a/src/config/index.js b/src/config/index.js index 153f4bf7..dceea83d 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -10,8 +10,12 @@ const hasFeatureFlagEnabled = (featureFlag) => { }; const features = { - ENABLE_PROGRAMS: (process.env.FEATURE_ENABLE_PROGRAMS === 'true') || hasFeatureFlagEnabled(FEATURE_ENABLE_PROGRAMS), - PROGRAM_TYPE_FACET: (process.env.FEATURE_PROGRAM_TYPE_FACET === 'true') || hasFeatureFlagEnabled(FEATURE_PROGRAM_TYPE_FACET), + ENABLE_PROGRAMS: + process.env.FEATURE_ENABLE_PROGRAMS === 'true' + || hasFeatureFlagEnabled(FEATURE_ENABLE_PROGRAMS), + PROGRAM_TYPE_FACET: + process.env.FEATURE_PROGRAM_TYPE_FACET === 'true' + || hasFeatureFlagEnabled(FEATURE_PROGRAM_TYPE_FACET), }; export default features; diff --git a/src/constants.js b/src/constants.js index 560dca35..4088c5a7 100644 --- a/src/constants.js +++ b/src/constants.js @@ -12,7 +12,11 @@ export const TRACKING_APP_NAME = 'explore-catalog'; // end: tracking related export const QUERY_TITLE_REFINEMENT = 'enterprise_catalog_query_titles'; export const AVAILABILITY_REFINEMENT = 'availability'; -export const AVAILABILITY_REFINEMENT_DEFAULTS = ['Available Now', 'Starting Soon', 'Upcoming']; +export const AVAILABILITY_REFINEMENT_DEFAULTS = [ + 'Available Now', + 'Starting Soon', + 'Upcoming', +]; export const CONTENT_TYPE_REFINEMENT = 'content_type'; export const HIDE_CARDS_REFINEMENT = 'hide_cards'; export const HIDE_PRICE_REFINEMENT = 'hide_price'; @@ -33,7 +37,10 @@ const CREDIT_VERIFIED_AUDIT_COURSE_TYPE = 'credit-verified-audit'; export const EXECUTIVE_EDUCATION_2U_COURSE_TYPE = 'executive-education-2u'; export const TWOU_COURSE_TYPES = [EXECUTIVE_EDUCATION_2U_COURSE_TYPE]; export const EDX_COURSES_COURSE_TYPES = [ - AUDIT_COURSE_TYPE, VERIFIED_AUDIT_COURSE_TYPE, PROFESSIONAL_COURSE_TYPE, CREDIT_VERIFIED_AUDIT_COURSE_TYPE, + AUDIT_COURSE_TYPE, + VERIFIED_AUDIT_COURSE_TYPE, + PROFESSIONAL_COURSE_TYPE, + CREDIT_VERIFIED_AUDIT_COURSE_TYPE, ]; const OVERRIDE_FACET_FILTERS = []; @@ -55,14 +62,16 @@ if (features.PROGRAM_TYPE_FACET) { OVERRIDE_FACET_FILTERS.push(PROGRAM_TYPE_FACET_OVERRIDE); } -OVERRIDE_FACET_FILTERS.forEach(({ overrideSearchKey, overrideSearchValue, updatedFacetFilterValue }) => { - SEARCH_FACET_FILTERS.find((facetFilter, index) => { - if (facetFilter[overrideSearchKey] === overrideSearchValue) { - SEARCH_FACET_FILTERS[index] = updatedFacetFilterValue; - return true; - } - return false; - }); -}); +OVERRIDE_FACET_FILTERS.forEach( + ({ overrideSearchKey, overrideSearchValue, updatedFacetFilterValue }) => { + SEARCH_FACET_FILTERS.find((facetFilter, index) => { + if (facetFilter[overrideSearchKey] === overrideSearchValue) { + SEARCH_FACET_FILTERS[index] = updatedFacetFilterValue; + return true; + } + return false; + }); + }, +); export { SEARCH_FACET_FILTERS }; diff --git a/src/data/services/EnterpriseCatalogAPIService.js b/src/data/services/EnterpriseCatalogAPIService.js index fb905778..79123a4e 100644 --- a/src/data/services/EnterpriseCatalogAPIService.js +++ b/src/data/services/EnterpriseCatalogAPIService.js @@ -9,35 +9,39 @@ class EnterpriseCatalogApiService { static fetchIndexedContentMetadata(options, query) { const facetQuery = query ? `&query=${query}` : ''; - const enterpriseListUrl = `${EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl}/catalog_csv_data/?${qs.stringify(options)}${facetQuery}`; + const enterpriseListUrl = `${ + EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl + }/catalog_csv_data/?${qs.stringify(options)}${facetQuery}`; return EnterpriseCatalogApiService.apiClient().get(enterpriseListUrl); } static generateCsvDownloadLink(options, query) { const facetQuery = query ? `&query=${encodeURIComponent(query)}` : ''; - const enterpriseListUrl = `${EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl}/catalog_workbook?${qs.stringify(options)}${facetQuery}`; + const enterpriseListUrl = `${ + EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl + }/catalog_workbook?${qs.stringify(options)}${facetQuery}`; return enterpriseListUrl; } static fetchContentMetadataWithFacets(facets, query) { - return this.fetchIndexedContentMetadata(facets, query) - .then((response) => { - const { data } = response; - return data; - }); + return this.fetchIndexedContentMetadata(facets, query).then((response) => { + const { data } = response; + return data; + }); } static fetchDefaultCoursesInCatalog(options) { - const enterpriseListUrl = `${EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl}/default_course_set?${qs.stringify(options)}`; + const enterpriseListUrl = `${ + EnterpriseCatalogApiService.enterpriseCatalogServiceApiUrl + }/default_course_set?${qs.stringify(options)}`; return EnterpriseCatalogApiService.apiClient().get(enterpriseListUrl); } static fetchDefaultCoursesInCatalogWithFacets(facets) { - return this.fetchDefaultCoursesInCatalog(facets) - .then((response) => { - const { data } = response; - return data; - }); + return this.fetchDefaultCoursesInCatalog(facets).then((response) => { + const { data } = response; + return data; + }); } } diff --git a/src/i18n/messages/ca.json b/src/i18n/messages/ca.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/ca.json +++ b/src/i18n/messages/ca.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/ko_kr.json b/src/i18n/messages/ko_kr.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/ko_kr.json +++ b/src/i18n/messages/ko_kr.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/pt_br.json b/src/i18n/messages/pt_br.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/pt_br.json +++ b/src/i18n/messages/pt_br.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/i18n/messages/uk.json b/src/i18n/messages/uk.json index 9e26dfee..0967ef42 100644 --- a/src/i18n/messages/uk.json +++ b/src/i18n/messages/uk.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/src/index.jsx b/src/index.jsx index df6d727b..f6c9a54a 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -2,7 +2,11 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; import { - APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig, + APP_INIT_ERROR, + APP_READY, + subscribe, + initialize, + mergeConfig, } from '@edx/frontend-platform'; import { ErrorPage } from '@edx/frontend-platform/react'; import React from 'react'; @@ -19,14 +23,14 @@ import appMessages from './i18n'; import './index.scss'; subscribe(APP_READY, () => { - ReactDOM.render( - , - document.getElementById('root'), - ); + ReactDOM.render(, document.getElementById('root')); }); subscribe(APP_INIT_ERROR, (error) => { - ReactDOM.render(, document.getElementById('root')); + ReactDOM.render( + , + document.getElementById('root'), + ); }); initialize({ @@ -39,17 +43,15 @@ initialize({ HUBSPOT_MARKETING_URL: process.env.HUBSPOT_MARKETING_URL || null, EDX_FOR_BUSINESS_TITLE: process.env.EDX_FOR_BUSINESS_TITLE || null, EDX_FOR_ONLINE_EDU_TITLE: process.env.EDX_FOR_ONLINE_EDU_TITLE || null, - EDX_ENTERPRISE_ALACARTE_TITLE: process.env.EDX_ENTERPRISE_ALACARTE_TITLE || null, - FEATURE_CARD_VIEW_ENABLED: process.env.FEATURE_CARD_VIEW_ENABLED || false, - FEATURE_PROGRAM_TYPE_FACET: process.env.FEATURE_PROGRAM_TYPE_FACET || false, + EDX_ENTERPRISE_ALACARTE_TITLE: + process.env.EDX_ENTERPRISE_ALACARTE_TITLE || null, + FEATURE_CARD_VIEW_ENABLED: + process.env.FEATURE_CARD_VIEW_ENABLED || false, + FEATURE_PROGRAM_TYPE_FACET: + process.env.FEATURE_PROGRAM_TYPE_FACET || false, }); }, auth: () => {}, }, - messages: [ - appMessages, - headerMessages, - footerMessages, - paragonMessages, - ], + messages: [appMessages, headerMessages, footerMessages, paragonMessages], }); diff --git a/src/index.scss b/src/index.scss index 74661bfd..ea9846b9 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,25 +1,25 @@ -@import "~@edx/brand/paragon/fonts.scss"; -@import "~@edx/brand/paragon/variables.scss"; -@import "@edx/paragon/scss/core/core.scss"; -@import "~@edx/brand/paragon/overrides.scss"; +@import '~@edx/brand/paragon/fonts.scss'; +@import '~@edx/brand/paragon/variables.scss'; +@import '@edx/paragon/scss/core/core.scss'; +@import '~@edx/brand/paragon/overrides.scss'; -@import "~@edx/frontend-component-header/dist/index"; -@import "~@edx/frontend-component-footer/dist/footer"; +@import '~@edx/frontend-component-header/dist/index'; +@import '~@edx/frontend-component-footer/dist/footer'; -@import "~@edx/frontend-enterprise-catalog-search"; +@import '~@edx/frontend-enterprise-catalog-search'; // Variables and mixins must come before other styles -@import "./styles/variables"; -@import "./styles/mixins"; +@import './styles/variables'; +@import './styles/mixins'; -@import "./styles/site_header"; -@import "./components/catalogs/styles"; -@import "./components/hero/Hero"; -@import "components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton"; -@import "components/catalogSelectionDeck/CatalogSelectionDeck"; +@import './styles/site_header'; +@import './components/catalogs/styles'; +@import './components/hero/Hero'; +@import 'components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton'; +@import 'components/catalogSelectionDeck/CatalogSelectionDeck'; .page-width { - max-width: 1350px; + max-width: 1350px; } .padded-catalog.badge { diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index ce915323..12b23851 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -1,6 +1,5 @@ - @mixin center-vertically { display: flex; align-items: center; justify-content: space-between; -} \ No newline at end of file +} diff --git a/src/utils/algoliaUtils.js b/src/utils/algoliaUtils.js index 1c9086f5..423e5979 100644 --- a/src/utils/algoliaUtils.js +++ b/src/utils/algoliaUtils.js @@ -1,6 +1,6 @@ import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM } from '../constants'; -const extractUuid = aggregationKey => aggregationKey.split(':')[1]; +const extractUuid = (aggregationKey) => aggregationKey.split(':')[1]; /** * Converts and algolia course object, into a course representation usable in this UI @@ -19,9 +19,11 @@ function mapAlgoliaObjectToCourse(algoliaCourseObject, intl, messages) { skill_names: skillNames, } = algoliaCourseObject; const { start: startDate, end: endDate } = courseRun; - const priceText = (coursePrice != null) ? `$${coursePrice.toString()}` : intl.formatMessage( - messages['catalogSearchResult.table.priceNotAvailable'], - ); + const priceText = coursePrice != null + ? `$${coursePrice.toString()}` + : intl.formatMessage( + messages['catalogSearchResult.table.priceNotAvailable'], + ); return { contentType: CONTENT_TYPE_COURSE, courseTitle, @@ -40,8 +42,8 @@ function mapAlgoliaObjectToCourse(algoliaCourseObject, intl, messages) { } /** -* Converts and algolia course object, into a course representation usable in this UI -*/ + * Converts and algolia course object, into a course representation usable in this UI + */ function mapAlgoliaObjectToProgram(algoliaProgramObject) { const { title, diff --git a/src/utils/catalogUtils.js b/src/utils/catalogUtils.js index 81164ffe..3b20d2b2 100644 --- a/src/utils/catalogUtils.js +++ b/src/utils/catalogUtils.js @@ -2,11 +2,21 @@ const nowDate = new Date(Date.now()); function checkSubscriptions(courseAssociatedCatalogs) { - const inBusiness = courseAssociatedCatalogs.includes(process.env.EDX_FOR_BUSINESS_TITLE); - const inEducation = courseAssociatedCatalogs.includes(process.env.EDX_FOR_ONLINE_EDU_TITLE); - if (inBusiness && inEducation) { return 'Included in education and business catalog'; } - if (inBusiness) { return 'Included in business catalog'; } - if (inEducation) { return 'Included in education catalog'; } + const inBusiness = courseAssociatedCatalogs.includes( + process.env.EDX_FOR_BUSINESS_TITLE, + ); + const inEducation = courseAssociatedCatalogs.includes( + process.env.EDX_FOR_ONLINE_EDU_TITLE, + ); + if (inBusiness && inEducation) { + return 'Included in education and business catalog'; + } + if (inBusiness) { + return 'Included in business catalog'; + } + if (inEducation) { + return 'Included in education catalog'; + } return false; } diff --git a/src/utils/common.js b/src/utils/common.js index 526d3b9e..d290e739 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -4,14 +4,21 @@ export function getSelectedCatalogFromURL() { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); - return (params.enterprise_catalog_query_titles); + return params.enterprise_catalog_query_titles; } export function formatDate(courseRun) { if (courseRun) { if (courseRun.start && courseRun.end) { - const startDate = (new Date(courseRun.start)).toLocaleDateString('default', { month: 'short', day: 'numeric', year: 'numeric' }); - const endDate = (new Date(courseRun.end)).toLocaleDateString('default', { month: 'short', day: 'numeric', year: 'numeric' }); + const startDate = new Date(courseRun.start).toLocaleDateString( + 'default', + { month: 'short', day: 'numeric', year: 'numeric' }, + ); + const endDate = new Date(courseRun.end).toLocaleDateString('default', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); return `${startDate} - ${endDate}`; } } @@ -19,8 +26,10 @@ export function formatDate(courseRun) { } export function makePlural(num, string) { - if (num > 1 || num === 0) { return (`${num} ${string}s`); } - return (`${num} ${string}`); + if (num > 1 || num === 0) { + return `${num} ${string}s`; + } + return `${num} ${string}`; } export function getCourses(numCourses, string) { @@ -28,8 +37,8 @@ export function getCourses(numCourses, string) { return 'Courses available upon enrollment'; } if (numCourses > 1) { - return (`${numCourses} ${string}s`); + return `${numCourses} ${string}s`; } - return (`${numCourses} ${string}`); + return `${numCourses} ${string}`; }