diff --git a/package-lock.json b/package-lock.json index bfc83e2e94..4623697e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@edx/frontend-enterprise-logistration": "2.1.1", "@edx/frontend-enterprise-utils": "2.2.0", "@edx/frontend-platform": "2.6.2", - "@edx/paragon": "20.21.2", + "@edx/paragon": "20.22.2", "@fortawesome/fontawesome-svg-core": "1.2.32", "@fortawesome/free-brands-svg-icons": "5.15.1", "@fortawesome/free-regular-svg-icons": "5.15.1", @@ -2985,9 +2985,9 @@ } }, "node_modules/@edx/paragon": { - "version": "20.21.2", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.21.2.tgz", - "integrity": "sha512-s3yJE0hkT5VdmUMQI7JWZeNvXw58Cq2qx3pmrXOziT4v8bwO4UcXzBW+92H7BUpnnVeF6iPrOm6SBjJPG4V9BQ==", + "version": "20.22.2", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.22.2.tgz", + "integrity": "sha512-m6Pxn5YL1Depi2A3HfHSJMzygWWuRz+bfyuw0014fdSXfIVoNXx9rloFJW1buqfxiZKtDRSqWurmlFShn9uaEw==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", @@ -26736,9 +26736,9 @@ } }, "@edx/paragon": { - "version": "20.21.2", - "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.21.2.tgz", - "integrity": "sha512-s3yJE0hkT5VdmUMQI7JWZeNvXw58Cq2qx3pmrXOziT4v8bwO4UcXzBW+92H7BUpnnVeF6iPrOm6SBjJPG4V9BQ==", + "version": "20.22.2", + "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.22.2.tgz", + "integrity": "sha512-m6Pxn5YL1Depi2A3HfHSJMzygWWuRz+bfyuw0014fdSXfIVoNXx9rloFJW1buqfxiZKtDRSqWurmlFShn9uaEw==", "requires": { "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", diff --git a/package.json b/package.json index 2767c2a6f9..b0df1c146d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@edx/frontend-enterprise-logistration": "2.1.1", "@edx/frontend-enterprise-utils": "2.2.0", "@edx/frontend-platform": "2.6.2", - "@edx/paragon": "20.21.2", + "@edx/paragon": "20.22.2", "@fortawesome/fontawesome-svg-core": "1.2.32", "@fortawesome/free-brands-svg-icons": "5.15.1", "@fortawesome/free-regular-svg-icons": "5.15.1", diff --git a/src/components/TagCloud/index.jsx b/src/components/TagCloud/index.jsx index 6e4defe7fc..0b6cfcec19 100644 --- a/src/components/TagCloud/index.jsx +++ b/src/components/TagCloud/index.jsx @@ -1,26 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; - -import './styles/TagCloud.scss'; +import { Chip } from '@edx/paragon'; +import { Close } from '@edx/paragon/icons'; const TagCloud = ({ tags, onRemove }) => ( - <> -
- -
- - +
+ {tags.map(tag => ( + onRemove(tag.metadata)} + data-testid={tag.title} + > + {tag.title} + + ))} +
); TagCloud.propTypes = { diff --git a/src/components/TagCloud/styles/TagCloud.scss b/src/components/TagCloud/styles/TagCloud.scss deleted file mode 100644 index 7330408b82..0000000000 --- a/src/components/TagCloud/styles/TagCloud.scss +++ /dev/null @@ -1,38 +0,0 @@ -.list-item { - background-color: silver; - color: #273F2F;; - padding-left: 8px; - border-radius: 4px; - margin: 5px; - width: max-content; - list-style: none; - display: inline-block; -} - -.list-item:hover * { - color: #273F2F; -} - -.item{ - padding-left: 0 !important; -} - -.remove { - margin-left: 1em; - cursor: pointer; - padding: 10px 14px; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - border-width: 0; -} - -.remove:hover { - background: #273F2F; - color: silver; -} - -.skills-tag { - line-height: 1.5em; - height: 6em; - overflow: hidden; -} diff --git a/src/components/course/CourseRecommendationCard.jsx b/src/components/course/CourseRecommendationCard.jsx index 515b6f5f60..7967a89215 100644 --- a/src/components/course/CourseRecommendationCard.jsx +++ b/src/components/course/CourseRecommendationCard.jsx @@ -69,7 +69,7 @@ const CourseRecommendationCard = ({ course, isPartnerRecommendation }) => { )} subtitle={course.owners?.length > 0 && ( -

+

{course.owners.map(partner => partner.name).join(', ')} @@ -79,10 +79,7 @@ const CourseRecommendationCard = ({ course, isPartnerRecommendation }) => { {/* Intentionally empty section so the footer is correctly spaced at the bottom of the card */} - - - Course - + Course} /> ); }; diff --git a/src/components/course/CourseRunCard.jsx b/src/components/course/CourseRunCard.jsx index 808a82fde8..976c28ffef 100644 --- a/src/components/course/CourseRunCard.jsx +++ b/src/components/course/CourseRunCard.jsx @@ -234,7 +234,7 @@ const CourseRunCard = ({ const triggerLicenseSubsidyEvent = shouldShowLicenseSubsidyPriceText; return ( - + diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx index 026acaf3fb..d8097ad5f3 100644 --- a/src/components/dashboard/DashboardPage.jsx +++ b/src/components/dashboard/DashboardPage.jsx @@ -38,36 +38,32 @@ export default function DashboardPage() { const userFirstName = useMemo(() => authenticatedUser?.name.split(' ').shift(), [authenticatedUser]); const CoursesTabComponent = ( <> - - - {LICENCE_ACTIVATION_MESSAGE} - - - - - - - - - - - {matches => (matches ? ( - - - - ) : null)} - - - - {subscriptionPlan && showExpirationNotifications && } - - + + {LICENCE_ACTIVATION_MESSAGE} + + + + + + + + + {matches => (matches ? ( + + + + ) : null)} + + + + {subscriptionPlan && showExpirationNotifications && } + ); const PAGE_TITLE = `Dashboard - ${enterpriseConfig.name}`; diff --git a/src/components/dashboard/sidebar/SubsidiesSummary.jsx b/src/components/dashboard/sidebar/SubsidiesSummary.jsx index ad82d56159..8f54c790b3 100644 --- a/src/components/dashboard/sidebar/SubsidiesSummary.jsx +++ b/src/components/dashboard/sidebar/SubsidiesSummary.jsx @@ -79,8 +79,8 @@ const SubsidiesSummary = ({ // TODO: Design debt, don't have cards in a card <>

{hasActiveLicenseOrLicenseRequest && ( @@ -89,7 +89,7 @@ const SubsidiesSummary = ({ licenseRequest={licenseRequests[0]} courseEndDate={courseEndDate} programProgressPage={programProgressPage} - className="mb-2 border-0" + className="border-0 shadow-none" /> )} {hasAssignedCodesOrCodeRequests && ( @@ -98,19 +98,19 @@ const SubsidiesSummary = ({ couponCodeRequestsCount={couponCodeRequests.length} totalCoursesEligibleForCertificate={totalCoursesEligibleForCertificate} programProgressPage={programProgressPage} - className="mb-2 border-0" + className="border-0 shadow-none" /> )} {canEnrollWithEnterpriseOffers && ( )}
{searchCoursesCta && ( {searchCoursesCta} diff --git a/src/components/pathway-progress/PathwayProgressCard.jsx b/src/components/pathway-progress/PathwayProgressCard.jsx index ae0eb43af6..53768e2d7f 100644 --- a/src/components/pathway-progress/PathwayProgressCard.jsx +++ b/src/components/pathway-progress/PathwayProgressCard.jsx @@ -16,7 +16,7 @@ const PathwayProgressCard = ({ pathway: { learnerPathwayProgress } }) => { }; return ( @@ -26,7 +26,6 @@ const PathwayProgressCard = ({ pathway: { learnerPathwayProgress } }) => { data-testid="pathway-card-image" srcAlt="dug" /> - @@ -34,8 +33,8 @@ const PathwayProgressCard = ({ pathway: { learnerPathwayProgress } }) => { )} /> - - + + { } return ( - <> - - - {pathwayProgressData?.length > 0 ? ( - pathwayProgressData.map((pathway) => ( - - )) - ) : ( -
-

{NO_PATHWAYS_ERROR_MESSAGE}

- - - -
- )} -
-
- +
+ {pathwayProgressData?.length > 0 ? ( + + {pathwayProgressData.map((pathway) => ( + + ))} + + ) : ( +
+

{NO_PATHWAYS_ERROR_MESSAGE}

+ + + +
+ )} +
); }; diff --git a/src/components/pathway/SearchPathwayCard.jsx b/src/components/pathway/SearchPathwayCard.jsx index 05b26be228..4cf6ec89e3 100644 --- a/src/components/pathway/SearchPathwayCard.jsx +++ b/src/components/pathway/SearchPathwayCard.jsx @@ -102,7 +102,7 @@ const SearchPathwayCard = ({ isClickable isLoading={isLoading} onClick={handleCardClick} - className="bg-primary-500 border-0" + variant="dark" {...rest} > { return ( @@ -74,28 +74,24 @@ const ProgramListingCard = ({ program }) => { {program.title} )} - subtitle={( -
-
- {program.authoringOrganizations?.length > 0 - && program.authoringOrganizations.map(org => org.key).join(' ')} -
- -
-
- Program Type Logo - {program.type} -
-
-
- )} + subtitle={program.authoringOrganizations?.length > 0 ? ( + + {program.authoringOrganizations.map(org => org.key).join(', ')} + + ) : undefined} /> - - + +
+ Program Type Logo + {program.type} +
+
+ { if (!learnerProgramsData) { return ( - +
- +
); } return ( - <> - - - {learnerProgramsData.length > 0 ? ( - learnerProgramsData.map((program) => ) - ) : ( -
-

{NO_PROGRAMS_ERROR_MESSAGE}

- - - -
- )} -
-
- +
+ {learnerProgramsData.length > 0 ? ( + + {learnerProgramsData.map((program) => )} + + ) : ( +
+

{NO_PROGRAMS_ERROR_MESSAGE}

+ + + +
+ )} +
); }; diff --git a/src/components/program-progress/tests/ProgramListingCard.test.jsx b/src/components/program-progress/tests/ProgramListingCard.test.jsx index b2b96d3e95..be59db9bec 100644 --- a/src/components/program-progress/tests/ProgramListingCard.test.jsx +++ b/src/components/program-progress/tests/ProgramListingCard.test.jsx @@ -113,7 +113,7 @@ describe('', () => { initialUserSubsidyState={userSubsidyState} programData={dummyDataWithMultipleOrgs} />); - const aggregatedOrganizations = dummyDataWithMultipleOrgs.authoringOrganizations.map(org => org.key).join(' '); + const aggregatedOrganizations = dummyDataWithMultipleOrgs.authoringOrganizations.map(org => org.key).join(', '); expect(screen.getByText(aggregatedOrganizations)).toBeInTheDocument(); }); diff --git a/src/components/progress-category-bubbles/ProgressCategoryBubbles.jsx b/src/components/progress-category-bubbles/ProgressCategoryBubbles.jsx index 0f6a9891c6..b8c2bfabb7 100644 --- a/src/components/progress-category-bubbles/ProgressCategoryBubbles.jsx +++ b/src/components/progress-category-bubbles/ProgressCategoryBubbles.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import './styles/index.scss'; const ProgressCategoryBubbles = ({ notStarted, inProgress, completed }) => ( - + {notStarted} diff --git a/src/components/search/SearchCourseCard.jsx b/src/components/search/SearchCourseCard.jsx index 1e162e23e8..a91b78716f 100644 --- a/src/components/search/SearchCourseCard.jsx +++ b/src/components/search/SearchCourseCard.jsx @@ -92,7 +92,7 @@ const SearchCourseCard = ({ hit, isLoading, ...rest }) => { {...rest} > { isLoading={isLoading} isClickable onClick={handleCardClick} - className="bg-primary-500 border-0" + variant="dark" data-testid="search-program-card" {...rest} > diff --git a/src/components/skills-quiz/CardLoadingSkeleton.jsx b/src/components/skills-quiz/CardLoadingSkeleton.jsx index ad2e4301e7..feb5bdf380 100644 --- a/src/components/skills-quiz/CardLoadingSkeleton.jsx +++ b/src/components/skills-quiz/CardLoadingSkeleton.jsx @@ -1,40 +1,19 @@ import React from 'react'; -import { Link } from 'react-router-dom'; -import { Card, Skeleton } from '@edx/paragon'; +import { Card, CardGrid } from '@edx/paragon'; +import { v4 as uuidv4 } from 'uuid'; + import { LOADING_NO_OF_CARDS } from './constants'; const CardLoadingSkeleton = () => ( -
-
- {Array.from({ length: LOADING_NO_OF_CARDS }, (_, i) => ( -
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - - - - - - } - /> - - - - - - -
- ))} -
-
+ + {Array.from({ length: LOADING_NO_OF_CARDS }, () => ( + + + + + + ))} + ); export default CardLoadingSkeleton; diff --git a/src/components/skills-quiz/CourseCard.jsx b/src/components/skills-quiz/CourseCard.jsx index 6cc8388704..6c0b67fa35 100644 --- a/src/components/skills-quiz/CourseCard.jsx +++ b/src/components/skills-quiz/CourseCard.jsx @@ -1,6 +1,6 @@ import React, { useContext, useMemo } from 'react'; -import { Badge, Card, Skeleton } from '@edx/paragon'; -import { Link } from 'react-router-dom'; +import { Badge, Card, Stack } from '@edx/paragon'; +import { useHistory } from 'react-router-dom'; import Truncate from 'react-truncate'; import { AppContext } from '@edx/frontend-platform/react'; import PropTypes from 'prop-types'; @@ -13,6 +13,7 @@ import { MAX_VISIBLE_SKILLS_COURSE, SKILL_NAME_CUTOFF_LIMIT } from './constants' const CourseCard = ({ isLoading, course, allSkills, }) => { + const history = useHistory(); const { enterpriseConfig } = useContext(AppContext); const { slug, uuid } = enterpriseConfig; const partnerDetails = useMemo(() => { @@ -25,124 +26,79 @@ const CourseCard = ({ }; }, [course]); - const loadingCard = () => ( - + const primaryPartnerLogo = partnerDetails.primaryPartner && partnerDetails.showPartnerLogo ? { + src: partnerDetails.primaryPartner.logoImageUrl, + alt: partnerDetails.primaryPartner.name, + } : undefined; + + const handleCardClick = () => { + if (isLoading) { + return; + } + history.push(linkToCourse(course, slug, uuid)); + }; + + return ( + - - } + title={( + + {course.title} + + )} + subtitle={course.partners.length > 0 && ( + + {course.partners + .map((partner) => partner.name) + .join(', ')} + + )} /> - - - - - - + + {course.skillNames?.length > 0 && getCommonSkills( + course, + allSkills, + MAX_VISIBLE_SKILLS_COURSE, + ).map((skill) => ( + + {shortenString( + skill, + SKILL_NAME_CUTOFF_LIMIT, + ELLIPSIS_STR, + )} + + ))} + ); - - const courseCard = () => { - const primaryPartnerLogo = partnerDetails.primaryPartner && partnerDetails.showPartnerLogo ? { - src: partnerDetails.primaryPartner.logoImageUrl, - alt: partnerDetails.primaryPartner.name, - } : undefined; - - return ( - - - - - {course.title} - - )} - subtitle={ - course.partners.length > 0 && ( -

- - {course.partners - .map((partner) => partner.name) - .join(', ')} - -

- ) - } - /> - - - <> - {course.skillNames?.length > 0 && ( -
- {getCommonSkills( - course, - allSkills, - MAX_VISIBLE_SKILLS_COURSE, - ) - .map((skill) => ( - - {shortenString( - skill, - SKILL_NAME_CUTOFF_LIMIT, - ELLIPSIS_STR, - )} - - ))} -
- )} - -
-
- ); - }; - - return ( -
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - - {isLoading ? loadingCard() : courseCard()} - -
- ); }; CourseCard.propTypes = { course: PropTypes.shape({ title: PropTypes.string.isRequired, cardImageUrl: PropTypes.string.isRequired, + originalImageUrl: PropTypes.string, key: PropTypes.string.isRequired, - partners: PropTypes.shape.isRequired, + partners: PropTypes.arrayOf(PropTypes.shape()).isRequired, skillNames: PropTypes.array.isRequired, }).isRequired, - allSkills: PropTypes.shape.isRequired, + allSkills: PropTypes.arrayOf(PropTypes.string).isRequired, isLoading: PropTypes.bool.isRequired, }; diff --git a/src/components/skills-quiz/CurrentJobDropdown.jsx b/src/components/skills-quiz/CurrentJobDropdown.jsx index f9b2c1a881..a13533f581 100644 --- a/src/components/skills-quiz/CurrentJobDropdown.jsx +++ b/src/components/skills-quiz/CurrentJobDropdown.jsx @@ -23,6 +23,7 @@ const CurrentJobDropdown = () => { doRefinement={false} customAttribute={customAttribute} showBadge={false} + variant="default" /> ); }; diff --git a/src/components/skills-quiz/GoalDropdown.jsx b/src/components/skills-quiz/GoalDropdown.jsx index 6871f95115..b56e313d80 100644 --- a/src/components/skills-quiz/GoalDropdown.jsx +++ b/src/components/skills-quiz/GoalDropdown.jsx @@ -17,8 +17,8 @@ const GoalDropdown = () => { DROPDOWN_OPTION_GET_PROMOTED, DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE, DROPDOWN_OPTION_OTHER]; return ( - - + + {goal} diff --git a/src/components/skills-quiz/JobCardComponent.jsx b/src/components/skills-quiz/JobCardComponent.jsx index d44e23d275..33fd093439 100644 --- a/src/components/skills-quiz/JobCardComponent.jsx +++ b/src/components/skills-quiz/JobCardComponent.jsx @@ -1,61 +1,40 @@ import React, { useContext } from 'react'; import { AppContext } from '@edx/frontend-platform/react'; -import { Card, Skeleton } from '@edx/paragon'; +import { Card, CardGrid } from '@edx/paragon'; import PropTypes from 'prop-types'; import { formatStringAsNumber } from '../../utils/common'; import { NOT_AVAILABLE } from './constants'; const JobCardComponent = ({ jobs, isLoading }) => { const { enterpriseConfig: { hideLaborMarketData } } = useContext(AppContext); + if (!jobs) { + return null; + } + return ( - <> + {jobs?.map(job => ( -
- - - ) : ( - - {job.name} - - ) - } - /> - - - {isLoading ? ( - - ) : ( - <> - {!hideLaborMarketData - && ( -
-

- Median U.S. Salary: - {job.job_postings?.length > 0 ? `$${ formatStringAsNumber(job.job_postings[0].median_salary)}` - : NOT_AVAILABLE } -

-

- Job Postings: - {job.job_postings?.length > 0 ? formatStringAsNumber(job.job_postings[0].unique_postings) - : NOT_AVAILABLE } -

-
- )} - - )} -
-
-
+ + + + {!hideLaborMarketData && ( +
+

+ Median U.S. Salary: + {job.job_postings?.length > 0 ? `$${ formatStringAsNumber(job.job_postings[0].median_salary)}` + : NOT_AVAILABLE } +

+

+ Job Postings: + {job.job_postings?.length > 0 ? formatStringAsNumber(job.job_postings[0].unique_postings) + : NOT_AVAILABLE } +

+
+ )} +
+
))} - +
); }; diff --git a/src/components/skills-quiz/SearchCourseCard.jsx b/src/components/skills-quiz/SearchCourseCard.jsx index b1dfb2378b..d4c211a63d 100644 --- a/src/components/skills-quiz/SearchCourseCard.jsx +++ b/src/components/skills-quiz/SearchCourseCard.jsx @@ -2,9 +2,10 @@ import React, { useContext, useMemo, useState, useEffect, } from 'react'; import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; import { AppContext } from '@edx/frontend-platform/react'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; -import { StatusAlert } from '@edx/paragon'; +import { StatusAlert, CardGrid } from '@edx/paragon'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearchMinus } from '@fortawesome/free-solid-svg-icons'; import { camelCaseObject } from '@edx/frontend-platform/utils'; @@ -96,25 +97,31 @@ const SearchCourseCard = ({ index }) => { [enrolledCourseIds, filters, index, selectedJob, skills, skillsFacetFilter], ); + if (hitCount === 0) { + return ( + + ); + } + return (
- {(hitCount > 0) ?

Get started with these courses

: null} -
- {(hitCount > 0) && courses.map(course => ( - - ))} -
-
- { hitCount === 0 && ( - Get started with these courses + + {courses.map(course => ( + - )} -
+ ))} +
); }; diff --git a/src/components/skills-quiz/SearchCurrentJobCard.jsx b/src/components/skills-quiz/SearchCurrentJobCard.jsx index b17d4a7fd0..b25433a86d 100644 --- a/src/components/skills-quiz/SearchCurrentJobCard.jsx +++ b/src/components/skills-quiz/SearchCurrentJobCard.jsx @@ -44,11 +44,7 @@ const SearchCurrentJobCard = ({ index }) => { [currentJob, dispatch, index, jobToFetch], ); - return ( -
- -
- ); + return ; }; SearchCurrentJobCard.propTypes = { diff --git a/src/components/skills-quiz/SearchJobCard.jsx b/src/components/skills-quiz/SearchJobCard.jsx index d38a96afa5..84631ae65e 100644 --- a/src/components/skills-quiz/SearchJobCard.jsx +++ b/src/components/skills-quiz/SearchJobCard.jsx @@ -44,11 +44,7 @@ const SearchJobCard = ({ index }) => { [dispatch, index, jobs, jobsToFetch], ); - return ( -
- -
- ); + return ; }; SearchJobCard.propTypes = { diff --git a/src/components/skills-quiz/SearchJobDropdown.jsx b/src/components/skills-quiz/SearchJobDropdown.jsx index 25a31ec1e6..b38f5a77fe 100644 --- a/src/components/skills-quiz/SearchJobDropdown.jsx +++ b/src/components/skills-quiz/SearchJobDropdown.jsx @@ -20,6 +20,7 @@ const SearchJobDropdown = () => { searchable={!!typeaheadOptions} doRefinement={false} showBadge={false} + variant="default" /> ); }; diff --git a/src/components/skills-quiz/SearchPathways.jsx b/src/components/skills-quiz/SearchPathways.jsx index d5da476bbe..de94807811 100644 --- a/src/components/skills-quiz/SearchPathways.jsx +++ b/src/components/skills-quiz/SearchPathways.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import { AppContext } from '@edx/frontend-platform/react'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; import { camelCaseObject } from '@edx/frontend-platform/utils'; +import { CardGrid } from '@edx/paragon'; import { SkillsContext } from './SkillsContextProvider'; import { useSelectedSkillsAndJobSkills } from './data/hooks'; @@ -76,14 +77,18 @@ const SearchPathways = ({ index }) => { [filters, index, selectedJob, skills, skillsFacetFilter], ); + if (hitCount === 0) { + return null; + } + return (
- {(hitCount > 0) ?

Get started with these pathways

: null} -
- {(hitCount > 0) && pathways.map(pathway => ( +

Get started with these pathways

+ + {pathways.map(pathway => ( ))} -
+
); }; diff --git a/src/components/skills-quiz/SearchProgramCard.jsx b/src/components/skills-quiz/SearchProgramCard.jsx index 3a1e2b7f7e..ab1ba417af 100644 --- a/src/components/skills-quiz/SearchProgramCard.jsx +++ b/src/components/skills-quiz/SearchProgramCard.jsx @@ -2,13 +2,14 @@ import React, { useContext, useMemo, useState, useEffect, } from 'react'; import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; import Truncate from 'react-truncate'; -import { Link } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { camelCaseObject } from '@edx/frontend-platform/utils'; import { - Badge, Card, Icon, StatusAlert, Skeleton, + Badge, Card, Icon, StatusAlert, CardGrid, Stack, } from '@edx/paragon'; import { Program } from '@edx/paragon/icons'; import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; @@ -31,15 +32,6 @@ const linkToProgram = (program, slug, enterpriseUUID, programUuid) => { if (!Object.keys(program).length) { return '#'; } - const { userId } = getAuthenticatedUser(); - sendEnterpriseTrackEvent( - enterpriseUUID, - 'edx.ui.enterprise.learner_portal.skills_quiz.program.clicked', - { - userId, - programUuid, - }, - ); return `/${slug}/program/${programUuid}`; }; @@ -55,6 +47,7 @@ const renderDialog = () => ( ); const SearchProgramCard = ({ index }) => { + const history = useHistory(); const { enterpriseConfig } = useContext(AppContext); const { slug, uuid } = enterpriseConfig; const { @@ -154,139 +147,105 @@ const SearchProgramCard = ({ index }) => { [programs], ); - const loadingCard = () => ( - - - - } - /> - - - - - - - - - - - - - - ); + const getProgramCourseCount = (program) => { + const numCourses = program.courseKeys?.length || 0; + if (!numCourses) { + return undefined; + } + return `${numCourses} ${numCourses > 1 ? 'Courses' : 'Course'}`; + }; - const programCard = (program) => { - const getProgramCourseCount = () => { - const numCourses = program.courseKeys?.length || 0; - if (!numCourses) { - return undefined; - } - return `${numCourses} ${numCourses > 1 ? 'Courses' : 'Course'}`; - }; - const primaryPartnerLogo = getPrimaryPartnerLogo(partnerDetails[program.aggregationKey]); + const handleCardClick = (program) => { + if (isLoading) { + return; + } + const url = linkToProgram(program, slug, uuid, programUuids[program.aggregationKey].uuid); + const { userId } = getAuthenticatedUser(); + sendEnterpriseTrackEvent( + uuid, + 'edx.ui.enterprise.learner_portal.skills_quiz.program.clicked', + { + userId, + programUuid: programUuids[program.aggregationKey].uuid, + }, + ); + history.push(url); + }; + if (hitCount === 0) { return ( - - - - {program.title} - - )} - subtitle={ - program.authoringOrganizations?.length > 0 && ( -

- - {program.authoringOrganizations.map(org => org.key).join(', ')} - -

- ) - } - /> - - - <> - {program.skillNames?.length > 0 && ( -
- {getCommonSkills(program, selectedJobSkills, MAX_VISIBLE_SKILLS_PROGRAM).map((skill) => ( - - { shortenString(skill, SKILL_NAME_CUTOFF_LIMIT, ELLIPSIS_STR) } - - ))} -
- )} - -
- - -
- - -
- - - -
-
-
-
- - -
+ ); - }; + } return (
- {(hitCount > 0) ?

Get started with these programs

: null} -
- {(hitCount > 0) && programs.map(program => ( -
- { /* eslint-disable-next-line jsx-a11y/anchor-is-valid */ } - Get started with these programs + + {programs.map(program => { + const primaryPartnerLogo = getPrimaryPartnerLogo(partnerDetails[program.aggregationKey]); + return ( + handleCardClick(program)} + variant="dark" + data-testid="search-program-card" > - {isLoading ? loadingCard() : programCard(program) } - -
- ))} -
-
- { hitCount === 0 && ( - - )} -
+ + + {program.title} + + )} + subtitle={program.authoringOrganizations?.length > 0 && ( + + {program.authoringOrganizations.map(org => org.key).join(', ')} + + )} + /> + + + {program.skillNames?.length > 0 + && getCommonSkills(program, selectedJobSkills, MAX_VISIBLE_SKILLS_PROGRAM).map((skill) => ( + + {shortenString(skill, SKILL_NAME_CUTOFF_LIMIT, ELLIPSIS_STR)} + + ))} + + + + +
+ + +
+
+
+ {getProgramCourseCount(program)}} + /> + + ); + })} +
); }; diff --git a/src/components/skills-quiz/SelectedJobSkills.jsx b/src/components/skills-quiz/SelectedJobSkills.jsx index 8bbd7d7204..4bde6635a6 100644 --- a/src/components/skills-quiz/SelectedJobSkills.jsx +++ b/src/components/skills-quiz/SelectedJobSkills.jsx @@ -11,13 +11,13 @@ const SelectedJobSkills = () => { // Select currentJobRole from state if goal is to improve current job role otherwise choose interestedJobs const jobSelected = goal === DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE ? currentJobRole : interestedJobs; - const selectedJobDetails = jobSelected.filter(job => job.name === selectedJob); + const selectedJobDetails = jobSelected?.filter(job => job.name === selectedJob) || []; let selectedJobSkills = selectedJobDetails[0]?.skills?.sort((a, b) => ( (a.significance < b.significance) ? 1 : -1)); selectedJobSkills = selectedJobSkills?.slice(0, 5); return ( -
+
{selectedJobSkills?.map(skill => ( { variant="light" data-testid="top-skills-badge" > - { skill.name } + {skill.name} ))}
diff --git a/src/components/skills-quiz/SkillsCourses.jsx b/src/components/skills-quiz/SkillsCourses.jsx index 83e3cb4b12..62490655df 100644 --- a/src/components/skills-quiz/SkillsCourses.jsx +++ b/src/components/skills-quiz/SkillsCourses.jsx @@ -1,10 +1,17 @@ import React, { useEffect, useState, useContext, useMemo, } from 'react'; -import { Badge, StatusAlert, Skeleton } from '@edx/paragon'; +import { + Button, + Badge, + StatusAlert, + Skeleton, + CardGrid, +} from '@edx/paragon'; import { SearchContext, } from '@edx/frontend-enterprise-catalog-search'; +import { v4 as uuidv4 } from 'uuid'; import { Link } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; import { camelCaseObject } from '@edx/frontend-platform/utils'; @@ -115,42 +122,54 @@ const SkillsCourses = ({ index }) => { }, [courses, skillsWithSignificanceOrder]); return ( -
+
{hitCount > 0 &&

Skills

}
- {isLoading ? - : coursesWithSkills?.map(coursesWithSkill => ( - - {coursesWithSkill.key} - - )) } + {isLoading ? ( +
+ +
+ ) : coursesWithSkills?.map(coursesWithSkill => ( + + {coursesWithSkill.key} + + ))}
- {isLoading ? : coursesWithSkills?.map((coursesWithSkill) => ( - <> -

Top courses in {coursesWithSkill.key}

-
-
- {coursesWithSkill?.value.map((course) => ( - - ))} -
- + ) : coursesWithSkills?.map((coursesWithSkill) => ( + +
+

Top courses in {coursesWithSkill.key}

+
- + + {coursesWithSkill?.value.map((course) => ( + + ))} + +
))}
- { hitCount === 0 && ( + {hitCount === 0 && ( { typeaheadOptions={typeaheadOptions} searchable={!!typeaheadOptions} doRefinement={false} + variant="default" /> ); }; diff --git a/src/components/skills-quiz/SkillsQuizStepper.jsx b/src/components/skills-quiz/SkillsQuizStepper.jsx index d8f8c92770..592f14de39 100644 --- a/src/components/skills-quiz/SkillsQuizStepper.jsx +++ b/src/components/skills-quiz/SkillsQuizStepper.jsx @@ -2,7 +2,7 @@ /* eslint-disable object-curly-newline */ import React, { useEffect, useState, useContext, useMemo } from 'react'; import { - Button, Stepper, ModalDialog, Container, Form, + Button, Stepper, ModalDialog, Container, Form, Stack, } from '@edx/paragon'; import algoliasearch from 'algoliasearch/lite'; import { InstantSearch, Configure } from 'react-instantsearch-dom'; @@ -208,7 +208,7 @@ const SkillsQuizStepper = () => { size="fullscreen" className="bg-light-200 skills-quiz-modal" isOpen - onClose={() => closeSkillsQuiz()} + onClose={closeSkillsQuiz} > { - -
-
-

- {SKILLS_QUIZ_SEARCH_PAGE_MESSAGE} -

-

- First, tell us a bit more about what you want to achieve. -

- -
- -
- { - skillsVisible && ( - - -
-
- Second, which skills are you interested in developing? (select at least one) -
-
- -
-
-
- ) - } - -
- {skillsVisible && ( - { - if (selectedSkills.length > 1) { - dispatch(removeFromRefinementArray('skill_names', skillMetadata.title)); - } else { - dispatch(deleteRefinementAction('skill_names')); - } - } - } - /> - )} -
- - { - jobsDropdownsVisible && ( -
-
- Next, tell us about your current job title. -
- - -
- - - I am currently a student - -
-
- { goal !== DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE - ? ( -
- Lastly, tell us about career paths you're interested in (select up to three) - -
- ) : null } -
-
- -
- ) - } - +
+

+ {SKILLS_QUIZ_SEARCH_PAGE_MESSAGE} +

+

+ First, tell us a bit more about what you want to achieve. +

+
+
- - { - jobsDropdownsVisible && ( -
- { goalExceptImproveAndJobSelected - ? : null } - { improveGoalAndCurrentJobSelected - ? : null } + {skillsVisible && ( + + +
+

+ Second, which skills are you interested in developing? (select at least one) +

+
+ +
- ) - } +
+ )} + {skillsVisible && ( + { + if (selectedSkills.length > 1) { + dispatch(removeFromRefinementArray('skill_names', skillMetadata.title)); + } else { + dispatch(deleteRefinementAction('skill_names')); + } + }} + /> + )} + {jobsDropdownsVisible && ( +
+

+ Next, tell us about your current job title. +

+ +
+ + + I am currently a student + +
+
+ {goal !== DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE + ? ( + <> +

+ Lastly, tell us about career paths you're interested in (select up to three) +

+
+ +
+ + ) : null } +
+
+
+ )} + {jobsDropdownsVisible && ( + <> + {goalExceptImproveAndJobSelected + ? : null } + {improveGoalAndCurrentJobSelected + ? : null } + + )}
-
+

Start Exploring Courses!

-
- { canContinueToRecommendedCourses ? : null} +
+ {canContinueToRecommendedCourses ? : null}
- { (selectedJob || skills || goal === DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE) - && ( -
- - - -
- )} + {(selectedJob || skills || goal === DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE) && ( + + + + + + )}
-
- +
+
- - @@ -364,7 +347,8 @@ const SkillsQuizStepper = () => { diff --git a/src/components/skills-quiz/styles/SkillsQuizDropdowns.scss b/src/components/skills-quiz/styles/SkillsQuizDropdowns.scss index a8214e5836..55674769e2 100644 --- a/src/components/skills-quiz/styles/SkillsQuizDropdowns.scss +++ b/src/components/skills-quiz/styles/SkillsQuizDropdowns.scss @@ -1,18 +1,3 @@ -.skills-quiz-dropdown { - .dropdown-toggle { - width: 55% !important; - border-color: #707070; - justify-content: left; - font-variant-caps: all-small-caps; - overflow: hidden; - } - .skills-drop-down{ - .dropdown-toggle { - margin-top: 15px; - } - } -} - .side-image { clip-path: polygon(19% 0, 100% 0, 78% 100%, 0 100%); margin-top: 20px; diff --git a/src/components/skills-quiz/styles/_SearchContentCard.scss b/src/components/skills-quiz/styles/_SearchContentCard.scss index d1485e77f2..c3333372c9 100644 --- a/src/components/skills-quiz/styles/_SearchContentCard.scss +++ b/src/components/skills-quiz/styles/_SearchContentCard.scss @@ -75,20 +75,3 @@ } } } - -.skill-quiz-results { - display: flex; - flex-wrap: wrap; - flex-direction: row; - column-gap: 1.0rem; -} - -.more-courses-link { - float: right; - align-self: center; - text-decoration: underline; -} - -.skills-badge { - margin-bottom: 30px; -} diff --git a/src/components/skills-quiz/styles/_SearchJobCard.scss b/src/components/skills-quiz/styles/_SearchJobCard.scss index e33413b0a1..25a3419fa8 100644 --- a/src/components/skills-quiz/styles/_SearchJobCard.scss +++ b/src/components/skills-quiz/styles/_SearchJobCard.scss @@ -1,14 +1,9 @@ .search-job-card { - .card { - box-shadow: $box-shadow; - width: 325px; - padding: 5px; - } - - .medium-font { + .medium-font { font-size: medium; - } - .pgn__form-radio{ - float: right; - } + } + // TODO: avoid overriding `.pgn__` classes + .pgn__form-radio{ + float: right; + } } diff --git a/src/components/skills-quiz/tests/SearchCourseCard.test.jsx b/src/components/skills-quiz/tests/SearchCourseCard.test.jsx index fb58d3161e..3b26e5f80f 100644 --- a/src/components/skills-quiz/tests/SearchCourseCard.test.jsx +++ b/src/components/skills-quiz/tests/SearchCourseCard.test.jsx @@ -1,7 +1,8 @@ /* eslint-disable react/prop-types */ import React from 'react'; import '@testing-library/jest-dom'; -import { screen, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; import { AppContext } from '@edx/frontend-platform/react'; import '@testing-library/jest-dom/extend-expect'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; @@ -29,14 +30,9 @@ jest.mock('react-truncate', () => ({ default: ({ children }) => children, })); -jest.mock('react-loading-skeleton', () => ({ - __esModule: true, - default: (props = {}) =>
, -})); - const TEST_COURSE_KEY = 'test-course-key'; const TEST_TITLE = 'Test Title'; -const TEST_CARD_IMG_URL = 'http://fake.image'; +const TEST_CARD_IMG_URL = 'https://fake.image'; const TEST_PARTNER = { name: 'Partner Name', logoImageUrl: TEST_IMAGE_URL, @@ -132,25 +128,30 @@ const SearchCourseCardWithContext = ({ describe('', () => { test('renders the correct data', async () => { - let containerDOM = {}; - await act(async () => { - const { container } = renderWithRouter( - , - ); - containerDOM = container; - }); + const { container, history } = renderWithRouter( + , + ); + + const searchCourseCard = await screen.findByTestId('skills-quiz-course-card'); + expect(searchCourseCard).toBeInTheDocument(); expect(screen.getByText(TEST_TITLE)).toBeInTheDocument(); expect(screen.getByAltText(TEST_PARTNER.name)).toBeInTheDocument(); + expect(screen.getByText(TEST_PARTNER.name)).toBeInTheDocument(); - expect(containerDOM.querySelector('.search-result-card > a')).toHaveAttribute( - 'href', - `/${TEST_ENTERPRISE_SLUG}/course/${TEST_COURSE_KEY}`, - ); - expect(containerDOM.querySelector('p.partner')).toHaveTextContent(TEST_PARTNER.name); - expect(containerDOM.querySelector('.pgn__card-image-cap')).toHaveAttribute('src', TEST_CARD_IMG_URL); + // should show both logo image and card image with proper URLs + const cardImages = container.querySelectorAll('img'); + expect(cardImages).toHaveLength(2); + cardImages.forEach((cardImg) => { + expect(cardImg).toHaveAttribute('src', TEST_CARD_IMG_URL); + }); + + // handles click + userEvent.click(searchCourseCard); + expect(history.entries).toHaveLength(2); + expect(history.location.pathname).toContain(`${TEST_ENTERPRISE_SLUG}/course/${TEST_COURSE_KEY}`); }); test('renders the correct data with skills', async () => { @@ -174,14 +175,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(coursesWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(skillNames[0])).toBeInTheDocument(); + renderWithRouter( + , + ); + expect(await screen.findByText(skillNames[0])).toBeInTheDocument(); expect(screen.getByText(skillNames[1])).toBeInTheDocument(); }); @@ -208,14 +207,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(coursesWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(skillNames[0])).toBeInTheDocument(); + renderWithRouter( + , + ); + expect(await screen.findByText(skillNames[0])).toBeInTheDocument(); expect(screen.getByText(skillNames[1])).toBeInTheDocument(); expect(screen.queryByText(irrelevantSkill)).not.toBeInTheDocument(); }); @@ -229,14 +226,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(noCourses)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(NO_COURSES_ALERT_MESSAGE)).toBeTruthy(); + renderWithRouter( + , + ); + expect(await screen.findByText(NO_COURSES_ALERT_MESSAGE)).toBeInTheDocument(); }); test('renders the recommended courses without already enrolled courses', async () => { @@ -280,14 +275,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(coursesWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(TEST_TITLE)).toBeInTheDocument(); + renderWithRouter( + , + ); + expect(await screen.findByText(TEST_TITLE)).toBeInTheDocument(); expect(screen.getByText('Test Title Two')).toBeInTheDocument(); expect(screen.queryByText('Test Title Three')).not.toBeInTheDocument(); }); diff --git a/src/components/skills-quiz/tests/SearchPathways.test.jsx b/src/components/skills-quiz/tests/SearchPathways.test.jsx index 1b99d8cdfa..63ac2ddc5a 100644 --- a/src/components/skills-quiz/tests/SearchPathways.test.jsx +++ b/src/components/skills-quiz/tests/SearchPathways.test.jsx @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react'; import '@testing-library/jest-dom'; -import { screen, act, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { AppContext } from '@edx/frontend-platform/react'; import '@testing-library/jest-dom/extend-expect'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; @@ -28,12 +28,6 @@ jest.mock('react-truncate', () => ({ default: ({ children }) => children, })); -jest.mock('react-loading-skeleton', () => ({ - __esModule: true, - // eslint-disable-next-line react/prop-types - default: (props = {}) =>
, -})); - const TEST_PATHWAY_UUID = 'test-pathway-uuid'; const TEST_TITLE = 'Test Title'; const TEST_CARD_IMAGE_URL = 'http://fake.image'; @@ -144,14 +138,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(pathwaysWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(skillNames[0])).toBeInTheDocument(); + renderWithRouter( + , + ); + expect(await screen.findByText(skillNames[0])).toBeInTheDocument(); expect(screen.getByText(skillNames[1])).toBeInTheDocument(); }); @@ -164,16 +156,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(noPathways)), }; - let containerDOM = {}; - await act(async () => { - const { container } = renderWithRouter( - , - ); - containerDOM = container; - }); - expect(screen.queryByText('Get started with these pathways')).not.toBeInTheDocument(); - expect(containerDOM.querySelector('.search-pathway-card')).not.toBeInTheDocument(); + const { container } = renderWithRouter( + , + ); + expect(await screen.findByText('Get started with these pathways')).not.toBeInTheDocument(); + expect(container.querySelector('.search-pathway-card')).not.toBeInTheDocument(); }); }); diff --git a/src/components/skills-quiz/tests/SearchProgramCard.test.jsx b/src/components/skills-quiz/tests/SearchProgramCard.test.jsx index 95e5a7d962..9a2d8f7989 100644 --- a/src/components/skills-quiz/tests/SearchProgramCard.test.jsx +++ b/src/components/skills-quiz/tests/SearchProgramCard.test.jsx @@ -1,7 +1,8 @@ /* eslint-disable react/prop-types */ import React from 'react'; import '@testing-library/jest-dom'; -import { screen, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; import { AppContext } from '@edx/frontend-platform/react'; import '@testing-library/jest-dom/extend-expect'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; @@ -31,12 +32,6 @@ jest.mock('react-truncate', () => ({ default: ({ children }) => children, })); -jest.mock('react-loading-skeleton', () => ({ - __esModule: true, - // eslint-disable-next-line react/prop-types - default: (props = {}) =>
, -})); - const PROGRAM_UUID = 'a9cbdeb6-5fc0-44ef-97f7-9ed605a149db'; const PROGRAM_TITLE = 'Intro to BatVerse'; const PROGRAM_TYPE_DISPLAYED = 'MicroMasters® Program'; @@ -140,28 +135,32 @@ const SearchProgramCardWithContext = ({ describe('', () => { test('renders the correct data', async () => { - let containerDOM = {}; - await act(async () => { - const { container } = renderWithRouter( - , - ); - containerDOM = container; - }); + const { container, history } = renderWithRouter( + , + ); + + const searchProgramCard = await screen.findByTestId('search-program-card'); + expect(searchProgramCard).toBeInTheDocument(); expect(screen.getByText(PROGRAM_TITLE)).toBeInTheDocument(); expect(screen.getByAltText(PROGRAM_AUTHOR_ORG.name)).toBeInTheDocument(); + expect(screen.getByText(PROGRAM_AUTHOR_ORG.name)).toBeInTheDocument(); - expect(containerDOM.querySelector('.search-result-card > a')).toHaveAttribute( - 'href', - `/${TEST_ENTERPRISE_SLUG}/program/${PROGRAM_UUID}`, - ); - - expect(containerDOM.querySelector('p.partner')).toHaveTextContent(PROGRAM_AUTHOR_ORG.name); - expect(containerDOM.querySelector('.pgn__card-image-cap')).toHaveAttribute('src', PROGRAM_CARD_IMG_URL); - expect(containerDOM.querySelector('span.badge-text')).toHaveTextContent(PROGRAM_TYPE_DISPLAYED); + expect(screen.getByTestId('program-type-badge')).toHaveTextContent(PROGRAM_TYPE_DISPLAYED); expect(screen.getByText(PROGRAM_COURSES_COUNT_TEXT)).toBeInTheDocument(); + + // should show both logo image and card image with proper URLs + const cardImages = container.querySelectorAll('img'); + expect(cardImages).toHaveLength(2); + expect(cardImages[0]).toHaveAttribute('src', PROGRAM_CARD_IMG_URL); + expect(cardImages[1]).toHaveAttribute('src', PROGRAM_PARTNER_LOGO_IMG_URL); + + // handles click + userEvent.click(searchProgramCard); + expect(history.entries).toHaveLength(2); + expect(history.location.pathname).toContain(`${TEST_ENTERPRISE_SLUG}/program/${PROGRAM_UUID}`); }); test('renders the correct data with skills', async () => { @@ -186,14 +185,13 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(programWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(skillNames[0])).toBeInTheDocument(); + + renderWithRouter( + , + ); + expect(await screen.findByText(skillNames[0])).toBeInTheDocument(); expect(screen.getByText(skillNames[1])).toBeInTheDocument(); }); @@ -221,14 +219,12 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(programWithSkills)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(skillNames[0])).toBeInTheDocument(); + renderWithRouter( + , + ); + expect(await screen.findByText(skillNames[0])).toBeInTheDocument(); expect(screen.getByText(skillNames[1])).toBeInTheDocument(); expect(screen.queryByText(irrelevantSkill)).not.toBeInTheDocument(); }); @@ -242,13 +238,11 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(noPrograms)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(NO_PROGRAMS_ALERT_MESSAGE)).toBeTruthy(); + renderWithRouter( + , + ); + expect(await screen.findByText(NO_PROGRAMS_ALERT_MESSAGE)).toBeInTheDocument(); }); }); diff --git a/src/components/skills-quiz/tests/SkillsCourses.test.jsx b/src/components/skills-quiz/tests/SkillsCourses.test.jsx index 48c38a5a04..c13a0bd024 100644 --- a/src/components/skills-quiz/tests/SkillsCourses.test.jsx +++ b/src/components/skills-quiz/tests/SkillsCourses.test.jsx @@ -1,7 +1,8 @@ /* eslint-disable react/prop-types */ import React from 'react'; import '@testing-library/jest-dom'; -import { screen, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { screen, waitFor } from '@testing-library/react'; import { AppContext } from '@edx/frontend-platform/react'; import '@testing-library/jest-dom/extend-expect'; import { SearchContext } from '@edx/frontend-enterprise-catalog-search'; @@ -29,16 +30,10 @@ jest.mock('react-truncate', () => ({ default: ({ children }) => children, })); -jest.mock('react-loading-skeleton', () => ({ - __esModule: true, - // eslint-disable-next-line react/prop-types - default: (props = {}) =>
, -})); - const TEST_COURSE_KEY = 'test-course-key'; const SKILLS_HEADING = 'Skills'; const TEST_TITLE = 'Test Title'; -const TEST_CARD_IMG_URL = 'http://fake.image'; +const TEST_CARD_IMG_URL = 'https://fake.image'; const TEST_PARTNER = { name: 'Partner Name', logo_image_url: TEST_IMAGE_URL, @@ -130,25 +125,28 @@ const SkillsCoursesWithContext = ({ describe('', () => { test('renders the correct data', async () => { - let containerDOM = {}; - await act(async () => { - const { container } = renderWithRouter( - , - ); - containerDOM = container; + const { container, history } = renderWithRouter( + , + ); + + await waitFor(() => { + expect(screen.getByText(SKILLS_HEADING)).toBeInTheDocument(); + expect(screen.getByAltText(TEST_PARTNER.name)).toBeInTheDocument(); }); - expect(screen.getByText(SKILLS_HEADING)).toBeInTheDocument(); - expect(screen.getByAltText(TEST_PARTNER.name)).toBeInTheDocument(); + expect(screen.getByText(TEST_PARTNER.name)).toBeInTheDocument(); + // should show both logo image and card image with proper URLs + const cardImages = container.querySelectorAll('img'); + expect(cardImages).toHaveLength(2); + cardImages.forEach((cardImg) => { + expect(cardImg).toHaveAttribute('src', TEST_CARD_IMG_URL); + }); - expect(containerDOM.querySelector('.search-result-card > a')).toHaveAttribute( - 'href', - `/${TEST_ENTERPRISE_SLUG}/course/${TEST_COURSE_KEY}`, - ); - expect(containerDOM.querySelector('p.partner')).toHaveTextContent(TEST_PARTNER.name); - expect(containerDOM.querySelector('.pgn__card-image-cap')).toHaveAttribute('src', TEST_CARD_IMG_URL); + userEvent.click(screen.getByTestId('skills-quiz-course-card')); + expect(history.entries).toHaveLength(2); + expect(history.location.pathname).toContain(`/${TEST_ENTERPRISE_SLUG}/course/${TEST_COURSE_KEY}`); }); test('renders an alert in case of no courses returned', async () => { @@ -160,13 +158,11 @@ describe('', () => { indexName: 'test-index-name', search: jest.fn().mockImplementation(() => Promise.resolve(noCourses)), }; - await act(async () => { - renderWithRouter( - , - ); - }); - expect(screen.getByText(NO_COURSES_ALERT_MESSAGE_AGAINST_SKILLS)).toBeTruthy(); + renderWithRouter( + , + ); + expect(await screen.findByText(NO_COURSES_ALERT_MESSAGE_AGAINST_SKILLS)).toBeInTheDocument(); }); }); diff --git a/src/components/skills-quiz/tests/SkillsQuizStepper.test.jsx b/src/components/skills-quiz/tests/SkillsQuizStepper.test.jsx index 58acd25c27..5d535a3aa0 100644 --- a/src/components/skills-quiz/tests/SkillsQuizStepper.test.jsx +++ b/src/components/skills-quiz/tests/SkillsQuizStepper.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import userEvent from '@testing-library/user-event'; -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { AppContext } from '@edx/frontend-platform/react'; import { SearchContext, removeFromRefinementArray, deleteRefinementAction, SearchData, @@ -73,38 +73,6 @@ describe('', () => { jest.restoreAllMocks(); }); - it('Handles removal skill is handled correctly.', async () => { - const searchContext = { - refinements: { skill_names: ['test-skill-1', 'test-skill-2'] }, - dispatch: () => null, - }; - - renderWithRouter( - - - - - - - - - - - , - { route: '/test/skills-quiz/' }, - ); - expect(screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION)).toBeInTheDocument(); - await act(async () => { - await screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION).click(); - await screen.queryByText(DROPDOWN_OPTION_GET_PROMOTED).click(); - }); - expect(screen.queryByText(SKILLS_FACET.title)).toBeInTheDocument(); - - // Remove the first selected skill. - screen.getByTestId('test-skill-1').click(); - expect(removeFromRefinementArray.mock.calls.length).toBe(1); - }); - it('checks header is correctly rendered', () => { const searchContext = { refinements: {}, @@ -304,15 +272,45 @@ describe('', () => { , { route: '/test/skills-quiz/?skill_names=xyz' }, ); + userEvent.click(screen.getByText(GOAL_DROPDOWN_DEFAULT_OPTION)); + userEvent.click(await screen.findByText(DROPDOWN_OPTION_GET_PROMOTED)); + expect(screen.getByText(SKILLS_FACET.title)).toBeInTheDocument(); + expect(screen.getByText(CURRENT_JOB_FACET.title)).toBeInTheDocument(); + expect(screen.getByText(DESIRED_JOB_FACET.title)).toBeInTheDocument(); + }); + + it('Handles removal skill is handled correctly.', async () => { + const searchContext = { + refinements: { skill_names: ['test-skill-1', 'test-skill-2'] }, + dispatch: () => null, + }; + + renderWithRouter( + + + + + + + + + + + , + { route: '/test/skills-quiz/' }, + ); expect(screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION)).toBeInTheDocument(); - await act(async () => { - await screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION).click(); - screen.queryByText(DROPDOWN_OPTION_GET_PROMOTED).click(); - }); + const goalDropdown = screen.getByText(GOAL_DROPDOWN_DEFAULT_OPTION); + userEvent.click(goalDropdown); + const getPromotedOption = await screen.findByText(DROPDOWN_OPTION_GET_PROMOTED); + expect(getPromotedOption).toBeInTheDocument(); + userEvent.click(getPromotedOption); - expect(screen.queryByText(SKILLS_FACET.title)).toBeInTheDocument(); - expect(screen.queryByText(CURRENT_JOB_FACET.title)).toBeInTheDocument(); - expect(screen.queryByText(DESIRED_JOB_FACET.title)).toBeInTheDocument(); + expect(await screen.findByText(SKILLS_FACET.title)).toBeInTheDocument(); + + // Remove the first selected skill. + userEvent.click(screen.getByTestId('test-skill-1').querySelector('[role="button"]')); + expect(removeFromRefinementArray.mock.calls.length).toBe(1); }); it('Handles removal of the last skill is handled correctly.', async () => { @@ -337,13 +335,16 @@ describe('', () => { ); expect(screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION)).toBeInTheDocument(); - await act(async () => { - await screen.queryByText(GOAL_DROPDOWN_DEFAULT_OPTION).click(); - await screen.queryByText(DROPDOWN_OPTION_GET_PROMOTED).click(); - }); - expect(screen.queryByText(SKILLS_FACET.title)).toBeInTheDocument(); + const goalDropdown = screen.getByText(GOAL_DROPDOWN_DEFAULT_OPTION); + userEvent.click(goalDropdown); + const getPromotedOption = await screen.findByText(DROPDOWN_OPTION_GET_PROMOTED); + expect(getPromotedOption).toBeInTheDocument(); + userEvent.click(getPromotedOption); + + expect(await screen.findByText(SKILLS_FACET.title)).toBeInTheDocument(); + // remove the last skill as well and make sure deleteRefinementAction is called. - screen.getByTestId('test-skill-1').click(); + userEvent.click(screen.getByTestId('test-skill-1').querySelector('[role="button"]')); expect(deleteRefinementAction.mock.calls.length).toBe(1); });