Merge pull request #706 from openedx/mfrank/retrieving-job-skills-data
APER-2187 Render jobs and related skills for Skills Builder
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useState, useContext } from 'react';
|
||||
import {
|
||||
Button, Container, Stepper, ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
STEP1, STEP2,
|
||||
@@ -17,7 +17,7 @@ import ViewResults from './view-results/ViewResults';
|
||||
import headerImage from '../images/headerImage.png';
|
||||
|
||||
const SkillsBuilderModal = () => {
|
||||
const intl = useIntl();
|
||||
const { formatMessage } = useIntl();
|
||||
const { state } = useContext(SkillsBuilderContext);
|
||||
const { careerInterests } = state;
|
||||
const [currentStep, setCurrentStep] = useState(STEP1);
|
||||
@@ -49,12 +49,12 @@ const SkillsBuilderModal = () => {
|
||||
<Stepper.Header />
|
||||
|
||||
<ModalDialog.Body>
|
||||
<Container size="md">
|
||||
<Stepper.Step eventKey={STEP1} title={intl.formatMessage(messages.selectPreferences)}>
|
||||
<Container size="md" className="p-4.5">
|
||||
<Stepper.Step eventKey={STEP1} title={formatMessage(messages.selectPreferences)}>
|
||||
<SelectPreferences />
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step eventKey={STEP2} title={intl.formatMessage(messages.reviewResults)}>
|
||||
<Stepper.Step eventKey={STEP2} title={formatMessage(messages.reviewResults)}>
|
||||
<ViewResults />
|
||||
</Stepper.Step>
|
||||
</Container>
|
||||
@@ -63,14 +63,14 @@ const SkillsBuilderModal = () => {
|
||||
<ModalDialog.Footer>
|
||||
<Stepper.ActionRow eventKey={STEP1}>
|
||||
<Button variant="outline-primary" onClick={onCloseHandle}>
|
||||
<FormattedMessage {...messages.goBackButton} />
|
||||
{formatMessage(messages.goBackButton)}
|
||||
</Button>
|
||||
<Stepper.ActionRow.Spacer />
|
||||
<Button
|
||||
onClick={() => setCurrentStep(STEP2)}
|
||||
disabled={careerInterests.length === 0}
|
||||
>
|
||||
<FormattedMessage {...messages.nextStepButton} />
|
||||
{formatMessage(messages.nextStepButton)}
|
||||
</Button>
|
||||
</Stepper.ActionRow>
|
||||
<Stepper.ActionRow eventKey={STEP2}>
|
||||
@@ -78,11 +78,11 @@ const SkillsBuilderModal = () => {
|
||||
variant="outline-primary"
|
||||
onClick={() => setCurrentStep(STEP1)}
|
||||
>
|
||||
<FormattedMessage {...messages.goBackButton} />
|
||||
{formatMessage(messages.goBackButton)}
|
||||
</Button>
|
||||
<Stepper.ActionRow.Spacer />
|
||||
<Button onClick={onCloseHandle}>
|
||||
<FormattedMessage {...messages.exitButton} />
|
||||
{formatMessage(messages.exitButton)}
|
||||
</Button>
|
||||
</Stepper.ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { removeCareerInterest } from '../../data/actions';
|
||||
import messages from './messages';
|
||||
|
||||
const CareerInterestCard = ({ interest }) => {
|
||||
const intl = useIntl();
|
||||
const { formatMessage } = useIntl();
|
||||
const { dispatch } = useContext(SkillsBuilderContext);
|
||||
|
||||
return (
|
||||
@@ -21,7 +21,7 @@ const CareerInterestCard = ({ interest }) => {
|
||||
<IconButton
|
||||
iconAs={Icon}
|
||||
src={Close}
|
||||
alt={`${intl.formatMessage(messages.removeCareerInterestButtonAltText)} ${interest}`}
|
||||
alt={`${formatMessage(messages.removeCareerInterestButtonAltText)} ${interest}`}
|
||||
onClick={() => dispatch(removeCareerInterest(interest))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Stack, Row, Col } from '@edx/paragon';
|
||||
import { InstantSearch } from 'react-instantsearch-hooks-web';
|
||||
import JobTitleInstantSearch from './JobTitleInstantSearch';
|
||||
@@ -10,6 +10,7 @@ import { SkillsBuilderContext } from '../../skills-builder-context';
|
||||
import messages from './messages';
|
||||
|
||||
const CareerInterestSelect = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { state, dispatch, algolia } = useContext(SkillsBuilderContext);
|
||||
const { careerInterests } = state;
|
||||
const { searchClient } = algolia;
|
||||
@@ -26,10 +27,13 @@ const CareerInterestSelect = () => {
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<h4>
|
||||
<FormattedMessage {...messages.careerInterestPrompt} />
|
||||
{formatMessage(messages.careerInterestPrompt)}
|
||||
</h4>
|
||||
<InstantSearch searchClient={searchClient} indexName={getConfig().ALGOLIA_JOBS_INDEX_NAME}>
|
||||
<JobTitleInstantSearch onSelected={handleCareerInterestSelect} />
|
||||
<JobTitleInstantSearch
|
||||
onSelected={handleCareerInterestSelect}
|
||||
placeholder={formatMessage(messages.careerInterestInputPlaceholder)}
|
||||
/>
|
||||
</InstantSearch>
|
||||
<Row>
|
||||
{careerInterests.map((interest, index) => (
|
||||
|
||||
@@ -3,19 +3,21 @@ import {
|
||||
Form,
|
||||
Stack,
|
||||
} from '@edx/paragon';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { setGoal } from '../../data/actions';
|
||||
import { SkillsBuilderContext } from '../../skills-builder-context';
|
||||
import messages from './messages';
|
||||
|
||||
const GoalDropdown = () => {
|
||||
const intl = useIntl();
|
||||
const { formatMessage } = useIntl();
|
||||
const { state, dispatch } = useContext(SkillsBuilderContext);
|
||||
const { currentGoal } = state;
|
||||
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<h4><FormattedMessage {...messages.learningGoalPrompt} /></h4>
|
||||
<h4>
|
||||
{formatMessage(messages.learningGoalPrompt)}
|
||||
</h4>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
as="select"
|
||||
@@ -23,12 +25,12 @@ const GoalDropdown = () => {
|
||||
onChange={(e) => dispatch(setGoal(e.target.value))}
|
||||
data-testid="goal-select-dropdown"
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages.selectLearningGoal)}</option>
|
||||
<option>{intl.formatMessage(messages.learningGoalStartCareer)}</option>
|
||||
<option>{intl.formatMessage(messages.learningGoalAdvanceCareer)}</option>
|
||||
<option>{intl.formatMessage(messages.learningGoalChangeCareer)}</option>
|
||||
<option>{intl.formatMessage(messages.learningGoalSomethingNew)}</option>
|
||||
<option>{intl.formatMessage(messages.learningGoalSomethingElse)}</option>
|
||||
<option value="">{formatMessage(messages.selectLearningGoal)}</option>
|
||||
<option>{formatMessage(messages.learningGoalStartCareer)}</option>
|
||||
<option>{formatMessage(messages.learningGoalAdvanceCareer)}</option>
|
||||
<option>{formatMessage(messages.learningGoalChangeCareer)}</option>
|
||||
<option>{formatMessage(messages.learningGoalSomethingNew)}</option>
|
||||
<option>{formatMessage(messages.learningGoalSomethingElse)}</option>
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
</Stack>
|
||||
|
||||
@@ -25,6 +25,8 @@ const JobTitleInstantSearch = (props) => {
|
||||
onChange={handleAutosuggestChange}
|
||||
name="job-title-suggest"
|
||||
onSelected={props.onSelected}
|
||||
autoComplete="off"
|
||||
placeholder={props.placeholder}
|
||||
>
|
||||
{hits.map(job => (
|
||||
<Form.AutosuggestOption key={job.id}>
|
||||
@@ -35,8 +37,13 @@ const JobTitleInstantSearch = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
JobTitleInstantSearch.defaultProps = {
|
||||
placeholder: '',
|
||||
};
|
||||
|
||||
JobTitleInstantSearch.propTypes = {
|
||||
onSelected: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
export default JobTitleInstantSearch;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
Form, Stack,
|
||||
} from '@edx/paragon';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { InstantSearch } from 'react-instantsearch-hooks-web';
|
||||
import { setCurrentJobTitle } from '../../data/actions';
|
||||
import { SkillsBuilderContext } from '../../skills-builder-context';
|
||||
@@ -11,35 +11,40 @@ import JobTitleInstantSearch from './JobTitleInstantSearch';
|
||||
import messages from './messages';
|
||||
|
||||
const JobTitleSelect = () => {
|
||||
const { dispatch, algolia } = useContext(SkillsBuilderContext);
|
||||
const { formatMessage } = useIntl();
|
||||
const { state, dispatch, algolia } = useContext(SkillsBuilderContext);
|
||||
const { searchClient } = algolia;
|
||||
const { currentJobTitle } = state;
|
||||
|
||||
const handleCurrentJobTitleSelect = (value) => {
|
||||
dispatch(setCurrentJobTitle(value));
|
||||
};
|
||||
|
||||
// Below implementation sets the job title to "student" or "looking_for_work" — this overwrites any previous selection
|
||||
// Below implementation sets the job title to "Student" or "Looking for work" — this overwrites any previous selection
|
||||
// This will need to be revisited when we decide what to do with this data
|
||||
const handleCheckboxChange = (e) => dispatch(setCurrentJobTitle(e.target.value));
|
||||
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<h4>
|
||||
<FormattedMessage {...messages.jobTitlePrompt} />
|
||||
{formatMessage(messages.jobTitlePrompt)}
|
||||
</h4>
|
||||
<InstantSearch searchClient={searchClient} indexName={getConfig().ALGOLIA_JOBS_INDEX_NAME}>
|
||||
<JobTitleInstantSearch onSelected={handleCurrentJobTitleSelect} />
|
||||
<JobTitleInstantSearch
|
||||
onSelected={handleCurrentJobTitleSelect}
|
||||
placeholder={currentJobTitle}
|
||||
/>
|
||||
</InstantSearch>
|
||||
<Form.Group>
|
||||
<Form.CheckboxSet
|
||||
name="other-occupations"
|
||||
onChange={handleCheckboxChange}
|
||||
>
|
||||
<Form.Checkbox value="student">
|
||||
<FormattedMessage {...messages.studentCheckboxPrompt} />
|
||||
<Form.Checkbox value="Student">
|
||||
{formatMessage(messages.studentCheckboxPrompt)}
|
||||
</Form.Checkbox>
|
||||
<Form.Checkbox value="looking_for_work">
|
||||
<FormattedMessage {...messages.currentlyLookingCheckboxPrompt} />
|
||||
<Form.Checkbox value="Looking for work">
|
||||
{formatMessage(messages.currentlyLookingCheckboxPrompt)}
|
||||
</Form.Checkbox>
|
||||
</Form.CheckboxSet>
|
||||
</Form.Group>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||
import {
|
||||
Stack,
|
||||
} from '@edx/paragon';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { SkillsBuilderContext } from '../../skills-builder-context';
|
||||
import GoalSelect from './GoalSelect';
|
||||
import JobTitleSelect from './JobTitleSelect';
|
||||
@@ -10,13 +10,14 @@ import CareerInterestSelect from './CareerInterestSelect';
|
||||
import messages from './messages';
|
||||
|
||||
const SelectPreferences = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { state } = useContext(SkillsBuilderContext);
|
||||
const { currentGoal, currentJobTitle } = state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="lead mb-5">
|
||||
<FormattedMessage {...messages.skillsBuilderDescription} />
|
||||
<Stack gap={4}>
|
||||
<p className="lead">
|
||||
{formatMessage(messages.skillsBuilderDescription)}
|
||||
</p>
|
||||
<Stack gap={4}>
|
||||
|
||||
@@ -30,7 +31,7 @@ const SelectPreferences = () => {
|
||||
<CareerInterestSelect />
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'What careers are you interested in?',
|
||||
description: 'Prompts the user to select careers they are interested in pursuing.',
|
||||
},
|
||||
careerInterestInputPlaceholder: {
|
||||
id: 'career.interest.input.placeholder',
|
||||
defaultMessage: 'Select up to 3 new job titles',
|
||||
description: 'Placeholder text for the career interest input control.',
|
||||
},
|
||||
removeCareerInterestButtonAltText: {
|
||||
id: 'career.interest.remove.button.alt.text',
|
||||
defaultMessage: 'Remove career interest: ',
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
SelectableBox, Chip, Stack, useMediaQuery, breakpoints,
|
||||
} from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import messages from './messages';
|
||||
|
||||
const RelatedSkillsSelectableBoxSet = ({ jobSkillsList, selectedJobTitle, onChange }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth });
|
||||
|
||||
const renderTopFiveSkills = (skills) => {
|
||||
const topFiveSkills = skills.sort((a, b) => b.significance - a.significance).slice(0, 5);
|
||||
return (
|
||||
topFiveSkills.map(skill => (
|
||||
<Chip key={skill.id}>
|
||||
{skill.name}
|
||||
</Chip>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectableBox.Set
|
||||
name="selected job title"
|
||||
type="radio"
|
||||
value={selectedJobTitle}
|
||||
onChange={onChange}
|
||||
columns={isExtraSmall ? 1 : 3}
|
||||
>
|
||||
{jobSkillsList.map(job => (
|
||||
<SelectableBox
|
||||
key={job.id}
|
||||
type="radio"
|
||||
value={job.name}
|
||||
aria-label={job.name}
|
||||
inputHidden={false}
|
||||
>
|
||||
<p>{job.name}</p>
|
||||
<Stack gap={2} className="align-items-start">
|
||||
<p className="heading-label x-small">{formatMessage(messages.relatedSkillsHeading)}</p>
|
||||
{renderTopFiveSkills(job.skills)}
|
||||
</Stack>
|
||||
</SelectableBox>
|
||||
))}
|
||||
</SelectableBox.Set>
|
||||
);
|
||||
};
|
||||
|
||||
RelatedSkillsSelectableBoxSet.propTypes = {
|
||||
jobSkillsList: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
selectedJobTitle: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default RelatedSkillsSelectableBoxSet;
|
||||
@@ -1,7 +1,82 @@
|
||||
import React from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Stack, Row, Alert, Spinner,
|
||||
} from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { CheckCircle } from '@edx/paragon/icons';
|
||||
import { SkillsBuilderContext } from '../../skills-builder-context';
|
||||
import RelatedSkillsSelectableBoxSet from './RelatedSkillsSelectableBoxSet';
|
||||
import { searchJobs, getProductRecommendations } from '../../utils/search';
|
||||
import messages from './messages';
|
||||
|
||||
const ViewResults = () => (
|
||||
<h3>Results will render on this step</h3>
|
||||
);
|
||||
const ViewResults = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { algolia, state } = useContext(SkillsBuilderContext);
|
||||
const { jobSearchIndex, productSearchIndex } = algolia;
|
||||
const { careerInterests } = state;
|
||||
|
||||
const [selectedJobTitle, setSelectedJobTitle] = useState('');
|
||||
const [jobSkillsList, setJobSkillsList] = useState([]);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [courseRecommendations, setCourseRecommendations] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const getJobs = async () => {
|
||||
// fetch list of jobs with related skills
|
||||
const jobInfo = await searchJobs(jobSearchIndex, careerInterests);
|
||||
|
||||
// fetch course recommendations based on related skills for each job
|
||||
const results = await Promise.all(jobInfo.map(async (job) => {
|
||||
const formattedSkills = job.skills.map(skill => skill.name);
|
||||
|
||||
const recommendations = await getProductRecommendations(productSearchIndex, 'course', formattedSkills);
|
||||
|
||||
const data = {
|
||||
id: job.id,
|
||||
name: job.name,
|
||||
recommendations,
|
||||
};
|
||||
|
||||
return data;
|
||||
}));
|
||||
|
||||
setJobSkillsList(jobInfo);
|
||||
setSelectedJobTitle(jobInfo[0].name);
|
||||
setCourseRecommendations(results);
|
||||
setIsLoading(false);
|
||||
};
|
||||
getJobs();
|
||||
}, [careerInterests, jobSearchIndex, productSearchIndex]);
|
||||
|
||||
return (
|
||||
isLoading ? (
|
||||
<Row>
|
||||
<Spinner
|
||||
animation="border"
|
||||
screenReaderText="loading"
|
||||
className="mx-auto"
|
||||
/>
|
||||
</Row>
|
||||
) : (
|
||||
<Stack gap={4.5}>
|
||||
<Alert
|
||||
variant="success"
|
||||
icon={CheckCircle}
|
||||
>
|
||||
<Alert.Heading>
|
||||
{formatMessage(messages.matchesFoundSuccessAlert)}
|
||||
</Alert.Heading>
|
||||
</Alert>
|
||||
|
||||
<RelatedSkillsSelectableBoxSet
|
||||
jobSkillsList={jobSkillsList}
|
||||
selectedJobTitle={selectedJobTitle}
|
||||
onChange={(e) => setSelectedJobTitle(e.target.value)}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewResults;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
matchesFoundSuccessAlert: {
|
||||
id: 'matches.found.success.alert',
|
||||
defaultMessage: 'We found skills and courses that match your preferences!',
|
||||
description: 'Success alert message to display when recommendations are presented to the learner.',
|
||||
},
|
||||
relatedSkillsHeading: {
|
||||
id: 'related.skills.heading',
|
||||
defaultMessage: 'Related Skills',
|
||||
description: 'Heading text for a selectable box that displays related skills for a corresponding selected job title.',
|
||||
},
|
||||
relatedSkillsSelectableBoxLabelText: {
|
||||
id: 'related.skills.selectable.box.label.text',
|
||||
defaultMessage: 'Related skills:',
|
||||
description: 'Label text for a selectable box that displays related skills for a corresponding selected job title.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -8,15 +8,29 @@ import { SkillsBuilder } from '..';
|
||||
import { SkillsBuilderModal } from '../skills-builder-modal';
|
||||
import { SkillsBuilderProvider, SkillsBuilderContext } from '../skills-builder-context';
|
||||
import { skillsInitialState } from '../data/reducer';
|
||||
import { mockData } from './__mocks__/jobSkills.mockData';
|
||||
import { getProductRecommendations, searchJobs, useAlgoliaSearch } from '../utils/search';
|
||||
|
||||
const dispatchMock = jest.fn();
|
||||
|
||||
jest.mock('@edx/frontend-platform/logging');
|
||||
|
||||
jest.mock('react-instantsearch-hooks-web', () => ({
|
||||
// eslint-disable-next-line react/prop-types
|
||||
InstantSearch: ({ children }) => (<div>{children}</div>),
|
||||
useSearchBox: jest.fn(() => ({ refine: jest.fn() })),
|
||||
useHits: jest.fn(() => ({ hits: [{ name: 'Text File Engineer' }, { name: 'Screen Viewer' }] })),
|
||||
useHits: jest.fn(() => ({ hits: mockData.hits })),
|
||||
}));
|
||||
|
||||
const dispatchMock = jest.fn();
|
||||
jest.mock('../utils/search', () => ({
|
||||
searchJobs: jest.fn(),
|
||||
getProductRecommendations: jest.fn(),
|
||||
useAlgoliaSearch: jest.fn(),
|
||||
}));
|
||||
|
||||
searchJobs.mockReturnValue(mockData.searchJobs);
|
||||
getProductRecommendations.mockReturnValue(mockData.productRecommendations);
|
||||
useAlgoliaSearch.mockReturnValue(mockData.useAlgoliaSearch);
|
||||
|
||||
const contextValue = {
|
||||
state: {
|
||||
@@ -26,6 +40,8 @@ const contextValue = {
|
||||
algolia: {
|
||||
// Without this, tests would fail to destructure `searchClient` in the <JobTitleSelect> component
|
||||
searchClient: {},
|
||||
productSearchIndex: {},
|
||||
jobSearchIndex: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,7 +92,7 @@ describe('skills-builder', () => {
|
||||
type: 'SET_GOAL',
|
||||
};
|
||||
const expectedJobTitle = {
|
||||
payload: 'student',
|
||||
payload: 'Student',
|
||||
type: 'SET_CURRENT_JOB_TITLE',
|
||||
};
|
||||
|
||||
@@ -149,4 +165,29 @@ describe('skills-builder', () => {
|
||||
fireEvent.click(screen.getByLabelText('Remove career interest: Prospector'));
|
||||
expect(dispatchMock).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should render a <JobSillsSelectableBox> for each career interest the learner has submitted', async () => {
|
||||
render(
|
||||
SkillsBuilderWrapperWithContext(
|
||||
{
|
||||
...contextValue,
|
||||
state: {
|
||||
...contextValue.state,
|
||||
currentGoal: 'I want to start my career',
|
||||
currentJobTitle: 'Goblin Lackey',
|
||||
careerInterests: ['Prospector'],
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next Step' }));
|
||||
});
|
||||
|
||||
const chipComponents = document.querySelectorAll('.pgn__chip');
|
||||
expect(chipComponents[0].textContent).toEqual('finding shiny things');
|
||||
expect(chipComponents[1].textContent).toEqual('mining');
|
||||
|
||||
expect(screen.getByText('Prospector')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
40
src/skills-builder/test/__mocks__/jobSkills.mockData.js
Normal file
40
src/skills-builder/test/__mocks__/jobSkills.mockData.js
Normal file
@@ -0,0 +1,40 @@
|
||||
export const mockData = {
|
||||
hits: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Text File Engineer'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Screen Viewer'
|
||||
},
|
||||
],
|
||||
searchJobs: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Prospector',
|
||||
skills: [
|
||||
{ id: 0,
|
||||
name: 'mining',
|
||||
significance: 50,
|
||||
},
|
||||
{ id: 1,
|
||||
name: 'finding shiny things',
|
||||
significance: 100,
|
||||
}],
|
||||
},
|
||||
],
|
||||
productRecommendations: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'Prospector',
|
||||
recommendations: [{ name: 'Mining with the Mons' }, { name: 'The Art of Warren Upkeep' }],
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Mirror Breaker',
|
||||
recommendations: [{ name: 'Mirror Breaking 101' }],
|
||||
},
|
||||
],
|
||||
useAlgoliaSearch: [{}, {}, {}],
|
||||
};
|
||||
Reference in New Issue
Block a user