WebComponent(spec) builds a custom-element base class from a spec: shadow root or declarative shadow DOM, template, shared stylesheet (adopted, or a <style> once per root for light DOM), reflected props with two-way attribute sync, a per-connection AbortSignal, host event wiring, and typed element refs. Types derive exact observedAttributes, prop value types, ref element types from selectors, and reject prop names that collide with the base class or mixin members. Tooling: strict tsconfig, type-aware oxlint, oxfmt, and Vitest browser mode (Chromium) with type tests.
75 lines
1.9 KiB
TypeScript
75 lines
1.9 KiB
TypeScript
import { expect, test } from 'vitest';
|
|
import { WebComponent, disable, enable, number, string } from '../src/index.ts';
|
|
|
|
const SPEC = {
|
|
props: {
|
|
userId: number(),
|
|
userName: string(),
|
|
min: number({ default: 1 }),
|
|
max: number({ default: 10 }),
|
|
ready: enable({ attribute: 'is-ready' }),
|
|
fetch: disable(),
|
|
},
|
|
} as const;
|
|
|
|
class ExampleElement extends WebComponent(SPEC) {}
|
|
|
|
customElements.define('example-element', ExampleElement);
|
|
|
|
function attributes(el: Element): Record<string, string> {
|
|
return Object.fromEntries([...el.attributes].map((a) => [a.name, a.value]));
|
|
}
|
|
|
|
test('observedAttributes', () => {
|
|
expect(ExampleElement.observedAttributes).toEqual([
|
|
'user-id',
|
|
'user-name',
|
|
'min',
|
|
'max',
|
|
'is-ready',
|
|
'disable-fetch',
|
|
]);
|
|
});
|
|
|
|
test('defaults', () => {
|
|
const el = new ExampleElement();
|
|
expect(el.max).toBe(10);
|
|
expect(el.min).toBe(1);
|
|
expect(el.fetch).toBe(true);
|
|
expect(el.ready).toBe(false);
|
|
expect(el.userId).toBeUndefined();
|
|
expect(el.userName).toBeUndefined();
|
|
expect(el.attributes).toHaveLength(0);
|
|
});
|
|
|
|
test('props reflect to attributes and attributes back to props', () => {
|
|
const el = new ExampleElement();
|
|
el.max = 9;
|
|
el.min = 2;
|
|
el.fetch = false;
|
|
el.ready = true;
|
|
el.userId = 123;
|
|
expect(attributes(el)).toEqual({
|
|
max: '9',
|
|
min: '2',
|
|
'disable-fetch': '',
|
|
'is-ready': '',
|
|
'user-id': '123',
|
|
});
|
|
|
|
el.removeAttribute('disable-fetch');
|
|
el.removeAttribute('is-ready');
|
|
el.setAttribute('max', '100');
|
|
el.setAttribute('min', '10');
|
|
el.setAttribute('user-id', '300');
|
|
expect(el.fetch).toBe(true);
|
|
expect(el.max).toBe(100);
|
|
expect(el.min).toBe(10);
|
|
expect(el.ready).toBe(false);
|
|
expect(el.userId).toBe(300);
|
|
expect(attributes(el)).toEqual({ max: '100', min: '10', 'user-id': '300' });
|
|
|
|
el.userId = undefined;
|
|
expect(el.hasAttribute('user-id')).toBe(false);
|
|
});
|