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