import React from 'react' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { type CustomBreadcrumbProps, type SectionTitleRenderProps } from './types' import { TitleBlock } from './index' const user = userEvent.setup() const mockMatchMedia = (matches: boolean = false): void => { Object.defineProperty(window, 'matchMedia', { writable: true, value: vi.fn().mockImplementation((query) => ({ matches, media: query, onchange: null, addListener: vi.fn(), // Deprecated removeListener: vi.fn(), // Deprecated addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), })), }) } describe('', () => { beforeEach(() => { mockMatchMedia() }) describe('when the primary action is a button with only an href', () => { const primaryActionAsLink = { label: 'primaryActionLabel', href: '#primaryActionHref', primary: true, } it('renders the primary action button label and href', () => { const { getByRole } = render( Example , ) const btn = getByRole('link', { name: primaryActionAsLink.label }) expect(btn.textContent).toEqual(primaryActionAsLink.label) expect(btn.getAttribute('href')).toEqual(primaryActionAsLink.href) }) }) describe('when the primary action is a button with only an onClick', () => { const testOnClickFn = vi.fn() const primaryActionAsButton = { label: 'primaryActionLabel', onClick: testOnClickFn, primary: true, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders the primary action button label and onClick', async () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: primaryActionAsButton.label }) expect(btn.textContent).toEqual(primaryActionAsButton.label) await user.click(btn) await waitFor(() => { expect(testOnClickFn).toHaveBeenCalled() }) }) }) describe('when the primary action is disabled', () => { const testOnClickFn = vi.fn() const primaryActionAsButton = { label: 'primaryActionLabel', onClick: testOnClickFn, disabled: true, primary: true, } const primaryActionAsLink = { label: 'primaryActionLabel', href: '#primaryActionHref', disabled: true, primary: true, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders a disabled primary action button', async () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: primaryActionAsButton.label }) as HTMLButtonElement expect(btn.textContent).toEqual(primaryActionAsButton.label) expect(btn.disabled).toBeTruthy() await user.click(btn) await waitFor(() => { expect(testOnClickFn).not.toHaveBeenCalled() }) }) it('renders a disabled primary action link button', () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: primaryActionAsButton.label }) as HTMLButtonElement expect(btn.textContent).toEqual(primaryActionAsLink.label) expect(btn.getAttribute('href')).not.toEqual(primaryActionAsLink.href) }) }) describe('when the primary action is a button with both an href and an onClick', () => { const testOnClickFn = vi.fn() const primaryActionAsLinkAndOnClick = { label: 'primaryActionLabel', href: '#primaryActionHref', onClick: testOnClickFn, primary: true, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders the primary action button label, href and onClick', async () => { const { getByRole } = render( Example , ) const btn = getByRole('link', { name: primaryActionAsLinkAndOnClick.label }) expect(btn.textContent).toEqual(primaryActionAsLinkAndOnClick.label) expect(btn.getAttribute('href')).toEqual(primaryActionAsLinkAndOnClick.href) await user.click(btn) await waitFor(() => { expect(testOnClickFn).toHaveBeenCalled() }) }) }) describe('when the primary action is a menu', () => { const primaryActionAsMenu = { label: 'primaryActionLabel', menuItems: [ { label: 'Menu item 1', href: '#', }, { label: 'Menu item 1', href: '#', }, ], } it('renders the primary action menu button with label and menu items', async () => { const { getByRole, getAllByRole } = render( Example , ) const btn = getByRole('button', { name: primaryActionAsMenu.label }) expect(btn).toHaveAccessibleName(primaryActionAsMenu.label) await user.click(btn) await waitFor(() => { const menuItems = getAllByRole('listitem') expect(menuItems.length).toEqual(2) }) }) }) describe('when the default action is a button with only an href', () => { const defaultActionAsLink = { label: 'defaultActionLabel', href: '#defaultActionHref', } it('renders the default action button label and href', () => { const { getByRole } = render( Example , ) const btn = getByRole('link', { name: defaultActionAsLink.label }) expect(btn.textContent).toEqual(defaultActionAsLink.label) expect(btn.getAttribute('href')).toEqual(defaultActionAsLink.href) }) }) describe('when the default action is a button with only an onClick', () => { const testOnClickFn = vi.fn() const defaultActionAsButton = { label: 'defaultActionLabel', onClick: testOnClickFn, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders the default action button label and onClick', async () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: defaultActionAsButton.label }) expect(btn.textContent).toEqual(defaultActionAsButton.label) await user.click(btn) await waitFor(() => { expect(testOnClickFn).toHaveBeenCalled() }) }) }) describe('when the default action is a button with both an href and an onClick', () => { const testOnClickFn = vi.fn() const defaultActionAsLinkAndOnClick = { label: 'defaultActionLabel', href: '#defaultActionHref', onClick: testOnClickFn, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders the default action button label, href and onClick', async () => { const { getByRole } = render( Example , ) const btn = getByRole('link', { name: defaultActionAsLinkAndOnClick.label }) expect(btn.textContent).toEqual(defaultActionAsLinkAndOnClick.label) expect(btn.getAttribute('href')).toEqual(defaultActionAsLinkAndOnClick.href) await user.click(btn) await waitFor(() => { expect(testOnClickFn).toHaveBeenCalled() }) }) }) describe('when the default action is disabled', () => { const testOnClickFn = vi.fn() const defaultActionAsButton = { label: 'defaultActionLabel', onClick: testOnClickFn, disabled: true, } const defaultActionAsLink = { label: 'defaultActionLabel', href: '#defaultActionHref', disabled: true, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders a disabled default action button', async () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: defaultActionAsButton.label }) as HTMLButtonElement expect(btn.textContent).toEqual(defaultActionAsButton.label) expect(btn.disabled).toBeTruthy() await user.click(btn) await waitFor(() => { expect(testOnClickFn).not.toHaveBeenCalled() }) }) it('renders a disabled default action link button', () => { const { getByRole } = render( Example , ) const btn = getByRole('button', { name: defaultActionAsLink.label }) as HTMLButtonElement expect(btn.textContent).toEqual(defaultActionAsLink.label) expect(btn.getAttribute('href')).not.toEqual(defaultActionAsLink.href) }) }) describe('when a secondary action is passed with both an href and an onClick', () => { const testOnClickFn = vi.fn() const secondaryActionWithLinkAndOnClick = { label: 'secondaryActionLabel', href: '#secondaryActionHref', onClick: testOnClickFn, } beforeEach(() => { testOnClickFn.mockClear() }) it('renders the secondary action with both the href and onClick', async () => { const mockWarnFn = vi.fn() const spy = vi.spyOn(global.console, 'warn').mockImplementation(mockWarnFn) const { getByRole } = render( Example , ) const btn = getByRole('link', { name: secondaryActionWithLinkAndOnClick.label, }) expect(btn).toBeTruthy() expect(mockWarnFn).toBeCalled() expect(btn.textContent).toEqual(secondaryActionWithLinkAndOnClick.label) expect(btn.getAttribute('href')).toEqual(secondaryActionWithLinkAndOnClick.href) await user.click(btn) await waitFor(() => { expect(testOnClickFn).toHaveBeenCalled() }) spy.mockRestore() }) }) describe('when a custom component is provided for section title', () => { it('renders a custom element in section title', async () => { const expectedText = 'This is a button' const CustomComponent = (props: SectionTitleRenderProps): JSX.Element => ( ) render( , ) const el = await screen.findByRole('button') expect(el.textContent).toBe(expectedText) }) it('renders a heading when no render prop is provided', async () => { const expectedText = 'My expected text' render() const el = await screen.findByRole('heading', { level: 2 }) expect(el.textContent).toBe(expectedText) }) }) describe('automation ID behaviour', () => { describe('when default automation IDs are not provided alongside required conditional renders', () => { it('renders the default automation IDs', () => { const automationdIds = { titleAutomationId: 'TitleBlock__Title', avatarAutomationId: 'TitleBlock__Avatar', subtitleAutomationId: 'TitleBlock__Subtitle', sectionTitleAutomationId: 'TitleBlock__SectionTitle', sectionTitleDescriptionAutomationId: 'TitleBlock__SectionTitleDescription', breadcrumbAutomationId: 'TitleBlock__Breadcrumb', breadcrumbTextAutomationId: 'TitleBlock__BreadcrumbText', } const { container } = render( Test JSX Avatar Component} breadcrumb={{ text: 'Test Breadcrumb', path: '/', handleClick: vi.fn(), }} sectionTitle="Test Section Title" sectionTitleDescription="Test Section Title Description" > Example , ) for (const automationId of Object.values(automationdIds)) { expect(container.querySelector(`[data-automation-id="${automationId}"]`)).toBeTruthy() } }) }) describe('when default automation IDs are provided alongside required conditional renders', () => { it('renders the provided automation IDs', () => { const automationdIds = { titleAutomationId: 'titleBlockTitle', avatarAutomationId: 'titleBlockAvatar', subtitleAutomationId: 'titleBlockSubtitle', sectionTitleAutomationId: 'titleBlockSectionTitle', sectionTitleDescriptionAutomationId: 'titleBlockSectionTitleDescription', breadcrumbAutomationId: 'breadcrumbAutomationId', breadcrumbTextAutomationId: 'breadcrumbTextAutomationId', } const { container } = render( Test JSX Avatar Component} breadcrumb={{ text: 'Test Breadcrumb', path: '/', handleClick: vi.fn(), }} sectionTitle="Test Section Title" sectionTitleDescription="Test Section Title Description" titleAutomationId={automationdIds.titleAutomationId} avatarAutomationId={automationdIds.avatarAutomationId} subtitleAutomationId={automationdIds.subtitleAutomationId} sectionTitleAutomationId={automationdIds.sectionTitleAutomationId} sectionTitleDescriptionAutomationId={automationdIds.sectionTitleDescriptionAutomationId} breadcrumbAutomationId={automationdIds.breadcrumbAutomationId} breadcrumbTextAutomationId={automationdIds.breadcrumbTextAutomationId} > Example , ) for (const automationId of Object.values(automationdIds)) { expect(container.querySelector(`[data-automation-id="${automationId}"]`)).toBeTruthy() } }) }) }) describe('breadcrumb', () => { it('renders a link when you pass a path prop', () => { render( undefined, }} > Example , ) expect(screen.getByRole('link', { name: 'Back' })).toBeInTheDocument() }) it("renders a button when you don't pass a path prop", () => { render( undefined, }} > Example , ) expect(screen.getByRole('button', { name: 'Back' })).toBeInTheDocument() }) it("renders a custom component when you pass a 'render' prop", async () => { const mockFn = vi.fn() const CustomComponent = (props: CustomBreadcrumbProps): JSX.Element => ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
{props.children}
) render( Example , ) const customElement = screen.getByTestId('custom-component') expect(customElement).toHaveTextContent('Back') await user.click(customElement) await waitFor(() => { expect(mockFn).toBeCalled() }) }) }) describe("renders a custom component when you pass a 'component' prop to a action", () => { const MockLinkComponent = (props: any): JSX.Element => ( {props.children} ) const MockButtonComponent = (props: any): JSX.Element => ( ) describe('primaryAction', () => { it('will render a custom anchor component in the main action toolbar', () => { render( Example , ) const toolbar = screen.getByTestId('title-block-main-actions-toolbar') within(toolbar).getByRole('link', { name: 'Primary action', }) expect( within(toolbar).getByRole('link', { name: 'Primary action', }), ).toHaveAttribute('href', '#test-primary') }) it('will render custom button with children and not label', () => { const testClickFunc = vi.fn() render( ( This will replace label ), }} > Example , ) const toolbar = screen.getByTestId('title-block-main-actions-toolbar') within(toolbar).getByRole('button', { name: 'This will replace label', }) expect( within(toolbar).queryByRole('button', { name: 'Primary action', }), ).toBeFalsy() }) }) describe('secondaryActions', () => { it('will render multiple custom anchor components in the secondary actions toolbar', () => { render( Example , ) const toolbar = screen.getByTestId('title-block-secondary-actions-toolbar') const links = within(toolbar).getAllByRole('link') expect(links.length).toBe(2) expect(links[0]).toHaveAttribute('href', '#test-secondary') expect(links[1]).toHaveAttribute('href', '#test-secondary-2') }) }) describe('defaultAction', () => { it('will render a custom anchor components in the main action toolbar', () => { render( Example , ) const toolbar = screen.getByTestId('title-block-main-actions-toolbar') const defaultActionAnchor = within(toolbar).getByRole('link', { name: 'Default action', }) expect(defaultActionAnchor).toHaveAttribute('href', '#test-default') }) }) }) })