Skip to content

Commit

Permalink
1847: add tests, create separate component for ActivityLogTable
Browse files Browse the repository at this point in the history
  • Loading branch information
f1sh1918 committed Feb 3, 2025
1 parent f419468 commit 7a07802
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 59 deletions.
2 changes: 2 additions & 0 deletions administration/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { JestConfigWithTsJest } from 'ts-jest'
import { defaults as tsjPreset } from 'ts-jest/presets'

process.env.TZ = 'GMT'

const config: JestConfigWithTsJest = {
...tsjPreset,
rootDir: 'src',
Expand Down
2 changes: 1 addition & 1 deletion administration/src/bp-modules/user-settings/ActivityLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { formatISO, parseISO } from 'date-fns'
import { Card, SerializedCard, deserializeCard, serializeCard } from '../../cards/Card'
import { CardConfig } from '../../project-configs/getProjectConfig'

const STORAGE_KEY = 'activity-log'
export const STORAGE_KEY = 'activity-log'

type JsonActivityLogEntry = { timestamp: string; card: SerializedCard }

Expand Down
53 changes: 2 additions & 51 deletions administration/src/bp-modules/user-settings/ActivityLogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import styled from 'styled-components'
import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext'
import { ActivityLogConfig } from '../../project-configs/getProjectConfig'
import { loadActivityLog } from './ActivityLog'
import ActivityLogTable from './ActivityLogTable'

const ActivityDialog = styled(Dialog)`
max-height: 800px;
Expand All @@ -17,41 +18,6 @@ const ActivityDialogBody = styled(DialogBody)`
overflow-x: hidden;
`

const StickyTableHeader = styled.thead`
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 2;
`

const EmptyLog = styled.div`
margin: 12px;
`

const StyledTable = styled.table`
border-spacing: 0;
min-width: 800px;
overflow-x: hidden;
& tbody tr:hover {
background: rgba(0, 0, 0, 0.05);
}
& td,
& th {
margin: 0;
padding: 16px;
text-align: center;
}
& th {
position: sticky;
top: 0;
background: white;
border-top: 1px solid lightgray;
border-bottom: 1px solid lightgray;
}
`
const ActivityLogCard = ({ activityLogConfig }: { activityLogConfig: ActivityLogConfig }): ReactElement => {
const { t } = useTranslation('userSettings')
const [openLog, setOpenLog] = useState<boolean>(false)
Expand All @@ -71,22 +37,7 @@ const ActivityLogCard = ({ activityLogConfig }: { activityLogConfig: ActivityLog
</div>
<ActivityDialog isOpen={openLog} title={t('activityLog')} onClose={() => setOpenLog(false)} isCloseButtonShown>
<ActivityDialogBody>
<StyledTable>
<StickyTableHeader>
<tr>
{activityLogConfig.columnNames.map(columnName => (
<th key={columnName}>{columnName}</th>
))}
</tr>
</StickyTableHeader>
<tbody>
{activityLogSorted.length > 0 ? (
activityLogSorted.map(activityLogConfig.renderLogEntry)
) : (
<EmptyLog>{t('noEntries')}</EmptyLog>
)}
</tbody>
</StyledTable>
<ActivityLogTable activityLog={activityLogSorted} activityLogConfig={activityLogConfig} />
</ActivityDialogBody>
</ActivityDialog>
</>
Expand Down
73 changes: 73 additions & 0 deletions administration/src/bp-modules/user-settings/ActivityLogTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'

import { ActivityLogConfig } from '../../project-configs/getProjectConfig'
import { ActivityLogEntryType } from './ActivityLog'

const StickyTableHeader = styled.thead`
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 2;
`

const EmptyLog = styled.tr`
margin: 12px;
`

const StyledTable = styled.table`
border-spacing: 0;
min-width: 800px;
overflow-x: hidden;
& tbody tr:hover {
background: rgba(0, 0, 0, 0.05);
}
& td,
& th {
margin: 0;
padding: 16px;
text-align: center;
}
& th {
position: sticky;
top: 0;
background: white;
border-top: 1px solid lightgray;
border-bottom: 1px solid lightgray;
}
`

type ActivityLogTableProps = {
activityLog: ActivityLogEntryType[]
activityLogConfig: ActivityLogConfig
}

const ActivityLogTable = ({ activityLog, activityLogConfig }: ActivityLogTableProps): ReactElement => {
const { t } = useTranslation('userSettings')
return (
<StyledTable>
<StickyTableHeader>
<tr data-testid='activity-log-column-names'>
{activityLogConfig.columnNames.map(columnName => (
<th key={columnName}>{columnName}</th>
))}
</tr>
</StickyTableHeader>
<tbody data-testid='activity-log-table-body'>
{activityLog.length > 0 ? (
activityLog.map(activityLogConfig.renderLogEntry)
) : (
<EmptyLog>
<td>{t('noEntries')}</td>
</EmptyLog>
)}
</tbody>
</StyledTable>
)
}

export default ActivityLogTable
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { initializeCard } from '../../../cards/Card'
import { Region } from '../../../generated/graphql'
import nuernbergConfig from '../../../project-configs/nuernberg/config'
import PlainDate from '../../../util/PlainDate'
import { ActivityLogEntryType } from '../ActivityLog'

const region: Region = {
id: 93,
name: 'Stadt Nürnberg',
prefix: 'nbg',
activatedForApplication: false,
activatedForCardConfirmationMail: false,
}
export const activityLogCardExample = initializeCard(nuernbergConfig.card, region, {
id: 732401,
fullName: 'Thea Test',
expirationDate: PlainDate.from('2026-01-01'),
extensions: {
nuernbergPassId: 3132222,
birthday: PlainDate.from('2000-02-01'),
startDay: PlainDate.from('2025-01-01'),
},
})

export const activityLogCardExample2 = initializeCard(nuernbergConfig.card, region, {
id: 7324321,
fullName: 'Thea Test',
expirationDate: PlainDate.from('2026-01-01'),
extensions: {
nuernbergPassId: 3132132,
birthday: PlainDate.from('2005-02-01'),
startDay: PlainDate.from('2025-05-01'),
},
})
export const activityLogEntries: ActivityLogEntryType[] = [
{ card: activityLogCardExample, timestamp: new Date() },
{ card: activityLogCardExample2, timestamp: new Date() },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import nuernbergConfig from '../../../project-configs/nuernberg/config'
import { loadActivityLog, saveActivityLog } from '../ActivityLog'
import { activityLogCardExample } from '../__mocks__/ActivityLogData'

jest.useFakeTimers({ now: new Date('2024-01-01T00:00:00.000Z') })
describe('ActivityLog', () => {
it('should save activity log session storage', () => {
const setItemSpy = jest.spyOn(Object.getPrototypeOf(sessionStorage), 'setItem')
saveActivityLog(activityLogCardExample)
expect(setItemSpy).toHaveBeenCalledTimes(1)
expect(setItemSpy).toHaveBeenCalledWith(
'activity-log',
'[{"timestamp":"2024-01-01T00:00:00Z","card":{"id":732401,"fullName":"Thea Test","expirationDate":"2026-01-01","extensions":{"startDay":"2025-01-01","birthday":"2000-02-01","nuernbergPassId":"3132222","addressLine1":"","addressLine2":"","addressPlz":"","addressLocation":"","regionId":"93"}}}]'
)
})

it('should properly load activity log from session storage', () => {
const getItemSpy = jest.spyOn(Object.getPrototypeOf(sessionStorage), 'getItem')
saveActivityLog(activityLogCardExample)
loadActivityLog(nuernbergConfig.card)
expect(getItemSpy).toHaveBeenCalledWith('activity-log')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'

import nuernbergConfig from '../../../project-configs/nuernberg/config'
import { renderWithTranslation } from '../../../testing/render'
import ActivityLogTable from '../ActivityLogTable'
import { activityLogEntries } from '../__mocks__/ActivityLogData'

jest.useFakeTimers({ now: new Date('2024-01-01T00:00:00.000Z') })
describe('ActivityLogTable', () => {
it('should render an empty list, if there are no log entries', () => {
const { getByText, getByTestId } = renderWithTranslation(
<ActivityLogTable activityLog={[]} activityLogConfig={nuernbergConfig.activityLogConfig!} />
)
expect(getByTestId('activity-log-column-names').textContent).toBe(
nuernbergConfig.activityLogConfig!.columnNames.join('')
)
expect(getByText('Keine Einträge vorhanden')).toBeTruthy()
})

it('should render the table body with correct amount of entries', () => {
const { queryByText, getByTestId } = renderWithTranslation(
<ActivityLogTable activityLog={activityLogEntries} activityLogConfig={nuernbergConfig.activityLogConfig!} />
)
expect(getByTestId('activity-log-column-names').textContent).toBe(
nuernbergConfig.activityLogConfig!.columnNames.join('')
)
expect(queryByText('Keine Einträge vorhanden')).toBeNull()
expect(getByTestId('activity-log-table-body').children).toHaveLength(2)
})
})
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React, { ReactNode } from 'react'
import React, { ReactElement} from 'react'

import { ActivityLogEntryType } from '../../bp-modules/user-settings/ActivityLog'
import { BIRTHDAY_EXTENSION_NAME } from '../../cards/extensions/BirthdayExtension'
import { NUERNBERG_PASS_ID_EXTENSION_NAME } from '../../cards/extensions/NuernbergPassIdExtension'

// Check column names of the activityLogConfig have the same order and amount than here
const ActivityLogEntry = (logEntry: ActivityLogEntryType): ReactNode => {
const ActivityLogEntry = (logEntry: ActivityLogEntryType): ReactElement => {
const { card, timestamp } = logEntry
const birthdayExtension = card.extensions[BIRTHDAY_EXTENSION_NAME] ?? null
const passIdExtension = card.extensions[NUERNBERG_PASS_ID_EXTENSION_NAME] ?? null

return (
<tr key={card.id}>
<td>{timestamp.toLocaleString()}</td>
<td>{card.fullName}</td>
{passIdExtension !== null && <td>{passIdExtension}</td>}
{birthdayExtension !== null && <td>{birthdayExtension.format()}</td>}
{card.expirationDate !== null && <td>{card.expirationDate.format()}</td>}
<td data-testid='activity-log-entry-timestamp'>{timestamp.toLocaleString()}</td>
<td data-testid='activity-log-entry-fullname'>{card.fullName}</td>
{passIdExtension !== null && <td data-testid='activity-log-entry-pass-id'>{passIdExtension}</td>}
{birthdayExtension !== null && <td data-testid='activity-log-entry-birthday'>{birthdayExtension.format()}</td>}
{card.expirationDate !== null && <td data-testid='activity-log-entry-expiry'>{card.expirationDate.format()}</td>}
</tr>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react'
import React from 'react'

import { activityLogCardExample } from '../../../bp-modules/user-settings/__mocks__/ActivityLogData'
import ActivityLogEntry from '../ActivityLogEntry'

jest.useFakeTimers({ now: new Date('2024-01-01T00:00:00.000Z') })
describe('ActivityLogEntry', () => {
it('should render the correct log entry content', () => {
const { getByTestId } = render(<ActivityLogEntry timestamp={new Date()} card={activityLogCardExample} />)
expect(getByTestId('activity-log-entry-timestamp').textContent).toBe('1/1/2024, 12:00:00 AM')
expect(getByTestId('activity-log-entry-fullname').textContent).toBe('Thea Test')
expect(getByTestId('activity-log-entry-pass-id').textContent).toBe('3132222')
expect(getByTestId('activity-log-entry-birthday').textContent).toBe('01.02.2000')
expect(getByTestId('activity-log-entry-expiry').textContent).toBe('01.01.2026')
})
})

0 comments on commit 7a07802

Please sign in to comment.