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