import { fixture, html, expect, elementUpdated } from '@open-wc/testing'; import './nile-tooltip'; import { NileTooltip } from './nile-tooltip'; describe('NileTooltip', () => { // === RENDERING === it('1. should render correctly', async () => { const el = await fixture(html`Hover me`); expect(el).to.exist; expect(el).to.be.instanceOf(NileTooltip); }); it('2. should have a shadow root', async () => { const el = await fixture(html`Hover`); expect(el.shadowRoot).to.not.be.null; }); it('3. should render a nile-popup element', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup).to.exist; }); it('4. should have an anchor slot', async () => { const el = await fixture(html`Hover`); const slot = el.shadowRoot!.querySelector('slot[slot="anchor"]'); expect(slot).to.exist; }); it('5. should have a content slot with role=tooltip', async () => { const el = await fixture(html`Hover`); const contentSlot = el.shadowRoot!.querySelector('[role="tooltip"]'); expect(contentSlot).to.exist; }); it('6. should render slot content', async () => { const el = await fixture(html`Hover me`); const span = el.querySelector('span'); expect(span).to.exist; expect(span!.textContent).to.equal('Hover me'); }); // === DEFAULT PROPERTIES === it('7. should have content default to empty string', async () => { const el = await fixture(html`Hover`); expect(el.content).to.equal(''); }); it('8. should have placement default to top', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('top'); }); it('9. should have disabled default to false', async () => { const el = await fixture(html`Hover`); expect(el.disabled).to.be.false; }); it('10. should have open default to false', async () => { const el = await fixture(html`Hover`); expect(el.open).to.be.false; }); it('11. should have distance default to 8', async () => { const el = await fixture(html`Hover`); expect(el.distance).to.equal(8); }); it('12. should have skidding default to 0', async () => { const el = await fixture(html`Hover`); expect(el.skidding).to.equal(0); }); it('13. should have trigger default to "hover focus"', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('hover focus'); }); it('14. should have hoist default to false', async () => { const el = await fixture(html`Hover`); expect(el.hoist).to.be.false; }); it('15. should have size default to small', async () => { const el = await fixture(html`Hover`); expect(el.size).to.equal('small'); }); it('16. should have enableVisibilityEffect default to false', async () => { const el = await fixture(html`Hover`); expect(el.enableVisibilityEffect).to.be.false; }); it('17. should have enableTabClose default to false', async () => { const el = await fixture(html`Hover`); expect(el.enableTabClose).to.be.false; }); // === CONTENT === it('18. should set content text', async () => { const el = await fixture(html`Hover`); expect(el.content).to.equal('Hello World'); }); it('19. should reflect content attribute', async () => { const el = await fixture(html`Hover`); expect(el.getAttribute('content')).to.equal('Test Content'); }); // === SIZE === it('20. should apply large class when size is large', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('.tooltip__body--large'); expect(body).to.exist; }); it('21. should not apply large class when size is small', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('.tooltip__body--large'); expect(body).to.be.null; }); it('22. should reflect size attribute', async () => { const el = await fixture(html`Hover`); expect(el.getAttribute('size')).to.equal('large'); }); // === PLACEMENT === it('23. should set placement on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('placement')).to.equal('bottom'); }); it('24. should support top placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('top'); }); it('25. should support top-start placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('top-start'); }); it('26. should support top-end placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('top-end'); }); it('27. should support bottom placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('bottom'); }); it('28. should support bottom-start placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('bottom-start'); }); it('29. should support bottom-end placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('bottom-end'); }); it('30. should support right placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('right'); }); it('31. should support left placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('left'); }); it('32. should support right-start placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('right-start'); }); it('33. should support right-end placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('right-end'); }); it('34. should support left-start placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('left-start'); }); it('35. should support left-end placement', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('left-end'); }); // === DISABLED === it('36. should not show tooltip when disabled', async () => { const el = await fixture(html`Hover`); expect(el.disabled).to.be.true; expect(el.open).to.be.false; }); it('37. should reflect disabled attribute', async () => { const el = await fixture(html`Hover`); expect(el.hasAttribute('disabled')).to.be.true; }); // === DISTANCE & SKIDDING === it('38. should set distance on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('distance')).to.equal('12'); }); it('39. should set skidding on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('skidding')).to.equal('5'); }); // === HOIST === it('40. should use fixed strategy when hoist is true', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('strategy')).to.equal('fixed'); }); it('41. should use absolute strategy when hoist is false', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('strategy')).to.equal('absolute'); }); // === TRIGGER === it('42. should support hover trigger', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('hover'); }); it('43. should support focus trigger', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('focus'); }); it('44. should support click trigger', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('click'); }); it('45. should support manual trigger', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('manual'); }); it('46. should support combined triggers', async () => { const el = await fixture(html`Hover`); expect(el.trigger).to.equal('hover focus'); }); // === OPEN STATE === it('47. should reflect open attribute', async () => { const el = await fixture(html`Hover`); expect(el.hasAttribute('open')).to.be.true; }); it('48. should apply tooltip--open class when open', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('.tooltip--open'); expect(popup).to.exist; }); it('49. should not apply tooltip--open class when closed', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('.tooltip--open'); expect(popup).to.be.null; }); // === TOOLTIP CLASS === it('50. should have tooltip class on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('.tooltip'); expect(popup).to.exist; }); // === CSS PARTS === it('51. should have base part', async () => { const el = await fixture(html`Hover`); expect(el.shadowRoot!.querySelector('[part~="base"]')).to.exist; }); it('52. should have body part', async () => { const el = await fixture(html`Hover`); expect(el.shadowRoot!.querySelector('[part~="body"]')).to.exist; }); // === ARIA === it('53. should have aria-live off when closed', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('[role="tooltip"]'); expect(body!.getAttribute('aria-live')).to.equal('off'); }); it('54. should have aria-live polite when open', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('[role="tooltip"]'); expect(body!.getAttribute('aria-live')).to.equal('polite'); }); it('55. should have aria-describedby on content', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('#tooltip'); expect(body).to.exist; }); // === SHOW/HIDE METHODS === it('56. show() should return undefined if content is empty', async () => { const el = await fixture(html`Hover`); const result = await el.show(); expect(result).to.be.undefined; }); it('57. show() should return undefined if already open', async () => { const el = await fixture(html`Hover`); await el.updateComplete; const result = await el.show(); expect(result).to.be.undefined; }); it('58. hide() should return undefined if already closed', async () => { const el = await fixture(html`Hover`); const result = await el.hide(); expect(result).to.be.undefined; }); it('59. show() should set open to true', async () => { const el = await fixture(html`Hover`); el.show(); await el.updateComplete; expect(el.open).to.be.true; }); it('60. hide() should set open to false', async () => { const el = await fixture(html`Hover`); await el.updateComplete; el.hide(); await el.updateComplete; expect(el.open).to.be.false; }); // === DYNAMIC UPDATES === it('61. should update content dynamically', async () => { const el = await fixture(html`Hover`); el.content = 'New Content'; await el.updateComplete; expect(el.content).to.equal('New Content'); }); it('62. should update placement dynamically', async () => { const el = await fixture(html`Hover`); el.placement = 'bottom'; await el.updateComplete; const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('placement')).to.equal('bottom'); }); it('63. should update disabled dynamically', async () => { const el = await fixture(html`Hover`); el.disabled = true; await el.updateComplete; expect(el.hasAttribute('disabled')).to.be.true; }); it('64. should update distance dynamically', async () => { const el = await fixture(html`Hover`); el.distance = 20; await el.updateComplete; expect(el.distance).to.equal(20); }); it('65. should update skidding dynamically', async () => { const el = await fixture(html`Hover`); el.skidding = 10; await el.updateComplete; expect(el.skidding).to.equal(10); }); it('66. should update hoist dynamically', async () => { const el = await fixture(html`Hover`); el.hoist = true; await el.updateComplete; const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('strategy')).to.equal('fixed'); }); it('67. should update trigger dynamically', async () => { const el = await fixture(html`Hover`); el.trigger = 'click'; await el.updateComplete; expect(el.trigger).to.equal('click'); }); it('68. should update size dynamically', async () => { const el = await fixture(html`Hover`); el.size = 'large'; await el.updateComplete; const body = el.shadowRoot!.querySelector('.tooltip__body--large'); expect(body).to.exist; }); // === POPUP ATTRIBUTES === it('69. popup should have flip attribute', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.hasAttribute('flip')).to.be.true; }); it('70. popup should have shift attribute', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.hasAttribute('shift')).to.be.true; }); it('71. popup should have arrow attribute', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.hasAttribute('arrow')).to.be.true; }); // === ELEMENT INSTANCE === it('72. should have correct tag name', async () => { const el = await fixture(html`Hover`); expect(el.tagName.toLowerCase()).to.equal('nile-tooltip'); }); it('73. should be a defined custom element', async () => { expect(customElements.get('nile-tooltip')).to.exist; }); it('74. should have static styles', async () => { const styles = NileTooltip.styles; expect(styles).to.exist; }); it('75. shadow root mode should be open', async () => { const el = await fixture(html`Hover`); expect(el.shadowRoot!.mode).to.equal('open'); }); // === KEYBOARD HANDLING === it('76. should hide on Escape key when open', async () => { const el = await fixture(html`Hover`); await el.updateComplete; el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await el.updateComplete; expect(el.open).to.be.false; }); it('77. should not react to non-escape keys', async () => { const el = await fixture(html`Hover`); await el.updateComplete; el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); await el.updateComplete; expect(el.open).to.be.true; }); // === TOOLTIP__BODY CLASS === it('78. should have tooltip__body class', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('.tooltip__body'); expect(body).to.exist; }); // === CONTENT DISPLAY === it('79. should display content text inside body', async () => { const el = await fixture(html`Hover`); const body = el.shadowRoot!.querySelector('.tooltip__body'); expect(body!.textContent).to.contain('My tooltip'); }); // === MULTIPLE INSTANCES === it('80. should handle multiple tooltips independently', async () => { const container = await fixture(html`
A B
`); const tooltips = container.querySelectorAll('nile-tooltip'); expect(tooltips.length).to.equal(2); expect((tooltips[0] as NileTooltip).content).to.equal('Tooltip 1'); expect((tooltips[1] as NileTooltip).content).to.equal('Tooltip 2'); }); // === COMBINED PROPERTIES === it('81. placement + distance + skidding', async () => { const el = await fixture(html`Hover`); expect(el.placement).to.equal('bottom'); expect(el.distance).to.equal(12); expect(el.skidding).to.equal(5); }); it('82. large + bottom placement', async () => { const el = await fixture(html`Hover`); expect(el.size).to.equal('large'); expect(el.placement).to.equal('bottom'); }); it('83. disabled + open should not show', async () => { const el = await fixture(html`Hover`); expect(el.disabled).to.be.true; expect(el.open).to.be.false; }); it('84. hoist + placement combo', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('strategy')).to.equal('fixed'); expect(popup!.getAttribute('placement')).to.equal('right'); }); // === ATTRIBUTE SETTING === it('85. should set content via setAttribute', async () => { const el = await fixture(html`Hover`); el.setAttribute('content', 'New'); await el.updateComplete; expect(el.content).to.equal('New'); }); it('86. should set disabled via setAttribute', async () => { const el = await fixture(html`Hover`); el.setAttribute('disabled', ''); await el.updateComplete; expect(el.disabled).to.be.true; }); it('87. should remove disabled via removeAttribute', async () => { const el = await fixture(html`Hover`); el.removeAttribute('disabled'); await el.updateComplete; expect(el.disabled).to.be.false; }); // === CREATION === it('88. should work when created via createElement', async () => { const el = document.createElement('nile-tooltip') as NileTooltip; el.content = 'Dynamic'; const span = document.createElement('span'); span.textContent = 'Anchor'; el.appendChild(span); document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-popup')).to.exist; document.body.removeChild(el); }); // === RAPID CHANGES === it('89. should handle rapid content changes', async () => { const el = await fixture(html`Hover`); el.content = 'B'; el.content = 'C'; el.content = 'D'; await el.updateComplete; expect(el.content).to.equal('D'); }); it('90. should handle rapid placement changes', async () => { const el = await fixture(html`Hover`); el.placement = 'bottom'; el.placement = 'left'; el.placement = 'right'; await el.updateComplete; expect(el.placement).to.equal('right'); }); // === EDGE CASES === it('91. should handle empty content string', async () => { const el = await fixture(html`Hover`); expect(el.content).to.equal(''); }); it('92. should handle whitespace-only content', async () => { const el = await fixture(html`Hover`); expect(el.content).to.equal(' '); }); it('93. should handle long content', async () => { const longContent = 'A'.repeat(1000); const el = await fixture(html`Hover`); expect(el.content).to.equal(longContent); }); it('94. should handle special characters in content', async () => { const el = await fixture(html`Hover`); expect(el.content).to.contain('script'); }); it('95. should handle unicode in content', async () => { const el = await fixture(html`Hover`); expect(el.content).to.contain('\u2713'); }); // === NILE-POPUP CONFIGURATION === it('96. should set distance attribute on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('distance')).to.equal('15'); }); it('97. should set skidding attribute on popup', async () => { const el = await fixture(html`Hover`); const popup = el.shadowRoot!.querySelector('nile-popup'); expect(popup!.getAttribute('skidding')).to.equal('10'); }); // === DATA ATTRIBUTES === it('98. should handle data attributes', async () => { const el = await fixture(html`Hover`); expect(el.getAttribute('data-tooltip-id')).to.equal('1'); }); it('99. should handle class attribute', async () => { const el = await fixture(html`Hover`); expect(el.classList.contains('custom')).to.be.true; }); it('100. should handle all properties together', async () => { const el = await fixture(html` Hover `); expect(el.content).to.equal('Full tooltip'); expect(el.placement).to.equal('bottom-start'); expect(el.distance).to.equal(12); expect(el.skidding).to.equal(5); expect(el.size).to.equal('large'); expect(el.trigger).to.equal('click'); expect(el.hoist).to.be.true; }); });