diff --git a/.gitignore b/.gitignore index d64d59e6f..aa727b817 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,10 @@ package-lock.json public/env-config.js public/test-env-config.js +# Generated Excel-sheet with translations and it's cache file +locale_export.xlsx +~$locale_export.xlsx + npm-debug.log* yarn-debug.log* yarn-error.log* diff --git a/README.md b/README.md index 394a1352b..bf7b6160c 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,20 @@ the identification method in the local environment, edit the `.env` -file. Then either rebuild the docker container or run `yarn update-runtime-env` as discussed above. -All cloud instances use Helsinki AD identification for now. +In the cloud instances, dev uses Helsinki AD identification while others use Suomi.fi. + +## Excel for translations + +You can export an Excel-file with current translations. This can then be sent to translators. + +1. In the repository root, run the export script with `yarn locales:export`. +2. The translations are written to `locale_export.xlsx`. + +After the translations are added to the Excel file, they can be imported back. + +1. Place the translated file inside repository root. It needs to named `locale_export.xlsx`. +2. Run the import script: `yarn locales:import`. +3. The translations in `/src/locales` are updated. ## API mocking diff --git a/public/helsinki.png b/public/helsinki.png new file mode 100644 index 000000000..e247c5af8 Binary files /dev/null and b/public/helsinki.png differ diff --git a/src/common/components/footer/Footer.tsx b/src/common/components/footer/Footer.tsx index b36c53a4d..058933a29 100644 --- a/src/common/components/footer/Footer.tsx +++ b/src/common/components/footer/Footer.tsx @@ -19,7 +19,7 @@ function HaitatonFooter() { - + ); } diff --git a/src/common/components/textInput/TextInput.tsx b/src/common/components/textInput/TextInput.tsx index 2471c4e01..a33823ec3 100644 --- a/src/common/components/textInput/TextInput.tsx +++ b/src/common/components/textInput/TextInput.tsx @@ -9,6 +9,7 @@ import { getInputErrorText } from '../../utils/form'; type PropTypes = { name: string; label?: string; + maxLength?: number | undefined; disabled?: boolean; required?: boolean; readOnly?: boolean; @@ -23,6 +24,7 @@ type PropTypes = { const TextInput: React.FC> = ({ name, label, + maxLength = undefined, disabled, tooltip, required, @@ -48,6 +50,7 @@ const TextInput: React.FC> = ({ className={className} label={label || t(`hankeForm:labels:${name}`)} value={value || ''} + maxLength={maxLength} helperText={helperText} placeholder={placeholder} errorText={getInputErrorText(t, error)} diff --git a/src/domain/application/applicationView/ApplicationView.test.tsx b/src/domain/application/applicationView/ApplicationView.test.tsx index 276f0339e..4bb709ad0 100644 --- a/src/domain/application/applicationView/ApplicationView.test.tsx +++ b/src/domain/application/applicationView/ApplicationView.test.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { rest } from 'msw'; -import { render, screen } from '../../../testUtils/render'; +import { render, screen, waitFor } from '../../../testUtils/render'; import ApplicationViewContainer from './ApplicationViewContainer'; import { waitForLoadingToFinish } from '../../../testUtils/helperFunctions'; import { server } from '../../mocks/test-server'; +import { SignedInUser } from '../../hanke/hankeUsers/hankeUser'; import * as applicationApi from '../utils'; test('Correct information about application should be displayed', async () => { @@ -57,7 +58,9 @@ test('Should show error notification if loading application fails', async () => test('Should be able to go editing application when editing is possible', async () => { const { user } = render(); - await waitForLoadingToFinish(); + await waitFor(() => screen.findByRole('button', { name: 'Muokkaa hakemusta' }), { + timeout: 4000, + }); await user.click(screen.getByRole('button', { name: 'Muokkaa hakemusta' })); expect(window.location.pathname).toBe('/fi/johtoselvityshakemus/4/muokkaa'); @@ -74,8 +77,7 @@ test('Application edit button should not be displayed when editing is not possib test('Should be able to cancel application if it is possible', async () => { const { user } = render(); - await waitForLoadingToFinish(); - + await waitFor(() => screen.findByRole('button', { name: 'Peru hakemus' }), { timeout: 4000 }); await user.click(screen.getByRole('button', { name: 'Peru hakemus' })); await user.click(screen.getByRole('button', { name: 'Vahvista' })); @@ -92,6 +94,28 @@ test('Should not be able to cancel application if it has moved to handling in Al expect(screen.queryByRole('button', { name: 'Peru hakemus' })).not.toBeInTheDocument(); }); +test('Should not show Edit application and Cancel application buttons if user does not have EDIT_APPLICATIONS permission', async () => { + server.use( + rest.get('/api/hankkeet/:hankeTunnus/whoami', async (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + hankeKayttajaId: '3fa85f64-5717-4562-b3fc-2c963f66afa6', + kayttooikeustaso: 'KATSELUOIKEUS', + kayttooikeudet: ['VIEW'], + }), + ); + }), + ); + + render(); + + await waitForLoadingToFinish(); + + expect(screen.queryByRole('button', { name: 'Muokkaa hakemusta' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Peru hakemus' })).not.toBeInTheDocument(); +}); + test('Should not send multiple requests if clicking application cancel confirm button many times', async () => { server.use( rest.delete('/api/hakemukset/:id', async (req, res, ctx) => { @@ -103,6 +127,7 @@ test('Should not send multiple requests if clicking application cancel confirm b const { user } = render(); await waitForLoadingToFinish(); + await waitFor(() => screen.findByRole('button', { name: 'Peru hakemus' }), { timeout: 4000 }); await user.click(screen.getByRole('button', { name: 'Peru hakemus' })); const confirmCancelButton = screen.getByRole('button', { name: 'Vahvista' }); diff --git a/src/domain/application/applicationView/ApplicationView.tsx b/src/domain/application/applicationView/ApplicationView.tsx index 2066e6717..542c89730 100644 --- a/src/domain/application/applicationView/ApplicationView.tsx +++ b/src/domain/application/applicationView/ApplicationView.tsx @@ -38,6 +38,7 @@ import { ApplicationCancel } from '../components/ApplicationCancel'; import AttachmentSummary from '../components/AttachmentSummary'; import useAttachments from '../hooks/useAttachments'; import FeatureFlags from '../../../common/components/featureFlags/FeatureFlags'; +import UserRightsCheck from '../../hanke/hankeUsers/UserRightsCheck'; type Props = { application: Application; @@ -119,21 +120,25 @@ function ApplicationView({ application, hanke, onEditApplication }: Props) { {isPending ? ( - + + + ) : null} {hanke ? ( - } - /> + + } + /> + ) : null} diff --git a/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx b/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx index 93bd3450a..cf5c08827 100644 --- a/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx +++ b/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx @@ -7,7 +7,7 @@ import ErrorLoadingText from '../../../common/components/errorLoadingText/ErrorL import { useHankeUsers } from '../hankeUsers/hooks/useHankeUsers'; import useHanke from '../hooks/useHanke'; import AccessRightsView from './AccessRightsView'; -import useSignedInUserRights from '../hankeUsers/hooks/useUserRights'; +import useUserRightsForHanke from '../hankeUsers/hooks/useUserRightsForHanke'; type Props = { hankeTunnus: string; @@ -17,7 +17,7 @@ function AccessRightsViewContainer({ hankeTunnus }: Props) { const { t } = useTranslation(); const { data: hankeUsers, isLoading, isError, error } = useHankeUsers(hankeTunnus); const { data: hankeData } = useHanke(hankeTunnus); - const { data: signedInUser } = useSignedInUserRights(hankeTunnus); + const { data: signedInUser } = useUserRightsForHanke(hankeTunnus); if (isLoading) { return ( diff --git a/src/domain/hanke/edit/HankeForm.test.tsx b/src/domain/hanke/edit/HankeForm.test.tsx index 77bf5b64e..2810ba916 100644 --- a/src/domain/hanke/edit/HankeForm.test.tsx +++ b/src/domain/hanke/edit/HankeForm.test.tsx @@ -119,6 +119,23 @@ describe('HankeForm', () => { expect(screen.getByTestId(FORMFIELD.KUVAUS)).toHaveValue(hankkeenKuvaus); }); + test('Hanke nimi should be limited to 100 characters and not exceed the limit with additional characters', async () => { + const { user } = render(); + const initialName = 'b'.repeat(90); + + fireEvent.change(screen.getByRole('textbox', { name: /hankkeen nimi/i }), { + target: { value: initialName }, + }); + + await user.type( + screen.getByRole('textbox', { name: /hankkeen nimi/i }), + 'additional_characters', + ); + + const result = screen.getByRole('textbox', { name: /hankkeen nimi/i }); + expect(result).toHaveValue(initialName.concat('additional')); + }); + test('Yhteystiedot can be filled', async () => { const { user } = await setupYhteystiedotPage(); diff --git a/src/domain/hanke/edit/HankeFormPerustiedot.tsx b/src/domain/hanke/edit/HankeFormPerustiedot.tsx index 789f2ae5c..de4546ee9 100644 --- a/src/domain/hanke/edit/HankeFormPerustiedot.tsx +++ b/src/domain/hanke/edit/HankeFormPerustiedot.tsx @@ -51,7 +51,7 @@ const HankeFormPerustiedot: React.FC> = ({

{t('form:requiredInstruction')}

- +