p-elements-core 1.2.32-rc2 → 1.2.32-rc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +17 -17
- package/.gitlab-ci.yml +18 -18
- package/CHANGELOG.md +201 -0
- package/demo/sample.js +1 -1
- package/demo/screen.css +16 -16
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/docs/package-lock.json +6897 -6897
- package/docs/package.json +27 -27
- package/docs/src/404.md +8 -8
- package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
- package/docs/src/_data/demos/hello-world/index.html +10 -10
- package/docs/src/_data/demos/hello-world/project.json +7 -7
- package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
- package/docs/src/_data/demos/timer/icons.tsx +62 -62
- package/docs/src/_data/demos/timer/index.html +12 -12
- package/docs/src/_data/demos/timer/project.json +8 -8
- package/docs/src/_data/global.js +13 -13
- package/docs/src/_data/helpers.js +19 -19
- package/docs/src/_includes/layouts/base.njk +30 -30
- package/docs/src/_includes/layouts/playground.njk +40 -40
- package/docs/src/_includes/partials/app-header.njk +8 -8
- package/docs/src/_includes/partials/head.njk +14 -14
- package/docs/src/_includes/partials/nav.njk +19 -19
- package/docs/src/_includes/partials/top-nav.njk +51 -51
- package/docs/src/documentation/custom-element.md +221 -221
- package/docs/src/documentation/decorators/bind.md +71 -71
- package/docs/src/documentation/decorators/custom-element-config.md +63 -63
- package/docs/src/documentation/decorators/property.md +83 -83
- package/docs/src/documentation/decorators/query.md +66 -66
- package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
- package/docs/src/documentation/decorators.md +9 -9
- package/docs/src/documentation/reactive-properties.md +53 -53
- package/docs/src/index.d.ts +25 -25
- package/docs/src/index.md +3 -3
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
- package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
- package/docs/tsconfig.json +22 -22
- package/package.json +9 -2
- package/readme.md +206 -206
- package/src/custom-element-controller.test.ts +226 -0
- package/src/custom-element-controller.ts +31 -31
- package/src/custom-element.test.ts +906 -0
- package/src/custom-element.ts +17 -1
- package/src/custom-style-element.ts +4 -1
- package/src/decorators/bind.test.ts +163 -0
- package/src/decorators/bind.ts +46 -46
- package/src/decorators/custom-element-config.ts +17 -17
- package/src/decorators/property.test.ts +279 -0
- package/src/decorators/property.ts +789 -684
- package/src/decorators/query.test.ts +146 -0
- package/src/decorators/query.ts +12 -12
- package/src/decorators/render-property-on-set.ts +3 -3
- package/src/helpers/css.test.ts +150 -0
- package/src/helpers/css.ts +71 -71
- package/src/maquette/cache.test.ts +150 -0
- package/src/maquette/cache.ts +35 -35
- package/src/maquette/dom.test.ts +263 -0
- package/src/maquette/dom.ts +115 -115
- package/src/maquette/h.test.ts +165 -0
- package/src/maquette/h.ts +100 -100
- package/src/maquette/index.ts +12 -12
- package/src/maquette/interfaces.ts +536 -536
- package/src/maquette/jsx.ts +61 -61
- package/src/maquette/mapping.test.ts +294 -0
- package/src/maquette/mapping.ts +56 -56
- package/src/maquette/maquette.test.ts +493 -0
- package/src/maquette/projection.test.ts +366 -0
- package/src/maquette/projection.ts +666 -666
- package/src/maquette/projector.test.ts +351 -0
- package/src/maquette/projector.ts +200 -200
- package/src/sample/mixin/highlight.tsx +33 -33
- package/src/test-setup.ts +85 -0
- package/src/test-utils.ts +223 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +41 -0
- package/webpack.config.js +1 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities for p-elements-core testing with Vitest
|
|
3
|
+
* Provides helpers for creating and testing custom elements
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CustomElement } from './custom-element.js';
|
|
7
|
+
import { customElementConfig } from './decorators/custom-element-config.js';
|
|
8
|
+
import { generateUniqueTagName, trackElement } from './test-setup.js';
|
|
9
|
+
import type { VNode } from './maquette/interfaces.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wait for an element to complete its update cycle
|
|
13
|
+
*/
|
|
14
|
+
export async function waitForRender(element: CustomElement): Promise<void> {
|
|
15
|
+
// Wait for shadowRoot to be created (async in requestAnimationFrame)
|
|
16
|
+
let attempts = 0;
|
|
17
|
+
const maxAttempts = 100;
|
|
18
|
+
while (!element.shadowRoot && attempts < maxAttempts) {
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
20
|
+
attempts++;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!element.shadowRoot && attempts >= maxAttempts) {
|
|
24
|
+
console.warn('waitForRender: shadowRoot was not created after max attempts');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Wait for projector to render (also async in requestAnimationFrame)
|
|
28
|
+
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined))));
|
|
29
|
+
|
|
30
|
+
// Then wait for update to complete
|
|
31
|
+
await element.updateComplete;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create and register a test custom element
|
|
36
|
+
* Returns the tag name for the registered element
|
|
37
|
+
*/
|
|
38
|
+
export function defineTestElement(
|
|
39
|
+
render: () => VNode,
|
|
40
|
+
options: {
|
|
41
|
+
tagName?: string;
|
|
42
|
+
isFormAssociated?: boolean;
|
|
43
|
+
delegatesFocus?: boolean;
|
|
44
|
+
style?: string;
|
|
45
|
+
extends?: string;
|
|
46
|
+
} = {}
|
|
47
|
+
): string {
|
|
48
|
+
const tagName = options.tagName || generateUniqueTagName();
|
|
49
|
+
|
|
50
|
+
@customElementConfig({
|
|
51
|
+
tagName,
|
|
52
|
+
options: options.extends ? { extends: options.extends } : undefined,
|
|
53
|
+
})
|
|
54
|
+
class TestElement extends CustomElement {
|
|
55
|
+
static style = options.style || '';
|
|
56
|
+
static isFormAssociated = options.isFormAssociated || false;
|
|
57
|
+
static delegatesFocus = options.delegatesFocus || false;
|
|
58
|
+
|
|
59
|
+
render() {
|
|
60
|
+
return render();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return tagName;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a simple test element directly in the DOM
|
|
69
|
+
*/
|
|
70
|
+
export async function createTestElement<T extends HTMLElement = HTMLElement>(
|
|
71
|
+
tagName: string,
|
|
72
|
+
attributes: Record<string, any> = {}
|
|
73
|
+
): Promise<T> {
|
|
74
|
+
const el = document.createElement(tagName) as T;
|
|
75
|
+
|
|
76
|
+
// Set attributes
|
|
77
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
78
|
+
if (typeof value === 'boolean') {
|
|
79
|
+
if (value) {
|
|
80
|
+
el.setAttribute(key, '');
|
|
81
|
+
}
|
|
82
|
+
} else if (value !== null && value !== undefined) {
|
|
83
|
+
el.setAttribute(key, String(value));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Add to body for connection
|
|
88
|
+
document.body.appendChild(el);
|
|
89
|
+
trackElement(el);
|
|
90
|
+
|
|
91
|
+
// Wait for render if it's a CustomElement
|
|
92
|
+
if (el instanceof CustomElement) {
|
|
93
|
+
await waitForRender(el as CustomElement);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return el;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get property descriptor from an element (helps test decorator-generated properties)
|
|
101
|
+
*/
|
|
102
|
+
export function getPropertyDescriptor(
|
|
103
|
+
element: any,
|
|
104
|
+
propertyName: string
|
|
105
|
+
): PropertyDescriptor | undefined {
|
|
106
|
+
let proto = Object.getPrototypeOf(element);
|
|
107
|
+
|
|
108
|
+
while (proto) {
|
|
109
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyName);
|
|
110
|
+
if (descriptor) {
|
|
111
|
+
return descriptor;
|
|
112
|
+
}
|
|
113
|
+
proto = Object.getPrototypeOf(proto);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wait for a condition to be true
|
|
121
|
+
*/
|
|
122
|
+
export async function waitFor(
|
|
123
|
+
condition: () => boolean,
|
|
124
|
+
timeout: number = 1000,
|
|
125
|
+
interval: number = 10
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
const startTime = Date.now();
|
|
128
|
+
|
|
129
|
+
while (!condition()) {
|
|
130
|
+
if (Date.now() - startTime > timeout) {
|
|
131
|
+
throw new Error('Timeout waiting for condition');
|
|
132
|
+
}
|
|
133
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Simulate an attribute change on an element
|
|
139
|
+
*/
|
|
140
|
+
export function setAttributeAndNotify(
|
|
141
|
+
element: HTMLElement & { attributeChangedCallback?: Function },
|
|
142
|
+
name: string,
|
|
143
|
+
value: string | null
|
|
144
|
+
): void {
|
|
145
|
+
const oldValue = element.getAttribute(name);
|
|
146
|
+
|
|
147
|
+
if (value === null) {
|
|
148
|
+
element.removeAttribute(name);
|
|
149
|
+
} else {
|
|
150
|
+
element.setAttribute(name, value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Manually trigger attributeChangedCallback if it exists
|
|
154
|
+
if (element.attributeChangedCallback) {
|
|
155
|
+
element.attributeChangedCallback(name, oldValue, value);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Assert that a VNode has specific properties
|
|
161
|
+
*/
|
|
162
|
+
export function assertVNode(
|
|
163
|
+
vnode: VNode,
|
|
164
|
+
expected: Partial<{
|
|
165
|
+
vnodeSelector: string;
|
|
166
|
+
text?: string;
|
|
167
|
+
children?: VNode[];
|
|
168
|
+
}>
|
|
169
|
+
): void {
|
|
170
|
+
if (expected.vnodeSelector) {
|
|
171
|
+
if (vnode.vnodeSelector !== expected.vnodeSelector) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Expected vnodeSelector "${expected.vnodeSelector}" but got "${vnode.vnodeSelector}"`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (expected.text !== undefined) {
|
|
179
|
+
if (vnode.text !== expected.text) {
|
|
180
|
+
throw new Error(`Expected text "${expected.text}" but got "${vnode.text}"`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (expected.children) {
|
|
185
|
+
if (!vnode.children || vnode.children.length !== expected.children.length) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Expected ${expected.children.length} children but got ${vnode.children?.length || 0}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get the shadow root of an element, throwing if not found
|
|
195
|
+
*/
|
|
196
|
+
export function getShadowRoot(element: HTMLElement): ShadowRoot {
|
|
197
|
+
if (!element.shadowRoot) {
|
|
198
|
+
throw new Error('Element does not have a shadow root');
|
|
199
|
+
}
|
|
200
|
+
return element.shadowRoot;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Query a selector within an element's shadow DOM
|
|
205
|
+
*/
|
|
206
|
+
export function shadowQuery<T extends Element = Element>(
|
|
207
|
+
element: HTMLElement,
|
|
208
|
+
selector: string
|
|
209
|
+
): T | null {
|
|
210
|
+
return getShadowRoot(element).querySelector<T>(selector);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Query all selectors within an element's shadow DOM
|
|
215
|
+
*/
|
|
216
|
+
export function shadowQueryAll<T extends Element = Element>(
|
|
217
|
+
element: HTMLElement,
|
|
218
|
+
selector: string
|
|
219
|
+
): NodeListOf<T> {
|
|
220
|
+
return getShadowRoot(element).querySelectorAll<T>(selector);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
package/tsconfig.json
CHANGED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
browser: {
|
|
7
|
+
enabled: true,
|
|
8
|
+
provider: playwright(),
|
|
9
|
+
headless: process.env.HEADED !== 'true',
|
|
10
|
+
instances: [
|
|
11
|
+
{
|
|
12
|
+
browser: 'chromium',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
globals: true,
|
|
17
|
+
setupFiles: ['./src/test-setup.ts'],
|
|
18
|
+
include: ['src/**/*.test.{ts,tsx}'],
|
|
19
|
+
exclude: ['node_modules', 'dist', '.idea', '.git', '.cache'],
|
|
20
|
+
coverage: {
|
|
21
|
+
provider: 'v8',
|
|
22
|
+
reporter: ['text', 'json', 'html', 'lcov'],
|
|
23
|
+
reportsDirectory: 'coverage',
|
|
24
|
+
exclude: [
|
|
25
|
+
'node_modules/',
|
|
26
|
+
'src/test-setup.ts',
|
|
27
|
+
'src/test-utils.ts',
|
|
28
|
+
'src/**/*.test.ts',
|
|
29
|
+
],
|
|
30
|
+
thresholds: {
|
|
31
|
+
lines: 70,
|
|
32
|
+
functions: 70,
|
|
33
|
+
branches: 60,
|
|
34
|
+
statements: 70,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
testTimeout: 5000,
|
|
38
|
+
hookTimeout: 5000,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|