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