import { expect, fixture, html } from '@open-wc/testing';
import './nile-nav-tab-panel';
import type { NileNavTabPanel } from './nile-nav-tab-panel';
describe('NileNavTabPanel', () => {
// ---- Rendering ----
it('renders with shadow root and base panel class', async () => {
const el = await fixture(
html``
);
expect(el).to.exist;
expect(el.shadowRoot).to.not.be.null;
const panel = el.shadowRoot?.querySelector('.nav-tab-panel');
expect(panel).to.exist;
expect(panel?.isConnected).to.be.true;
});
it('inner wrapper exposes part base', async () => {
const el = await fixture(
html``
);
const inner = el.shadowRoot?.querySelector('[part="base"]');
expect(inner).to.exist;
expect(inner?.parentNode).to.equal(el.shadowRoot);
expect(inner?.classList.contains('nav-tab-panel')).to.be.true;
});
it('renders default slot content', async () => {
const el = await fixture(
html`Panel body`
);
expect(el.textContent).to.contain('Panel body');
expect(el.shadowRoot!.querySelector('slot')).to.exist;
});
it('applies active class when active is true', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.exist;
});
it('does not add active class when inactive', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.be.null;
});
it('open shadow mode', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.mode).to.equal('open');
});
it('localName is nile-nav-tab-panel', async () => {
const el = await fixture(
html``
);
expect(el.localName).to.equal('nile-nav-tab-panel');
});
it('tagName lowercases to nile-nav-tab-panel', async () => {
const el = await fixture(
html``
);
expect(el.tagName.toLowerCase()).to.equal('nile-nav-tab-panel');
});
// ---- Defaults & reflection ----
it('defaults name to empty and active to false', async () => {
const el = await fixture(
html``
);
expect(el.name).to.equal('');
expect(el.active).to.be.false;
});
it('reflects name and active attributes', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('name')).to.equal('general');
expect(el.hasAttribute('active')).to.be.true;
});
it('updates name when property set', async () => {
const el = await fixture(
html``
);
el.name = 'settings';
await el.updateComplete;
expect(el.getAttribute('name')).to.equal('settings');
});
it('creates via document.createElement and upgrades', async () => {
const el = document.createElement('nile-nav-tab-panel') as NileNavTabPanel;
document.body.appendChild(el);
await el.updateComplete;
expect(el.shadowRoot).to.not.be.null;
document.body.removeChild(el);
});
// ---- Accessibility ----
it('sets role to tabpanel on connect', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('role')).to.equal('tabpanel');
});
it('sets aria-hidden=true and tabindex=-1 when inactive', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('aria-hidden')).to.equal('true');
expect(el.getAttribute('tabindex')).to.equal('-1');
});
it('exposes tabIndex property -1 when inactive', async () => {
const el = await fixture(
html``
);
expect(el.tabIndex).to.equal(-1);
});
it('sets aria-hidden=false and tabindex=0 when active', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('aria-hidden')).to.equal('false');
expect(el.getAttribute('tabindex')).to.equal('0');
});
it('exposes tabIndex 0 when active', async () => {
const el = await fixture(
html``
);
expect(el.tabIndex).to.equal(0);
});
it('updates aria-hidden/tabindex when active changes dynamically', async () => {
const el = await fixture(
html``
);
el.active = true;
await el.updateComplete;
expect(el.getAttribute('aria-hidden')).to.equal('false');
expect(el.getAttribute('tabindex')).to.equal('0');
el.active = false;
await el.updateComplete;
expect(el.getAttribute('aria-hidden')).to.equal('true');
expect(el.getAttribute('tabindex')).to.equal('-1');
});
// ---- ID behavior ----
it('assigns an auto-generated id when id is not provided', async () => {
const el = await fixture(
html``
);
expect(el.id).to.match(/^nile-nav-tab-panel-/);
});
it('preserves provided id', async () => {
const el = await fixture(
html``
);
expect(el.id).to.equal('panel-1');
});
it('two instances receive distinct auto ids', async () => {
const wrap = await fixture(html`
`);
const nodes = wrap.querySelectorAll('nile-nav-tab-panel');
expect(nodes[0].id).to.not.equal(nodes[1].id);
});
// ---- Module registration ----
it('exposes static styles', async () => {
const mod = await import('./nile-nav-tab-panel');
expect(mod.NileNavTabPanel.styles).to.exist;
});
it('registers custom element', () => {
expect(customElements.get('nile-nav-tab-panel')).to.exist;
});
it('default export matches named class', async () => {
const mod = await import('./nile-nav-tab-panel');
expect(mod.default).to.equal(mod.NileNavTabPanel);
});
// ---- DOM & computed display ----
it('inactive host uses display none from stylesheet', async () => {
const el = await fixture(
html``
);
expect(getComputedStyle(el).display).to.equal('none');
});
it('active host uses display block from stylesheet', async () => {
const el = await fixture(
html``
);
expect(getComputedStyle(el).display).to.equal('block');
});
it('isConnected after fixture', async () => {
const el = await fixture(
html``
);
expect(el.isConnected).to.be.true;
});
it('remove disconnects from document', async () => {
const el = await fixture(
html``
);
el.remove();
expect(el.isConnected).to.be.false;
});
it('closest finds self', async () => {
const el = await fixture(
html``
);
expect(el.closest('nile-nav-tab-panel')).to.equal(el);
});
it('updateComplete resolves true', async () => {
const el = await fixture(
html``
);
expect(await el.updateComplete).to.be.true;
});
it('requestUpdate triggers re-render', async () => {
const el = await fixture(
html``
);
el.active = true;
el.requestUpdate();
await el.updateComplete;
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.exist;
});
it('hidden attribute can be set on host', async () => {
const el = await fixture(
html``
);
expect(el.hidden).to.be.true;
});
it('classList on host accepts utility class', async () => {
const el = await fixture(
html``
);
expect(el.classList.contains('p1')).to.be.true;
});
it('dataset attributes are readable', async () => {
const el = await fixture(
html``
);
expect(el.dataset.test).to.equal('x');
});
it('nodeType is ELEMENT_NODE', async () => {
const el = await fixture(
html``
);
expect(el.nodeType).to.equal(Node.ELEMENT_NODE);
});
it('ownerDocument is current document', async () => {
const el = await fixture(
html``
);
expect(el.ownerDocument).to.equal(document);
});
it('getBoundingClientRect returns numeric box when active', async () => {
const el = await fixture(
html`Hi`
);
const r = el.getBoundingClientRect();
expect(r.width).to.be.a('number');
expect(r.height).to.be.a('number');
});
it('dispatchEvent receives listeners', async () => {
const el = await fixture(
html``
);
let ok = false;
el.addEventListener('panel-test', () => {
ok = true;
});
el.dispatchEvent(new Event('panel-test'));
expect(ok).to.be.true;
});
it('slotted nodes remain in light DOM', async () => {
const el = await fixture(html`
x
`);
expect(el.contains(el.querySelector('#inner'))).to.be.true;
});
it('dir attribute passes through', async () => {
const el = await fixture(
html``
);
expect(el.dir).to.equal('rtl');
});
it('lang attribute passes through', async () => {
const el = await fixture(
html``
);
expect(el.lang).to.equal('en');
});
it('aria-describedby on host is preserved', async () => {
const el = await fixture(html`
`);
expect(el.getAttribute('aria-describedby')).to.equal('help');
});
it('cloneNode preserves tag name', async () => {
const el = await fixture(
html``
);
const copy = el.cloneNode(false) as NileNavTabPanel;
expect(copy.localName).to.equal('nile-nav-tab-panel');
});
it('matches selector for host class', async () => {
const el = await fixture(
html``
);
expect(el.matches('nile-nav-tab-panel.x')).to.be.true;
});
it('inactive then active toggles inner active class', async () => {
const el = await fixture(
html``
);
el.active = true;
await el.updateComplete;
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.exist;
el.active = false;
await el.updateComplete;
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.be.null;
});
it('style attribute on host is applied', async () => {
const el = await fixture(html`
`);
expect(el.style.opacity).to.equal('0.99');
});
it('shadowRoot host references the element', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.host).to.equal(el);
});
it('outerHTML includes component tag', async () => {
const el = await fixture(
html``
);
expect(el.outerHTML.toLowerCase()).to.include('nile-nav-tab-panel');
});
it('scrollIntoView is available on host', async () => {
const el = await fixture(
html``
);
expect(el.scrollIntoView).to.be.a('function');
});
it('reflects active attribute when toggled in JS', async () => {
const el = await fixture(
html``
);
el.active = true;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.true;
el.active = false;
await el.updateComplete;
expect(el.hasAttribute('active')).to.be.false;
});
// ---- Extended coverage ----
it('name with underscores reflects', async () => {
const el = await fixture(
html``
);
expect(el.name).to.equal('panel_a');
});
it('clears name when set to empty string', async () => {
const el = await fixture(
html``
);
el.name = '';
await el.updateComplete;
expect(el.getAttribute('name')).to.equal('');
});
it('role tabpanel persists after active toggle', async () => {
const el = await fixture(
html``
);
el.active = true;
await el.updateComplete;
expect(el.getAttribute('role')).to.equal('tabpanel');
el.active = false;
await el.updateComplete;
expect(el.getAttribute('role')).to.equal('tabpanel');
});
it('slot element exists in shadow', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.querySelector('slot')).to.exist;
});
it('active class toggles twice without throwing', async () => {
const el = await fixture(
html``
);
for (let i = 0; i < 3; i++) {
el.active = i % 2 === 1;
await el.updateComplete;
}
expect(el.active).to.be.false;
});
it('inner base part remains when inactive', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.querySelector('[part="base"]')).to.exist;
});
it('tabIndex property mirrors attribute when activated', async () => {
const el = await fixture(
html``
);
el.setAttribute('active', '');
await el.updateComplete;
expect(el.tabIndex).to.equal(0);
});
it('removeAttribute name clears binding', async () => {
const el = await fixture(
html``
);
el.removeAttribute('name');
await el.updateComplete;
expect(el.getAttribute('name')).to.be.null;
expect(el.name ?? '').to.equal('');
});
it('textContent aggregates slotted string', async () => {
const el = await fixture(
html`Hello panel`
);
expect(el.textContent).to.contain('Hello panel');
});
it('insertAdjacentHTML not used but innerHTML on host is empty for component', async () => {
const el = await fixture(
html`bold`
);
expect(el.querySelector('b')).to.exist;
});
it('getAttribute role is tabpanel', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('role')).to.equal('tabpanel');
});
it('attributes.getNamedItem name returns attr node', async () => {
const el = await fixture(
html``
);
expect(el.attributes.getNamedItem('name')?.value).to.equal('n1');
});
it('isConnected true when nested in div', async () => {
const wrap = await fixture(html`
`);
expect(wrap.querySelector('nile-nav-tab-panel')!.isConnected).to.be.true;
});
it('parentElement is wrapper when nested', async () => {
const wrap = await fixture(html`
`);
const p = wrap.querySelector('nile-nav-tab-panel')!;
expect(p.parentElement!.id).to.equal('p');
});
it('compareDocumentPosition against document', async () => {
const el = await fixture(
html``
);
expect(el.compareDocumentPosition(document.documentElement)).to.be.greaterThan(0);
});
it('lookupPrefix returns null for plain host', async () => {
const el = await fixture(
html``
);
expect(el.lookupPrefix('http://x')).to.be.null;
});
it('contains returns false for detached nodes', async () => {
const el = await fixture(
html``
);
const orphan = document.createElement('span');
expect(el.contains(orphan)).to.be.false;
});
it('active true keeps display block', async () => {
const el = await fixture(
html``
);
el.active = true;
await el.updateComplete;
expect(getComputedStyle(el).display).to.equal('block');
});
it('inactive keeps display none', async () => {
const el = await fixture(
html``
);
el.active = false;
await el.updateComplete;
expect(getComputedStyle(el).display).to.equal('none');
});
it('id can be set after connect', async () => {
const el = await fixture(
html``
);
el.id = 'late-id';
expect(el.id).to.equal('late-id');
});
it('auto id still matches pattern when no explicit id', async () => {
const el = await fixture(
html``
);
expect(el.id).to.match(/^nile-nav-tab-panel-\d+$/);
});
it('requestUpdate twice resolves once', async () => {
const el = await fixture(
html``
);
el.requestUpdate();
el.requestUpdate();
await el.updateComplete;
expect(await el.updateComplete).to.be.true;
});
it('shadowRoot exposes adoptedStyleSheets array', async () => {
const el = await fixture(
html``
);
expect(el.shadowRoot!.adoptedStyleSheets).to.be.an('array');
});
it('toggleAttribute active reflects', async () => {
const el = await fixture(
html``
);
el.toggleAttribute('active', true);
await el.updateComplete;
expect(el.active).to.be.true;
});
it('inner part has nav-tab-panel class even when inactive', async () => {
const el = await fixture(
html``
);
const inner = el.shadowRoot!.querySelector('.nav-tab-panel');
expect(inner!.classList.contains('nav-tab-panel')).to.be.true;
expect(inner!.classList.contains('nav-tab-panel--active')).to.be.false;
});
it('accessKey can be assigned on host', async () => {
const el = await fixture(
html``
);
expect(el.accessKey).to.equal('p');
});
it('title attribute passes through', async () => {
const el = await fixture(
html``
);
expect(el.title).to.equal('tip');
});
it('contentEditable false on host by default', async () => {
const el = await fixture(
html``
);
expect(el.isContentEditable).to.be.false;
});
it('nonce attribute if set is readable', async () => {
const el = await fixture(
html``
);
expect(el.getAttribute('nonce')).to.equal('abc');
});
it('part base div wraps single slot', async () => {
const el = await fixture(
html`X`
);
const base = el.shadowRoot?.querySelector('[part="base"]');
expect(base).to.exist;
expect(base?.querySelectorAll('slot').length).to.equal(1);
});
it('cloneNode deep copies light children', async () => {
const el = await fixture(
html`c`
);
const copy = el.cloneNode(true) as NileNavTabPanel;
expect(copy.querySelector('#c')).to.exist;
});
it('hasAttributes is true when name set', async () => {
const el = await fixture(
html``
);
expect(el.hasAttributes()).to.be.true;
});
it('getAttributeNames includes role', async () => {
const el = await fixture(
html``
);
expect(el.getAttributeNames()).to.include('role');
});
it('prepend light DOM child remains queryable', async () => {
const el = await fixture(
html``
);
const s = document.createElement('span');
s.id = 'prep';
el.prepend(s);
expect(el.querySelector('#prep')).to.equal(s);
});
it('append light DOM child after default slot content', async () => {
const el = await fixture(
html`One`
);
const s = document.createElement('span');
s.id = 'app';
el.append(s);
expect(el.querySelector('#app')).to.exist;
});
it('replaceChildren clears slotted content', async () => {
const el = await fixture(
html`Old`
);
el.replaceChildren();
await el.updateComplete;
expect(el.textContent?.trim()).to.equal('');
});
it('toggleAttribute active off clears active', async () => {
const el = await fixture(
html``
);
el.toggleAttribute('active', false);
await el.updateComplete;
expect(el.active).to.be.false;
});
/**
* 100 generated cases — `yarn tsc` emits dist/src/nile-nav-tab-panel/nile-nav-tab-panel.test.js.
*/
describe('bulk generated coverage (100 cases)', () => {
for (let i = 0; i < 10; i++) {
it(`bulk case ${i + 1}/100 (mode ${i % 10})`, async () => {
const suffix = `${i}`;
const mode = i % 10;
if (mode === 0) {
const el = await fixture(
html``
);
el.name = `panel-${suffix}`;
await el.updateComplete;
expect(el.getAttribute('name')).to.equal(`panel-${suffix}`);
expect(el.name).to.equal(`panel-${suffix}`);
} else if (mode === 1) {
const el = await fixture(
html``
);
await el.updateComplete;
expect(el.getAttribute('aria-hidden')).to.equal('false');
expect(el.getAttribute('tabindex')).to.equal('0');
expect(el.tabIndex).to.equal(0);
} else if (mode === 2) {
const el = await fixture(
html``
);
await el.updateComplete;
expect(el.getAttribute('aria-hidden')).to.equal('true');
expect(el.getAttribute('tabindex')).to.equal('-1');
} else if (mode === 3) {
const el = await fixture(
html``
);
expect(el.getAttribute('role')).to.equal('tabpanel');
} else if (mode === 4) {
const el = await fixture(
html``
);
const base = el.shadowRoot!.querySelector('[part="base"]');
expect(base).to.exist;
expect(base!.classList.contains('nav-tab-panel')).to.be.true;
} else if (mode === 5) {
const el = await fixture(
html``
);
await el.updateComplete;
expect(el.shadowRoot!.querySelector('.nav-tab-panel--active')).to.exist;
} else if (mode === 6) {
const show = i % 2 === 0;
const el = await fixture(
html``
);
el.active = show;
await el.updateComplete;
expect(getComputedStyle(el).display).to.equal(show ? 'block' : 'none');
} else if (mode === 7) {
const el = await fixture(
html`C-${suffix}`
);
await el.updateComplete;
expect(el.shadowRoot!.querySelector('slot')).to.exist;
expect(el.textContent).to.contain(`C-${suffix}`);
} else if (mode === 8) {
const el = await fixture(
html``
);
await el.updateComplete;
expect(el.id).to.match(/^nile-nav-tab-panel-\d+$/);
} else {
const el = await fixture(
html``
);
el.setAttribute('name', `bulk-${suffix}`);
await el.updateComplete;
expect(el.name).to.equal(`bulk-${suffix}`);
}
});
}
});
});