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 => (
- -
- {tag.title}
-
-
- ),
- )
- }
-
-
-
- >
+
+ {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}
-
-
-
- )}
+ subtitle={program.authoringOrganizations?.length > 0 ? (
+
+ {program.authoringOrganizations.map(org => org.key).join(', ')}
+
+ ) : undefined}
/>
-
-
+
+
+
})
+ {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);
});