Réduit l'utilisation de Ramda

En vue de sa suppression dans un prochain commit
pull/2052/head
Maxime Quandalle 2022-05-03 14:56:13 +02:00 committed by Maxime Quandalle
parent af0458ae41
commit 5233abc7ef
24 changed files with 149 additions and 155 deletions

View File

@ -25,7 +25,6 @@ Nous utilisons :
- [Prettier](https://prettier.io/) pour formater le code source, l'idéal est de configurer votre éditeur de texte pour que les fichiers soit formatés automatiquement quand vous sauvegardez un fichier. Si vous utilisez [VS Code](https://code.visualstudio.com/) cette configuration est automatique.
- [ViteJS](https://vitejs.dev) pour le “bundling” et le serveur de développement
- [Eslint](http://eslint.org) qui permet par exemple d'éviter de garder des variables inutilisées
- [Ramda](https://ramdajs.com) comme libraire d'utilitaires pour manipuler les listes/objects/etc (c'est une alternative à lodash ou underscore)
- [Vitest](https://vitest.dev) et [Cypress](https://www.cypress.io) pour les l'execution des tests. Plus d'informations dans la section consacrée aux tests.
### Démarrage

View File

@ -2,8 +2,6 @@ import 'dotenv/config.js'
import 'isomorphic-fetch'
import fs from 'fs'
import path from 'path'
import { filter, flatten, map, partition, pipe } from 'ramda'
import { compose } from 'redux'
import { fileURLToPath } from 'url'
import { createDataDir, writeInDataDir } from './utils.js'
@ -157,21 +155,23 @@ const last36Months = {
.slice(0, 8) + '01',
end: yesterday,
}
const uniformiseData = pipe(
// For some reason, an artifact create ghost page with unlogical chapter metrics...
// It seems to only by one per month thought... This hacks resolves it
filter(({ m_visits }) => m_visits === undefined || m_visits > 2),
map(({ d_evo_day, d_evo_month, m_visits, m_events, ...data }) => ({
date: d_evo_day != null ? d_evo_day : d_evo_month,
nombre: m_visits != null ? m_visits : m_events,
...data,
}))
)
const flattenPage = compose(
flatten,
map(({ Rows, ...page }) => Rows.map((r) => ({ ...page, ...r }))),
filter((p) => p.page_chapter2 !== 'N/A') // Remove simulateur landing page
)
const uniformiseData = (data) =>
data
.map(({ d_evo_day, d_evo_month, m_visits, m_events, ...data }) => ({
date: d_evo_day != null ? d_evo_day : d_evo_month,
nombre: m_visits != null ? m_visits : m_events,
...data,
}))
// For some reason, an artifact create ghost page with unlogical chapter metrics...
// It seems to only by one per month thought... This hacks resolves it
.filter(({ m_visits }) => m_visits === undefined || m_visits > 2)
const flattenPage = (list) =>
list
.flat()
.map(({ Rows, ...page }) => Rows.map((r) => ({ ...page, ...r })))
.filter((p) => p.page_chapter2 !== 'N/A') // Remove simulateur landing page
async function fetchDailyVisits() {
const pages = uniformiseData(
flattenPage(await fetchApi(buildSimulateursQuery(last60days, 'D')))
@ -261,12 +261,12 @@ async function fetchUserFeedbackIssues() {
const issues = Object.entries(data.data.repository)
.filter(([, value]) => !!value)
.map(([k, value]) => ({ ...value, count: +/[\d]+$/.exec(k)[0] }))
const [closed, open] = partition((s) => s.closedAt, issues)
return {
open,
closed: closed.sort(
(i1, i2) => new Date(i2.closedAt) - new Date(i1.closedAt)
),
open: issues.filter((s) => !s.closedAt),
closed: issues
.filter((s) => s.closedAt)
.sort((i1, i2) => new Date(i2.closedAt) - new Date(i1.closedAt)),
}
}
async function main() {

View File

@ -2,7 +2,7 @@ import 'dotenv/config.js'
import { readFileSync } from 'fs'
import 'isomorphic-fetch'
import { stringify } from 'querystring'
import { equals, mergeAll, path as _path, pick, toPairs } from 'ramda'
import { equals, mergeAll, path as _path, pick } from 'ramda'
import yaml from 'yaml'
import rules from '../../../modele-social/dist/index.js'
@ -36,7 +36,7 @@ export function getRulesMissingTranslations() {
])
.map(([dottedName, rule]) => ({
[dottedName]: mergeAll(
toPairs(rule)
Object.entries(rule)
.filter(([, v]) => !!v)
.map(([k, v]) => {
let attrToTranslate = attributesToTranslate.find(equals(k))

View File

@ -1,6 +1,5 @@
import { EngineContext, useEngine } from '@/components/utils/EngineContext'
import { DottedName } from 'modele-social'
import { max } from 'ramda'
import { useContext } from 'react'
import { useSelector } from 'react-redux'
import { targetUnitSelector } from '@/selectors/simulationSelectors'
@ -28,7 +27,7 @@ export default function Distribution() {
.filter(([, value]) => value > 0)
.sort(([, a], [, b]) => b - a)
const maximum = distribution.map(([, value]) => value).reduce(max, 0)
const maximum = Math.max(...distribution.map(([, value]) => value))
return (
<div className="distribution-chart__container">

View File

@ -1,7 +1,6 @@
import { Link } from '@/design-system/typography/link'
import { SmallBody } from '@/design-system/typography/paragraphs'
import { ASTNode } from 'publicodes'
import { toPairs } from 'ramda'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -27,7 +26,7 @@ export default function InputSuggestions({
return (
<StyledInputSuggestion className={className}>
{toPairs(suggestions).map(([text, value]: [string, ASTNode]) => {
{Object.entries(suggestions).map(([text, value]: [string, ASTNode]) => {
return (
<Link
key={text}

View File

@ -28,19 +28,19 @@ export default function Footer() {
? `${window.location.protocol}//${window.location.host}`
: '') + window.location.pathname
const uri = decodeURIComponent(encodedUri || '').replace(/\/$/, '')
const hrefLink = hrefLangLink[language][uri] || []
const hrefLink = hrefLangLink[language][uri]
return (
<>
<Helmet>
{hrefLink.map(({ href, hrefLang }) => (
{hrefLink && (
<link
key={hrefLang}
key={hrefLink.hrefLang}
rel="alternate"
hrefLang={hrefLang}
href={href}
hrefLang={hrefLink.hrefLang}
href={hrefLink.href}
/>
))}
)}
</Helmet>
<div
css={`
@ -97,23 +97,23 @@ export default function Footer() {
<InscriptionBetaTesteur />
</li>
)}
{hrefLink.map(({ hrefLang, href }) => (
<li key={hrefLang}>
<Link href={href} openInSameWindow>
{hrefLang === 'fr' ? (
{hrefLink && (
<li key={hrefLink.hrefLang}>
<Link href={hrefLink.href} openInSameWindow>
{hrefLink.hrefLang === 'fr' ? (
<>
Passer en français <Emoji emoji="🇫🇷" />
</>
) : hrefLang === 'en' ? (
) : hrefLink.hrefLang === 'en' ? (
<>
Switch to English <Emoji emoji="🇬🇧" />
</>
) : (
hrefLang
hrefLink.hrefLang
)}
</Link>
</li>
))}
)}
</ul>
</FooterColumn>

View File

@ -14,7 +14,6 @@ import { Li, Ul } from '@/design-system/typography/list'
import { SmallBody } from '@/design-system/typography/paragraphs'
import { targetUnitSelector } from '@/selectors/simulationSelectors'
import { DottedName } from 'modele-social'
import { max } from 'ramda'
import { useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
@ -115,7 +114,7 @@ function Distribution() {
.filter(([, value]) => value > 0)
.sort(([, a], [, b]) => b - a)
const maximum = distribution.map(([, value]) => value).reduce(max, 0)
const maximum = Math.max(...distribution.map(([, value]) => value))
return (
<>

View File

@ -8,7 +8,6 @@ import { configSelector } from '@/selectors/simulationSelectors'
import Engine, { ParsedRules, serializeEvaluation } from 'publicodes'
import { DottedName } from 'modele-social'
import { setActiveTarget, batchUpdateSituation } from '@/actions/actions'
import { isEmpty } from 'ramda'
type Objectifs = (string | { objectifs: string[] })[]
type ShortName = string
@ -44,7 +43,7 @@ export default function useSearchParamsSimulationSharing() {
searchParams,
dottedNameParamName
)
if (!isEmpty(newSituation)) {
if (Object.keys(newSituation).length > 0) {
dispatch(batchUpdateSituation(newSituation as Situation))
}

View File

@ -1,4 +1,3 @@
import { assoc, mapObjIndexed } from 'ramda'
import { Rule } from 'publicodes'
type Translation = Record<string, string>
@ -15,21 +14,17 @@ const translateSuggestion: translateAttribute = (
rule,
translation,
lang
) =>
assoc(
'suggestions',
Object.entries(rule.suggestions!).reduce(
(acc, [name, value]) => ({
...acc,
[translation[`${prop}.${name}.${lang}`]?.replace(
/^\[automatic\] /,
''
)]: value,
}),
{}
),
rule
)
) => ({
...rule,
suggestions: Object.entries(rule.suggestions!).reduce(
(acc, [name, value]) => ({
...acc,
[translation[`${prop}.${name}.${lang}`]?.replace(/^\[automatic\] /, '')]:
value,
}),
{}
),
})
export const attributesToTranslate = [
'titre',
@ -49,7 +44,7 @@ const translateProp =
let propTrans = translation[prop + '.' + lang]
propTrans = propTrans?.replace(/^\[automatic\] /, '')
return propTrans ? assoc(prop, propTrans, rule) : rule
return propTrans ? { ...rule, [prop]: propTrans } : rule
}
function translateRule<Names extends string>(
@ -74,13 +69,14 @@ export default function translateRules<Names extends string>(
translations: Record<Names, Translation>,
rules: Record<Names, Rule>
): Record<Names, Rule> {
const translatedRules = mapObjIndexed(
(rule: Rule, name: string) =>
const translatedRules = Object.fromEntries(
Object.entries<Rule>(rules).map(([name, rule]) => [
name,
rule && typeof rule === 'object'
? translateRule(lang, translations, name, rule)
: rule,
rules
])
)
return translatedRules
return translatedRules as Record<Names, Rule>
}

View File

@ -6,7 +6,6 @@ import { ScrollToTop } from '@/components/utils/Scroll'
import { Item, Select } from '@/design-system/field/Select'
import { H1, H2 } from '@/design-system/typography/heading'
import { formatValue } from 'publicodes'
import { sum, uniq } from 'ramda'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -33,17 +32,21 @@ const ressources = {
2022: ressources2022,
} as const
const arraySum = (arr: number[]) => arr.reduce((a, b) => a + b, 0)
export default function Budget() {
const years = ['2019', '2020', '2021', '2022'] as const
const quarters = ['T1', 'T2', 'T3', 'T4']
const [selectedYear, setSelectedYear] = useState<typeof years[number]>(
years[years.length - 1]
)
const categories = uniq(
quarters
.map((q) => Object.keys(budget[selectedYear]?.[q] ?? {}))
.reduce((acc, curr) => [...acc, ...curr], [])
)
const categories = [
...new Set(
quarters
.map((q) => Object.keys(budget[selectedYear]?.[q] ?? {}))
.reduce((acc, curr) => [...acc, ...curr], [])
),
]
const { language } = useTranslation().i18n
@ -115,7 +118,7 @@ export default function Budget() {
})}
<td>
{formatValue(
sum(
arraySum(
quarters.map(
(q) => budget[selectedYear]?.[q]?.[label] ?? 0
)
@ -133,7 +136,7 @@ export default function Budget() {
<tr>
<td>Total HT</td>
{quarters.map((q) => {
const value = sum(
const value = arraySum(
Object.values(budget[selectedYear]?.[q] ?? {})
)
@ -150,9 +153,11 @@ export default function Budget() {
})}
<td>
{formatValue(
sum(
arraySum(
quarters.map((q) =>
sum(Object.values(budget[selectedYear]?.[q] ?? {}))
arraySum(
Object.values(budget[selectedYear]?.[q] ?? {})
)
)
),
{
@ -166,7 +171,8 @@ export default function Budget() {
<td>Total TTC</td>
{quarters.map((q) => {
const value = Math.round(
sum(Object.values(budget[selectedYear]?.[q] ?? {})) * 1.2
arraySum(Object.values(budget[selectedYear]?.[q] ?? {})) *
1.2
)
return (
@ -183,10 +189,10 @@ export default function Budget() {
<td>
{formatValue(
Math.round(
sum(
arraySum(
quarters.map(
(q) =>
sum(
arraySum(
Object.values(budget[selectedYear]?.[q] ?? {})
) * 1.2
)

View File

@ -2,7 +2,6 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Button } from '@/design-system/buttons'
import { H2, H3 } from '@/design-system/typography/heading'
import { Body } from '@/design-system/typography/paragraphs'
import { filter } from 'ramda'
import { Fragment, useContext } from 'react'
import { Helmet } from 'react-helmet-async'
import { Trans, useTranslation } from 'react-i18next'
@ -111,20 +110,25 @@ export default function SetMainStatus() {
)}
</H2>
{Object.keys(filter(Boolean, possibleStatus)).map(
/* https://github.com/microsoft/TypeScript/issues/32811 */
(statut: any) => (
<Fragment key={statut}>
<H3>
<StatutTitle statut={statut} language={i18n.language} />
</H3>
<Body>
<StatutDescription statut={statut} />
</Body>
<StatutButton statut={statut} />
</Fragment>
)
)}
{Object.entries(possibleStatus)
.filter(([, v]) => Boolean(v))
.map(
/* https://github.com/microsoft/TypeScript/issues/32811 */
([statut]) => (
<Fragment key={statut}>
<H3>
<StatutTitle
statut={statut as LegalStatus}
language={i18n.language}
/>
</H3>
<Body>
<StatutDescription statut={statut as LegalStatus} />
</Body>
<StatutButton statut={statut as LegalStatus} />
</Fragment>
)
)}
</>
)
}

View File

@ -1,6 +1,5 @@
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Link } from '@/design-system/typography/link'
import { isNil } from 'ramda'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { useSelector } from 'react-redux'
@ -77,7 +76,7 @@ export default function PreviousAnswers() {
<PreviousAnswersList>
{Object.entries(legalStatus).map(
([key, value]) =>
!isNil(value) && (
value !== undefined && (
<PreviousAnswersItem key={key}>
<Link
to={

View File

@ -3,7 +3,6 @@ import { FromBottom } from '@/components/ui/animate'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { H1 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { dropWhile, toPairs } from 'ramda'
import { useContext, useEffect } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@ -29,21 +28,24 @@ const useResetFollowingAnswers = () => {
) as (keyof typeof state.choixStatutJuridique.companyLegalStatus)[]
)
useEffect(() => {
const companyStatusCurrentQuestionName = (toPairs(
const companyStatusCurrentQuestionName = (Object.entries(
sitePaths.créer.guideStatut
).find(([, pathname]) => location.pathname === pathname) || [])[0]
if (!companyStatusCurrentQuestionName) {
return
}
const answersToReset = dropWhile(
(a) => a !== companyStatusCurrentQuestionName,
answeredQuestion
const firstAnswerToResetIndex = answeredQuestion.findIndex(
(a) => a === companyStatusCurrentQuestionName
)
if (!answersToReset.length) {
return
if (firstAnswerToResetIndex !== -1) {
dispatch(
resetCompanyStatusChoice(
answeredQuestion.slice(firstAnswerToResetIndex)
)
)
}
dispatch(resetCompanyStatusChoice(answersToReset))
}, [location.pathname, dispatch, sitePaths.créer.guideStatut])
}

View File

@ -5,7 +5,6 @@ import { ScrollToTop } from '@/components/utils/Scroll'
import { Spacing } from '@/design-system/layout'
import { H1, H2 } from '@/design-system/typography/heading'
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
import { intersection } from 'ramda'
import { useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { TrackPage } from '../../../ATInternetTracking'
@ -90,10 +89,9 @@ export const ActivitéSelection = ({
}: ActivitéSelectionProps) => {
const { state } = useContext(StoreContext)
const activitéRépondue = activitésRéponduesSelector(state)
const nextButtonDisabled = !intersection(
activitésEffectuéesSelector(state),
activités
).length
const nextButtonDisabled = activitésEffectuéesSelector(state).every(
(a) => !activités.includes(a)
)
return (
<>

View File

@ -3,7 +3,6 @@ import Emoji from '@/components/utils/Emoji'
import { Strong } from '@/design-system/typography'
import { Li, Ul } from '@/design-system/typography/list'
import { Body } from '@/design-system/typography/paragraphs'
import { add, mapObjIndexed } from 'ramda'
import {
Bar,
BarChart,
@ -25,9 +24,11 @@ export const SatisfactionStyle: [
]
function toPercentage(data: Record<string, number>): Record<string, number> {
const total = Object.values(data).reduce(add)
const total = Object.values(data).reduce((a, b: number) => a + b, 0)
return { ...mapObjIndexed((value) => (100 * value) / total, data), total }
return Object.fromEntries(
Object.entries(data).map(([key, value]) => [key, (100 * value) / total])
)
}
type SatisfactionChartProps = {

View File

@ -8,7 +8,7 @@ import { Item, Select } from '@/design-system/field/Select'
import { Spacing } from '@/design-system/layout'
import { H2, H3 } from '@/design-system/typography/heading'
import { formatValue } from 'publicodes'
import { add, groupBy, mapObjIndexed, mergeWith, toPairs } from 'ramda'
import { add, groupBy, mapObjIndexed, mergeWith } from 'ramda'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans } from 'react-i18next'
import { useHistory, useLocation } from 'react-router-dom'
@ -46,7 +46,7 @@ const isPAM = (name: string | undefined) =>
].includes(name)
const filterByChapter2 = (pages: Pageish[], chapter2: Chapter2 | '') => {
return toPairs(
return Object.entries(
groupBy(
(p) => ('date' in p ? p.date : p.month),
pages.filter(
@ -67,7 +67,7 @@ const filterByChapter2 = (pages: Pageish[], chapter2: Chapter2 | '') => {
}
function groupByDate(data: Pageish[]) {
return toPairs(
return Object.entries(
groupBy(
(p) => ('date' in p ? p.date : p.month),
data.filter((d) => 'page' in d && d.page === 'accueil')

View File

@ -10,11 +10,10 @@ import { Button } from '@/design-system/buttons'
import { Spacing } from '@/design-system/layout'
import { headings } from '@/design-system/typography'
import { Intro, SmallBody } from '@/design-system/typography/paragraphs'
import { evaluateQuestion, hash } from '@/utils'
import { evaluateQuestion, hash, omit } from '@/utils'
import { Grid } from '@mui/material'
import { DottedName } from 'modele-social'
import Engine, { PublicodesExpression } from 'publicodes'
import { omit } from 'ramda'
import {
Fragment,
lazy,
@ -80,7 +79,7 @@ function FormulairePublicodes() {
const onChange = useCallback(
(dottedName, value) => {
if (value === undefined) {
setSituation((situation) => omit([dottedName], situation))
setSituation((situation) => omit(situation, dottedName))
} else {
setSituation((situation) => ({
...situation,

View File

@ -2,7 +2,6 @@ import { Action } from '@/actions/actions'
import { Commune } from '@/api/commune'
import { PreviousSimulation } from '@/selectors/previousSimulationSelectors'
import { DottedName } from 'modele-social'
import { defaultTo, without } from 'ramda'
import reduceReducers from 'reduce-reducers'
import { combineReducers, Reducer } from 'redux'
import { objectifsSelector } from '../selectors/simulationSelectors'
@ -114,17 +113,16 @@ function simulation(
}
case 'UPDATE_SITUATION': {
const objectifs = without(
['entreprise . charges'],
objectifsSelector({ simulation: state } as RootState)
)
const objectifs = objectifsSelector({
simulation: state,
} as RootState).filter((name) => name !== 'entreprise . charges')
const situation = state.situation
const { fieldName: dottedName, value } = action
if (value === undefined) {
return { ...state, situation: omit(situation, dottedName) }
}
if (objectifs.includes(dottedName)) {
const objectifsToReset = without([dottedName], objectifs)
const objectifsToReset = objectifs.filter((name) => name !== dottedName)
const newSituation = Object.fromEntries(
Object.entries(situation).filter(
([dottedName]) =>
@ -160,7 +158,7 @@ function simulation(
if (name === 'unfold') {
return {
...state,
foldedSteps: without([step], state.foldedSteps),
foldedSteps: state.foldedSteps.filter((name) => name !== step),
unfoldedStep: step,
}
}
@ -197,7 +195,7 @@ const mainReducer = combineReducers({
explainedVariable,
simulation,
companySituation,
previousSimulation: defaultTo(null) as Reducer<PreviousSimulation | null>,
previousSimulation: ((p) => p ?? null) as Reducer<PreviousSimulation | null>,
activeTargetInput,
choixStatutJuridique,
})

View File

@ -3,7 +3,7 @@ import { DottedName } from 'modele-social'
export type PreviousSimulation = {
situation: Simulation['situation']
activeTargetInput: RootState['activeTargetInput']
activeTargetInput: DottedName | null
foldedSteps: Array<DottedName> | undefined
}

View File

@ -1,5 +1,4 @@
import { MetadataSrc } from 'pages/Simulateurs/metadata-src'
import { reduce, toPairs, zipObj } from 'ramda'
import { LegalStatus } from '@/selectors/companyStatusSelectors'
export const LANDING_LEGAL_STATUS_LIST: Array<LegalStatus> = [
@ -235,13 +234,12 @@ export const constructLocalizedSitePath = (language: 'en' | 'fr') => {
export type SitePathsType = ReturnType<typeof constructLocalizedSitePath>
const deepReduce = (fn: any, initialValue?: any, object?: any): any =>
reduce(
Object.entries(object).reduce(
(acc, [key, value]) =>
typeof value === 'object'
? deepReduce(fn, acc, value)
: fn(acc, value, key),
initialValue,
toPairs(object)
initialValue
)
type SiteMap = Array<string>
@ -271,12 +269,10 @@ const frSiteMap = generateSiteMap(constructLocalizedSitePath('fr')).map(
)
export const hrefLangLink = {
en: zipObj(
enSiteMap,
frSiteMap.map((href) => [{ href, hrefLang: 'fr' }])
en: Object.fromEntries(
enSiteMap.map((key, i) => [key, { href: frSiteMap[i], hrefLang: 'fr' }])
),
fr: zipObj(
frSiteMap,
enSiteMap.map((href) => [{ href, hrefLang: 'en' }])
fr: Object.fromEntries(
frSiteMap.map((key, i) => [key, { href: enSiteMap[i], hrefLang: 'en' }])
),
}

View File

@ -1,7 +1,6 @@
import { Action } from '@/actions/actions'
import { RootState } from '@/reducers/rootReducer'
import { PreviousSimulation } from '@/selectors/previousSimulationSelectors'
import { isEmpty } from 'ramda'
import { Store } from 'redux'
import { debounce } from '../utils'
import * as safeLocalStorage from './safeLocalStorage'
@ -21,7 +20,7 @@ export function setupSimulationPersistence(
if (!state.simulation?.url) {
return
}
if (isEmpty(state.simulation?.situation)) {
if (Object.keys(state.simulation?.situation).length === 0) {
return
}
safeLocalStorage.setItem(

View File

@ -1,6 +1,7 @@
import { pipe } from 'ramda'
import { currentSimulationSelector } from '@/selectors/previousSimulationSelectors'
export const serialize = pipe(currentSimulationSelector, JSON.stringify)
export const serialize = (
...args: Parameters<typeof currentSimulationSelector>
) => JSON.stringify(currentSimulationSelector(...args))
export const deserialize = JSON.parse

View File

@ -77,8 +77,7 @@ export function hash(str: string): number {
}
export function omit<T, K extends keyof T>(obj: T, key: K): Omit<T, K> {
const returnObject = { ...obj }
delete returnObject[key]
const { [key]: _ignore, ...returnObject } = obj
return returnObject
}

View File

@ -1,22 +1,23 @@
import { it, expect, describe } from 'vitest'
import { parsePublicodes } from 'publicodes'
import { uniq } from 'ramda'
import rawRules from 'modele-social'
import unitsTranslations from '../source/locales/units.yaml'
describe('Tests units', function () {
it('use unit that exists in publicodes', function () {
const { parsedRules } = parsePublicodes(rawRules)
const units = uniq(
Object.keys(parsedRules).reduce(
(prev, name) => [
...prev,
...(parsedRules[name].unit?.numerators ?? []),
...(parsedRules[name].unit?.denumerators ?? []),
],
[]
)
)
const units = [
...new Set(
Object.keys(parsedRules).reduce(
(prev, name) => [
...prev,
...(parsedRules[name].unit?.numerators ?? []),
...(parsedRules[name].unit?.denumerators ?? []),
],
[]
)
),
]
const blackList = ['€', '%']
const translatedKeys = Object.keys(unitsTranslations.en)