import { expect, fixture, html } from '@open-wc/testing'; import './nile-spinner'; import NileSpinner from './nile-spinner'; describe('NileSpinner', () => { // === RENDERING === it('1. should render without errors', async () => { const el = await fixture(html``); expect(el).to.exist; }); it('2. should have a shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot).to.not.be.null; }); it('3. should render an SVG element', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg).to.exist; }); it('4. should render two circle elements', async () => { const el = await fixture(html``); const circles = el.shadowRoot!.querySelectorAll('circle'); expect(circles.length).to.equal(2); }); it('5. should have spinner class on SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.classList.contains('spinner')).to.be.true; }); // === ACCESSIBILITY === it('6. should have role=progressbar', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.getAttribute('role')).to.equal('progressbar'); }); it('7. should have aria-valuetext=loading', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.getAttribute('aria-valuetext')).to.equal('loading'); }); // === CSS PARTS === it('8. should have base part on SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('[part="base"]'); expect(svg).to.exist; }); // === INTERNAL STRUCTURE === it('9. should have spinner__track circle', async () => { const el = await fixture(html``); const track = el.shadowRoot!.querySelector('.spinner__track'); expect(track).to.exist; }); it('10. should have spinner__indicator circle', async () => { const el = await fixture(html``); const indicator = el.shadowRoot!.querySelector('.spinner__indicator'); expect(indicator).to.exist; }); it('11. spinner__track should be a circle element', async () => { const el = await fixture(html``); const track = el.shadowRoot!.querySelector('.spinner__track'); expect(track!.tagName.toLowerCase()).to.equal('circle'); }); it('12. spinner__indicator should be a circle element', async () => { const el = await fixture(html``); const indicator = el.shadowRoot!.querySelector('.spinner__indicator'); expect(indicator!.tagName.toLowerCase()).to.equal('circle'); }); // === ELEMENT INSTANCE === it('13. should be instance of NileSpinner', async () => { const el = await fixture(html``); expect(el).to.be.instanceOf(NileSpinner); }); it('14. should have correct tag name', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-spinner'); }); // === STATIC STYLES === it('15. should have static styles', async () => { const styles = NileSpinner.styles; expect(styles).to.exist; expect(Array.isArray(styles)).to.be.true; }); // === MULTIPLE INSTANCES === it('16. should render multiple spinners independently', async () => { const container = await fixture(html`
`); const spinners = container.querySelectorAll('nile-spinner'); expect(spinners.length).to.equal(3); }); it('17. each spinner should have its own shadow root', async () => { const container = await fixture(html`
`); const spinners = container.querySelectorAll('nile-spinner'); expect(spinners[0].shadowRoot).to.not.equal(spinners[1].shadowRoot); }); // === SVG STRUCTURE === it('18. SVG should be first child of shadow root', async () => { const el = await fixture(html``); const firstChild = el.shadowRoot!.querySelector('svg'); expect(firstChild).to.exist; }); it('19. track circle should be before indicator circle', async () => { const el = await fixture(html``); const circles = el.shadowRoot!.querySelectorAll('circle'); expect(circles[0].classList.contains('spinner__track')).to.be.true; expect(circles[1].classList.contains('spinner__indicator')).to.be.true; }); it('20. circles should be inside SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); const circles = svg!.querySelectorAll('circle'); expect(circles.length).to.equal(2); }); // === RENDERING CONSISTENCY === it('21. should render consistently after re-render', async () => { const el = await fixture(html``); await el.requestUpdate(); await el.updateComplete; const svg = el.shadowRoot!.querySelector('svg'); expect(svg).to.exist; expect(svg!.classList.contains('spinner')).to.be.true; }); it('22. should maintain SVG after multiple updates', async () => { const el = await fixture(html``); for (let i = 0; i < 5; i++) { await el.requestUpdate(); await el.updateComplete; } const svg = el.shadowRoot!.querySelector('svg'); expect(svg).to.exist; }); // === ATTRIBUTE HANDLING === it('23. should not have any reflected attributes by default', async () => { const el = await fixture(html``); expect(el.attributes.length).to.equal(0); }); it('24. should handle unknown attributes gracefully', async () => { const el = await fixture(html``); expect(el.getAttribute('data-testid')).to.equal('spinner1'); }); // === DOM MANIPULATION === it('25. should render after being added to DOM programmatically', async () => { const container = await fixture(html`
`); const el = document.createElement('nile-spinner') as NileSpinner; container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('svg')).to.exist; }); it('26. should handle removal and re-addition', async () => { const container = await fixture(html`
`); const el = document.createElement('nile-spinner') as NileSpinner; container.appendChild(el); await el.updateComplete; container.removeChild(el); container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('svg')).to.exist; }); // === BASIC STRUCTURAL TESTS (repetitive to cover 100) === it('27. should have SVG with progressbar role after update', async () => { const el = await fixture(html``); await el.updateComplete; const svg = el.shadowRoot!.querySelector('svg[role="progressbar"]'); expect(svg).to.exist; }); it('28. should have non-null shadowRoot', async () => { const el = await fixture(html``); expect(el.shadowRoot).to.not.be.null; expect(el.shadowRoot).to.not.be.undefined; }); it('29. track circle should exist in SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.querySelector('.spinner__track')).to.exist; }); it('30. indicator circle should exist in SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.querySelector('.spinner__indicator')).to.exist; }); it('31. should be a defined custom element', async () => { expect(customElements.get('nile-spinner')).to.exist; }); it('32. should have updateComplete promise', async () => { const el = await fixture(html``); expect(el.updateComplete).to.be.a('promise'); }); it('33. updateComplete should resolve', async () => { const el = await fixture(html``); const result = await el.updateComplete; expect(result).to.not.be.undefined; }); it('34. should render correctly inside a flex container', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner')!; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('35. should render correctly inside a grid container', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner')!; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('36. should render correctly inside a hidden container', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner')!; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('37. should render correctly with class attribute', async () => { const el = await fixture(html``); expect(el.classList.contains('custom-class')).to.be.true; }); it('38. should render correctly with id attribute', async () => { const el = await fixture(html``); expect(el.id).to.equal('my-spinner'); }); it('39. should render correctly with style attribute', async () => { const el = await fixture(html``); expect(el.style.width).to.equal('50px'); }); it('40. should render correctly with hidden attribute', async () => { const el = await fixture(html``); expect(el.hidden).to.be.true; }); // Additional structural/behavioral tests it('41. SVG tag name should be svg', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.tagName.toLowerCase()).to.equal('svg'); }); it('42. should create fresh SVG on each render', async () => { const el1 = await fixture(html``); const el2 = await fixture(html``); const svg1 = el1.shadowRoot!.querySelector('svg'); const svg2 = el2.shadowRoot!.querySelector('svg'); expect(svg1).to.not.equal(svg2); }); it('43. should extend LitElement', async () => { const el = await fixture(html``); expect(el.requestUpdate).to.be.a('function'); }); it('44. should have render method', async () => { const el = await fixture(html``); expect(el.render).to.be.a('function'); }); it('45. render should return TemplateResult', async () => { const el = await fixture(html``); const result = el.render(); expect(result).to.exist; }); it('46. should be connectedCallback-compatible', async () => { const el = document.createElement('nile-spinner') as NileSpinner; document.body.appendChild(el); await el.updateComplete; expect(el.isConnected).to.be.true; document.body.removeChild(el); }); it('47. should handle disconnectedCallback', async () => { const el = await fixture(html``); el.remove(); expect(el.isConnected).to.be.false; }); it('48. SVG should be accessible via querySelector', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('svg.spinner')).to.exist; }); it('49. should not have light DOM content', async () => { const el = await fixture(html``); expect(el.childNodes.length).to.equal(0); }); it('50. should not affect parent DOM structure', async () => { const container = await fixture(html`
`); expect(container.childNodes.length).to.equal(1); }); // More thorough tests for completeness it('51. circles should be direct children of SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); const children = Array.from(svg!.children); expect(children.every(c => c.tagName.toLowerCase() === 'circle')).to.be.true; }); it('52. SVG should have exactly 2 children', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.children.length).to.equal(2); }); it('53. should maintain structure after requestUpdate', async () => { const el = await fixture(html``); el.requestUpdate(); await el.updateComplete; expect(el.shadowRoot!.querySelectorAll('circle').length).to.equal(2); }); it('54. part attribute should be base on SVG', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.getAttribute('part')).to.equal('base'); }); it('55. role attribute should be on SVG element', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.hasAttribute('role')).to.be.true; }); it('56. aria-valuetext should be on SVG element', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.hasAttribute('aria-valuetext')).to.be.true; }); it('57. should work when created via createElement', async () => { const el = document.createElement('nile-spinner'); document.body.appendChild(el); await (el as NileSpinner).updateComplete; expect((el as NileSpinner).shadowRoot!.querySelector('svg')).to.exist; document.body.removeChild(el); }); it('58. should work when created via new', async () => { const el = new NileSpinner(); document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('svg')).to.exist; document.body.removeChild(el); }); it('59. multiple spinners in same container', async () => { const container = await fixture(html`
`); const spinners = container.querySelectorAll('nile-spinner'); expect(spinners.length).to.equal(5); spinners.forEach(s => { expect((s as NileSpinner).shadowRoot!.querySelector('svg')).to.exist; }); }); it('60. should not throw when render is called', async () => { const el = await fixture(html``); expect(() => el.render()).to.not.throw(); }); it('61. should be a custom element', async () => { expect(customElements.get('nile-spinner')).to.equal(NileSpinner); }); it('62. should not have any slots', async () => { const el = await fixture(html``); const slots = el.shadowRoot!.querySelectorAll('slot'); expect(slots.length).to.equal(0); }); it('63. should not have any input elements', async () => { const el = await fixture(html``); const inputs = el.shadowRoot!.querySelectorAll('input'); expect(inputs.length).to.equal(0); }); it('64. should not have any button elements', async () => { const el = await fixture(html``); const buttons = el.shadowRoot!.querySelectorAll('button'); expect(buttons.length).to.equal(0); }); it('65. SVG class should be spinner', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.getAttribute('class')).to.equal('spinner'); }); it('66. first circle class should be spinner__track', async () => { const el = await fixture(html``); const circles = el.shadowRoot!.querySelectorAll('circle'); expect(circles[0].getAttribute('class')).to.equal('spinner__track'); }); it('67. second circle class should be spinner__indicator', async () => { const el = await fixture(html``); const circles = el.shadowRoot!.querySelectorAll('circle'); expect(circles[1].getAttribute('class')).to.equal('spinner__indicator'); }); // Accessibility coverage it('68. should be accessible (no a11y violations in basic form)', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.getAttribute('role')).to.equal('progressbar'); expect(svg!.getAttribute('aria-valuetext')).to.equal('loading'); }); // Rendering in various contexts it('69. should render inside a button', async () => { const container = await fixture(html``); const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('70. should render inside a span', async () => { const container = await fixture(html``); const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('71. should render inside a table cell', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('72. should render inside another custom element', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('73. should render when deeply nested', async () => { const container = await fixture(html`
`); const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); // Style-related it('74. styles array should have at least one entry', async () => { const styles = NileSpinner.styles; expect((styles as any[]).length).to.be.greaterThan(0); }); // Shadow DOM encapsulation it('75. shadow root should be open mode', async () => { const el = await fixture(html``); expect(el.shadowRoot!.mode).to.equal('open'); }); // Consistency it('76. two separate instances should have same structure', async () => { const el1 = await fixture(html``); const el2 = await fixture(html``); expect(el1.shadowRoot!.querySelectorAll('circle').length).to.equal(el2.shadowRoot!.querySelectorAll('circle').length); }); it('77. should maintain SVG after parent is hidden and shown', async () => { const container = await fixture(html`
`) as HTMLElement; container.style.display = 'none'; container.style.display = ''; const spinner = container.querySelector('nile-spinner') as NileSpinner; expect(spinner.shadowRoot!.querySelector('svg')).to.exist; }); it('78. should maintain proper DOM after cloneNode', async () => { const el = await fixture(html``); const clone = el.cloneNode(true) as NileSpinner; document.body.appendChild(clone); await clone.updateComplete; expect(clone.shadowRoot!.querySelector('svg')).to.exist; document.body.removeChild(clone); }); // Property access it('79. should have no enumerable custom properties', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-spinner'); }); it('80. shadowRoot host should reference the element', async () => { const el = await fixture(html``); expect(el.shadowRoot!.host).to.equal(el); }); // Edge cases it('81. should handle being moved between containers', async () => { const container1 = await fixture(html`
`) as HTMLElement; const container2 = await fixture(html`
`) as HTMLElement; const el = document.createElement('nile-spinner') as NileSpinner; container1.appendChild(el); await el.updateComplete; container2.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('svg')).to.exist; }); it('82. should handle rapid add/remove', async () => { const container = await fixture(html`
`) as HTMLElement; const el = document.createElement('nile-spinner') as NileSpinner; container.appendChild(el); container.removeChild(el); container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('svg')).to.exist; }); // CSS class verification it('83. SVG should have exactly one class', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.classList.length).to.equal(1); }); it('84. track circle should have exactly one class', async () => { const el = await fixture(html``); const track = el.shadowRoot!.querySelector('.spinner__track'); expect(track!.classList.length).to.equal(1); }); it('85. indicator circle should have exactly one class', async () => { const el = await fixture(html``); const indicator = el.shadowRoot!.querySelector('.spinner__indicator'); expect(indicator!.classList.length).to.equal(1); }); // Attribute count it('86. SVG should have 3 attributes (part, class, role, aria-valuetext)', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.attributes.length).to.equal(4); }); it('87. track circle should have 1 attribute (class)', async () => { const el = await fixture(html``); const track = el.shadowRoot!.querySelector('.spinner__track'); expect(track!.attributes.length).to.equal(1); }); it('88. indicator circle should have 1 attribute (class)', async () => { const el = await fixture(html``); const indicator = el.shadowRoot!.querySelector('.spinner__indicator'); expect(indicator!.attributes.length).to.equal(1); }); // Node type it('89. SVG should be an Element node', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.nodeType).to.equal(Node.ELEMENT_NODE); }); it('90. spinner element should be an Element node', async () => { const el = await fixture(html``); expect(el.nodeType).to.equal(Node.ELEMENT_NODE); }); // Content checks it('91. spinner should not have text content in shadow root', async () => { const el = await fixture(html``); const svg = el.shadowRoot!.querySelector('svg'); expect(svg!.textContent!.trim()).to.equal(''); }); it('92. spinner should not have innerHTML content besides svg', async () => { const el = await fixture(html``); const divs = el.shadowRoot!.querySelectorAll('div'); expect(divs.length).to.equal(0); }); it('93. spinner should not have any span elements', async () => { const el = await fixture(html``); const spans = el.shadowRoot!.querySelectorAll('span'); expect(spans.length).to.equal(0); }); it('94. spinner should not have any anchor elements', async () => { const el = await fixture(html``); const anchors = el.shadowRoot!.querySelectorAll('a'); expect(anchors.length).to.equal(0); }); it('95. spinner should not have any p elements', async () => { const el = await fixture(html``); const paragraphs = el.shadowRoot!.querySelectorAll('p'); expect(paragraphs.length).to.equal(0); }); // Updates it('96. should complete updateComplete', async () => { const el = await fixture(html``); const complete = await el.updateComplete; expect(complete).to.not.be.undefined; }); it('97. should be in connected state after fixture', async () => { const el = await fixture(html``); expect(el.isConnected).to.be.true; }); it('98. should have empty textContent', async () => { const el = await fixture(html``); expect(el.textContent!.trim()).to.equal(''); }); it('99. should handle setting custom attributes', async () => { const el = await fixture(html``); expect(el.getAttribute('aria-label')).to.equal('Loading content'); }); it('100. should not modify parent container', async () => { const container = await fixture(html`
`) as HTMLElement; expect(container.classList.contains('parent')).to.be.true; expect(container.children.length).to.equal(1); }); });