diff --git a/.eslintrc b/.eslintrc index 99cf75fce3..b39a701a04 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,15 @@ { - "extends": "eslint-config-mitodl" + "extends": ["eslint-config-mitodl", "eslint-config-mitodl/flow", "eslint-config-mitodl/mocha"], + "plugins": ["import"], + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + // disabled for now to ensure no code changes + "no-unused-vars": "off", + "operator-linebreak": "off", + "react/jsx-no-target-blank": "off" + } } diff --git a/.flowconfig b/.flowconfig index 5eee7e4ed1..d710d3127e 100644 --- a/.flowconfig +++ b/.flowconfig @@ -34,6 +34,10 @@ .*/node_modules/react-popper/.* .*/node_modules/gensync/index.js.flow .*/node_modules/@webassemblyjs/ieee754/src/index.js +.*/node_modules/@webassemblyjs/helper-numbers/src/index.js +.*/node_modules/html-entities/dist/commonjs/index.js.flow +.*/node_modules/eslint-plugin-flowtype/.* +.*/node_modules/flow-typed/.* .*.git/.* .*/staticfiles/.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e873562e8..8c474953b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: [push] jobs: python-tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 services: # Label used to access the service container @@ -50,7 +50,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: "3.9.1" + python-version: "3.9.24" - id: cache uses: actions/cache@v4 @@ -75,7 +75,8 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - name: Runs Opensearch - uses: ankane/setup-opensearch@v1 + # pinned because versions after this drop support for opensearch 1.x + uses: ankane/setup-opensearch@06d8ca27e5805f1d0ad93062c0e947eab2594d64 with: opensearch-version: ${{ matrix.opensearch-version }} @@ -109,7 +110,7 @@ jobs: - name: Set up NodeJS uses: actions/setup-node@v2-beta with: - node-version: "14.18.2" + node-version: "22.20.0" - name: Set up environment run: sudo apt-get install libelf1 diff --git a/.mocharc.cjs b/.mocharc.cjs new file mode 100644 index 0000000000..ba45c2d21b --- /dev/null +++ b/.mocharc.cjs @@ -0,0 +1,12 @@ +module.exports = { + require: [ + "core-js", + "regenerator-runtime", + "@babel/register" + ], + // these need to be here because mocha doesn't provide + // hooks like beforeEach when the file is --require + file: [ + "static/js/tests_init.js" + ] +} diff --git a/Dockerfile b/Dockerfile index 265f28a80f..0146fed95f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-bullseye +FROM python:3.9.24 LABEL maintainer "ODL DevOps " diff --git a/Dockerfile-node b/Dockerfile-node index 3f55679b0f..884bfc71b4 100644 --- a/Dockerfile-node +++ b/Dockerfile-node @@ -1,4 +1,4 @@ -FROM node:14.18.2-bullseye +FROM node:22.20.0-bullseye LABEL maintainer "ODL DevOps " diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..83de0c340d --- /dev/null +++ b/babel.config.js @@ -0,0 +1,26 @@ +module.exports = function(api) { + const isProduction = api.env("production"); + const isTest = api.env("test"); + return { + presets: isTest ? [ + "@babel/env", + "@babel/preset-react" + ] : [ + ["@babel/preset-env", { modules: false }], + "@babel/preset-react", + "@babel/preset-flow" + ], + plugins: [ + "@babel/plugin-transform-flow-strip-types", + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-syntax-dynamic-import", + ...(isProduction ? [ + "@babel/plugin-transform-react-constant-elements", + "@babel/plugin-transform-react-inline-elements" + ] : [ + "react-hot-loader/babel" + ]) + ] + }; +}; diff --git a/courses/mit_learn_api.py b/courses/mit_learn_api.py index 092f118820..95bab70e75 100644 --- a/courses/mit_learn_api.py +++ b/courses/mit_learn_api.py @@ -1,16 +1,16 @@ +"""MIT Learn APIs""" from urllib.parse import urlencode +from typing import Any, Dict, List +from django.utils.dateparse import parse_datetime import requests -import re -from typing import Any, Dict, List, Optional -from courses.models import Course, CourseRun -from django.utils.dateparse import parse_datetime +from courses.models import CourseRun class MITLearnAPIError(Exception): """Custom exception for MIT Learn API errors.""" - pass + LEARN_API_COURSES_LIST_URL = "https://api.learn.mit.edu/api/v1/courses/" @@ -46,12 +46,6 @@ def fetch_course_from_mit_learn(course_id) -> Dict[str, Any]: return {} - - - - - - def sync_mit_learn_courseruns_for_course(course, raw_courseruns: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Process and normalize raw course data from MIT Learn API, but only for courses that already exist in the database. @@ -83,4 +77,3 @@ def sync_mit_learn_courseruns_for_course(course, raw_courseruns: List[Dict[str, num_created += 1 print(f"Created course run: {course_run.edx_course_key} for course {course.title}") return num_created - diff --git a/flow-typed/npm/redux-asserts_v0.x.x.js b/flow-typed/npm/redux-asserts_v0.x.x.js index c9e382a24a..eac251aad0 100644 --- a/flow-typed/npm/redux-asserts_v0.x.x.js +++ b/flow-typed/npm/redux-asserts_v0.x.x.js @@ -4,15 +4,16 @@ declare module 'redux-asserts' { declare type State = any; declare type StateFunc = ((state: State) => State); - declare type TestStore = { + declare export type ListenForActionsFunc = (actions: Array, () => void) => Promise; + declare export type DispatchThenFunc = (action: Action, expectedActions: Array) => Promise + + declare export type TestStore = { dispatch: Dispatch, getState: () => State, subscribe: (listener: () => void) => () => void, replaceReducer: (reducer: Reducer) => void, - createListenForActions: (stateFunc?: StateFunc) => ((actions: Array, () => void) => Promise), - createDispatchThen: (stateFunc?: StateFunc) => ( - (action: Action, expectedActions: Array) => Promise - ) + createListenForActions: (stateFunc?: StateFunc) => ListenForActionsFunc, + createDispatchThen: (stateFunc?: StateFunc) => DispatchThenFunc } declare export default function configureTestStore(reducerFunc?: (state: State) => State): TestStore; } diff --git a/package.json b/package.json index 2094141938..c9a1fb8db9 100644 --- a/package.json +++ b/package.json @@ -6,32 +6,34 @@ "type": "git", "url": "https://github.com/mitodl/micromasters.git" }, + "private": true, "dependencies": { + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.4", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-flow-strip-types": "^7.27.1", + "@babel/plugin-transform-react-constant-elements": "^7.27.1", + "@babel/plugin-transform-react-inline-elements": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/preset-env": "^7.28.3", + "@babel/preset-flow": "^7.27.1", + "@babel/preset-react": "^7.27.1", + "@babel/register": "^7.28.3", "@fancyapps/fancybox": "^3.5.7", "@material-ui/core": "^4.2.1", "@mitodl/iso-3166-2": "^1.0.1", "@sentry/browser": "^6.4.1", "@sentry/webpack-plugin": "^1.18.3", + "@trust/webcrypto": "^0.9.2", + "@typescript-eslint/eslint-plugin": "^8.46.1", "ajaxchimp": "^1.3.0", "autoprefixer": "^7.1.1", "awesome-phonenumber": "^2.35.0", "axios-mock-adapter": "^1.8.1", - "babel-core": "^6.24.1", - "babel-eslint": "^10.1.0", - "babel-loader": "^7.0.0", - "babel-plugin-jsx": "^1.2.0", - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-flow-strip-types": "^6.8.0", - "babel-plugin-transform-object-rest-spread": "^6.19.0", - "babel-plugin-transform-react-constant-elements": "^6.9.1", - "babel-plugin-transform-react-inline-elements": "^6.8.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-polyfill": "^6.22.0", - "babel-preset-env": "^1.5.1", - "babel-preset-latest": "^6.24.1", - "babel-preset-react": "^6.24.1", - "babel-register": "^6.24.1", + "babel-loader": "^10.0.0", + "babel-plugin-istanbul": "^7.0.1", "blueimp-canvas-to-blob": "^3.6.0", "bootstrap": "^3.3.7", "casual": "^1.5.17", @@ -47,20 +49,24 @@ "diacritics": "^1.3.0", "draft-js": "^0.10.1", "draft-js-export-html": "^1.4.1", - "eslint": "7", - "eslint-config-defaults": "9.0.0", - "eslint-config-google": "^0.9.1", - "eslint-config-mitodl": "0.1.0", - "eslint-plugin-babel": "^4.1.1", - "eslint-plugin-flow-vars": "^0.5.0", - "eslint-plugin-flowtype": "^2.35.1", - "eslint-plugin-mocha": "4.11.0", - "eslint-plugin-react": "7.1.0", - "eslint-plugin-react-hooks": "4", + "enzyme": "3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "eslint": "^8.x", + "eslint-config-google": "0.14.0", + "eslint-config-mitodl": "^2.1.0", + "eslint-plugin-babel": "5.3.1", + "eslint-plugin-flow-vars": "0.5.0", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "latest", + "eslint-plugin-mocha": "^10.4.3", + "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react-hooks": "^4.6.2", "express": "^4.15.3", "fetch-mock": "^5.11.0", "file-loader": "^0.11.1", "flow-bin": "^0.43.1", + "flow-typed": "^4.1.1", + "flowgen": "^1.21.0", "fuse-js-latest": "^2.6.2", "history": "^4.6.0", "iflow-lodash": "^1.1.16", @@ -68,20 +74,21 @@ "imports-loader": "^0.7.1", "isomorphic-fetch": "2.2.1", "jquery": "^3.5.0", - "jsdom": "^11.0.0", - "jsdom-global": "^3.0.2", + "jsdom": "16.7.0", "keycode": "^2.1.9", "lodash": "^4.17.21", "mini-css-extract-plugin": "^1.1.2", "minimist": "^1.2.8", - "mocha": "8.3.0", + "mocha": "^11.7.4", "moment": "^2.18.1", "moment-timezone": "^0.5.13", "nyc": "^15.0.0", "object.entries": "^1.0.4", "postcss-loader": "^2.0.5", - "prettier-eslint-cli": "^5.0.1", + "prettier-eslint": "^16.3.0", + "prettier-eslint-cli": "^8.0.1", "prop-types": "^15.5.10", + "raf": "^3.4.1", "ramda": "^0.24.0", "react": "~16.8", "react-addons-shallow-compare": "^15.4.0", @@ -126,14 +133,14 @@ "url-join": "^2.0.2", "url-loader": "^0.5.8", "warning": "^3.0.0", - "webpack": "^4.46.0", + "webpack": "^5.102.1", "webpack-bundle-tracker": "^0.4.3", - "webpack-cli": "^3.3.10", - "webpack-dev-middleware": "^1.9.0", - "webpack-hot-middleware": "^2.17.1" + "webpack-cli": "^6.0.1", + "webpack-dev-middleware": "latest", + "webpack-hot-middleware": "latest" }, "engines": { - "node": "14.18.2" + "node": "22.20.0" }, "scripts": { "postinstall": "./webpack_if_prod.sh", @@ -144,15 +151,12 @@ "codecov": "CODECOV=1 ./scripts/test/js_test.sh", "watch": "WATCH=1 ./scripts/test/js_test.sh", "flow": "flow check", - "fmt": "LOG_LEVEL= prettier-eslint --write --no-semi --ignore 'static/js/flow/**/*.js' 'static/js/**/*.js'", - "fmt:check": "prettier-eslint --list-different --no-semi --ignore 'static/js/flow/**/*.js' 'static/js/**/*.js'", + "fmt": "LOG_LEVEL= prettier-eslint --write --no-semi --ignore 'static/js/flow/**' --ignore 'static/js/vendor/**' --ignore 'static/js/data/**' --ignore '*/node_modules/**' 'static/js/**/*.js'", + "fmt:check": "prettier-eslint --list-different --no-semi --ignore 'static/js/flow/**' --ignore 'static/js/vendor/**' --ignore 'static/js/data/**' --ignore '*/node_modules/**' 'static/js/**/*.js'", "repl": "node --require ./scripts/repl.js" }, - "devDependencies": { - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.15.2" - }, "resolutions": { + "cheerio": "1.0.0-rc.3", "merge": "^2.1.1" }, "packageManager": "yarn@3.1.1" diff --git a/runtime.txt b/runtime.txt index 1a18179443..e0435e8248 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.9.1 +python-3.9.24 diff --git a/scripts/test/js_test.sh b/scripts/test/js_test.sh index bfc68f6a07..1274c8d09a 100755 --- a/scripts/test/js_test.sh +++ b/scripts/test/js_test.sh @@ -3,19 +3,19 @@ export TMP_FILE=$(mktemp) if [[ ! -z "$COVERAGE" ]] then - export CMD="node ./node_modules/nyc/bin/nyc.js --reporter=html mocha" + export CMD="npx nyc --reporter=html mocha" elif [[ ! -z "$CODECOV" ]] then - export CMD="node ./node_modules/nyc/bin/nyc.js --reporter=lcovonly -R spec mocha" + export CMD="npx nyc --reporter=lcovonly -R spec mocha" elif [[ ! -z "$WATCH" ]] then - export CMD="node ./node_modules/mocha/bin/_mocha --watch" + export CMD="npx mocha --watch" else - export CMD="node ./node_modules/mocha/bin/_mocha" + export CMD="npx mocha" fi export FILE_PATTERN=${1:-'"static/**/*/*_test.js"'} -CMD_ARGS="--require ./static/js/babelhook.js static/js/global_init.js $FILE_PATTERN" +CMD_ARGS="$FILE_PATTERN --exit" # Second argument (if specified) should be a string that will match specific test case descriptions # @@ -31,7 +31,7 @@ if [[ ! -z "$2" ]]; then CMD_ARGS+=" -g \"$2\"" fi -eval "$CMD $CMD_ARGS" 2> >(tee "$TMP_FILE") +eval "NODE_ENV=test $CMD $CMD_ARGS" 2> >(tee "$TMP_FILE") export TEST_RESULT=$? diff --git a/static/js/actions/programs.js b/static/js/actions/programs.js index dec8334f92..0452fb1254 100644 --- a/static/js/actions/programs.js +++ b/static/js/actions/programs.js @@ -82,10 +82,8 @@ export const addProgramEnrollment = ( dispatch(receiveAddProgramEnrollmentSuccess(enrollment)) dispatch( setToastMessage({ - message: `You are now enrolled in the ${ - enrollment.title - } MicroMasters`, - icon: TOAST_SUCCESS + message: `You are now enrolled in the ${enrollment.title} MicroMasters`, + icon: TOAST_SUCCESS }) ) dispatch(setEnrollProgramDialogVisibility(false)) diff --git a/static/js/actions/send_grades_dialog.js b/static/js/actions/send_grades_dialog.js index c1a97300da..c9c8bedf1a 100644 --- a/static/js/actions/send_grades_dialog.js +++ b/static/js/actions/send_grades_dialog.js @@ -1,6 +1,7 @@ +// @flow import { createAction } from "redux-actions" import type { Dispatcher } from "../flow/reduxTypes" -import { Dispatch } from "redux" +import type { Dispatch } from "redux" import { sendGradesRecordMail } from "../lib/api" export const SET_DIALOG_VISIBILITY = "SET_DIALOG_VISIBILITY" diff --git a/static/js/components/DiscussionCard.js b/static/js/components/DiscussionCard.js index ec1bd446c1..cf12ac766f 100644 --- a/static/js/components/DiscussionCard.js +++ b/static/js/components/DiscussionCard.js @@ -9,11 +9,7 @@ import { channelURL, postURL } from "../lib/discussions" import type { Post } from "../flow/discussionTypes" import CardContent from "@material-ui/core/CardContent" -const formatTime = R.compose( - date => date.fromNow(), - moment, - R.prop("created") -) +const formatTime = R.compose(date => date.fromNow(), moment, R.prop("created")) const renderPosts = R.compose( R.map(post => ( diff --git a/static/js/components/DiscussionCard_test.js b/static/js/components/DiscussionCard_test.js index cb6a411d6a..b31a760513 100644 --- a/static/js/components/DiscussionCard_test.js +++ b/static/js/components/DiscussionCard_test.js @@ -30,20 +30,11 @@ describe("DiscussionCard", () => { assert.equal(postDisplay.find("img").props().src, post.profile_image) assert.equal( - postDisplay - .find("a") - .at(0) - .props().href, + postDisplay.find("a").at(0).props().href, postURL(post.id, post.channel_name) ) - assert.equal( - postDisplay - .find("a") - .at(0) - .text(), - post.title - ) + assert.equal(postDisplay.find("a").at(0).text(), post.title) }) it("should link to the original channel", () => { @@ -52,30 +43,15 @@ describe("DiscussionCard", () => { const [post] = postList assert.equal( - postDisplay - .find("a") - .at(1) - .props().href, + postDisplay.find("a").at(1).props().href, channelURL(post.channel_name) ) - assert.equal( - postDisplay - .find("a") - .at(1) - .text(), - post.channel_name - ) + assert.equal(postDisplay.find("a").at(1).text(), post.channel_name) }) it("should link to open discussions", () => { const wrapper = renderCard() - assert.equal( - wrapper - .find("a") - .at(0) - .props().href, - "/discussions" - ) + assert.equal(wrapper.find("a").at(0).props().href, "/discussions") }) }) diff --git a/static/js/components/EmailCampaignsCard.js b/static/js/components/EmailCampaignsCard.js index 67cbcefc6a..358781286d 100644 --- a/static/js/components/EmailCampaignsCard.js +++ b/static/js/components/EmailCampaignsCard.js @@ -70,12 +70,8 @@ type CampaignCardProps = { } const EmailCampaignsCard = (props: CampaignCardProps) => { - const { - getEmails, - toggleEmailActive, - emailsInFlight, - openEmailComposer - } = props + const { getEmails, toggleEmailActive, emailsInFlight, openEmailComposer } = + props return ( diff --git a/static/js/components/EmailCampaignsCard_test.js b/static/js/components/EmailCampaignsCard_test.js index f35aada6a2..c086818b7e 100644 --- a/static/js/components/EmailCampaignsCard_test.js +++ b/static/js/components/EmailCampaignsCard_test.js @@ -55,11 +55,7 @@ describe("EmailCampaignsCard", () => { it("should render a switch, and call toggleEmailActive on click", () => { const card = renderCard() - card - .find(Switch) - .first() - .props() - .onChange() + card.find(Switch).first().props().onChange() assert( emailCardProps.toggleEmailActive.calledWith( GET_AUTOMATIC_EMAILS_RESPONSE[0] @@ -69,10 +65,7 @@ describe("EmailCampaignsCard", () => { it('should render an "edit" button, and call openEmailComposer with the AutomaticEmail on click', () => { const card = renderCard() - card - .find("a") - .first() - .simulate("click") + card.find("a").first().simulate("click") assert( emailCardProps.openEmailComposer.calledWith( GET_AUTOMATIC_EMAILS_RESPONSE[0] diff --git a/static/js/components/EmploymentForm.js b/static/js/components/EmploymentForm.js index b4f9048bdd..31fe8ed65d 100644 --- a/static/js/components/EmploymentForm.js +++ b/static/js/components/EmploymentForm.js @@ -341,6 +341,7 @@ class EmploymentForm extends ProfileFormFields { // eslint-disable-next-line camelcase profile: { work_history } } = this.props + // eslint-disable-next-line camelcase if (showSwitch && work_history.length === 0) { return this.renderWorkQuestionForm() } else { diff --git a/static/js/components/ErrorMessage_test.js b/static/js/components/ErrorMessage_test.js index 521a0c1fce..ecd1f781bf 100644 --- a/static/js/components/ErrorMessage_test.js +++ b/static/js/components/ErrorMessage_test.js @@ -48,7 +48,10 @@ describe("ErrorMessage", () => { const renderErrorMessage = props => { return makeStrippedHtml() } - const codeAttributes = [["error_code", "500"], ["errorStatusCode", 404]] + const codeAttributes = [ + ["error_code", "500"], + ["errorStatusCode", 404] + ] const messageAttributes = [ ["user_message", "A message"], ["detail", "Some details"] diff --git a/static/js/components/LearnerInfoCard.js b/static/js/components/LearnerInfoCard.js index 08b8a7eb20..6ac378b862 100644 --- a/static/js/components/LearnerInfoCard.js +++ b/static/js/components/LearnerInfoCard.js @@ -19,9 +19,7 @@ import CardContent from "@material-ui/core/CardContent" const showLegalNameIfStaff = profile => { return hasAnyStaffRole(SETTINGS.roles) ? ( -
{`(Legal name: ${profile.first_name} ${ - profile.last_name - })`}
+
{`(Legal name: ${profile.first_name} ${profile.last_name})`}
) : null } @@ -113,11 +111,8 @@ export default class LearnerInfoCard extends React.Component { } render() { - const { - profile, - toggleShowPersonalDialog, - toggleShowAboutMeDialog - } = this.props + const { profile, toggleShowPersonalDialog, toggleShowAboutMeDialog } = + this.props let personalInfoEditContent if (this.isOwnProfilePage()) { diff --git a/static/js/components/LearnersInProgramCard.js b/static/js/components/LearnersInProgramCard.js index 53ac47b63d..b082b66438 100644 --- a/static/js/components/LearnersInProgramCard.js +++ b/static/js/components/LearnersInProgramCard.js @@ -1,3 +1,4 @@ +// @flow /* global SETTINGS: false */ import React from "react" import R from "ramda" diff --git a/static/js/components/LeaveProgramWizard_test.js b/static/js/components/LeaveProgramWizard_test.js index 2398f1d5a1..ca56ea4183 100644 --- a/static/js/components/LeaveProgramWizard_test.js +++ b/static/js/components/LeaveProgramWizard_test.js @@ -1,3 +1,4 @@ +// @flow /* global SETTINGS: false */ import PropTypes from "prop-types" import { assert } from "chai" @@ -21,10 +22,12 @@ describe("LeaveProgramWizard", () => { dialogVisibility: { unenrollProgramDialog: false } - } + }, + dispatch: null } const getEl = (selector: string): HTMLElement => { + // $FlowFixMe return (document.querySelector(selector): HTMLElement) } diff --git a/static/js/components/OrderSummary_test.js b/static/js/components/OrderSummary_test.js index 3bce626d02..ace4893f45 100644 --- a/static/js/components/OrderSummary_test.js +++ b/static/js/components/OrderSummary_test.js @@ -77,19 +77,13 @@ describe("OrderSummary", () => { const descriptions = wrapper.find(".description") assert.equal(descriptions.length, 3) assert.equal( - descriptions - .children() - .at(1) - .text(), + descriptions.children().at(1).text(), code ? `Discount from coupon ${code}` : "Discount from coupon" ) const amounts = wrapper.find(".amount") assert.equal(amounts.length, 3) assert.equal( - amounts - .children() - .at(1) - .text(), + amounts.children().at(1).text(), `-$${COURSE_PRICES_RESPONSE[1].price}` ) }) diff --git a/static/js/components/PersonalTab.js b/static/js/components/PersonalTab.js index 4d26505473..c50d6b67ed 100644 --- a/static/js/components/PersonalTab.js +++ b/static/js/components/PersonalTab.js @@ -58,12 +58,7 @@ export default class PersonalTab extends React.Component { dispatch: Function } - sortPrograms = R.sortBy( - R.compose( - R.toLower, - R.prop("title") - ) - ) + sortPrograms = R.sortBy(R.compose(R.toLower, R.prop("title"))) programOptions = R.compose( R.map(program => ({ value: program.id, label: program.title })), diff --git a/static/js/components/PersonalTab_test.js b/static/js/components/PersonalTab_test.js index 2e31f1a120..4f1b728d3e 100644 --- a/static/js/components/PersonalTab_test.js +++ b/static/js/components/PersonalTab_test.js @@ -49,12 +49,9 @@ describe("PersonalTab", () => { .find("Select") .props().options assert.equal(programOptions.length, PROGRAMS.length) - const sortedEnrollments = R.sortBy( - R.compose( - R.toLower, - R.prop("title") - ) - )(PROGRAMS) + const sortedEnrollments = R.sortBy(R.compose(R.toLower, R.prop("title")))( + PROGRAMS + ) programOptions.forEach((menuItem, i) => { const program = sortedEnrollments[i] assert.equal(program.title, menuItem.label) @@ -65,10 +62,7 @@ describe("PersonalTab", () => { it("should have the current program enrollment selected", () => { const selectedProgram = PROGRAMS[0] const wrapper = renderPersonalTab(selectedProgram) - const props = wrapper - .find(".program-select") - .find("Select") - .props() + const props = wrapper.find(".program-select").find("Select").props() assert.equal(props.value, selectedProgram.id) }) }) diff --git a/static/js/components/PrivacyForm.js b/static/js/components/PrivacyForm.js index 5b79e6d7d7..e7d6b60410 100644 --- a/static/js/components/PrivacyForm.js +++ b/static/js/components/PrivacyForm.js @@ -65,12 +65,8 @@ class PrivacyForm extends ProfileFormFields { label: string, options: Option[] ): React$Element<*> { - const { - profile, - updateProfile, - validator, - updateValidationVisibility - } = this.props + const { profile, updateProfile, validator, updateValidationVisibility } = + this.props const onChange = e => { const clone = _.cloneDeep(profile) let value = e.target.value diff --git a/static/js/components/ProgramEnrollmentDialog.js b/static/js/components/ProgramEnrollmentDialog.js index fb5ef7d16c..2b7dd098ba 100644 --- a/static/js/components/ProgramEnrollmentDialog.js +++ b/static/js/components/ProgramEnrollmentDialog.js @@ -1,3 +1,4 @@ +// @flow import React from "react" import Dialog from "@material-ui/core/Dialog" import _ from "lodash" @@ -38,23 +39,20 @@ export default class ProgramEnrollmentDialog extends React.Component { if (_.isNil(selectedProgram)) { setError("No program selected") } else { + // $FlowFixMe enrollInProgram(selectedProgram) } } + // $FlowFixMe handleSelectedProgramChange = event => { const { setSelectedProgram } = this.props setSelectedProgram(event.target.value) } render() { - const { - error, - visibility, - selectedProgram, - programs, - fetchAddStatus - } = this.props + const { error, visibility, selectedProgram, programs, fetchAddStatus } = + this.props const unenrolledPrograms = _.sortBy( programs.filter(program => !program.enrolled), diff --git a/static/js/components/ProgramEnrollmentDialog_test.js b/static/js/components/ProgramEnrollmentDialog_test.js index 64761af96c..791a9d34d5 100644 --- a/static/js/components/ProgramEnrollmentDialog_test.js +++ b/static/js/components/ProgramEnrollmentDialog_test.js @@ -56,10 +56,9 @@ describe("ProgramEnrollmentDialog", () => { return helper .renderComponent("/dashboard", DASHBOARD_SUCCESS_ACTIONS) .then(([wrapper]) => { - const handler = wrapper - .find(ProgramEnrollmentDialog) - .at(0) - .props()[funcName] + const handler = wrapper.find(ProgramEnrollmentDialog).at(0).props()[ + funcName + ] handler(value) assert(stub.calledWith(value)) }) @@ -71,10 +70,9 @@ describe("ProgramEnrollmentDialog", () => { return helper .renderComponent("/dashboard", DASHBOARD_SUCCESS_ACTIONS) .then(([wrapper]) => { - const actual = wrapper - .find(ProgramEnrollmentDialog) - .at(0) - .props()[propName] + const actual = wrapper.find(ProgramEnrollmentDialog).at(0).props()[ + propName + ] assert.equal(actual, value) }) }) diff --git a/static/js/components/ProgressWidget_test.js b/static/js/components/ProgressWidget_test.js index 3f140874c7..d12f8209b8 100644 --- a/static/js/components/ProgressWidget_test.js +++ b/static/js/components/ProgressWidget_test.js @@ -127,18 +127,9 @@ describe("ProgressWidget", () => { it("progress widget display", () => { const wrapper = shallow() + assert.equal(wrapper.find(".progress-title").children().text(), "Progress") assert.equal( - wrapper - .find(".progress-title") - .children() - .text(), - "Progress" - ) - assert.equal( - wrapper - .find(".text-course-complete") - .children() - .text(), + wrapper.find(".text-course-complete").children().text(), "Courses complete" ) assert.equal(wrapper.find(".circular-progress-widget-txt").text(), "3/5") @@ -166,10 +157,7 @@ describe("ProgressWidget", () => { const wrapper = shallow() assert.equal( - wrapper - .find(".mm-minor-action") - .children() - .text(), + wrapper.find(".mm-minor-action").children().text(), "View Program Letter" ) }) diff --git a/static/js/components/SocialAuthDialog.js b/static/js/components/SocialAuthDialog.js index 92eed3131e..ee59e7b51e 100644 --- a/static/js/components/SocialAuthDialog.js +++ b/static/js/components/SocialAuthDialog.js @@ -1,4 +1,6 @@ +// @flow /* global SETTINGS: false */ +// $FlowFixMe import React, { useState, useEffect } from "react" import Button from "@material-ui/core/Button" import Dialog from "@material-ui/core/Dialog" diff --git a/static/js/components/SocialAuthDialog_test.js b/static/js/components/SocialAuthDialog_test.js index a8a1f1c799..73947580e4 100644 --- a/static/js/components/SocialAuthDialog_test.js +++ b/static/js/components/SocialAuthDialog_test.js @@ -68,10 +68,7 @@ describe("SocialAuthDialog", () => { it("should have a description of what the learner needs to do", () => { const wrapper = renderDialog(unauthenticatedEnrollment) - const text = wrapper - .find(Grid) - .at(0) - .text() + const text = wrapper.find(Grid).at(0).text() assert.equal( text, `Courses for ${ diff --git a/static/js/components/SocialAuthReauthenticateDialog.js b/static/js/components/SocialAuthReauthenticateDialog.js index 15e070e9ea..d7cddabcde 100644 --- a/static/js/components/SocialAuthReauthenticateDialog.js +++ b/static/js/components/SocialAuthReauthenticateDialog.js @@ -1,4 +1,6 @@ +// @flow /* global SETTINGS: false */ +// $FlowFixMe import React, { useState, useEffect } from "react" import Button from "@material-ui/core/Button" import Dialog from "@material-ui/core/Dialog" diff --git a/static/js/components/SocialAuthReauthenticateDialog_test.js b/static/js/components/SocialAuthReauthenticateDialog_test.js index c5252794ab..f8eedee3c6 100644 --- a/static/js/components/SocialAuthReauthenticateDialog_test.js +++ b/static/js/components/SocialAuthReauthenticateDialog_test.js @@ -47,10 +47,7 @@ describe("SocialAuthReauthenticateDialog", () => { it("should have a description of what the learner needs to do", () => { const wrapper = renderDialog([invalidAuth]) - const text = wrapper - .find(Grid) - .at(0) - .text() + const text = wrapper.find(Grid).at(0).text() assert.equal( text, `Your account is linked to ${String( diff --git a/static/js/components/SpinnerButton.js b/static/js/components/SpinnerButton.js index 484005e001..9434748b84 100644 --- a/static/js/components/SpinnerButton.js +++ b/static/js/components/SpinnerButton.js @@ -1,20 +1,26 @@ +// @flow import React from "react" import CircularProgress from "@material-ui/core/CircularProgress" type SpinnerButtonProps = { spinning: boolean, - component: React.Component<*, *, *>, + component: React.Component<*, *, *> | string, className?: string, onClick?: Function, children?: any, disabled?: ?boolean, - ignoreRecentlyClicked: ?boolean + ignoreRecentlyClicked?: ?boolean +} + +type SpinnerButtonState = { + recentlyClicked: boolean } export default class SpinnerButton extends React.Component { props: SpinnerButtonProps + state: SpinnerButtonState - constructor(props) { + constructor(props: SpinnerButtonProps) { super(props) this.state = { // This keeps track of if a button was recently clicked, to indicate @@ -38,10 +44,11 @@ export default class SpinnerButton extends React.Component { // If button is not disabled and has an onClick handler, make sure to set recentlyClicked // so we display the spinner - onClick = (...args) => { + onClick = (...args: any[]) => { this.setState({ recentlyClicked: true }) + // $FlowFixMe return this.props.onClick(...args) } @@ -68,6 +75,7 @@ export default class SpinnerButton extends React.Component { } return ( + // $FlowFixMe ) => { this.closeAndClearDialog() if (SETTINGS.open_discussions_redirect_url) { - const channelUrl = `${ - SETTINGS.open_discussions_redirect_url - }channel/${channel.name}` + const channelUrl = `${SETTINGS.open_discussions_redirect_url}channel/${channel.name}` window.open(channelUrl, "_self") } }, @@ -114,11 +112,9 @@ export const withChannelCreateDialog = (WrappedComponent: ReactClass<*>) => { ) } - updateFieldEdit = R.curry( - (fieldName, e): void => { - this.updateChannelChanges(fieldName, e.target.value) - } - ) + updateFieldEdit = R.curry((fieldName, e): void => { + this.updateChannelChanges(fieldName, e.target.value) + }) render() { const { diff --git a/static/js/components/dashboard/CourseAction_test.js b/static/js/components/dashboard/CourseAction_test.js index c4a3cb9d93..a0053b9493 100644 --- a/static/js/components/dashboard/CourseAction_test.js +++ b/static/js/components/dashboard/CourseAction_test.js @@ -102,34 +102,10 @@ describe("CourseAction", () => { for (const data of [ ["", "", true], ["foo/bar/baz", "", true], - [ - "foo/bar/baz", - moment() - .add(10, "days") - .toISOString(), - true - ], - [ - "", - moment() - .add(10, "days") - .toISOString(), - true - ], - [ - "", - moment() - .subtract(10, "days") - .toISOString(), - true - ], - [ - "foo/bar/baz", - moment() - .subtract(10, "days") - .toISOString(), - false - ] + ["foo/bar/baz", moment().add(10, "days").toISOString(), true], + ["", moment().add(10, "days").toISOString(), true], + ["", moment().subtract(10, "days").toISOString(), true], + ["foo/bar/baz", moment().subtract(10, "days").toISOString(), false] ]) { it(`should ${data[2] ? "disable" : "enable"} Re-Enroll button`, () => { const run = course.runs[0] diff --git a/static/js/components/dashboard/DashboardUserCard_test.js b/static/js/components/dashboard/DashboardUserCard_test.js index 4cc4cb09e6..d2bbd4ac23 100644 --- a/static/js/components/dashboard/DashboardUserCard_test.js +++ b/static/js/components/dashboard/DashboardUserCard_test.js @@ -17,20 +17,12 @@ describe("DashboardUserCard", () => { ) assert.equal( - wrapper - .find(".dashboard-user-card-image") - .find(ProfileImage) - .props().profile, + wrapper.find(".dashboard-user-card-image").find(ProfileImage).props() + .profile, profile ) const textContainer = wrapper.find(".dashboard-user-card-text") - assert.equal( - textContainer - .find(Typography) - .first() - .text(), - "Jane" - ) + assert.equal(textContainer.find(Typography).first().text(), "Jane") assert.equal( textContainer.find(".dashboard-user-card-text-program").text(), diff --git a/static/js/components/dashboard/courses/GradeDetailPopup.js b/static/js/components/dashboard/courses/GradeDetailPopup.js index 5a801e74c2..2aee61445c 100644 --- a/static/js/components/dashboard/courses/GradeDetailPopup.js +++ b/static/js/components/dashboard/courses/GradeDetailPopup.js @@ -156,12 +156,8 @@ type GradeDetailPopupProps = { } const GradeDetailPopup = (props: GradeDetailPopupProps) => { - const { - course, - setShowGradeDetailDialog, - dialogVisibility, - gradeType - } = props + const { course, setShowGradeDetailDialog, dialogVisibility, gradeType } = + props return ( { makeRunPaid(course.runs[0]) const wrapper = renderDetailPopup() assert.equal( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), `${course.runs[0].year_season}Paid` ) }) @@ -69,10 +66,7 @@ describe("GradeDetailPopup", () => { makeRunEnrolled(course.runs[0]) const wrapper = renderDetailPopup() assert.equal( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), `${course.runs[0].year_season}Auditing` ) }) @@ -82,10 +76,7 @@ describe("GradeDetailPopup", () => { makeRunPaid(course.runs[0]) const wrapper = renderDetailPopup() assert.equal( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), `${course.runs[0].year_season}In Progress (paid)` ) }) @@ -94,10 +85,7 @@ describe("GradeDetailPopup", () => { makeRunPassed(course.runs[0]) const wrapper = renderDetailPopup() assert.equal( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), `${course.runs[0].year_season}Passed` ) }) @@ -105,10 +93,7 @@ describe("GradeDetailPopup", () => { it("shows a grade, if there is one", () => { course.runs[0].final_grade = 93 assert.include( - renderDetailPopup() - .find(".course-run-row") - .first() - .text(), + renderDetailPopup().find(".course-run-row").first().text(), "93" ) }) @@ -116,10 +101,7 @@ describe("GradeDetailPopup", () => { it("shows info for a failed course", () => { makeRunFailed(course.runs[1]) assert.equal( - renderDetailPopup() - .find(".course-run-row") - .at(1) - .text(), + renderDetailPopup().find(".course-run-row").at(1).text(), `${course.runs[1].year_season}Not passed` ) }) @@ -128,20 +110,8 @@ describe("GradeDetailPopup", () => { course.runs[0].final_grade = 22 course.runs[1].final_grade = 82 const wrapper = renderDetailPopup() - assert.equal( - wrapper - .find(".course-run-row") - .at(0) - .find(Icon).length, - 0 - ) - assert.equal( - wrapper - .find(".course-run-row") - .at(1) - .find(Icon).length, - 1 - ) + assert.equal(wrapper.find(".course-run-row").at(0).find(Icon).length, 0) + assert.equal(wrapper.find(".course-run-row").at(1).find(Icon).length, 1) }) it("includes helpful information", () => { @@ -165,10 +135,7 @@ describe("GradeDetailPopup", () => { it("should display exam grades, if passed the right grade type", () => { const wrapper = renderDetailPopup({ gradeType: EXAM_GRADE }) assert.include( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), formatGrade(course.proctorate_exams_grades[0].percentage_grade * 100) ) }) @@ -177,10 +144,7 @@ describe("GradeDetailPopup", () => { course.proctorate_exams_grades[0].percentage_grade = 0 const wrapper = renderDetailPopup({ gradeType: EXAM_GRADE }) assert.include( - wrapper - .find(".course-run-row") - .first() - .text(), + wrapper.find(".course-run-row").first().text(), formatGrade(course.proctorate_exams_grades[0].percentage_grade * 100) ) }) @@ -191,19 +155,7 @@ describe("GradeDetailPopup", () => { course.proctorate_exams_grades[1].percentage_grade = 0.8 course.proctorate_exams_grades[1].passed = true const wrapper = renderDetailPopup({ gradeType: EXAM_GRADE }) - assert.equal( - wrapper - .find(".course-run-row") - .at(0) - .find(Icon).length, - 0 - ) - assert.equal( - wrapper - .find(".course-run-row") - .at(1) - .find(Icon).length, - 1 - ) + assert.equal(wrapper.find(".course-run-row").at(0).find(Icon).length, 0) + assert.equal(wrapper.find(".course-run-row").at(1).find(Icon).length, 1) }) }) diff --git a/static/js/components/dashboard/courses/Grades_test.js b/static/js/components/dashboard/courses/Grades_test.js index a4725b3588..1c46905d69 100644 --- a/static/js/components/dashboard/courses/Grades_test.js +++ b/static/js/components/dashboard/courses/Grades_test.js @@ -69,7 +69,10 @@ describe("Course Grades", () => { }) it("should only display the course grade if has_exam == false", () => { - [[true, 3], [false, 1]].forEach(([hasExam, expectedGradeCount]) => { + [ + [true, 3], + [false, 1] + ].forEach(([hasExam, expectedGradeCount]) => { course.has_exam = hasExam const grades = renderGrades() assert.equal( @@ -121,17 +124,11 @@ describe("Course Grades", () => { examGrade.passed = true course.proctorate_exams_grades.push(examGrade) const grades = renderGrades() - grades - .find(".open-popup") - .first() - .simulate("click") + grades.find(".open-popup").first().simulate("click") assert.ok( setShowGradeDetailDialogStub.calledWith(true, COURSE_GRADE, course.title) ) - grades - .find(".open-popup") - .at(1) - .simulate("click") + grades.find(".open-popup").at(1).simulate("click") assert.ok( setShowGradeDetailDialogStub.calledWith(true, EXAM_GRADE, course.title) ) diff --git a/static/js/components/dashboard/courses/StatusMessages_test.js b/static/js/components/dashboard/courses/StatusMessages_test.js index a4bd2bc04d..a257949177 100644 --- a/static/js/components/dashboard/courses/StatusMessages_test.js +++ b/static/js/components/dashboard/courses/StatusMessages_test.js @@ -192,7 +192,7 @@ describe("Course Status Messages", () => { ) assert.deepEqual(messages[1], { - action: "course action was called", + action: "course action was called", message: "You are re-taking this course. To get a new grade, you need to pay again." }) @@ -235,14 +235,13 @@ describe("Course Status Messages", () => { const dueDate = moment(course.runs[0].course_upgrade_deadline) .tz(moment.tz.guess()) .format(COURSE_DEADLINE_FORMAT) - calculateMessagesProps["financialAid"][ - "application_status" - ] = FA_STATUS_PENDING_DOCS + calculateMessagesProps["financialAid"]["application_status"] = + FA_STATUS_PENDING_DOCS calculateMessagesProps["hasFinancialAid"] = true assertIsJust(calculateMessages(calculateMessagesProps), [ { - action: "course action was called", + action: "course action was called", message: "You are auditing. Your personal course price is pending, " + `and needs to be approved before you can pay for courses. (Payment due on ${dueDate})` @@ -265,7 +264,7 @@ describe("Course Status Messages", () => { assertIsJust(calculateMessages(calculateMessagesProps), [ { - action: "course action was called", + action: "course action was called", message: "You are auditing. Your personal course price is pending, " + "and needs to be approved before you can pay for courses." @@ -285,7 +284,7 @@ describe("Course Status Messages", () => { assertIsJust(calculateMessages(calculateMessagesProps), [ { - action: "course action was called", + action: "course action was called", message: "You are auditing. To get credit, you need to pay for the course." } @@ -337,18 +336,8 @@ describe("Course Status Messages", () => { mounted.text(), "You passed this course! View Certificate | Re-enroll" ) - assert.equal( - mounted - .find("a") - .at(0) - .props().href, - "certificate_url" - ) - mounted - .find("a") - .at(1) - .props() - .onClick() + assert.equal(mounted.find("a").at(0).props().href, "certificate_url") + mounted.find("a").at(1).props().onClick() assert(calculateMessagesProps.setShowExpandedCourseStatus.called) }) @@ -410,15 +399,11 @@ describe("Course Status Messages", () => { for (const nextEnrollmentStart of [ ["", ""], [ - moment() - .add(10, "days") - .toISOString(), + moment().add(10, "days").toISOString(), ` Enrollment starts ${formatDate(moment().add(10, "days"))}.` ] ]) { - it(`should nag about missing the payment deadline when future re-enrollments and date is ${ - nextEnrollmentStart[0] - }`, () => { + it(`should nag about missing the payment deadline when future re-enrollments and date is ${nextEnrollmentStart[0]}`, () => { makeRunPast(course.runs[0]) makeRunMissedDeadline(course.runs[0]) makeRunOverdue(course.runs[0]) @@ -427,10 +412,8 @@ describe("Course Status Messages", () => { const date = formatDate(course.runs[1].course_start_date) assertIsJust(calculateMessages(calculateMessagesProps), [ { - message: `You missed the payment deadline, but you can re-enroll. Next course starts ${date}.${ - nextEnrollmentStart[1] - }`, - action: "course action was called" + message: `You missed the payment deadline, but you can re-enroll. Next course starts ${date}.${nextEnrollmentStart[1]}`, + action: "course action was called" } ]) assert( @@ -528,15 +511,11 @@ describe("Course Status Messages", () => { for (const nextEnrollmentStart of [ ["", ""], [ - moment() - .add(10, "days") - .toISOString(), + moment().add(10, "days").toISOString(), ` Enrollment starts ${formatDate(moment().add(10, "days"))}.` ] ]) { - it(`should inform next enrollment date after failing edx course when date is ${ - nextEnrollmentStart[0] - }`, () => { + it(`should inform next enrollment date after failing edx course when date is ${nextEnrollmentStart[0]}`, () => { makeRunPast(course.runs[0]) makeRunFailed(course.runs[0]) makeRunFuture(course.runs[1]) @@ -546,10 +525,8 @@ describe("Course Status Messages", () => { ) assertIsJust(calculateMessages(calculateMessagesProps), [ { - message: `You did not pass the course, but you can re-enroll. Next course starts ${date}.${ - nextEnrollmentStart[1] - }`, - action: "course action was called" + message: `You did not pass the course, but you can re-enroll. Next course starts ${date}.${nextEnrollmentStart[1]}`, + action: "course action was called" } ]) assert( diff --git a/static/js/components/dashboard/courses/test_util.js b/static/js/components/dashboard/courses/test_util.js index 3d186650c2..8fcdbaec41 100644 --- a/static/js/components/dashboard/courses/test_util.js +++ b/static/js/components/dashboard/courses/test_util.js @@ -11,45 +11,27 @@ import { } from "../../../constants" export const makeRunCurrent = (run: CourseRun) => { - run.course_start_date = moment() - .subtract(10, "days") - .format() - run.enrollment_start_date = moment() - .subtract(12, "days") - .format() - run.course_end_date = moment() - .add(10, "days") - .format() + run.course_start_date = moment().subtract(10, "days").format() + run.enrollment_start_date = moment().subtract(12, "days").format() + run.course_end_date = moment().add(10, "days").format() } export const makeRunPast = (run: CourseRun) => { - run.course_start_date = moment() - .subtract(30, "days") - .format() - run.course_end_date = moment() - .subtract(10, "days") - .format() + run.course_start_date = moment().subtract(30, "days").format() + run.course_end_date = moment().subtract(10, "days").format() } export const makeRunFuture = (run: CourseRun) => { - run.course_start_date = moment() - .add(10, "days") - .format() - run.course_end_date = moment() - .add(30, "days") - .format() + run.course_start_date = moment().add(10, "days").format() + run.course_end_date = moment().add(30, "days").format() } export const makeRunOverdue = (run: CourseRun) => { - run.course_upgrade_deadline = moment() - .subtract(10, "days") - .format() + run.course_upgrade_deadline = moment().subtract(10, "days").format() } export const makeRunDueSoon = (run: CourseRun) => { - run.course_upgrade_deadline = moment() - .add(5, "days") - .format() + run.course_upgrade_deadline = moment().add(5, "days").format() } export const makeRunEnrolled = (run: CourseRun) => { diff --git a/static/js/components/dashboard/courses/util_test.js b/static/js/components/dashboard/courses/util_test.js index f7abb17046..a1be731445 100644 --- a/static/js/components/dashboard/courses/util_test.js +++ b/static/js/components/dashboard/courses/util_test.js @@ -45,9 +45,7 @@ describe("dashboard course utilities", () => { it("should show start date, if >10 days away", () => { [10, 11, 15].forEach(days => { - const inTheFuture = moment() - .add(days, "days") - .add(10, "minutes") + const inTheFuture = moment().add(days, "days").add(10, "minutes") run.course_start_date = inTheFuture.format() assert.equal( courseStartDateMessage(run), @@ -68,9 +66,7 @@ describe("dashboard course utilities", () => { }) it('should say "started" if the course already started', () => { - run.course_start_date = moment() - .subtract(2, "days") - .format() + run.course_start_date = moment().subtract(2, "days").format() assert(courseStartDateMessage(run).startsWith("Course started")) }) @@ -125,47 +121,31 @@ describe("dashboard course utilities", () => { }) it("should return true if the start date passed and the end date is in the future", () => { - run.course_start_date = moment() - .subtract(5, "days") - .format() - run.course_end_date = moment() - .add(5, "days") - .format() + run.course_start_date = moment().subtract(5, "days").format() + run.course_end_date = moment().add(5, "days").format() assert.isTrue(courseCurrentlyInProgress(run)) }) it("should return true for a future course run", () => { - run.course_start_date = moment() - .add(5, "days") - .format() - run.course_end_date = moment() - .add(25, "days") - .format() + run.course_start_date = moment().add(5, "days").format() + run.course_end_date = moment().add(25, "days").format() assert.isFalse(courseCurrentlyInProgress(run)) }) it("should return false for a past course run", () => { - run.course_start_date = moment() - .add(5, "days") - .format() - run.course_end_date = moment() - .add(25, "days") - .format() + run.course_start_date = moment().add(5, "days").format() + run.course_end_date = moment().add(25, "days").format() assert.isFalse(courseCurrentlyInProgress(run)) }) it("should return true if the start date passed and end date is empty", () => { - run.course_start_date = moment() - .subtract(5, "days") - .format() + run.course_start_date = moment().subtract(5, "days").format() run.course_end_date = "" assert.isTrue(courseCurrentlyInProgress(run)) }) it("should return false for a future course run end date is empty", () => { - run.course_start_date = moment() - .add(5, "days") - .format() + run.course_start_date = moment().add(5, "days").format() run.course_end_date = "" assert.isFalse(courseCurrentlyInProgress(run)) }) @@ -179,16 +159,12 @@ describe("dashboard course utilities", () => { }) it("should return true if the end date is after the current time", () => { - run.course_end_date = moment() - .add(5, "days") - .format() + run.course_end_date = moment().add(5, "days").format() assert.isTrue(courseUpcomingOrCurrent(run)) }) it("should return false otherwise", () => { - run.course_end_date = moment() - .subtract(5, "days") - .format() + run.course_end_date = moment().subtract(5, "days").format() assert.isFalse(courseUpcomingOrCurrent(run)) }) @@ -198,9 +174,7 @@ describe("dashboard course utilities", () => { }) it("should return true if course ended but status currently-enrolled", () => { - run.course_end_date = moment() - .subtract(5, "days") - .format() + run.course_end_date = moment().subtract(5, "days").format() run.status = STATUS_CURRENTLY_ENROLLED assert.isTrue(courseUpcomingOrCurrent(run)) }) @@ -404,18 +378,8 @@ describe("dashboard course utilities", () => { for (const data of [ ["", false], [now.toISOString(), true], - [ - moment() - .add(10, "days") - .toISOString(), - false - ], - [ - moment() - .subtract(10, "days") - .toISOString(), - true - ] + [moment().add(10, "days").toISOString(), false], + [moment().subtract(10, "days").toISOString(), true] ]) { it(`should return ${ data[1] ? "true" : "false" diff --git a/static/js/components/email/EmailCompositionDialog.js b/static/js/components/email/EmailCompositionDialog.js index cbf33dd4c4..1ad67f877a 100644 --- a/static/js/components/email/EmailCompositionDialog.js +++ b/static/js/components/email/EmailCompositionDialog.js @@ -45,12 +45,7 @@ const convertHTMLToEditorState = (html: string): Object => { const editorStateFromProps = R.compose( S.maybe({ editorState: EditorState.createEmpty() }, R.objOf("editorState")), S.map(convertHTMLToEditorState), - S.filter( - R.compose( - R.not, - R.isEmpty - ) - ), + S.filter(R.compose(R.not, R.isEmpty)), getm("body"), R.pathOr({}, ["activeEmail", "inputs"]) ) @@ -73,7 +68,7 @@ type EmailDialogProps = { closeAndClearEmailComposer: () => void, closeEmailComposerAndSend: () => Promise, updateEmailFieldEdit: () => void, - renderRecipients?: (filters: ?Array) => React$Element<*>, + renderRecipients?: (filters: ?Array) => React$Element<*> | null, updateEmailBody: (e: Object) => void, dialogType: string, supportBulkEmails: boolean diff --git a/static/js/components/email/hoc.js b/static/js/components/email/hoc.js index e96f5ae1ea..0b4c1ce1e5 100644 --- a/static/js/components/email/hoc.js +++ b/static/js/components/email/hoc.js @@ -68,11 +68,9 @@ export const withEmailDialog = R.curry( } } - updateEmailFieldEdit = R.curry( - (fieldName, e): void => { - this.saveEmailChanges(fieldName, e.target.value) - } - ) + updateEmailFieldEdit = R.curry((fieldName, e): void => { + this.saveEmailChanges(fieldName, e.target.value) + }) updateEmailBody = (editorState: EditorState): void => { this.saveEmailChanges( diff --git a/static/js/components/email/lib.js b/static/js/components/email/lib.js index f6d4185dfd..77a198be0b 100644 --- a/static/js/components/email/lib.js +++ b/static/js/components/email/lib.js @@ -1,3 +1,4 @@ +// @flow import React from "react" import Grid from "@material-ui/core/Grid" import R from "ramda" @@ -40,6 +41,7 @@ export const renderFilterOptions = R.map(filter => { } if ( + // $FlowFixMe R.or(isLocation(labelKey), isTranslated) && filter.value in translations ) { @@ -106,11 +108,12 @@ export const SEARCH_RESULT_EMAIL_CONFIG: EmailConfig = { emailSendParams: (emailState, searchkit) => [ emailState.inputs.subject || "", emailState.inputs.body || "", + // $FlowFixMe searchkit.buildQuery().query, emailState.inputs.sendAutomaticEmails || false ], - renderRecipients: (filters?: Array) => { + renderRecipients: (filters: ?Array) => { if (!filters || filters.length <= 0) { return null } @@ -140,7 +143,10 @@ export const LEARNER_EMAIL_CONFIG: EmailConfig = { {`${activeEmail.subheading} {activeEmail.subheading} @@ -171,6 +177,7 @@ export const convertEmailEdit = mapObj(([k, v]) => [ v ]) +// $FlowFixMe export const findFilters = tree => { // eslint-disable-next-line no-prototype-builtins if (tree.hasOwnProperty("term") && !tree.term.hasOwnProperty("program.id")) { @@ -193,6 +200,7 @@ export const findFilters = tree => { } const serializeValue = (value: Object | string) => + // $FlowFixMe _.isObject(value) ? `${value.gte} - ${value.lte}` : value export const getFilters = (root: Object) => { @@ -205,12 +213,9 @@ export const getFilters = (root: Object) => { } export const AUTOMATIC_EMAIL_ADMIN_CONFIG: EmailConfig = { - title: "Edit Email Campaign", - editEmail: actions.automaticEmails.patch, - emailSendParams: R.compose( - convertEmailEdit, - R.prop("inputs") - ), + title: "Edit Email Campaign", + editEmail: actions.automaticEmails.patch, + emailSendParams: R.compose(convertEmailEdit, R.prop("inputs")), supportBulkEmails: true, emailOpenParams: (emailOpenParams: AutomaticEmail) => ({ @@ -223,7 +228,7 @@ export const AUTOMATIC_EMAIL_ADMIN_CONFIG: EmailConfig = { filters: getFilters(emailOpenParams.query.original_query.post_filter) }), - renderRecipients: (filters?: Array) => { + renderRecipients: (filters: ?Array) => { if (!filters || filters.length <= 0) { return null } diff --git a/static/js/components/email/test_constants.js b/static/js/components/email/test_constants.js index 9fa1fd464c..bcde47541e 100644 --- a/static/js/components/email/test_constants.js +++ b/static/js/components/email/test_constants.js @@ -1,3 +1,4 @@ +// @flow import React from "react" import { INITIAL_EMAIL_STATE } from "../../reducers/email" import type { EmailState } from "../../flow/emailTypes" diff --git a/static/js/components/inputs/CountrySelectField.js b/static/js/components/inputs/CountrySelectField.js index fb34aad169..fb7f7defd3 100644 --- a/static/js/components/inputs/CountrySelectField.js +++ b/static/js/components/inputs/CountrySelectField.js @@ -50,13 +50,8 @@ export default class CountrySelectField extends React.Component { } onChange = (selection: Option): void => { - const { - stateKeySet, - countryKeySet, - updateProfile, - validator, - profile - } = this.props + const { stateKeySet, countryKeySet, updateProfile, validator, profile } = + this.props // clear state field when country field changes const clone = _.cloneDeep(profile) _.set(clone, stateKeySet, null) diff --git a/static/js/components/inputs/inputs_test.js b/static/js/components/inputs/inputs_test.js index be2ab20050..2f070b954d 100644 --- a/static/js/components/inputs/inputs_test.js +++ b/static/js/components/inputs/inputs_test.js @@ -286,10 +286,7 @@ describe("Profile inputs", () => { inputProps.profile.country_key = "US" inputProps.profile.state_key = "US-MA" const countryField = renderCountrySelect() - countryField - .find(SelectField) - .props() - .onChange({ value: "AL" }) + countryField.find(SelectField).props().onChange({ value: "AL" }) assert.equal(inputProps.profile.country_key, "AL") assert.equal(inputProps.profile.state_key, null) }) diff --git a/static/js/components/search/CustomResetFiltersDisplay_test.js b/static/js/components/search/CustomResetFiltersDisplay_test.js index 9594402592..42f2100875 100644 --- a/static/js/components/search/CustomResetFiltersDisplay_test.js +++ b/static/js/components/search/CustomResetFiltersDisplay_test.js @@ -49,13 +49,7 @@ describe("CustomResetFiltersDisplay", () => { } }) const wrapper = renderFilters() - assert.equal( - wrapper - .children() - .children() - .text(), - "Clear all filters" - ) + assert.equal(wrapper.children().children().text(), "Clear all filters") }) it("reset filter link does not render when hasFilters is false", () => { diff --git a/static/js/components/search/FilterVisibilityToggle.js b/static/js/components/search/FilterVisibilityToggle.js index ae309a8e62..330e447f69 100644 --- a/static/js/components/search/FilterVisibilityToggle.js +++ b/static/js/components/search/FilterVisibilityToggle.js @@ -100,11 +100,8 @@ export default class FilterVisibilityToggle extends SearchkitComponent { } toggleFilterVisibility = (): void => { - const { - filterName, - checkFilterVisibility, - setFilterVisibility - } = this.props + const { filterName, checkFilterVisibility, setFilterVisibility } = + this.props setFilterVisibility(filterName, !checkFilterVisibility(filterName)) } diff --git a/static/js/components/search/FinalGradeRangeFilter.js b/static/js/components/search/FinalGradeRangeFilter.js index 76f3b0c1a2..d151b5e443 100644 --- a/static/js/components/search/FinalGradeRangeFilter.js +++ b/static/js/components/search/FinalGradeRangeFilter.js @@ -84,9 +84,8 @@ class FinalGradeRangeAccessor extends NestedAccessorMixin( * Gets the appropriate range bucket for this element's agg query. */ getRangeBucket(query) { - const otherAppliedFiltersOnPath = this.createFilterForOtherElementsOnPath( - query - ) + const otherAppliedFiltersOnPath = + this.createFilterForOtherElementsOnPath(query) const rangeBucket = this.createInnerRangeBucket() if (otherAppliedFiltersOnPath) { return FilterBucket(this.key, otherAppliedFiltersOnPath, rangeBucket) diff --git a/static/js/components/search/LearnerResult_test.js b/static/js/components/search/LearnerResult_test.js index 0f0eb396bd..77e41b6923 100644 --- a/static/js/components/search/LearnerResult_test.js +++ b/static/js/components/search/LearnerResult_test.js @@ -87,9 +87,7 @@ describe("LearnerResult", () => { }) it("should include the user's location for US residence", () => { - const result = renderLearnerResult() - .find(".learner-location") - .find("span") + const result = renderLearnerResult().find(".learner-location").find("span") assert.include(result.text(), USER_PROFILE_RESPONSE.city) assert.include(result.text(), USER_PROFILE_RESPONSE.country) assert.include(result.text(), USER_PROFILE_RESPONSE.state_or_territory) @@ -128,9 +126,7 @@ describe("LearnerResult", () => { it("should not include the user's current program grade if the searching user is not staff", () => { SETTINGS.roles = [] assert.isFalse( - renderLearnerResult() - .find(".learner-grade .percent") - .exists() + renderLearnerResult().find(".learner-grade .percent").exists() ) }) @@ -165,10 +161,7 @@ describe("LearnerResult", () => { it("should use the small avatar", () => { const result = renderLearnerResult() assert.isTrue( - result - .find(".learner-avatar") - .find(ProfileImage) - .props().useSmall + result.find(".learner-avatar").find(ProfileImage).props().useSmall ) }) diff --git a/static/js/components/search/ModifiedSelectedFilter.js b/static/js/components/search/ModifiedSelectedFilter.js index 1a03bcfbcb..5370a47218 100644 --- a/static/js/components/search/ModifiedSelectedFilter.js +++ b/static/js/components/search/ModifiedSelectedFilter.js @@ -1,3 +1,4 @@ +// @flow import React from "react" import R from "ramda" import _ from "lodash" @@ -10,8 +11,8 @@ export default class ModifiedSelectedFilter extends React.Component { props: { labelKey: string, labelValue: string, - removeFilters: Function, - bemBlocks?: any, + removeFilter: Function, + bemBlocks: any, filterId: string } diff --git a/static/js/components/search/MultiSelectCheckboxItemList.js b/static/js/components/search/MultiSelectCheckboxItemList.js index 5ab587e38c..61913d90ce 100644 --- a/static/js/components/search/MultiSelectCheckboxItemList.js +++ b/static/js/components/search/MultiSelectCheckboxItemList.js @@ -132,10 +132,7 @@ export default class MultiSelectCheckboxItemList extends SearchkitComponent { return (
{[this.allAction(this.allItemsSelected()), ...this.itemComponentList()]}
diff --git a/static/js/components/search/NestedAggregatingMenuFilter.js b/static/js/components/search/NestedAggregatingMenuFilter.js index 00448e9edd..81ea88ebf0 100644 --- a/static/js/components/search/NestedAggregatingMenuFilter.js +++ b/static/js/components/search/NestedAggregatingMenuFilter.js @@ -102,9 +102,8 @@ export class NestedAggregatingFacetAccessor extends NestedAccessorMixin( * Gets the appropriate terms bucket for this element's agg query. */ getTermsBucket(query) { - const otherAppliedFiltersOnPath = this.createFilterForOtherElementsOnPath( - query - ) + const otherAppliedFiltersOnPath = + this.createFilterForOtherElementsOnPath(query) const termsKey = otherAppliedFiltersOnPath ? INNER_TERMS_AGG_KEY : this.key const termsBucket = ReverseNestedTermsBucket( termsKey, diff --git a/static/js/components/search/util.js b/static/js/components/search/util.js index 433c26684a..f3bf733efe 100644 --- a/static/js/components/search/util.js +++ b/static/js/components/search/util.js @@ -1,3 +1,4 @@ +// @flow import _ from "lodash" import R from "ramda" import { NestedQuery, BoolMust } from "searchkit" @@ -8,8 +9,9 @@ import { NestedQuery, BoolMust } from "searchkit" * is applied, or undefined if no filter is applied. */ export const getAppliedFilterValue = ( + // $FlowFixMe appliedFilterValue -): string | Object | undefined => { +): string | Object | void => { if (appliedFilterValue !== undefined) { if (_.isPlainObject(appliedFilterValue)) { // If the filter value is an object, it can be returned as it is. @@ -33,6 +35,7 @@ export const getAppliedFilterValue = ( * * @param {class} BaseSearchkitAccessorClass - A Searchkit accessor class (eg: FacetAccessor, RangeAccessor) */ +// $FlowFixMe export const NestedAccessorMixin = BaseSearchkitAccessorClass => class extends BaseSearchkitAccessorClass { /** @@ -40,6 +43,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * If a filter has been applied for this element, we need to build the shared query as normal, then alter the * values for some Searchkit internals in order to produce the correct query for ES. */ + // $FlowFixMe buildSharedQuery(query) { if (!this.shouldApplyFilter()) { return query @@ -78,6 +82,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * documents as 'OR' filters, and the changes to 'query.index.filters' and 'query.index.filtersMap' * are being made in order to treat those filters as 'AND'. */ + // $FlowFixMe amendSharedQueryForNestedDoc(query, filterToAdd) { const groupedNestedFilter = this.createGroupedNestedFilter( query, @@ -101,15 +106,11 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => filtersMap[nestedPath] = groupedNestedFilter // If it exists, delete the key for this specific filter (since all filters on this path are grouped together). const oldKey = this.getFilterMapKey() + // $FlowFixMe const matcher = matchFieldName(oldKey) filtersMap = R.compose( R.fromPairs, - R.reject( - R.compose( - matcher, - R.view(R.lensIndex(0)) - ) - ), + R.reject(R.compose(matcher, R.view(R.lensIndex(0)))), R.toPairs )(filtersMap) @@ -133,6 +134,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * } * } */ + // $FlowFixMe createGroupedNestedFilter(query, filterToAdd) { const nestedPath = this.getNestedPath() const appliedFiltersOnPath = query.getFiltersWithKeys([nestedPath]) @@ -157,6 +159,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * to this element's nested path, and (b) filters on this element's nested path minus the filter for this specific * element. */ + // $FlowFixMe createAggFilter(query) { const filters = [] const nestedPath = this.getNestedPath() @@ -164,9 +167,8 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => if (unrelatedFilters) { filters.push(unrelatedFilters) } - const otherAppliedFiltersOnPath = this.createFilterForOtherElementsOnPath( - query - ) + const otherAppliedFiltersOnPath = + this.createFilterForOtherElementsOnPath(query) if (otherAppliedFiltersOnPath) { filters.push(NestedQuery(nestedPath, otherAppliedFiltersOnPath)) } @@ -182,6 +184,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * {'term': {'program.courses.payment_status': 'Paid'} * ] */ + // $FlowFixMe getAllFiltersOnPath(query) { const nestedPath = this.getNestedPath() const appliedNestedFilters = query.getFiltersWithKeys(nestedPath) @@ -212,6 +215,7 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => * } * } */ + // $FlowFixMe createFilterForOtherElementsOnPath(query) { const allFilters = this.getAllFiltersOnPath(query) let otherFilters = [] @@ -233,11 +237,9 @@ export const NestedAccessorMixin = BaseSearchkitAccessorClass => } // Accept keys that start with the given prefix and end with numbers -export const matchFieldName: ( - resultIdPrefix: string, - key: string -) => boolean = R.curry( - (resultIdPrefix: string, key: string) => - key.startsWith(resultIdPrefix) && - !isNaN(key.substring(resultIdPrefix.length)) -) +export const matchFieldName: (resultIdPrefix: string, key: string) => boolean = + R.curry( + (resultIdPrefix: string, key: string) => + key.startsWith(resultIdPrefix) && + !isNaN(key.substring(resultIdPrefix.length)) + ) diff --git a/static/js/constants_test.js b/static/js/constants_test.js index 39ed175c16..fb2eab6e45 100644 --- a/static/js/constants_test.js +++ b/static/js/constants_test.js @@ -1,3 +1,4 @@ +// @flow /* eslint-disable no-unused-vars */ import _ from "lodash" import { assert } from "chai" diff --git a/static/js/containers/App.js b/static/js/containers/App.js index 78e760cedf..aa268350dc 100644 --- a/static/js/containers/App.js +++ b/static/js/containers/App.js @@ -215,6 +215,7 @@ class App extends React.Component { } return ( + // $FlowFixMe
{icon} diff --git a/static/js/containers/AutomaticEmailPage.js b/static/js/containers/AutomaticEmailPage.js index d95c56ae88..55f1dd1a41 100644 --- a/static/js/containers/AutomaticEmailPage.js +++ b/static/js/containers/AutomaticEmailPage.js @@ -29,10 +29,7 @@ const noEmailsMessage = () => ( const emptyMessage = automaticEmails => fetchingEmail(automaticEmails) ? : noEmailsMessage() -const notEmpty = R.compose( - R.not, - R.isEmpty -) +const notEmpty = R.compose(R.not, R.isEmpty) type AutomaticEmailsType = RestState> & { emailsInFlight: Set diff --git a/static/js/containers/CopyLinkDialog.js b/static/js/containers/CopyLinkDialog.js index 5292d15f9d..398087283e 100644 --- a/static/js/containers/CopyLinkDialog.js +++ b/static/js/containers/CopyLinkDialog.js @@ -1,3 +1,4 @@ +// @flow import React from "react" import { createSimpleActionHelpers } from "../lib/redux" import { @@ -12,6 +13,7 @@ import DialogContent from "@material-ui/core/DialogContent" import DialogContentText from "@material-ui/core/DialogContentText" class CopyLinkDialog extends React.Component { + input: any props: { open: boolean, setShareDialogVisibility: (b: boolean) => void, @@ -62,6 +64,7 @@ class CopyLinkDialog extends React.Component { value={recordShareLink} /> + {/* $FlowFixMe */} {document.queryCommandSupported("copy") && (