diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 55a761d914..c7704cb779 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,6 +16,13 @@ Closes # ## How Has This Been Tested? +## Demos: +[Docs](https://city-of-helsinki.github.io/hds-demo/preview_/) + +[Core Storybook](https://city-of-helsinki.github.io/hds-demo/preview_/storybook/core) + +[React Storybook](https://city-of-helsinki.github.io/hds-demo/preview_/storybook/react) + ## Screenshots (if appropriate): ## Add to changelog diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000000..b013ae7d07 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,59 @@ +# this is reference workflow to run e2e tests and get test results +name: e2e-tests + +on: + workflow_dispatch: + pull_request: + push: + branches: + - playwright_initial_HDS-2224 + +jobs: + e2e-test: + 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 config set network-timeout 300000 + yarn + working-directory: ./e2e + + - name: install test tools + run: | + yarn inst + working-directory: ./e2e + + - name: run tests + run: | + yarn start + env: + E2E_TESTS_ENV_URL: "https://hds.hel.fi" + working-directory: ./e2e + + - name: upload test results in case of failure + uses: actions/upload-artifact@v4 + with: + name: e2e_test_report + path: e2e/report/** + # if: failure() + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + e2e/report/**/*.xml diff --git a/.github/workflows/hds-demo-preview-clean.yml b/.github/workflows/hds-demo-preview-clean.yml new file mode 100644 index 0000000000..27068529ea --- /dev/null +++ b/.github/workflows/hds-demo-preview-clean.yml @@ -0,0 +1,45 @@ +name: hds-demo-preview-clean + +on: + pull_request: + branches: + - development + - release-* + types: + - closed + +jobs: + build_and_publish_demo: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Checkout code hds-demo + uses: actions/checkout@v4 + with: + repository: City-of-Helsinki/hds-demo + path: hds-demo + ssh-key: ${{ secrets.HDSDEMO_SSH_DEPLOY_KEY }} + + - name: Remove PR directory + id: remove_preview + run: | + if [ -d "./docs/preview_${{ github.event.number }}" ] ; then + rm -fr ./docs/preview_${{ github.event.number }} + echo "preview_exists=true" >> $GITHUB_OUTPUT + else + echo "preview_exists=false" >> $GITHUB_OUTPUT + fi + + working-directory: ./hds-demo + + - name: Commit + if: steps.remove_preview.outputs.preview_exists == 'true' + run: | + git config --global user.email "hds@hel.fi" + git config --global user.name "Github Actions" + git status + git add . + git commit -m "Updated pr ${{ github.event.number }}" + git push + working-directory: ./hds-demo diff --git a/.github/workflows/hds-demo-preview.yml b/.github/workflows/hds-demo-preview.yml new file mode 100644 index 0000000000..feb43855d6 --- /dev/null +++ b/.github/workflows/hds-demo-preview.yml @@ -0,0 +1,125 @@ +name: hds-demo-preview + +on: + pull_request: + branches: + - development + - release-* + push: + branches: + - development + - release-* + + +jobs: + build_and_publish_demo: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + env: + PATH_PREFIX: "/hds-demo/preview_${{ github.event.number != '' && github.event.number || github.ref_name }}" + DEMO_NAME: preview_${{ github.event.number != '' && github.event.number || github.ref_name }} + + steps: + - name: Checkout code hds + 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' + + # Github cache + - name: get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - name: restore yarn cache + uses: actions/cache@v3 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-cache-folder-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + yarn-cache-folder- + + - name: restore lerna + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: yarn-node_modules-folder-${{ hashFiles('**/yarn.lock') }} + + # Build + - name: install dependencies + run: | + yarn config set network-timeout 300000 + yarn + + # Build site + - 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: build site package + run: yarn build --prefix-paths + working-directory: ./site + + # Build core storybook + - name: build core storybook + run: yarn build-storybook + working-directory: ./packages/core + + - name: move core storybook under site + run: mkdir ./site/public/storybook && mv ./packages/core/storybook-static $_/core + + # Build react storybook + - name: build react storybook + run: yarn build-storybook + working-directory: ./packages/react + + - name: move react storybook under site + run: mv ./packages/react/storybook-static ./site/public/storybook/react + + # Publish to hds-demo + - name: Checkout code hds-demo + uses: actions/checkout@v4 + with: + repository: City-of-Helsinki/hds-demo + path: hds-demo + ssh-key: ${{ secrets.HDSDEMO_SSH_DEPLOY_KEY }} + + - name: Clean old directory + run: | + rm -fr ./hds-demo/docs/$DEMO_NAME + mkdir -p ./hds-demo/docs/$DEMO_NAME + + - name: Copy build results + run: cp -r ./site/public/* ./hds-demo/docs/$DEMO_NAME + + - name: Commit + run: | + git config --global user.email "hds@hel.fi" + git config --global user.name "Github Actions" + git status + git add . + git commit -m "Updated preview to $DEMO_NAME" + git status + git push + working-directory: ./hds-demo diff --git a/.github/workflows/update-icon-library.yml b/.github/workflows/update-icon-library.yml index 65d6702eac..2589aeb132 100644 --- a/.github/workflows/update-icon-library.yml +++ b/.github/workflows/update-icon-library.yml @@ -31,16 +31,19 @@ jobs: # 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 + id: build_checker 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" + echo "SKIP_REST_STEPS=true" >> $GITHUB_OUTPUT exit 0 fi - name: Run Glypfig + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: | npx glypfig \ --apikey $API_KEY \ @@ -61,46 +64,58 @@ jobs: NODE_ID: '172:2478' - name: Append React interface into index file + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: | echo -e "export { IconProps } from './Icon.interface';\n" | \ cat - ./icon-library/react/tsx/index.ts > temp && mv temp ./icon-library/react/tsx/index.ts - name: Bump version in Changelog + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: | PKG_VER=`node -pe "require('./packages/react/package.json').version"` sed -i -E "s/version [0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}/version ${PKG_VER}/" ./release/icon-kit-template-CHANGELOG.txt - name: Copy Changelog file to icon library + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: cp ./release/icon-kit-template-CHANGELOG.txt ./icon-library/CHANGELOG.txt - name: Create release zip file + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' uses: TheDoctor0/zip-release@0.7.6 with: filename: 'release/hds-icon-kit.zip' path: './icon-library' - name: Copy svg files to repo folders + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: cp ./icon-library/svg/* ./packages/core/src/svg - name: Copy css files to repo folders + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: cp ./icon-library/css/* ./packages/core/src/icons - name: Copy react files to repo folders + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: cp ./icon-library/react/tsx/* ./packages/react/src/icons - name: Install React package NPM dependencies + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: (cd ./packages/react && yarn) - name: Lint React files + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: npx prettier --write './packages/react/src/icons/*.{ts,tsx}' - name: Code analysis for React files + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: npx eslint --debug -c './packages/react/.eslintrc.json' --ignore-path './packages/react/.eslintignore' --fix './packages/react/src/icons/*.{ts,tsx}' - name: Remove icon library build directory + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: rm -rf ./icon-library - name: Commit changed files + if: steps.build_checker.outputs.SKIP_REST_STEPS != 'true' run: | git config --global user.email "hds@hel.fi" git config --global user.name "Github Actions" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c012a41ea..802868f6fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,80 @@ 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.8.0] - May, 7, 2024 + +### React + +#### Added + +- [CookieConsent] Data stored by the HDS login component is now in the common cookies. +- [Login] Added a utility function to detect login callback error that could be ignored. +- [DateInput] Added example how to handle date ranges. + +#### Changed + +- [Login] API tokens are removed when user token renewal starts +- [Notification] Change auto closing notification progressbar to decrease instead of increase. +- [CookieConsent] Fixed SSR problem with "document not defined" + +#### Fixed + +- [Component] What bugs/typos are fixed? +- [Footer] Fix Koros height issue (Calm type was of wrong height) +- [Login] Fixed initialization failure in React strict mode when external modules are used. +- [ErrorSummary] Change wrong error icon to correct one + +### Core + +#### Changed + +- [ErrorSummary] Change wrong error icon to correct one + +### Documentation + +#### Added + +- [Login] Improved LoginProvider and api token client documentation. +- Added measure and outline addons to Storybook +- [DateInput] Added examples how to handle date ranges and validation. + +#### Changed + +- [Login] Login system is good enough to start using in production as well. We still welcome feedback and improve the component. +- [CookieConsent] Data stored by the HDS login component added to common cookie list. + +#### Fixed + +- [Component] What bugs/typos are fixed? +- [ErrorSummary] Change wrong error icon to correct one + +### Figma + +#### Added: +- [Hero] Added secondary buttons and a responsive wrapper to buttons. +- [Hero] Added buttons for the NoImage variant. +- [Hero] Introduced XXL variant sizes. + +#### Fixed: +- [Hero] Adjusted arrow and photographer info for improved responsiveness and ensured all variants are built using the same component structure. + +#### Removed: +- [Hero] Replaced preselected images with placeholders. +- [Hero] Replaced Diagonal variant's image with Placeholder image component (breaking change – resets used image). +- [Hero] Replaced ImageBottom variant's links with buttons (breaking change). + +### Hds-js + +#### Added + +- [login] Added a utility function to detect login callback error that could be ignored. + +### Hds-js + +#### Changed + +- [Login] API tokens are removed when user token renewal starts + ## [3.7.0] - April, 11, 2024 ### React diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 0000000000..aa90dd6706 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,7 @@ +*.log +lib +node_modules +.idea/ +.vscode/ +report/ +test-results/ \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000000..bccd11daed --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,15 @@ +{ + "name": "e2e", + "private": true, + "description": "e2e tests using Playwright", + "version": "3.8.0", + "scripts": { + "inst": "yarn playwright install", + "ci": "npx playwright test", + "start": "npx playwright test", + "record": "npx playwright codegen https://hds.hel.fi" + }, + "devDependencies": { + "@playwright/test": "^1.40.1" + } +} diff --git a/e2e/playwright.config.js b/e2e/playwright.config.js new file mode 100644 index 0000000000..2e20af1134 --- /dev/null +++ b/e2e/playwright.config.js @@ -0,0 +1,43 @@ +import { defineConfig } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + timeout: 180 * 1000, + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + ['junit', { outputFile: 'report/e2e-junit-results.xml' }], + ['html', { open: 'never', outputFolder: 'report/html' }] + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + actionTimeout: 30 * 1000, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.E2E_TESTS_ENV_URL ?? "https://hds.hel.fi", + ignoreHTTPSErrors: true, + screenshot: { + fullPage: true, + mode: "only-on-failure" + }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + // https://playwright.dev/docs/videos + video: 'on-first-retry', + contextOptions: {recordVideo: { dir: "./report/videos/"}} + }, + + projects: [ + { + name: 'logged-out', + testMatch: [/pages/], + } + ], +}); diff --git a/e2e/tests/pages/frontpage-spec.js b/e2e/tests/pages/frontpage-spec.js new file mode 100644 index 0000000000..0ac5997329 --- /dev/null +++ b/e2e/tests/pages/frontpage-spec.js @@ -0,0 +1,18 @@ +import { Page, expect, test } from '@playwright/test'; + + +test.describe("Frontpage", () => { + let page; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + await page.goto('/'); + }); + + + test('title', async () => { + const pageTitle = await page.title(); + expect(pageTitle).toContain("Helsinki Design System"); + }); + +}) diff --git a/package.json b/package.json index f0111b8f27..3cba5371af 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "license": "MIT", "workspaces": [ "packages/*", - "site" + "site", + "e2e" ], "scripts": { "build": "yarn build:code && yarn build:site", diff --git a/packages/core/.storybook/main.js b/packages/core/.storybook/main.js index 943c8a2848..f9b109ac6b 100644 --- a/packages/core/.storybook/main.js +++ b/packages/core/.storybook/main.js @@ -8,7 +8,9 @@ module.exports = { '@storybook/addon-a11y', '@storybook/addon-backgrounds', '@storybook/addon-viewport', - '@storybook/addon-storysource' + '@storybook/addon-storysource', + '@storybook/addon-measure', + '@storybook/addon-outline', ], staticDirs: ['../src/fonts'] }; diff --git a/packages/core/package.json b/packages/core/package.json index b903dc70cc..0a6b10e0d8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "hds-core", - "version": "3.7.0", + "version": "3.8.0", "description": "Core styles for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", @@ -22,6 +22,8 @@ "@storybook/addon-a11y": "6.4.18", "@storybook/addon-backgrounds": "6.4.18", "@storybook/addon-docs": "6.4.18", + "@storybook/addon-measure": "6.4.22", + "@storybook/addon-outline": "6.4.22", "@storybook/addon-storysource": "^6.4.19", "@storybook/addon-viewport": "6.4.18", "@storybook/addons": "6.4.18", @@ -30,7 +32,7 @@ "@storybook/manager-webpack5": "^6.5.16", "copyfiles": "2.2.0", "cssnano": "4.1.10", - "hds-design-tokens": "3.7.0", + "hds-design-tokens": "3.8.0", "postcss": "8.2.15", "postcss-cli": "8.3.1", "postcss-import": "12.0.1", diff --git a/packages/core/src/components/error-summary/error-summary.stories.js b/packages/core/src/components/error-summary/error-summary.stories.js index 2f93354f98..a0fb4abac8 100644 --- a/packages/core/src/components/error-summary/error-summary.stories.js +++ b/packages/core/src/components/error-summary/error-summary.stories.js @@ -1,7 +1,7 @@ import '../notification/notification.css'; import './error-summary.css'; import '../../icons/icon.css'; -import '../../icons/alert-circle.css'; +import '../../icons/error-fill.css'; export default { title: 'Components/Error summary', @@ -12,7 +12,7 @@ export const Default = () => `
- + Form contains following errors
@@ -36,7 +36,7 @@ export const Large = () => `
- + Form contains following errors
diff --git a/packages/design-tokens/package.json b/packages/design-tokens/package.json index 0c692205e3..3ca356aa16 100644 --- a/packages/design-tokens/package.json +++ b/packages/design-tokens/package.json @@ -1,6 +1,6 @@ { "name": "hds-design-tokens", - "version": "3.7.0", + "version": "3.8.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 1e1ee50292..3c02b35888 100644 --- a/packages/hds-js/package.json +++ b/packages/hds-js/package.json @@ -1,6 +1,6 @@ { "name": "hds-js", - "version": "3.7.0", + "version": "3.8.0", "description": "Vanilla js for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Default.png b/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Default.png index edba5a4120..7c4c0b9138 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Default.png and b/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Default.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Large.png b/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Large.png index 617d4c8ac0..821ad13a36 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Large.png and b/packages/react/.loki/reference/chrome_iphone7_Components_ErrorSummary_Large.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_Section.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_Section.png index eb9019a555..4d3ce94fd1 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_Section.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_Section.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_theme.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_theme.png index bfe4989aff..d45c7edde6 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_theme.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Custom_theme.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Example.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Example.png index 510e1593d3..e0a5c9942d 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Example.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Example.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Minimal.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Minimal.png index 62c8c6def7..39273d75c7 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Minimal.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Minimal.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_No_navigation.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_No_navigation.png index 7a68c39285..cdd647d3dd 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_No_navigation.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_No_navigation.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Sitemap.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Sitemap.png index 131851dc1e..bc33cf6152 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Sitemap.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Sitemap.png differ diff --git a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Utility_Groups.png b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Utility_Groups.png index cbc7124f16..c41a96aa0a 100644 Binary files a/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Utility_Groups.png and b/packages/react/.loki/reference/chrome_iphone7_Components_Footer_Utility_Groups.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Default.png b/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Default.png index 970e5ace76..d34b509684 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Default.png and b/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Default.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Large.png b/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Large.png index b8da66ef53..0a464e59b8 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Large.png and b/packages/react/.loki/reference/chrome_laptop_Components_ErrorSummary_Large.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_Section.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_Section.png index 8f6db4115a..36ce36c4fa 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_Section.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_Section.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_theme.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_theme.png index f262bf3ed1..2c95cac0c2 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_theme.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Custom_theme.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Example.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Example.png index 649356ef3a..02e500d9c0 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Example.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Example.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Minimal.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Minimal.png index 77749f90a2..96d63f8627 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Minimal.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Minimal.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_No_navigation.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_No_navigation.png index 589d022987..0fdaea6048 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_No_navigation.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_No_navigation.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Sitemap.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Sitemap.png index 89397dadf4..b16678d8af 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Sitemap.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Sitemap.png differ diff --git a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Utility_Groups.png b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Utility_Groups.png index 1d1e1ee1ba..87e8da947f 100644 Binary files a/packages/react/.loki/reference/chrome_laptop_Components_Footer_Utility_Groups.png and b/packages/react/.loki/reference/chrome_laptop_Components_Footer_Utility_Groups.png differ diff --git a/packages/react/.storybook/main.js b/packages/react/.storybook/main.js index d34c6787cd..b000f2578d 100644 --- a/packages/react/.storybook/main.js +++ b/packages/react/.storybook/main.js @@ -17,6 +17,8 @@ module.exports = { '@storybook/addon-a11y', '@storybook/addon-actions', '@storybook/addon-storysource', + '@storybook/addon-measure', + '@storybook/addon-outline', ], staticDirs: ['../src/fonts', { from: '../src/components/login/storybookStatic', to: '/static-login' }], webpack: async (config) => ({ diff --git a/packages/react/package.json b/packages/react/package.json index b66fca2054..8eb9eedbc1 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "hds-react", - "version": "3.7.0", + "version": "3.8.0", "description": "React components for the Helsinki Design System", "homepage": "https://github.com/City-of-Helsinki/helsinki-design-system#readme", "license": "MIT", @@ -53,6 +53,8 @@ "@storybook/addon-controls": "6.4.22", "@storybook/addon-docs": "6.4.22", "@storybook/addon-links": "6.4.22", + "@storybook/addon-measure": "6.4.22", + "@storybook/addon-outline": "6.4.22", "@storybook/addon-storysource": "6.4.22", "@storybook/addon-viewport": "6.4.22", "@storybook/addons": "6.4.22", @@ -133,7 +135,7 @@ "crc-32": "1.2.0", "date-fns": "2.16.1", "downshift": "6.0.6", - "hds-core": "3.7.0", + "hds-core": "3.8.0", "http-status-typed": "^1.0.1", "jwt-decode": "^3.1.2", "kashe": "1.0.4", diff --git a/packages/react/src/components/checkbox/Checkbox.test.tsx b/packages/react/src/components/checkbox/Checkbox.test.tsx index a8c3f25867..5c309f49b0 100644 --- a/packages/react/src/components/checkbox/Checkbox.test.tsx +++ b/packages/react/src/components/checkbox/Checkbox.test.tsx @@ -2,14 +2,16 @@ import React from 'react'; import { render } from '@testing-library/react'; import { axe } from 'jest-axe'; -import { Checkbox } from './Checkbox'; - -const checkboxProps = { - label: 'label text', - id: 'test', -}; +import { Checkbox, CheckboxProps } from './Checkbox'; describe(' spec', () => { + const checkboxProps: CheckboxProps = { + label: 'label text', + id: 'test', + helperText: 'Helper text', + errorText: 'Error text', + }; + it('renders the component', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); diff --git a/packages/react/src/components/checkbox/Checkbox.tsx b/packages/react/src/components/checkbox/Checkbox.tsx index 9841ff5618..9300eacbeb 100644 --- a/packages/react/src/components/checkbox/Checkbox.tsx +++ b/packages/react/src/components/checkbox/Checkbox.tsx @@ -108,7 +108,6 @@ export const Checkbox = React.forwardRef( } const ariaDescribedBy = composeAriaDescribedBy(id, helperText, errorText, undefined, undefined); - return (
( type="checkbox" disabled={disabled} checked={checked} - aria-describedby={ariaDescribedBy.length > 0 ? ariaDescribedBy : null} + aria-describedby={ariaDescribedBy} {...rest} /> +
+ Error text +
+
+ Helper text +
`; diff --git a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx index a05ae989a6..e505e80eb9 100644 --- a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx +++ b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx @@ -1057,8 +1057,28 @@ export const TunnistamoLoginCookies = (args) => { currentLanguage: language, requiredCookies: { groups: [ - { commonGroup: 'tunnistamoLogin' }, - { commonGroup: 'loadBalancing', cookies: [{ commonCookie: 'tunnistamo-login-loadbalancer' }] }, + { + commonGroup: 'tunnistamoLogin', + cookies: [ + { + commonCookie: 'oidc-ts-storage', + }, + { + commonCookie: 'hds-api-token-storage', + }, + { + commonCookie: 'hds-api-token-user-reference', + }, + ], + }, + { + commonGroup: 'loadBalancing', + cookies: [ + { + commonCookie: 'tunnistamo-login-loadbalancer', + }, + ], + }, { commonGroup: 'informationSecurity', cookies: [{ commonCookie: 'tunnistamo-csrftoken' }], diff --git a/packages/react/src/components/cookieConsent/cookieController.ts b/packages/react/src/components/cookieConsent/cookieController.ts index a869e9156d..96264f860f 100644 --- a/packages/react/src/components/cookieConsent/cookieController.ts +++ b/packages/react/src/components/cookieConsent/cookieController.ts @@ -1,5 +1,7 @@ import cookie, { CookieSerializeOptions } from 'cookie'; +import { isSsrEnvironment } from '../../utils/isSsrEnvironment'; + export type CookieSetOptions = CookieSerializeOptions; export const defaultCookieSetOptions: CookieSetOptions = { @@ -10,6 +12,9 @@ export const defaultCookieSetOptions: CookieSetOptions = { }; function getAll() { + if (isSsrEnvironment()) { + return {}; + } return cookie.parse(document.cookie); } @@ -19,7 +24,9 @@ function getNamedCookie(name: string): string | undefined { } function setNamedCookie(name: string, value: string, options?: CookieSerializeOptions) { - document.cookie = cookie.serialize(name, value, options); + if (!isSsrEnvironment()) { + document.cookie = cookie.serialize(name, value, options); + } } function createCookieController( @@ -34,7 +41,7 @@ function createCookieController( ...options, }; - if (typeof window === 'undefined') { + if (isSsrEnvironment()) { return { get: () => '', set: () => '', diff --git a/packages/react/src/components/cookieConsent/cookieStorageProxy.ts b/packages/react/src/components/cookieConsent/cookieStorageProxy.ts index 0a73a3c7fb..fc9b8205ec 100644 --- a/packages/react/src/components/cookieConsent/cookieStorageProxy.ts +++ b/packages/react/src/components/cookieConsent/cookieStorageProxy.ts @@ -1,5 +1,6 @@ import { CookieSerializeOptions, parse } from 'cookie'; +import { isSsrEnvironment } from '../../utils/isSsrEnvironment'; import { createCookieController, setNamedCookie, defaultCookieSetOptions, getAll } from './cookieController'; export type CookieSetOptions = CookieSerializeOptions; @@ -10,7 +11,7 @@ export const VERSION_COOKIE_NAME = 'city-of-helsinki-consent-version'; // the old version how default cookie domain was picked function getCookieDomainForMultiDomainAccess(): string { - if (typeof window === 'undefined') { + if (isSsrEnvironment()) { return ''; } @@ -19,7 +20,7 @@ function getCookieDomainForMultiDomainAccess(): string { // the new version how to pick default cookie domain export function getCookieDomainForSubDomainAccess(): string { - if (typeof window === 'undefined') { + if (isSsrEnvironment()) { return ''; } @@ -53,6 +54,9 @@ export function createCookieStorageProxy( }; const getConsentCookies = (): string[] => { + if (isSsrEnvironment()) { + return []; + } const cookies = document.cookie || ''; if (!cookies || cookies.indexOf('=') < 0) { return []; diff --git a/packages/react/src/components/cookieConsent/getContent.ts b/packages/react/src/components/cookieConsent/getContent.ts index 347c08451d..e9e78027bc 100644 --- a/packages/react/src/components/cookieConsent/getContent.ts +++ b/packages/react/src/components/cookieConsent/getContent.ts @@ -92,6 +92,11 @@ export function getCookieContent() { const tunnistamoUrl = 'api.hel.fi'; const keycloakUrl = 'tunnistus.hel.fi'; const suomiFiUrl = 'suomi.fi'; + const currentSiteTranslations = { + fi: 'Tämä sivusto', + sv: 'Denna webbsida', + en: 'Current domain', + }; return { texts: { @@ -610,6 +615,20 @@ export function getCookieContent() { }, ], }, + hdsLoginComponent: { + ...commonLoginGroupTranslations, + cookies: [ + { + commonCookie: 'oidc-ts-storage', + }, + { + commonCookie: 'hds-api-token-storage', + }, + { + commonCookie: 'hds-api-token-user-reference', + }, + ], + }, }, commonCookies: { helConsentCookie: { @@ -833,6 +852,67 @@ export function getCookieContent() { hostName: suomiFiUrl, ...commonLanguageTranslations, }, + 'oidc-ts-storage': { + id: 'oidc-ts-storage', + name: 'oidc.user:*', + fi: { + hostName: currentSiteTranslations.fi, + description: 'Käyttäjän kirjautumistiedot tallennetaan selaimen muistiin (session storage).', + expiration: 'Istunto', + }, + sv: { + hostName: currentSiteTranslations.sv, + description: 'Användarens inloggningsuppgifter lagras i webbläsarens minne (session storage).', + expiration: 'Session', + }, + en: { + hostName: currentSiteTranslations.en, + description: "Authentication information of the user is saved to browser's memory (session storage).", + expiration: 'Session', + }, + }, + 'hds-api-token-storage': { + id: 'hds_login_api_token_storage_key', + name: 'hds_login_api_token_storage_key', + fi: { + hostName: currentSiteTranslations.fi, + description: + 'Kirjautuneen käyttäjän rajanpinta-avaimet (api tokens) tallennetaan selaimen muistiin (session storage).', + expiration: 'Istunto', + }, + sv: { + hostName: currentSiteTranslations.sv, + description: 'Api-token för en autentiserad användare sparas i webbläsarens minne (session storage).', + expiration: 'Session', + }, + en: { + hostName: currentSiteTranslations.en, + description: "Api tokens of an authenticated user is saved to browser's memory (session storage).", + expiration: 'Session', + }, + }, + 'hds-api-token-user-reference': { + id: 'hds_login_api_token_user_reference', + name: 'hds_login_api_token_user_reference', + fi: { + hostName: currentSiteTranslations.fi, + description: + 'Kirjautuneen käyttäjän pääsyoikeudet tallennetaan selaimen muistiin, jotta tunnistetaan kenen rajapinta-avaimet on tallessa.', + expiration: 'Istunto', + }, + sv: { + hostName: currentSiteTranslations.sv, + description: + 'Den inloggade användarens åtkomsträttigheter lagras i webbläsarens minne för att identifiera vems token som lagras.', + expiration: 'Session', + }, + en: { + hostName: currentSiteTranslations.en, + description: + "Access token of an authenticated user is saved to browser's memory (session storage) to identify whose api tokens are stored.", + expiration: 'Session', + }, + }, }, }; } diff --git a/packages/react/src/components/dateInput/DateInput.stories.tsx b/packages/react/src/components/dateInput/DateInput.stories.tsx index 2e571058fe..db944acdab 100644 --- a/packages/react/src/components/dateInput/DateInput.stories.tsx +++ b/packages/react/src/components/dateInput/DateInput.stories.tsx @@ -1,10 +1,8 @@ import React, { useState } from 'react'; import parse from 'date-fns/parse'; -import addDays from 'date-fns/addDays'; -import format from 'date-fns/format'; import isWeekend from 'date-fns/isWeekend'; import isSameDay from 'date-fns/isSameDay'; -import { addMonths } from 'date-fns'; +import { addMonths, addDays, format, subDays, isValid } from 'date-fns'; import { DateInput, DateInputProps } from '.'; import { Button } from '../button'; @@ -274,3 +272,101 @@ export const WithCustomDayStyles = (args: DateInputProps) => { }; WithCustomDayStyles.storyName = 'With custom day styles'; WithCustomDayStyles.parameters = { loki: { skip: true } }; + +export const WithRange = (args) => { + const [range, setRange] = useState>([null, null]); + const [errors, setErrors] = useState>(['', '']); + const storeDate = (index: number, date: Date | null) => { + const newRange = [...range]; + newRange[index] = date; + setRange(newRange); + }; + const validate = (index: number, date: Date | null) => { + if (!date) { + setErrors(['', '']); + return true; + } + const comparison = range[index === 0 ? 1 : 0]; + if (!comparison) { + setErrors(['', '']); + return true; + } + if (index === 0 && comparison <= date) { + setErrors(['The start date cannot be the same or after the end date', '']); + return false; + } + if (index === 1 && comparison >= date) { + setErrors(['', 'The end date cannot be the same or before the start date']); + return false; + } + setErrors(['', '']); + return true; + }; + + const setMinDate: DateInputProps['onChange'] = (str, date) => { + if (!validate(0, date)) { + return; + } + storeDate(0, date); + }; + const setMaxDate: DateInputProps['onChange'] = (str, date) => { + if (!validate(1, date)) { + return; + } + storeDate(1, date); + }; + + const startMinDate = undefined; + const startMaxDate = range[1] ? subDays(range[1], 1) : undefined; + + const endMinDate = range[0] ? addDays(range[0], 1) : undefined; + const endMaxDate = undefined; + + const dateToString = (date: Date | null) => { + return date && isValid(date) ? format(date, 'dd.MM.yyyy') : ''; + }; + + const rangeToString = () => { + if (range[0] && range[1]) { + return `You have selected a range between ${dateToString(range[0])} and ${dateToString(range[1])}.`; + } + if (range[0] && isValid(range[0])) { + return `You have selected a start date of ${dateToString(range[0])}.`; + } + if (range[1] && isValid(range[1])) { + return `You have selected a end date of ${dateToString(range[0])}.`; + } + return `Please select a range!`; + }; + + return ( +
+ + +

{rangeToString()}

+

{formatHelperTextEnglish}

+
+ ); +}; + +WithRange.parameters = { loki: { skip: true } }; diff --git a/packages/react/src/components/errorSummary/ErrorSummary.tsx b/packages/react/src/components/errorSummary/ErrorSummary.tsx index 68be8ce14e..e1b85da16e 100644 --- a/packages/react/src/components/errorSummary/ErrorSummary.tsx +++ b/packages/react/src/components/errorSummary/ErrorSummary.tsx @@ -3,7 +3,7 @@ import React, { useRef, useEffect } from 'react'; import '../../styles/base.module.css'; import errorSummaryStyles from './ErrorSummary.module.scss'; import notificationStyles from '../notification/Notification.module.css'; -import { IconAlertCircleFill } from '../../icons'; +import { IconErrorFill } from '../../icons'; import classNames from '../../utils/classNames'; export type ErrorSummarySize = 'default' | 'large'; @@ -62,7 +62,7 @@ export const ErrorSummary = React.forwardRef( tabIndex={-1} ref={labelRef} > - + {label}
{children}
diff --git a/packages/react/src/components/errorSummary/__snapshots__/ErrorSummary.test.tsx.snap b/packages/react/src/components/errorSummary/__snapshots__/ErrorSummary.test.tsx.snap index 0210344db7..939eb1fd0a 100644 --- a/packages/react/src/components/errorSummary/__snapshots__/ErrorSummary.test.tsx.snap +++ b/packages/react/src/components/errorSummary/__snapshots__/ErrorSummary.test.tsx.snap @@ -18,7 +18,7 @@ exports[` spec renders the component 1`] = ` > spec renders the component 1`] = ` > diff --git a/packages/react/src/components/fileInput/FileInput.test.tsx b/packages/react/src/components/fileInput/FileInput.test.tsx index b3312a6071..ad17c70935 100644 --- a/packages/react/src/components/fileInput/FileInput.test.tsx +++ b/packages/react/src/components/fileInput/FileInput.test.tsx @@ -6,38 +6,30 @@ import { axe } from 'jest-axe'; import { FileInput, formatBytes } from './FileInput'; -// eslint-disable-next-line -const onChangeTest = () => {}; - describe(' spec', () => { + const defaultInputProps: Parameters[0] = { + id: 'test-file-input', + label: 'Choose a file', + language: 'en', + accept: '.png,.jpg', + onChange: () => {}, + helperText: 'Helper text', + }; + it('renders the component', () => { - const { asFragment } = render( - , - ); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('should not have basic accessibility issues', async () => { - const { container } = render( - , - ); + const { container } = render(); const results = await axe(container); expect(results).toHaveNoViolations(); }); it('should not have accessibility issues when there are files added', async () => { - const inputLabel = 'Choose a file'; - const { container } = render( - , - ); - const fileUpload = screen.getByLabelText(inputLabel); + const { container } = render(); + const fileUpload = screen.getByLabelText(defaultInputProps.label); userEvent.upload(fileUpload, [ new File(['test-a'], 'test-file-a.png', { type: 'image/png' }), new File(['test-b'], 'test-file-b.png', { type: 'image/png' }), @@ -47,11 +39,18 @@ describe(' spec', () => { }); it('should list files when user adds multiple files', async () => { - let filesValue; + let filesValue: File[] = []; + const inputLabel = 'Choose files'; const onChangeCallback = (files: File[]) => { filesValue = files; }; - const inputLabel = 'Choose files'; + const inputProps = { + ...defaultInputProps, + label: inputLabel, + onChange: onChangeCallback, + multiple: true, + accept: undefined, + }; const fileNameA = 'test-image-a.png'; const fileA = new File([''], fileNameA, { type: 'image/png' }); Object.defineProperty(fileA, 'size', { value: 12.5 * 1024 * 1024 }); @@ -62,7 +61,7 @@ describe(' spec', () => { const fileC = new File([''], fileNameC, { type: 'image/png' }); Object.defineProperty(fileC, 'size', { value: 3.3 * 1024 * 1024 * 1024 }); const files: File[] = [fileA, fileB, fileC]; - render(); + render(); const fileUpload = screen.getByLabelText(inputLabel); userEvent.upload(fileUpload, files); const list = screen.getByLabelText('3 files added.'); @@ -70,17 +69,17 @@ describe(' spec', () => { const fileListItems = getAllByRole('listitem'); expect(fileListItems.length).toBe(3); - const fileItemA = fileListItems.find((i) => i.innerHTML.includes(fileNameA)); + const fileItemA = fileListItems.find((i) => i.innerHTML.includes(fileNameA)) as HTMLElement; const { getByText: getByTextInA, getByLabelText: getByLabelInA } = within(fileItemA); expect(getByTextInA('(12.5 MB)')).toBeInTheDocument(); expect(getByLabelInA(`Remove ${fileNameA} from the added files.`)).toBeInTheDocument(); - const fileItemB = fileListItems.find((i) => i.innerHTML.includes(fileNameB)); + const fileItemB = fileListItems.find((i) => i.innerHTML.includes(fileNameB)) as HTMLElement; const { getByText: getByTextInB, getByLabelText: getByLabelInB } = within(fileItemB); expect(getByTextInB('(110 KB)')).toBeInTheDocument(); expect(getByLabelInB(`Remove ${fileNameB} from the added files.`)).toBeInTheDocument(); - const fileItemC = fileListItems.find((i) => i.innerHTML.includes(fileNameC)); + const fileItemC = fileListItems.find((i) => i.innerHTML.includes(fileNameC)) as HTMLElement; const { getByText: getByTextInC, getByLabelText: getByLabelInC } = within(fileItemC); expect(getByTextInC('(3.3 GB)')).toBeInTheDocument(); expect(getByLabelInC(`Remove ${fileNameC} from the added files.`)).toBeInTheDocument(); @@ -88,17 +87,23 @@ describe(' spec', () => { }); it('should append files when user selects one at the time', async () => { - let filesValue; + let filesValue: File[] = []; const onChangeCallback = (files: File[]) => { filesValue = files; }; - const inputLabel = 'Choose files'; + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + onChange: onChangeCallback, + multiple: true, + accept: undefined, + }; + render(); const firstFileName = 'test-file-a'; const firstFile = new File(['test-file'], firstFileName, { type: 'image/png' }); const secondFileName = 'test-file-b'; const secondFile = new File(['test-file'], secondFileName, { type: 'image/png' }); - render(); - const fileUpload = screen.getByLabelText(inputLabel); + const fileUpload = screen.getByLabelText(inputProps.label); userEvent.upload(fileUpload, [firstFile]); userEvent.upload(fileUpload, [secondFile]); expect(screen.getByText(firstFileName)).toBeInTheDocument(); @@ -108,24 +113,24 @@ describe(' spec', () => { }); it('should add file when user drops it into drag-and-drop area', async () => { - let filesValue; + let filesValue: File[] = []; const onChangeCallback = (files: File[]) => { filesValue = files; }; - const inputLabel = 'Choose files'; - const dragAndDropLabel = 'Drag files here'; + + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + dragAndDropLabel: 'Drag files here', + onChange: onChangeCallback, + dragAndDrop: true, + accept: undefined, + }; + const fileName = 'test-file-a'; const file = new File(['test-file'], fileName, { type: 'image/png' }); - render( - , - ); - fireEvent.drop(screen.getByText(dragAndDropLabel, { exact: false }), { + render(); + fireEvent.drop(screen.getByText(inputProps.dragAndDropLabel, { exact: false }), { dataTransfer: { files: [file], }, @@ -136,25 +141,21 @@ describe(' spec', () => { }); it('should validate files based on maxSize property', async () => { - const inputLabel = 'Choose files'; - const maxSize = 10; + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + multiple: true, + accept: undefined, + maxSize: 10, + }; const firstFileName = 'test-file-a'; const firstFile = new File(['test'], firstFileName, { type: 'image/png' }); const secondFileName = 'test-file-b'; const secondFile = new File(['test-file-with-too-long-content'], secondFileName, { type: 'image/png' }); const thirdFileName = 'test-file-with-exactly-max-size-bytes'; const thirdFile = new File(['0123456789'], thirdFileName, { type: 'image/png' }); - render( - , - ); - const fileUpload = screen.getByLabelText(inputLabel); + render(); + const fileUpload = screen.getByLabelText(inputProps.label); userEvent.upload(fileUpload, [firstFile, secondFile, thirdFile]); expect(screen.getByText(firstFileName)).toBeInTheDocument(); expect(screen.getByText('2/3 file(s) added', { exact: false })).toBeInTheDocument(); @@ -167,26 +168,22 @@ describe(' spec', () => { }); it('should validate files based on accept file extension', async () => { - const inputLabel = 'Choose files'; - const maxSize = 10; + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + multiple: true, + accept: '.jpg,.png', + maxSize: 10, + }; + render(); + const firstFileName = 'test-file-a.jpg'; const firstFile = new File(['test-jpg'], firstFileName, { type: 'image/jpeg' }); const secondFileName = 'test-file-b.json'; const secondFile = new File(['test-json'], secondFileName, { type: 'application/json' }); const thirdFileName = 'test-file-c.JPG'; const thirdFile = new File(['test-JPG'], thirdFileName, { type: 'image/jpeg' }); - render( - , - ); - const fileUpload = screen.getByLabelText(inputLabel); + const fileUpload = screen.getByLabelText(inputProps.label); userEvent.upload(fileUpload, [firstFile, secondFile, thirdFile]); expect(screen.getByText(firstFileName)).toBeInTheDocument(); expect(screen.getByText('2/3 file(s) added', { exact: false })).toBeInTheDocument(); @@ -199,26 +196,22 @@ describe(' spec', () => { }); it('should validate files based on accept file type', async () => { - const inputLabel = 'Choose files'; - const maxSize = 10; + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + multiple: true, + accept: 'image/*', + maxSize: 10, + }; + render(); + const firstFileName = 'test-file-a.jpg'; const firstFile = new File(['test-jpg'], firstFileName, { type: 'image/jpeg' }); const secondFileName = 'test-file-b.json'; const secondFile = new File(['test-json'], secondFileName, { type: 'application/json' }); const thirdFileName = 'test-file-c.png'; const thirdFile = new File(['test-png'], thirdFileName, { type: 'image/png' }); - render( - , - ); - const fileUpload = screen.getByLabelText(inputLabel); + const fileUpload = screen.getByLabelText(inputProps.label); userEvent.upload(fileUpload, [firstFile, secondFile, thirdFile]); expect(screen.getByText(firstFileName)).toBeInTheDocument(); expect(screen.getByText('2/3 file(s) added', { exact: false })).toBeInTheDocument(); @@ -231,17 +224,24 @@ describe(' spec', () => { }); it('should remove files when user clicks remove-buttons', async () => { - let filesValue; + let filesValue: File[] = []; const onChangeCallback = (files: File[]) => { filesValue = files; }; - const inputLabel = 'Choose files'; + const inputProps = { + ...defaultInputProps, + label: 'Choose files', + multiple: true, + accept: undefined, + maxSize: 10, + onChange: onChangeCallback, + }; + render(); const fileNameA = 'test-file-a'; const fileA = new File(['test-file'], fileNameA, { type: 'image/png' }); const fileNameB = 'test-file-b'; const fileB = new File(['test-file'], fileNameB, { type: 'image/png' }); - render(); - const fileUpload = screen.getByLabelText(inputLabel); + const fileUpload = screen.getByLabelText(inputProps.label); userEvent.upload(fileUpload, [fileA, fileB]); const list = screen.getByLabelText('2 files added.'); const { getAllByRole } = within(list); diff --git a/packages/react/src/components/fileInput/__snapshots__/FileInput.test.tsx.snap b/packages/react/src/components/fileInput/__snapshots__/FileInput.test.tsx.snap index ed508dd778..4f4b9cd473 100644 --- a/packages/react/src/components/fileInput/__snapshots__/FileInput.test.tsx.snap +++ b/packages/react/src/components/fileInput/__snapshots__/FileInput.test.tsx.snap @@ -54,7 +54,7 @@ exports[` spec renders the component 1`] = ` spec renders the component 1`] = `
+
+ Error text +
spec renders the component 1`] = ` class="helperText" id="test-file-input-helper" > - Only .png and .jpg files. + Helper text
    { // custom theme class that is applied to the root element const customThemeClass = useTheme(styles.footer, theme); + const korosHeight = getShapeHeight({ type: korosType }); return (