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);
});
});
});