diff --git a/.github/workflows/patchrelease.yml b/.github/workflows/patchrelease.yml new file mode 100644 index 0000000000..0e9ba83acc --- /dev/null +++ b/.github/workflows/patchrelease.yml @@ -0,0 +1,47 @@ +name: patchrelease + +on: + push: + branches: + - patchrelease + +jobs: + build-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Read .nvmrc + run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_OUTPUT + id: nvmrc + + - name: setup node ${{ steps.nvmrc.outputs.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: '${{ steps.nvmrc.outputs.NODE_VERSION }}' + registry-url: 'https://registry.npmjs.org' + + - name: install dependencies + run: yarn + + - name: build design tokens package + run: yarn build + working-directory: ./packages/design-tokens + + - name: build core package + run: yarn build + working-directory: ./packages/core + + - name: build react package + run: yarn build + working-directory: ./packages/react + + - name: build hds-js package + run: yarn build:hds-js + working-directory: ./packages/react + + - name: release npm packages + run: yarn release --dist-tag patch + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/build-icon-library.yml b/.github/workflows/update-icon-library.yml similarity index 69% rename from .github/workflows/build-icon-library.yml rename to .github/workflows/update-icon-library.yml index a0a1f24a36..65d6702eac 100644 --- a/.github/workflows/build-icon-library.yml +++ b/.github/workflows/update-icon-library.yml @@ -1,8 +1,11 @@ -# Build icon library, create zip file for release and create PR -name: icon library +# Build icon library, create zip file for release and commit to current branch +name: update icon library on: workflow_dispatch: + push: + branches: + - release-* jobs: build-icon-library: @@ -17,6 +20,26 @@ jobs: node-version: '20.3.1' registry-url: 'https://registry.npmjs.org' + # Don't do anything if we're on development, main or master branches + - name: Check branch + run: | + if [[ $GITHUB_REF == refs/heads/development || $GITHUB_REF == refs/heads/master || $GITHUB_REF == refs/heads/main ]]; then + echo "Development, master pr main branch, skipping icon library build" + exit 1 + fi + + # Don't do anything if we're on release-x.x.x AND the icon-kit has the same version number (already built for the release) + # Skip this step if workflow was triggered by workflow_dispatch + - name: Check if icon library has already been built for this release + if: github.event_name != 'workflow_dispatch' + run: | + PKG_VER=`node -pe "require('./packages/react/package.json').version"` + ICON_KIT_VER=`sed -n -E 's/.*version[[:space:]]+([0-9]+([.][0-9]+)*).*/\1/p' ./release/icon-kit-template-CHANGELOG.txt` + if [[ ${PKG_VER} == ${ICON_KIT_VER} ]]; then + echo "Icon library has already been built for this release, skipping" + exit 0 + fi + - name: Run Glypfig run: | npx glypfig \ @@ -83,9 +106,4 @@ jobs: git config --global user.name "Github Actions" git add . git commit -m 'Updated icon library' - git push - - - name: create pull request - run: gh pr create -B development --title 'Icon library build' --body 'Created by Github action icon library' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + git push \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4251d421ba..1cbad29877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.0] - March, 6, 2024 + +### React + +#### Changed + +- [Tag] Marked changed and removed properties in the next major release + +#### Fixed + +- Warnings about "unmet peer dependency". +- Removed old & deprecated individual `lodash` dependencies and replaced with the full package and importing the needed functions only. +- [HeaderActionBarItemWithDropdown] Removed useless `@layer` css style which caused Jest/jsdom tests output errors. +- [Checkbox] Layout issue when using external label with htmlLabelFor-attribute +- [Table] Append className prop instead of overriding the existing classes + +### Core + +#### Fixed + +- [Icons] Document-group icon wrong colors +- [Button] Some iOS-versions rendering rounded link buttons wrongly + +### Documentation + +#### Added + +- [Icons] Added missing icons to site icons list +- Added links to React and Core Storybooks to Resources and Components pages + +#### Fixed + +- [Button] Fix wrong label on secondary Button variant example. +- [Notification] Fix Core showing and hiding Toast Notification example + +### Figma + +#### Fixed + +- [Icons] Fixed document-group icon, removed stroke from the vector. Removed few export settings and fixed box’s black color to use HDS-token. + +### Icon kit + +#### Fixed: + +- [Forms and information] Fixed document-group icon svg, removed stroke from the vector. + ## [3.5.0] - February, 6, 2024 ### React @@ -62,6 +109,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Icon kit #### Added: + - [Icons] New icons in the Forms category: document-group, document-blank, document-blank-group, folder, folder-group & box ## [3.4.0] - January, 16, 2024 @@ -487,6 +535,14 @@ There is a brand new Figma library available. The licenses are checked and provi - [Header] Add HDS Header.sketch file to shared libraries for HDS 3.0.0 Alpha release purposes. The shared library file includes new Header and Side navigation symbols. +## [2.17.1] - Feb, 7, 2024 + +### React + +#### Fixed + +- Removed old & deprecated individual `lodash` dependencies and replaced with the full package and importing the needed functions only. + ## [2.17.0] - Aug, 18, 2023 ### React diff --git a/package.json b/package.json index 77aa43891d..f0111b8f27 100644 --- a/package.json +++ b/package.json @@ -7,17 +7,18 @@ "site" ], "scripts": { - "build": "yarn build:tokens && yarn build:core && yarn build:react && yarn build:hds-js && yarn build:site", + "build": "yarn build:code && yarn build:site", "build:tokens": "lerna run --scope hds-design-tokens build", "build:core": "lerna run --scope hds-core build", "build:react": "lerna run --scope hds-react build", - "build:hds-js": "lerna run --scope hds-react build:hds-js", "build:site": "lerna run --scope site build", + "build:code": "yarn build:tokens && yarn build:core && yarn build:react", "clean": "lerna clean --yes && rimraf node_modules/ packages/core/lib packages/core/storybook-static packages/react/lib packages/react/storybook-static packages/design-tokens/lib site/.cache site/public", "start:core": "lerna run --scope hds-core start", "start:react": "lerna run --scope hds-react start", "release": "lerna publish from-package --yes", - "update-versions": "lerna version --exact --no-git-tag-version --no-push --amend --yes" + "update-versions": "lerna version --exact --no-git-tag-version --no-push --amend --yes", + "update-changelog": "node ./scripts/changelog/update-changelog.js" }, "devDependencies": { "lerna": "^7.0.1", @@ -29,8 +30,9 @@ "@types/react-dom/@types/react": "17.0.2", "@types/react-dom/**/@types/react": "17.0.2", "**/@types/react-dom/**/@types/react": "17.0.2", - "multer": "1.4.4-lts.1", + "multer": "1.4.5-lts.1", "jpeg-js": "0.4.4", - "json5": "2.2.2" + "json5@>=2.0.0": "2.2.3", + "json5@>=1.0.0": "1.0.2" } } diff --git a/packages/core/package.json b/packages/core/package.json index d067316dd5..272752da38 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "hds-core", - "version": "3.5.0", + "version": "3.6.0", "description": "Core styles for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", @@ -30,7 +30,7 @@ "@storybook/manager-webpack5": "^6.5.16", "copyfiles": "2.2.0", "cssnano": "4.1.10", - "hds-design-tokens": "3.5.0", + "hds-design-tokens": "3.6.0", "postcss": "8.2.15", "postcss-cli": "8.3.1", "postcss-import": "12.0.1", diff --git a/packages/core/src/components/button/button.css b/packages/core/src/components/button/button.css index 428cf57e4e..b09e6e36a8 100644 --- a/packages/core/src/components/button/button.css +++ b/packages/core/src/components/button/button.css @@ -12,7 +12,6 @@ * Normalize.css rules * Correct the inability to style clickable types in iOS and Safari. */ - -webkit-appearance: button; background-color: var(--background-color, transparent); border: var(--border-width) solid var(--border-color, transparent); border-radius: 0; @@ -63,6 +62,7 @@ * Normalize.css rules * Correct the inability to style clickable types in iOS and Safari. */ +button.hds-button, .hds-button[type="button"], .hds-button[type="reset"], .hds-button[type="submit"] { diff --git a/packages/core/src/components/checkbox/checkbox.css b/packages/core/src/components/checkbox/checkbox.css index 0c1b8f9bcf..4c7076fe4b 100644 --- a/packages/core/src/components/checkbox/checkbox.css +++ b/packages/core/src/components/checkbox/checkbox.css @@ -136,7 +136,7 @@ } .hds-checkbox .hds-checkbox__label--hidden { - padding-left: 0; + padding-left: var(--size); } /* ERROR */ diff --git a/packages/core/src/icons/document-group.css b/packages/core/src/icons/document-group.css index 1a17c89cc3..c5afdd62e6 100644 --- a/packages/core/src/icons/document-group.css +++ b/packages/core/src/icons/document-group.css @@ -1,4 +1,4 @@ .hds-icon--document-group { - -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E %3Cmask id='path-1-inside-1_7789_308' fill='currentColor'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18 1L22 4.00001L22 18H8V1H18ZM17 3V6H20L17 3ZM17 3L20 6L20 16H10V3H17Z'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7 4H5V21H19V19H7V4Z'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4 7H2V24H16V22H4V7Z'%3E%3C/path%3E %3Cpath d='M16 6H11V8H16V6Z'%3E%3C/path%3E %3Cpath d='M11 11V9H19V11H11Z'%3E%3C/path%3E %3Cpath d='M11 14V12H19V14H11Z'%3E%3C/path%3E %3C/mask%3E %3Cpath d='M23.5355 2.46447L20.5355 -0.535534L13.4645 6.53553L16.4645 9.53553L23.5355 2.46447ZM18 1L24 -6.99999L21.3333 -9H18V1ZM22 4.00001L32 4.00002L32 -0.999978L28 -3.99998L22 4.00001ZM22 18V28H32L32 18L22 18ZM8 18H-2V28H8V18ZM8 1V-9H-2V1H8ZM17 3H27V-7H17V3ZM17 6H7V16H17V6ZM20 6L30 5.99999L30 -4H20V6ZM20 16V26H30L30 16L20 16ZM10 16H0V26H10V16ZM10 3V-7H0V3H10ZM19 21V31H29V21H19ZM5 21H-5V31H5V21ZM5 4V-6H-5V4H5ZM7 19H-3V29H7V19ZM7 4H17V-6H7V4ZM19 19H29V9H19V19ZM16 24V34H26V24H16ZM2 24H-8V34H2V24ZM2 7V-3H-8V7H2ZM4 22H-6V32H4V22ZM4 7H14V-3H4V7ZM16 22H26V12H16V22ZM11 6V-4H1V6H11ZM16 6H26V-4H16V6ZM16 8V18H26V8H16ZM11 8H1V18H11V8ZM11 9V-1H1V9H11ZM11 11H1V21H11V11ZM19 11V21H29V11H19ZM19 9H29V-1H19V9ZM11 12V2H1V12H11ZM11 14H1V24H11V14ZM19 14V24H29V14H19ZM19 12H29V2H19V12ZM12 8.99999L16 12L28 -3.99998L24 -6.99999L12 8.99999ZM12 4.00001L12 18L32 18L32 4.00002L12 4.00001ZM22 8H8V28H22V8ZM18 18V1H-2V18H18ZM8 11H18V-9H8V11ZM7 3V6H27V3H7ZM17 16H20V-4H17V16ZM10 6.00001L10 16L30 16L30 5.99999L10 6.00001ZM20 6H10V26H20V6ZM20 16V3H0V16H20ZM10 13H17V-7H10V13ZM19 11H5V31H19V11ZM15 21V4H-5V21H15ZM5 14H7V-6H5V14ZM9 19V21H29V19H9ZM16 14H2V34H16V14ZM12 24V7H-8V24H12ZM2 17H4V-3H2V17ZM6 22V24H26V22H6ZM19 9H7V29H19V9ZM17 19V4H-3V19H17ZM16 12H4V32H16V12ZM14 22V7H-6V22H14ZM11 16H16V-4H11V16ZM6 6V8H26V6H6ZM16 -2H11V18H16V-2ZM21 8V6H1V8H21ZM1 9V11H21V9H1ZM11 21H19V1H11V21ZM29 11V9H9V11H29ZM19 -1H11V19H19V-1ZM1 12V14H21V12H1ZM11 24H19V4H11V24ZM29 14V12H9V14H29ZM19 2H11V22H19V2Z' fill='currentColor' mask='url(%23path-1-inside-1_7789_308)'%3E%3C/path%3E %3C/svg%3E"); - mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E %3Cmask id='path-1-inside-1_7789_308' fill='currentColor'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18 1L22 4.00001L22 18H8V1H18ZM17 3V6H20L17 3ZM17 3L20 6L20 16H10V3H17Z'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7 4H5V21H19V19H7V4Z'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4 7H2V24H16V22H4V7Z'%3E%3C/path%3E %3Cpath d='M16 6H11V8H16V6Z'%3E%3C/path%3E %3Cpath d='M11 11V9H19V11H11Z'%3E%3C/path%3E %3Cpath d='M11 14V12H19V14H11Z'%3E%3C/path%3E %3C/mask%3E %3Cpath d='M23.5355 2.46447L20.5355 -0.535534L13.4645 6.53553L16.4645 9.53553L23.5355 2.46447ZM18 1L24 -6.99999L21.3333 -9H18V1ZM22 4.00001L32 4.00002L32 -0.999978L28 -3.99998L22 4.00001ZM22 18V28H32L32 18L22 18ZM8 18H-2V28H8V18ZM8 1V-9H-2V1H8ZM17 3H27V-7H17V3ZM17 6H7V16H17V6ZM20 6L30 5.99999L30 -4H20V6ZM20 16V26H30L30 16L20 16ZM10 16H0V26H10V16ZM10 3V-7H0V3H10ZM19 21V31H29V21H19ZM5 21H-5V31H5V21ZM5 4V-6H-5V4H5ZM7 19H-3V29H7V19ZM7 4H17V-6H7V4ZM19 19H29V9H19V19ZM16 24V34H26V24H16ZM2 24H-8V34H2V24ZM2 7V-3H-8V7H2ZM4 22H-6V32H4V22ZM4 7H14V-3H4V7ZM16 22H26V12H16V22ZM11 6V-4H1V6H11ZM16 6H26V-4H16V6ZM16 8V18H26V8H16ZM11 8H1V18H11V8ZM11 9V-1H1V9H11ZM11 11H1V21H11V11ZM19 11V21H29V11H19ZM19 9H29V-1H19V9ZM11 12V2H1V12H11ZM11 14H1V24H11V14ZM19 14V24H29V14H19ZM19 12H29V2H19V12ZM12 8.99999L16 12L28 -3.99998L24 -6.99999L12 8.99999ZM12 4.00001L12 18L32 18L32 4.00002L12 4.00001ZM22 8H8V28H22V8ZM18 18V1H-2V18H18ZM8 11H18V-9H8V11ZM7 3V6H27V3H7ZM17 16H20V-4H17V16ZM10 6.00001L10 16L30 16L30 5.99999L10 6.00001ZM20 6H10V26H20V6ZM20 16V3H0V16H20ZM10 13H17V-7H10V13ZM19 11H5V31H19V11ZM15 21V4H-5V21H15ZM5 14H7V-6H5V14ZM9 19V21H29V19H9ZM16 14H2V34H16V14ZM12 24V7H-8V24H12ZM2 17H4V-3H2V17ZM6 22V24H26V22H6ZM19 9H7V29H19V9ZM17 19V4H-3V19H17ZM16 12H4V32H16V12ZM14 22V7H-6V22H14ZM11 16H16V-4H11V16ZM6 6V8H26V6H6ZM16 -2H11V18H16V-2ZM21 8V6H1V8H21ZM1 9V11H21V9H1ZM11 21H19V1H11V21ZM29 11V9H9V11H29ZM19 -1H11V19H19V-1ZM1 12V14H21V12H1ZM11 24H19V4H11V24ZM29 14V12H9V14H29ZM19 2H11V22H19V2Z' fill='currentColor' mask='url(%23path-1-inside-1_7789_308)'%3E%3C/path%3E %3C/svg%3E"); + -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E %3Cpath d='M11 6H16V8H11V6Z' fill='currentColor'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18 1L22 4.00001L22 18H8V1H18ZM17 3L20 6L20 16H10V3H17Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M17 3V6H20L17 3Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M11 9V11H19V9H11Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M11 12V14H19V12H11Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M19 21H5V4H7V19H19V21Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M16 24H2V7H4V22H16V24Z' fill='currentColor'%3E%3C/path%3E %3C/svg%3E"); + mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E %3Cpath d='M11 6H16V8H11V6Z' fill='currentColor'%3E%3C/path%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18 1L22 4.00001L22 18H8V1H18ZM17 3L20 6L20 16H10V3H17Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M17 3V6H20L17 3Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M11 9V11H19V9H11Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M11 12V14H19V12H11Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M19 21H5V4H7V19H19V21Z' fill='currentColor'%3E%3C/path%3E %3Cpath d='M16 24H2V7H4V22H16V24Z' fill='currentColor'%3E%3C/path%3E %3C/svg%3E"); } diff --git a/packages/core/src/svg/document-group.svg b/packages/core/src/svg/document-group.svg index aff92735c2..b50009485d 100644 --- a/packages/core/src/svg/document-group.svg +++ b/packages/core/src/svg/document-group.svg @@ -1,11 +1,9 @@ - - - - - - - - - + + + + + + + diff --git a/packages/design-tokens/package.json b/packages/design-tokens/package.json index 8e61dd1275..9e7b1f49fa 100644 --- a/packages/design-tokens/package.json +++ b/packages/design-tokens/package.json @@ -1,6 +1,6 @@ { "name": "hds-design-tokens", - "version": "3.5.0", + "version": "3.6.0", "description": "Design tokens for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", diff --git a/packages/hds-js/package.json b/packages/hds-js/package.json index 3e949e7f28..782783ff8d 100644 --- a/packages/hds-js/package.json +++ b/packages/hds-js/package.json @@ -1,6 +1,6 @@ { "name": "hds-js", - "version": "3.5.0", + "version": "3.6.0", "description": "Vanilla js for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", @@ -13,9 +13,7 @@ "cookie": "^0.4.1", "http-status-typed": "^1.0.1", "jwt-decode": "^3.1.2", - "lodash.isobject": "3.0.2", - "lodash.isundefined": "3.0.1", - "lodash.pick": "^4.4.0", + "lodash": "^4.17.21", "oidc-client-ts": "^2.2.2" }, "scripts": { diff --git a/packages/react/.eslintrc.json b/packages/react/.eslintrc.json index 93e0cc2b24..0defff58ec 100644 --- a/packages/react/.eslintrc.json +++ b/packages/react/.eslintrc.json @@ -10,11 +10,9 @@ "plugin:import/typescript", "plugin:jest/recommended", "airbnb", - "prettier/@typescript-eslint", - "prettier/react", "plugin:prettier/recommended" // this should always be the last item on this array ], - "plugins": ["@typescript-eslint", "prettier", "jest", "import"], + "plugins": ["@typescript-eslint", "prettier", "react", "jest", "import"], "parserOptions": { "ecmaVersion": 2018, "ecmaFeatures": { @@ -24,6 +22,7 @@ "ignorePatterns": ["*.module.css"], "rules": { "no-use-before-define": "off", + "default-param-last": "off", "@typescript-eslint/no-use-before-define": ["error"], "no-shadow": "off", // replaced by ts-eslint rule below "@typescript-eslint/no-shadow": "error", @@ -40,6 +39,7 @@ "newlines-between": "always" } ], + "react/function-component-definition": "off", "react/jsx-filename-extension": "off", "react/jsx-one-expression-per-line": "off", "react/jsx-props-no-spreading": "off", @@ -53,7 +53,12 @@ "react/prop-types": "off", "import/extensions": "off", "import/prefer-default-export": "off", - "jsx-a11y/control-has-associated-label": "warn", + "jsx-a11y/control-has-associated-label": [ + 1, + { + "ignoreElements": ["td"] + } + ], "jsx-a11y/no-interactive-element-to-noninteractive-role": "warn", "jsx-a11y/label-has-associated-control": [ 2, diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Checkbox_With_External_Label.png b/packages/react/.loki/reference/chrome_iphone7_Components_Checkbox_With_External_Label.png new file mode 100644 index 0000000000..7f364e0faf Binary files /dev/null and b/packages/react/.loki/reference/chrome_iphone7_Components_Checkbox_With_External_Label.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Checkbox_With_External_Label.png b/packages/react/.loki/reference/chrome_laptop_Components_Checkbox_With_External_Label.png new file mode 100644 index 0000000000..d04ea114f9 Binary files /dev/null and b/packages/react/.loki/reference/chrome_laptop_Components_Checkbox_With_External_Label.png differ diff --git a/packages/react/package.json b/packages/react/package.json index 402a1b4ed0..e5ab21319f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "hds-react", - "version": "3.5.0", + "version": "3.6.0", "description": "React components for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", @@ -60,6 +60,7 @@ "@storybook/manager-webpack5": "^6.5.12", "@storybook/preset-create-react-app": "^4.0.0", "@storybook/react": "6.4.22", + "@testing-library/dom": "^7.31.2", "@testing-library/jest-dom": "5.11.6", "@testing-library/react": "11.2.0", "@testing-library/react-hooks": "^8.0.1", @@ -71,20 +72,20 @@ "@types/rollup-plugin-generate-package-json": "^3.2.8", "@types/uuid": "^9.0.1", "@types/yup": "^0.32.0", - "@typescript-eslint/eslint-plugin": "5.10.2", + "@typescript-eslint/eslint-plugin": "^6.19.0", "@wessberg/rollup-plugin-ts": "2.0.4", "babel-jest": "^26.0.1", "babel-plugin-require-context-hook": "1.0.0", "chalk": "4.0.0", - "eslint": "7.1.0", - "eslint-config-airbnb": "18.2.1", - "eslint-config-prettier": "6.11.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "23.13.1", - "eslint-plugin-jsx-a11y": "6.2.3", - "eslint-plugin-prettier": "3.1.3", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "4.0.2", + "eslint": "8.56.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^27.6.3", + "eslint-plugin-jsx-a11y": "^6.6.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "identity-obj-proxy": "3.0.0", "inquirer": "7.1.0", "jest": "^26.0.1", @@ -94,7 +95,7 @@ "loki": "0.32.0", "path": "0.12.7", "postcss-import": "^12.0.1", - "prettier": "2.0.5", + "prettier": "3.2.2", "react": "17.0.2", "react-dom": "17.0.2", "react-scripts": "^5.0.0", @@ -124,34 +125,25 @@ "@juggle/resize-observer": "3.2.0", "@popperjs/core": "2.11.5", "@react-aria/visually-hidden": "3.8.0", + "@react-spring/web": "9.3.0", "@types/cookie": "^0.4.1", - "@typescript-eslint/parser": "^5.56.0", + "@typescript-eslint/parser": "^6.19.0", "await-to-js": "^3.0.0", "cookie": "^0.4.1", "crc-32": "1.2.0", "date-fns": "2.16.1", "downshift": "6.0.6", - "hds-core": "3.5.0", + "hds-core": "3.6.0", "http-status-typed": "^1.0.1", "jwt-decode": "^3.1.2", "kashe": "1.0.4", - "lodash.flatten": "^4.4.0", - "lodash.get": "^4.4.2", - "lodash.isequal": "4.5.0", - "lodash.isfunction": "3.0.9", - "lodash.isobject": "3.0.2", - "lodash.isundefined": "3.0.1", - "lodash.pick": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.throttle": "^4.1.1", - "lodash.uniqueid": "4.0.1", - "lodash.xor": "^4.5.0", + "lodash": "^4.17.21", "memoize-one": "5.2.1", "oidc-client-ts": "^2.2.2", + "postcss": "^8.4.21", "react-hook-form": "^7.43.3", "react-merge-refs": "1.1.0", "react-popper": "2.2.5", - "react-spring": "9.3.0", "react-use-measure": "2.0.1", "react-virtual": "2.10.4", "uuid": "^9.0.0", @@ -159,7 +151,6 @@ }, "resolutions": { "babel-jest": "^26.0.1", - "eslint-plugin-react": "7.27.1", "jest": "^26.0.1", "downshift/react-is": "^16.13.1", "react-scripts": "^5.0.0", diff --git a/packages/react/src/components/accordion/Accordion.test.tsx b/packages/react/src/components/accordion/Accordion.test.tsx index 1bf01dc338..0d0398ccba 100644 --- a/packages/react/src/components/accordion/Accordion.test.tsx +++ b/packages/react/src/components/accordion/Accordion.test.tsx @@ -27,7 +27,7 @@ describe(' spec', () => { Bar , ); - userEvent.click(container.querySelector('[id="accordion-heading"] .label')); + userEvent.click(container.querySelector('[id="accordion-heading"] .label') as Element); expect(container.querySelector('[id="accordion-content"]')).toBeVisible(); }); @@ -39,7 +39,7 @@ describe(' spec', () => { , ); - userEvent.click(container.querySelector('[data-testid="accordion-closeButton"]')); + userEvent.click(container.querySelector('[data-testid="accordion-closeButton"]') as Element); await act(async () => { jest.runAllTimers(); diff --git a/packages/react/src/components/accordion/Accordion.tsx b/packages/react/src/components/accordion/Accordion.tsx index 2214eb2279..43a29b948b 100644 --- a/packages/react/src/components/accordion/Accordion.tsx +++ b/packages/react/src/components/accordion/Accordion.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import uniqueId from 'lodash.uniqueid'; -import pickBy from 'lodash.pickby'; +import { uniqueId, pickBy } from 'lodash'; import '../../styles/base.module.css'; import styles from './Accordion.module.scss'; diff --git a/packages/react/src/components/breadcrumb/Breadcrumb.test.tsx b/packages/react/src/components/breadcrumb/Breadcrumb.test.tsx index 264b1c7330..d53c1da98c 100644 --- a/packages/react/src/components/breadcrumb/Breadcrumb.test.tsx +++ b/packages/react/src/components/breadcrumb/Breadcrumb.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-conditional-expect */ import React from 'react'; import { render } from '@testing-library/react'; import { axe } from 'jest-axe'; diff --git a/packages/react/src/components/card/Card.stories.tsx b/packages/react/src/components/card/Card.stories.tsx index 1b3bf9d3d6..da10ad12e4 100644 --- a/packages/react/src/components/card/Card.stories.tsx +++ b/packages/react/src/components/card/Card.stories.tsx @@ -29,8 +29,7 @@ export const TextHeading = (args) => ; TextHeading.storyName = 'With text & heading'; TextHeading.args = { heading: 'Card', - text: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', }; export const WithOtherComponents = (args) => ( @@ -43,8 +42,7 @@ export const WithOtherComponents = (args) => ( WithOtherComponents.storyName = 'With other components'; WithOtherComponents.args = { heading: 'Card', - text: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', }; export const WithCustomTheme = (args) => ; @@ -52,8 +50,7 @@ WithCustomTheme.storyName = 'With custom theme'; WithCustomTheme.args = { border: true, heading: 'Card', - text: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', theme: { '--background-color': 'var(--color-white)', '--border-color': 'var(--color-black-90)', diff --git a/packages/react/src/components/checkbox/Checkbox.stories.tsx b/packages/react/src/components/checkbox/Checkbox.stories.tsx index 98d8bdb80c..8fd0267a95 100644 --- a/packages/react/src/components/checkbox/Checkbox.stories.tsx +++ b/packages/react/src/components/checkbox/Checkbox.stories.tsx @@ -213,6 +213,22 @@ export const GroupWithParent = () => { GroupWithParent.storyName = 'Group with a parent'; +export const WithExternalLabel = () => { + const customStyle = { + '--background-unselected': 'teal', + 'margin-right': 'var(--spacing-2-xs)', + } as React.CSSProperties; + + return ( +
+ + +
+ ); +}; + export const Playground = (args) => { const [checkedItems, setCheckedItems] = useState({}); const options = ['Option 1', 'Option 2', 'Option 3']; diff --git a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx index 9276a74edf..a05ae989a6 100644 --- a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx +++ b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx @@ -1050,8 +1050,7 @@ export const TunnistamoLoginCookies = (args) => { sections: { main: { title: 'List all cookies used in Tunnistamo login', - text: - 'This is an example how to get consents for all cookies when using Tunnistamo login flow. All used session, language, load balancer and csrf cookies are listed. Tunnistamo uses Tunnistus and Suomi.fi services and cookies of those services are also listed as they have no consent queries of their own.', + text: 'This is an example how to get consents for all cookies when using Tunnistamo login flow. All used session, language, load balancer and csrf cookies are listed. Tunnistamo uses Tunnistus and Suomi.fi services and cookies of those services are also listed as they have no consent queries of their own.', }, }, }, @@ -1076,11 +1075,7 @@ export const TunnistamoLoginCookies = (args) => { focusTargetSelector: '#focused-element-after-cookie-consent-closed', }; - return ( - <> - - - ); + return ; }; TunnistamoLoginCookies.parameters = { diff --git a/packages/react/src/components/cookieConsent/__mocks__/mockWindowLocation.ts b/packages/react/src/components/cookieConsent/__mocks__/mockWindowLocation.ts deleted file mode 100644 index 9943edca33..0000000000 --- a/packages/react/src/components/cookieConsent/__mocks__/mockWindowLocation.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type MockedWindowLocationActions = { - setUrl: (url: string) => void; - restore: () => void; -}; -export default function mockWindowLocation(): MockedWindowLocationActions { - const globalWin = (global as unknown) as Window; - let oldWindowLocation: Location | undefined = globalWin.location; - let urlObject = new URL('https://default.domain.com'); - const location = Object.defineProperties( - {}, - { - ...Object.getOwnPropertyDescriptors(oldWindowLocation), - hostname: { - get: () => urlObject.hostname, - }, - }, - ); - Reflect.deleteProperty(globalWin, 'location'); - Reflect.defineProperty(globalWin, 'location', { - configurable: true, - value: location, - writable: true, - }); - - return { - setUrl: (url: string) => { - urlObject = new URL(url); - }, - restore: () => { - if (oldWindowLocation) { - globalWin.location = oldWindowLocation; - } - oldWindowLocation = undefined; - }, - }; -} diff --git a/packages/react/src/components/cookieConsent/consentGroupDataTable/ConsentGroupDataTable.tsx b/packages/react/src/components/cookieConsent/consentGroupDataTable/ConsentGroupDataTable.tsx index c6b7a7ea06..d009787648 100644 --- a/packages/react/src/components/cookieConsent/consentGroupDataTable/ConsentGroupDataTable.tsx +++ b/packages/react/src/components/cookieConsent/consentGroupDataTable/ConsentGroupDataTable.tsx @@ -30,16 +30,7 @@ export function ConsentGroupDataTable(props: { consents: CookieGroup['cookies']; return (
- +
); } diff --git a/packages/react/src/components/cookieConsent/content.builder.test.ts b/packages/react/src/components/cookieConsent/content.builder.test.ts index ccdfb54f2f..85c5dc1998 100644 --- a/packages/react/src/components/cookieConsent/content.builder.test.ts +++ b/packages/react/src/components/cookieConsent/content.builder.test.ts @@ -1,6 +1,6 @@ -import _get from 'lodash.get'; +import { get } from 'lodash'; -import { CookieContentSource, ContentSourceCookieGroup, createContent, setPropsToObject } from './content.builder'; +import { CookieContentSource, ContentSourceCookieGroup, createContent } from './content.builder'; import { getCookieContent } from './getContent'; import { CookieData, CookieGroup, Content, Category } from './contexts/ContentContext'; @@ -155,7 +155,7 @@ describe(`content.builder.ts`, () => { expect(plainContent.requiredCookies).toBeUndefined(); expect(plainContent.optionalCookies).toBeUndefined(); - expect(_get(plainContent, mainTitlePath).indexOf(siteName)).toBe(0); + expect(get(plainContent, mainTitlePath).indexOf(siteName)).toBe(0); }); }); describe('contentSource.texts', () => { @@ -166,14 +166,14 @@ describe(`content.builder.ts`, () => { ...commonContentTestProps, texts: { sections: { main: { title: newMainTitle } } }, }); - expect(_get(contentWithNewMainTitle, mainTitlePath)).toBe(newMainTitle); - expect(_get(contentWithNewMainTitle, mainTextPath)).toBe(_get(defaults, mainTextPath)); + expect(get(contentWithNewMainTitle, mainTitlePath)).toBe(newMainTitle); + expect(get(contentWithNewMainTitle, mainTextPath)).toBe(get(defaults, mainTextPath)); const contentWithNewDetailsText = createContent({ ...commonContentTestProps, texts: { sections: { details: { text: newDetailsText } } }, }); - expect(_get(contentWithNewDetailsText, detailsTextPath)).toBe(newDetailsText); - expect(_get(contentWithNewDetailsText, detailsTitlePath)).toBe(_get(defaults, detailsTitlePath)); + expect(get(contentWithNewDetailsText, detailsTextPath)).toBe(newDetailsText); + expect(get(contentWithNewDetailsText, detailsTitlePath)).toBe(get(defaults, detailsTitlePath)); }); }); describe('contentSource.language', () => { @@ -884,7 +884,7 @@ describe(`content.builder.ts`, () => { }); }); describe('Automatically adds the consent storage cookie to required consents', () => { - it('when noCommonConsentCookie is not true ', () => { + it('when noCommonConsentCookie is not true', () => { const content = createContent({ siteName, currentLanguage: 'fi' }); expect(content.requiredCookies).toBeDefined(); expect(content.requiredCookies?.groups[0].title).toBe(commonContent.commonGroups.sharedConsents.fi.title); @@ -893,89 +893,4 @@ describe(`content.builder.ts`, () => { ); }); }); - describe('setPropsToObject merges given value/object to the target object', () => { - type AnyObject = { [key: string]: unknown }; - type TargetObject = { [key: string]: TargetObject | string }; - it('Path indicates where to merge. Path is a given as a comma delimetered path.', () => { - const target = setPropsToObject({}, 'a', 'valueOfA'); - expect(target).toEqual({ a: 'valueOfA' }); - - const target2 = setPropsToObject({}, 'a.a', 'valueOfAA'); - expect(target2).toEqual({ a: { a: 'valueOfAA' } }); - - const target3 = setPropsToObject({}, 'a.b.c.d.e.f', 'valueOfABCDEF'); - expect(target3).toEqual({ - a: { - b: { - c: { - d: { - e: { - f: 'valueOfABCDEF', - }, - }, - }, - }, - }, - }); - }); - it('existing props are not overridden or copied', () => { - const target: TargetObject & { obj1: { obj2?: AnyObject; newObj2?: { newObj3?: AnyObject } } } = { - prop1: 'valueOfProp1', - obj1: { obj2: { value: 'valueOfObj2' } }, - }; - - // save refs to objects to check refs do not change - const { obj1: obj1Ref } = target; - const { obj2: obj2Ref } = target.obj1 as TargetObject; - - // objects added later - const obj3 = { value: 'valueOfObj3' }; - const newObj4 = { newObj5: 'valueOfNewObj5' }; - - setPropsToObject(target, 'prop2', 'valueOfProp2'); - expect(target.prop2).toBe('valueOfProp2'); - - setPropsToObject(target.obj1.obj2 as AnyObject, 'obj3', obj3); - expect((target.obj1.obj2 as AnyObject).obj3).toBe(obj3); - - setPropsToObject(target, 'obj1.newObj2.newObj3', newObj4); - expect((target.obj1.newObj2 as AnyObject).newObj3).toBe(newObj4); - - // check initial values/refs have not changed - expect(target.prop1).toBe('valueOfProp1'); - expect(target.obj1).toBe(obj1Ref); - expect(target.obj1.obj2).toBe(obj2Ref); - expect((target.obj1.obj2 as AnyObject).value).toBe('valueOfObj2'); - }); - it('object to merge is not copied, but assigned', () => { - const target: AnyObject = {}; - const mergedObject1 = { obj: 'valueOfObj', object2: {} }; - const mergedObject2 = { obj2: 'valueOfObj2' }; - const finalObject1 = { obj3: 'valueOfObj3' }; - - setPropsToObject(target, 'object1', mergedObject1); - expect(target.object1).toEqual(mergedObject1); - - setPropsToObject(target, 'object1.object2', mergedObject2); - expect((target.object1 as AnyObject).object2).toEqual(mergedObject2); - - setPropsToObject(target, 'object1', finalObject1); - expect(target.object1).toEqual(finalObject1); - expect((target.object1 as AnyObject).object2).toBeUndefined(); - }); - it('merged value can be anything', () => { - const target: AnyObject = {}; - const values = [null, undefined, 1, 'abc', () => undefined, new Map(), Object.create({})]; - values.forEach((value, index) => { - const key = `value-${index}`; - setPropsToObject(target, key, value); - expect(target[key]).toBe(value); - }); - }); - it('Using "_" is prohibited to avoid prototype pollution', () => { - const target: AnyObject = {}; - expect(() => setPropsToObject(target, '__proto__.toString', () => 'hello')).toThrow(); - expect(target.toString()).not.toBe('hello'); - }); - }); }); diff --git a/packages/react/src/components/cookieConsent/content.builder.ts b/packages/react/src/components/cookieConsent/content.builder.ts index 46c995cb4a..22d9f2280b 100644 --- a/packages/react/src/components/cookieConsent/content.builder.ts +++ b/packages/react/src/components/cookieConsent/content.builder.ts @@ -1,4 +1,4 @@ -import _get from 'lodash.get'; +import { get, set } from 'lodash'; import type { CookieData, @@ -92,9 +92,10 @@ function getLanguage(lang: SupportedLanguage, overrides: CookieContentSource['la } as Content['language']; } -function getCategoryDescriptions( - language: SupportedLanguage, -): { requiredCookies: Description; optionalCookies: Description } { +function getCategoryDescriptions(language: SupportedLanguage): { + requiredCookies: Description; + optionalCookies: Description; +} { const { requiredCookies, optionalCookies } = commonContent; return { requiredCookies: requiredCookies[language], @@ -143,40 +144,15 @@ function getCommonCookie(language: string, id: string): CookieData { return cookie; } -// lodash.set has a high vulnerability without a fix -// replaced with this custom merge function -export function setPropsToObject( - targetObject: Record, - path: string, - value: unknown, -): Record { - if (path.includes('_')) { - throw new Error('String "_" is not allowed in the path to avoid prototype pollution'); - } - const splitPath = path.split('.'); - const lastPath = splitPath.pop(); - const targetPointInObject = splitPath.reduce((currentObj, currentPath) => { - if (typeof currentObj[currentPath] === 'undefined') { - // eslint-disable-next-line no-param-reassign - currentObj[currentPath] = Object.create(null); - } - return currentObj[currentPath]; - }, targetObject); - if (lastPath) { - targetPointInObject[lastPath] = value; - } - return targetObject; -} - function mergeObjects(target: MergableContent, source: MergableContent, paths: string[]) { paths.forEach((path) => { - const pickedFromSource = _get(source, path); + const pickedFromSource = get(source, path); if (pickedFromSource) { - const pickedFromTarget = _get(target, path); + const pickedFromTarget = get(target, path); if (typeof pickedFromSource === 'string') { - setPropsToObject(target, path, pickedFromSource || pickedFromTarget); + set(target, path, pickedFromSource || pickedFromTarget); } else { - setPropsToObject(target, path, { + set(target, path, { ...pickedFromTarget, ...pickedFromSource, }); @@ -185,9 +161,10 @@ function mergeObjects(target: MergableContent, source: MergableContent, paths: s }); } -function buildCookieGroups( - props: CookieContentSource, -): { requiredCookies: CookieGroup[]; optionalCookies: CookieGroup[] } { +function buildCookieGroups(props: CookieContentSource): { + requiredCookies: CookieGroup[]; + optionalCookies: CookieGroup[]; +} { const requiredCookies = []; const optionalCookies = []; const groupMap = new Map(); @@ -334,9 +311,10 @@ export function createContent(props: CookieContentSource): Content { return content as Content; } -export function pickConsentIdsFromContentSource( - contentSource: Partial, -): { required: string[]; optional: string[] } { +export function pickConsentIdsFromContentSource(contentSource: Partial): { + required: string[]; + optional: string[]; +} { let required: string[] = []; let optional: string[] = []; diff --git a/packages/react/src/components/cookieConsent/contexts/ContextComponent.test.tsx b/packages/react/src/components/cookieConsent/contexts/ContextComponent.test.tsx index 6c594107b0..3ffc0e8df5 100644 --- a/packages/react/src/components/cookieConsent/contexts/ContextComponent.test.tsx +++ b/packages/react/src/components/cookieConsent/contexts/ContextComponent.test.tsx @@ -6,7 +6,7 @@ import { render, RenderResult } from '@testing-library/react'; import { ConsentList, ConsentObject, COOKIE_NAME } from '../cookieConsentController'; import { ConsentContext } from './ConsentContext'; import { CookieConsentContext } from './ContextComponent'; -import mockWindowLocation from '../__mocks__/mockWindowLocation'; +import mockWindowLocation from '../../../utils/mockWindowLocation'; import mockDocumentCookie from '../__mocks__/mockDocumentCookie'; import { extractSetCookieArguments, getContentSource } from '../test.util'; @@ -17,7 +17,7 @@ type TestConsentData = { cookieDomain?: string; }; -describe('ContextComponent ', () => { +describe('ContextComponent', () => { const mockedCookieControls = mockDocumentCookie(); const mockedWindowControls = mockWindowLocation(); const getSetCookieArguments = (index = -1) => extractSetCookieArguments(mockedCookieControls, index); @@ -166,7 +166,7 @@ describe('ContextComponent ', () => { ); }; - describe('Consumers should not ask for consents when hasUserHandledAllConsents() returns true. It ', () => { + describe('Consumers should not ask for consents when hasUserHandledAllConsents() returns true. It', () => { it('returns false if all required consents are not true.', () => { const consentsWithUnApprovedRequiredConsent = createConsentData({ requiredConsentCookieValue: false, @@ -203,7 +203,7 @@ describe('ContextComponent ', () => { }); }); - describe('onConsentsParsed is called when context is created and controller has read the cookie. ', () => { + describe('onConsentsParsed is called when context is created and controller has read the cookie.', () => { it('Arguments are ({consents}, false) when user has not handled all consents', () => { renderCookieConsent(allNotApprovedConsentData); expect(onConsentsParsed).toHaveBeenCalledTimes(1); @@ -227,7 +227,7 @@ describe('ContextComponent ', () => { }); }); - describe('onAllConsentsGiven ', () => { + describe('onAllConsentsGiven', () => { it('is called after user has given all consents.', () => { const result = renderCookieConsent(allNotApprovedConsentData); expect(onAllConsentsGiven).toHaveBeenCalledTimes(0); @@ -246,7 +246,7 @@ describe('ContextComponent ', () => { }); }); - describe('Saving ', () => { + describe('Saving', () => { it('by clicking "Approve all" sends also unknown consents', () => { mockedWindowControls.setUrl('https://subdomain.hel.fi'); const result = renderCookieConsent(allNotApprovedConsentData); diff --git a/packages/react/src/components/cookieConsent/cookieConsentController.test.ts b/packages/react/src/components/cookieConsent/cookieConsentController.test.ts index e3c435784b..beec8a4a64 100644 --- a/packages/react/src/components/cookieConsent/cookieConsentController.test.ts +++ b/packages/react/src/components/cookieConsent/cookieConsentController.test.ts @@ -1,5 +1,5 @@ /* eslint-disable jest/no-mocks-import */ -import mockWindowLocation from './__mocks__/mockWindowLocation'; +import mockWindowLocation from '../../utils/mockWindowLocation'; import createConsentController, { ConsentController, ConsentList, diff --git a/packages/react/src/components/cookieConsent/cookieConsentController.ts b/packages/react/src/components/cookieConsent/cookieConsentController.ts index be65eb9c11..ca415e4e98 100644 --- a/packages/react/src/components/cookieConsent/cookieConsentController.ts +++ b/packages/react/src/components/cookieConsent/cookieConsentController.ts @@ -1,6 +1,4 @@ -import _pick from 'lodash.pick'; -import _isObject from 'lodash.isobject'; -import _isUndefined from 'lodash.isundefined'; +import { pick, isObject, isUndefined } from 'lodash'; import { createCookieController } from './cookieController'; @@ -48,7 +46,7 @@ function mergeConsents(set1: ConsentObject, set2: ConsentObject, set3?: ConsentO } function createConsentsString(consents: ConsentObject): string { - if (!_isObject(consents)) { + if (!isObject(consents)) { return '{}'; } return JSON.stringify(consents); @@ -84,9 +82,7 @@ export const getCookieDomainFromUrl = (): string => { return window.location.hostname.split('.').slice(-2).join('.'); }; -export function createStorage( - initialValues: ConsentStorage, -): { +export function createStorage(initialValues: ConsentStorage): { getAll: () => ConsentStorage; getConsentByName: (consentName: string) => boolean; approve: (keys: string[]) => ConsentStorage; @@ -107,10 +103,10 @@ export function createStorage( }; const findConsentSource = (consentName: string, targetStorage: ConsentStorage): ConsentObject | undefined => { - if (!_isUndefined(targetStorage.required[consentName])) { + if (!isUndefined(targetStorage.required[consentName])) { return targetStorage.required; } - if (!_isUndefined(targetStorage.optional[consentName])) { + if (!isUndefined(targetStorage.optional[consentName])) { return targetStorage.optional; } return undefined; @@ -168,17 +164,17 @@ export default function createConsentController(props: ConsentControllerProps): const required = mergeConsents( convertStringArrayToKeyConsentObject(requiredConsents), - _pick(currentConsentsInCookie, requiredConsents), + pick(currentConsentsInCookie, requiredConsents), ); const optional = mergeConsents( convertStringArrayToKeyConsentObject(optionalConsents), - _pick(currentConsentsInCookie, optionalConsents), + pick(currentConsentsInCookie, optionalConsents), ); const unknownConsentKeys = Object.keys(currentConsentsInCookie).filter((key) => !allConsents.includes(key)); - const unknown = unknownConsentKeys.length ? _pick(currentConsentsInCookie, unknownConsentKeys) : undefined; + const unknown = unknownConsentKeys.length ? pick(currentConsentsInCookie, unknownConsentKeys) : undefined; const storage = createStorage({ required, optional, unknown }); @@ -222,7 +218,7 @@ export default function createConsentController(props: ConsentControllerProps): rejectAll, getUnhandledConsents: () => { const storedCookies = parseConsents(cookieController.get()); - return allConsents.filter((key) => _isUndefined(storedCookies[key])); + return allConsents.filter((key) => isUndefined(storedCookies[key])); }, save, }; diff --git a/packages/react/src/components/cookieConsent/cookieController.test.ts b/packages/react/src/components/cookieConsent/cookieController.test.ts index 5461623351..6e27c02fc9 100644 --- a/packages/react/src/components/cookieConsent/cookieController.test.ts +++ b/packages/react/src/components/cookieConsent/cookieController.test.ts @@ -107,7 +107,7 @@ describe(`cookieController.ts`, () => { }); it('throws when setting invalid options', () => { const options: CookieSetOptions = { - expires: (1111 as unknown) as Date, + expires: 1111 as unknown as Date, }; expect(() => setNamedCookie(dummyKey, dummyValue, options)).toThrow(); }); diff --git a/packages/react/src/components/cookieConsent/cookieModal/CookieModal.test.tsx b/packages/react/src/components/cookieConsent/cookieModal/CookieModal.test.tsx index 3791aeb942..3d1909e240 100644 --- a/packages/react/src/components/cookieConsent/cookieModal/CookieModal.test.tsx +++ b/packages/react/src/components/cookieConsent/cookieModal/CookieModal.test.tsx @@ -159,7 +159,7 @@ describe(' spec', () => { }, 15000); }); -describe(' ', () => { +describe('', () => { afterEach(() => { mockedCookieControls.clear(); jest.clearAllMocks(); @@ -169,7 +169,7 @@ describe(' ', () => { mockedCookieControls.restore(); }); - describe('Portal and modal ', () => { + describe('Portal and modal', () => { it('and child components are rendered when consents have not been handled', () => { const result = renderCookieConsent(defaultConsentData); verifyElementExistsByTestId(result, dataTestIds.container); diff --git a/packages/react/src/components/cookieConsent/cookieModal/Portal.tsx b/packages/react/src/components/cookieConsent/cookieModal/Portal.tsx index f15678dae4..ab59d7079a 100644 --- a/packages/react/src/components/cookieConsent/cookieModal/Portal.tsx +++ b/packages/react/src/components/cookieConsent/cookieModal/Portal.tsx @@ -72,5 +72,6 @@ export function Portal({ rootId, children }: { rootId: string; children: React.R return null; } wasPortalCreated.current = true; + // eslint-disable-next-line react/jsx-no-useless-fragment return createPortal(<>{children}, containerElementRef.current as HTMLElement); } diff --git a/packages/react/src/components/cookieConsent/cookiePage/CookiePage.test.tsx b/packages/react/src/components/cookieConsent/cookiePage/CookiePage.test.tsx index 2c9993189f..60682dd446 100644 --- a/packages/react/src/components/cookieConsent/cookiePage/CookiePage.test.tsx +++ b/packages/react/src/components/cookieConsent/cookiePage/CookiePage.test.tsx @@ -61,7 +61,7 @@ describe(' spec', () => { }, 15000); }); -describe(' ', () => { +describe('', () => { afterEach(() => { mockedCookieControls.clear(); }); @@ -72,7 +72,7 @@ describe(' ', () => { const getSetCookieArguments = (index = -1) => extractSetCookieArguments(mockedCookieControls, index); - describe('Cookie consent ', () => { + describe('Cookie consent', () => { it('and child components are rendered even if consents have been handled', () => { const result = renderCookieConsent({ ...defaultConsentData, @@ -115,7 +115,7 @@ describe(' ', () => { }); }); - describe('Details are shown and ', () => { + describe('Details are shown and', () => { it('required and optional consent groups are rendered', async () => { const result = renderCookieConsent(defaultConsentData); verifyElementExistsByTestId(result, dataTestIds.getConsentsCheckboxId(requiredGroupParent)); @@ -148,13 +148,13 @@ describe(' ', () => { }); }); }); - describe('Accordions of each consent group can be opened and ', () => { + describe('Accordions of each consent group can be opened and', () => { it('all consents in the group are rendered', async () => { const result = renderCookieConsent(defaultConsentData); const checkConsentsExist = async (groupParent: TestGroupParent) => { - const list = (groupParent === 'required' - ? content.requiredCookies?.groups - : content.optionalCookies?.groups) as CookieGroup[]; + const list = ( + groupParent === 'required' ? content.requiredCookies?.groups : content.optionalCookies?.groups + ) as CookieGroup[]; let index = 0; // cannot use async/await with array.forEach // eslint-disable-next-line no-restricted-syntax diff --git a/packages/react/src/components/cookieConsent/getContent.ts b/packages/react/src/components/cookieConsent/getContent.ts index 8d0d956085..2b9c973364 100644 --- a/packages/react/src/components/cookieConsent/getContent.ts +++ b/packages/react/src/components/cookieConsent/getContent.ts @@ -99,35 +99,29 @@ export function getCookieContent() { main: { fi: { title: '{{siteName}} käyttää evästeitä', - text: - 'Tämä sivusto käyttää välttämättömiä evästeitä sivun perustoimintojen ja suorituskyvyn varmistamiseksi. Lisäksi käytämme kohdennusevästeitä käyttäjäkokemuksen parantamiseksi, analytiikkaan ja yksilöidyn sisällön näyttämiseen.', + text: 'Tämä sivusto käyttää välttämättömiä evästeitä sivun perustoimintojen ja suorituskyvyn varmistamiseksi. Lisäksi käytämme kohdennusevästeitä käyttäjäkokemuksen parantamiseksi, analytiikkaan ja yksilöidyn sisällön näyttämiseen.', }, sv: { title: '{{siteName}} använder kakor', - text: - 'Denna webbplats använder obligatoriska kakor för att säkerställa de grundläggande funktionerna och prestandan. Dessutom använder vi inriktningskakor för bättre användarupplevelse, analytik och individualiserat innehåll.', + text: 'Denna webbplats använder obligatoriska kakor för att säkerställa de grundläggande funktionerna och prestandan. Dessutom använder vi inriktningskakor för bättre användarupplevelse, analytik och individualiserat innehåll.', }, en: { title: '{{siteName}} uses cookies', - text: - 'This website uses required cookies to ensure the basic functionality and performance. In addition, we use targeting cookies to improve the user experience, perform analytics and display personalised content.', + text: 'This website uses required cookies to ensure the basic functionality and performance. In addition, we use targeting cookies to improve the user experience, perform analytics and display personalised content.', }, }, details: { fi: { title: 'Tietoa sivustolla käytetyistä evästeistä', - text: - 'Sivustolla käytetyt evästeet on luokiteltu käyttötarkoituksen mukaan. Alla voit lukea eri luokista ja sallia tai kieltää evästeiden käytön.', + text: 'Sivustolla käytetyt evästeet on luokiteltu käyttötarkoituksen mukaan. Alla voit lukea eri luokista ja sallia tai kieltää evästeiden käytön.', }, sv: { title: 'Information om kakor som används på webbplatsen', - text: - 'Kakorna som används på webbplatsen har klassificerats enligt användningsändamål. Du kan läsa om de olika klasserna och acceptera eller förbjuda användningen av kakor.', + text: 'Kakorna som används på webbplatsen har klassificerats enligt användningsändamål. Du kan läsa om de olika klasserna och acceptera eller förbjuda användningen av kakor.', }, en: { title: 'About the cookies used on the website', - text: - 'The cookies used on the website have been classified according to their intended use. Below, you can read about the various categories and accept or reject the use of cookies.', + text: 'The cookies used on the website have been classified according to their intended use. Below, you can read about the various categories and accept or reject the use of cookies.', }, }, }, @@ -192,22 +186,19 @@ export function getCookieContent() { requiredCookies: { fi: { title: 'Välttämättömät evästeet', - text: - 'Välttämättömien evästeiden käyttöä ei voi kieltää. Ne mahdollistavat sivuston toiminnan ja vaikuttavat käytettävyyteen.', + text: 'Välttämättömien evästeiden käyttöä ei voi kieltää. Ne mahdollistavat sivuston toiminnan ja vaikuttavat käytettävyyteen.', checkboxAriaDescription: 'Välttämättömien evästeiden käyttöä ei voi kieltää. Ne mahdollistavat sivuston toiminnan ja vaikuttavat käytettävyyteen.', }, sv: { title: 'Nödvändig kakor', - text: - 'Du kan inte förbjuda användningen av nödvändiga kakorna. De möjliggör webbplatsens funktioner och påverkar användbarheten.', + text: 'Du kan inte förbjuda användningen av nödvändiga kakorna. De möjliggör webbplatsens funktioner och påverkar användbarheten.', checkboxAriaDescription: 'Du kan inte förbjuda användningen av nödvändiga kakorna. De möjliggör webbplatsens funktioner och påverkar användbarheten.', }, en: { title: 'Necessary cookies', - text: - 'Necessary cookies cannot be rejected. They enable the proper functioning of the website and affect the usability.', + text: 'Necessary cookies cannot be rejected. They enable the proper functioning of the website and affect the usability.', checkboxAriaDescription: 'Necessary cookies cannot be rejected. They enable the proper functioning of the website and affect the usability.', }, @@ -254,8 +245,7 @@ export function getCookieContent() { preferences: { fi: { title: 'Mieltymykset', - text: - 'Mieltymysevästeet mukauttavat sivuston ulkoasua ja toimintaa käyttäjän tekemien valintojen perusteella.', + text: 'Mieltymysevästeet mukauttavat sivuston ulkoasua ja toimintaa käyttäjän tekemien valintojen perusteella.', expandAriaLabel: 'Näytä mieltymyksiin liittyvien evästeiden tiedot', checkboxAriaDescription: 'Mieltymysevästeet mukauttavat sivuston ulkoasua ja toimintaa käyttäjän tekemien valintojen perusteella.', @@ -269,8 +259,7 @@ export function getCookieContent() { }, en: { title: 'Preferences', - text: - 'Preference cookies customise the layout and operation of the website based on the choices made by the user.', + text: 'Preference cookies customise the layout and operation of the website based on the choices made by the user.', expandAriaLabel: 'Show cookie information related to preferences', checkboxAriaDescription: 'Preference cookies customise the layout and operation of the website based on the choices made by the user.', @@ -365,24 +354,21 @@ export function getCookieContent() { deviceInfo: { fi: { title: 'Laitetiedot', - text: - 'Laitetietoevästeet sisältävät sellaisia käyttäjän laitteelta kerättyjä tietoja, joilla varmistetaan palvelun toiminta.', + text: 'Laitetietoevästeet sisältävät sellaisia käyttäjän laitteelta kerättyjä tietoja, joilla varmistetaan palvelun toiminta.', expandAriaLabel: 'Näytä laitetietoihin liittyvien evästeiden tiedot', checkboxAriaDescription: 'Laitetietoevästeet sisältävät sellaisia käyttäjän laitteelta kerättyjä tietoja, joilla varmistetaan palvelun toiminta.', }, sv: { title: 'Enhetsuppgifter', - text: - 'Kakor för enhetsuppgifterna innehåller sådana uppgifter som samlats in från användarens enhet med vilka tjänstens funktioner säkerställs.', + text: 'Kakor för enhetsuppgifterna innehåller sådana uppgifter som samlats in från användarens enhet med vilka tjänstens funktioner säkerställs.', expandAriaLabel: 'Visa information om kakor för enhetsuppgifterna', checkboxAriaDescription: 'Kakor för enhetsuppgifterna innehåller sådana uppgifter som samlats in från användarens enhet med vilka tjänstens funktioner säkerställs.', }, en: { title: 'Device information', - text: - "Device information cookies contain information collected from the user's device that ensures the functionality of the service.", + text: "Device information cookies contain information collected from the user's device that ensures the functionality of the service.", expandAriaLabel: 'Show cookie information related to device information', checkboxAriaDescription: "Device information cookies contain information collected from the user's device that ensures the functionality of the service.", @@ -412,16 +398,14 @@ export function getCookieContent() { thirdParty: { fi: { title: 'Kolmannen osapuolen palvelut', - text: - 'Evästeet mahdollistavat ulkopuolisten palvelujen (kuten sosiaalisen median alustojen) toiminnan tällä sivustolla.', + text: 'Evästeet mahdollistavat ulkopuolisten palvelujen (kuten sosiaalisen median alustojen) toiminnan tällä sivustolla.', expandAriaLabel: 'Näytä kolmansiin osapuoliin liittyvien evästeiden tiedot', checkboxAriaDescription: 'Evästeet mahdollistavat ulkopuolisten palvelujen (kuten sosiaalisen median alustojen) toiminnan tällä sivustolla.', }, sv: { title: 'Tredje parts tjänster', - text: - 'Kakor möjliggör utomstående tjänsters (såsom sociala medie-plattformar) funktioner på denna webbplats.', + text: 'Kakor möjliggör utomstående tjänsters (såsom sociala medie-plattformar) funktioner på denna webbplats.', expandAriaLabel: 'Visa information om tredjepartskakor', checkboxAriaDescription: 'Kakor möjliggör utomstående tjänsters (såsom sociala medie-plattformar) funktioner på denna webbplats.', @@ -487,8 +471,7 @@ export function getCookieContent() { }, sv: { title: 'Belastningsutjämning', - text: - 'Med hjälp av belastningsutjämningskakor säkerställer man att tjänsten laddas och fungerar snabbt och effektivt.', + text: 'Med hjälp av belastningsutjämningskakor säkerställer man att tjänsten laddas och fungerar snabbt och effektivt.', expandAriaLabel: 'Visa information om belastningsutjämningskakor', checkboxAriaDescription: 'Med hjälp av belastningsutjämningskakor säkerställer man att tjänsten laddas och fungerar snabbt och effektivt.', @@ -504,23 +487,20 @@ export function getCookieContent() { essential: { fi: { title: 'Perustoimintoihin liittyvät evästeet', - text: - 'Perustoimintoihin liittyviä evästeitä ei voi hylätä. Ne mahdollistavat sivuston kunnollisen toimivuuden ja vaikuttavat käytettävyyteen.', + text: 'Perustoimintoihin liittyviä evästeitä ei voi hylätä. Ne mahdollistavat sivuston kunnollisen toimivuuden ja vaikuttavat käytettävyyteen.', expandAriaLabel: 'Näytä perustoimintoihin liittyvien evästeiden tiedot', checkboxAriaDescription: 'Sivusto ei toimi ilman näitä', }, sv: { title: 'Kakor för grundläggande funktioner', - text: - 'Du kan inte förbjuda användningen av kakor för grundläggande funktioner. De möjliggör webbplatsens funktioner och påverkar användbarheten.', + text: 'Du kan inte förbjuda användningen av kakor för grundläggande funktioner. De möjliggör webbplatsens funktioner och påverkar användbarheten.', expandAriaLabel: 'Visa information om kakor för grundläggande funktioner', checkboxAriaDescription: 'Du kan inte förbjuda användningen av kakor för grundläggande funktioner. De möjliggör webbplatsens funktioner och påverkar användbarheten.', }, en: { title: 'Cookies related to basic functionalities', - text: - 'Cookies related to basic functionalities cannot be rejected. They enable the proper functioning of the website and affect the usability.', + text: 'Cookies related to basic functionalities cannot be rejected. They enable the proper functioning of the website and affect the usability.', expandAriaLabel: 'Show cookie information related to basic functionalities', checkboxAriaDescription: 'Cookies related to basic functionalities cannot be rejected. They enable the proper functioning of the website and affect the usability', @@ -549,24 +529,21 @@ export function getCookieContent() { sharedConsents: { fi: { title: 'Yhteiset evästeet', - text: - 'Helsingin kaupungin palvelut käyttävät yhteisiä evästeitä. Tallennamme nämä suostumukset, jottei sinun tarvitse hyväksyä samoja evästeitä uudelleen kaupungin muissa palveluissa.', + text: 'Helsingin kaupungin palvelut käyttävät yhteisiä evästeitä. Tallennamme nämä suostumukset, jottei sinun tarvitse hyväksyä samoja evästeitä uudelleen kaupungin muissa palveluissa.', expandAriaLabel: 'Näytä yhteisiin evästesuostumuksiin liittyvien evästeiden tiedot', checkboxAriaDescription: 'Helsingin kaupungin palvelut käyttävät yhteisiä evästeitä. Tallennamme nämä suostumukset, jottei sinun tarvitse hyväksyä samoja evästeitä uudelleen muissa kaupungin palveluissa.', }, sv: { title: 'Gemensamma kakor', - text: - 'Helsingfors stads tjänster använder gemensamma kakor . Vi lagrar dessa samtycken så att du inte behöver godkänna samma kakor igen i stadens andra tjänster.', + text: 'Helsingfors stads tjänster använder gemensamma kakor . Vi lagrar dessa samtycken så att du inte behöver godkänna samma kakor igen i stadens andra tjänster.', expandAriaLabel: 'Visa information om kakor för gemensamt samtycke', checkboxAriaDescription: 'Helsingfors stads tjänster använder gemensamma kakor . Vi lagrar dessa samtycken så att du inte behöver godkänna samma kakor igen i stadens andra tjänster.', }, en: { title: 'Shared consent', - text: - 'City of Helsinki services use shared consent. We will store these consents so that you do not have to accept the same cookies again on other City services.', + text: 'City of Helsinki services use shared consent. We will store these consents so that you do not have to accept the same cookies again on other City services.', expandAriaLabel: 'Show cookie information related to shared cookie consent', checkboxAriaDescription: 'City of Helsinki services use shared consent. We will store these consents so that you do not have to accept the same cookies again on other City services.', diff --git a/packages/react/src/components/cookieConsent/test.util.ts b/packages/react/src/components/cookieConsent/test.util.ts index a2c14a2f99..67318f55ac 100644 --- a/packages/react/src/components/cookieConsent/test.util.ts +++ b/packages/react/src/components/cookieConsent/test.util.ts @@ -186,12 +186,12 @@ export function createConsentObjectWithSelectedRejections( export async function openAllAccordions( result: RenderResult, content: Content, - dataTestIds: typeof commonTestProps['dataTestIds'], + dataTestIds: (typeof commonTestProps)['dataTestIds'], ): Promise { const openAccordions = async (groupParent: TestGroupParent) => { - const list = (groupParent === 'required' - ? content.requiredCookies?.groups - : content.optionalCookies?.groups) as CookieGroup[]; + const list = ( + groupParent === 'required' ? content.requiredCookies?.groups : content.optionalCookies?.groups + ) as CookieGroup[]; let index = 0; /* eslint-disable no-restricted-syntax */ // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/react/src/components/cookieConsent/useModalPlaceHolder.test.tsx b/packages/react/src/components/cookieConsent/useModalPlaceHolder.test.tsx index ee0219fd68..479834c289 100644 --- a/packages/react/src/components/cookieConsent/useModalPlaceHolder.test.tsx +++ b/packages/react/src/components/cookieConsent/useModalPlaceHolder.test.tsx @@ -117,7 +117,7 @@ afterAll(() => { jest.clearAllMocks(); }); -describe('useModalPlaceHolder ', () => { +describe('useModalPlaceHolder', () => { describe('creates a placeholder element', () => { it('and matches its height to the observed element height every time element change is detected', async () => { const result = renderElements(); diff --git a/packages/react/src/components/dialog/Dialog.tsx b/packages/react/src/components/dialog/Dialog.tsx index 46762f89de..a6f0c30065 100644 --- a/packages/react/src/components/dialog/Dialog.tsx +++ b/packages/react/src/components/dialog/Dialog.tsx @@ -40,9 +40,9 @@ const focusToDialogElement = (position: TabBarrierPosition, dialogElement?: HTML if (dialogElement) { const focusableElements = findFocusableDialogElements(dialogElement); if (focusableElements.length) { - (focusableElements[ - position === TabBarrierPosition.top ? 0 : focusableElements.length - 1 - ] as HTMLElement).focus(); + ( + focusableElements[position === TabBarrierPosition.top ? 0 : focusableElements.length - 1] as HTMLElement + ).focus(); } } }; diff --git a/packages/react/src/components/dropdown/combobox/Combobox.stories.tsx b/packages/react/src/components/dropdown/combobox/Combobox.stories.tsx index fb11efa467..74e2f20739 100644 --- a/packages/react/src/components/dropdown/combobox/Combobox.stories.tsx +++ b/packages/react/src/components/dropdown/combobox/Combobox.stories.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; -import uniqueId from 'lodash.uniqueid'; +import { uniqueId } from 'lodash'; import { Button } from '../../button'; import { Combobox } from './Combobox'; diff --git a/packages/react/src/components/dropdown/combobox/Combobox.test.tsx b/packages/react/src/components/dropdown/combobox/Combobox.test.tsx index fcf81c6b2f..c2cdf26a1b 100644 --- a/packages/react/src/components/dropdown/combobox/Combobox.test.tsx +++ b/packages/react/src/components/dropdown/combobox/Combobox.test.tsx @@ -59,7 +59,7 @@ describe('', () => { expect(results).toHaveNoViolations(); }); - it('user should be able to search and choose an option ', async () => { + it('user should be able to search and choose an option', async () => { const onChange = jest.fn(); const { getAllByLabelText, getAllByRole, getByDisplayValue } = getWrapper({ onChange }); const input = getAllByLabelText(label)[0]; diff --git a/packages/react/src/components/dropdown/combobox/Combobox.tsx b/packages/react/src/components/dropdown/combobox/Combobox.tsx index 255ce42179..e62573ec57 100644 --- a/packages/react/src/components/dropdown/combobox/Combobox.tsx +++ b/packages/react/src/components/dropdown/combobox/Combobox.tsx @@ -2,8 +2,7 @@ /* eslint-disable react/destructuring-assignment */ import React, { useRef, useState, KeyboardEvent, FocusEvent, FocusEventHandler, useMemo, useCallback } from 'react'; import { useCombobox, useMultipleSelection } from 'downshift'; -import isEqual from 'lodash.isequal'; -import uniqueId from 'lodash.uniqueid'; +import { isEqual, uniqueId } from 'lodash'; import { useVirtual } from 'react-virtual'; import '../../../styles/base.module.css'; @@ -317,13 +316,12 @@ export const Combobox = (props: ComboboxProps) => { } }; - const ignoreFocusHandlerWhenClickingItem = (handler: FocusEventHandler) => ( - event: FocusEvent, - ) => { - if (!isClicking) { - handler(event); - } - }; + const ignoreFocusHandlerWhenClickingItem = + (handler: FocusEventHandler) => (event: FocusEvent) => { + if (!isClicking) { + handler(event); + } + }; const handleWrapperFocus = (e: FocusEvent) => { if (getIsElementFocused(e)) { diff --git a/packages/react/src/components/dropdown/dropdownUtils.ts b/packages/react/src/components/dropdown/dropdownUtils.ts index 0dc3239342..e46fa8c925 100644 --- a/packages/react/src/components/dropdown/dropdownUtils.ts +++ b/packages/react/src/components/dropdown/dropdownUtils.ts @@ -1,4 +1,4 @@ -import isEqual from 'lodash.isequal'; +import { isEqual } from 'lodash'; export const DROPDOWN_MENU_ITEM_HEIGHT = 52; diff --git a/packages/react/src/components/dropdown/select/Select.stories.tsx b/packages/react/src/components/dropdown/select/Select.stories.tsx index 3cee16580c..c8d8ffa3c6 100644 --- a/packages/react/src/components/dropdown/select/Select.stories.tsx +++ b/packages/react/src/components/dropdown/select/Select.stories.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; -import uniqueId from 'lodash.uniqueid'; +import { uniqueId } from 'lodash'; import { Button } from '../../button'; import { Select } from './Select'; diff --git a/packages/react/src/components/dropdown/select/Select.tsx b/packages/react/src/components/dropdown/select/Select.tsx index 03665a902a..1bf130ed32 100644 --- a/packages/react/src/components/dropdown/select/Select.tsx +++ b/packages/react/src/components/dropdown/select/Select.tsx @@ -9,8 +9,7 @@ import { UseMultipleSelectionStateChangeOptions, UseMultipleSelectionState, } from 'downshift'; -import isEqual from 'lodash.isequal'; -import uniqueId from 'lodash.uniqueid'; +import { isEqual, uniqueId } from 'lodash'; import { useVirtual } from 'react-virtual'; import '../../../styles/base.module.css'; diff --git a/packages/react/src/components/fileInput/FileInput.tsx b/packages/react/src/components/fileInput/FileInput.tsx index bf2202e2a7..b6111e74bf 100644 --- a/packages/react/src/components/fileInput/FileInput.tsx +++ b/packages/react/src/components/fileInput/FileInput.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; -import uniqueId from 'lodash.uniqueid'; +import { uniqueId } from 'lodash'; import '../../styles/base.module.css'; import composeAriaDescribedBy from '../../utils/composeAriaDescribedBy'; @@ -297,32 +297,37 @@ const getExtension = (path: string): string => { return extensionWithDot; }; -const validateAccept = (language: Language, accept: string) => (file: File): true | ValidationError => { - const extension: string = getExtension(file.name); - const fileType: string = file.type; - const acceptedExtensions = accept.split(',').map((str) => str.trim()); - const isMatchingType = !!acceptedExtensions.find( - (acceptExtension) => acceptExtension.includes(fileType) || acceptExtension.includes(`${fileType.split('/')[0]}/*`), - ); - const hasMatchingFileExtension = !!acceptedExtensions.find((acceptExtension) => acceptExtension === extension); +const validateAccept = + (language: Language, accept: string) => + (file: File): true | ValidationError => { + const extension: string = getExtension(file.name); + const fileType: string = file.type; + const acceptedExtensions = accept.split(',').map((str) => str.trim()); + const isMatchingType = !!acceptedExtensions.find( + (acceptExtension) => + acceptExtension.includes(fileType) || acceptExtension.includes(`${fileType.split('/')[0]}/*`), + ); + const hasMatchingFileExtension = !!acceptedExtensions.find((acceptExtension) => acceptExtension === extension); - return ( - isMatchingType || - hasMatchingFileExtension || { - type: ValidationErrorType.accept, - text: getAcceptErrorMessage(language, file, accept), - } - ); -}; + return ( + isMatchingType || + hasMatchingFileExtension || { + type: ValidationErrorType.accept, + text: getAcceptErrorMessage(language, file, accept), + } + ); + }; -const validateMaxSize = (language: Language, maxSize: number) => (file: File): true | ValidationError => { - return ( - file.size <= maxSize || { - type: ValidationErrorType.maxSize, - text: getMaxSizeErrorMessage(language, file, maxSize), - } - ); -}; +const validateMaxSize = + (language: Language, maxSize: number) => + (file: File): true | ValidationError => { + return ( + file.size <= maxSize || { + type: ValidationErrorType.maxSize, + text: getMaxSizeErrorMessage(language, file, maxSize), + } + ); + }; export const FileInput = ({ id, diff --git a/packages/react/src/components/header/Header.stories.tsx b/packages/react/src/components/header/Header.stories.tsx index 154e7a543e..67784bec63 100644 --- a/packages/react/src/components/header/Header.stories.tsx +++ b/packages/react/src/components/header/Header.stories.tsx @@ -281,7 +281,7 @@ const FullFeaturedNavigationMenu = ({ href, setHref, }: { - I18n: typeof translations['fi']; + I18n: (typeof translations)['fi']; href: string; setHref: (anchor: string) => void; }) => { diff --git a/packages/react/src/components/header/LanguageContext.test.tsx b/packages/react/src/components/header/LanguageContext.test.tsx index 23a9d6f62e..89d54064ae 100644 --- a/packages/react/src/components/header/LanguageContext.test.tsx +++ b/packages/react/src/components/header/LanguageContext.test.tsx @@ -180,7 +180,7 @@ describe('', () => { cleanup(); }); - it('Sets the defaultLanguage to DEFAULT_LANGUAGE by default. onDidChangeLanguage is not called ', async () => { + it('Sets the defaultLanguage to DEFAULT_LANGUAGE by default. onDidChangeLanguage is not called', async () => { const { getActiveLanguage } = renderTestScenario(); expect(getActiveLanguage()).toBe(DEFAULT_LANGUAGE); expect(handleLanguageChange.mock.calls.length).toBe(0); diff --git a/packages/react/src/components/header/components/headerActionBar/HeaderActionBar.tsx b/packages/react/src/components/header/components/headerActionBar/HeaderActionBar.tsx index 3c830583fe..e17bff679b 100644 --- a/packages/react/src/components/header/components/headerActionBar/HeaderActionBar.tsx +++ b/packages/react/src/components/header/components/headerActionBar/HeaderActionBar.tsx @@ -45,9 +45,9 @@ const focusToActionBar = (position: TabBarrierPosition, element?: HTMLElement) = const focusableElements = findFocusableElementsWithin(element); if (focusableElements.length) { - (focusableElements[ - position === TabBarrierPosition.top ? 0 : focusableElements.length - 1 - ] as HTMLElement).focus(); + ( + focusableElements[position === TabBarrierPosition.top ? 0 : focusableElements.length - 1] as HTMLElement + ).focus(); } } }; diff --git a/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.test.tsx b/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.test.tsx index d3bf4e2c6d..4a6b6b680e 100644 --- a/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.test.tsx +++ b/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.test.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { getActiveElement } from '../../../cookieConsent/test.util'; import { Header } from '../../Header'; // eslint-disable-next-line jest/no-mocks-import -import mockWindowLocation from '../../../login/__mocks__/mockWindowLocation'; +import mockWindowLocation from '../../../../utils/mockWindowLocation'; jest.mock('../../../../hooks/useMediaQuery', () => ({ ...(jest.requireActual('../../../../hooks/useMediaQuery') as Record), @@ -397,14 +397,8 @@ describe(' spec', () => { expect(getNavSections()).toHaveLength(1); }); it('Nav sections are rendered and visible only when needed and removed when not needed', async () => { - const { - openMobileMenu, - navigateTo, - getNavSections, - navigateBack, - closeMobileMenu, - getCSSVisibleSections, - } = renderHeader(); + const { openMobileMenu, navigateTo, getNavSections, navigateBack, closeMobileMenu, getCSSVisibleSections } = + renderHeader(); // one is always rendered expect(getNavSections()).toHaveLength(1); // but it is hidden @@ -425,14 +419,8 @@ describe(' spec', () => { expect(getNavSections()).toHaveLength(1); }); it('Previous and active links change while navigating', async () => { - const { - openMobileMenu, - navigateTo, - navigateBack, - verifyActiveItem, - verifyPreviousItem, - getCSSVisibleSections, - } = renderHeader(); + const { openMobileMenu, navigateTo, navigateBack, verifyActiveItem, verifyPreviousItem, getCSSVisibleSections } = + renderHeader(); const menu3 = getMenuItem([3]); const menu31 = getMenuItem([3, 1]); const menu32 = getMenuItem([3, 2]); @@ -491,13 +479,8 @@ describe(' spec', () => { }); }); it('If a lower level active link is clicked, menu is closed and onClick handler is called.', async () => { - const { - openMobileMenu, - getActiveLink, - selectMenuItem, - getCSSVisibleSections, - triggerMenuAnimationEnd, - } = renderHeader(); + const { openMobileMenu, getActiveLink, selectMenuItem, getCSSVisibleSections, triggerMenuAnimationEnd } = + renderHeader(); await openMobileMenu(); await selectMenuItem(getMenuItem([0])); diff --git a/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.tsx b/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.tsx index 48d44da911..b52ef9d465 100644 --- a/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.tsx +++ b/packages/react/src/components/header/components/headerActionBar/HeaderActionBarNavigationMenu.tsx @@ -321,7 +321,7 @@ export const HeaderActionBarNavigationMenu = ({ const getLinksOrChildren = (parent: React.ReactElement) => { return parent.props && parent.props.dropdownLinks ? parent.props.dropdownLinks - : getChildrenAsArray(((parent as unknown) as React.PropsWithChildren).children); + : getChildrenAsArray((parent as unknown as React.PropsWithChildren).children); }; // Picks given child by MenuInfo.index diff --git a/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItem.module.scss b/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItem.module.scss index 421d3f53a9..2dc194565f 100644 --- a/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItem.module.scss +++ b/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItem.module.scss @@ -1,5 +1,5 @@ -@use "../../Header.module.scss"; -@use "../../../../styles/common.scss"; +@use '../../Header.module.scss'; +@use '../../../../styles/common.scss'; button.actionBarItem { align-self: center; @@ -22,8 +22,8 @@ button.actionBarItem { height: var(--icon-size); margin: 0 auto; padding: 3px 0 0; - width: var(--icon-size); pointer-events: none; + width: var(--icon-size); & > svg { display: block; @@ -37,8 +37,8 @@ button.actionBarItem { display: block; font-size: var(--action-bar-item-title-font-size); margin: 0 auto; - white-space: nowrap; pointer-events: none; + white-space: nowrap; & > svg { display: block; diff --git a/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItemWithDropdown.module.scss b/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItemWithDropdown.module.scss index ab1f80efeb..1ac2107557 100644 --- a/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItemWithDropdown.module.scss +++ b/packages/react/src/components/header/components/headerActionBarItem/HeaderActionBarItemWithDropdown.module.scss @@ -1,73 +1,71 @@ -@layer actionBar { - .dropdownWrapper { - bottom: 0; - overflow: hidden; - position: absolute; - right: 0; - transform: translateY(100%) translateY(1px); - transition-duration: 0ms; - transition-property: max-height, padding-bottom; - z-index: 20; - } - - .container { - display: flex; +.dropdownWrapper { + bottom: 0; + overflow: hidden; + position: absolute; + right: 0; + transform: translateY(100%) translateY(1px); + transition-duration: 0ms; + transition-property: max-height, padding-bottom; + z-index: 20; +} - .dropdownWrapper { - max-height: 0; - min-width: 300px; - padding-bottom: 0; - transition-delay: calc(var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown)); - } +.container { + display: flex; - &.visible .dropdownWrapper { - max-height: 1000vh; - padding-bottom: 20px; - transition-delay: 0ms; - } + .dropdownWrapper { + max-height: 0; + min-width: 300px; + padding-bottom: 0; + transition-delay: calc(var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown)); + } - &:not(.hasContent) { - display: none; - } + &.visible .dropdownWrapper { + max-height: 1000vh; + padding-bottom: 20px; + transition-delay: 0ms; + } - &:not(.fullWidth) { - display: flex; - position: relative; - } + &:not(.hasContent) { + display: none; + } - &.fullWidth > .dropdownWrapper { - left: 0; - } + &:not(.fullWidth) { + display: flex; + position: relative; } - .icon { - display: block; + &.fullWidth>.dropdownWrapper { + left: 0; } +} - .dropdown { - background: white; - border: 1px solid var(--color-black-10); - margin-top: -1px; - position: relative; - right: 0; - transform: translateY(-100%); - transition: var(--animation-duration-dropwdown) transform var(--animation-close-delay-dropdown), - 0ms visibility calc((var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown))); - visibility: hidden; +.icon { + display: block; +} - &.fullWidth { - left: 0; - } +.dropdown { + background: white; + border: 1px solid var(--color-black-10); + margin-top: -1px; + position: relative; + right: 0; + transform: translateY(-100%); + transition: var(--animation-duration-dropwdown) transform var(--animation-close-delay-dropdown), + 0ms visibility calc((var(--animation-duration-dropwdown) + var(--animation-close-delay-dropdown))); + visibility: hidden; - &.visible { - transform: translateY(0%); - transition: var(--animation-duration-dropwdown) transform 0ms, 0ms visibility 0ms; - visibility: visible; - } + &.fullWidth { + left: 0; + } - > * { - padding: var(--spacing-xs) var(--spacing-s); - width: 100%; - } + &.visible { + transform: translateY(0%); + transition: var(--animation-duration-dropwdown) transform 0ms, 0ms visibility 0ms; + visibility: visible; } -} + + >* { + padding: var(--spacing-xs) var(--spacing-s); + width: 100%; + } +} \ No newline at end of file diff --git a/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.test.tsx b/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.test.tsx index 6c706e55fe..a3851b78b3 100644 --- a/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.test.tsx +++ b/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.test.tsx @@ -120,7 +120,7 @@ describe(' spec', () => { expect(asFragment()).toMatchSnapshot(); }); - it('renders the consumer component with props and children passed from the LanguageSelector ', () => { + it('renders the consumer component with props and children passed from the LanguageSelector', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.tsx b/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.tsx index df6ca3fe2f..eaeb15aace 100644 --- a/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.tsx +++ b/packages/react/src/components/header/components/headerLanguageSelector/HeaderLanguageSelector.tsx @@ -9,6 +9,7 @@ import classes from './HeaderLanguageSelector.module.scss'; import { useHeaderContext } from '../../HeaderContext'; import { getComponentFromChildren } from '../../../../utils/getChildren'; +/* eslint-disable react/no-unused-prop-types */ type LanguageSelectorComponentProps = { /** * Aria-label attribute for the dropdown button. @@ -23,6 +24,7 @@ type LanguageSelectorComponentProps = { */ sortLanguageOptions?: (options: LanguageOption[], selectedLanguage: string) => [LanguageOption[], LanguageOption[]]; }; +/* eslint-enable react/no-unused-prop-types */ export type LanguageSelectorProps = PropsWithChildren; diff --git a/packages/react/src/components/header/components/headerLanguageSelector/__snapshots__/HeaderLanguageSelector.test.tsx.snap b/packages/react/src/components/header/components/headerLanguageSelector/__snapshots__/HeaderLanguageSelector.test.tsx.snap index 099b477533..09cd60671f 100644 --- a/packages/react/src/components/header/components/headerLanguageSelector/__snapshots__/HeaderLanguageSelector.test.tsx.snap +++ b/packages/react/src/components/header/components/headerLanguageSelector/__snapshots__/HeaderLanguageSelector.test.tsx.snap @@ -152,7 +152,7 @@ exports[` spec renders the consumer component correct `; -exports[` spec renders the consumer component with props and children passed from the LanguageSelector 1`] = ` +exports[` spec renders the consumer component with props and children passed from the LanguageSelector 1`] = `
; diff --git a/packages/react/src/components/koros/Koros.tsx b/packages/react/src/components/koros/Koros.tsx index 34ebf96a3d..2947920d5d 100644 --- a/packages/react/src/components/koros/Koros.tsx +++ b/packages/react/src/components/koros/Koros.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import uniqueId from 'lodash.uniqueid'; +import { uniqueId } from 'lodash'; import '../../styles/base.module.css'; import classNames from '../../utils/classNames'; diff --git a/packages/react/src/components/loadingSpinner/useNotificationArea.ts b/packages/react/src/components/loadingSpinner/useNotificationArea.ts index 965efe1ec5..f30005259b 100644 --- a/packages/react/src/components/loadingSpinner/useNotificationArea.ts +++ b/packages/react/src/components/loadingSpinner/useNotificationArea.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState, useCallback } from 'react'; -import uniqueId from 'lodash.uniqueid'; +import { uniqueId } from 'lodash'; import styles from './LoadingSpinner.module.scss'; diff --git a/packages/react/src/components/login/apiTokensClient/apiTokenClient.test.ts b/packages/react/src/components/login/apiTokensClient/apiTokenClient.test.ts index 1999d7159b..d730a55029 100644 --- a/packages/react/src/components/login/apiTokensClient/apiTokenClient.test.ts +++ b/packages/react/src/components/login/apiTokensClient/apiTokenClient.test.ts @@ -118,14 +118,8 @@ describe(`apiTokenClient`, () => { removeUserAfterInit: false, }; - const { - waitUntilRequestFinished, - waitUntilRequestStarted, - cleanUp, - setResponders, - addResponse, - getRequestCount, - } = createControlledFetchMockUtil([{ path: endPointPath }]); + const { waitUntilRequestFinished, waitUntilRequestStarted, cleanUp, setResponders, addResponse, getRequestCount } = + createControlledFetchMockUtil([{ path: endPointPath }]); const getApiTokenResponseBody = () => { return { @@ -256,6 +250,7 @@ describe(`apiTokenClient`, () => { await advanceUntilPromiseResolvedAndReturnValue(apiTokenClient.fetch(currentUser as User)); const fetchCalls = getFetchMockCalls(); expect(fetchCalls).toHaveLength(1); + // eslint-disable-next-line jest/no-interpolation-in-snapshots expect(fetchCalls[0]).toMatchInlineSnapshot(` Array [ "${defaultClientProps.url}", @@ -340,7 +335,7 @@ describe(`apiTokenClient`, () => { expect(emittedErrors).toHaveLength(1); expect(emittedErrors[0].isInvalidTokensError).toBeTruthy(); }); - it('Returns a fetch error when fetch fails for a network error or other status. An error is emitted. ', async () => { + it('Returns a fetch error when fetch fails for a network error or other status. An error is emitted.', async () => { const apiTokenClient = initTests({ apiTokenClientProps: { maxRetries: 0 }, responses: [{ error: true }, errorResponse], @@ -379,6 +374,7 @@ describe(`apiTokenClient`, () => { const fetchCalls = getFetchMockCalls(); const fetchCallBodies = fetchCalls.map(getFetchRequestBodyAsString); expect(fetchCallBodies[0]).toBe(`grant_type=${grantType}&permission=${permission}`); + // eslint-disable-next-line jest/no-interpolation-in-snapshots expect(fetchCalls[0]).toMatchInlineSnapshot(` Array [ "${defaultClientProps.url}", @@ -500,7 +496,7 @@ describe(`apiTokenClient`, () => { }); }); }); - describe(`When init signal is received `, () => { + describe(`When init signal is received`, () => { it('and user matches api token reference, tokens are restored from storage. API_TOKENS_UPDATED event is emitted', async () => { const tokens = { token: 'a token' }; const userAccessToken = 'an access token'; @@ -609,7 +605,7 @@ describe(`apiTokenClient`, () => { expect(apiTokenClient.isRenewing()).toBeFalsy(); }); - it('tokens are not renewed, if USER_UPDATED event has no valid user ', async () => { + it('tokens are not renewed, if USER_UPDATED event has no valid user', async () => { const apiTokenClient = await initTestsWithDefaultPropsAndWaitForInit({ removeUserAfterInit: true }); const eventPayloads = getEmittedEventPayloads(); expect(eventPayloads).toHaveLength(2); diff --git a/packages/react/src/components/login/apiTokensClient/apiTokensClientError.ts b/packages/react/src/components/login/apiTokensClient/apiTokensClientError.ts index 0f0c796b6f..409c4e96cc 100644 --- a/packages/react/src/components/login/apiTokensClient/apiTokensClientError.ts +++ b/packages/react/src/components/login/apiTokensClient/apiTokensClientError.ts @@ -7,7 +7,11 @@ export const apiTokensClientError = { } as const; export class ApiTokensClientError extends Error { - constructor(public message: string, public type: ApiTokensClientErrorType, public originalError?: Error | null) { + constructor( + public message: string, + public type: ApiTokensClientErrorType, + public originalError?: Error | null, + ) { super(message); this.type = type; this.originalError = originalError; diff --git a/packages/react/src/components/login/apiTokensClient/hooks.test.tsx b/packages/react/src/components/login/apiTokensClient/hooks.test.tsx index 1c3730674f..9134e880f2 100644 --- a/packages/react/src/components/login/apiTokensClient/hooks.test.tsx +++ b/packages/react/src/components/login/apiTokensClient/hooks.test.tsx @@ -58,13 +58,8 @@ describe('apiToken hooks testing', () => { const apiTokensResponder: Responder = { id: 'apiTokensResponder', path: endPointPath, delay: 10000 }; - const { - waitUntilRequestFinished, - waitUntilRequestStarted, - cleanUp, - setResponders, - addResponse, - } = createControlledFetchMockUtil([apiTokensResponder]); + const { waitUntilRequestFinished, waitUntilRequestStarted, cleanUp, setResponders, addResponse } = + createControlledFetchMockUtil([apiTokensResponder]); const getApiTokenResponseBody = () => { return { @@ -218,7 +213,7 @@ describe('apiToken hooks testing', () => { }); }); describe('useApiTokens hook', () => { - describe('Returns getStoredApiTokens function ', () => { + describe('Returns getStoredApiTokens function', () => { it('which returns apiTokens, if user is authenticated and apiTokens are fetched', async () => { const user = createUserAndPlaceUserToStorage(defaultOidcClientProps.userManagerSettings); @@ -241,7 +236,7 @@ describe('apiToken hooks testing', () => { expect(getTokensError()).not.toBeUndefined(); }); }); - describe('Returns isRenewing function ', () => { + describe('Returns isRenewing function', () => { it('which returns true, if apiTokens are renewing', async () => { createUserAndPlaceUserToStorage(defaultOidcClientProps.userManagerSettings); init({ component: 'tokens' }); diff --git a/packages/react/src/components/login/beacon/beacon.test.ts b/packages/react/src/components/login/beacon/beacon.test.ts index a67708eb64..f68fb62ede 100644 --- a/packages/react/src/components/login/beacon/beacon.test.ts +++ b/packages/react/src/components/login/beacon/beacon.test.ts @@ -173,7 +173,7 @@ describe(`beacon`, () => { expect(trigger({ type: '*', namespace: '*' })).toBeFalsy(); expect(trigger({ type: '', namespace: '' })).toBeFalsy(); }); - it(`Trigger with signal type "*" and given namespace listens to all signals in that namespace `, async () => { + it(`Trigger with signal type "*" and given namespace listens to all signals in that namespace`, async () => { const trigger = createSignalTrigger('*:signalNamespace'); expect(trigger({ type: 'signalType', namespace: 'signalNamespace' })).toBeTruthy(); expect(trigger({ type: 'any', namespace: 'signalNamespace' })).toBeTruthy(); @@ -451,7 +451,7 @@ describe(`beacon`, () => { }); }); - describe(`When emitting signals, the signal.context is `, () => { + describe(`When emitting signals, the signal.context is`, () => { const syncListener = jest.fn(); const syncListener2 = jest.fn(); const syncSignalType = 'sync'; diff --git a/packages/react/src/components/login/beacon/beacon.ts b/packages/react/src/components/login/beacon/beacon.ts index df9ccf2e9a..a66906a05a 100644 --- a/packages/react/src/components/login/beacon/beacon.ts +++ b/packages/react/src/components/login/beacon/beacon.ts @@ -65,7 +65,7 @@ export function convertToComparableSignals( // If source has context, it is a deep object with unnecessary key/values to compare. // Only important prop is the namespace, so just pick that if (!source.context) { - return (source as unknown) as SignalTriggerProps; + return source as unknown as SignalTriggerProps; } return { ...source, context: source.context ? { namespace: source.context.namespace } : undefined }; } @@ -87,7 +87,7 @@ export function compareSignals(signalOrJustType: SignalType | Partial, s // If source has no context, it is not compared to the target, so no need to convert it. const target = source.context ? convertToComparableSignals(signalToCheckFrom) - : ((signalToCheckFrom as unknown) as SignalTriggerProps); + : (signalToCheckFrom as unknown as SignalTriggerProps); return compareSignalTriggers(source, target); } @@ -103,7 +103,7 @@ export function createSignalTrigger(signalOrJustSignalType: SignalType | Signal } const target = source.context ? convertToComparableSignals(incomingSignal) - : ((incomingSignal as unknown) as SignalTriggerProps); + : (incomingSignal as unknown as SignalTriggerProps); return compareSignalTriggers(source, target); }; } diff --git a/packages/react/src/components/login/beacon/hooks.ts b/packages/react/src/components/login/beacon/hooks.ts index 94e97711b2..e9f375d16e 100644 --- a/packages/react/src/components/login/beacon/hooks.ts +++ b/packages/react/src/components/login/beacon/hooks.ts @@ -59,7 +59,7 @@ const useSignalTracking = (signalProps: SignalListenerSource, callback?: SignalL return signalProps as SignalTrigger; } return (signal: Signal) => { - return compareSignalTriggers(signalProps as SignalTriggerProps, (signal as unknown) as SignalTriggerProps); + return compareSignalTriggers(signalProps as SignalTriggerProps, signal as unknown as SignalTriggerProps); }; }, []); const callbackRef = useRef(callback); diff --git a/packages/react/src/components/login/beacon/signals.test.ts b/packages/react/src/components/login/beacon/signals.test.ts index d68c1e7427..2ca3636250 100644 --- a/packages/react/src/components/login/beacon/signals.test.ts +++ b/packages/react/src/components/login/beacon/signals.test.ts @@ -61,7 +61,7 @@ describe(`signals`, () => { const allSignals = [...eventSignals, ...errorSignals]; const allSources = [...plainObjects, ...allSignals]; - describe(`and returns true, `, () => { + describe(`and returns true,`, () => { it(`if all source object key/value pairs match target key/value pairs`, async () => { allSources.forEach((signal) => { expect(compareSignals(signal, signal as Signal)).toBeTruthy(); @@ -69,7 +69,7 @@ describe(`signals`, () => { }); it(`also when target has more keys that source`, async () => { allSources.forEach((signal) => { - expect(compareSignals(signal, ({ ...signal, extra: 'value' } as unknown) as Signal)).toBeTruthy(); + expect(compareSignals(signal, { ...signal, extra: 'value' } as unknown as Signal)).toBeTruthy(); }); eventSignals.forEach((signal) => { expect(compareSignals(signal, { ...eventSignalWithNamespaceBPayloadBContextB, ...signal })).toBeTruthy(); @@ -99,7 +99,7 @@ describe(`signals`, () => { }); }); }); - describe(`and returns false, `, () => { + describe(`and returns false,`, () => { it(`if all source object key/value pairs does not match target key/value pairs`, async () => { allSignals.forEach((signal) => { expect(compareSignals({ ...signal, type: 'X' }, signal)).toBeFalsy(); @@ -107,7 +107,7 @@ describe(`signals`, () => { expect(compareSignals({ ...signal, payload: { ...payloadA, ...payloadB } }, signal)).toBeFalsy(); expect( compareSignals( - { ...signal, context: ({ ...contextA, namespace: 'Z' } as unknown) as ConnectedModule }, + { ...signal, context: { ...contextA, namespace: 'Z' } as unknown as ConnectedModule }, signal, ), ).toBeFalsy(); @@ -120,7 +120,7 @@ describe(`signals`, () => { }); it(`also when source has more keys that target`, async () => { allSignals.forEach((signal) => { - expect(compareSignals(({ ...signal, extra: 'value' } as unknown) as Signal, signal)).toBeFalsy(); + expect(compareSignals({ ...signal, extra: 'value' } as unknown as Signal, signal)).toBeFalsy(); }); }); }); @@ -194,7 +194,7 @@ describe(`signals`, () => { signalListTypes[0], ]); }); - it(`if options.allowSkipping is true (default), then all listed signals before the triggered one are marked as triggered. Results have only triggered signals. `, async () => { + it(`if options.allowSkipping is true (default), then all listed signals before the triggered one are marked as triggered. Results have only triggered signals.`, async () => { const promise = waitForSignals(beacon, signalList) .then((signals) => { result = signals; @@ -205,7 +205,7 @@ describe(`signals`, () => { await promise; expect(result.map((signal) => signal.type)).toEqual([signalListTypes[1], signalListTypes[3]]); }); - it(`if options.allowSkipping is true (default), then one signal can fulfill the promise `, async () => { + it(`if options.allowSkipping is true (default), then one signal can fulfill the promise`, async () => { const promise = waitForSignals(beacon, [...signalList, ...signalList, fulfillingSignal]) .then((signals) => { result = signals; @@ -215,7 +215,7 @@ describe(`signals`, () => { await promise; expect(result.map((signal) => signal.type)).toEqual([fulfillingSignal.type]); }); - it(`if options.rejectsOn has triggers, any of the triggers will reject the promise `, async () => { + it(`if options.rejectsOn has triggers, any of the triggers will reject the promise`, async () => { const promise = waitForSignals(beacon, signalList, { rejectOn: signalList }) .then((signals) => { result = signals; @@ -226,7 +226,7 @@ describe(`signals`, () => { expect(result).toHaveLength(0); expect(rejections).toHaveLength(1); }); - it(`if options.allowSkipping is false, catching a signal in wrong order will reject `, async () => { + it(`if options.allowSkipping is false, catching a signal in wrong order will reject`, async () => { const promise = waitForSignals(beacon, signalList, { strictOrder: true }) .then((signals) => { result = signals; diff --git a/packages/react/src/components/login/beacon/signals.ts b/packages/react/src/components/login/beacon/signals.ts index de1789a5a0..fe561b58a4 100644 --- a/packages/react/src/components/login/beacon/signals.ts +++ b/packages/react/src/components/login/beacon/signals.ts @@ -209,7 +209,7 @@ export function createTriggerForAllNamespaces(): Pick { export function convertSignalToTrigger(signal: Signal): SignalTriggerProps { if (!Object.prototype.hasOwnProperty.call(signal, 'context')) { - return (signal as unknown) as SignalTriggerProps; + return signal as unknown as SignalTriggerProps; } const { type, namespace, payload } = signal; return { type, namespace, payload }; diff --git a/packages/react/src/components/login/client/hooks.test.tsx b/packages/react/src/components/login/client/hooks.test.tsx index d41039b08b..a99e6ea3bb 100644 --- a/packages/react/src/components/login/client/hooks.test.tsx +++ b/packages/react/src/components/login/client/hooks.test.tsx @@ -40,7 +40,7 @@ describe('Client hooks', () => { }); it('Returns an user object, if valid user is found', async () => { init({ invalidUser: false }); - expect(((getUser() as unknown) as User).access_token).not.toBeUndefined(); + expect((getUser() as unknown as User).access_token).not.toBeUndefined(); }); it('Returns null, if a valid user is not found', async () => { init({ diff --git a/packages/react/src/components/login/client/oidcClient.test.ts b/packages/react/src/components/login/client/oidcClient.test.ts index d529a7b8f0..affb205d8b 100644 --- a/packages/react/src/components/login/client/oidcClient.test.ts +++ b/packages/react/src/components/login/client/oidcClient.test.ts @@ -2,8 +2,7 @@ import to from 'await-to-js'; import { User, UserManagerSettings, SigninResponse } from 'oidc-client-ts'; import { waitFor } from '@testing-library/react'; -// eslint-disable-next-line jest/no-mocks-import -import mockWindowLocation from '../__mocks__/mockWindowLocation'; +import mockWindowLocation from '../../../utils/mockWindowLocation'; // eslint-disable-next-line jest/no-mocks-import import { jwtWithHelloStringAmr, jwtWithHelloWorldArrayAmr } from '../__mocks__/jwtTokens'; import { @@ -47,14 +46,8 @@ import { Signal, SignalNamespace } from '../beacon/beacon'; import { getAllMockCallArgs, getLastMockCallArgs } from '../../../utils/testHelpers'; import { waitForFetchMockRequestsToFinish } from '../testUtils/fetchMockTestUtil'; -const { - initTests, - waitForLoginToTimeout, - waitForLogoutToTimeout, - cleanUp, - setSignInResponse, - placeUserToStorage, -} = createOidcClientTestSuite(); +const { initTests, waitForLoginToTimeout, waitForLogoutToTimeout, cleanUp, setSignInResponse, placeUserToStorage } = + createOidcClientTestSuite(); describe('oidcClient', () => { let testData: InitTestResult; @@ -303,12 +296,12 @@ describe('oidcClient', () => { const { oidcClient } = await initTests(createUserProps('invalid')); expect(oidcClient.getAmr()).toBeUndefined(); }); - it('returns undefined, if amr has invalid value and id_token is not set ', async () => { + it('returns undefined, if amr has invalid value and id_token is not set', async () => { const amrValue = 'notAnArray'; const initProps = { ...createUserProps(undefined), }; - ((initProps.userProps.signInResponseProfileProps as unknown) as Record).amr = amrValue; + (initProps.userProps.signInResponseProfileProps as unknown as Record).amr = amrValue; const { oidcClient } = await initTests(initProps); expect(oidcClient.getAmr()).toBeUndefined(); }); @@ -739,7 +732,7 @@ describe('oidcClient', () => { }); }); describe('Manual renewal can be started with renewUser()', () => { - it('It is handled like the automatic renewal', async () => { + it('is handled like the automatic renewal', async () => { const { oidcClient, waitForRefreshToEnd, refreshTokens } = await initRenewalTests(true, true); expect(oidcClient.isRenewing()).toBeFalsy(); const initialUser = oidcClient.getUser() as User; diff --git a/packages/react/src/components/login/client/oidcClient.ts b/packages/react/src/components/login/client/oidcClient.ts index cc055d72ca..39fd17428b 100644 --- a/packages/react/src/components/login/client/oidcClient.ts +++ b/packages/react/src/components/login/client/oidcClient.ts @@ -175,9 +175,9 @@ export function createOidcClient(props: OidcClientProps): OidcClient { ]); }; - const handleUserRenewal = async ({ triggerSigninSilent = false }: { triggerSigninSilent?: boolean } = {}): Promise< - RenewalResult - > => { + const handleUserRenewal = async ({ + triggerSigninSilent = false, + }: { triggerSigninSilent?: boolean } = {}): Promise => { if (isRenewing()) { return renewPromise; } diff --git a/packages/react/src/components/login/client/oidcClientError.ts b/packages/react/src/components/login/client/oidcClientError.ts index 699acaa6af..c49fbbc785 100644 --- a/packages/react/src/components/login/client/oidcClientError.ts +++ b/packages/react/src/components/login/client/oidcClientError.ts @@ -6,7 +6,11 @@ export const oidcClientErrors = { } as const; export class OidcClientError extends Error { - constructor(public message: string, public type: OidcClientErrorType, public originalError?: Error | null) { + constructor( + public message: string, + public type: OidcClientErrorType, + public originalError?: Error | null, + ) { super(message); this.type = type; this.originalError = originalError; diff --git a/packages/react/src/components/login/components/LoginButton.tsx b/packages/react/src/components/login/components/LoginButton.tsx index e2dc26f526..9c25510fb5 100644 --- a/packages/react/src/components/login/components/LoginButton.tsx +++ b/packages/react/src/components/login/components/LoginButton.tsx @@ -46,16 +46,14 @@ export function LoginButton({ }, }; return ( - <> -
- - {loginError && ( -
- - {errorText} -
- )} -
- +
+ + {loginError && ( +
+ + {errorText} +
+ )} +
); } diff --git a/packages/react/src/components/login/components/LoginCallbackHandler.test.tsx b/packages/react/src/components/login/components/LoginCallbackHandler.test.tsx index c9fa6e3d20..1e6f555163 100644 --- a/packages/react/src/components/login/components/LoginCallbackHandler.test.tsx +++ b/packages/react/src/components/login/components/LoginCallbackHandler.test.tsx @@ -59,7 +59,7 @@ describe('LoginCallbackHandler', () => { ); }; - it('calls onSuccess with user object, if user already exists ', async () => { + it('calls onSuccess with user object, if user already exists', async () => { renderComponent(oidcClientStates.VALID_SESSION, true); expect(onSuccess).toHaveBeenCalledTimes(1); expect(isValidUser(getLastMockCallArgs(onSuccess)[0])).toBeTruthy(); diff --git a/packages/react/src/components/login/components/LoginCallbackHandler.tsx b/packages/react/src/components/login/components/LoginCallbackHandler.tsx index e9a635bb5f..89723f64d2 100644 --- a/packages/react/src/components/login/components/LoginCallbackHandler.tsx +++ b/packages/react/src/components/login/components/LoginCallbackHandler.tsx @@ -41,5 +41,6 @@ export function LoginCallbackHandler({ ), ); } + // eslint-disable-next-line react/jsx-no-useless-fragment return <>{children}; } diff --git a/packages/react/src/components/login/components/WithoutAuthenticatedUser.tsx b/packages/react/src/components/login/components/WithoutAuthenticatedUser.tsx index e21ccb7bcd..3afc4b75c8 100644 --- a/packages/react/src/components/login/components/WithoutAuthenticatedUser.tsx +++ b/packages/react/src/components/login/components/WithoutAuthenticatedUser.tsx @@ -8,6 +8,7 @@ import { WithAuthentication } from './WithAuthentication'; */ export function WithoutAuthenticatedUser(props: React.PropsWithChildren): React.ReactElement | null { const UnauthorisedComponent = () => { + // eslint-disable-next-line react/jsx-no-useless-fragment return <>{props.children}; }; return ; diff --git a/packages/react/src/components/login/sessionPoller/hooks.test.tsx b/packages/react/src/components/login/sessionPoller/hooks.test.tsx index 170cec3136..e0992dbf91 100644 --- a/packages/react/src/components/login/sessionPoller/hooks.test.tsx +++ b/packages/react/src/components/login/sessionPoller/hooks.test.tsx @@ -37,13 +37,8 @@ describe('sessionPoller hooks testing', () => { const endPointPath = '/userInfoEndPoint'; const successfulResponse: ResponseType = { returnedStatus: HttpStatusCode.OK }; const unauthorizedResponse: ResponseType = { returnedStatus: HttpStatusCode.UNAUTHORIZED }; - const { - waitUntilRequestStarted, - waitUntilRequestFinished, - cleanUp, - setResponders, - addResponse, - } = createControlledFetchMockUtil([{ path: endPointPath }]); + const { waitUntilRequestStarted, waitUntilRequestFinished, cleanUp, setResponders, addResponse } = + createControlledFetchMockUtil([{ path: endPointPath }]); const openIdResponse = { status: HttpStatusCode.OK, diff --git a/packages/react/src/components/login/sessionPoller/sessionPoller.test.ts b/packages/react/src/components/login/sessionPoller/sessionPoller.test.ts index 771c7585db..4993933303 100644 --- a/packages/react/src/components/login/sessionPoller/sessionPoller.test.ts +++ b/packages/react/src/components/login/sessionPoller/sessionPoller.test.ts @@ -382,7 +382,7 @@ describe(`sessionPoller`, () => { await waitForFetchMockResultFulfillment(0); }); - it('when fetch fails and statusCode indicates an auth error, the error signal is sent and polling is stopped ', async () => { + it('when fetch fails and statusCode indicates an auth error, the error signal is sent and polling is stopped', async () => { initTests({ setValidSession: true, responses: [successfulResponse, unauthorizedResponse, forbiddenResponse] }); await waitUntilRequestFinished(); const result = (await waitForFetchMockResultFulfillment(0)) as Response; diff --git a/packages/react/src/components/login/sessionPoller/sessionPollerError.ts b/packages/react/src/components/login/sessionPoller/sessionPollerError.ts index 0ae0105588..a3d320bd18 100644 --- a/packages/react/src/components/login/sessionPoller/sessionPollerError.ts +++ b/packages/react/src/components/login/sessionPoller/sessionPollerError.ts @@ -6,7 +6,11 @@ export const sessionPollerErrors = { } as const; export class SessionPollerError extends Error { - constructor(public message: string, public type: SessionPollerErrorType, public originalError?: Error | null) { + constructor( + public message: string, + public type: SessionPollerErrorType, + public originalError?: Error | null, + ) { super(message); this.type = type; this.originalError = originalError; diff --git a/packages/react/src/components/login/sessionPoller/signals.test.ts b/packages/react/src/components/login/sessionPoller/signals.test.ts index 9550db8b2a..45347b0b63 100644 --- a/packages/react/src/components/login/sessionPoller/signals.test.ts +++ b/packages/react/src/components/login/sessionPoller/signals.test.ts @@ -30,11 +30,9 @@ import { oidcClientNamespace } from '../client'; describe(`signals`, () => { const eventData = { tokens: 'token' }; - const eventChecks: Array<[ - typeof isSessionPollerStartedSignal, - SessionPollerEvent, - typeof sessionPollingStartedTrigger, - ]> = [ + const eventChecks: Array< + [typeof isSessionPollerStartedSignal, SessionPollerEvent, typeof sessionPollingStartedTrigger] + > = [ [isSessionPollerStartedSignal, sessionPollerEvents.SESSION_POLLING_STARTED, sessionPollingStartedTrigger], [isSessionPollerStoppedSignal, sessionPollerEvents.SESSION_POLLING_STOPPED, sessionPollingStoppedTrigger], ]; diff --git a/packages/react/src/components/login/testUtils/oidcClientTestUtil.ts b/packages/react/src/components/login/testUtils/oidcClientTestUtil.ts index 6c1f60f79b..94e0b50a33 100644 --- a/packages/react/src/components/login/testUtils/oidcClientTestUtil.ts +++ b/packages/react/src/components/login/testUtils/oidcClientTestUtil.ts @@ -19,7 +19,7 @@ import openIdConfiguration from '../__mocks__/openIdConfiguration.json'; import { UserCreationProps, createSignInResponse, createUserAndPlaceUserToStorage } from './userTestUtil'; import { Beacon, ConnectedModule, createBeacon } from '../beacon/beacon'; // eslint-disable-next-line jest/no-mocks-import -import { MockedWindowLocationActions } from '../__mocks__/mockWindowLocation'; +import { MockedWindowLocationActions } from '../../../utils/mockWindowLocation'; export type InitTestResult = { oidcClient: OidcClient; @@ -50,9 +50,11 @@ export function getDefaultOidcClientTestProps(): OidcClientProps { } export function getPrivateUserManagerClient(userManager: UserManager): OidcClientFromNpm { - const client = ((userManager as unknown) as { - _client: OidcClientFromNpm; - })._client; + const client = ( + userManager as unknown as { + _client: OidcClientFromNpm; + } + )._client; return client; } @@ -174,7 +176,7 @@ export function createMockUserManager(userMock: jest.Mock): UserManager { getUser: userMock, }; - return (manager as unknown) as UserManager; + return manager as unknown as UserManager; } export function createMockOidcClient() { diff --git a/packages/react/src/components/login/testUtils/timerTestUtil.ts b/packages/react/src/components/login/testUtils/timerTestUtil.ts index 5e60243f3e..ce8a5063eb 100644 --- a/packages/react/src/components/login/testUtils/timerTestUtil.ts +++ b/packages/react/src/components/login/testUtils/timerTestUtil.ts @@ -18,7 +18,7 @@ export function createTimedPromise(response: unknown, delay = 1000) { export async function advanceUntilListenerCalled(listener: jest.Mock, advanceTime = 200) { await waitFor(() => { jest.advanceTimersByTime(advanceTime); - expect(listener).toBeCalledTimes(1); + expect(listener).toHaveBeenCalledTimes(1); }); return getLastMockCallArgs(listener); } diff --git a/packages/react/src/components/login/testUtils/userTestUtil.ts b/packages/react/src/components/login/testUtils/userTestUtil.ts index 5e6b35cb2d..52c2f15433 100644 --- a/packages/react/src/components/login/testUtils/userTestUtil.ts +++ b/packages/react/src/components/login/testUtils/userTestUtil.ts @@ -72,12 +72,12 @@ export function createUser(userCreationProps: UserCreationProps = {}): User { ...response, expired: userCreationProps.expiredUser, }; - return ({ + return { ...user, toStorageString() { return JSON.stringify(this); }, - } as unknown) as User; + } as unknown as User; } export function createUserAndPlaceUserToStorage( @@ -91,7 +91,7 @@ export function createUserAndPlaceUserToStorage( } export function raiseUserUserManagerEvent(targetUserManager: UserManager, eventType: keyof PublicUserManagerEvents) { - const events = (targetUserManager.events as unknown) as PublicUserManagerEvents; + const events = targetUserManager.events as unknown as PublicUserManagerEvents; events[eventType].raise(); } diff --git a/packages/react/src/components/login/utils/httpPoller.test.ts b/packages/react/src/components/login/utils/httpPoller.test.ts index f24f8eb3cd..cd483c3514 100644 --- a/packages/react/src/components/login/utils/httpPoller.test.ts +++ b/packages/react/src/components/login/utils/httpPoller.test.ts @@ -79,7 +79,9 @@ describe(`httpPoller`, () => { await advanceOneInterval(); await advanceOneInterval(); // https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function - await new Promise((resolve) => setImmediate(resolve)); + await new Promise((resolve) => { + setImmediate(resolve); + }); }; const advanceFromStartTimerToLoadEnd = async () => { await advanceToTimerEnd(); @@ -103,15 +105,15 @@ describe(`httpPoller`, () => { beforeEach(() => { jest.useFakeTimers(); }); - describe('Calling start() starts the timer and when timer ends ', () => { + describe('Calling start() starts the timer and when timer ends', () => { it('the pollFunction and shouldPoll have been called continuously', async () => { poller = createPoller({ ...pollerDefaultTestProps, }); poller.start(); - expect(shouldPollMockCallback).not.toBeCalled(); - expect(pollFunctionMockCallback).not.toBeCalled(); - expect(onErrorMockCallback).not.toBeCalled(); + expect(shouldPollMockCallback).not.toHaveBeenCalled(); + expect(pollFunctionMockCallback).not.toHaveBeenCalled(); + expect(onErrorMockCallback).not.toHaveBeenCalled(); await advanceToTimerEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(1); expect(pollFunctionMockCallback).toHaveBeenCalledTimes(1); @@ -144,7 +146,7 @@ describe(`httpPoller`, () => { await advanceFromStartTimerToLoadEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(1); expect(onErrorMockCallback).toHaveBeenCalledTimes(1); - expect(onErrorMockCallback).toBeCalledWith(HttpStatusCode.FORBIDDEN, null); + expect(onErrorMockCallback).toHaveBeenCalledWith(HttpStatusCode.FORBIDDEN, null); await advanceToTimerEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(2); }); @@ -158,14 +160,14 @@ describe(`httpPoller`, () => { poller.start(); await advanceFromStartTimerToLoadEnd(); expect(onErrorMockCallback).toHaveBeenCalledTimes(1); - expect(onErrorMockCallback).toBeCalledWith(undefined, expect.any(Error)); + expect(onErrorMockCallback).toHaveBeenCalledWith(undefined, expect.any(Error)); expect(shouldPollMockCallback).toHaveBeenCalledTimes(1); expect(pollFunctionMockCallback).toHaveBeenCalledTimes(1); await advanceToTimerEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(1); expect(pollFunctionMockCallback).toHaveBeenCalledTimes(1); }); - it(`neither onError or onSuccess are called if request is aborted, if onErrorStatusWhenAborted is not defined `, async () => { + it(`neither onError or onSuccess are called if request is aborted, if onErrorStatusWhenAborted is not defined`, async () => { const fetchAborter = createFetchAborter(); const timeoutListener = jest.fn(); fetchMock.mockOnce( @@ -203,7 +205,7 @@ describe(`httpPoller`, () => { expect(onErrorMockCallback).toHaveBeenCalledTimes(0); expect(onSuccessMockCallback).toHaveBeenCalledTimes(0); }); - it(`onError is called when aborted, if onErrorStatusWhenAborted is defined `, async () => { + it(`onError is called when aborted, if onErrorStatusWhenAborted is defined`, async () => { const onErrorStatusWhenAborted = -100; const fetchAborter = createFetchAborter(); const timeoutListener = jest.fn(); @@ -293,7 +295,7 @@ describe(`httpPoller`, () => { await advanceFromStartTimerToLoadEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(1); expect(onSuccessMockCallback).toHaveBeenCalledTimes(1); - expect(onSuccessMockCallback).toBeCalledWith(pollerDefaultTestProps.requestResponses[0]); + expect(onSuccessMockCallback).toHaveBeenCalledWith(pollerDefaultTestProps.requestResponses[0]); await advanceToTimerEnd(); expect(shouldPollMockCallback).toHaveBeenCalledTimes(2); expect(onSuccessMockCallback).toHaveBeenCalledTimes(1); diff --git a/packages/react/src/components/login/utils/userRenewalPromise.test.ts b/packages/react/src/components/login/utils/userRenewalPromise.test.ts index 9d3bd6bf55..269ab8c1ce 100644 --- a/packages/react/src/components/login/utils/userRenewalPromise.test.ts +++ b/packages/react/src/components/login/utils/userRenewalPromise.test.ts @@ -55,7 +55,7 @@ describe(`createRenewalTrackingPromise`, () => { raiseExpiringEvent(); await advanceUntilListenerCalled(fulfillmentListener); await waitFor(() => { - expect(promiseListener).toBeCalledTimes(1); + expect(promiseListener).toHaveBeenCalledTimes(1); }); expect(getLastMockCallArgs(promiseListener)[0]).toBeInstanceOf(Error); expect(fulfillmentListener).toHaveBeenCalledTimes(1); @@ -74,7 +74,7 @@ describe(`createRenewalTrackingPromise`, () => { raiseUnloadedEvent(); await advanceUntilListenerCalled(fulfillmentListener); await waitFor(() => { - expect(promiseListener).toBeCalledTimes(1); + expect(promiseListener).toHaveBeenCalledTimes(1); }); expect(getLastMockCallArgs(promiseListener)[0]).toBeInstanceOf(Error); expect(fulfillmentListener).toHaveBeenCalledTimes(1); diff --git a/packages/react/src/components/login/whole.setup.test.ts b/packages/react/src/components/login/whole.setup.test.ts index 3c03500338..9d3a9401fb 100644 --- a/packages/react/src/components/login/whole.setup.test.ts +++ b/packages/react/src/components/login/whole.setup.test.ts @@ -4,8 +4,7 @@ import to from 'await-to-js'; import HttpStatusCode from 'http-status-typed'; import { waitFor } from '@testing-library/react'; -// eslint-disable-next-line jest/no-mocks-import -import mockWindowLocation from './__mocks__/mockWindowLocation'; +import mockWindowLocation from '../../utils/mockWindowLocation'; // eslint-disable-next-line jest/no-mocks-import import apiTokens from './__mocks__/apiTokens.json'; // eslint-disable-next-line jest/no-mocks-import diff --git a/packages/react/src/components/notification/Notification.tsx b/packages/react/src/components/notification/Notification.tsx index 544dcf9b76..c99903c12d 100644 --- a/packages/react/src/components/notification/Notification.tsx +++ b/packages/react/src/components/notification/Notification.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { animated, useSpring } from 'react-spring'; +import { animated, useSpring } from '@react-spring/web'; import { VisuallyHidden } from '@react-aria/visually-hidden'; import '../../styles/base.module.css'; diff --git a/packages/react/src/components/pagination/Pagination.tsx b/packages/react/src/components/pagination/Pagination.tsx index 5934c99662..0059fd8d6a 100644 --- a/packages/react/src/components/pagination/Pagination.tsx +++ b/packages/react/src/components/pagination/Pagination.tsx @@ -209,11 +209,10 @@ export const Pagination = ({ } }, [pageIndex, hasUserChangedPage]); - const itemList = useMemo(() => createPaginationItemList({ pageCount, pageIndex, siblingCount }), [ - pageCount, - pageIndex, - siblingCount, - ]); + const itemList = useMemo( + () => createPaginationItemList({ pageCount, pageIndex, siblingCount }), + [pageCount, pageIndex, siblingCount], + ); const customThemeClass = useTheme(styles.pagination, theme); diff --git a/packages/react/src/components/searchInput/SearchInput.tsx b/packages/react/src/components/searchInput/SearchInput.tsx index 603d29ff2e..bd84ac8356 100644 --- a/packages/react/src/components/searchInput/SearchInput.tsx +++ b/packages/react/src/components/searchInput/SearchInput.tsx @@ -221,10 +221,13 @@ export const SearchInput = ({ } if (event.key === 'Home' || event.key === 'End') { // When preventDownshiftDefault = true, downshift does not alter native behavior - // eslint-disable-next-line no-param-reassign - (event.nativeEvent as typeof event.nativeEvent & { - preventDownshiftDefault: boolean; - }).preventDownshiftDefault = true; + /* eslint-disable no-param-reassign */ + ( + event.nativeEvent as typeof event.nativeEvent & { + preventDownshiftDefault: boolean; + } + ).preventDownshiftDefault = true; + /* eslint-enable no-param-reassign */ } }; diff --git a/packages/react/src/components/sideNavigation/mainLevel/MainLevel.tsx b/packages/react/src/components/sideNavigation/mainLevel/MainLevel.tsx index d8a6050f36..0f5776f1c2 100644 --- a/packages/react/src/components/sideNavigation/mainLevel/MainLevel.tsx +++ b/packages/react/src/components/sideNavigation/mainLevel/MainLevel.tsx @@ -154,9 +154,8 @@ export const MainLevel = ({ ...rest }: MainLevelProps) => { const menuId = `${id}-menu`; - const { defaultOpenMainLevels, activeParentLevel, setActiveParentLevel, setMobileMenuOpen } = useContext( - SideNavigationContext, - ); + const { defaultOpenMainLevels, activeParentLevel, setActiveParentLevel, setMobileMenuOpen } = + useContext(SideNavigationContext); const [isOpen, setIsOpen] = useState(defaultOpenMainLevels.includes(index as number)); const [isActiveParent, setIsActiveParent] = useState(false); diff --git a/packages/react/src/components/stepper/Stepper.stories.tsx b/packages/react/src/components/stepper/Stepper.stories.tsx index cbf8489de7..9f54dc0867 100644 --- a/packages/react/src/components/stepper/Stepper.stories.tsx +++ b/packages/react/src/components/stepper/Stepper.stories.tsx @@ -816,19 +816,16 @@ export const SimpleFormExample = (args) => { )} {state.activeStepIndex === 3 && ( dispatch({ type: 'changeField', fieldName: 'files', newValue: event })} /> diff --git a/packages/react/src/components/stepper/Stepper.tsx b/packages/react/src/components/stepper/Stepper.tsx index 4c6bad5c64..0f338ee7e1 100644 --- a/packages/react/src/components/stepper/Stepper.tsx +++ b/packages/react/src/components/stepper/Stepper.tsx @@ -189,6 +189,7 @@ export const Stepper = ({
{showPreviousButton && (
+ {/* eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role */} {verticalHeaders && verticalHeaders.length &&
} {checkboxSelection && } {visibleColumns.map((column) => { diff --git a/packages/react/src/components/table/components/TableContainer/TableContainer.tsx b/packages/react/src/components/table/components/TableContainer/TableContainer.tsx index e95ba1e8e8..0421cdedd6 100644 --- a/packages/react/src/components/table/components/TableContainer/TableContainer.tsx +++ b/packages/react/src/components/table/components/TableContainer/TableContainer.tsx @@ -18,6 +18,7 @@ export type TableContainerProps = { export const TableContainer = ({ children, + className, dataTestId, variant = 'dark', id, @@ -32,6 +33,7 @@ export const TableContainer = ({ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
{children}
diff --git a/packages/react/src/components/tabs/TabList.tsx b/packages/react/src/components/tabs/TabList.tsx index 74c8aeb814..9948ed6d3f 100644 --- a/packages/react/src/components/tabs/TabList.tsx +++ b/packages/react/src/components/tabs/TabList.tsx @@ -8,6 +8,7 @@ import { TabsContext } from './TabsContext'; import { IconAngleLeft, IconAngleRight } from '../../icons'; import { getChildByIndex, isElementOutsideLeftEdge, isElementOutsideRightEdge } from './tabUtils'; import { getChildrenAsArray } from '../../utils/getChildren'; +import { TabProps } from './Tab'; export type TabListProps = React.PropsWithChildren<{ /** @@ -35,10 +36,10 @@ export const TabList = ({ children, className, style = {} }: TabListProps) => { /** * Pass the index as prop to each tab element */ - const tabs = childElements.map((child, index) => { + const tabs = childElements.map((child, index: number) => { if (React.isValidElement(child) && (child.type as FCWithName).componentName === 'Tab') { // eslint-disable-next-line react/no-array-index-key - return React.cloneElement(child, { index, key: index }); + return React.cloneElement(child, { index, key: index }); } return null; }); @@ -176,6 +177,7 @@ export const TabList = ({ children, className, style = {} }: TabListProps) => {
{showPreviousButton && ( {showNextButton && (