UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

361 lines 17.7 kB
import { createInjector } from '@furystack/inject'; import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'; import { usingAsync } from '@furystack/utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ThemeProviderService } from '../../services/theme-provider-service.js'; import { Form, FormContextToken } from '../form.js'; import { Checkbox } from './checkbox.js'; describe('Checkbox', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; vi.restoreAllMocks(); }); it('should render as custom element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, null), }); await flushUpdates(); const checkbox = document.querySelector('shade-checkbox'); expect(checkbox).not.toBeNull(); }); }); it('should render the inner checkbox input element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { name: "testField" }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); expect(input).not.toBeNull(); expect(input.name).toBe('testField'); }); }); it('should render the label title', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { labelTitle: "Accept terms" }), }); await flushUpdates(); const label = document.querySelector('shade-checkbox label'); expect(label).not.toBeNull(); expect(label.textContent).toContain('Accept terms'); }); }); it('should not render label span when no labelTitle is provided', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, null), }); await flushUpdates(); const labelSpan = document.querySelector('shade-checkbox .checkbox-label'); expect(labelSpan).toBeNull(); }); }); describe('checked state', () => { it('should be checked when checked prop is true', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { checked: true }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); expect(input.checked).toBe(true); }); }); it('should not be checked when checked prop is false', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { checked: false }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); expect(input.checked).toBe(false); }); }); }); describe('disabled state', () => { it('should set data-disabled attribute when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { disabled: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); expect(wrapper.hasAttribute('data-disabled')).toBe(true); const input = wrapper.querySelector('input[type="checkbox"]'); expect(input.disabled).toBe(true); }); }); it('should not have data-disabled attribute when not disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { disabled: false }), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); expect(wrapper.hasAttribute('data-disabled')).toBe(false); }); }); }); describe('indeterminate state', () => { it('should set data-indeterminate attribute when indeterminate', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { indeterminate: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); expect(wrapper.hasAttribute('data-indeterminate')).toBe(true); }); }); it('should not have data-indeterminate attribute when not indeterminate', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { indeterminate: false }), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); expect(wrapper.hasAttribute('data-indeterminate')).toBe(false); }); }); }); describe('theme integration', () => { it('should set CSS color variable from theme', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, null), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); const themeService = injector.get(ThemeProviderService); expect(wrapper.style.getPropertyValue('--checkbox-color')).toBe(themeService.theme.palette.primary.main); }); }); it('should use custom color from color prop', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { color: "secondary" }), }); await flushUpdates(); const wrapper = document.querySelector('shade-checkbox'); const themeService = injector.get(ThemeProviderService); expect(wrapper.style.getPropertyValue('--checkbox-color')).toBe(themeService.theme.palette.secondary.main); }); }); }); describe('callbacks', () => { it('should call onchange when checkbox is clicked', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { onchange: onchange }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); input.dispatchEvent(new Event('change', { bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledOnce(); }); }); }); describe('value and name', () => { it('should set value attribute', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { name: "terms", value: "accepted" }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); expect(input.value).toBe('accepted'); expect(input.name).toBe('terms'); }); }); }); describe('required', () => { it('should set required attribute', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { required: true }), }); await flushUpdates(); const input = document.querySelector('shade-checkbox input[type="checkbox"]'); expect(input.required).toBe(true); }); }); }); describe('labelProps', () => { it('should pass labelProps to the label element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { labelProps: { className: 'custom-label' } }), }); await flushUpdates(); const label = document.querySelector('shade-checkbox label'); expect(label.className).toContain('custom-label'); }); }); }); describe('FormService integration', () => { it('should register input with FormService when inside a Form', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(Form, { onSubmit: () => { }, validate: (_data) => true }, createComponent(Checkbox, { name: "agree", labelTitle: "I agree" }))), }); await flushUpdates(); const form = document.querySelector('form[is="shade-form"]'); const formInjector = form.injector; const formService = formInjector.get(FormContextToken); expect(formService.inputs.size).toBe(1); }); }); it('should unregister input from FormService on cleanup', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(Form, { onSubmit: () => { }, validate: (_data) => true }, createComponent(Checkbox, { name: "agree", labelTitle: "I agree" }))), }); await flushUpdates(); const form = document.querySelector('form[is="shade-form"]'); const formInjector = form.injector; const formService = formInjector.get(FormContextToken); expect(formService.inputs.size).toBe(1); rootElement.innerHTML = ''; await flushUpdates(); }); }); it('should propagate change events to the parent Form so rawFormData updates', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(Form, { onSubmit: () => { }, validate: (_data) => true }, createComponent(Checkbox, { name: "workshops", value: "yes", labelTitle: "Workshops" }))), }); await flushUpdates(); const form = document.querySelector('form[is="shade-form"]'); const input = form.querySelector('input[type="checkbox"]'); const formInjector = form.injector; const formService = formInjector.get(FormContextToken); input.checked = true; input.dispatchEvent(new Event('change', { bubbles: true })); await flushUpdates(); expect(formService.rawFormData.getValue()).toEqual({ workshops: 'yes' }); }); }); it('should submit the native "on" default when value prop is omitted', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(Form, { onSubmit: () => { }, validate: (_data) => true }, createComponent(Checkbox, { name: "agree", labelTitle: "I agree", checked: true }))), }); await flushUpdates(); const form = document.querySelector('form[is="shade-form"]'); const input = form.querySelector('input[type="checkbox"]'); expect(input.hasAttribute('value')).toBe(false); expect(Object.fromEntries(new FormData(form).entries())).toEqual({ agree: 'on' }); }); }); }); describe('size', () => { it('should not set data-size when size is not specified', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, null) }); await flushUpdates(); const el = document.querySelector('shade-checkbox'); expect(el.getAttribute('data-size')).toBeNull(); }); }); it('should not set data-size for medium size (default)', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { size: "medium" }) }); await flushUpdates(); const el = document.querySelector('shade-checkbox'); expect(el.getAttribute('data-size')).toBeNull(); }); }); it('should set data-size="small" for small size', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { size: "small" }) }); await flushUpdates(); const el = document.querySelector('shade-checkbox'); expect(el.getAttribute('data-size')).toBe('small'); }); }); it('should set data-size="large" for large size', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Checkbox, { size: "large" }) }); await flushUpdates(); const el = document.querySelector('shade-checkbox'); expect(el.getAttribute('data-size')).toBe('large'); }); }); }); }); //# sourceMappingURL=checkbox.spec.js.map