import { expect, fixture, html, elementUpdated } from '@open-wc/testing';
import './nile-button';
import NileButton from './nile-button';
describe('NileButton', () => {
// === RENDERING TESTS ===
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 button element by default', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.tagName).to.equal('BUTTON');
});
it('4. should render an anchor element when href is set', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.tagName).to.equal('A');
});
it('5. should render slot content', async () => {
const el = await fixture(html`Click Me`);
expect(el.textContent!.trim()).to.equal('Click Me');
});
it('6. should render prefix slot', async () => {
const el = await fixture(html`PLabel`);
const prefixSlot = el.shadowRoot!.querySelector('slot[name="prefix"]');
expect(prefixSlot).to.exist;
});
it('7. should render suffix slot', async () => {
const el = await fixture(html`SLabel`);
const suffixSlot = el.shadowRoot!.querySelector('slot[name="suffix"]');
expect(suffixSlot).to.exist;
});
it('8. should render default slot for label', async () => {
const el = await fixture(html``);
const labelSlot = el.shadowRoot!.querySelector('slot:not([name])');
expect(labelSlot).to.exist;
});
// === DEFAULT PROPERTIES ===
it('9. should have variant default to primary', async () => {
const el = await fixture(html``);
expect(el.variant).to.equal('primary');
});
it('10. should have size default to medium', async () => {
const el = await fixture(html``);
expect(el.size).to.equal('medium');
});
it('11. should have type default to button', async () => {
const el = await fixture(html``);
expect(el.type).to.equal('button');
});
it('12. should have disabled default to false', async () => {
const el = await fixture(html``);
expect(el.disabled).to.be.false;
});
it('13. should have loading default to false', async () => {
const el = await fixture(html``);
expect(el.loading).to.be.false;
});
it('14. should have caret default to false', async () => {
const el = await fixture(html``);
expect(el.caret).to.be.false;
});
it('15. should have outline default to false', async () => {
const el = await fixture(html``);
expect(el.outline).to.be.false;
});
it('16. should have pill default to false', async () => {
const el = await fixture(html``);
expect(el.pill).to.be.false;
});
it('17. should have circle default to false', async () => {
const el = await fixture(html``);
expect(el.circle).to.be.false;
});
it('18. should have name default to empty string', async () => {
const el = await fixture(html``);
expect(el.name).to.equal('');
});
it('19. should have value default to empty string', async () => {
const el = await fixture(html``);
expect(el.value).to.equal('');
});
it('20. should have href default to empty string', async () => {
const el = await fixture(html``);
expect(el.href).to.equal('');
});
it('21. should have hideBorder default to false', async () => {
const el = await fixture(html``);
expect(el.hideBorder).to.be.false;
});
it('22. should have title default to empty string', async () => {
const el = await fixture(html``);
expect(el.title).to.equal('');
});
// === VARIANT TESTS ===
it('23. should apply primary variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--primary')).to.be.true;
});
it('24. should apply secondary variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--secondary')).to.be.true;
});
it('25. should apply tertiary variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--tertiary')).to.be.true;
});
it('26. should apply caution variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--caution')).to.be.true;
});
it('27. should apply ghost variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--ghost')).to.be.true;
});
it('28. should apply destructive variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--destructive')).to.be.true;
});
it('29. should apply secondary-grey variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--secondary-grey')).to.be.true;
});
it('30. should apply secondary-blue variant class', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--secondary-blue')).to.be.true;
});
it('31. should reflect variant attribute', async () => {
const el = await fixture(html``);
expect(el.getAttribute('variant')).to.equal('caution');
});
// === DISABLED STATE ===
it('32. should be disabled when disabled property is set', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.hasAttribute('disabled')).to.be.true;
});
it('33. should have aria-disabled true when disabled', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('aria-disabled')).to.equal('true');
});
it('34. should have aria-disabled false when not disabled', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('aria-disabled')).to.equal('false');
});
it('35. should apply disabled class when disabled', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--disabled')).to.be.true;
});
it('36. should have tabindex -1 when disabled', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('tabindex')).to.equal('-1');
});
it('37. should have tabindex 0 when not disabled', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('tabindex')).to.equal('0');
});
it('38. should prevent click events when disabled', async () => {
const el = await fixture(html``);
let clicked = false;
el.addEventListener('click', () => (clicked = true));
el.click();
expect(clicked).to.be.false;
});
it('39. should reflect disabled attribute', async () => {
const el = await fixture(html``);
expect(el.hasAttribute('disabled')).to.be.true;
});
// === LOADING STATE ===
it('40. should show spinner when loading', async () => {
const el = await fixture(html``);
const spinner = el.shadowRoot!.querySelector('nile-spinner');
expect(spinner).to.exist;
});
it('41. should not show spinner when not loading', async () => {
const el = await fixture(html``);
const spinner = el.shadowRoot!.querySelector('nile-spinner');
expect(spinner).to.be.null;
});
it('42. should apply loading class when loading', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--loading')).to.be.true;
});
it('43. should prevent click when loading', async () => {
const el = await fixture(html``);
let clicked = false;
el.addEventListener('click', () => (clicked = true));
el.click();
expect(clicked).to.be.false;
});
it('44. should reflect loading attribute', async () => {
const el = await fixture(html``);
expect(el.hasAttribute('loading')).to.be.true;
});
// === CARET ===
it('45. should show caret icon when caret is true', async () => {
const el = await fixture(html``);
const caret = el.shadowRoot!.querySelector('nile-icon[part="caret"]');
expect(caret).to.exist;
});
it('46. should not show caret icon when caret is false', async () => {
const el = await fixture(html``);
const caret = el.shadowRoot!.querySelector('nile-icon[part="caret"]');
expect(caret).to.be.null;
});
it('47. should apply caret class when caret is set', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--caret')).to.be.true;
});
// === PILL ===
it('48. should apply pill class when pill is true', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--pill')).to.be.true;
});
it('49. should reflect pill attribute', async () => {
const el = await fixture(html``);
expect(el.hasAttribute('pill')).to.be.true;
});
// === CIRCLE ===
it('50. should apply circle class when circle is true', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--circle')).to.be.true;
});
it('51. should reflect circle attribute', async () => {
const el = await fixture(html``);
expect(el.hasAttribute('circle')).to.be.true;
});
// === OUTLINE ===
it('52. should apply outline class when outline is true', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--outline')).to.be.true;
});
it('53. should apply standard class when outline is false', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--standard')).to.be.true;
});
// === LINK MODE (href) ===
it('54. should set href attribute on anchor', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.getAttribute('href')).to.equal('https://example.com');
});
it('55. should set target on anchor', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.getAttribute('target')).to.equal('_blank');
});
it('56. should set role=button on anchor', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.getAttribute('role')).to.equal('button');
});
it('57. should set rel attribute on anchor', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.getAttribute('rel')).to.equal('noreferrer noopener');
});
it('58. should set download attribute on anchor when provided', async () => {
const el = await fixture(html``);
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.getAttribute('download')).to.equal('file.pdf');
});
// === BUTTON TYPE ===
it('59. should set button type attribute', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('type')).to.equal('submit');
});
it('60. should support type=reset', async () => {
const el = await fixture(html``);
expect(el.type).to.equal('reset');
});
it('61. should set name attribute on button', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('name')).to.equal('myButton');
});
it('62. should set value attribute on button', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('value')).to.equal('myValue');
});
// === EVENTS ===
it('63. should emit nile-focus when focused', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
let focusEmitted = false;
el.addEventListener('nile-focus', () => (focusEmitted = true));
button!.dispatchEvent(new FocusEvent('focus'));
expect(focusEmitted).to.be.true;
});
it('64. should emit nile-blur when blurred', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
let blurEmitted = false;
el.addEventListener('nile-blur', () => (blurEmitted = true));
button!.dispatchEvent(new FocusEvent('blur'));
expect(blurEmitted).to.be.true;
});
it('65. should emit nile-init on connectedCallback', async () => {
let initEmitted = false;
const container = await fixture(html``);
const el = document.createElement('nile-button') as NileButton;
el.addEventListener('nile-init', () => (initEmitted = true));
container.appendChild(el);
await el.updateComplete;
expect(initEmitted).to.be.true;
});
it('66. should emit nile-destroy on disconnectedCallback', async () => {
const el = await fixture(html``);
let destroyEmitted = false;
el.addEventListener('nile-destroy', () => (destroyEmitted = true));
el.remove();
expect(destroyEmitted).to.be.true;
});
// === FOCUS/BLUR METHODS ===
it('67. should focus programmatically', async () => {
const el = await fixture(html`Test`);
el.focus();
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--focused')).to.be.true;
});
it('68. should blur programmatically', async () => {
const el = await fixture(html`Test`);
el.focus();
await el.updateComplete;
el.blur();
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--focused')).to.be.false;
});
it('69. should click programmatically', async () => {
const el = await fixture(html`Test`);
let clicked = false;
el.addEventListener('click', () => (clicked = true));
el.click();
expect(clicked).to.be.true;
});
// === VALIDATION ===
it('70. should set custom validation message', async () => {
const el = await fixture(html``);
const form = document.createElement('form');
form.appendChild(el);
document.body.appendChild(form);
el.setCustomValidity('Invalid');
expect(el.validationMessage).to.equal('Invalid');
el.setCustomValidity('');
expect(el.validationMessage).to.equal('');
document.body.removeChild(form);
});
it('71. should check validity', async () => {
const el = await fixture(html``);
expect(el.checkValidity()).to.be.true;
});
it('72. should report validity', async () => {
const el = await fixture(html``);
expect(el.reportValidity()).to.be.true;
});
it('73. should get validity state', async () => {
const el = await fixture(html``);
expect(el.validity).to.exist;
expect(el.validity.valid).to.be.true;
});
// === FORM INTEGRATION ===
it('74. should return null getForm when not in a form', async () => {
const el = await fixture(html``);
expect(el.getForm()).to.be.null;
});
it('75. should set formAction property', async () => {
const el = await fixture(html``);
expect(el.formAction).to.equal('/submit');
});
it('76. should set formMethod property', async () => {
const el = await fixture(html``);
expect(el.formMethod).to.equal('post');
});
it('77. should set formNoValidate property', async () => {
const el = await fixture(html``);
expect(el.formNoValidate).to.be.true;
});
it('78. should set formTarget property', async () => {
const el = await fixture(html``);
expect(el.formTarget).to.equal('_blank');
});
it('79. should set formEnctype property', async () => {
const el = await fixture(html``);
expect(el.formEnctype).to.equal('multipart/form-data');
});
// === HIDE BORDER ===
it('80. should apply hideborder class when hide-border is set', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--hideborder')).to.be.true;
});
it('81. should reflect hide-border attribute', async () => {
const el = await fixture(html``);
expect(el.hasAttribute('hide-border')).to.be.true;
});
// === TITLE ===
it('82. should set title on internal button', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.getAttribute('title')).to.equal('My Title');
});
// === DYNAMIC PROPERTY CHANGES ===
it('83. should update variant dynamically', async () => {
const el = await fixture(html``);
el.variant = 'secondary';
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--secondary')).to.be.true;
});
it('84. should update disabled dynamically', async () => {
const el = await fixture(html``);
el.disabled = true;
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.hasAttribute('disabled')).to.be.true;
});
it('85. should update loading dynamically', async () => {
const el = await fixture(html``);
el.loading = true;
await el.updateComplete;
const spinner = el.shadowRoot!.querySelector('nile-spinner');
expect(spinner).to.exist;
});
it('86. should update caret dynamically', async () => {
const el = await fixture(html``);
el.caret = true;
await el.updateComplete;
const caret = el.shadowRoot!.querySelector('nile-icon[part="caret"]');
expect(caret).to.exist;
});
it('87. should update pill dynamically', async () => {
const el = await fixture(html``);
el.pill = true;
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--pill')).to.be.true;
});
it('88. should update circle dynamically', async () => {
const el = await fixture(html``);
el.circle = true;
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--circle')).to.be.true;
});
it('89. should update outline dynamically', async () => {
const el = await fixture(html``);
el.outline = true;
await el.updateComplete;
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--outline')).to.be.true;
});
it('90. should switch from button to anchor when href is set dynamically', async () => {
const el = await fixture(html``);
el.href = 'https://example.com';
await el.updateComplete;
const anchor = el.shadowRoot!.querySelector('.button');
expect(anchor!.tagName).to.equal('A');
});
// === CSS PARTS ===
it('91. should have base part on button', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('[part~="base"]');
expect(button).to.exist;
});
it('92. should have prefix part', async () => {
const el = await fixture(html``);
const prefix = el.shadowRoot!.querySelector('[part~="prefix"]');
expect(prefix).to.exist;
});
it('93. should have label part', async () => {
const el = await fixture(html``);
const label = el.shadowRoot!.querySelector('[part~="label"]');
expect(label).to.exist;
});
it('94. should have suffix part', async () => {
const el = await fixture(html``);
const suffix = el.shadowRoot!.querySelector('[part~="suffix"]');
expect(suffix).to.exist;
});
it('95. should have caret part when caret is set', async () => {
const el = await fixture(html``);
const caret = el.shadowRoot!.querySelector('[part~="caret"]');
expect(caret).to.exist;
});
// === COMBINED STATES ===
it('96. should handle disabled and loading together', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--disabled')).to.be.true;
expect(button!.classList.contains('button--loading')).to.be.true;
});
it('97. should handle pill and outline together', async () => {
const el = await fixture(html``);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--pill')).to.be.true;
expect(button!.classList.contains('button--outline')).to.be.true;
});
it('98. should show caret on link button', async () => {
const el = await fixture(html``);
const caret = el.shadowRoot!.querySelector('nile-icon[part="caret"]');
expect(caret).to.exist;
});
it('99. should show spinner on link button when loading', async () => {
const el = await fixture(html``);
const spinner = el.shadowRoot!.querySelector('nile-spinner');
expect(spinner).to.exist;
});
it('100. should handle all visual modifiers simultaneously', async () => {
const el = await fixture(
html``
);
const button = el.shadowRoot!.querySelector('.button');
expect(button!.classList.contains('button--destructive')).to.be.true;
expect(button!.classList.contains('button--pill')).to.be.true;
expect(button!.classList.contains('button--outline')).to.be.true;
expect(button!.classList.contains('button--caret')).to.be.true;
expect(button!.classList.contains('button--loading')).to.be.true;
expect(button!.classList.contains('button--disabled')).to.be.true;
});
});