From dd5d2919beabc415caebe55fb3a6820beb3c8c19 Mon Sep 17 00:00:00 2001 From: awu43 <46470763+awu43@users.noreply.github.com> Date: Mon, 9 Aug 2021 01:30:31 -0700 Subject: [PATCH] feat(client): ts-migrate client/utils/** (#42823) * rename js files * update imports and references * migrate build-challenges * migrate challenge-types * migrate utils/index * migrate state-management * install @types/psl for tags * migrate tags * migrate tags.test * migrate challenge-page-creator * migrate utils/gatsby/index * migrate layout-selector * migrate layout-selector.test * revert challenge-types Curriculum can't handle TS or modules * convert arrow functions * revert build-challenges * revert utils/gatsby/index * revert challenge-page-creator * revert challenge-types reference * Delete state-management Deleted in #42960 * Disable render-result-naming-convention (for now) * update layout-selector.test comment * reorder imports in build-challenges * change ts-ignore to ts-expect-error --- client/gatsby-config.js | 2 +- client/package-lock.json | 6 ++ client/package.json | 1 + client/src/redux/failed-updates-epic.js | 2 +- .../src/templates/Challenges/classic/Show.tsx | 2 +- .../Challenges/projects/solution-form.tsx | 2 +- .../Challenges/redux/completion-epic.js | 2 +- .../src/templates/Challenges/redux/index.js | 2 +- .../src/templates/Challenges/utils/build.js | 2 +- ...buildChallenges.js => build-challenges.js} | 1 + .../{challengeTypes.js => challenge-types.js} | 0 ...geCreator.js => challenge-page-creator.js} | 82 ++++++++++--------- client/utils/gatsby/index.js | 2 +- ...ector.test.js => layout-selector.test.tsx} | 23 +++++- client/utils/gatsby/layout-selector.tsx | 14 +--- client/utils/index.js | 3 - client/utils/index.ts | 3 + client/utils/{tags.test.js => tags.test.tsx} | 11 +-- client/utils/{tags.js => tags.tsx} | 29 ++++--- curriculum/getChallenges.js | 2 +- curriculum/schema/challengeSchema.js | 2 +- curriculum/test/test-challenges.js | 2 +- docs/how-to-work-on-coding-challenges.md | 4 +- 23 files changed, 116 insertions(+), 83 deletions(-) rename client/utils/{buildChallenges.js => build-challenges.js} (99%) rename client/utils/{challengeTypes.js => challenge-types.js} (100%) rename client/utils/gatsby/{challengePageCreator.js => challenge-page-creator.js} (63%) rename client/utils/gatsby/{layoutSelector.test.js => layout-selector.test.tsx} (78%) delete mode 100644 client/utils/index.js create mode 100644 client/utils/index.ts rename client/utils/{tags.test.js => tags.test.tsx} (80%) rename client/utils/{tags.js => tags.tsx} (84%) diff --git a/client/gatsby-config.js b/client/gatsby-config.js index 3e4ad85b5e..31ca65c273 100644 --- a/client/gatsby-config.js +++ b/client/gatsby-config.js @@ -4,7 +4,7 @@ const { buildChallenges, replaceChallengeNode, localeChallengesRootDir -} = require('./utils/buildChallenges'); +} = require('./utils/build-challenges'); const { clientLocale, curriculumLocale, homeLocation } = envData; diff --git a/client/package-lock.json b/client/package-lock.json index 2ab707300f..f82fdaa96d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4461,6 +4461,12 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, + "@types/psl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.0.tgz", + "integrity": "sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==", + "dev": true + }, "@types/reach__router": { "version": "1.3.9", "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.9.tgz", diff --git a/client/package.json b/client/package.json index 4a0e89b96f..3943b8e248 100644 --- a/client/package.json +++ b/client/package.json @@ -138,6 +138,7 @@ "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.4", "@types/prismjs": "1.16.6", + "@types/psl": "^1.1.0", "@types/reach__router": "1.3.9", "@types/react-dom": "17.0.9", "@types/react-helmet": "6.1.2", diff --git a/client/src/redux/failed-updates-epic.js b/client/src/redux/failed-updates-epic.js index 1c96b121b1..80b905f705 100644 --- a/client/src/redux/failed-updates-epic.js +++ b/client/src/redux/failed-updates-epic.js @@ -11,7 +11,7 @@ import { import store from 'store'; import { v4 as uuid } from 'uuid'; -import { backEndProject } from '../../utils/challengeTypes'; +import { backEndProject } from '../../utils/challenge-types'; import { isGoodXHRStatus } from '../templates/Challenges/utils'; import postUpdate$ from '../templates/Challenges/utils/postUpdate$'; import { actionTypes } from './action-types'; diff --git a/client/src/templates/Challenges/classic/Show.tsx b/client/src/templates/Challenges/classic/Show.tsx index f709ed52ff..cbb3b4518c 100644 --- a/client/src/templates/Challenges/classic/Show.tsx +++ b/client/src/templates/Challenges/classic/Show.tsx @@ -13,7 +13,7 @@ import { createStructuredSelector } from 'reselect'; // Local Utilities import store from 'store'; -import { challengeTypes } from '../../../../utils/challengeTypes'; +import { challengeTypes } from '../../../../utils/challenge-types'; import LearnLayout from '../../../components/layouts/learn'; import { ChallengeNodeType, diff --git a/client/src/templates/Challenges/projects/solution-form.tsx b/client/src/templates/Challenges/projects/solution-form.tsx index f3cfe8f06e..f802e99b88 100644 --- a/client/src/templates/Challenges/projects/solution-form.tsx +++ b/client/src/templates/Challenges/projects/solution-form.tsx @@ -7,7 +7,7 @@ import { backEndProject, frontEndProject, pythonProject -} from '../../../../utils/challengeTypes'; +} from '../../../../utils/challenge-types'; import { Form } from '../../../components/formHelpers'; interface SubmitProps { diff --git a/client/src/templates/Challenges/redux/completion-epic.js b/client/src/templates/Challenges/redux/completion-epic.js index 0ba44814f2..788ae40b21 100644 --- a/client/src/templates/Challenges/redux/completion-epic.js +++ b/client/src/templates/Challenges/redux/completion-epic.js @@ -10,7 +10,7 @@ import { finalize } from 'rxjs/operators'; -import { challengeTypes, submitTypes } from '../../../../utils/challengeTypes'; +import { challengeTypes, submitTypes } from '../../../../utils/challenge-types'; import { userSelector, isSignedInSelector, diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index cba8aab9a5..de9579a48e 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -3,7 +3,7 @@ import { createAction, handleActions } from 'redux-actions'; import { getLines } from '../../../../../utils/get-lines'; import { createPoly } from '../../../../../utils/polyvinyl'; -import { challengeTypes } from '../../../../utils/challengeTypes'; +import { challengeTypes } from '../../../../utils/challenge-types'; import { completedChallengesSelector } from '../../../redux'; import { getTargetEditor } from '../utils/getTargetEditor'; import { actionTypes, ns } from './action-types'; diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index b7c307bb36..53737d3771 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -3,7 +3,7 @@ import frameRunnerData from '../../../../../config/client/frame-runner.json'; // eslint-disable-next-line import/no-unresolved import testEvaluatorData from '../../../../../config/client/test-evaluator.json'; -import { challengeTypes } from '../../../../utils/challengeTypes'; +import { challengeTypes } from '../../../../utils/challenge-types'; import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js'; import { getTransformers } from '../rechallenge/transformers'; import { diff --git a/client/utils/buildChallenges.js b/client/utils/build-challenges.js similarity index 99% rename from client/utils/buildChallenges.js rename to client/utils/build-challenges.js index c64bd1c029..3d2033fe2d 100644 --- a/client/utils/buildChallenges.js +++ b/client/utils/build-challenges.js @@ -1,4 +1,5 @@ const path = require('path'); + const _ = require('lodash'); const envData = require('../../config/env.json'); diff --git a/client/utils/challengeTypes.js b/client/utils/challenge-types.js similarity index 100% rename from client/utils/challengeTypes.js rename to client/utils/challenge-types.js diff --git a/client/utils/gatsby/challengePageCreator.js b/client/utils/gatsby/challenge-page-creator.js similarity index 63% rename from client/utils/gatsby/challengePageCreator.js rename to client/utils/gatsby/challenge-page-creator.js index 96aa1b9c39..513c312777 100644 --- a/client/utils/gatsby/challengePageCreator.js +++ b/client/utils/gatsby/challenge-page-creator.js @@ -1,7 +1,7 @@ const path = require('path'); const { dasherize } = require('../../../utils/slugs'); -const { viewTypes } = require('../challengeTypes'); +const { viewTypes } = require('../challenge-types'); const backend = path.resolve( __dirname, @@ -42,21 +42,22 @@ const views = { // quiz: Quiz }; -const getNextChallengePath = (node, index, nodeArray) => { +function getNextChallengePath(_node, index, nodeArray) { const next = nodeArray[index + 1]; return next ? next.node.fields.slug : '/learn'; -}; +} -const getPrevChallengePath = (node, index, nodeArray) => { +function getPrevChallengePath(_node, index, nodeArray) { const prev = nodeArray[index - 1]; return prev ? prev.node.fields.slug : '/learn'; -}; +} -const getTemplateComponent = challengeType => views[viewTypes[challengeType]]; +function getTemplateComponent(challengeType) { + return views[viewTypes[challengeType]]; +} -exports.createChallengePages = - createPage => - ({ node }, index, thisArray) => { +exports.createChallengePages = function (createPage) { + return function ({ node }, index, thisArray) { const { superBlock, block, @@ -69,7 +70,7 @@ exports.createChallengePages = // TODO: challengeType === 7 and isPrivate are the same, right? If so, we // should remove one of them. - return createPage({ + createPage({ path: slug, component: getTemplateComponent(challengeType), context: { @@ -86,35 +87,40 @@ exports.createChallengePages = } }); }; - -exports.createBlockIntroPages = createPage => edge => { - const { - fields: { slug }, - frontmatter: { block } - } = edge.node; - - return createPage({ - path: slug, - component: intro, - context: { - block: dasherize(block), - slug - } - }); }; -exports.createSuperBlockIntroPages = createPage => edge => { - const { - fields: { slug }, - frontmatter: { superBlock } - } = edge.node; +exports.createBlockIntroPages = function (createPage) { + return function (edge) { + const { + fields: { slug }, + frontmatter: { block } + } = edge.node; - return createPage({ - path: slug, - component: superBlockIntro, - context: { - superBlock, - slug - } - }); + createPage({ + path: slug, + component: intro, + context: { + block: dasherize(block), + slug + } + }); + }; +}; + +exports.createSuperBlockIntroPages = function (createPage) { + return function (edge) { + const { + fields: { slug }, + frontmatter: { superBlock } + } = edge.node; + + createPage({ + path: slug, + component: superBlockIntro, + context: { + superBlock, + slug + } + }); + }; }; diff --git a/client/utils/gatsby/index.js b/client/utils/gatsby/index.js index c8dce644f3..8a3739f1f1 100644 --- a/client/utils/gatsby/index.js +++ b/client/utils/gatsby/index.js @@ -1,4 +1,4 @@ -const challengePageCreators = require('./challengePageCreator'); +const challengePageCreators = require('./challenge-page-creator'); module.exports = { ...challengePageCreators diff --git a/client/utils/gatsby/layoutSelector.test.js b/client/utils/gatsby/layout-selector.test.tsx similarity index 78% rename from client/utils/gatsby/layoutSelector.test.js rename to client/utils/gatsby/layout-selector.test.tsx index 64f515a49b..ac769f44d1 100644 --- a/client/utils/gatsby/layoutSelector.test.js +++ b/client/utils/gatsby/layout-selector.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import React from 'react'; import { Provider } from 'react-redux'; import ShallowRenderer from 'react-test-renderer/shallow'; @@ -11,10 +12,19 @@ import layoutSelector from './layout-selector'; jest.mock('../../src/analytics'); const store = createStore(); -function getComponentNameAndProps(elementType, pathname) { - const shallow = new ShallowRenderer(); + +interface NameAndProps { + props: Record; + name: string; +} +function getComponentNameAndProps( + elementType: React.JSXElementConstructor, + pathname: string +): NameAndProps { + // eslint-disable-next-line testing-library/render-result-naming-convention + const shallow = ShallowRenderer.createRenderer(); const LayoutReactComponent = layoutSelector({ - element: { type: elementType }, + element: { type: elementType, props: {}, key: '' }, props: { location: { pathname @@ -24,8 +34,13 @@ function getComponentNameAndProps(elementType, pathname) { shallow.render({LayoutReactComponent}); const view = shallow.getRenderOutput(); return { - props: view.props, + props: view.props as Record, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment name: view.type.WrappedComponent.displayName + // TODO: Revisit this when react-test-renderer is replaced with + // react-testing-library }; } diff --git a/client/utils/gatsby/layout-selector.tsx b/client/utils/gatsby/layout-selector.tsx index 3d0858b4e9..483b004930 100644 --- a/client/utils/gatsby/layout-selector.tsx +++ b/client/utils/gatsby/layout-selector.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { CertificationLayout, DefaultLayout @@ -6,21 +7,14 @@ import { import FourOhFourPage from '../../src/pages/404'; import { isChallenge } from '../../src/utils/path-parsers'; -interface Location { - pathname: string; -} - interface LayoutSelectorProps { - props: { - location: Location; - }; - element: React.ReactElement; + element: JSX.Element; + props: { location: { pathname: string } }; } - export default function layoutSelector({ element, props -}: LayoutSelectorProps): React.ReactElement { +}: LayoutSelectorProps): JSX.Element { const { location: { pathname } } = props; diff --git a/client/utils/index.js b/client/utils/index.js deleted file mode 100644 index 6cf17822af..0000000000 --- a/client/utils/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.isBrowser = function isBrowser() { - return typeof window !== 'undefined'; -}; diff --git a/client/utils/index.ts b/client/utils/index.ts new file mode 100644 index 0000000000..0b1aed80b7 --- /dev/null +++ b/client/utils/index.ts @@ -0,0 +1,3 @@ +export function isBrowser(): boolean { + return typeof window !== 'undefined'; +} diff --git a/client/utils/tags.test.js b/client/utils/tags.test.tsx similarity index 80% rename from client/utils/tags.test.js rename to client/utils/tags.test.tsx index 81015051b7..b5d8e215e4 100644 --- a/client/utils/tags.test.js +++ b/client/utils/tags.test.tsx @@ -1,8 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { injectConditionalTags } from './tags'; describe('Tags', () => { it('injectConditionalTags should inject gap dev homelocation', () => { - let injectedTags = injectConditionalTags( + const injectedTags = injectConditionalTags( [], 'https://www.freecodecamp.dev' ); @@ -10,7 +11,7 @@ describe('Tags', () => { expect(injectedTags[0].props.id === 'gap-dev').toBeTruthy(); }); it('injectConditionalTags should inject gap for english homeLocation', () => { - let injectedTags = injectConditionalTags( + const injectedTags = injectConditionalTags( [], 'https://www.freecodecamp.org' ); @@ -18,7 +19,7 @@ describe('Tags', () => { expect(injectedTags[0].props.id === 'gap-org').toBeTruthy(); }); it('injectConditionalTags should inject gap for espanol homeLocation', () => { - let injectedTags = injectConditionalTags( + const injectedTags = injectConditionalTags( [], 'https://www.freecodecamp.org/espanol' ); @@ -26,7 +27,7 @@ describe('Tags', () => { expect(injectedTags[0].props.id === 'gap-org').toBeTruthy(); }); it('injectConditionalTags should inject cap and chinese gap for chinese homeLocation', () => { - let injectedTags = injectConditionalTags( + const injectedTags = injectConditionalTags( [], 'https://chinese.freecodecamp.org' ); @@ -35,7 +36,7 @@ describe('Tags', () => { expect(injectedTags[1].props.id === 'gap-org-chinese').toBeTruthy(); }); it('injectConditionalTags should not inject tags for localhost homeLocation', () => { - let injectedTags = injectConditionalTags([], 'http://localhost:8000/'); + const injectedTags = injectConditionalTags([], 'http://localhost:8000/'); expect(injectedTags.length === 0).toBeTruthy(); }); }); diff --git a/client/utils/tags.js b/client/utils/tags.tsx similarity index 84% rename from client/utils/tags.js rename to client/utils/tags.tsx index dd1c1e5738..33d02a4c8c 100644 --- a/client/utils/tags.js +++ b/client/utils/tags.tsx @@ -6,13 +6,13 @@ import env from '../../config/env.json'; const { homeLocation } = env; -export const getheadTagComponents = () => { +export function getheadTagComponents(): JSX.Element[] { const socialImage = 'https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png'; const pathToBootstrap = withPrefix('/css/bootstrap.min.css'); - let headTags = [ + const headTags = [ { /> ]; return injectConditionalTags(headTags, homeLocation); -}; +} // strips subpath and protocol -export const injectConditionalTags = (tagsArray, homeLocation) => { +export function injectConditionalTags( + tagsArray: JSX.Element[], + homeLocation: string +): JSX.Element[] { if (homeLocation.includes('localhost')) return tagsArray; - const parsedHomeUrl = psl.parse(new URL(homeLocation).host); + const parsedHomeUrl = psl.parse( + new URL(homeLocation).host + ) as psl.ParsedDomain; // inject gap all production languages except Chinese if (parsedHomeUrl.subdomain === 'www' && parsedHomeUrl.tld === 'org') { tagsArray.push(