@craft-ts/component 0.7.0-beta.13
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/LICENSE +21 -0
- package/package.json +36 -0
- package/src/index.d.ts +40 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +38 -0
- package/src/index.js.map +1 -0
- package/src/lib/a11y-control.d.ts +58 -0
- package/src/lib/a11y-control.d.ts.map +1 -0
- package/src/lib/a11y-control.js +74 -0
- package/src/lib/a11y-control.js.map +1 -0
- package/src/lib/a11y.d.ts +87 -0
- package/src/lib/a11y.d.ts.map +1 -0
- package/src/lib/a11y.js +96 -0
- package/src/lib/a11y.js.map +1 -0
- package/src/lib/ai/ai-context-menu.d.ts +140 -0
- package/src/lib/ai/ai-context-menu.d.ts.map +1 -0
- package/src/lib/ai/ai-context-menu.js +66 -0
- package/src/lib/ai/ai-context-menu.js.map +1 -0
- package/src/lib/ai/ai-send-dialog.d.ts +1263 -0
- package/src/lib/ai/ai-send-dialog.d.ts.map +1 -0
- package/src/lib/ai/ai-send-dialog.js +288 -0
- package/src/lib/ai/ai-send-dialog.js.map +1 -0
- package/src/lib/ai/send-context-to-ai.d.ts +29 -0
- package/src/lib/ai/send-context-to-ai.d.ts.map +1 -0
- package/src/lib/ai/send-context-to-ai.js +127 -0
- package/src/lib/ai/send-context-to-ai.js.map +1 -0
- package/src/lib/assert-defined-input.d.ts +26 -0
- package/src/lib/assert-defined-input.d.ts.map +1 -0
- package/src/lib/assert-defined-input.js +47 -0
- package/src/lib/assert-defined-input.js.map +1 -0
- package/src/lib/block.d.ts +36 -0
- package/src/lib/block.d.ts.map +1 -0
- package/src/lib/block.js +55 -0
- package/src/lib/block.js.map +1 -0
- package/src/lib/bootstrap.d.ts +32 -0
- package/src/lib/bootstrap.d.ts.map +1 -0
- package/src/lib/bootstrap.js +60 -0
- package/src/lib/bootstrap.js.map +1 -0
- package/src/lib/bridge.d.ts +56 -0
- package/src/lib/bridge.d.ts.map +1 -0
- package/src/lib/bridge.js +81 -0
- package/src/lib/bridge.js.map +1 -0
- package/src/lib/component.d.ts +46 -0
- package/src/lib/component.d.ts.map +1 -0
- package/src/lib/component.js +118 -0
- package/src/lib/component.js.map +1 -0
- package/src/lib/composition.d.ts +17 -0
- package/src/lib/composition.d.ts.map +1 -0
- package/src/lib/composition.js +28 -0
- package/src/lib/composition.js.map +1 -0
- package/src/lib/craft-defaults.d.ts +2 -0
- package/src/lib/craft-defaults.d.ts.map +1 -0
- package/src/lib/craft-defaults.js +41 -0
- package/src/lib/craft-defaults.js.map +1 -0
- package/src/lib/craft-host-tokens.d.ts +6 -0
- package/src/lib/craft-host-tokens.d.ts.map +1 -0
- package/src/lib/craft-host-tokens.js +7 -0
- package/src/lib/craft-host-tokens.js.map +1 -0
- package/src/lib/craft-router-outlet.d.ts +15 -0
- package/src/lib/craft-router-outlet.d.ts.map +1 -0
- package/src/lib/craft-router-outlet.js +10 -0
- package/src/lib/craft-router-outlet.js.map +1 -0
- package/src/lib/css-vars.d.ts +31 -0
- package/src/lib/css-vars.d.ts.map +1 -0
- package/src/lib/css-vars.js +43 -0
- package/src/lib/css-vars.js.map +1 -0
- package/src/lib/css-vars.type.d.ts +151 -0
- package/src/lib/css-vars.type.d.ts.map +1 -0
- package/src/lib/css-vars.type.js +1 -0
- package/src/lib/css-vars.type.js.map +1 -0
- package/src/lib/defer.d.ts +31 -0
- package/src/lib/defer.d.ts.map +1 -0
- package/src/lib/defer.js +19 -0
- package/src/lib/defer.js.map +1 -0
- package/src/lib/directive.d.ts +9 -0
- package/src/lib/directive.d.ts.map +1 -0
- package/src/lib/directive.js +31 -0
- package/src/lib/directive.js.map +1 -0
- package/src/lib/each-scheduling.d.ts +59 -0
- package/src/lib/each-scheduling.d.ts.map +1 -0
- package/src/lib/each-scheduling.js +120 -0
- package/src/lib/each-scheduling.js.map +1 -0
- package/src/lib/each.d.ts +29 -0
- package/src/lib/each.d.ts.map +1 -0
- package/src/lib/each.js +23 -0
- package/src/lib/each.js.map +1 -0
- package/src/lib/factory-runtime.d.ts +5 -0
- package/src/lib/factory-runtime.d.ts.map +1 -0
- package/src/lib/factory-runtime.js +26 -0
- package/src/lib/factory-runtime.js.map +1 -0
- package/src/lib/field-exception-block.d.ts +96 -0
- package/src/lib/field-exception-block.d.ts.map +1 -0
- package/src/lib/field-exception-block.js +44 -0
- package/src/lib/field-exception-block.js.map +1 -0
- package/src/lib/host-runtime.d.ts +60 -0
- package/src/lib/host-runtime.d.ts.map +1 -0
- package/src/lib/host-runtime.js +16 -0
- package/src/lib/host-runtime.js.map +1 -0
- package/src/lib/hydrate.d.ts +15 -0
- package/src/lib/hydrate.d.ts.map +1 -0
- package/src/lib/hydrate.js +79 -0
- package/src/lib/hydrate.js.map +1 -0
- package/src/lib/hyperscript.d.ts +148 -0
- package/src/lib/hyperscript.d.ts.map +1 -0
- package/src/lib/hyperscript.js +96 -0
- package/src/lib/hyperscript.js.map +1 -0
- package/src/lib/if-block.d.ts +8 -0
- package/src/lib/if-block.d.ts.map +1 -0
- package/src/lib/if-block.js +16 -0
- package/src/lib/if-block.js.map +1 -0
- package/src/lib/locator.d.ts +170 -0
- package/src/lib/locator.d.ts.map +1 -0
- package/src/lib/locator.js +151 -0
- package/src/lib/locator.js.map +1 -0
- package/src/lib/match-block.d.ts +38 -0
- package/src/lib/match-block.d.ts.map +1 -0
- package/src/lib/match-block.js +29 -0
- package/src/lib/match-block.js.map +1 -0
- package/src/lib/pending-block.d.ts +125 -0
- package/src/lib/pending-block.d.ts.map +1 -0
- package/src/lib/pending-block.js +48 -0
- package/src/lib/pending-block.js.map +1 -0
- package/src/lib/project.d.ts +19 -0
- package/src/lib/project.d.ts.map +1 -0
- package/src/lib/project.js +56 -0
- package/src/lib/project.js.map +1 -0
- package/src/lib/render/hydration-metadata.d.ts +3 -0
- package/src/lib/render/hydration-metadata.d.ts.map +1 -0
- package/src/lib/render/hydration-metadata.js +8 -0
- package/src/lib/render/hydration-metadata.js.map +1 -0
- package/src/lib/render/hydration.d.ts +24 -0
- package/src/lib/render/hydration.d.ts.map +1 -0
- package/src/lib/render/hydration.js +181 -0
- package/src/lib/render/hydration.js.map +1 -0
- package/src/lib/render/interpreter.d.ts +32 -0
- package/src/lib/render/interpreter.d.ts.map +1 -0
- package/src/lib/render/interpreter.js +3588 -0
- package/src/lib/render/interpreter.js.map +1 -0
- package/src/lib/render/server-style-collector.d.ts +6 -0
- package/src/lib/render/server-style-collector.d.ts.map +1 -0
- package/src/lib/render/server-style-collector.js +23 -0
- package/src/lib/render/server-style-collector.js.map +1 -0
- package/src/lib/render/ssr-coordinator.d.ts +20 -0
- package/src/lib/render/ssr-coordinator.d.ts.map +1 -0
- package/src/lib/render/ssr-coordinator.js +99 -0
- package/src/lib/render/ssr-coordinator.js.map +1 -0
- package/src/lib/render/string-dom.d.ts +9 -0
- package/src/lib/render/string-dom.d.ts.map +1 -0
- package/src/lib/render/string-dom.js +377 -0
- package/src/lib/render/string-dom.js.map +1 -0
- package/src/lib/render/style-registry.d.ts +14 -0
- package/src/lib/render/style-registry.d.ts.map +1 -0
- package/src/lib/render/style-registry.js +79 -0
- package/src/lib/render/style-registry.js.map +1 -0
- package/src/lib/render/style-scope.d.ts +30 -0
- package/src/lib/render/style-scope.d.ts.map +1 -0
- package/src/lib/render/style-scope.js +261 -0
- package/src/lib/render/style-scope.js.map +1 -0
- package/src/lib/render/vnode.d.ts +330 -0
- package/src/lib/render/vnode.d.ts.map +1 -0
- package/src/lib/render/vnode.js +293 -0
- package/src/lib/render/vnode.js.map +1 -0
- package/src/lib/server-render.d.ts +34 -0
- package/src/lib/server-render.d.ts.map +1 -0
- package/src/lib/server-render.js +84 -0
- package/src/lib/server-render.js.map +1 -0
- package/src/lib/start.d.ts +13 -0
- package/src/lib/start.d.ts.map +1 -0
- package/src/lib/start.js +20 -0
- package/src/lib/start.js.map +1 -0
- package/src/lib/template-contract.d.ts +347 -0
- package/src/lib/template-contract.d.ts.map +1 -0
- package/src/lib/template-contract.js +1 -0
- package/src/lib/template-contract.js.map +1 -0
- package/src/lib/template.d.ts +11 -0
- package/src/lib/template.d.ts.map +1 -0
- package/src/lib/template.js +21 -0
- package/src/lib/template.js.map +1 -0
- package/src/lib/testing.d.ts +157 -0
- package/src/lib/testing.d.ts.map +1 -0
- package/src/lib/testing.js +416 -0
- package/src/lib/testing.js.map +1 -0
- package/src/lib/types.d.ts +442 -0
- package/src/lib/types.d.ts.map +1 -0
- package/src/lib/types.js +22 -0
- package/src/lib/types.js.map +1 -0
- package/testing/public-api.d.ts +3 -0
- package/testing/public-api.d.ts.map +1 -0
- package/testing/public-api.js +3 -0
- package/testing/public-api.js.map +1 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { createEnvironmentInjector, ElementRef, Injector, runInInjectionContext, ɵEffectScheduler, ɵINJECTOR_SCOPE, } from './host-runtime';
|
|
2
|
+
import { CRAFT_SERVICE_PROVIDER_BRAND, createExposedServiceValue, getRegisteredAppStartServices, getServiceMetaData, runServiceAppStart, SERVICE_RUNTIME_OVERRIDES, } from '@craft-ts/core';
|
|
3
|
+
import { mountInterpretedComponent, mountInterpretedComponentTemplate, } from './render/interpreter';
|
|
4
|
+
import { craftComponent } from './component';
|
|
5
|
+
import { executeCraftComponentFactory } from './factory-runtime';
|
|
6
|
+
import { CRAFT_COMPONENT, CRAFT_DIRECTIVE, } from './types';
|
|
7
|
+
function isProvider(value) {
|
|
8
|
+
return (typeof value === 'object' &&
|
|
9
|
+
value !== null &&
|
|
10
|
+
CRAFT_SERVICE_PROVIDER_BRAND in value);
|
|
11
|
+
}
|
|
12
|
+
function createTestingInjector(providers) {
|
|
13
|
+
return createEnvironmentInjector([
|
|
14
|
+
{ provide: ɵINJECTOR_SCOPE, useValue: 'root' },
|
|
15
|
+
...(providers ?? []),
|
|
16
|
+
], Injector.NULL, 'CraftComponentTestRoot');
|
|
17
|
+
}
|
|
18
|
+
function createRuntimeProviders(register) {
|
|
19
|
+
const providers = [];
|
|
20
|
+
const overrides = new Map();
|
|
21
|
+
const mocks = {};
|
|
22
|
+
for (const [name, entry] of Object.entries(register)) {
|
|
23
|
+
if (entry === 'real' || entry === 'provided' || entry === 'notReached') {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (isProvider(entry)) {
|
|
27
|
+
providers.push(entry);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const value = createExposedServiceValue(entry);
|
|
31
|
+
overrides.set(name, { kind: 'useValue', value });
|
|
32
|
+
mocks[name] = value;
|
|
33
|
+
}
|
|
34
|
+
providers.push({
|
|
35
|
+
provide: SERVICE_RUNTIME_OVERRIDES,
|
|
36
|
+
useValue: overrides,
|
|
37
|
+
});
|
|
38
|
+
return { providers, mocks };
|
|
39
|
+
}
|
|
40
|
+
function createComponentInjector(component, parent, host, register) {
|
|
41
|
+
const definition = component[CRAFT_COMPONENT];
|
|
42
|
+
const { providers, mocks } = createRuntimeProviders(register);
|
|
43
|
+
const injector = createEnvironmentInjector([
|
|
44
|
+
...(definition.meta.providers ?? []),
|
|
45
|
+
{ provide: ElementRef, useValue: new ElementRef(host) },
|
|
46
|
+
...providers,
|
|
47
|
+
], parent, 'CraftComponentTest');
|
|
48
|
+
return { injector, mocks };
|
|
49
|
+
}
|
|
50
|
+
function isActiveRegisterEntry(entry) {
|
|
51
|
+
return entry === 'real' || entry === 'provided' || isProvider(entry);
|
|
52
|
+
}
|
|
53
|
+
function isPromiseLike(value) {
|
|
54
|
+
return (typeof value === 'object' &&
|
|
55
|
+
value !== null &&
|
|
56
|
+
'then' in value &&
|
|
57
|
+
typeof value.then === 'function');
|
|
58
|
+
}
|
|
59
|
+
async function waitForAppStart(result) {
|
|
60
|
+
if (isPromiseLike(result)) {
|
|
61
|
+
await result;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (typeof result === 'object' &&
|
|
65
|
+
result !== null &&
|
|
66
|
+
'subscribe' in result &&
|
|
67
|
+
typeof result.subscribe === 'function') {
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
result.subscribe({ error: reject, complete: resolve });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function runConfiguredAppStart(register, appStart, injector) {
|
|
74
|
+
const references = new Map(getRegisteredAppStartServices().map((reference) => {
|
|
75
|
+
const metadata = getServiceMetaData(reference);
|
|
76
|
+
return [metadata.name, reference];
|
|
77
|
+
}));
|
|
78
|
+
const missingDecisions = Array.from(references.keys()).filter((name) => isActiveRegisterEntry(register[name]) && appStart?.[name] === undefined);
|
|
79
|
+
if (missingDecisions.length > 0) {
|
|
80
|
+
throw new Error(`Craft testing requires appStart decisions for: ${missingDecisions.join(', ')}.`);
|
|
81
|
+
}
|
|
82
|
+
for (const [name, decision] of Object.entries(appStart ?? {})) {
|
|
83
|
+
if (decision !== 'run' && decision !== 'ignore') {
|
|
84
|
+
throw new Error(`Invalid appStart decision for "${name}". Expected "run" or "ignore".`);
|
|
85
|
+
}
|
|
86
|
+
const entry = register[name];
|
|
87
|
+
if (!isActiveRegisterEntry(entry) || decision === 'ignore') {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const reference = references.get(name);
|
|
91
|
+
if (!reference) {
|
|
92
|
+
throw new Error(`Register entry "${name}" is not a craftService configured with appStart: true.`);
|
|
93
|
+
}
|
|
94
|
+
await runInInjectionContext(injector, async () => {
|
|
95
|
+
const metadata = getServiceMetaData(reference);
|
|
96
|
+
const value = metadata.inject();
|
|
97
|
+
await waitForAppStart(runServiceAppStart(reference, value));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function componentTemplateMount(component, context, register, providers) {
|
|
102
|
+
const runtime = createRuntimeProviders(register);
|
|
103
|
+
const parent = createTestingInjector([
|
|
104
|
+
...(providers ?? []),
|
|
105
|
+
...runtime.providers,
|
|
106
|
+
]);
|
|
107
|
+
const host = document.createElement('div');
|
|
108
|
+
document.body.append(host);
|
|
109
|
+
const mounted = mountInterpretedComponentTemplate(component, host, parent, context, runtime.providers);
|
|
110
|
+
return { host, mounted, mocks: runtime.mocks, parent };
|
|
111
|
+
}
|
|
112
|
+
async function setupCraftComponentLogicTestImpl(component, options) {
|
|
113
|
+
const parent = createTestingInjector(options.providers);
|
|
114
|
+
const host = document.createElement('div');
|
|
115
|
+
const definition = component[CRAFT_COMPONENT];
|
|
116
|
+
const { injector, mocks } = createComponentInjector(component, parent, host, options.register);
|
|
117
|
+
const factory = definition.factory;
|
|
118
|
+
const args = (options.args ?? []);
|
|
119
|
+
const context = runInInjectionContext(injector, () => executeCraftComponentFactory(factory, args, injector));
|
|
120
|
+
if (isPromiseLike(context)) {
|
|
121
|
+
throw new Error('Async component factories are not supported by setupCraftComponentLogicTest.');
|
|
122
|
+
}
|
|
123
|
+
await runConfiguredAppStart(options.register, options.appStart, injector);
|
|
124
|
+
return {
|
|
125
|
+
context,
|
|
126
|
+
mocks: mocks,
|
|
127
|
+
injector,
|
|
128
|
+
destroy: () => {
|
|
129
|
+
injector.destroy();
|
|
130
|
+
parent.destroy();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export const setupCraftComponentLogicTest = Object.assign(setupCraftComponentLogicTestImpl, { byRegister: setupCraftComponentLogicTestImpl });
|
|
135
|
+
function matchesName(actual, expected) {
|
|
136
|
+
if (expected === undefined)
|
|
137
|
+
return true;
|
|
138
|
+
if (typeof expected === 'string') {
|
|
139
|
+
return actual === expected;
|
|
140
|
+
}
|
|
141
|
+
const lastIndex = expected.lastIndex;
|
|
142
|
+
try {
|
|
143
|
+
expected.lastIndex = 0;
|
|
144
|
+
return expected.test(actual);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
expected.lastIndex = lastIndex;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function findById(root, id) {
|
|
151
|
+
return Array.from(root.querySelectorAll('[id]')).find((element) => element.id === id);
|
|
152
|
+
}
|
|
153
|
+
function findLabelFor(root, id) {
|
|
154
|
+
return Array.from(root.querySelectorAll('label')).find((label) => label.htmlFor === id);
|
|
155
|
+
}
|
|
156
|
+
function accessibleName(element, root) {
|
|
157
|
+
const labelledBy = element.getAttribute('aria-labelledby');
|
|
158
|
+
if (labelledBy) {
|
|
159
|
+
return labelledBy
|
|
160
|
+
.split(/\s+/)
|
|
161
|
+
.map((id) => findById(root, id)?.textContent ?? '')
|
|
162
|
+
.join(' ')
|
|
163
|
+
.trim();
|
|
164
|
+
}
|
|
165
|
+
const ariaLabel = element.getAttribute('aria-label');
|
|
166
|
+
if (ariaLabel)
|
|
167
|
+
return ariaLabel.trim();
|
|
168
|
+
if (element instanceof HTMLInputElement ||
|
|
169
|
+
element instanceof HTMLTextAreaElement ||
|
|
170
|
+
element instanceof HTMLSelectElement ||
|
|
171
|
+
element instanceof HTMLButtonElement) {
|
|
172
|
+
if (element.id) {
|
|
173
|
+
const label = findLabelFor(root, element.id);
|
|
174
|
+
if (label)
|
|
175
|
+
return (label.textContent ?? '').trim();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return (element.textContent ?? '').trim();
|
|
179
|
+
}
|
|
180
|
+
function implicitRole(element) {
|
|
181
|
+
const explicit = element.getAttribute('role');
|
|
182
|
+
if (explicit)
|
|
183
|
+
return explicit;
|
|
184
|
+
const tag = element.tagName.toLowerCase();
|
|
185
|
+
if (tag === 'button')
|
|
186
|
+
return 'button';
|
|
187
|
+
if (tag === 'a' && element.hasAttribute('href'))
|
|
188
|
+
return 'link';
|
|
189
|
+
if (tag === 'textarea')
|
|
190
|
+
return 'textbox';
|
|
191
|
+
if (tag === 'input') {
|
|
192
|
+
const type = element.type;
|
|
193
|
+
if (type === 'checkbox')
|
|
194
|
+
return 'checkbox';
|
|
195
|
+
if (type === 'radio')
|
|
196
|
+
return 'radio';
|
|
197
|
+
if (type === 'submit' || type === 'button' || type === 'reset')
|
|
198
|
+
return 'button';
|
|
199
|
+
return 'textbox';
|
|
200
|
+
}
|
|
201
|
+
if (tag === 'img')
|
|
202
|
+
return 'img';
|
|
203
|
+
if (tag === 'nav')
|
|
204
|
+
return 'navigation';
|
|
205
|
+
if (tag === 'main')
|
|
206
|
+
return 'main';
|
|
207
|
+
if (/^h[1-6]$/.test(tag))
|
|
208
|
+
return 'heading';
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function queryAllByRole(root, role, options) {
|
|
212
|
+
return Array.from(root.querySelectorAll('*')).filter((node) => {
|
|
213
|
+
if (implicitRole(node) !== role)
|
|
214
|
+
return false;
|
|
215
|
+
return matchesName(accessibleName(node, root), options?.name);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function requireSingle(elements, query, allowEmpty) {
|
|
219
|
+
if (elements.length > 1) {
|
|
220
|
+
throw new Error(`Found ${elements.length} elements with ${query}`);
|
|
221
|
+
}
|
|
222
|
+
if (elements.length === 0) {
|
|
223
|
+
if (allowEmpty)
|
|
224
|
+
return undefined;
|
|
225
|
+
throw new Error(`Unable to find ${query}`);
|
|
226
|
+
}
|
|
227
|
+
return elements[0];
|
|
228
|
+
}
|
|
229
|
+
function createAccessibleQueries(root) {
|
|
230
|
+
return {
|
|
231
|
+
getByRole(role, options) {
|
|
232
|
+
const query = `role "${role}"${options?.name === undefined
|
|
233
|
+
? ''
|
|
234
|
+
: ` with name "${String(options.name)}"`}`;
|
|
235
|
+
return requireSingle(queryAllByRole(root, role, options), query, false);
|
|
236
|
+
},
|
|
237
|
+
queryByRole(role, options) {
|
|
238
|
+
const query = `role "${role}"${options?.name === undefined
|
|
239
|
+
? ''
|
|
240
|
+
: ` with name "${String(options.name)}"`}`;
|
|
241
|
+
return requireSingle(queryAllByRole(root, role, options), query, true);
|
|
242
|
+
},
|
|
243
|
+
getByLabel(name) {
|
|
244
|
+
const labelled = Array.from(root.querySelectorAll('input, textarea, select, button')).filter((node) => matchesName(accessibleName(node, root), name));
|
|
245
|
+
return requireSingle(labelled, `label "${String(name)}"`, false);
|
|
246
|
+
},
|
|
247
|
+
queryByLabel(name) {
|
|
248
|
+
const labelled = Array.from(root.querySelectorAll('input, textarea, select, button')).filter((node) => matchesName(accessibleName(node, root), name));
|
|
249
|
+
return requireSingle(labelled, `label "${String(name)}"`, true);
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async function setupCraftComponentTemplateTestImpl(component, options) {
|
|
254
|
+
const { host, mounted, mocks, parent } = componentTemplateMount(component, options.context, options.register, options.providers);
|
|
255
|
+
const detectChanges = () => parent.get(ɵEffectScheduler).flush();
|
|
256
|
+
if (options.detectChanges ?? true) {
|
|
257
|
+
detectChanges();
|
|
258
|
+
}
|
|
259
|
+
const locator = (tag, criteria) => mounted.locator(tag, criteria);
|
|
260
|
+
return {
|
|
261
|
+
nativeElement: host,
|
|
262
|
+
element: host,
|
|
263
|
+
mocks: mocks,
|
|
264
|
+
...createAccessibleQueries(host),
|
|
265
|
+
detectChanges,
|
|
266
|
+
updateContext(context) {
|
|
267
|
+
mounted.updateContext(context);
|
|
268
|
+
},
|
|
269
|
+
locator,
|
|
270
|
+
toBeAccessible: () => assertAccessible(host),
|
|
271
|
+
destroy() {
|
|
272
|
+
mounted.destroy();
|
|
273
|
+
host.remove();
|
|
274
|
+
parent.destroy();
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export const setupCraftComponentTemplateTest = Object.assign(setupCraftComponentTemplateTestImpl, { byRegister: setupCraftComponentTemplateTestImpl });
|
|
279
|
+
export async function renderCraftComponent(component, options = {}) {
|
|
280
|
+
const parent = createTestingInjector(options.providers);
|
|
281
|
+
const host = document.createElement('div');
|
|
282
|
+
document.body.append(host);
|
|
283
|
+
const mounted = mountInterpretedComponent(component, host, parent, (options.props ?? {}));
|
|
284
|
+
const detectChanges = () => parent.get(ɵEffectScheduler).flush();
|
|
285
|
+
let destroyed = false;
|
|
286
|
+
const flush = async () => {
|
|
287
|
+
if (destroyed)
|
|
288
|
+
return;
|
|
289
|
+
for (let index = 0; index < 5; index += 1) {
|
|
290
|
+
detectChanges();
|
|
291
|
+
await Promise.resolve();
|
|
292
|
+
}
|
|
293
|
+
if (!destroyed) {
|
|
294
|
+
detectChanges();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
await flush();
|
|
298
|
+
return {
|
|
299
|
+
nativeElement: host,
|
|
300
|
+
element: host,
|
|
301
|
+
mounted,
|
|
302
|
+
// The injector the component was mounted under, so a test can reach the
|
|
303
|
+
// services its own `providers` installed.
|
|
304
|
+
injector: parent,
|
|
305
|
+
detectChanges,
|
|
306
|
+
flush,
|
|
307
|
+
destroy() {
|
|
308
|
+
destroyed = true;
|
|
309
|
+
mounted.destroy();
|
|
310
|
+
host.remove();
|
|
311
|
+
parent.destroy();
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async function setupCraftDirectiveLogicTestImpl(directive, options) {
|
|
316
|
+
const parent = createTestingInjector(options.providers);
|
|
317
|
+
const host = document.createElement('div');
|
|
318
|
+
const { injector, mocks } = createRuntimeDirectiveInjector(parent, host, options.register);
|
|
319
|
+
const definition = directive[CRAFT_DIRECTIVE];
|
|
320
|
+
const logic = definition.logic(options.baseLogic);
|
|
321
|
+
const context = runInInjectionContext(injector, () => executeCraftComponentFactory(logic, (options.args ?? []), injector));
|
|
322
|
+
if (isPromiseLike(context)) {
|
|
323
|
+
throw new Error('Async directive factories are not supported by setupCraftDirectiveLogicTest.');
|
|
324
|
+
}
|
|
325
|
+
await runConfiguredAppStart(options.register, options.appStart, injector);
|
|
326
|
+
return {
|
|
327
|
+
context,
|
|
328
|
+
mocks: mocks,
|
|
329
|
+
injector,
|
|
330
|
+
destroy: () => {
|
|
331
|
+
injector.destroy();
|
|
332
|
+
parent.destroy();
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function createRuntimeDirectiveInjector(parent, host, register) {
|
|
337
|
+
const { providers, mocks } = createRuntimeProviders(register);
|
|
338
|
+
return {
|
|
339
|
+
injector: createEnvironmentInjector([{ provide: ElementRef, useValue: new ElementRef(host) }, ...providers], parent, 'CraftDirectiveTest'),
|
|
340
|
+
mocks,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
export const setupCraftDirectiveLogicTest = Object.assign(setupCraftDirectiveLogicTestImpl, { byRegister: setupCraftDirectiveLogicTestImpl });
|
|
344
|
+
async function setupCraftDirectiveTemplateTestImpl(directive, options) {
|
|
345
|
+
const definition = directive[CRAFT_DIRECTIVE];
|
|
346
|
+
const decoratedTemplate = definition.template(options.baseTemplate);
|
|
347
|
+
const synthetic = craftComponent(`CraftDirectiveTemplate:${definition.name}`, {
|
|
348
|
+
styles: definition.meta.styles,
|
|
349
|
+
stylesUrl: definition.meta.stylesUrl,
|
|
350
|
+
}, () => ({}), decoratedTemplate);
|
|
351
|
+
const { host, mounted, mocks, parent } = componentTemplateMount(synthetic, options.context, options.register, options.providers);
|
|
352
|
+
const detectChanges = () => parent.get(ɵEffectScheduler).flush();
|
|
353
|
+
if (options.detectChanges ?? true) {
|
|
354
|
+
detectChanges();
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
nativeElement: host,
|
|
358
|
+
element: host,
|
|
359
|
+
mocks: mocks,
|
|
360
|
+
...createAccessibleQueries(host),
|
|
361
|
+
detectChanges,
|
|
362
|
+
updateContext(context) {
|
|
363
|
+
mounted.updateContext(context);
|
|
364
|
+
},
|
|
365
|
+
destroy() {
|
|
366
|
+
mounted.destroy();
|
|
367
|
+
host.remove();
|
|
368
|
+
parent.destroy();
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
export const setupCraftDirectiveTemplateTest = Object.assign(setupCraftDirectiveTemplateTestImpl, { byRegister: setupCraftDirectiveTemplateTestImpl });
|
|
373
|
+
/**
|
|
374
|
+
* WCAG 2.2 AA smoke checks on a mounted Craft tree. Complements axe/AccessLint
|
|
375
|
+
* in application CI: images have alt, controls have a name, tabindex is not
|
|
376
|
+
* positive, ARIA attributes are known.
|
|
377
|
+
*/
|
|
378
|
+
export async function assertAccessible(container) {
|
|
379
|
+
const violations = [];
|
|
380
|
+
container.querySelectorAll('img').forEach((image) => {
|
|
381
|
+
if (!image.hasAttribute('alt')) {
|
|
382
|
+
violations.push(`<img src="${image.getAttribute('src') ?? ''}"> is missing alt`);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
container
|
|
386
|
+
.querySelectorAll('button, a, [role="button"]')
|
|
387
|
+
.forEach((control) => {
|
|
388
|
+
const name = (control.getAttribute('aria-label') ||
|
|
389
|
+
control.getAttribute('aria-labelledby') ||
|
|
390
|
+
control.textContent ||
|
|
391
|
+
'').trim();
|
|
392
|
+
if (!name &&
|
|
393
|
+
control.getAttribute('href') !== null &&
|
|
394
|
+
!(control.textContent ?? '').trim()) {
|
|
395
|
+
violations.push(`<${control.tagName.toLowerCase()}> has no accessible name`);
|
|
396
|
+
}
|
|
397
|
+
else if (!name && control.tagName === 'BUTTON') {
|
|
398
|
+
violations.push('<button> has no accessible name');
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
container.querySelectorAll('[tabindex]').forEach((node) => {
|
|
402
|
+
const value = Number(node.getAttribute('tabindex'));
|
|
403
|
+
if (Number.isFinite(value) && value > 0) {
|
|
404
|
+
violations.push(`${node.tagName.toLowerCase()} has tabIndex=${value}`);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
container.querySelectorAll('iframe').forEach((frame) => {
|
|
408
|
+
if (!frame.getAttribute('title')) {
|
|
409
|
+
violations.push('<iframe> is missing title');
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
if (violations.length > 0) {
|
|
413
|
+
throw new Error(`Accessibility violations:\n- ${violations.join('\n- ')}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../../../../libs/component/src/lib/testing.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createEnvironmentInjector,\n ElementRef,\n EnvironmentInjector,\n Injector,\n runInInjectionContext,\n ɵEffectScheduler,\n ɵINJECTOR_SCOPE,\n} from './host-runtime';\nimport {\n CRAFT_SERVICE_PROVIDER_BRAND,\n createExposedServiceValue,\n getRegisteredAppStartServices,\n getServiceMetaData,\n runServiceAppStart,\n SERVICE_RUNTIME_OVERRIDES,\n type CraftServiceProvider,\n type CompleteServiceDependencyMapFromYielded,\n type FlattenDependencyTree,\n} from '@craft-ts/core';\nimport {\n mountInterpretedComponent,\n mountInterpretedComponentTemplate,\n} from './render/interpreter';\nimport { craftComponent } from './component';\nimport { executeCraftComponentFactory } from './factory-runtime';\nimport {\n CRAFT_COMPONENT,\n CRAFT_DIRECTIVE,\n type ComponentFactory,\n type ComponentTemplate,\n type CraftComponent,\n type CraftDirective,\n type FactoryContext,\n type FactoryYielded,\n type PropsOf,\n type TemplateDependencies,\n} from './types';\nimport type { CraftTemplateLocatorApi } from './locator';\n\ntype IsAny<T> = 0 extends 1 & T ? true : false;\n\ntype ComponentFactoryOf<Component> =\n Component extends CraftComponent<\n any,\n any,\n infer Factory extends ComponentFactory\n >\n ? Factory\n : ComponentFactory;\n\ntype ComponentContextOf<Component> = FactoryContext<\n ComponentFactoryOf<Component>\n>;\n\ntype ComponentTemplateDepsOf<Component> =\n Component extends CraftComponent<any, any, any, any, any, infer TemplateDeps>\n ? TemplateDeps\n : {};\n\ntype LogicDependenciesOf<Component> =\n Component extends CraftComponent<any, infer Dependencies extends object>\n ? Omit<Dependencies, 'missingProvider'> & { missingProvider: {} }\n : {};\n\ntype ServiceNamesFromNodeMap<Dependencies extends object> =\n IsAny<Dependencies> extends true\n ? never\n : string extends keyof Dependencies\n ? never\n : Extract<keyof FlattenDependencyTree<Dependencies>, string>;\n\ntype ServiceNamesFromProperties<Properties extends object> = {\n [Name in Extract<keyof Properties, string>]: Properties[Name] extends object\n ? ServiceNamesFromNodeMap<Properties[Name]>\n : never;\n}[Extract<keyof Properties, string>];\n\ntype ServiceNamesFromContract<Contract> = Contract extends object\n ?\n | (Contract extends { deps: infer Dependencies extends object }\n ? ServiceNamesFromNodeMap<Dependencies>\n : never)\n | (Contract extends {\n propertiesDeps: infer Properties extends object;\n }\n ? ServiceNamesFromProperties<Properties>\n : never)\n | (Contract extends {\n missingProvider: infer Missing extends object;\n }\n ? ServiceNamesFromNodeMap<Missing>\n : never)\n : never;\n\n/** The dependency contract used when only a Craft component factory is tested. */\nexport type CraftComponentLogicDepsOf<Component> =\n LogicDependenciesOf<Component>;\n\n/** The service dependency contract used when only a Craft component template is tested. */\nexport type CraftComponentTemplateDepsOf<Component> = {\n deps: Record<\n Extract<\n ServiceNamesFromContract<ComponentTemplateDepsOf<Component>>,\n string\n >,\n unknown\n >;\n propertiesDeps: {};\n provided: {};\n publicProperties: {};\n missingProvider: {};\n};\n\ntype DirectiveLogicFactory<Directive> =\n Directive extends CraftDirective<infer Logic, any, any>\n ? ReturnType<Logic> extends ComponentFactory\n ? ReturnType<Logic>\n : ComponentFactory\n : ComponentFactory;\n\ntype LogicDependenciesForFactory<Factory extends ComponentFactory> = {\n deps: CompleteServiceDependencyMapFromYielded<FactoryYielded<Factory>>;\n propertiesDeps: {};\n provided: {};\n publicProperties: {};\n missingProvider: {};\n};\n\ntype TemplateContractForTemplate<Template extends ComponentTemplate<any>> = {\n deps: Record<\n Extract<ServiceNamesFromContract<TemplateDependencies<Template>>, string>,\n unknown\n >;\n};\n\n/** The dependency contract added by a directive's logic decorator. */\nexport type CraftDirectiveLogicDepsOf<Directive> = LogicDependenciesForFactory<\n DirectiveLogicFactory<Directive>\n>;\n\ntype DirectiveTemplateDependenciesOf<Directive> =\n Directive extends CraftDirective<any, infer Template, any>\n ? Template extends (baseTemplate: ComponentTemplate<any>) => infer Decorated\n ? TemplateDependencies<Decorated>\n : {}\n : {};\n\n/** The service dependencies used by a directive's template decorator. */\nexport type CraftDirectiveTemplateDepsOf<Directive> = {\n deps: Record<\n Extract<\n ServiceNamesFromContract<DirectiveTemplateDependenciesOf<Directive>>,\n string\n >,\n unknown\n >;\n propertiesDeps: {};\n provided: {};\n publicProperties: {};\n missingProvider: {};\n};\n\ntype RegisterValue =\n | 'real'\n | 'provided'\n | 'notReached'\n | CraftServiceProvider\n | Record<string, unknown>;\n\ntype RegisterForNames<Names extends string> = {\n [Name in Names]: RegisterValue;\n};\n\ntype RegisterForContract<Contract> = RegisterForNames<\n Extract<ServiceNamesFromContract<Contract>, string>\n>;\n\ntype AppStartDecision = 'run' | 'ignore';\n\ntype TestBedOptions = {\n providers?: CraftServiceProvider[];\n appStart?: Record<string, AppStartDecision>;\n};\n\ntype LogicOptions<\n Factory extends ComponentFactory,\n Contract,\n> = TestBedOptions & {\n register: RegisterForContract<Contract>;\n args?: Parameters<Factory>;\n};\n\ntype TemplateOptions<Context, Contract> = TestBedOptions & {\n context: Context;\n register: RegisterForContract<Contract>;\n detectChanges?: boolean;\n};\n\ntype RegisterRuntimeEntry =\n | RegisterValue\n | { readonly [CRAFT_SERVICE_PROVIDER_BRAND]?: unknown };\n\nfunction isProvider(value: unknown): value is CraftServiceProvider {\n return (\n typeof value === 'object' &&\n value !== null &&\n CRAFT_SERVICE_PROVIDER_BRAND in value\n );\n}\n\nfunction createTestingInjector(\n providers: readonly CraftServiceProvider[] | undefined,\n) {\n return createEnvironmentInjector(\n [\n { provide: ɵINJECTOR_SCOPE, useValue: 'root' },\n ...(providers ?? []),\n ],\n Injector.NULL as EnvironmentInjector,\n 'CraftComponentTestRoot',\n );\n}\n\nfunction createRuntimeProviders(\n register: Record<string, RegisterRuntimeEntry>,\n): { providers: CraftServiceProvider[]; mocks: Record<string, unknown> } {\n const providers: CraftServiceProvider[] = [];\n const overrides = new Map<string, { kind: 'useValue'; value: unknown }>();\n const mocks: Record<string, unknown> = {};\n\n for (const [name, entry] of Object.entries(register)) {\n if (entry === 'real' || entry === 'provided' || entry === 'notReached') {\n continue;\n }\n\n if (isProvider(entry)) {\n providers.push(entry);\n continue;\n }\n\n const value = createExposedServiceValue(entry);\n overrides.set(name, { kind: 'useValue', value });\n mocks[name] = value;\n }\n\n providers.push({\n provide: SERVICE_RUNTIME_OVERRIDES,\n useValue: overrides,\n });\n\n return { providers, mocks };\n}\n\nfunction createComponentInjector(\n component: CraftComponent<any>,\n parent: EnvironmentInjector,\n host: Element,\n register: Record<string, RegisterRuntimeEntry>,\n): { injector: EnvironmentInjector; mocks: Record<string, unknown> } {\n const definition = component[CRAFT_COMPONENT];\n const { providers, mocks } = createRuntimeProviders(register);\n const injector = createEnvironmentInjector(\n [\n ...(definition.meta.providers ?? []),\n { provide: ElementRef, useValue: new ElementRef(host) },\n ...providers,\n ],\n parent,\n 'CraftComponentTest',\n );\n\n return { injector, mocks };\n}\n\nfunction isActiveRegisterEntry(entry: RegisterRuntimeEntry | undefined) {\n return entry === 'real' || entry === 'provided' || isProvider(entry);\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n );\n}\n\nasync function waitForAppStart(result: unknown): Promise<void> {\n if (isPromiseLike(result)) {\n await result;\n return;\n }\n\n if (\n typeof result === 'object' &&\n result !== null &&\n 'subscribe' in result &&\n typeof result.subscribe === 'function'\n ) {\n await new Promise<void>((resolve, reject) => {\n (\n result as {\n subscribe: (observer: {\n error: (error: unknown) => void;\n complete: () => void;\n }) => unknown;\n }\n ).subscribe({ error: reject, complete: resolve });\n });\n }\n}\n\nasync function runConfiguredAppStart(\n register: Record<string, RegisterRuntimeEntry>,\n appStart: Record<string, AppStartDecision> | undefined,\n injector: Injector,\n): Promise<void> {\n const references = new Map(\n getRegisteredAppStartServices().map((reference) => {\n const metadata = getServiceMetaData(reference);\n return [metadata.name, reference] as const;\n }),\n );\n\n const missingDecisions = Array.from(references.keys()).filter(\n (name) =>\n isActiveRegisterEntry(register[name]) && appStart?.[name] === undefined,\n );\n if (missingDecisions.length > 0) {\n throw new Error(\n `Craft testing requires appStart decisions for: ${missingDecisions.join(', ')}.`,\n );\n }\n\n for (const [name, decision] of Object.entries(appStart ?? {})) {\n if (decision !== 'run' && decision !== 'ignore') {\n throw new Error(\n `Invalid appStart decision for \"${name}\". Expected \"run\" or \"ignore\".`,\n );\n }\n\n const entry = register[name];\n if (!isActiveRegisterEntry(entry) || decision === 'ignore') {\n continue;\n }\n\n const reference = references.get(name);\n if (!reference) {\n throw new Error(\n `Register entry \"${name}\" is not a craftService configured with appStart: true.`,\n );\n }\n\n await runInInjectionContext(injector, async () => {\n const metadata = getServiceMetaData(reference);\n const value = metadata.inject();\n await waitForAppStart(runServiceAppStart(reference, value));\n });\n }\n}\n\nfunction componentTemplateMount<Component extends CraftComponent<any>, Context>(\n component: Component,\n context: Context,\n register: Record<string, RegisterRuntimeEntry>,\n providers: readonly CraftServiceProvider[] | undefined,\n) {\n const runtime = createRuntimeProviders(register);\n const parent = createTestingInjector([\n ...(providers ?? []),\n ...runtime.providers,\n ]);\n const host = document.createElement('div');\n document.body.append(host);\n const mounted = mountInterpretedComponentTemplate(\n component,\n host,\n parent,\n context,\n runtime.providers,\n );\n\n return { host, mounted, mocks: runtime.mocks, parent };\n}\n\nasync function setupCraftComponentLogicTestImpl<\n Component extends CraftComponent<any>,\n const Contract extends\n CraftComponentLogicDepsOf<Component> = CraftComponentLogicDepsOf<Component>,\n>(\n component: Component,\n options: LogicOptions<ComponentFactoryOf<Component>, Contract>,\n) {\n const parent = createTestingInjector(options.providers);\n const host = document.createElement('div');\n const definition = component[CRAFT_COMPONENT];\n const { injector, mocks } = createComponentInjector(\n component,\n parent,\n host,\n options.register as Record<string, RegisterRuntimeEntry>,\n );\n const factory = definition.factory;\n const args = (options.args ?? []) as Parameters<typeof factory>;\n const context = runInInjectionContext(injector, () =>\n executeCraftComponentFactory(factory, args, injector),\n ) as ComponentContextOf<Component>;\n\n if (isPromiseLike(context)) {\n throw new Error(\n 'Async component factories are not supported by setupCraftComponentLogicTest.',\n );\n }\n\n await runConfiguredAppStart(\n options.register as Record<string, RegisterRuntimeEntry>,\n options.appStart,\n injector,\n );\n\n return {\n context,\n mocks: mocks as Record<string, unknown>,\n injector,\n destroy: () => {\n injector.destroy();\n parent.destroy();\n },\n };\n}\n\nexport const setupCraftComponentLogicTest = Object.assign(\n setupCraftComponentLogicTestImpl,\n { byRegister: setupCraftComponentLogicTestImpl },\n);\n\ntype TemplateTestResult<\n Component extends CraftComponent<any>,\n Context,\n> = CraftTemplateLocatorApi<Component> & {\n nativeElement: HTMLDivElement;\n element: HTMLDivElement;\n mocks: Record<string, unknown>;\n detectChanges(): void;\n updateContext(context: Context): void;\n destroy(): void;\n toBeAccessible(): Promise<void>;\n getByRole(role: string, options?: { name?: string | RegExp }): HTMLElement;\n queryByRole(\n role: string,\n options?: { name?: string | RegExp },\n ): HTMLElement | undefined;\n getByLabel(name: string | RegExp): HTMLElement;\n queryByLabel(name: string | RegExp): HTMLElement | undefined;\n};\n\nfunction matchesName(\n actual: string,\n expected: string | RegExp | undefined,\n): boolean {\n if (expected === undefined) return true;\n if (typeof expected === 'string') {\n return actual === expected;\n }\n const lastIndex = expected.lastIndex;\n try {\n expected.lastIndex = 0;\n return expected.test(actual);\n } finally {\n expected.lastIndex = lastIndex;\n }\n}\n\nfunction findById(root: ParentNode, id: string): Element | undefined {\n return Array.from(root.querySelectorAll<HTMLElement>('[id]')).find(\n (element) => element.id === id,\n );\n}\n\nfunction findLabelFor(root: ParentNode, id: string): Element | undefined {\n return Array.from(root.querySelectorAll<HTMLLabelElement>('label')).find(\n (label) => label.htmlFor === id,\n );\n}\n\nfunction accessibleName(element: Element, root: ParentNode): string {\n const labelledBy = element.getAttribute('aria-labelledby');\n if (labelledBy) {\n return labelledBy\n .split(/\\s+/)\n .map((id) => findById(root, id)?.textContent ?? '')\n .join(' ')\n .trim();\n }\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel) return ariaLabel.trim();\n if (\n element instanceof HTMLInputElement ||\n element instanceof HTMLTextAreaElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLButtonElement\n ) {\n if (element.id) {\n const label = findLabelFor(root, element.id);\n if (label) return (label.textContent ?? '').trim();\n }\n }\n return (element.textContent ?? '').trim();\n}\n\nfunction implicitRole(element: Element): string | null {\n const explicit = element.getAttribute('role');\n if (explicit) return explicit;\n const tag = element.tagName.toLowerCase();\n if (tag === 'button') return 'button';\n if (tag === 'a' && element.hasAttribute('href')) return 'link';\n if (tag === 'textarea') return 'textbox';\n if (tag === 'input') {\n const type = (element as HTMLInputElement).type;\n if (type === 'checkbox') return 'checkbox';\n if (type === 'radio') return 'radio';\n if (type === 'submit' || type === 'button' || type === 'reset')\n return 'button';\n return 'textbox';\n }\n if (tag === 'img') return 'img';\n if (tag === 'nav') return 'navigation';\n if (tag === 'main') return 'main';\n if (/^h[1-6]$/.test(tag)) return 'heading';\n return null;\n}\n\nfunction queryAllByRole(\n root: Element,\n role: string,\n options?: { name?: string | RegExp },\n): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>('*')).filter((node) => {\n if (implicitRole(node) !== role) return false;\n return matchesName(accessibleName(node, root), options?.name);\n });\n}\n\nfunction requireSingle(\n elements: HTMLElement[],\n query: string,\n allowEmpty: boolean,\n): HTMLElement | undefined {\n if (elements.length > 1) {\n throw new Error(`Found ${elements.length} elements with ${query}`);\n }\n if (elements.length === 0) {\n if (allowEmpty) return undefined;\n throw new Error(`Unable to find ${query}`);\n }\n return elements[0];\n}\n\nfunction createAccessibleQueries(root: HTMLElement) {\n return {\n getByRole(role: string, options?: { name?: string | RegExp }) {\n const query = `role \"${role}\"${\n options?.name === undefined\n ? ''\n : ` with name \"${String(options.name)}\"`\n }`;\n return requireSingle(queryAllByRole(root, role, options), query, false)!;\n },\n queryByRole(role: string, options?: { name?: string | RegExp }) {\n const query = `role \"${role}\"${\n options?.name === undefined\n ? ''\n : ` with name \"${String(options.name)}\"`\n }`;\n return requireSingle(queryAllByRole(root, role, options), query, true);\n },\n getByLabel(name: string | RegExp) {\n const labelled = Array.from(\n root.querySelectorAll<HTMLElement>('input, textarea, select, button'),\n ).filter((node) => matchesName(accessibleName(node, root), name));\n return requireSingle(labelled, `label \"${String(name)}\"`, false)!;\n },\n queryByLabel(name: string | RegExp) {\n const labelled = Array.from(\n root.querySelectorAll<HTMLElement>('input, textarea, select, button'),\n ).filter((node) => matchesName(accessibleName(node, root), name));\n return requireSingle(labelled, `label \"${String(name)}\"`, true);\n },\n };\n}\n\nfunction setupCraftComponentTemplateTestImpl<\n Component extends CraftComponent<any>,\n const Contract extends\n CraftComponentTemplateDepsOf<Component> = CraftComponentTemplateDepsOf<Component>,\n Context = unknown,\n>(\n component: Component,\n options: TemplateOptions<Context, Contract>,\n): Promise<TemplateTestResult<Component, Context>>;\n\nasync function setupCraftComponentTemplateTestImpl<\n Component extends CraftComponent<any>,\n const Contract extends\n CraftComponentTemplateDepsOf<Component> = CraftComponentTemplateDepsOf<Component>,\n Context = unknown,\n>(component: Component, options: TemplateOptions<Context, Contract>) {\n const { host, mounted, mocks, parent } = componentTemplateMount(\n component,\n options.context,\n options.register as Record<string, RegisterRuntimeEntry>,\n options.providers,\n );\n\n const detectChanges = () => parent.get(ɵEffectScheduler).flush();\n if (options.detectChanges ?? true) {\n detectChanges();\n }\n\n const locator: CraftTemplateLocatorApi<Component>['locator'] = (\n tag: keyof HTMLElementTagNameMap,\n criteria: Readonly<Record<string, unknown>>,\n ) =>\n mounted.locator(\n tag,\n criteria as Readonly<Record<string, unknown>>,\n ) as never;\n\n return {\n nativeElement: host,\n element: host,\n mocks: mocks as Record<string, unknown>,\n ...createAccessibleQueries(host),\n detectChanges,\n updateContext(context: Context) {\n mounted.updateContext(context);\n },\n locator,\n toBeAccessible: () => assertAccessible(host),\n destroy() {\n mounted.destroy();\n host.remove();\n parent.destroy();\n },\n };\n}\n\nexport const setupCraftComponentTemplateTest = Object.assign(\n setupCraftComponentTemplateTestImpl,\n { byRegister: setupCraftComponentTemplateTestImpl },\n);\n\nexport async function renderCraftComponent<\n Component extends CraftComponent<any>,\n>(\n component: Component,\n options: {\n props?: PropsOf<Component>;\n providers?: CraftServiceProvider[];\n } = {},\n) {\n const parent = createTestingInjector(options.providers);\n const host = document.createElement('div');\n document.body.append(host);\n const mounted = mountInterpretedComponent(\n component as never,\n host,\n parent,\n (options.props ?? {}) as PropsOf<Component>,\n );\n const detectChanges = () => parent.get(ɵEffectScheduler).flush();\n let destroyed = false;\n const flush = async () => {\n if (destroyed) return;\n for (let index = 0; index < 5; index += 1) {\n detectChanges();\n await Promise.resolve();\n }\n if (!destroyed) {\n detectChanges();\n }\n };\n await flush();\n return {\n nativeElement: host,\n element: host,\n mounted,\n // The injector the component was mounted under, so a test can reach the\n // services its own `providers` installed.\n injector: parent,\n detectChanges,\n flush,\n destroy() {\n destroyed = true;\n mounted.destroy();\n host.remove();\n parent.destroy();\n },\n };\n}\n\ntype DirectiveLogicOptions<\n BaseLogic extends ComponentFactory,\n Contract,\n> = LogicOptions<BaseLogic, Contract> & {\n baseLogic: BaseLogic;\n};\n\nasync function setupCraftDirectiveLogicTestImpl<\n Directive extends CraftDirective,\n const BaseLogic extends ComponentFactory,\n const Contract extends CraftDirectiveLogicDepsOf<Directive> &\n LogicDependenciesForFactory<BaseLogic> = CraftDirectiveLogicDepsOf<Directive> &\n LogicDependenciesForFactory<BaseLogic>,\n>(directive: Directive, options: DirectiveLogicOptions<BaseLogic, Contract>) {\n const parent = createTestingInjector(options.providers);\n const host = document.createElement('div');\n const { injector, mocks } = createRuntimeDirectiveInjector(\n parent,\n host,\n options.register as Record<string, RegisterRuntimeEntry>,\n );\n const definition = directive[CRAFT_DIRECTIVE];\n const logic = definition.logic(options.baseLogic);\n const context = runInInjectionContext(injector, () =>\n executeCraftComponentFactory(\n logic,\n (options.args ?? []) as Parameters<typeof logic>,\n injector,\n ),\n ) as FactoryContext<DirectiveLogicFactory<Directive>>;\n\n if (isPromiseLike(context)) {\n throw new Error(\n 'Async directive factories are not supported by setupCraftDirectiveLogicTest.',\n );\n }\n\n await runConfiguredAppStart(\n options.register as Record<string, RegisterRuntimeEntry>,\n options.appStart,\n injector,\n );\n\n return {\n context,\n mocks: mocks as Record<string, unknown>,\n injector,\n destroy: () => {\n injector.destroy();\n parent.destroy();\n },\n };\n}\n\nfunction createRuntimeDirectiveInjector(\n parent: EnvironmentInjector,\n host: Element,\n register: Record<string, RegisterRuntimeEntry>,\n) {\n const { providers, mocks } = createRuntimeProviders(register);\n return {\n injector: createEnvironmentInjector(\n [{ provide: ElementRef, useValue: new ElementRef(host) }, ...providers],\n parent,\n 'CraftDirectiveTest',\n ),\n mocks,\n };\n}\n\nexport const setupCraftDirectiveLogicTest = Object.assign(\n setupCraftDirectiveLogicTestImpl,\n { byRegister: setupCraftDirectiveLogicTestImpl },\n);\n\ntype DirectiveTemplateOptions<Context, Contract> = TemplateOptions<\n Context,\n Contract\n> & {\n baseTemplate: ComponentTemplate<any>;\n};\n\nasync function setupCraftDirectiveTemplateTestImpl<\n Directive extends CraftDirective,\n const BaseTemplate extends ComponentTemplate<any>,\n const Contract extends CraftDirectiveTemplateDepsOf<Directive> &\n TemplateContractForTemplate<BaseTemplate> = CraftDirectiveTemplateDepsOf<Directive> &\n TemplateContractForTemplate<BaseTemplate>,\n Context = unknown,\n>(\n directive: Directive,\n options: DirectiveTemplateOptions<Context, Contract> & {\n baseTemplate: BaseTemplate;\n },\n) {\n const definition = directive[CRAFT_DIRECTIVE];\n const decoratedTemplate = definition.template(options.baseTemplate);\n const synthetic = craftComponent(\n `CraftDirectiveTemplate:${definition.name}`,\n {\n styles: definition.meta.styles,\n stylesUrl: definition.meta.stylesUrl,\n },\n () => ({}),\n decoratedTemplate as ComponentTemplate<{}>,\n );\n const { host, mounted, mocks, parent } = componentTemplateMount(\n synthetic,\n options.context,\n options.register as Record<string, RegisterRuntimeEntry>,\n options.providers,\n );\n\n const detectChanges = () => parent.get(ɵEffectScheduler).flush();\n if (options.detectChanges ?? true) {\n detectChanges();\n }\n\n return {\n nativeElement: host,\n element: host,\n mocks: mocks as Record<string, unknown>,\n ...createAccessibleQueries(host),\n detectChanges,\n updateContext(context: Context) {\n mounted.updateContext(context);\n },\n destroy() {\n mounted.destroy();\n host.remove();\n parent.destroy();\n },\n };\n}\n\nexport const setupCraftDirectiveTemplateTest = Object.assign(\n setupCraftDirectiveTemplateTestImpl,\n { byRegister: setupCraftDirectiveTemplateTestImpl },\n);\n\n/**\n * WCAG 2.2 AA smoke checks on a mounted Craft tree. Complements axe/AccessLint\n * in application CI: images have alt, controls have a name, tabindex is not\n * positive, ARIA attributes are known.\n */\nexport async function assertAccessible(container: Element): Promise<void> {\n const violations: string[] = [];\n\n container.querySelectorAll('img').forEach((image) => {\n if (!image.hasAttribute('alt')) {\n violations.push(\n `<img src=\"${image.getAttribute('src') ?? ''}\"> is missing alt`,\n );\n }\n });\n\n container\n .querySelectorAll('button, a, [role=\"button\"]')\n .forEach((control) => {\n const name = (\n control.getAttribute('aria-label') ||\n control.getAttribute('aria-labelledby') ||\n control.textContent ||\n ''\n ).trim();\n if (\n !name &&\n control.getAttribute('href') !== null &&\n !(control.textContent ?? '').trim()\n ) {\n violations.push(\n `<${control.tagName.toLowerCase()}> has no accessible name`,\n );\n } else if (!name && control.tagName === 'BUTTON') {\n violations.push('<button> has no accessible name');\n }\n });\n\n container.querySelectorAll('[tabindex]').forEach((node) => {\n const value = Number(node.getAttribute('tabindex'));\n if (Number.isFinite(value) && value > 0) {\n violations.push(`${node.tagName.toLowerCase()} has tabIndex=${value}`);\n }\n });\n\n container.querySelectorAll('iframe').forEach((frame) => {\n if (!frame.getAttribute('title')) {\n violations.push('<iframe> is missing title');\n }\n });\n\n if (violations.length > 0) {\n throw new Error(`Accessibility violations:\\n- ${violations.join('\\n- ')}`);\n }\n}\n"]}
|