import { expect, fixture, html, elementUpdated } from '@open-wc/testing'; import './nile-tag'; import NileTag from './nile-tag'; describe('NileTag', () => { // === 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 a span as base element', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span[part="base"]'); expect(span).to.exist; }); it('4. should render slot content', async () => { const el = await fixture(html`Tag Text`); expect(el.textContent!.trim()).to.equal('Tag Text'); }); it('5. should render default slot', async () => { const el = await fixture(html``); const slot = el.shadowRoot!.querySelector('slot[part="content"]'); expect(slot).to.exist; }); it('6. should render prefix slot', async () => { const el = await fixture(html``); const slot = el.shadowRoot!.querySelector('slot[name="prefix"]'); expect(slot).to.exist; }); // === DEFAULT PROPERTIES === it('7. should have variant default to normal', async () => { const el = await fixture(html``); expect(el.variant).to.equal('normal'); }); it('8. should have size default to medium', async () => { const el = await fixture(html``); expect(el.size).to.equal('medium'); }); it('9. should have pill default to false', async () => { const el = await fixture(html``); expect(el.pill).to.be.false; }); it('10. should have removable default to false', async () => { const el = await fixture(html``); expect(el.removable).to.be.false; }); it('11. should have disabled default to false', async () => { const el = await fixture(html``); expect(el.disabled).to.be.false; }); // === VARIANT TESTS === it('12. should apply primary variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--primary')).to.be.true; }); it('13. should apply success variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--success')).to.be.true; }); it('14. should apply normal variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--normal')).to.be.true; }); it('15. should apply warning variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--warning')).to.be.true; }); it('16. should apply error variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; }); it('17. should apply info variant class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--info')).to.be.true; }); it('18. should reflect variant attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('error'); }); // === SIZE TESTS === it('19. should apply small size class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--small')).to.be.true; }); it('20. should apply medium size class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--medium')).to.be.true; }); it('21. should apply large size class', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--large')).to.be.true; }); it('22. should reflect size attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('size')).to.equal('large'); }); // === PILL === it('23. should apply pill class when pill is true', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--pill')).to.be.true; }); it('24. should not apply pill class when pill is false', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--pill')).to.be.false; }); it('25. should reflect pill attribute', async () => { const el = await fixture(html``); expect(el.hasAttribute('pill')).to.be.true; }); // === REMOVABLE === it('26. should show remove button when removable', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn).to.exist; }); it('27. should not show remove button when not removable', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn).to.be.null; }); it('28. should apply removable class when removable', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--removable')).to.be.true; }); it('29. should emit nile-remove when remove button is clicked', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]') as HTMLElement; let removeEmitted = false; el.addEventListener('nile-remove', () => (removeEmitted = true)); removeBtn!.click(); expect(removeEmitted).to.be.true; }); it('30. should set remove button label to "remove"', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn!.getAttribute('label')).to.equal('remove'); }); it('31. should set tabindex -1 on remove button', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn!.getAttribute('tabindex')).to.equal('-1'); }); // === DISABLED === it('32. should disable remove button when tag is disabled', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; }); it('33. should not disable remove button when tag is not disabled', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn!.hasAttribute('disabled')).to.be.false; }); // === CSS PARTS === it('34. should have base part', async () => { const el = await fixture(html``); const base = el.shadowRoot!.querySelector('[part~="base"]'); expect(base).to.exist; }); it('35. should have content part', async () => { const el = await fixture(html``); const content = el.shadowRoot!.querySelector('[part~="content"]'); expect(content).to.exist; }); it('36. should have remove-button part when removable', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('[part~="remove-button"]'); expect(removeBtn).to.exist; }); it('37. should have prefix part', async () => { const el = await fixture(html``); const prefix = el.shadowRoot!.querySelector('[part~="prefix"]'); expect(prefix).to.exist; }); it('38. should have prefix_content_wrapper part', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('[part~="prefix_content_wrapper"]'); expect(wrapper).to.exist; }); // === CLOSE BUTTON COLOR === it('39. should return correct color for primary variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-blue-500'); }); it('40. should return correct color for success variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-green-500'); }); it('41. should return correct color for normal variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-dark-500'); }); it('42. should return correct color for warning variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-yellow-500'); }); it('43. should return correct color for error variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-red-500'); }); it('44. should return correct color for info variant', async () => { const el = await fixture(html``); const color = el.getCloseButtonColor(); expect(color).to.contain('--nile-colors-blue-500'); }); // === DYNAMIC PROPERTY CHANGES === it('45. should update variant dynamically', async () => { const el = await fixture(html``); el.variant = 'error'; await el.updateComplete; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; expect(span!.classList.contains('tag--normal')).to.be.false; }); it('46. should update size dynamically', async () => { const el = await fixture(html``); el.size = 'large'; await el.updateComplete; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--large')).to.be.true; expect(span!.classList.contains('tag--medium')).to.be.false; }); it('47. should update pill dynamically', async () => { const el = await fixture(html``); el.pill = true; await el.updateComplete; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--pill')).to.be.true; }); it('48. should update removable dynamically', async () => { const el = await fixture(html``); el.removable = true; await el.updateComplete; const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn).to.exist; }); it('49. should hide remove button when removable set to false', async () => { const el = await fixture(html``); el.removable = false; await el.updateComplete; const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn).to.be.null; }); it('50. should update disabled dynamically', async () => { const el = await fixture(html``); el.disabled = true; await el.updateComplete; const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; }); // === COMBINED STATES === it('51. should handle all variant + size combos (primary small)', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--primary')).to.be.true; expect(span!.classList.contains('tag--small')).to.be.true; }); it('52. should handle all variant + size combos (success medium)', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--success')).to.be.true; expect(span!.classList.contains('tag--medium')).to.be.true; }); it('53. should handle all variant + size combos (error large)', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; expect(span!.classList.contains('tag--large')).to.be.true; }); it('54. should handle variant + pill', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--warning')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; }); it('55. should handle variant + removable', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--info')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); it('56. should handle all modifiers together', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; expect(span!.classList.contains('tag--large')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); // === TAG CLASS === it('57. should always have tag class on base span', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag')).to.be.true; }); // === INTERNAL STRUCTURE === it('58. should have prefix_content_wrapper div', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('.prefix_content_wrapper'); expect(wrapper).to.exist; }); it('59. should have tag__prefix class on prefix slot', async () => { const el = await fixture(html``); const prefix = el.shadowRoot!.querySelector('.tag__prefix'); expect(prefix).to.exist; }); it('60. should have tag__content class on content slot', async () => { const el = await fixture(html``); const content = el.shadowRoot!.querySelector('.tag__content'); expect(content).to.exist; }); it('61. should have tag__remove class on remove button', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('.tag__remove'); expect(removeBtn).to.exist; }); it('62. should have cross_icon class on remove button', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('.cross_icon'); expect(removeBtn).to.exist; }); // === REMOVE EVENT DETAILS === it('63. should emit nile-remove as CustomEvent', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button[part="remove-button"]') as HTMLElement; let eventType = ''; el.addEventListener('nile-remove', (e: Event) => { eventType = e.constructor.name; }); removeBtn!.click(); expect(eventType).to.equal('CustomEvent'); }); // === ELEMENT INSTANCE === it('64. should be instance of NileTag', async () => { const el = await fixture(html``); expect(el).to.be.instanceOf(NileTag); }); it('65. should have correct tag name', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-tag'); }); // === STATIC STYLES === it('66. should have static styles', async () => { expect(NileTag.styles).to.exist; }); // === ATTRIBUTE REFLECTION === it('67. primary variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('primary'); }); it('68. success variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('success'); }); it('69. normal variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('normal'); }); it('70. warning variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('warning'); }); it('71. error variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('error'); }); it('72. info variant attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('variant')).to.equal('info'); }); it('73. small size attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('size')).to.equal('small'); }); it('74. medium size attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('size')).to.equal('medium'); }); it('75. large size attribute reflection', async () => { const el = await fixture(html``); expect(el.getAttribute('size')).to.equal('large'); }); // === VARIANT + REMOVABLE + DISABLED === it('76. primary removable disabled', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; }); it('77. success removable', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn).to.exist; }); it('78. error removable disabled', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; }); it('79. warning removable', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn).to.exist; }); it('80. info removable disabled', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; }); // === SIZE + PILL + REMOVABLE === it('81. small pill removable', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--small')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); it('82. medium pill removable', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--medium')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); it('83. large pill removable', async () => { const el = await fixture(html``); const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--large')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); // === CONTENT === it('84. should render HTML content in slot', async () => { const el = await fixture(html`Bold Tag`); const strong = el.querySelector('strong'); expect(strong).to.exist; expect(strong!.textContent).to.equal('Bold Tag'); }); it('85. should render multiple elements in slot', async () => { const el = await fixture(html`AB`); const spans = el.querySelectorAll('span'); expect(spans.length).to.equal(2); }); it('86. should handle empty content', async () => { const el = await fixture(html``); expect(el.textContent!.trim()).to.equal(''); }); it('87. should handle long text content', async () => { const longText = 'A'.repeat(500); const el = await fixture(html`${longText}`); expect(el.textContent!.trim()).to.equal(longText); }); // === ATTRIBUTE SETTING === it('88. should set variant via setAttribute', async () => { const el = await fixture(html``); el.setAttribute('variant', 'error'); await el.updateComplete; expect(el.variant).to.equal('error'); }); it('89. should set size via setAttribute', async () => { const el = await fixture(html``); el.setAttribute('size', 'large'); await el.updateComplete; expect(el.size).to.equal('large'); }); it('90. should set pill via setAttribute', async () => { const el = await fixture(html``); el.setAttribute('pill', ''); await el.updateComplete; expect(el.pill).to.be.true; }); // === REMOVE BUTTON COLOR === it('91. should set color on remove button for primary', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); const color = removeBtn!.getAttribute('color'); expect(color).to.contain('--nile-colors-blue-500'); }); it('92. should set color on remove button for success', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); const color = removeBtn!.getAttribute('color'); expect(color).to.contain('--nile-colors-green-500'); }); it('93. should set color on remove button for error', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); const color = removeBtn!.getAttribute('color'); expect(color).to.contain('--nile-colors-red-500'); }); it('94. should set color on remove button for warning', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); const color = removeBtn!.getAttribute('color'); expect(color).to.contain('--nile-colors-yellow-500'); }); it('95. should set color on remove button for normal', async () => { const el = await fixture(html``); const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); const color = removeBtn!.getAttribute('color'); expect(color).to.contain('--nile-colors-dark-500'); }); // === RAPID UPDATES === it('96. should handle rapid variant changes', async () => { const el = await fixture(html``); el.variant = 'primary'; el.variant = 'success'; el.variant = 'error'; await el.updateComplete; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; }); it('97. should handle rapid size changes', async () => { const el = await fixture(html``); el.size = 'small'; el.size = 'large'; el.size = 'medium'; await el.updateComplete; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--medium')).to.be.true; }); it('98. should handle toggling removable', async () => { const el = await fixture(html``); el.removable = true; await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-icon-button')).to.exist; el.removable = false; await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-icon-button')).to.be.null; }); it('99. should handle toggling disabled', async () => { const el = await fixture(html``); el.disabled = true; await el.updateComplete; const removeBtn = el.shadowRoot!.querySelector('nile-icon-button'); expect(removeBtn!.hasAttribute('disabled')).to.be.true; el.disabled = false; await el.updateComplete; expect(removeBtn!.hasAttribute('disabled')).to.be.false; }); it('100. should handle all states simultaneously', async () => { const el = await fixture( html`Error Tag` ); expect(el.variant).to.equal('error'); expect(el.size).to.equal('large'); expect(el.pill).to.be.true; expect(el.removable).to.be.true; expect(el.disabled).to.be.true; const span = el.shadowRoot!.querySelector('span'); expect(span!.classList.contains('tag--error')).to.be.true; expect(span!.classList.contains('tag--large')).to.be.true; expect(span!.classList.contains('tag--pill')).to.be.true; expect(span!.classList.contains('tag--removable')).to.be.true; }); });