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