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