diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 1300a1d6d..198aa4887 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -25,7 +25,6 @@ env: settings: react: version: 'detect' - flowVersion: '0.92' overrides: - files: ['*.test.js', 'cypress/integration/**/*.js'] @@ -39,7 +38,6 @@ extends: - eslint:recommended - plugin:react/recommended - prettier - - prettier/flowtype - prettier/react parserOptions: ecmaFeatures: diff --git a/.gitignore b/.gitignore index ded3eb6cb..e6db162e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,5 @@ dist/ .DS_Store package-lock.json yarn-error.log -flow-typed/ cypress/videos cypress/screenshots diff --git a/babel.config.js b/babel.config.js index a6735e70b..1ea088270 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,8 +9,7 @@ module.exports = { } ], '@babel/react', - '@babel/preset-typescript', - '@babel/flow' + '@babel/preset-typescript' ], plugins: [ '@babel/plugin-proposal-class-properties', diff --git a/package.json b/package.json index 989b6dd43..1cedd3253 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "@babel/plugin-proposal-optional-chaining": "^7.0.0", "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/preset-env": "^7.6.3", - "@babel/preset-flow": "^7.0.0-beta.51", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.6.0", "@types/classnames": "^2.2.9", @@ -113,6 +112,7 @@ "@types/react-color": "^3.0.1", "@types/react-dom": "^16.9.3", "@types/react-helmet": "^5.0.13", + "@types/react-highlight-words": "^0.16.0", "@types/react-redux": "^7.1.5", "@types/react-router": "^5.1.2", "@types/react-router-dom": "^5.1.0", diff --git a/source/actions/actions.ts b/source/actions/actions.ts index 6087e0893..33c81d739 100644 --- a/source/actions/actions.ts +++ b/source/actions/actions.ts @@ -20,7 +20,7 @@ export type Action = | UpdateDefaultUnit | SetActiveTargetAction -type ThunkResult = ThunkAction< +export type ThunkResult = ThunkAction< R, RootState, { history: History; sitePaths: SitePaths }, @@ -74,7 +74,7 @@ export const goToQuestion = (question: string) => export const validateStepWithValue = ( dottedName: DottedName, - value: any + value: unknown ): ThunkResult => dispatch => { dispatch(updateSituation(dottedName, value)) dispatch({ @@ -119,7 +119,7 @@ export const deletePreviousSimulation = (): ThunkResult => dispatch => { deletePersistedSimulation() } -export const updateSituation = (fieldName: DottedName, value: any) => +export const updateSituation = (fieldName: DottedName, value: unknown) => ({ type: 'UPDATE_SITUATION', fieldName, diff --git a/source/actions/companyStatusActions.js b/source/actions/companyStatusActions.js deleted file mode 100644 index 845ee6b73..000000000 --- a/source/actions/companyStatusActions.js +++ /dev/null @@ -1,88 +0,0 @@ - -import { dropWhile, last } from 'ramda' -import { nextQuestionUrlSelector } from 'Selectors/companyStatusSelectors' -import type { - IsSoleProprietorshipAction, - CompanyHasMultipleAssociatesAction, - DirectorStatus, - IsAutoentrepreneurAction, - ResetCompanyStatusAction, - DirectorIsInAMinorityAction, - DefineDirectorStatusAction -} from 'Types/companyTypes' -import type { Thunk } from 'Types/ActionsTypes' - -// Bug : last et dropline sont automatiquement enlevé par le formatOnSave de visual studio code sinon -// eslint-disable-next-line -let x = [dropWhile, last] - -const thenGoToNextQuestion = actionCreator => (...args: any) => - ((dispatch, getState, { history, sitePaths }) => { - dispatch(actionCreator(...args)) - history.push(nextQuestionUrlSelector(getState(), { sitePaths })) - }: Thunk) - -export const isSoleProprietorship = thenGoToNextQuestion( - (isSoleProprietorship: ?boolean): IsSoleProprietorshipAction => ({ - type: 'COMPANY_IS_SOLE_PROPRIETORSHIP', - isSoleProprietorship -}) -) - -export const defineDirectorStatus = thenGoToNextQuestion( - (status: ?DirectorStatus): DefineDirectorStatusAction => ({ - type: 'DEFINE_DIRECTOR_STATUS', - status -}) -) - -export const companyHasMultipleAssociates = thenGoToNextQuestion( - (multipleAssociates: ?boolean): CompanyHasMultipleAssociatesAction => ({ - type: 'COMPANY_HAS_MULTIPLE_ASSOCIATES', - multipleAssociates -}) -) - -export const isAutoentrepreneur = thenGoToNextQuestion( - (autoEntrepreneur: ?boolean): IsAutoentrepreneurAction => ({ - type: 'COMPANY_IS_MICROENTERPRISE', - autoEntrepreneur -}) -) - -export const directorIsInAMinority = thenGoToNextQuestion( - (minorityDirector: ?boolean): DirectorIsInAMinorityAction => ({ - type: 'SPECIFY_DIRECTORS_SHARE', - minorityDirector -}) -) - -export const goToCompanyStatusChoice = (): Thunk => ( - dispatch, - _, - {history, sitePaths} - ) => { - dispatch( - ({ - type: 'RESET_COMPANY_STATUS_CHOICE' - }: ResetCompanyStatusAction) -) -history.push(sitePaths.créer.index) -} - -export const resetCompanyStatusChoice = ( -from: string -): Thunk => (dispatch, getState) => { - const answeredQuestion = Object.keys( - getState().inFranceApp.companyLegalStatus - ) - const answersToReset = dropWhile(a => a !== from, answeredQuestion) - if (!answersToReset.length) { - return - } - dispatch({ - type: 'RESET_COMPANY_STATUS_CHOICE', - answersToReset - }) - } - \ No newline at end of file diff --git a/source/actions/companyStatusActions.ts b/source/actions/companyStatusActions.ts new file mode 100644 index 000000000..91f335b26 --- /dev/null +++ b/source/actions/companyStatusActions.ts @@ -0,0 +1,81 @@ +import { dropWhile } from 'ramda' +import { nextQuestionUrlSelector } from 'Selectors/companyStatusSelectors' + +const thenGoToNextQuestion = actionCreator => (...args: unknown[]) => ( + dispatch, + getState, + { history, sitePaths } +) => { + dispatch(actionCreator(...args)) + history.push(nextQuestionUrlSelector(getState(), { sitePaths })) +} + +export const isSoleProprietorship = thenGoToNextQuestion( + (isSoleProprietorship?: boolean) => + ({ + type: 'COMPANY_IS_SOLE_PROPRIETORSHIP', + isSoleProprietorship + } as const) +) + +type DirectorStatus = 'SALARIED' | 'SELF_EMPLOYED' + +export const defineDirectorStatus = thenGoToNextQuestion( + (status: DirectorStatus) => + ({ + type: 'DEFINE_DIRECTOR_STATUS', + status + } as const) +) + +export const companyHasMultipleAssociates = thenGoToNextQuestion( + (multipleAssociates?: boolean) => + ({ + type: 'COMPANY_HAS_MULTIPLE_ASSOCIATES', + multipleAssociates + } as const) +) + +export const isAutoentrepreneur = thenGoToNextQuestion( + (autoEntrepreneur?: boolean) => + ({ + type: 'COMPANY_IS_MICROENTERPRISE', + autoEntrepreneur + } as const) +) + +export const directorIsInAMinority = thenGoToNextQuestion( + (minorityDirector?: boolean) => + ({ + type: 'SPECIFY_DIRECTORS_SHARE', + minorityDirector + } as const) +) + +export const goToCompanyStatusChoice = () => ( + dispatch, + _, + { history, sitePaths } +) => { + dispatch({ + type: 'RESET_COMPANY_STATUS_CHOICE' + } as const) + history.push(sitePaths.créer.index) +} + +export const resetCompanyStatusChoice = (from: string) => ( + dispatch, + getState +) => { + const answeredQuestion = Object.keys( + getState().inFranceApp.companyLegalStatus + ) + const answersToReset = dropWhile(a => a !== from, answeredQuestion) + if (!answersToReset.length) { + return + } + dispatch({ + type: 'RESET_COMPANY_STATUS_CHOICE', + answersToReset + }) +} diff --git a/source/components/PaySlip.js b/source/components/PaySlip.js deleted file mode 100644 index cdbde0bfd..000000000 --- a/source/components/PaySlip.js +++ /dev/null @@ -1,138 +0,0 @@ -import type { FicheDePaie } from 'Types/ResultViewTypes' -import withColours from 'Components/utils/withColours' -import Value from 'Components/Value' -import { findRuleByDottedName, getRuleFromAnalysis } from 'Engine/rules' -import { compose } from 'ramda' -import React, { Fragment } from 'react' -import { Trans } from 'react-i18next' -import { connect } from 'react-redux' -import { - analysisWithDefaultsSelector, - parsedRulesSelector -} from 'Selectors/analyseSelectors' -import { analysisToCotisationsSelector } from 'Selectors/ficheDePaieSelectors' -import './PaySlip.css' -import { Line, SalaireBrutSection, SalaireNetSection } from './PaySlipSections' -import RuleLink from './RuleLink' - -type ConnectedPropTypes = ?FicheDePaie & { - colours: { lightestColour: string } -} - -export default compose( - withColours, - connect(state => ({ - cotisations: analysisToCotisationsSelector(state), - analysis: analysisWithDefaultsSelector(state), - parsedRules: parsedRulesSelector(state) - })) -)( - ({ - colours: { lightestColour }, - cotisations, - analysis, - parsedRules - }: ConnectedPropTypes) => { - let getRule = getRuleFromAnalysis(analysis) - - const heuresSupplémentaires = getRule( - 'contrat salarié . temps de travail . heures supplémentaires' - ) - return ( -
-
- - {heuresSupplémentaires?.nodeValue > 0 && ( - - )} -
- - - {/* Section cotisations */} -
-

- Cotisations sociales -

-

- Part employeur -

-

- Part salarié -

- {cotisations.map(([brancheDottedName, cotisationList]) => { - let branche = findRuleByDottedName(parsedRules, brancheDottedName) - return ( - -
- -
- {cotisationList.map(cotisation => ( - - - - {cotisation.montant.partPatronale} - - - {cotisation.montant.partSalariale} - - - ))} -
- ) - })} - - {/* Total cotisation */} -
- Total des retenues -
- - - {/* Salaire chargé */} - - -
- {/* Section salaire net */} - -
- ) - } -) diff --git a/source/components/PaySlip.tsx b/source/components/PaySlip.tsx new file mode 100644 index 000000000..ca91041cc --- /dev/null +++ b/source/components/PaySlip.tsx @@ -0,0 +1,122 @@ +import { ThemeColoursContext } from 'Components/utils/withColours' +import Value from 'Components/Value' +import { findRuleByDottedName, getRuleFromAnalysis } from 'Engine/rules' +import React, { Fragment, useContext } from 'react' +import { Trans } from 'react-i18next' +import { useSelector } from 'react-redux' +import { + analysisWithDefaultsSelector, + parsedRulesSelector +} from 'Selectors/analyseSelectors' +import { analysisToCotisationsSelector } from 'Selectors/ficheDePaieSelectors' +import './PaySlip.css' +import { Line, SalaireBrutSection, SalaireNetSection } from './PaySlipSections' +import RuleLink from './RuleLink' + +export default function PaySlip() { + const { lightestColour } = useContext(ThemeColoursContext) + const cotisations = useSelector(analysisToCotisationsSelector) + const analysis = useSelector(analysisWithDefaultsSelector) + const parsedRules = useSelector(parsedRulesSelector) + let getRule = getRuleFromAnalysis(analysis) + + const heuresSupplémentaires = getRule( + 'contrat salarié . temps de travail . heures supplémentaires' + ) + return ( +
+
+ + {heuresSupplémentaires?.nodeValue > 0 && ( + + )} +
+ + + {/* Section cotisations */} +
+

+ Cotisations sociales +

+

+ Part employeur +

+

+ Part salarié +

+ {cotisations.map(([brancheDottedName, cotisationList]) => { + let branche = findRuleByDottedName(parsedRules, brancheDottedName) + return ( + +
+ +
+ {cotisationList.map(cotisation => ( + + + + {cotisation.montant.partPatronale} + + + {cotisation.montant.partSalariale} + + + ))} +
+ ) + })} + + {/* Total cotisation */} +
+ Total des retenues +
+ + + {/* Salaire chargé */} + + +
+ {/* Section salaire net */} + +
+ ) +} diff --git a/source/components/QuickLinks.tsx b/source/components/QuickLinks.tsx index bd291b530..bbcee1971 100644 --- a/source/components/QuickLinks.tsx +++ b/source/components/QuickLinks.tsx @@ -1,6 +1,6 @@ import { goToQuestion } from 'Actions/actions' import { T } from 'Components' -import { compose, contains, filter, reject, toPairs } from 'ramda' +import { contains, filter, pipe, reject, toPairs } from 'ramda' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { RootState } from 'Reducers/rootReducer' @@ -8,6 +8,7 @@ import { currentQuestionSelector, nextStepsSelector } from 'Selectors/analyseSelectors' +import { DottedName } from 'Types/rule' export default function QuickLinks() { const currentQuestion = useSelector(currentQuestionSelector) @@ -23,11 +24,11 @@ export default function QuickLinks() { if (!quickLinks) { return null } - const links = compose( - toPairs, - filter(dottedName => contains(dottedName, nextSteps)) as any, - reject(dottedName => contains(dottedName, quickLinksToHide)) - )(quickLinks) as any + const links = pipe( + reject((dottedName: DottedName) => contains(dottedName, quickLinksToHide)), + filter((dottedName: DottedName) => contains(dottedName, nextSteps)), + toPairs + )(quickLinks) return ( !!links.length && ( diff --git a/source/components/RuleLink.tsx b/source/components/RuleLink.tsx index 23f41d6bb..427deba1e 100644 --- a/source/components/RuleLink.tsx +++ b/source/components/RuleLink.tsx @@ -10,7 +10,7 @@ type RuleLinkProps = { dottedName: Rule['dottedName'] title?: Rule['title'] style?: React.CSSProperties - children: React.ReactNode + children?: React.ReactNode } export default function RuleLink({ diff --git a/source/components/RulePage.tsx b/source/components/RulePage.tsx index d19940c6d..97e9840e5 100644 --- a/source/components/RulePage.tsx +++ b/source/components/RulePage.tsx @@ -39,7 +39,7 @@ export default function RulePage({ match }) { if (!findRuleByDottedName(flatRules, decodedRuleName)) return - return renderRule(decodedRuleName) + return renderRule(decodedRuleName as DottedName) } const BackToSimulation = connect(null, { goBackToSimulation })( diff --git a/source/components/SearchBar.js b/source/components/SearchBar.tsx similarity index 92% rename from source/components/SearchBar.js rename to source/components/SearchBar.tsx index 60e47b1db..908b512a6 100644 --- a/source/components/SearchBar.js +++ b/source/components/SearchBar.tsx @@ -5,16 +5,23 @@ import React, { useContext, useEffect, useState } from 'react' import Highlighter from 'react-highlight-words' import { useTranslation } from 'react-i18next' import { Link, Redirect } from 'react-router-dom' +import { Rule } from 'Types/rule' import Worker from 'worker-loader!./SearchBar.worker.js' import { capitalise0 } from '../utils' const worker = new Worker() +type SearchBarProps = { + rules: Array + showDefaultList: boolean + finally?: () => void +} + export default function SearchBar({ rules, showDefaultList, finally: finallyCallback -}) { +}: SearchBarProps) { const sitePaths = useContext(SitePathsContext) const [input, setInput] = useState('') const [selectedOption, setSelectedOption] = useState(null) @@ -31,7 +38,7 @@ export default function SearchBar({ worker.onmessage = ({ data: results }) => setResults(results) }, [rules]) - let renderOptions = rules => { + let renderOptions = (rules?: Array) => { let options = (rules && sortBy(rule => rule.dottedName, rules)) || take(5)(results) return
    {options.map(option => renderOption(option))}
@@ -57,7 +64,7 @@ export default function SearchBar({ >
( - -

- {emoji('📋 ')} - Mes réponses - - {emoji('🗑')}{' '} - - -

- -

- {emoji('🔮 ')} - Prochaines questions -

- -
-) - -let StepsTable = ({ rules, onClose, goToQuestion }) => ( - - - {rules.map(rule => ( - - - - - ))} - -
- - - {' '} -
-) - -const stepsToRules = createSelector( - state => state.conversationSteps.foldedSteps, - nextStepsSelector, - analysisWithDefaultsSelector, - (folded, nextSteps, analysis) => ({ - folded: folded - .map(softCatch(getRuleFromAnalysis(analysis))) - .filter(Boolean), - next: nextSteps - .map(softCatch(getRuleFromAnalysis(analysis))) - .filter(Boolean) - }) -) - -export default compose( - connect( - state => stepsToRules(state), - { - resetSimulation, - goToQuestion - } - ) -)(AnswerList) diff --git a/source/components/conversation/AnswerList.tsx b/source/components/conversation/AnswerList.tsx new file mode 100644 index 000000000..fc10b007b --- /dev/null +++ b/source/components/conversation/AnswerList.tsx @@ -0,0 +1,112 @@ +import { goToQuestion, resetSimulation } from 'Actions/actions' +import Overlay from 'Components/Overlay' +import RuleLink from 'Components/RuleLink' +import Value from 'Components/Value' +import { getRuleFromAnalysis } from 'Engine/rules' +import React from 'react' +import emoji from 'react-easy-emoji' +import { Trans } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from 'Reducers/rootReducer' +import { createSelector } from 'reselect' +import { + analysisWithDefaultsSelector, + nextStepsSelector +} from 'Selectors/analyseSelectors' +import { softCatch } from '../../utils' +import './AnswerList.css' + +export default function AnswerList({ onClose }) { + const dispatch = useDispatch() + const { folded, next } = useSelector(stepsToRules) + return ( + +

+ {emoji('📋 ')} + Mes réponses + + {emoji('🗑')}{' '} + + +

+ +

+ {emoji('🔮 ')} + Prochaines questions +

+ +
+ ) +} + +function StepsTable({ rules, onClose }) { + const dispatch = useDispatch() + return ( + + + {rules.map(rule => ( + + + + + ))} + +
+ + + {' '} +
+ ) +} + +const stepsToRules = createSelector( + (state: RootState) => state.conversationSteps.foldedSteps, + nextStepsSelector, + analysisWithDefaultsSelector, + (folded, nextSteps, analysis) => ({ + folded: folded + .map(softCatch(getRuleFromAnalysis(analysis))) + .filter(Boolean), + next: nextSteps + .map(softCatch(getRuleFromAnalysis(analysis))) + .filter(Boolean) + }) +) diff --git a/source/components/conversation/InputSuggestions.tsx b/source/components/conversation/InputSuggestions.tsx index 5c2a8f872..34f253846 100644 --- a/source/components/conversation/InputSuggestions.tsx +++ b/source/components/conversation/InputSuggestions.tsx @@ -3,14 +3,21 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { defaultUnitsSelector } from 'Selectors/analyseSelectors' -import { convertUnit, parseUnit } from '../../engine/units' +import { convertUnit, parseUnit, Unit } from '../../engine/units' + +type InputSuggestionsProps = { + suggestions: Record + onFirstClick: (val: number) => void + onSecondClick?: (val: number) => void + unit: Unit +} export default function InputSuggestions({ suggestions, onSecondClick = x => x, onFirstClick, unit -}) { +}: InputSuggestionsProps) { const [suggestion, setSuggestion] = useState(null) const { t } = useTranslation() const defaultUnit = parseUnit(useSelector(defaultUnitsSelector)[0]) diff --git a/source/engine/generateQuestions.js b/source/engine/generateQuestions.ts similarity index 95% rename from source/engine/generateQuestions.js rename to source/engine/generateQuestions.ts index 8750a4cf8..0168a8695 100644 --- a/source/engine/generateQuestions.js +++ b/source/engine/generateQuestions.ts @@ -53,7 +53,7 @@ export let getNextSteps = missingVariablesByTarget => { missingByTotalScore ), pairs = toPairs(missingByCompound), - sortedPairs = sortWith([descend(byCount), descend(byScore)], pairs) + sortedPairs = sortWith([descend(byCount), descend(byScore) as any], pairs) return map(head, sortedPairs) } diff --git a/source/engine/parseRule.js b/source/engine/parseRule.tsx similarity index 87% rename from source/engine/parseRule.js rename to source/engine/parseRule.tsx index bcd7fd0e3..0033e4471 100644 --- a/source/engine/parseRule.js +++ b/source/engine/parseRule.tsx @@ -15,11 +15,16 @@ export default (rules, rule, parsedRules) => { parsedRules[rule.dottedName] = 'being parsed' /* - The parseRule function will traverse the tree of the `rule` and produce an AST, an object containing other objects containing other objects... - Some of the attributes of the rule are dynamic, they need to be parsed. It is the case of `non applicable si`, `applicable si`, `formule`. - These attributes' values themselves may have mechanism properties (e. g. `barème`) or inline expressions (e. g. `maVariable + 3`). - These mechanisms or variables are in turn traversed by `parse()`. During this processing, 'evaluate' and'jsx' functions are attached to the objects of the AST. They will be evaluated during the evaluation phase, called "analyse". -*/ + The parseRule function will traverse the tree of the `rule` and produce an + AST, an object containing other objects containing other objects... Some of + the attributes of the rule are dynamic, they need to be parsed. It is the + case of `non applicable si`, `applicable si`, `formule`. These attributes' + values themselves may have mechanism properties (e. g. `barème`) or inline + expressions (e. g. `maVariable + 3`). These mechanisms or variables are in + turn traversed by `parse()`. During this processing, 'evaluate' and'jsx' + functions are attached to the objects of the AST. They will be evaluated + during the evaluation phase, called "analyse". + */ let parentDependencies = findParentDependencies(rules, rule) @@ -84,7 +89,7 @@ export default (rules, rule, parsedRules) => { let child = parse(rules, rule, parsedRules)(value) - let jsx = (nodeValue, explanation) => makeJsx(explanation) + let jsx = (_nodeValue, explanation) => makeJsx(explanation) return { evaluate, @@ -96,7 +101,7 @@ export default (rules, rule, parsedRules) => { explanation: child } }, - contrôles: map(control => { + contrôles: map((control: any) => { let testExpression = parse(rules, rule, parsedRules)(control.si) if ( !testExpression.explanation && @@ -118,7 +123,9 @@ export default (rules, rule, parsedRules) => { })(root) parsedRules[rule.dottedName] = { - // Pas de propriété explanation et jsx ici car on est parti du (mauvais) principe que 'non applicable si' et 'formule' sont particuliers, alors qu'ils pourraient être rangé avec les autres mécanismes + // Pas de propriété explanation et jsx ici car on est parti du (mauvais) + // principe que 'non applicable si' et 'formule' sont particuliers, alors + // qu'ils pourraient être rangé avec les autres mécanismes ...parsedRoot, evaluate, parsed: true, @@ -135,7 +142,7 @@ export default (rules, rule, parsedRules) => { const explanation = { ...node.explanation, isDisabledBy } return { ...node, explanation, nodeValue } }, - jsx: (nodeValue, { isDisabledBy }) => { + jsx: (_nodeValue, { isDisabledBy }) => { return ( isDisabledBy.length > 0 && ( <> @@ -183,6 +190,7 @@ let evolveCond = (name, rule, rules, parsedRules) => value => { classes="ruleProp mecanism cond" name={name} value={nodeValue} + unit={undefined} child={ explanation.category === 'variable' ? (
{makeJsx(explanation)}
diff --git a/source/engine/rules.js b/source/engine/rules.js index e334ba82b..868711e8c 100644 --- a/source/engine/rules.js +++ b/source/engine/rules.js @@ -46,7 +46,7 @@ export let enrichRule = rule => { if (defaultUnit && unit) { warning( dottedName, - "Le paramètre `unité` n'est plus contraignant que `unité par défaut`.", + 'Le paramètre `unité` est plus contraignant que `unité par défaut`.', 'Si vous souhaitez que la valeur de votre variable soit toujours la même unité, gardez `unité`' ) } diff --git a/source/engine/traverse.js b/source/engine/traverse.ts similarity index 85% rename from source/engine/traverse.js rename to source/engine/traverse.ts index b491af288..0ac4aa34c 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.ts @@ -1,6 +1,7 @@ import { evaluateControls } from 'Engine/controls' import parseRule from 'Engine/parseRule' import { chain, path } from 'ramda' +import { DottedName } from 'Types/rule' import { evaluateNode } from './evaluation' import { parseReference } from './parseReference' import { @@ -8,7 +9,7 @@ import { findRule, findRuleByDottedName } from './rules' -import { parseUnit } from './units' +import { parseUnit, Unit } from './units' /* Dans ce fichier, les règles YAML sont parsées. @@ -47,14 +48,17 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre */ export let parseAll = flatRules => { - /* First we parse each rule one by one. When a mechanism is encountered, it is recursively parsed. When a reference to a variable is encountered, a 'variable' node is created, we don't parse variables recursively. */ + /* First we parse each rule one by one. When a mechanism is encountered, it is + recursively parsed. When a reference to a variable is encountered, a + 'variable' node is created, we don't parse variables recursively. */ let parsedRules = {} - /* A rule `A` can disable a rule `B` using the rule `rend non applicable: B` in the definition of `A`. - We need to map these exonerations to be able to retreive them from `B` */ - let nonApplicableMapping = {} - let replacedByMapping = {} + /* A rule `A` can disable a rule `B` using the rule `rend non applicable: B` + in the definition of `A`. We need to map these exonerations to be able to + retreive them from `B` */ + let nonApplicableMapping: Record = {} + let replacedByMapping: Record = {} flatRules.forEach(rule => { const parsed = parseRule(flatRules, rule, parsedRules) if (parsed['rend non applicable']) { @@ -107,7 +111,7 @@ export let parseAll = flatRules => { export let getTargets = (target, rules) => { let multiSimulation = path(['simulateur', 'objectifs'])(target) - let targets = multiSimulation + let targets = Array.isArray(multiSimulation) ? // On a un simulateur qui définit une liste d'objectifs multiSimulation .map(n => disambiguateRuleReference(rules, target, n)) @@ -118,16 +122,23 @@ export let getTargets = (target, rules) => { return targets } -export let analyseMany = ( - parsedRules, - targetNames, - defaultUnits = [] -) => situationGate => { +type CacheMeta = { + contextRule: Array + defaultUnits: Array + inversionFail?: { + given: string + estimated: string + } +} + +export let analyseMany = (parsedRules, targetNames, defaultUnits = []) => ( + situationGate: (name: DottedName) => any +) => { // TODO: we should really make use of namespaces at this level, in particular // setRule in Rule.js needs to get smarter and pass dottedName defaultUnits = defaultUnits.map(parseUnit) let cache = { - _meta: { contextRule: [], defaultUnits } + _meta: { contextRule: [], defaultUnits } as CacheMeta } let parsedTargets = targetNames.map(t => { @@ -148,6 +159,8 @@ export let analyseMany = ( return { targets, cache, controls } } +export type Analysis = ReturnType> + export let analyse = (parsedRules, target, defaultUnits = []) => { return analyseMany(parsedRules, [target], defaultUnits) } diff --git a/source/reducers/rootReducer.ts b/source/reducers/rootReducer.ts index 841c13010..56063c033 100644 --- a/source/reducers/rootReducer.ts +++ b/source/reducers/rootReducer.ts @@ -1,13 +1,14 @@ import { Action } from 'Actions/actions' +import { Analysis } from 'Engine/traverse' import { areUnitConvertible, convertUnit, parseUnit } from 'Engine/units' import { - compose, defaultTo, dissoc, identity, lensPath, omit, over, + pipe, set, uniq, without @@ -18,7 +19,6 @@ import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors' import { SavedSimulation } from 'Selectors/storageSelectors' import { DottedName, Rule } from 'Types/rule' import i18n, { AvailableLangs } from '../i18n' -import { Unit } from './../engine/units' import inFranceAppReducer from './inFranceAppReducer' import storageRootReducer from './storageReducer' @@ -180,7 +180,7 @@ export type Simulation = { function simulation( state: Simulation = null, action: Action, - analysis: Record + analysis: Analysis | Array ): Simulation | null { if (action.type === 'SET_SIMULATION') { const { config, url } = action @@ -226,23 +226,27 @@ function simulation( return state } -const addAnswerToSituation = (dottedName: DottedName, value: any, state) => { - return (compose( - set(lensPath(['simulation', 'situation', dottedName]), value), +const addAnswerToSituation = ( + dottedName: DottedName, + value: unknown, + state: RootState +) => { + return pipe( over(lensPath(['conversationSteps', 'foldedSteps']), (steps = []) => uniq([...steps, dottedName]) - ) as any - ) as any)(state) + ), + set(lensPath(['simulation', 'situation', dottedName]), value) + )(state) } -const removeAnswerFromSituation = (dottedName: DottedName, state) => { - return (compose( - over(lensPath(['simulation', 'situation']), dissoc(dottedName)), - over( - lensPath(['conversationSteps', 'foldedSteps']), - without([dottedName]) - ) as any - ) as any)(state) +const removeAnswerFromSituation = ( + dottedName: DottedName, + state: RootState +) => { + return pipe( + over(lensPath(['conversationSteps', 'foldedSteps']), without([dottedName])), + over(lensPath(['simulation', 'situation']), dissoc(dottedName)) + )(state) } const existingCompanyRootReducer = (state: RootState, action) => { diff --git a/source/selectors/analyseSelectors.ts b/source/selectors/analyseSelectors.ts index 52338beca..b81590eb4 100644 --- a/source/selectors/analyseSelectors.ts +++ b/source/selectors/analyseSelectors.ts @@ -86,7 +86,7 @@ export const useTarget = (dottedName: DottedName) => { return targets && targets.find(t => t.dottedName === dottedName) } -export let noUserInputSelector = state => +export let noUserInputSelector = (state: RootState) => !Object.keys(situationSelector(state)).length export let firstStepCompletedSelector = createSelector( @@ -221,7 +221,7 @@ export let exampleAnalysisSelector = createSelector( analyseRule( rules, dottedName, - dottedName => situation[dottedName], + (dottedName: DottedName) => situation[dottedName], example.defaultUnits ) ) @@ -234,18 +234,19 @@ let makeAnalysisSelector = (situationSelector: SituationSelectorType) => situationSelector, defaultUnitsSelector ], - (parsedRules, targetNames, situations, defaultUnits) => - mapOrApply( + (parsedRules, targetNames, situations, defaultUnits) => { + return mapOrApply( situation => analyseMany( parsedRules, targetNames, defaultUnits - )(dottedName => { + )((dottedName: DottedName) => { return situation[dottedName] }), situations ) + } ) export let analysisWithDefaultsSelector = makeAnalysisSelector( diff --git a/source/selectors/ficheDePaieSelectors.js b/source/selectors/ficheDePaieSelectors.ts similarity index 75% rename from source/selectors/ficheDePaieSelectors.js rename to source/selectors/ficheDePaieSelectors.ts index 5dfc2165b..5617e0a81 100644 --- a/source/selectors/ficheDePaieSelectors.js +++ b/source/selectors/ficheDePaieSelectors.ts @@ -1,4 +1,4 @@ -/* @flow */ +import { Analysis } from 'Engine/traverse' import { add, concat, @@ -16,10 +16,20 @@ import { } from 'ramda' import { createSelector } from 'reselect' import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors' +import { Branch, Cotisation } from './repartitionSelectors' +// Used for type consistency +export const BLANK_COTISATION: Cotisation = { + montant: { + partPatronale: 0, + partSalariale: 0 + }, + unit: 'ERROR_SHOULD_BE_INSTANCIATED', + dottedName: 'ERROR_SHOULD_BE_INSTANCIATED' as any, + title: 'ERROR_SHOULD_BE_INSTANCIATED', + branche: 'protection sociale . autres' +} -// These functions help build the payslip. They take the cotisations from the cache, braving all the particularities of the current engine's implementation, handles the part patronale and part salariale, and gives a map by branch. - -export const COTISATION_BRANCHE_ORDER: Array = [ +export const COTISATION_BRANCHE_ORDER: Array = [ 'protection sociale . santé', 'protection sociale . accidents du travail et maladies professionnelles', 'protection sociale . retraite', @@ -30,50 +40,34 @@ export const COTISATION_BRANCHE_ORDER: Array = [ 'protection sociale . autres' ] -// Used for type consistency -export const BLANK_COTISATION: Cotisation = { - montant: { - partPatronale: 0, - partSalariale: 0 - }, - dottedName: 'ERROR_SHOULD_BE_INSTANCIATED', - title: 'ERROR_SHOULD_BE_INSTANCIATED', - branche: 'protection sociale . autres' -} - -function duParSelector( - variable: VariableWithCotisation -): ?('employeur' | 'employé') { +function duParSelector(variable): 'employeur' | 'salarié' { const dusPar = [ ['cotisation', 'dû par'], ['taxe', 'dû par'], ['explanation', 'cotisation', 'dû par'], ['explanation', 'taxe', 'dû par'] ].map(p => path(p, variable)) - return dusPar.filter(Boolean)[0] + return dusPar.filter(Boolean)[0] as any } -function brancheSelector(variable: VariableWithCotisation): Branche { +function brancheSelector(variable): Branch { const branches = [ ['cotisation', 'branche'], ['taxe', 'branche'], ['explanation', 'cotisation', 'branche'], ['explanation', 'taxe', 'branche'] ].map(p => path(p, variable)) - return ( - // $FlowFixMe - 'protection sociale . ' + (branches.filter(Boolean)[0] || 'autres') - ) + return ('protection sociale . ' + + (branches.filter(Boolean)[0] || 'autres')) as any } -// $FlowFixMe export const mergeCotisations: ( - Cotisation, - Cotisation + a: Cotisation, + b: Cotisation ) => Cotisation = mergeWithKey((key, a, b) => key === 'montant' ? mergeWith(add, a, b) : b ) -const variableToCotisation = (variable: VariableWithCotisation): Cotisation => { +const variableToCotisation = (variable): Cotisation => { return mergeCotisations(BLANK_COTISATION, { ...variable.explanation, branche: brancheSelector(variable), @@ -84,7 +78,7 @@ const variableToCotisation = (variable: VariableWithCotisation): Cotisation => { } }) } -const groupByBranche = (cotisations: Array): Cotisations => { +const groupByBranche = (cotisations: Array) => { const cotisationsMap = cotisations.reduce( (acc, cotisation) => ({ ...acc, @@ -94,11 +88,10 @@ const groupByBranche = (cotisations: Array): Cotisations => { ) return COTISATION_BRANCHE_ORDER.map(branche => [ branche, - // $FlowFixMe cotisationsMap[branche] ]) } -export let analysisToCotisations = analysis => { +export let analysisToCotisations = (analysis: Analysis) => { const variables = [ 'contrat salarié . cotisations . salariales', 'contrat salarié . cotisations . patronales' @@ -108,7 +101,7 @@ export let analysisToCotisations = analysis => { .reduce(concat, []) const cotisations = pipe( - map(rule => + map((rule: any) => // Following : weird logic to automatically handle negative negated value in sum rule.operationType === 'calculation' && diff --git a/source/selectors/repartitionSelectors.js b/source/selectors/repartitionSelectors.ts similarity index 73% rename from source/selectors/repartitionSelectors.js rename to source/selectors/repartitionSelectors.ts index 3c4f3b2e9..09876c623 100644 --- a/source/selectors/repartitionSelectors.js +++ b/source/selectors/repartitionSelectors.ts @@ -1,5 +1,3 @@ -/* @flow */ - import { getRuleFromAnalysis } from 'Engine/rules' import { add, @@ -20,25 +18,39 @@ import { } from 'ramda' import { createSelector } from 'reselect' import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors' +import { Rule } from 'Types/rule' import { analysisToCotisations, BLANK_COTISATION, mergeCotisations } from './ficheDePaieSelectors' -import type { - Cotisation, - MontantPartagé, - Branche, - Répartition -} from 'Types/ResultViewTypes' +export type Cotisation = Rule & { + branche: Branch + montant: MontantPartagé +} + +type MontantPartagé = { + partSalariale: number + partPatronale: number +} + +export type Branch = + | 'protection sociale . santé' + | 'protection sociale . accidents du travail et maladies professionnelles' + | 'protection sociale . retraite' + | 'protection sociale . famille' + | 'protection sociale . assurance chômage' + | 'protection sociale . formation' + | 'protection sociale . transport' + | 'protection sociale . autres' const totalCotisations = (cotisations: Array): MontantPartagé => cotisations.reduce(mergeCotisations, BLANK_COTISATION).montant const byMontantTotal = ( - a: [Branche, MontantPartagé], - b: [Branche, MontantPartagé] + a: [Branch, MontantPartagé], + b: [Branch, MontantPartagé] ): number => { return ( b[1].partPatronale + @@ -48,7 +60,7 @@ const byMontantTotal = ( ) } -const REPARTITION_CSG: { [Branche]: number } = { +const REPARTITION_CSG: Partial> = { 'protection sociale . famille': 0.85, 'protection sociale . santé': 7.75, // TODO: cette part correspond à l'amortissement de la dette de la sécurité sociale. @@ -57,10 +69,9 @@ const REPARTITION_CSG: { [Branche]: number } = { } function applyCSGInPlace( CSG: Cotisation, - rawRépartition: { [Branche]: MontantPartagé } + rawRépartition: Record ): void { - // $FlowFixMe - for (const branche: Branche in REPARTITION_CSG) { + for (const branche in REPARTITION_CSG) { rawRépartition[branche] = { partPatronale: rawRépartition[branche].partPatronale + @@ -82,15 +93,14 @@ const brancheConcernéeParLaRéduction = [ ].map(branche => 'protection sociale . ' + branche) function applyReduction( réduction, - répartitionMap: { [Branche]: MontantPartagé } -): { [Branche]: MontantPartagé } { - const totalPatronal = pipe( + répartitionMap: Record +): Record { + const totalPatronal = (pipe( pick(brancheConcernéeParLaRéduction), Object.values, reduce(mergeWith(add), {}) - // $FlowFixMe - )(répartitionMap).partPatronale + )(répartitionMap) as any).partPatronale return mapObjIndexed( ({ partPatronale, partSalariale }, branche) => ({ partPatronale: brancheConcernéeParLaRéduction.find(equals(branche)) @@ -98,16 +108,12 @@ function applyReduction( : partPatronale, partSalariale }), - // $FlowFixMe répartitionMap ) } -const répartition = (analysis): ?Répartition => { - // $FlowFixMe - let cotisations: { [Branche]: Array } = fromPairs( - analysisToCotisations(analysis) - ) +const répartition = analysis => { + let cotisations = fromPairs(analysisToCotisations(analysis) as any) const getRule = getRuleFromAnalysis(analysis), salaireNet = getRule('contrat salarié . rémunération . net'), @@ -117,7 +123,7 @@ const répartition = (analysis): ?Répartition => { 'contrat salarié . cotisations . patronales . réductions de cotisations' ) let CSG - const autresCotisations = cotisations['protection sociale . autres'] + const autresCotisations = cotisations['protection sociale . autres'] as any if (autresCotisations) { CSG = autresCotisations.find(propEq('dottedName', 'contrat salarié . CSG')) cotisations['protection sociale . autres'] = without( @@ -126,39 +132,34 @@ const répartition = (analysis): ?Répartition => { ) } - // $FlowFixMe - let répartitionMap: { [Branche]: MontantPartagé } = map( - totalCotisations, - cotisations - ) + let répartitionMap = map(totalCotisations, cotisations) as Record< + Branch, + MontantPartagé + > if (CSG) { applyCSGInPlace(CSG, répartitionMap) } répartitionMap = applyReduction(réductionsDeCotisations, répartitionMap) return { - // $FlowFixMe répartition: compose( sort(byMontantTotal), - Object.entries, + Object.entries as any, filter( ({ partPatronale, partSalariale }) => Math.round(partPatronale + partSalariale) !== 0 ) )(répartitionMap), - // $FlowFixMe total: cotisationsRule.nodeValue, cotisations: cotisationsRule, maximum: compose( reduce(max, 0), map(montant => montant.partPatronale + montant.partSalariale), Object.values - // $FlowFixMe )(répartitionMap), salaireNet, salaireChargé } } -// $FlowFixMe export default createSelector([analysisWithDefaultsSelector], répartition) diff --git a/source/storage/persistEverything.js b/source/storage/persistEverything.ts similarity index 69% rename from source/storage/persistEverything.js rename to source/storage/persistEverything.ts index 28d26b81b..8c7a105ea 100644 --- a/source/storage/persistEverything.js +++ b/source/storage/persistEverything.ts @@ -1,10 +1,9 @@ - -import type { Store } from 'redux' +import { Action } from 'Actions/actions' import { omit } from 'ramda' +import { RootState } from 'Reducers/rootReducer' +import { Store } from 'redux' import { debounce } from '../utils' import safeLocalStorage from './safeLocalStorage' -import type { State } from 'Types/State' -import type { Action } from 'Types/ActionsTypes' const VERSION = 3 @@ -13,8 +12,8 @@ const LOCAL_STORAGE_KEY = 'mycompanyinfrance::persisted-everything:v' + VERSION type OptionsType = { except?: Array } -export const persistEverything = (options?: OptionsType = {}) => ( - store: Store +export const persistEverything = (options: OptionsType = {}) => ( + store: Store ): void => { const listener = () => { const state = store.getState() @@ -26,7 +25,7 @@ export const persistEverything = (options?: OptionsType = {}) => ( store.subscribe(debounce(1000, listener)) } -export function retrievePersistedState(): ?State { +export function retrievePersistedState(): RootState { const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY) return serializedState ? JSON.parse(serializedState) : null } diff --git a/source/storage/persistSimulation.js b/source/storage/persistSimulation.ts similarity index 69% rename from source/storage/persistSimulation.js rename to source/storage/persistSimulation.ts index 14535e860..64ffa9411 100644 --- a/source/storage/persistSimulation.js +++ b/source/storage/persistSimulation.ts @@ -1,16 +1,16 @@ - -import type { Store } from 'redux' +import { Action } from 'Actions/actions' +import { RootState } from 'Reducers/rootReducer' +import { Store } from 'redux' +import { SavedSimulation } from 'Selectors/storageSelectors' import { debounce } from '../utils' import safeLocalStorage from './safeLocalStorage' import { deserialize, serialize } from './serializeSimulation' -import type { State, SavedSimulation } from '../types/State' -import type { Action } from 'Types/ActionsTypes' const VERSION = 3 const LOCAL_STORAGE_KEY = 'embauche.gouv.fr::persisted-simulation::v' + VERSION -export function persistSimulation(store: Store) { +export function persistSimulation(store: Store) { const listener = () => { const state = store.getState() if (!state.conversationSteps.foldedSteps.length) { @@ -21,7 +21,7 @@ export function persistSimulation(store: Store) { store.subscribe(debounce(1000, listener)) } -export function retrievePersistedSimulation(): ?SavedSimulation { +export function retrievePersistedSimulation(): SavedSimulation { const serializedState = safeLocalStorage.getItem(LOCAL_STORAGE_KEY) return serializedState ? deserialize(serializedState) : null } diff --git a/source/storage/safeLocalStorage.js b/source/storage/safeLocalStorage.ts similarity index 59% rename from source/storage/safeLocalStorage.js rename to source/storage/safeLocalStorage.ts index 0f10fdb93..ca1646b9a 100644 --- a/source/storage/safeLocalStorage.js +++ b/source/storage/safeLocalStorage.ts @@ -1,9 +1,10 @@ export default { - removeItem: function (...args) { + removeItem: function (key: string) { try { - return window.localStorage.removeItem(...args) + return window.localStorage.removeItem(key) } catch (error) { if (error.name === 'SecurityError') { + // eslint-disable-next-line no-console console.warn( '[localStorage] Unable to remove item due to security settings' ) @@ -11,11 +12,12 @@ export default { return null } }, - getItem: function (...args) { + getItem: function (key: string) { try { - return window.localStorage.getItem(...args) + return window.localStorage.getItem(key) } catch (error) { if (error.name === 'SecurityError') { + // eslint-disable-next-line no-console console.warn( '[localStorage] Unable to get item due to security settings' ) @@ -23,11 +25,12 @@ export default { return null } }, - setItem: function (...args) { + setItem: function (key: string, value: string) { try { - return window.localStorage.setItem(...args) + return window.localStorage.setItem(key, value) } catch (error) { if (error.name === 'SecurityError') { + // eslint-disable-next-line no-console console.warn( '[localStorage] Unable to set item due to security settings' ) diff --git a/source/storage/serializeSimulation.js b/source/storage/serializeSimulation.js deleted file mode 100644 index f6eb950a9..000000000 --- a/source/storage/serializeSimulation.js +++ /dev/null @@ -1,10 +0,0 @@ -import type { State, SavedSimulation } from '../types/State.js' -import { pipe } from 'ramda' -import { currentSimulationSelector } from 'Selectors/storageSelectors' - -export const serialize: State => string = pipe( - currentSimulationSelector, - JSON.stringify -) - -export const deserialize: string => SavedSimulation = JSON.parse diff --git a/source/storage/serializeSimulation.ts b/source/storage/serializeSimulation.ts new file mode 100644 index 000000000..d3b2a2d15 --- /dev/null +++ b/source/storage/serializeSimulation.ts @@ -0,0 +1,9 @@ +import { pipe } from 'ramda' +import { currentSimulationSelector } from 'Selectors/storageSelectors' + +export const serialize = pipe( + currentSimulationSelector, + JSON.stringify +) + +export const deserialize = JSON.parse diff --git a/source/types/ResultViewTypes.js b/source/types/ResultViewTypes.js deleted file mode 100644 index f53fa50bb..000000000 --- a/source/types/ResultViewTypes.js +++ /dev/null @@ -1,68 +0,0 @@ -import type { RègleAvecMontant, Règle } from './RegleTypes' - -export type Cotisation = Règle & { - branche: Branche, - montant: MontantPartagé -} - -export type Branche = - | 'protection sociale . santé' - | 'protection sociale . accidents du travail et maladies professionnelles' - | 'protection sociale . retraite' - | 'protection sociale . famille' - | 'protection sociale . assurance chômage' - | 'protection sociale . formation' - | 'protection sociale . transport' - | 'protection sociale . autres' - -export type MontantPartagé = { - partSalariale: number, - partPatronale: number -} -export type Cotisations = Array<[Règle, Array]> - -export type VariableWithCotisation = { - category: 'variable', - name: string, - title: string, - cotisation: {| - 'dû par'?: 'salarié' | 'employeur', - branche?: Branche - |}, - dottedName: string, - nodeValue: number, - explanation: { - cotisation: { - 'dû par'?: 'salarié' | 'employeur', - branche?: Branche - }, - taxe: { - 'dû par'?: 'salarié' | 'employeur', - branche?: Branche - } - } -} - -export type FicheDePaie = { - salaireBrut: RègleAvecMontant, - avantagesEnNature: RègleAvecMontant, - indemnitésSalarié: RègleAvecMontant, - salaireDeBase: RègleAvecMontant, - // TODO supprimer (cf https://github.com/betagouv/syso/issues/242) - réductionsDeCotisations: RègleAvecMontant, - cotisations: Cotisations, - totalCotisations: MontantPartagé, - salaireChargé: RègleAvecMontant, - salaireNetDeCotisations: RègleAvecMontant, - rémunérationNetteImposable: RègleAvecMontant, - salaireNet: RègleAvecMontant, - nombreHeuresTravaillées: number -} - -export type Répartition = { - répartition: Array<[Règle, MontantPartagé]>, - total: MontantPartagé, - salaireNet: RègleAvecMontant, - salaireChargé: RègleAvecMontant, - cotisationMaximum: number -} diff --git a/source/types/worker-loader.d.ts b/source/types/worker-loader.d.ts new file mode 100644 index 000000000..e39b0915e --- /dev/null +++ b/source/types/worker-loader.d.ts @@ -0,0 +1,7 @@ +declare module "worker-loader*" { + class WebpackWorker extends Worker { + constructor(); + } + + export = WebpackWorker; +} diff --git a/source/utils.ts b/source/utils.ts index 33fec2834..731ddb142 100644 --- a/source/utils.ts +++ b/source/utils.ts @@ -3,8 +3,8 @@ export let capitalise0 = (name: string): string => export function debounce( timeout: number, - fn: (arg: ArgType) => void -): (arg: ArgType) => void { + fn: (arg?: ArgType) => void +): (arg?: ArgType) => void { let timeoutId: ReturnType return (...args) => { clearTimeout(timeoutId) @@ -43,10 +43,10 @@ export function softCatch( } } } -export function mapOrApply( - fn: (a: A) => B, - x: Array | A -): Array | B { + +export function mapOrApply(fn: (a: A) => B, x: A): B +export function mapOrApply(fn: (a: A) => B, x: Array): Array +export function mapOrApply(fn, x) { return Array.isArray(x) ? x.map(fn) : fn(x) } diff --git a/test/ficheDePaieSelector.test.js b/test/ficheDePaieSelector.test.js index f14c20318..517ca7229 100644 --- a/test/ficheDePaieSelector.test.js +++ b/test/ficheDePaieSelector.test.js @@ -1,5 +1,4 @@ import { expect } from 'chai' -// $FlowFixMe import salariéConfig from 'Components/simulationConfigs/salarié.yaml' import { getRuleFromAnalysis, rules } from 'Engine/rules' import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors' @@ -27,20 +26,17 @@ let cotisations = null, describe('pay slip selector', function() { beforeEach(() => { - // $FlowFixMe cotisations = analysisToCotisationsSelector(state) analysis = analysisWithDefaultsSelector(state) expect(cotisations).not.to.eq(null) }) it('should have cotisations grouped by branches in the proper ordering', function() { - // $FlowFixMe let branches = cotisations.map(([branche]) => branche) expect(branches).to.eql(COTISATION_BRANCHE_ORDER) }) it('should collect all cotisations in a branche', function() { - // $FlowFixMe let cotisationsSanté = (cotisations.find(([branche]) => branche.includes('santé') ) || [])[1].map(cotisation => cotisation.name) @@ -61,7 +57,6 @@ describe('pay slip selector', function() { }) it('should have value for "salarié" and "employeur" for a cotisation', function() { - // $FlowFixMe let cotisationATMP = (cotisations.find(([branche]) => branche.includes('accidents du travail et maladies professionnelles') ) || [])[1][0] diff --git a/tsconfig.json b/tsconfig.json index f5f3b75ae..e24e4fe3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,14 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, + "allowJs": true, + // The end goal is to enable `"strict": true` which correspond to the + // following settings: noImplicitAny, noImplicitThis, alwaysStrict, + // strictBindCallApply, strictNullChecks, strictFunctionTypes, and + // strictPropertyInitialization. During the transition we enable these + // settings one by one. + "noImplicitThis": true, + "strictBindCallApply": true, "paths": { "Actions/*": ["actions/*"], "Components": ["components"], diff --git a/yarn.lock b/yarn.lock index 9d13c4594..d2bf83c5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -390,13 +390,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-flow@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz#a765f061f803bc48f240c26f8747faf97c26bf7c" - integrity sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" @@ -529,14 +522,6 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-flow-strip-types@^7.0.0": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.4.tgz#d267a081f49a8705fc9146de0768c6b58dccd8f7" - integrity sha512-WyVedfeEIILYEaWGAUWzVNyqG4sfsNooMhXWsu/YzOvVGcsnPb5PguysjJqI3t3qiaYj0BR8T2f5njdjTGe44Q== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.2.0" - "@babel/plugin-transform-for-of@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" @@ -805,14 +790,6 @@ js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/preset-flow@^7.0.0-beta.51": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2" - integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/preset-react@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" @@ -1383,6 +1360,13 @@ dependencies: "@types/react" "*" +"@types/react-highlight-words@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.0.tgz#bcd67e9724fb5f070c955732f604068de5cbe30b" + integrity sha512-bSVlhM5OXLO67UZD/orsoT1lS5p7w8ffoDis3TtU1mqmXW7epHHt4kB7QmmZzwXjgzRm++yOo6Xpp7PhRFBpXA== + dependencies: + "@types/react" "*" + "@types/react-native@*": version "0.60.21" resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.60.21.tgz#81a41cae7b232f52ab3983d854f4a0b0df79531e"