import React from 'react';
import { vi } from 'vitest';
import { Heading, HeadingLevelProvider, Paragraph } from '@shoptet/ui-core-web';
import { render } from '../../../vitest/render';
describe('core Heading level context', () => {
it('renders
with no provider (context base 0 + level 1)', () => {
const { container } = render(Title);
expect(container.querySelector('h1')).not.toBeNull();
});
it('respects the local level prop without a provider', () => {
const { container } = render(Title);
expect(container.querySelector('h3')).not.toBeNull();
});
it('adds provider offset to the base so a default Heading renders h2 (base 1 + level 1)', () => {
const { container } = render(
Title
);
expect(container.querySelector('h2')).not.toBeNull();
expect(container.querySelector('h1')).toBeNull();
});
it('accumulates nested provider offsets (outer base + inner offset + local level)', () => {
const { container } = render(
Title
);
expect(container.querySelector('h6')).not.toBeNull();
expect(container.querySelector('h2')).toBeNull();
});
it('adds the local level to the inherited base (base 1 + level 2 = h3)', () => {
const { container } = render(
Title
);
expect(container.querySelector('h3')).not.toBeNull();
});
it('emits when the computed level exceeds 6', () => {
const { container } = render(
Title
);
const heading = container.querySelector('h6');
expect(heading).not.toBeNull();
expect(heading?.getAttribute('role')).toBeNull();
expect(heading?.getAttribute('aria-level')).toBe('7');
});
it('warns when the computed level exceeds 9 and caps the exposed aria-level at 9', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
try {
const { container } = render(
Title
);
const heading = container.querySelector('h6');
expect(heading).not.toBeNull();
expect(heading?.getAttribute('aria-level')).toBe('9');
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Computed level 10 exceeds 9'));
} finally {
warnSpy.mockRestore();
}
});
it('owns role/aria-level at render time while forwarding other aria attributes', () => {
// Consumers may pass HTML attributes, but Heading overwrites role/aria-level when it needs
// explicit heading semantics. A native heading carries neither attribute; other aria-* forward.
const { container } = render(Title);
const h1 = container.querySelector('h1');
expect(h1).not.toBeNull();
expect(h1?.getAttribute('role')).toBeNull();
expect(h1?.getAttribute('aria-level')).toBeNull();
expect(h1?.getAttribute('aria-label')).toBe('label');
});
it('forwards arbitrary attributes and resolves className from state', () => {
const { container } = render(
`level-${state.level}`}>
Title
);
const h1 = container.querySelector('h1');
expect(h1?.getAttribute('id')).toBe('anchor');
expect(h1?.className).toBe('level-1');
});
});
describe('core Paragraph block-nesting degrade', () => {
it('renders a
at the top level', () => {
const { container } = render(Text);
expect(container.querySelector('p')).not.toBeNull();
});
it('degrades a nested Paragraph to an inline ', () => {
const { container } = render(
outer
inner
);
const outer = container.querySelector('p');
expect(outer).not.toBeNull();
expect(outer?.querySelector('p')).toBeNull();
expect(outer?.querySelector('span')).not.toBeNull();
expect(outer?.querySelector('span')?.textContent).toBe('inner');
});
});
describe('core block-in-block nesting across Heading and Paragraph', () => {
it('degrades a Heading inside a Paragraph to a plain (no in the )', () => {
const { container } = render(
text
Sub
);
const outer = container.querySelector('p');
expect(outer).not.toBeNull();
expect(outer?.querySelector('h1')).toBeNull();
const inner = outer?.querySelector('span');
expect(inner).not.toBeNull();
expect(inner?.hasAttribute('role')).toBe(false);
expect(inner?.hasAttribute('aria-level')).toBe(false);
expect(inner?.textContent).toBe('Sub');
});
it('degrades a Paragraph inside a Heading to an inline (no in the heading)', () => {
const { container } = render(
Title
body
);
const outer = container.querySelector('h1');
expect(outer).not.toBeNull();
expect(outer?.querySelector('p')).toBeNull();
const inner = outer?.querySelector('span');
expect(inner).not.toBeNull();
expect(inner?.hasAttribute('role')).toBe(false);
expect(inner?.textContent).toBe('body');
});
it('degrades a Heading inside a Heading to a plain (no nested )', () => {
const { container } = render(
Title
Sub
);
const outer = container.querySelector('h1');
expect(outer).not.toBeNull();
expect(outer?.querySelector('h2')).toBeNull();
const inner = outer?.querySelector('span');
expect(inner).not.toBeNull();
expect(inner?.hasAttribute('role')).toBe(false);
expect(inner?.hasAttribute('aria-level')).toBe(false);
});
});