import { expect, fixture, html } from '@open-wc/testing'; import './nile-checkbox-group'; import '../nile-checkbox/nile-checkbox'; import type { NileCheckboxGroup } from './nile-checkbox-group'; import type NileCheckbox from '../nile-checkbox/nile-checkbox'; describe('NileCheckboxGroup', () => { // === 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 have correct tag name', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-checkbox-group'); }); it('4. should be defined in custom elements registry', async () => { expect(customElements.get('nile-checkbox-group')).to.exist; }); it('5. should have open shadow mode', async () => { const el = await fixture(html``); expect(el.shadowRoot!.mode).to.equal('open'); }); it('6. should have styles', async () => { expect((await import('./nile-checkbox-group')).NileCheckboxGroup.styles).to.exist; }); it('7. should render a fieldset', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('fieldset')).to.exist; }); it('8. should render default slot', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('slot:not([name])')).to.exist; }); it('9. should render label slot', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('slot[name="label"]')).to.exist; }); it('10. should have role="group" on fieldset', async () => { const el = await fixture(html``); const fieldset = el.shadowRoot!.querySelector('fieldset'); expect(fieldset!.getAttribute('role')).to.equal('group'); }); it('11. should have hidden validation input', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('input')).to.exist; }); // === DEFAULT PROPERTIES === it('12. should have label default to empty string', async () => { const el = await fixture(html``); expect(el.label).to.equal(''); }); it('13. should have name default to empty string', async () => { const el = await fixture(html``); expect(el.name).to.equal(''); }); it('14. should have value default to empty array', async () => { const el = await fixture(html``); expect(el.value).to.deep.equal([]); }); it('15. should have for default to empty string', async () => { const el = await fixture(html``); expect(el.for).to.equal(''); }); it('16. should have form default to empty string', async () => { const el = await fixture(html``); expect(el.form).to.equal(''); }); it('17. should have required default to false', async () => { const el = await fixture(html``); expect(el.required).to.be.false; }); it('18. should have disabled default to false', async () => { const el = await fixture(html``); expect(el.disabled).to.be.false; }); it('19. should have labelInline default to false', async () => { const el = await fixture(html``); expect(el.labelInline).to.be.false; }); it('20. should have helpText default to empty string', async () => { const el = await fixture(html``); expect(el.helpText).to.equal(''); }); it('21. should have errorMessage default to empty string', async () => { const el = await fixture(html``); expect(el.errorMessage).to.equal(''); }); it('22. should have max default to undefined', async () => { const el = await fixture(html``); expect(el.max).to.be.undefined; }); it('23. should have min default to undefined', async () => { const el = await fixture(html``); expect(el.min).to.be.undefined; }); // === ATTRIBUTE SETTING === it('24. should set label', async () => { const el = await fixture(html``); expect(el.label).to.equal('My Group'); }); it('25. should set name', async () => { const el = await fixture(html``); expect(el.name).to.equal('opts'); }); it('26. should set for', async () => { const el = await fixture(html``); expect(el.for).to.equal('myGroup'); }); it('27. should reflect for attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('for')).to.equal('myGroup'); }); it('28. should set required', async () => { const el = await fixture(html``); expect(el.required).to.be.true; }); it('29. should reflect required attribute', async () => { const el = await fixture(html``); expect(el.hasAttribute('required')).to.be.true; }); it('30. should set disabled', async () => { const el = await fixture(html``); expect(el.disabled).to.be.true; }); it('31. should reflect disabled attribute', async () => { const el = await fixture(html``); expect(el.hasAttribute('disabled')).to.be.true; }); it('32. should set help-text', async () => { const el = await fixture(html``); expect(el.helpText).to.equal('Help'); }); it('33. should reflect help-text attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('help-text')).to.equal('Help'); }); it('34. should set error-message', async () => { const el = await fixture(html``); expect(el.errorMessage).to.equal('Err'); }); it('35. should reflect error-message attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('error-message')).to.equal('Err'); }); it('36. should set max', async () => { const el = await fixture(html``); expect(el.max).to.equal(3); }); it('37. should reflect max attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('max')).to.equal('3'); }); it('38. should set min', async () => { const el = await fixture(html``); expect(el.min).to.equal(1); }); it('39. should set form', async () => { const el = await fixture(html``); expect(el.form).to.equal('f1'); }); it('40. should reflect form attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('form')).to.equal('f1'); }); // === HELP TEXT / ERROR RENDERING === it('41. should render help text when set', async () => { const el = await fixture(html``); const helpText = el.shadowRoot!.querySelector('nile-form-help-text'); expect(helpText).to.exist; }); it('42. should not render help text when empty', async () => { const el = await fixture(html``); const helpText = el.shadowRoot!.querySelector('nile-form-help-text'); expect(helpText).to.be.null; }); it('43. should render error message when set', async () => { const el = await fixture(html``); const errorMsg = el.shadowRoot!.querySelector('nile-form-error-message'); expect(errorMsg).to.exist; }); it('44. should not render error message when empty', async () => { const el = await fixture(html``); const errorMsg = el.shadowRoot!.querySelector('nile-form-error-message'); expect(errorMsg).to.be.null; }); it('45. should render both help text and error message', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-form-help-text')).to.exist; expect(el.shadowRoot!.querySelector('nile-form-error-message')).to.exist; }); // === CSS PARTS === it('46. should have form-control part', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('[part~="form-control"]')).to.exist; }); it('47. should have form-control-label part', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('[part~="form-control-label"]')).to.exist; }); it('48. should have form-control-input part', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('[part~="form-control-input"]')).to.exist; }); it('49. should have options-base part', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('[part~="options-base"]')).to.exist; }); // === SLOTTED MODE: SELECTION === it('50. should collect value from checked slotted checkboxes on click', async () => { const el = await fixture(html` A B `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(el.value).to.deep.equal(['a']); }); it('51. should support multi-selection', async () => { const el = await fixture(html` A B C `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); checkboxes[2].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(el.value).to.deep.equal(['a', 'c']); }); it('52. should remove value on uncheck', async () => { const el = await fixture(html` A B `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); checkboxes[1].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(el.value).to.deep.equal(['a', 'b']); checkboxes[0].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(el.value).to.deep.equal(['b']); }); // === EVENTS === it('53. should emit change event on selection', async () => { const el = await fixture(html` A `); let changeDetail: any = null; el.addEventListener('change', ((e: CustomEvent) => { changeDetail = e.detail; }) as EventListener); el.querySelector('nile-checkbox')!.shadowRoot!.querySelector('input')!.click(); expect(changeDetail).to.exist; expect(changeDetail.value).to.deep.equal(['a']); }); it('54. should emit change event with value on selection', async () => { const el = await fixture(html` A `); let changeFired = false; el.addEventListener('change', () => { changeFired = true; }); el.querySelector('nile-checkbox')!.shadowRoot!.querySelector('input')!.click(); expect(changeFired).to.be.true; }); it('55. should emit exactly one change event per click', async () => { const el = await fixture(html` A `); let changeCount = 0; el.addEventListener('change', () => { changeCount++; }); el.querySelector('nile-checkbox')!.shadowRoot!.querySelector('input')!.click(); expect(changeCount).to.equal(1); }); it('56. should emit nile-init on connected', async () => { let initFired = false; const container = await fixture(html`
`); const el = document.createElement('nile-checkbox-group') as NileCheckboxGroup; el.addEventListener('nile-init', () => { initFired = true; }); container.appendChild(el); await el.updateComplete; expect(initFired).to.be.true; }); it('57. should emit nile-destroy on disconnected', async () => { const el = await fixture(html``); let destroyFired = false; el.addEventListener('nile-destroy', () => { destroyFired = true; }); el.remove(); expect(destroyFired).to.be.true; }); it('58. should not emit change when value does not change', async () => { const el = await fixture(html` A `); let changeFired = false; el.addEventListener('change', () => { changeFired = true; }); el.querySelector('nile-checkbox')!.shadowRoot!.querySelector('input')!.click(); expect(changeFired).to.be.false; }); // === DISABLED === it('59. should propagate disabled to slotted checkboxes', async () => { const el = await fixture(html` A B `); await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes.forEach(cb => expect(cb.disabled).to.be.true); }); it('60. should not emit change when group is disabled', async () => { const el = await fixture(html` A `); let changeFired = false; el.addEventListener('change', () => { changeFired = true; }); el.querySelector('nile-checkbox')!.shadowRoot!.querySelector('input')!.click(); expect(changeFired).to.be.false; }); it('61. should keep individually-disabled checkboxes disabled when group re-enables', async () => { const el = await fixture(html` A B `); el.disabled = false; await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes.forEach(cb => expect(cb.disabled).to.be.true); }); // === PROGRAMMATIC VALUE === it('62. should sync checkbox checked states from programmatic value', async () => { const el = await fixture(html` A B C `); await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); expect(checkboxes[0].checked).to.be.false; expect(checkboxes[1].checked).to.be.true; expect(checkboxes[2].checked).to.be.false; }); it('63. should update checkboxes when value changes programmatically', async () => { const el = await fixture(html` A B C `); el.value = ['a', 'c']; await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); expect(checkboxes[0].checked).to.be.true; expect(checkboxes[1].checked).to.be.false; expect(checkboxes[2].checked).to.be.true; }); it('64. should clear all checkboxes when value set to empty array', async () => { const el = await fixture(html` A B `); el.value = []; await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes.forEach(cb => expect(cb.checked).to.be.false); }); // === MAX LIMIT === it('65. should enforce max limit on click', async () => { const el = await fixture(html` A B C `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); checkboxes[1].shadowRoot!.querySelector('input')!.click(); checkboxes[2].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(el.value.length).to.be.at.most(2); }); it('66. should disable unchecked boxes when max is reached', async () => { const el = await fixture(html` A B `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(checkboxes[0].checked).to.be.true; expect(checkboxes[1].disabled).to.be.true; }); it('67. should re-enable unchecked boxes when count drops below max', async () => { const el = await fixture(html` A B `); const checkboxes = el.querySelectorAll('nile-checkbox'); checkboxes[0].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(checkboxes[1].disabled).to.be.true; checkboxes[0].shadowRoot!.querySelector('input')!.click(); await el.updateComplete; expect(checkboxes[1].disabled).to.be.false; }); it('68. should truncate value array when set programmatically above max', async () => { const el = await fixture(html` A B C `); el.value = ['a', 'b', 'c']; await el.updateComplete; expect(el.value.length).to.be.at.most(2); }); // === INLINE LAYOUT === it('69. should set labelInline', async () => { const el = await fixture(html``); expect(el.labelInline).to.be.true; }); it('70. should apply inline class when labelInline is true', async () => { const el = await fixture(html``); const optionsBase = el.shadowRoot!.querySelector('[part="options-base"]'); expect(optionsBase!.classList.contains('form-control--inline-checkbox')).to.be.true; }); it('71. should not apply inline class by default', async () => { const el = await fixture(html``); const optionsBase = el.shadowRoot!.querySelector('[part="options-base"]'); expect(optionsBase!.classList.contains('form-control--inline-checkbox')).to.be.false; }); // === REMOTE MODE (for/group) === it('72. should discover remote checkboxes via for/group', async () => { const container = await fixture(html`
X Y
`); const group = container.querySelector('nile-checkbox-group')!; await group.updateComplete; const checkboxes = container.querySelectorAll('nile-checkbox[group="test-remote"]'); expect(checkboxes.length).to.equal(2); }); it('73. should sync remote checkboxes when value is set', async () => { const container = await fixture(html`
X Y
`); const group = container.querySelector('nile-checkbox-group')!; group.value = ['y']; await group.updateComplete; const checkboxes = container.querySelectorAll('nile-checkbox'); expect(checkboxes[0].checked).to.be.false; expect(checkboxes[1].checked).to.be.true; }); it('74. should not discover checkboxes with different group name', async () => { const container = await fixture(html`
A B
`); const group = container.querySelector('nile-checkbox-group')!; group.value = ['a', 'b']; await group.updateComplete; const cbAlpha = container.querySelector('nile-checkbox[group="alpha"]')!; const cbBeta = container.querySelector('nile-checkbox[group="beta"]')!; expect(cbAlpha.checked).to.be.true; expect(cbBeta.checked).to.be.false; }); // === CHECKBOX group PROPERTY === it('75. should have group property on nile-checkbox', async () => { const cb = await fixture(html``); expect(cb.group).to.equal('myGroup'); }); it('76. should reflect group attribute on nile-checkbox', async () => { const cb = await fixture(html``); expect(cb.getAttribute('group')).to.equal('myGroup'); }); it('77. should default group to empty string', async () => { const cb = await fixture(html``); expect(cb.group).to.equal(''); }); // === DYNAMIC PROPERTY CHANGES === it('78. should update label dynamically', async () => { const el = await fixture(html``); el.label = 'New Label'; await el.updateComplete; expect(el.label).to.equal('New Label'); }); it('79. should update name dynamically', async () => { const el = await fixture(html``); el.name = 'newName'; await el.updateComplete; expect(el.name).to.equal('newName'); }); it('80. should update helpText dynamically', async () => { const el = await fixture(html``); el.helpText = 'New Help'; await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-form-help-text')).to.exist; }); it('81. should update errorMessage dynamically', async () => { const el = await fixture(html``); el.errorMessage = 'New Error'; await el.updateComplete; expect(el.shadowRoot!.querySelector('nile-form-error-message')).to.exist; }); it('82. should update required dynamically', async () => { const el = await fixture(html``); el.required = true; await el.updateComplete; expect(el.required).to.be.true; expect(el.hasAttribute('required')).to.be.true; }); it('83. should update max dynamically', async () => { const el = await fixture(html` A B C `); el.max = 2; await el.updateComplete; const checkboxes = el.querySelectorAll('nile-checkbox'); expect(checkboxes[2].disabled).to.be.true; }); // === DOM / LIFECYCLE === it('84. should be connected', async () => { const el = await fixture(html``); expect(el.isConnected).to.be.true; }); it('85. should be disconnected after removal', async () => { const el = await fixture(html``); el.remove(); expect(el.isConnected).to.be.false; }); it('86. should handle updateComplete', async () => { const el = await fixture(html``); const r = await el.updateComplete; expect(r).to.not.be.undefined; }); it('87. should have render method', async () => { const el = await fixture(html``); expect(el.render).to.be.a('function'); }); it('88. should handle multiple instances', async () => { const c = await fixture(html`
`); expect(c.querySelectorAll('nile-checkbox-group').length).to.equal(2); }); it('89. should support createElement', async () => { const el = document.createElement('nile-checkbox-group') as NileCheckboxGroup; document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot).to.not.be.null; document.body.removeChild(el); }); it('90. should handle multiple re-renders', async () => { const el = await fixture(html``); for (let i = 0; i < 3; i++) { el.requestUpdate(); await el.updateComplete; } expect(el.shadowRoot).to.not.be.null; }); // === GENERIC DOM === it('91. should support class attribute', async () => { const el = await fixture(html``); expect(el.classList.contains('cg')).to.be.true; }); it('92. should support id attribute', async () => { const el = await fixture(html``); expect(el.id).to.equal('cg1'); }); it('93. should support hidden attribute', async () => { const el = await fixture(html``); expect(el.hidden).to.be.true; }); it('94. should support style attribute', async () => { const el = await fixture(html``); expect(el.style.color).to.equal('red'); }); it('95. should support data attributes', async () => { const el = await fixture(html``); expect(el.dataset.idx).to.equal('0'); }); it('96. should support aria-label', async () => { const el = await fixture(html``); expect(el.getAttribute('aria-label')).to.equal('CG'); }); it('97. should support dir attribute', async () => { const el = await fixture(html``); expect(el.dir).to.equal('rtl'); }); it('98. should support dispatchEvent', async () => { const el = await fixture(html``); let fired = false; el.addEventListener('custom', () => { fired = true; }); el.dispatchEvent(new Event('custom')); expect(fired).to.be.true; }); it('99. should support cloneNode', async () => { const el = await fixture(html``); expect((el.cloneNode(true) as Element).tagName.toLowerCase()).to.equal('nile-checkbox-group'); }); it('100. should handle full integration', async () => { const el = await fixture(html` A B C `); expect(el.label).to.equal('Options'); expect(el.name).to.equal('opts'); expect(el.required).to.be.true; expect(el.helpText).to.equal('Pick some'); expect(el.errorMessage).to.equal('Required'); expect(el.max).to.equal(2); expect(el.id).to.equal('cg1'); expect(el.querySelectorAll('nile-checkbox').length).to.equal(3); }); });