diff --git a/frontend/bun.lockb b/frontend/bun.lockb index b089174c..d97b225d 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/bunfig.toml b/frontend/bunfig.toml new file mode 100644 index 00000000..eb9f8130 --- /dev/null +++ b/frontend/bunfig.toml @@ -0,0 +1,2 @@ +[test] +preload = ["./src/utils/bun-test-setup.ts", "./src/utils/test-utils.tsx"] \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 776a0ec2..e17ee490 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,14 +18,15 @@ "@types/react-redux": "7.1.34", "@vitejs/plugin-react": "4.3.3", "typescript": "5.6.3", - "vite": "5.4.10", - "vite-tsconfig-paths": "5.1.0" + "vite": "5.4.11", + "vite-tsconfig-paths": "5.1.2" }, "dependencies": { "@biomejs/biome": "1.9.4", "@grafana/faro-react": "1.11.0", "@grafana/faro-web-sdk": "1.11.0", "@grafana/faro-web-tracing": "1.11.0", + "@happy-dom/global-registrator": "15.11.0", "@navikt/aksel-icons": "7.5.1", "@navikt/ds-css": "7.5.1", "@navikt/ds-react": "7.5.1", @@ -33,12 +34,14 @@ "@navikt/nav-dekoratoren-moduler": "3.1.1", "@reduxjs/toolkit": "2.3.0", "@styled-icons/material": "10.47.0", + "@testing-library/react": "16.0.1", + "@testing-library/user-event": "14.5.2", "date-fns": "4.1.0", - "react": "18.3.1", "react-dom": "18.3.1", "react-redux": "9.1.2", - "react-router": "6.28.0", "react-router-dom": "6.28.0", + "react-router": "6.28.0", + "react": "18.3.1", "styled-components": "6.1.13" } } diff --git a/frontend/src/components/case/innlogget/kvittering/kvittering-loading.tsx b/frontend/src/components/case/innlogget/status/status-loading.tsx similarity index 94% rename from frontend/src/components/case/innlogget/kvittering/kvittering-loading.tsx rename to frontend/src/components/case/innlogget/status/status-loading.tsx index 29b0b467..08535fd2 100644 --- a/frontend/src/components/case/innlogget/kvittering/kvittering-loading.tsx +++ b/frontend/src/components/case/innlogget/status/status-loading.tsx @@ -9,7 +9,7 @@ interface Props { type: CaseType; } -export const KvitteringLoading = ({ informStillWorking, type }: Props) => { +export const StatusLoading = ({ informStillWorking, type }: Props) => { const { skjema, icons } = useTranslation(); return ( diff --git a/frontend/src/components/case/innlogget/kvittering/kvittering-page-loader.tsx b/frontend/src/components/case/innlogget/status/status-page-loader.tsx similarity index 69% rename from frontend/src/components/case/innlogget/kvittering/kvittering-page-loader.tsx rename to frontend/src/components/case/innlogget/status/status-page-loader.tsx index 0fbf4e15..937351c7 100644 --- a/frontend/src/components/case/innlogget/kvittering/kvittering-page-loader.tsx +++ b/frontend/src/components/case/innlogget/status/status-page-loader.tsx @@ -2,15 +2,15 @@ import { useGetCaseQuery } from '@app/redux-api/case/api'; import { CaseStatus, type CaseType } from '@app/redux-api/case/types'; import { API_PATH } from '@app/redux-api/common'; import { useEffect, useState } from 'react'; -import { Journalpost, Kvittering } from './kvittering'; -import { KvitteringLoading } from './kvittering-loading'; +import { Journalpost, Status } from './status'; +import { StatusLoading } from './status-loading'; interface Props { caseId: string; type: CaseType; } -export const KvitteringPageLoader = ({ type, caseId }: Props) => { +export const StatusPageLoader = ({ type, caseId }: Props) => { const [showStillWorking, setShowStillWorking] = useState(false); const [showKvittering, setShowKvittering] = useState(false); @@ -27,14 +27,14 @@ export const KvitteringPageLoader = ({ type, caseId }: Props) => { }, []); if (data === undefined) { - return ; + return ; } const isDone = data.status === CaseStatus.DONE; if (isDone) { return ( - + {data.journalpostId !== null ? ( { type={data.type} /> ) : null} - + ); } if (showKvittering) { - return ; + return ; } - return ; + return ; }; diff --git a/frontend/src/components/case/innlogget/kvittering/kvittering-page.tsx b/frontend/src/components/case/innlogget/status/status-page.tsx similarity index 75% rename from frontend/src/components/case/innlogget/kvittering/kvittering-page.tsx rename to frontend/src/components/case/innlogget/status/status-page.tsx index 68cd60ff..0a8f7935 100644 --- a/frontend/src/components/case/innlogget/kvittering/kvittering-page.tsx +++ b/frontend/src/components/case/innlogget/status/status-page.tsx @@ -1,18 +1,18 @@ import { DigitalFormContainer } from '@app/components/case/common/digital/digital-form-container'; -import { KvitteringPageLoader } from '@app/components/case/innlogget/kvittering/kvittering-page-loader'; import { CaseLoader } from '@app/components/case/innlogget/loader'; +import { StatusPageLoader } from '@app/components/case/innlogget/status/status-page-loader'; import { useLanguage } from '@app/language/use-language'; import { useTranslation } from '@app/language/use-translation'; import { type Case, CaseStatus } from '@app/redux-api/case/types'; import { Navigate } from 'react-router-dom'; -export const CaseKvitteringPage = () => ; +export const CaseStatusPage = () => ; interface Props { data: Case; } -const RenderCaseKvitteringPage = ({ data }: Props) => { +const RenderCaseStatusPage = ({ data }: Props) => { const language = useLanguage(); const { skjema } = useTranslation(); @@ -32,7 +32,7 @@ const RenderCaseKvitteringPage = ({ data }: Props) => { innsendingsytelse={data.innsendingsytelse} title_fragment={title_fragment[data.type]} > - + ); }; diff --git a/frontend/src/components/case/innlogget/kvittering/kvittering.tsx b/frontend/src/components/case/innlogget/status/status.tsx similarity index 97% rename from frontend/src/components/case/innlogget/kvittering/kvittering.tsx rename to frontend/src/components/case/innlogget/status/status.tsx index f2e8f729..9f279de5 100644 --- a/frontend/src/components/case/innlogget/kvittering/kvittering.tsx +++ b/frontend/src/components/case/innlogget/status/status.tsx @@ -18,7 +18,7 @@ interface Props { ytelse: Innsendingsytelse; } -export const Kvittering = ({ children, type, ytelse }: Props) => { +export const Status = ({ children, type, ytelse }: Props) => { const { skjema, icons } = useTranslation(); return ( diff --git a/frontend/src/components/case/innlogget/status/timeline-item.test.tsx b/frontend/src/components/case/innlogget/status/timeline-item.test.tsx new file mode 100644 index 00000000..edead416 --- /dev/null +++ b/frontend/src/components/case/innlogget/status/timeline-item.test.tsx @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'bun:test'; +import { TimelineItem } from '@app/components/case/innlogget/status/timeline-item'; +import { render, screen } from '@app/utils/test-utils'; +import { FileIcon } from '@navikt/aksel-icons'; + +describe('TimelineItem', () => { + it('has heading, date and icon', () => { + render(} />); + expect(screen.getByText(/Heading/)).toBeDefined(); + expect(screen.getByText(/30.11.2024/)).toBeDefined(); + }); +}); diff --git a/frontend/src/components/case/innlogget/status/timeline-item.tsx b/frontend/src/components/case/innlogget/status/timeline-item.tsx new file mode 100644 index 00000000..c573ca72 --- /dev/null +++ b/frontend/src/components/case/innlogget/status/timeline-item.tsx @@ -0,0 +1,72 @@ +import { BodyShort, HStack, Heading, VStack } from '@navikt/ds-react'; +import { format, parse } from 'date-fns'; +import { styled } from 'styled-components'; + +interface TimelineItemProps { + title: string; + date: string; + icon: React.ReactNode; + text?: string; + children?: React.ReactNode; +} + +export const TimelineItem = ({ title, date, icon, text, children }: TimelineItemProps) => ( + + + {icon} + + + + + {title} + + + + + {text === undefined ? null : {text}} + + {children === undefined ? null : {children}} + + +); + +const Icon = styled.div` + position: relative; + z-index: 0; + + &::after { + content: ''; + height: 100%; + width: var(--a-spacing-05); + background-color: var(--a-bg-subtle); + position: absolute; + top: 0; + bottom: 0; + left: 50%; + transform: translateX(-50%); + z-index: -1; + } +`; + +const IconCircle = styled.div` + width: var(--a-spacing-10); + height: var(--a-spacing-10); + border-radius: 50%; + background-color: var(--a-bg-subtle); + display: flex; + justify-content: center; + align-items: center; +`; + +interface DateProps { + date: string; +} + +const DisplayDate = ({ date }: DateProps) => ( + {format(parse(date, 'yyyy-MM-dd', new Date()), 'dd.MM.yyyy')} +); + +const StyledTime = styled.time` + font-size: var(--a-font-size-small); + color: var(--a-text-subtle); +`; diff --git a/frontend/src/routes/routes.tsx b/frontend/src/routes/routes.tsx index 4707e61f..e46f2fe2 100644 --- a/frontend/src/routes/routes.tsx +++ b/frontend/src/routes/routes.tsx @@ -1,6 +1,6 @@ import { CaseBegrunnelsePage } from '@app/components/case/innlogget/begrunnelse/begrunnelse-page'; import { CaseInnsendingPage } from '@app/components/case/innlogget/innsending/innsending-page'; -import { CaseKvitteringPage } from '@app/components/case/innlogget/kvittering/kvittering-page'; +import { CaseStatusPage } from '@app/components/case/innlogget/status/status-page'; import { CaseOppsummeringPage } from '@app/components/case/innlogget/summary/oppsummering-page'; import { SessionCasebegrunnelsePage } from '@app/components/case/uinnlogget/begrunnelse/begrunnelse-page'; import { SessionCaseInnsendingPage } from '@app/components/case/uinnlogget/innsending/innsending-page'; @@ -30,7 +30,8 @@ export const Router = () => ( } /> } /> } /> - } /> + } /> + } /> diff --git a/frontend/src/utils/bun-test-setup.ts b/frontend/src/utils/bun-test-setup.ts new file mode 100644 index 00000000..1512a7e6 --- /dev/null +++ b/frontend/src/utils/bun-test-setup.ts @@ -0,0 +1,3 @@ +import { GlobalRegistrator } from '@happy-dom/global-registrator'; + +GlobalRegistrator.register(); diff --git a/frontend/src/utils/test-utils.tsx b/frontend/src/utils/test-utils.tsx new file mode 100644 index 00000000..a51fd1ea --- /dev/null +++ b/frontend/src/utils/test-utils.tsx @@ -0,0 +1,8 @@ +import { render } from '@testing-library/react'; +import type { ReactElement } from 'react'; + +const customRender = (ui: ReactElement, options = {}) => render(ui, options); + +export { default as userEvent } from '@testing-library/user-event'; +export * from '@testing-library/react'; +export { customRender as render };