Skip to content

Commit

Permalink
Merge branch 'main' into 1624-add-role-for-verein-360-api-tokens
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/TestData.kt
#	backend/src/test/kotlin/app/ehrenamtskarte/backend/userdata/UserImportTest.kt
  • Loading branch information
ztefanie committed Dec 3, 2024
2 parents 4487008 + 014ef5d commit e28c088
Show file tree
Hide file tree
Showing 105 changed files with 1,673 additions and 527 deletions.
6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion administration/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ module.exports = {
},
overrides: [
{
files: ['*.test.{ts,tsx}', '**/__mocks__/*.{ts,tsx}', 'jest.setup.ts', 'jest.config.ts'],
files: ['*.test.{ts,tsx}', '**/__mocks__/*.{ts,tsx}', '**/testing/*.{ts,tsx}', 'jest.setup.ts', 'jest.config.ts'],
rules: {
'global-require': 'off',
'no-console': 'off',
Expand Down
4 changes: 2 additions & 2 deletions administration/src/bp-modules/cards/AddCardsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import styled from 'styled-components'
import { Card, initializeCard, initializeCardFromCSV } from '../../cards/Card'
import { Region } from '../../generated/graphql'
import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext'
import { getCsvHeaders } from '../../project-configs/helper'
import AddCardForm from './AddCardForm'
import CardFormButton from './CardFormButton'
import { getHeaders } from './ImportCardsController'

const FormsWrapper = styled(FlipMove)`
flex-wrap: wrap;
Expand Down Expand Up @@ -55,7 +55,7 @@ const AddCardsForm = ({

useEffect(() => {
if (cards.length === 0) {
const headers = getHeaders(projectConfig)
const headers = getCsvHeaders(projectConfig)
const values = headers.map(header => searchParams.get(header))
setCards([initializeCardFromCSV(projectConfig.card, values, headers, region, true)])

Expand Down
40 changes: 5 additions & 35 deletions administration/src/bp-modules/cards/ImportCardsController.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
import { NonIdealState, Spinner } from '@blueprintjs/core'
import React, { ReactElement, useCallback, useContext, useMemo } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import React, { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router-dom'

import { FREINET_PARAM } from '../../Router'
import { WhoAmIContext } from '../../WhoAmIProvider'
import { Card, initializeCardFromCSV } from '../../cards/Card'
import { Region } from '../../generated/graphql'
import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext'
import { ProjectConfig } from '../../project-configs/getProjectConfig'
import useBlockNavigation from '../../util/useBlockNavigation'
import GenerationFinished from './CardsCreatedMessage'
import CreateCardsButtonBar from './CreateCardsButtonBar'
import { convertFreinetImport } from './ImportCardsFromFreinetController'
import ImportCardsInput from './ImportCardsInput'
import CardImportTable from './ImportCardsTable'
import useCardGenerator, { CardActivationState } from './hooks/useCardGenerator'

export const getHeaders = (projectConfig: ProjectConfig): string[] => [
projectConfig.card.nameColumnName,
projectConfig.card.expiryColumnName,
...(projectConfig.card.extensionColumnNames.filter(Boolean) as string[]),
]

const InnerImportCardsController = ({ region }: { region: Region }): ReactElement => {
const { state, setState, generateCardsPdf, generateCardsCsv, setCards, cards } = useCardGenerator(region)
const projectConfig = useContext(ProjectConfigContext)
const headers = useMemo(() => getHeaders(projectConfig), [projectConfig])
const navigate = useNavigate()

const isFreinetFormat = new URLSearchParams(useLocation().search).get(FREINET_PARAM) === 'true'

useBlockNavigation({
when: cards.length > 0,
message: 'Falls Sie fortfahren, werden alle Eingaben verworfen.',
Expand All @@ -43,20 +28,10 @@ const InnerImportCardsController = ({ region }: { region: Region }): ReactElemen
}
}

// TODO headers or csvHeader?
const lineToCard = useCallback(
(line: string[], csvHeader: string[]): Card => {
if (isFreinetFormat) {
convertFreinetImport(line, csvHeader, projectConfig)
}
return initializeCardFromCSV(projectConfig.card, line, csvHeader, region)
},
[projectConfig, region, isFreinetFormat]
)

if (state === CardActivationState.loading) {
return <Spinner />
}

if (state === CardActivationState.finished) {
return (
<GenerationFinished
Expand All @@ -71,14 +46,9 @@ const InnerImportCardsController = ({ region }: { region: Region }): ReactElemen
return (
<>
{cards.length === 0 ? (
<ImportCardsInput
setCards={setCards}
lineToCard={lineToCard}
headers={headers}
isFreinetFormat={isFreinetFormat}
/>
<ImportCardsInput setCards={setCards} region={region} />
) : (
<CardImportTable cards={cards} cardConfig={projectConfig.card} headers={headers} />
<CardImportTable cards={cards} />
)}
<CreateCardsButtonBar
cards={cards}
Expand Down
148 changes: 97 additions & 51 deletions administration/src/bp-modules/cards/ImportCardsInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import { OverlayToaster } from '@blueprintjs/core'
import { fireEvent, render, waitFor } from '@testing-library/react'
import React, { ReactNode } from 'react'
import { fireEvent, waitFor } from '@testing-library/react'
import React from 'react'

import { Card, initializeCard } from '../../cards/Card'
import { Region } from '../../generated/graphql'
import { ProjectConfigProvider } from '../../project-configs/ProjectConfigContext'
import bayernConfig from '../../project-configs/bayern/config'
import { LOCAL_STORAGE_PROJECT_KEY } from '../../project-configs/constants'
import { ProjectConfig } from '../../project-configs/getProjectConfig'
import { ProjectConfig, setProjectConfigOverride } from '../../project-configs/getProjectConfig'
import koblenzConfig from '../../project-configs/koblenz/config'
import nuernbergConfig from '../../project-configs/nuernberg/config'
import { renderWithRouter } from '../../testing/render'
import PlainDate from '../../util/PlainDate'
import { AppToasterProvider } from '../AppToaster'
import { getHeaders } from './ImportCardsController'
import ImportCardsInput, { ENTRY_LIMIT } from './ImportCardsInput'

jest.mock('../../Router', () => ({}))

const wrapper = ({ children }: { children: ReactNode }) => (
<AppToasterProvider>
<ProjectConfigProvider>{children}</ProjectConfigProvider>
</AppToasterProvider>
)

describe('ImportCardsInput', () => {
beforeEach(jest.clearAllMocks)

const region: Region = {
id: 0,
name: 'augsburg',
Expand All @@ -30,12 +26,10 @@ describe('ImportCardsInput', () => {
activatedForCardConfirmationMail: true,
}

const renderAndSubmitCardsInput = async (
projectConfig: ProjectConfig,
csv: string,
lineToCard: () => Card,
setCards: () => void
) => {
const toaster = jest.spyOn(OverlayToaster.prototype, 'show')
const setCards = jest.fn()

const renderAndSubmitCardsInput = async (projectConfig: ProjectConfig, csv: string, setCards: () => void) => {
const fileReaderMock = {
// eslint-disable-next-line func-names
readAsText: jest.fn(function (this: FileReader, _: Blob) {
Expand All @@ -44,17 +38,14 @@ describe('ImportCardsInput', () => {
} as unknown as FileReader
jest.spyOn(global, 'FileReader').mockReturnValue(fileReaderMock)
const file = new File([csv], `${projectConfig.name}.csv`, { type: 'text/csv' })

localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, projectConfig.projectId)

const { getByTestId } = render(
<ImportCardsInput
headers={getHeaders(projectConfig)}
lineToCard={lineToCard}
setCards={setCards}
isFreinetFormat={false}
/>,
{ wrapper }
setProjectConfigOverride(projectConfig.projectId)

const { getByTestId } = renderWithRouter(
<AppToasterProvider>
<ProjectConfigProvider>
<ImportCardsInput setCards={setCards} region={region} />
</ProjectConfigProvider>
</AppToasterProvider>
)

const fileInput = getByTestId('file-upload') as HTMLInputElement
Expand All @@ -65,33 +56,90 @@ describe('ImportCardsInput', () => {
await waitFor(() => expect(fileReaderMock.readAsText).toHaveBeenCalledTimes(1))
}

it.each([
{
projectConfig: bayernConfig,
csv: `
it('should correctly import CSV Card for bayern', async () => {
const projectConfig = bayernConfig
const csv = `
Name,Ablaufdatum,Kartentyp
Thea Test,03.04.2024,Standard
Tilo Traber,,Gold
`,
},
{
projectConfig: nuernbergConfig,
csv: `
Name,Ablaufdatum,Geburtsdatum,Passnummer
`
await renderAndSubmitCardsInput(projectConfig, csv, setCards)

expect(toaster).not.toHaveBeenCalled()
expect(setCards).toHaveBeenCalledTimes(1)
expect(setCards).toHaveBeenCalledWith([
{
expirationDate: PlainDate.fromCustomFormat('03.04.2024'),
extensions: { bavariaCardType: 'Standard', regionId: 0 },
fullName: 'Thea Test',
id: expect.any(Number),
},
{ expirationDate: null, extensions: { regionId: 0 }, fullName: 'Tilo Traber', id: expect.any(Number) },
])
})

it('should correctly import CSV Card for nuernberg', async () => {
const projectConfig = nuernbergConfig
const csv = `
Name,Ablaufdatum,Geburtsdatum,Pass-ID
Thea Test,03.04.2024,10.10.2000,12345678
Tilo Traber,03.04.2025,12.01.1984,98765432
`,
},
])(`Correctly import CSV Card for project $projectConfig.name`, async ({ projectConfig, csv }) => {
const toaster = jest.spyOn(OverlayToaster.prototype, 'show')
const lineToCard = jest.fn(() => initializeCard(projectConfig.card, region))
const setCards = jest.fn()
`

await renderAndSubmitCardsInput(projectConfig, csv, setCards)

expect(toaster).not.toHaveBeenCalled()
expect(setCards).toHaveBeenCalledTimes(1)
expect(setCards).toHaveBeenCalledWith([
{
expirationDate: PlainDate.fromCustomFormat('03.04.2024'),
extensions: { birthday: PlainDate.fromCustomFormat('10.10.2000'), regionId: 0, nuernbergPassId: 12345678 },
fullName: 'Thea Test',
id: expect.any(Number),
},
{
expirationDate: PlainDate.fromCustomFormat('03.04.2025'),
extensions: { birthday: PlainDate.fromCustomFormat('12.01.1984'), regionId: 0, nuernbergPassId: 98765432 },
fullName: 'Tilo Traber',
id: expect.any(Number),
},
])
})

it('should correctly import CSV Card for koblenz', async () => {
const projectConfig = koblenzConfig
const csv = `
Name,Ablaufdatum,Geburtsdatum,Referenznummer
Thea Test,03.04.2024,10.10.2000,123k
Tilo Traber,03.04.2025,12.01.1984,98765432
`

await renderAndSubmitCardsInput(projectConfig, csv, lineToCard, setCards)
await renderAndSubmitCardsInput(projectConfig, csv, setCards)

expect(toaster).not.toHaveBeenCalled()
expect(setCards).toHaveBeenCalledTimes(1)
expect(lineToCard).toHaveBeenCalledTimes(2)
expect(setCards).toHaveBeenCalledWith([
{
expirationDate: PlainDate.fromCustomFormat('03.04.2024'),
extensions: {
birthday: PlainDate.fromCustomFormat('10.10.2000'),
regionId: 0,
koblenzReferenceNumber: '123k',
},
fullName: 'Thea Test',
id: expect.any(Number),
},
{
expirationDate: PlainDate.fromCustomFormat('03.04.2025'),
extensions: {
birthday: PlainDate.fromCustomFormat('12.01.1984'),
regionId: 0,
koblenzReferenceNumber: '98765432',
},
fullName: 'Tilo Traber',
id: expect.any(Number),
},
])
})

it.each([
Expand All @@ -118,15 +166,13 @@ ${'Thea Test,03.04.2024,12345678\n'.repeat(ENTRY_LIMIT + 1)}
`,
error: `Die Datei hat mehr als ${ENTRY_LIMIT} Einträge.`,
},
])(`Import CSV Card should fail with error '$error'`, async ({ csv, error }) => {
])(`import CSV Card should fail with error '$error'`, async ({ csv, error }) => {
const toaster = jest.spyOn(OverlayToaster.prototype, 'show')
const lineToCard = jest.fn(() => initializeCard(bayernConfig.card, region))
const setCards = jest.fn()

await renderAndSubmitCardsInput(bayernConfig, csv, lineToCard, setCards)
await renderAndSubmitCardsInput(bayernConfig, csv, setCards)

expect(toaster).toHaveBeenCalledWith({ intent: 'danger', message: error })
expect(setCards).not.toHaveBeenCalled()
expect(lineToCard).not.toHaveBeenCalled()
})
})
Loading

0 comments on commit e28c088

Please sign in to comment.