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