import { render } from '@stencil/vitest'; import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'; describe('ontario-back-button', () => { let warnSpy: MockInstance; let historyBackSpy: MockInstance; beforeEach(() => { warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); historyBackSpy = vi.spyOn(window.history, 'back').mockImplementation(() => undefined); }); afterEach(() => { warnSpy.mockRestore(); historyBackSpy.mockRestore(); }); describe('renders', () => { it('renders the default button output', async () => { const page = await render(``); expect(page.root?.shadowRoot?.querySelector('button')).not.toBeNull(); expect(page.root?.shadowRoot?.querySelector('a')).toBeNull(); expect(page.root?.shadowRoot?.querySelector('button')?.textContent?.replace(/\s+/g, ' ').trim()).toBe('Back'); }); it('renders link output in href mode', async () => { const page = await render(``); const link = page.root?.shadowRoot?.querySelector('a'); expect(link).not.toBeNull(); expect(link?.getAttribute('href')).toBe('/previous-step'); expect(page.root?.shadowRoot?.querySelector('button')).toBeNull(); }); it('renders Back by default', async () => { const page = await render(``); expect(page.root?.shadowRoot?.querySelector('button')?.textContent?.replace(/\s+/g, ' ').trim()).toBe('Back'); }); it('renders Retour when language is fr and label is not provided', async () => { const page = await render(``); expect(page.root?.shadowRoot?.querySelector('button')?.textContent?.replace(/\s+/g, ' ').trim()).toBe('Retour'); }); it('uses label prop when provided', async () => { const page = await render(``); expect(page.root?.shadowRoot?.querySelector('button')?.textContent?.replace(/\s+/g, ' ').trim()).toBe( 'Go back now', ); }); it('renders an anchor when href is provided and back-mode is omitted', async () => { const page = await render(``); const link = page.root?.shadowRoot?.querySelector('a'); const button = page.root?.shadowRoot?.querySelector('button'); expect(link).not.toBeNull(); expect(link?.getAttribute('href')).toBe('/previous-step'); expect(button).toBeNull(); }); it('renders a button when back-mode is href but href is missing', async () => { const page = await render(``); expect(page.root?.shadowRoot?.querySelector('button')).not.toBeNull(); expect(page.root?.shadowRoot?.querySelector('a')).toBeNull(); expect(warnSpy).toHaveBeenCalledTimes(1); }); }); describe('behaviour by mode', () => { it('honours explicit event mode set via property when href is also set', async () => { const page = await render(``); const host = page.root as HTMLOntarioBackButtonElement; host.backMode = 'event'; await page.waitForChanges(); const eventSpy = vi.fn(); host.addEventListener('backClick', eventSpy); const button = host.shadowRoot?.querySelector('button') as HTMLButtonElement; expect(button).not.toBeNull(); expect(host.shadowRoot?.querySelector('a')).toBeNull(); button.click(); expect(eventSpy).toHaveBeenCalledTimes(1); expect(historyBackSpy).toHaveBeenCalledTimes(0); }); it('honours explicit history mode set via property when href is also set', async () => { const page = await render(``); const host = page.root as HTMLOntarioBackButtonElement; host.backMode = 'history'; await page.waitForChanges(); const eventSpy = vi.fn(); host.addEventListener('backClick', eventSpy); const button = host.shadowRoot?.querySelector('button') as HTMLButtonElement; expect(button).not.toBeNull(); expect(host.shadowRoot?.querySelector('a')).toBeNull(); button.click(); expect(eventSpy).toHaveBeenCalledTimes(1); expect(historyBackSpy).toHaveBeenCalledTimes(1); }); it('history mode emits event and calls window.history.back', async () => { const page = await render(``); const eventSpy = vi.fn(); page.root?.addEventListener('backClick', eventSpy); const button = page.root?.shadowRoot?.querySelector('button') as HTMLButtonElement; button.click(); expect(eventSpy).toHaveBeenCalledTimes(1); expect(historyBackSpy).toHaveBeenCalledTimes(1); }); it('event mode emits event only', async () => { const page = await render(``); const eventSpy = vi.fn(); page.root?.addEventListener('backClick', eventSpy); const button = page.root?.shadowRoot?.querySelector('button') as HTMLButtonElement; button.click(); expect(eventSpy).toHaveBeenCalledTimes(1); expect(historyBackSpy).toHaveBeenCalledTimes(0); }); it('href mode emits event on click', async () => { const page = await render(``); const eventSpy = vi.fn(); page.root?.addEventListener('backClick', eventSpy); const link = page.root?.shadowRoot?.querySelector('a') as HTMLAnchorElement; link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); expect(eventSpy).toHaveBeenCalledTimes(1); expect(historyBackSpy).toHaveBeenCalledTimes(0); }); it('disabled mode blocks interaction and does not emit events', async () => { const page = await render(``); const eventSpy = vi.fn(); page.root?.addEventListener('backClick', eventSpy); const button = page.root?.shadowRoot?.querySelector('button') as HTMLButtonElement; button.click(); expect(eventSpy).toHaveBeenCalledTimes(0); expect(historyBackSpy).toHaveBeenCalledTimes(0); }); }); describe('accessibility', () => { it('has accessible button semantics', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); expect(button?.tagName).toBe('BUTTON'); expect(button?.getAttribute('type')).toBe('button'); }); it('decorative icon is hidden from screen readers', async () => { const page = await render(``); const icon = page.root?.shadowRoot?.querySelector('ontario-icon-chevron-left'); expect(icon?.getAttribute('aria-hidden')).toBe('true'); }); it('has accessible name from visible label text', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); const buttonText = button?.textContent?.trim(); expect(buttonText).toContain('Back'); }); }); describe('href attribute handling', () => { it('renders correct href value when provided', async () => { const page = await render(``); const link = page.root?.shadowRoot?.querySelector('a'); expect(link?.getAttribute('href')).toBe('/contact-details'); }); it('warns when href mode is used without href value', async () => { await render(``); expect(warnSpy).toHaveBeenCalled(); }); }); describe('bilingual support', () => { it('renders English label by default', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); const text = button?.textContent?.replace(/\s+/g, ' ').trim(); expect(text).toBe('Back'); }); it('renders French label when language="fr"', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); const text = button?.textContent?.replace(/\s+/g, ' ').trim(); expect(text).toBe('Retour'); }); it('preserves custom label across language changes', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); const text = button?.textContent?.replace(/\s+/g, ' ').trim(); expect(text).toBe('Custom Label'); }); }); describe('disabled prop variants', () => { it('recognizes disabled boolean attribute', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); expect(button?.hasAttribute('disabled')).toBe(true); }); it('recognizes disabled="true" string attribute', async () => { const page = await render(``); const button = page.root?.shadowRoot?.querySelector('button'); expect(button?.hasAttribute('disabled')).toBe(true); }); }); });