diff --git a/src/login-web-app/src/haapi-stepper/README.md b/src/login-web-app/src/haapi-stepper/README.md index b6c8e09e..aac608ed 100644 --- a/src/login-web-app/src/haapi-stepper/README.md +++ b/src/login-web-app/src/haapi-stepper/README.md @@ -114,6 +114,19 @@ Because the `HaapiStepperStepUI` handles all possible HAAPI authentication flows Check out [the HaapiStepperStepUI documentation and usage examples](./feature/steps/HaapiStepperStepUI.tsx). +### ViewName built-in UIs + +Some HAAPI viewNames (`step.metadata.viewName`) need a UI that the generic step rendering can't deliver well. For example, the **BankID** screen needs to render a spinner while the polling status is `pending`, and lifts the QR code above the actions. + +To handle this kind of view, the library ships **viewName built-in UIs** that automatically take over when the matching `step.metadata.viewName` arrives from the server. + +#### The `enableViewNameBuiltInUIs` prop + +`` accepts a +`enableViewNameBuiltInUIs?: HaapiStepperViewNameBuiltInUI[] | boolean` prop that opts in to which viewName built-in UIs are active. It is **opt-in**: when the prop is omitted (or `false`), no viewName built-in UIs are applied and every step renders through the generic render pipeline. Pass `true` to enable all known built-ins, or an array of built-in view names (`HaapiStepperViewNameBuiltInUI[]`) to pin a specific subset. + +Check out documentation and usage examples in [`HaapiStepperStepUI`](./feature/steps/HaapiStepperStepUI.tsx), and the test use cases in [`HaapiStepperStepUI.spec.tsx`](./feature/steps/HaapiStepperStepUI.spec.tsx) (`describe('ViewName built-in UIs Rendering')`) for more details. + ## HAAPI Stepper UI Components @@ -132,6 +145,7 @@ Check out documentation and usage examples in the links below: * [HaapiStepperSelectorUI](./feature/actions/selector/HaapiStepperSelectorUI.tsx) * [HaapiStepperClientOperationUI](./feature/actions/client-operation/HaapiStepperClientOperationUI.tsx) * [HaapiStepperMessagesUI](./ui/messages/HaapiStepperMessagesUI.tsx) +* [HaapiStepperMessageUI](./ui/messages/HaapiStepperMessageUI.tsx) * [HaapiStepperLinksUI](./ui/links/HaapiStepperLinksUI.tsx) * [HaapiStepperLinkUI](./ui/links/HaapiStepperLinkUI.tsx) diff --git a/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.spec.tsx b/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.spec.tsx index e71ee598..95500db3 100644 --- a/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.spec.tsx +++ b/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.spec.tsx @@ -46,14 +46,17 @@ import { HAAPI_ACTION_CLIENT_OPERATIONS, HaapiBaseClientOperationModel, } from '../../data-access/types/haapi-action.types'; -import { HAAPI_STEPS, HAAPI_PROBLEM_STEPS } from '../../data-access/types/haapi-step.types'; +import { HAAPI_STEPS, HAAPI_PROBLEM_STEPS, HAAPI_POLLING_STATUS } from '../../data-access/types/haapi-step.types'; +import { HaapiStepperViewNameBuiltInUI } from '../viewnames'; import { HTTP_METHODS } from '../../data-access/types/haapi-form.types'; import { HaapiStepperStepUI } from './HaapiStepperStepUI'; import { + createBankIdPollingStep, createMockClientOperationAction, createMockFormAction, createMockLink, createMockMessage, + createMockQrLink, createMockSelectorAction, createMockStep, defaultStepperAPI, @@ -1765,4 +1768,181 @@ describe('HaapiStepperStepUI', () => { }); }); }); + + describe('ViewName built-in UIs Rendering', () => { + describe('Default Rendering', () => { + it('should render the generic step shell when enableViewNameBuiltInUIs is not provided', () => { + const step = createBankIdPollingStep(); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + expect(screen.queryByTestId('messages')).toBeInTheDocument(); + }); + + it('should render the generic step shell when enableViewNameBuiltInUIs is an empty array', () => { + const step = createBankIdPollingStep(); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + expect(screen.queryByTestId('messages')).toBeInTheDocument(); + }); + }); + + describe('Custom Rendering', () => { + describe('Opt-in via boolean shorthand', () => { + it('should apply the matching built-in when enableViewNameBuiltInUIs is true', () => { + const step = createBankIdPollingStep(); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + + it('should apply the matching built-in when the JSX boolean shorthand is used', () => { + const step = createBankIdPollingStep(); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + + it('should render the generic step shell when the viewName has no registered built-in', () => { + const step = createMockStep(HAAPI_STEPS.AUTHENTICATION); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + expect(screen.queryByTestId('messages')).toBeInTheDocument(); + expect(screen.queryByTestId('form-action')).toBeInTheDocument(); + }); + }); + + describe('Opt-in via subset array', () => { + it('should apply the built-in when its viewName is in the array', () => { + const step = createBankIdPollingStep(); + + renderWithContext(, { + currentStep: step, + }); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + }); + + describe('Composition with stepRenderInterceptor', () => { + it('should apply the built-in when stepRenderInterceptor returns pass-through data', () => { + const step = createBankIdPollingStep(); + const passThroughInterceptor: HaapiStepperStepUIStepRenderInterceptor = ( + haapiStepperAPI: HaapiStepperAPIWithRequiredCurrentStep + ) => { + return haapiStepperAPI; + }; + + renderWithContext( + , + { currentStep: step } + ); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + + it('should be skipped when stepRenderInterceptor returns a React element', () => { + const step = createBankIdPollingStep(); + const elementInterceptor: HaapiStepperStepUIStepRenderInterceptor = () => { + return
Custom UI
; + }; + + renderWithContext( + , + { + currentStep: step, + } + ); + + expect(screen.queryByTestId('custom-step-element')).toBeInTheDocument(); + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + }); + + it('should be skipped (and render nothing) when stepRenderInterceptor returns null', () => { + const step = createBankIdPollingStep(); + const nullInterceptor: HaapiStepperStepUIStepRenderInterceptor = () => { + return null; + }; + + renderWithContext(, { + currentStep: step, + }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + expect(screen.queryByTestId('messages')).not.toBeInTheDocument(); + expect(screen.queryByTestId('form-action')).not.toBeInTheDocument(); + }); + }); + + describe('BankID viewName built-in UI', () => { + it('should render the spinner while polling status is pending', () => { + const step = createBankIdPollingStep({ status: HAAPI_POLLING_STATUS.PENDING }); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + + it('should not render the spinner when polling status is done', () => { + const step = createBankIdPollingStep({ status: HAAPI_POLLING_STATUS.DONE }); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + }); + + it('should not render the spinner when polling status is failed', () => { + const step = createBankIdPollingStep({ status: HAAPI_POLLING_STATUS.FAILED }); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('bankid-spinner')).not.toBeInTheDocument(); + }); + + it('should render the spinner during the step transition (loading=true after polling has resolved)', () => { + const step = createBankIdPollingStep({ status: HAAPI_POLLING_STATUS.DONE }); + + renderWithContext(, { + currentStep: step, + loading: true, + }); + + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + }); + + it('should render the QR link above the actions', () => { + const qrLink = createMockQrLink(); + const otherLink = createMockLink({ rel: 'help', title: 'Help' }); + const step = createBankIdPollingStep({ links: [qrLink, otherLink] }); + + renderWithContext(, { currentStep: step }); + + const renderedTestIds = screen.getAllByTestId(/^(qr-code-button|form-action)$/).map(element => { + return element.getAttribute('data-testid'); + }); + + expect(renderedTestIds).toEqual(['qr-code-button', 'form-action']); + }); + + it('should render gracefully when no QR link is present', () => { + const otherLink = createMockLink({ rel: 'help', title: 'Help' }); + const step = createBankIdPollingStep({ links: [otherLink] }); + + renderWithContext(, { currentStep: step }); + + expect(screen.queryByTestId('qr-code-button')).not.toBeInTheDocument(); + expect(screen.queryByTestId('bankid-spinner')).toBeInTheDocument(); + expect(screen.queryByTestId('messages')).toBeInTheDocument(); + expect(screen.queryByTestId('links')).toBeInTheDocument(); + }); + }); + }); + }); }); diff --git a/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.tsx b/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.tsx index cae29627..84bc8713 100644 --- a/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.tsx +++ b/src/login-web-app/src/haapi-stepper/feature/steps/HaapiStepperStepUI.tsx @@ -17,6 +17,7 @@ import { HaapiStepperMessagesUI } from '../../ui/messages/HaapiStepperMessagesUI import { Well } from '../../ui/well/Well'; import { applyRenderInterceptor } from '../../util/generic-render-interceptor'; import { formatNextStepData } from '../stepper/data-formatters/format-next-step-data'; +import { HaapiStepperViewNameBuiltInUI, getViewNameBuiltInUI } from '../viewnames'; import type { HaapiStepperAPI, HaapiStepperAPIWithRequiredCurrentStep, @@ -52,6 +53,7 @@ interface HaapiStepperStepUIProps { clientOperationActionRenderInterceptor?: HaapiStepperStepUIClientOperationActionRenderInterceptor; linkRenderInterceptor?: HaapiStepperStepUILinkRenderInterceptor; messageRenderInterceptor?: HaapiStepperStepUIMessageRenderInterceptor; + enableViewNameBuiltInUIs?: HaapiStepperViewNameBuiltInUI[] | boolean; } /** @@ -79,6 +81,56 @@ interface HaapiStepperStepUIProps { * Note: Redirection, and Continue Same steps are handled automatically by the HaapiStepper and never * reach this component * + * ### VIEW NAME BUILT-IN UIs + * + * The HaapiStepperStepUI component also provides built-in UIs for specific HAAPI `viewName`s that require a more + * tailored UI than the generic step shell can provide (e.g. the BankID QR code step, which requires lifting + * the QR code up and showing a spinner while polling). + * + * The viewName built-in UIs are opt-in: `enableViewNameBuiltInUIs` defaults to `undefined` (no built-ins active). + * Pass: + * + * - `true` (or the JSX shorthand `enableViewNameBuiltInUIs`) to enable all known built-ins. This + * stays in sync with the library — if a new built-in is added in a future release, it is + * activated automatically. + * - An array of `HaapiStepperViewNameBuiltInUI` values to enable only specific built-ins. + * This pins the active set, so adding a new built-in to the library is a purely additive + * change that doesn't affect existing rendering. + * - `false` or `undefined` to keep all built-ins disabled (every view renders through the + * generic shell). + * + * Composition: the matching viewName built-in UI is rendered after the `stepRenderInterceptor` has processed the + * step, and before any of the per-element render interceptors (actions, messages, links…). It is only rendered + * when `stepRenderInterceptor` was not provided or if it returns the stepper API data (pass-through) — the same + * rule that governs every other render interceptor. + * + * #### ViewName Built-in UIs Example + * + * @example + * ```tsx + * import { HaapiStepperViewNameBuiltInUI } from '...'; + * + * // No prop = no built-ins active. The component renders every view through the generic shell. + * + * + * // Boolean shorthand: opt in to all known built-ins (current and future). + * + * + * // Pin to a specific subset. + * + * + * // Override a viewName built-in UI with a `stepRenderInterceptor` + * const customBankIdUI: HaapiStepperStepUIStepRenderInterceptor = ({ currentStep, ...rest }) => { + * if (currentStep.metadata?.viewName === 'authenticator/bankid/wait/index') { + * return ; + * } + * return { currentStep, ...rest }; + * }; + * + * // MyBankId will be rendered instead of the built-in UI for the BankID + * + * ``` + * * ## CUSTOMIZATION * * ### CUSTOMIZATION DIMENSIONS @@ -241,6 +293,7 @@ export const HaapiStepperStepUI = ({ clientOperationActionRenderInterceptor, linkRenderInterceptor, messageRenderInterceptor, + enableViewNameBuiltInUIs, }: HaapiStepperStepUIProps) => { const haapiStepperAPI = useHaapiStepper(); const loadingElement: ReactElement | null = getLoadingElement(haapiStepperAPI, loadingRenderInterceptor); @@ -249,38 +302,46 @@ export const HaapiStepperStepUI = ({ return loadingElement; } - let haapiUIStepperAPI = haapiStepperAPI as HaapiStepperAPIWithRequiredCurrentStep; + let haapiStepperUiAPI = haapiStepperAPI as HaapiStepperAPIWithRequiredCurrentStep; if (stepRenderInterceptor) { - const customStepRenderInterceptorResult = stepRenderInterceptor(haapiUIStepperAPI); + const stepRenderInterceptorResult = stepRenderInterceptor(haapiStepperUiAPI); - if (isValidElement(customStepRenderInterceptorResult)) { - return customStepRenderInterceptorResult; - } else if (customStepRenderInterceptorResult === null || customStepRenderInterceptorResult === undefined) { + if (isValidElement(stepRenderInterceptorResult)) { + return stepRenderInterceptorResult; + } + + if (stepRenderInterceptorResult === null || stepRenderInterceptorResult === undefined) { return null; - } else { - haapiUIStepperAPI = { - ...customStepRenderInterceptorResult, - currentStep: formatNextStepData(customStepRenderInterceptorResult.currentStep), - }; } + + haapiStepperUiAPI = { + ...stepRenderInterceptorResult, + currentStep: formatNextStepData(stepRenderInterceptorResult.currentStep), + }; + } + + const ViewNameBuiltInUI = getViewNameBuiltInUI(haapiStepperUiAPI, enableViewNameBuiltInUIs); + + if (ViewNameBuiltInUI) { + return ; } - const { error, currentStep } = haapiUIStepperAPI; - const errorElement: ReactElement | null = getErrorElement(haapiUIStepperAPI, errorRenderInterceptor); + const { error, currentStep } = haapiStepperUiAPI; + const errorElement: ReactElement | null = getErrorElement(haapiStepperUiAPI, errorRenderInterceptor); const linksToDisplay = getLinksToDisplay(error, currentStep); const messagesToDisplay = error?.input ? error.input.dataHelpers.messages : currentStep.dataHelpers.messages; - const messagesElement = getMessagesElement(haapiUIStepperAPI, messagesToDisplay, messageRenderInterceptor); + const messagesElement = getMessagesElement(haapiStepperUiAPI, messagesToDisplay, messageRenderInterceptor); const actionsElement = getActionsElement( - haapiUIStepperAPI, + haapiStepperUiAPI, actionsRenderInterceptor, formActionRenderInterceptor, formFieldRenderInterceptor, selectorActionRenderInterceptor, clientOperationActionRenderInterceptor ); - const linksElement = getLinksElement(haapiUIStepperAPI, linksToDisplay, linkRenderInterceptor); + const linksElement = getLinksElement(haapiStepperUiAPI, linksToDisplay, linkRenderInterceptor); return ( diff --git a/src/login-web-app/src/haapi-stepper/feature/viewnames/BankIdViewNameBuiltInUI.tsx b/src/login-web-app/src/haapi-stepper/feature/viewnames/BankIdViewNameBuiltInUI.tsx new file mode 100644 index 00000000..0a331ca5 --- /dev/null +++ b/src/login-web-app/src/haapi-stepper/feature/viewnames/BankIdViewNameBuiltInUI.tsx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { Spinner } from '../../../shared/ui/Spinner'; +import { Well } from '../../ui/well/Well'; +import { HaapiStepperMessagesUI } from '../../ui/messages/HaapiStepperMessagesUI'; +import { HaapiStepperActionsUI } from '../../ui/actions/HaapiStepperActionsUI'; +import { HaapiStepperLinksUI } from '../../ui/links/HaapiStepperLinksUI'; +import { HAAPI_POLLING_STATUS, HAAPI_STEPS } from '../../data-access/types/haapi-step.types'; +import { HaapiStepperAPIWithRequiredCurrentStep, HaapiStepperLink } from '../stepper/haapi-stepper.types'; + +/** + * Built-in UI for the BankID viewName (`HaapiStepperViewNameBuiltInUI.BANKID`). + * + * - Renders a spinner while the polling status is `pending` *or* while the stepper is loading. Together + * these cover the full BankID-in-progress window so the user always sees a progress indicator. + * - Lifts the QR code link above the actions so it's the primary element on the screen. + */ +export const BankIdViewNameBuiltInUI = ({ currentStep, nextStep, loading }: HaapiStepperAPIWithRequiredCurrentStep) => { + const { messages, actions, links } = currentStep.dataHelpers; + const isQrLink = (link: HaapiStepperLink) => link.subtype?.startsWith('image/') ?? false; + const qrLink = links.find(isQrLink); + const nonQrLinks = links.filter(link => !isQrLink(link)); + const isPollingPending = + currentStep.type === HAAPI_STEPS.POLLING && currentStep.properties.status === HAAPI_POLLING_STATUS.PENDING; + const showSpinner = loading || isPollingPending; + + return ( + + {showSpinner && } + + {qrLink && } + + {nonQrLinks.length > 0 && } + + ); +}; diff --git a/src/login-web-app/src/haapi-stepper/feature/viewnames/index.ts b/src/login-web-app/src/haapi-stepper/feature/viewnames/index.ts new file mode 100644 index 00000000..d6cc12b8 --- /dev/null +++ b/src/login-web-app/src/haapi-stepper/feature/viewnames/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2025 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +export * from './viewname.types'; +export * from './viewname-built-in-uis'; +export * from './BankIdViewNameBuiltInUI'; diff --git a/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname-built-in-uis.ts b/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname-built-in-uis.ts new file mode 100644 index 00000000..a9c344e7 --- /dev/null +++ b/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname-built-in-uis.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import type { FC } from 'react'; +import type { HaapiStepperAPIWithRequiredCurrentStep } from '../stepper/haapi-stepper.types'; +import { BankIdViewNameBuiltInUI } from './BankIdViewNameBuiltInUI'; +import { HaapiStepperViewNameBuiltInUI } from './viewname.types'; + +/** + * Registry of built-in viewName UIs keyed by `HaapiStepperViewNameBuiltInUI`. + * + * Every enum member must have a matching entry here — this is the invariant that keeps the + * set of "view names with built-in UX" in sync with the set of available components. + */ +export const VIEW_NAME_BUILT_IN_UIS_MAP: Record< + HaapiStepperViewNameBuiltInUI, + FC +> = { + [HaapiStepperViewNameBuiltInUI.BANKID]: BankIdViewNameBuiltInUI, +}; + +export const VIEW_NAMES_BUILT_IN_UIS: HaapiStepperViewNameBuiltInUI[] = Object.values(HaapiStepperViewNameBuiltInUI); + +export const getViewNameBuiltInUI = ( + haapiStepperAPI: HaapiStepperAPIWithRequiredCurrentStep, + enableViewNameBuiltInUIs?: HaapiStepperViewNameBuiltInUI[] | boolean +): FC | undefined => { + const currentViewName = haapiStepperAPI.currentStep.metadata?.viewName; + const enabledViewNames: HaapiStepperViewNameBuiltInUI[] = + enableViewNameBuiltInUIs === true + ? VIEW_NAMES_BUILT_IN_UIS + : Array.isArray(enableViewNameBuiltInUIs) + ? enableViewNameBuiltInUIs + : []; + + const isOptedInViewNameBuiltIn = (viewName: string): viewName is HaapiStepperViewNameBuiltInUI => { + return enabledViewNames.includes(viewName as HaapiStepperViewNameBuiltInUI); + }; + + if (!currentViewName || !isOptedInViewNameBuiltIn(currentViewName)) { + return undefined; + } + + return VIEW_NAME_BUILT_IN_UIS_MAP[currentViewName]; +}; diff --git a/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname.types.ts b/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname.types.ts new file mode 100644 index 00000000..e40417f8 --- /dev/null +++ b/src/login-web-app/src/haapi-stepper/feature/viewnames/viewname.types.ts @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +/** + * HAAPI view names that the LWA ships built-in step render interceptors for + * (see `VIEW_NAME_BUILT_IN_UIS_MAP` in `./viewname-built-in-uis`). + * + * Members map one-to-one to entries in the built-in registry: adding a new member here + * must be accompanied by a matching interceptor registration. + */ +export enum HaapiStepperViewNameBuiltInUI { + BANKID = 'authenticator/bankid/wait/index', +} diff --git a/src/login-web-app/src/haapi-stepper/util/tests/mocks.ts b/src/login-web-app/src/haapi-stepper/util/tests/mocks.ts index 7a5739e6..04bc40cb 100644 --- a/src/login-web-app/src/haapi-stepper/util/tests/mocks.ts +++ b/src/login-web-app/src/haapi-stepper/util/tests/mocks.ts @@ -1,5 +1,9 @@ import { MEDIA_TYPES } from '../../../shared/util/types/media.types'; -import { HAAPI_STEPPER_ELEMENT_TYPES, HAAPI_STEPS } from '../../data-access/types/haapi-step.types'; +import { + HAAPI_POLLING_STATUS, + HAAPI_STEPPER_ELEMENT_TYPES, + HAAPI_STEPS, +} from '../../data-access/types/haapi-step.types'; import { HAAPI_ACTION_TYPES, HAAPI_ACTION_CLIENT_OPERATIONS } from '../../data-access/types/haapi-action.types'; import { HAAPI_FORM_FIELDS, HTTP_METHODS } from '../../data-access/types/haapi-form.types'; import type { @@ -12,6 +16,7 @@ import type { HaapiStepperAPI, } from '../../feature/stepper/haapi-stepper.types'; import { formatNextStepData } from '../../feature/stepper/data-formatters/format-next-step-data'; +import { HaapiStepperViewNameBuiltInUI } from '../../feature/viewnames'; export const mockNextStep = vi.fn(); export const MockMessageText = 'Step Message'; @@ -139,3 +144,26 @@ export const defaultStepperAPI: HaapiStepperAPI = { history: [], nextStep: mockNextStep, }; + +export const createBankIdPollingStep = ( + overrides: { status?: HAAPI_POLLING_STATUS; links?: HaapiStepperLink[]; viewName?: string } = {} +) => { + return createMockStep(HAAPI_STEPS.POLLING, { + metadata: { + templateArea: 'lwa', + viewName: overrides.viewName ?? HaapiStepperViewNameBuiltInUI.BANKID, + }, + properties: { status: overrides.status ?? HAAPI_POLLING_STATUS.PENDING }, + ...(overrides.links !== undefined && { links: overrides.links }), + }); +}; + +export const createMockQrLink = (overrides: Partial = {}) => { + return createMockLink({ + rel: 'activation', + title: 'QR Code', + href: 'data:image/svg+xml;base64,abc', + type: 'image/svg+xml', + ...overrides, + }); +};