refactor: display solutions (#45139)
* refactor: re-organise show-project-links * refactor: update ChallengeFile's declared shape * fix: handle missing challenge solution * refactor: use display function for Certification * refactor: use display function for TimeLine * refactor: use common component for timeline + cert * fix: handle legacy solutions * refactor: use widget for certifications * refactor: reorganise ShowDisplayWidget * refactor: remove unused ids * test: pass dataCy, not projectTitle, to widget * chore: kebabify * revert: add id back for dropdown Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * revert: add the ids back Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
committed by
GitHub
parent
92778f1b2f
commit
b223cdd255
@ -551,6 +551,7 @@
|
||||
"heading-legacy-full-stack": "As part of this Legacy Full Stack certification, {{user}} completed the following certifications:",
|
||||
"heading": "As part of this certification, {{user}} built the following projects and got all automated test suites to pass:",
|
||||
"solution": "solution",
|
||||
"no-solution": "error displaying solution, email support@freeCodeCamp.org to get help.",
|
||||
"source": "source",
|
||||
"footnote": "If you suspect that any of these projects violate the <2>academic honesty policy</2>, please <5>report this to our team</5>.",
|
||||
"title": {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
legacyProjectMap
|
||||
} from '../resources/cert-and-project-map';
|
||||
|
||||
import { maybeUrlRE } from '../utils';
|
||||
import { SolutionDisplayWidget } from '../components/solution-display-widget';
|
||||
|
||||
interface ShowProjectLinksProps {
|
||||
certName: string;
|
||||
@ -52,8 +52,8 @@ const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { solution, githubLink, challengeFiles } = completedProject;
|
||||
const onClickHandler = () =>
|
||||
const { solution, challengeFiles } = completedProject;
|
||||
const showFilesSolution = () =>
|
||||
setSolutionState({
|
||||
projectTitle,
|
||||
challengeFiles,
|
||||
@ -61,46 +61,13 @@ const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
|
||||
isOpen: true
|
||||
});
|
||||
|
||||
if (challengeFiles?.length) {
|
||||
return (
|
||||
<button
|
||||
className='project-link-button-override'
|
||||
data-cy={`${projectTitle} solution`}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
{t('certification.project.solution')}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
if (githubLink) {
|
||||
return (
|
||||
<>
|
||||
<a href={solution ?? ''} rel='noopener noreferrer' target='_blank'>
|
||||
{t('certification.project.solution')}
|
||||
</a>
|
||||
,{' '}
|
||||
<a href={githubLink} rel='noopener noreferrer' target='_blank'>
|
||||
{t('certification.project.source')}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (maybeUrlRE.test(solution ?? '')) {
|
||||
return (
|
||||
<a
|
||||
className='btn-invert'
|
||||
href={solution ?? ''}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('certification.project.solution')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button className='project-link-button-override' onClick={onClickHandler}>
|
||||
{t('certification.project.solution')}
|
||||
</button>
|
||||
<SolutionDisplayWidget
|
||||
completedChallenge={completedProject}
|
||||
dataCy={`${projectTitle} solution`}
|
||||
displayContext='certification'
|
||||
showFilesSolution={showFilesSolution}
|
||||
></SolutionDisplayWidget>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { createSelector } from 'reselect';
|
||||
import envData from '../../../config/env.json';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { Loader, Spacer } from '../components/helpers';
|
||||
import Certification from '../components/settings/Certification';
|
||||
import Certification from '../components/settings/certification';
|
||||
import About from '../components/settings/about';
|
||||
import DangerZone from '../components/settings/danger-zone';
|
||||
import Email from '../components/settings/email';
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useStaticQuery } from 'gatsby';
|
||||
import React from 'react';
|
||||
import TimeLine from './TimeLine';
|
||||
import TimeLine from './time-line';
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
|
@ -1,11 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Table,
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { Button, Modal, Table } from '@freecodecamp/react-bootstrap';
|
||||
import Loadable from '@loadable/component';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
import { reverse, sortBy } from 'lodash-es';
|
||||
@ -21,8 +15,8 @@ import {
|
||||
} from '../../../../../utils';
|
||||
import CertificationIcon from '../../../assets/icons/certification-icon';
|
||||
import { ChallengeFiles, CompletedChallenge } from '../../../redux/prop-types';
|
||||
import { maybeUrlRE } from '../../../utils';
|
||||
import { FullWidthRow, Link } from '../../helpers';
|
||||
import { SolutionDisplayWidget } from '../../solution-display-widget';
|
||||
import TimelinePagination from './timeline-pagination';
|
||||
|
||||
import './timeline.css';
|
||||
@ -53,7 +47,6 @@ function TimelineInner({
|
||||
idToNameMap,
|
||||
sortedTimeline,
|
||||
totalPages,
|
||||
|
||||
completedMap,
|
||||
t,
|
||||
username
|
||||
@ -96,73 +89,20 @@ function TimelineInner({
|
||||
}
|
||||
|
||||
function renderViewButton(
|
||||
id: string,
|
||||
challengeFiles: ChallengeFiles,
|
||||
githubLink?: string,
|
||||
solution?: string | null
|
||||
completedChallenge: CompletedChallenge
|
||||
): React.ReactNode {
|
||||
if (challengeFiles?.length) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`btn-for-${id}`}
|
||||
onClick={() => viewSolution(id, solution, challengeFiles)}
|
||||
>
|
||||
{t('buttons.show-code')}
|
||||
</Button>
|
||||
);
|
||||
} else if (githubLink) {
|
||||
return (
|
||||
<div className='solutions-dropdown'>
|
||||
<DropdownButton
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`dropdown-for-${id}`}
|
||||
title='View'
|
||||
>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={solution}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.frontend')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={githubLink}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.backend')}
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
);
|
||||
} else if (solution && maybeUrlRE.test(solution)) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
href={solution}
|
||||
id={`btn-for-${id}`}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.view')}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const { id, solution, challengeFiles } = completedChallenge;
|
||||
return (
|
||||
<SolutionDisplayWidget
|
||||
completedChallenge={completedChallenge}
|
||||
showFilesSolution={() => viewSolution(id, solution, challengeFiles)}
|
||||
displayContext={'timeline'}
|
||||
></SolutionDisplayWidget>
|
||||
);
|
||||
}
|
||||
|
||||
function renderCompletion(completed: CompletedChallenge): JSX.Element {
|
||||
const { id, challengeFiles, githubLink, solution } = completed;
|
||||
const { id } = completed;
|
||||
const completedDate = new Date(completed.completedDate);
|
||||
// @ts-expect-error idToNameMap is not a <string, string> Map...
|
||||
const { challengeTitle, challengePath, certPath } = idToNameMap.get(id);
|
||||
@ -181,7 +121,7 @@ function TimelineInner({
|
||||
<Link to={challengePath as string}>{challengeTitle}</Link>
|
||||
)}
|
||||
</td>
|
||||
<td>{renderViewButton(id, challengeFiles, githubLink, solution)}</td>
|
||||
<td>{renderViewButton(completed)}</td>
|
||||
<td className='text-center'>
|
||||
<time dateTime={completedDate.toISOString()}>
|
||||
{completedDate.toLocaleString([localeCode, 'en-US'], {
|
@ -5,7 +5,7 @@ import { TFunction, useTranslation } from 'react-i18next';
|
||||
|
||||
import { FullWidthRow, Link, Spacer } from '../helpers';
|
||||
import { User } from './../../redux/prop-types';
|
||||
import Timeline from './components/TimeLine';
|
||||
import Timeline from './components/time-line';
|
||||
import Camper from './components/camper';
|
||||
import Certifications from './components/certifications';
|
||||
import HeatMap from './components/heat-map';
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { Table, Button } from '@freecodecamp/react-bootstrap';
|
||||
import { Link, navigate } from 'gatsby';
|
||||
import { find, first } from 'lodash-es';
|
||||
import PropTypes from 'prop-types';
|
||||
@ -16,12 +11,10 @@ import {
|
||||
projectMap,
|
||||
legacyProjectMap
|
||||
} from '../../resources/cert-and-project-map';
|
||||
|
||||
import { maybeUrlRE } from '../../utils';
|
||||
import { FlashMessages } from '../Flash/redux/flash-messages';
|
||||
import ProjectModal from '../SolutionViewer/ProjectModal';
|
||||
import { FullWidthRow, Spacer } from '../helpers';
|
||||
|
||||
import { SolutionDisplayWidget } from '../solution-display-widget';
|
||||
import SectionHeader from './section-header';
|
||||
|
||||
import './certification.css';
|
||||
@ -163,7 +156,7 @@ export class CertificationSettings extends Component {
|
||||
getUserIsCertMap = () => isCertMapSelector(this.props);
|
||||
|
||||
getProjectSolution = (projectId, projectTitle) => {
|
||||
const { completedChallenges, t } = this.props;
|
||||
const { completedChallenges } = this.props;
|
||||
const completedProject = find(
|
||||
completedChallenges,
|
||||
({ id }) => projectId === id
|
||||
@ -171,7 +164,7 @@ export class CertificationSettings extends Component {
|
||||
if (!completedProject) {
|
||||
return null;
|
||||
}
|
||||
const { solution, githubLink, challengeFiles } = completedProject;
|
||||
const { solution, challengeFiles } = completedProject;
|
||||
const onClickHandler = () =>
|
||||
this.setState({
|
||||
solutionViewer: {
|
||||
@ -181,75 +174,14 @@ export class CertificationSettings extends Component {
|
||||
isOpen: true
|
||||
}
|
||||
});
|
||||
if (challengeFiles?.length) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
data-cy={projectTitle}
|
||||
id={`btn-for-${projectId}`}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
{t('buttons.show-code')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (githubLink) {
|
||||
return (
|
||||
<div className='solutions-dropdown'>
|
||||
<DropdownButton
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`dropdown-for-${projectId}`}
|
||||
title='Show Solutions'
|
||||
>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={solution}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.frontend')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={githubLink}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.backend')}
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (maybeUrlRE.test(solution)) {
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
href={solution}
|
||||
id={`btn-for-${projectId}`}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.show-solution')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`btn-for-${projectId}`}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
{t('buttons.show-code')}
|
||||
</Button>
|
||||
<SolutionDisplayWidget
|
||||
completedChallenge={completedProject}
|
||||
dataCy={projectTitle}
|
||||
showFilesSolution={onClickHandler}
|
||||
displayContext={'settings'}
|
||||
></SolutionDisplayWidget>
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from '../../redux/createStore';
|
||||
|
||||
import { CertificationSettings } from './Certification';
|
||||
import { CertificationSettings } from './certification';
|
||||
|
||||
jest.mock('../../analytics');
|
||||
|
141
client/src/components/solution-display-widget/index.tsx
Normal file
141
client/src/components/solution-display-widget/index.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import {
|
||||
Button,
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CompletedChallenge } from '../../redux/prop-types';
|
||||
import { getSolutionDisplayType } from '../../utils/solution-display-type';
|
||||
|
||||
interface Props {
|
||||
completedChallenge: CompletedChallenge;
|
||||
dataCy?: string;
|
||||
showFilesSolution: () => void;
|
||||
displayContext: 'timeline' | 'settings' | 'certification';
|
||||
}
|
||||
|
||||
export function SolutionDisplayWidget({
|
||||
completedChallenge,
|
||||
dataCy,
|
||||
showFilesSolution,
|
||||
displayContext
|
||||
}: Props) {
|
||||
const { id, solution, githubLink } = completedChallenge;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dropdownTitle =
|
||||
displayContext === 'settings' ? 'Show Solutions' : 'View';
|
||||
const projectLinkText =
|
||||
displayContext === 'settings'
|
||||
? t('buttons.show-solution')
|
||||
: t('buttons.view');
|
||||
|
||||
const ShowFilesSolutionForCertification = (
|
||||
<button
|
||||
className='project-link-button-override'
|
||||
data-cy={dataCy}
|
||||
onClick={showFilesSolution}
|
||||
>
|
||||
{t('certification.project.solution')}
|
||||
</button>
|
||||
);
|
||||
const ShowProjectAndGithubLinkForCertification = (
|
||||
<>
|
||||
<a href={solution ?? ''} rel='noopener noreferrer' target='_blank'>
|
||||
{t('certification.project.solution')}
|
||||
</a>
|
||||
,{' '}
|
||||
<a href={githubLink} rel='noopener noreferrer' target='_blank'>
|
||||
{t('certification.project.source')}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
const ShowProjectLinkForCertification = (
|
||||
<a
|
||||
className='btn-invert'
|
||||
href={solution ?? ''}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('certification.project.solution')}
|
||||
</a>
|
||||
);
|
||||
const MissingSolutionComponentForCertification = (
|
||||
<>{t('certification.project.no-solution')}</>
|
||||
);
|
||||
const ShowFilesSolution = (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
data-cy={dataCy}
|
||||
id={`btn-for-${id}`}
|
||||
onClick={showFilesSolution}
|
||||
>
|
||||
{t('buttons.show-code')}
|
||||
</Button>
|
||||
);
|
||||
const ShowProjectAndGithubLinks = (
|
||||
<div className='solutions-dropdown'>
|
||||
<DropdownButton
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
id={`dropdown-for-${id}`}
|
||||
title={dropdownTitle}
|
||||
>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={solution}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.frontend')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
bsStyle='primary'
|
||||
href={githubLink}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('buttons.backend')}
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
);
|
||||
const ShowProjectLink = (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-invert'
|
||||
href={solution}
|
||||
id={`btn-for-${id}`}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{projectLinkText}
|
||||
</Button>
|
||||
);
|
||||
const MissingSolutionComponent =
|
||||
displayContext === 'settings' ? (
|
||||
<>{t('certification.project.no-solution')}</>
|
||||
) : null;
|
||||
|
||||
const displayComponents =
|
||||
displayContext === 'certification'
|
||||
? {
|
||||
showFilesSolution: ShowFilesSolutionForCertification,
|
||||
showProjectAndGitHubLinks: ShowProjectAndGithubLinkForCertification,
|
||||
showProjectLink: ShowProjectLinkForCertification,
|
||||
none: MissingSolutionComponentForCertification
|
||||
}
|
||||
: {
|
||||
showFilesSolution: ShowFilesSolution,
|
||||
showProjectAndGitHubLinks: ShowProjectAndGithubLinks,
|
||||
showProjectLink: ShowProjectLink,
|
||||
none: MissingSolutionComponent
|
||||
};
|
||||
|
||||
return displayComponents[getSolutionDisplayType(completedChallenge)];
|
||||
}
|
@ -363,7 +363,7 @@ export type ChallengeFile = {
|
||||
seed: string;
|
||||
contents: string;
|
||||
id: string;
|
||||
history: [[string], string];
|
||||
history: string[];
|
||||
};
|
||||
|
||||
export type ChallengeFiles = ChallengeFile[] | null;
|
||||
|
39
client/src/utils/__fixtures/completed-challenges.ts
Normal file
39
client/src/utils/__fixtures/completed-challenges.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {challengeFiles} from '../../../../utils/__fixtures__/challenges';
|
||||
|
||||
const baseChallenge = {
|
||||
id: '1',
|
||||
completedDate: 1,
|
||||
challengeFiles: []
|
||||
}
|
||||
|
||||
export const onlySolution = {
|
||||
...baseChallenge,
|
||||
solution: 'https://some-url.com'
|
||||
}
|
||||
|
||||
export const legacySolution = {
|
||||
...baseChallenge,
|
||||
solution: 'var x = 1;'
|
||||
}
|
||||
|
||||
export const bothLinks = {
|
||||
...baseChallenge,
|
||||
githubLink: 'https://some.thing',
|
||||
solution: 'https://some-url.com'
|
||||
}
|
||||
|
||||
export const withChallenges = {
|
||||
...bothLinks,
|
||||
challengeFiles
|
||||
}
|
||||
|
||||
export const onlyGithubLink = {
|
||||
...baseChallenge,
|
||||
githubLink: 'https://some.thing'
|
||||
}
|
||||
|
||||
export const invalidGithubLink = {
|
||||
...baseChallenge,
|
||||
githubLink: 'something',
|
||||
solution: 'https://some-url.com'
|
||||
}
|
29
client/src/utils/solution-display-type.test.ts
Normal file
29
client/src/utils/solution-display-type.test.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {
|
||||
bothLinks,
|
||||
legacySolution,
|
||||
invalidGithubLink,
|
||||
onlyGithubLink,
|
||||
onlySolution,
|
||||
withChallenges
|
||||
} from './__fixtures/completed-challenges';
|
||||
import { getSolutionDisplayType } from './solution-display-type';
|
||||
|
||||
describe('getSolutionDisplayType', () => {
|
||||
it('should handle missing solutions', () => {
|
||||
expect(getSolutionDisplayType(onlyGithubLink)).toBe('none');
|
||||
});
|
||||
it('should handle legacy solutions', () => {
|
||||
expect(getSolutionDisplayType(legacySolution)).toBe('showFilesSolution');
|
||||
});
|
||||
it('should handle solutions with files', () => {
|
||||
expect(getSolutionDisplayType(withChallenges)).toBe('showFilesSolution');
|
||||
});
|
||||
it('should handle solutions with a single valid url', () => {
|
||||
expect.assertions(2);
|
||||
expect(getSolutionDisplayType(onlySolution)).toBe('showProjectLink');
|
||||
expect(getSolutionDisplayType(invalidGithubLink)).toBe('showProjectLink');
|
||||
});
|
||||
it('should handle solutions with both links', () => {
|
||||
expect(getSolutionDisplayType(bothLinks)).toBe('showProjectAndGitHubLinks');
|
||||
});
|
||||
});
|
16
client/src/utils/solution-display-type.ts
Normal file
16
client/src/utils/solution-display-type.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { CompletedChallenge } from '../redux/prop-types';
|
||||
import { maybeUrlRE } from '.';
|
||||
|
||||
export const getSolutionDisplayType = ({
|
||||
solution,
|
||||
githubLink,
|
||||
challengeFiles
|
||||
}: CompletedChallenge) => {
|
||||
if (challengeFiles?.length) return 'showFilesSolution';
|
||||
if (!solution) return 'none';
|
||||
// Some of the user records still have JavaScript project solutions stored as
|
||||
// solution strings
|
||||
if (!maybeUrlRE.test(solution)) return 'showFilesSolution';
|
||||
if (maybeUrlRE.test(githubLink ?? '')) return 'showProjectAndGitHubLinks';
|
||||
return 'showProjectLink';
|
||||
};
|
@ -18,7 +18,7 @@ const fileJoi = Joi.object().keys({
|
||||
seed: Joi.string().allow(''),
|
||||
contents: Joi.string().allow(''),
|
||||
id: Joi.string().allow(''),
|
||||
history: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')]
|
||||
history: Joi.array().items(Joi.string().allow(''))
|
||||
});
|
||||
|
||||
const schema = Joi.object()
|
||||
|
@ -1,5 +1,8 @@
|
||||
exports.challengeFiles = [
|
||||
import { ChallengeFile } from "../../client/src/redux/prop-types";
|
||||
|
||||
export const challengeFiles: ChallengeFile[] = [
|
||||
{
|
||||
id: '1',
|
||||
contents: 'some css',
|
||||
error: null,
|
||||
ext: 'css',
|
||||
@ -7,11 +10,13 @@ exports.challengeFiles = [
|
||||
history: ['styles.css'],
|
||||
fileKey: 'stylescss',
|
||||
name: 'styles',
|
||||
path: 'styles.css',
|
||||
seed: 'some css',
|
||||
tail: ''
|
||||
tail: '',
|
||||
editableRegionBoundaries: [],
|
||||
usesMultifileEditor: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
contents: 'some html',
|
||||
error: null,
|
||||
ext: 'html',
|
||||
@ -19,11 +24,13 @@ exports.challengeFiles = [
|
||||
history: ['index.html'],
|
||||
fileKey: 'indexhtml',
|
||||
name: 'index',
|
||||
path: 'index.html',
|
||||
seed: 'some html',
|
||||
tail: ''
|
||||
tail: '',
|
||||
editableRegionBoundaries: [],
|
||||
usesMultifileEditor: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
contents: 'some js',
|
||||
error: null,
|
||||
ext: 'js',
|
||||
@ -31,11 +38,13 @@ exports.challengeFiles = [
|
||||
history: ['script.js'],
|
||||
fileKey: 'scriptjs',
|
||||
name: 'script',
|
||||
path: 'script.js',
|
||||
seed: 'some js',
|
||||
tail: ''
|
||||
tail: '',
|
||||
editableRegionBoundaries: [],
|
||||
usesMultifileEditor: true,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
contents: 'some jsx',
|
||||
error: null,
|
||||
ext: 'jsx',
|
||||
@ -43,8 +52,9 @@ exports.challengeFiles = [
|
||||
history: ['index.jsx'],
|
||||
fileKey: 'indexjsx',
|
||||
name: 'index',
|
||||
path: 'index.jsx',
|
||||
seed: 'some jsx',
|
||||
tail: ''
|
||||
tail: '',
|
||||
editableRegionBoundaries: [],
|
||||
usesMultifileEditor: true,
|
||||
}
|
||||
];
|
||||
]
|
Reference in New Issue
Block a user