@avimate/msfs-jest-utils 1.0.0
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 +22 -0
- package/README.md +170 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +24 -0
- package/dist/mocks/CoherentMock.d.ts +74 -0
- package/dist/mocks/CoherentMock.js +148 -0
- package/dist/mocks/GarminSDKAdapter.d.ts +79 -0
- package/dist/mocks/GarminSDKAdapter.js +127 -0
- package/dist/mocks/MSFSGlobals.d.ts +6 -0
- package/dist/mocks/MSFSGlobals.js +514 -0
- package/dist/mocks/SDKAdapter.d.ts +126 -0
- package/dist/mocks/SDKAdapter.js +696 -0
- package/dist/mocks/SDKClasses.d.ts +32 -0
- package/dist/mocks/SDKClasses.js +118 -0
- package/dist/mocks/SimVarMock.d.ts +97 -0
- package/dist/mocks/SimVarMock.js +215 -0
- package/dist/mocks/index.d.ts +9 -0
- package/dist/mocks/index.js +25 -0
- package/dist/setupTests.d.ts +13 -0
- package/dist/setupTests.js +109 -0
- package/dist/test-utils/ComponentTestHelper.d.ts +73 -0
- package/dist/test-utils/ComponentTestHelper.js +558 -0
- package/dist/test-utils/ObservableTestHelper.d.ts +25 -0
- package/dist/test-utils/ObservableTestHelper.js +75 -0
- package/dist/test-utils/TestEnvironment.d.ts +76 -0
- package/dist/test-utils/TestEnvironment.js +227 -0
- package/dist/test-utils/index.d.ts +6 -0
- package/dist/test-utils/index.js +22 -0
- package/package.json +64 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mock MSFS global objects and types required by SDK
|
|
4
|
+
*
|
|
5
|
+
* These must be set on global object BEFORE any SDK code loads
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.setupMSFSGlobals = setupMSFSGlobals;
|
|
9
|
+
function setupMSFSGlobals() {
|
|
10
|
+
const globalObj = globalThis;
|
|
11
|
+
// BaseInstrument stub
|
|
12
|
+
if (typeof globalObj.BaseInstrument === 'undefined') {
|
|
13
|
+
globalObj.BaseInstrument = class BaseInstrument {
|
|
14
|
+
getChildById(id) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// DisplayComponent - must be available globally for SDK classes that extend it
|
|
20
|
+
if (typeof globalObj.DisplayComponent === 'undefined') {
|
|
21
|
+
globalObj.DisplayComponent = class DisplayComponent {
|
|
22
|
+
constructor(props) {
|
|
23
|
+
this.props = props;
|
|
24
|
+
}
|
|
25
|
+
render() {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
onAfterRender(_vnode) {
|
|
29
|
+
// Default implementation
|
|
30
|
+
}
|
|
31
|
+
destroy() {
|
|
32
|
+
// Default implementation
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// FSComponent - must be available globally for JSX factory
|
|
37
|
+
// This implementation creates actual DOM elements for testing
|
|
38
|
+
if (typeof globalObj.FSComponent === 'undefined') {
|
|
39
|
+
globalObj.FSComponent = {
|
|
40
|
+
buildComponent: (type, props, ...children) => {
|
|
41
|
+
// If it's a string (HTML/SVG tag), create a VNode structure with DOM element
|
|
42
|
+
if (typeof type === 'string') {
|
|
43
|
+
const doc = globalThis.document;
|
|
44
|
+
if (!doc) {
|
|
45
|
+
return { type, props, children };
|
|
46
|
+
}
|
|
47
|
+
// Create actual DOM element
|
|
48
|
+
let element;
|
|
49
|
+
if (type === 'svg' || ['g', 'circle', 'text', 'line', 'polygon', 'path', 'rect', 'ellipse', 'polyline', 'defs', 'use', 'clipPath', 'mask', 'pattern', 'linearGradient', 'radialGradient', 'stop', 'filter', 'feGaussianBlur', 'feColorMatrix', 'feOffset', 'feMerge', 'feMergeNode'].includes(type)) {
|
|
50
|
+
element = doc.createElementNS('http://www.w3.org/2000/svg', type);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
element = doc.createElement(type);
|
|
54
|
+
}
|
|
55
|
+
// Handle ref first (before processing other props)
|
|
56
|
+
let ref = null;
|
|
57
|
+
if (props && props.ref) {
|
|
58
|
+
ref = props.ref;
|
|
59
|
+
}
|
|
60
|
+
// Apply props
|
|
61
|
+
if (props) {
|
|
62
|
+
Object.keys(props).forEach(key => {
|
|
63
|
+
if (key === 'key' || key === 'ref') {
|
|
64
|
+
// Skip these - ref is handled separately
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const value = props[key];
|
|
68
|
+
if (value === null || value === undefined) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// For SVG elements, always use setAttribute
|
|
72
|
+
if (element instanceof SVGElement) {
|
|
73
|
+
// Style object support (including Subscribable values)
|
|
74
|
+
if (key === 'style' && typeof value === 'object') {
|
|
75
|
+
Object.keys(value).forEach(styleKey => {
|
|
76
|
+
const styleVal = value[styleKey];
|
|
77
|
+
if (styleVal && typeof styleVal === 'object' && typeof styleVal.get === 'function' && typeof styleVal.sub === 'function') {
|
|
78
|
+
try {
|
|
79
|
+
element.style[styleKey] = String(styleVal.get());
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
styleVal.sub((v) => { try {
|
|
83
|
+
element.style[styleKey] = String(v);
|
|
84
|
+
}
|
|
85
|
+
catch { } });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
try {
|
|
89
|
+
element.style[styleKey] = String(styleVal);
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (key === 'className') {
|
|
97
|
+
element.setAttribute('class', String(value));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (key === 'class') {
|
|
101
|
+
element.setAttribute('class', String(value));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const svgAttrMap = {
|
|
105
|
+
strokeWidth: 'stroke-width',
|
|
106
|
+
fillRule: 'fill-rule',
|
|
107
|
+
dominantBaseline: 'dominant-baseline',
|
|
108
|
+
textAnchor: 'text-anchor',
|
|
109
|
+
};
|
|
110
|
+
const attrName = svgAttrMap[key] || key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
111
|
+
element.setAttribute(attrName, String(value));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// For HTML elements
|
|
115
|
+
if (key === 'style' && typeof value === 'object') {
|
|
116
|
+
Object.keys(value).forEach(styleKey => {
|
|
117
|
+
const styleVal = value[styleKey];
|
|
118
|
+
if (styleVal && typeof styleVal === 'object' && typeof styleVal.get === 'function' && typeof styleVal.sub === 'function') {
|
|
119
|
+
try {
|
|
120
|
+
element.style[styleKey] = String(styleVal.get());
|
|
121
|
+
}
|
|
122
|
+
catch { }
|
|
123
|
+
styleVal.sub((v) => { try {
|
|
124
|
+
element.style[styleKey] = String(v);
|
|
125
|
+
}
|
|
126
|
+
catch { } });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
element.style[styleKey] = String(styleVal);
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (key === 'className') {
|
|
138
|
+
element.className = String(value);
|
|
139
|
+
}
|
|
140
|
+
else if (key === 'class') {
|
|
141
|
+
element.className = String(value);
|
|
142
|
+
}
|
|
143
|
+
else if (key.startsWith('data-')) {
|
|
144
|
+
element.setAttribute(key, String(value));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Try to set as property first, fallback to attribute
|
|
148
|
+
try {
|
|
149
|
+
element[key] = value;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
element.setAttribute(key, String(value));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Set ref.instance after element is created and props are applied
|
|
159
|
+
if (ref && typeof ref === 'object' && 'instance' in ref) {
|
|
160
|
+
ref.instance = element;
|
|
161
|
+
}
|
|
162
|
+
// Helper to safely append a node
|
|
163
|
+
const safeAppendChild = (parent, child) => {
|
|
164
|
+
try {
|
|
165
|
+
parent.appendChild(child);
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
// If appendChild fails (e.g., cross-document issue), try to adopt/clone first
|
|
169
|
+
try {
|
|
170
|
+
if (doc.adoptNode) {
|
|
171
|
+
parent.appendChild(doc.adoptNode(child));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
parent.appendChild(child.cloneNode(true));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (e2) {
|
|
178
|
+
// If all else fails, clone
|
|
179
|
+
parent.appendChild(child.cloneNode(true));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
// Process children - recursively build VNodes into DOM nodes
|
|
184
|
+
const processChildren = (childList) => {
|
|
185
|
+
childList.forEach(child => {
|
|
186
|
+
if (child === null || child === undefined) {
|
|
187
|
+
return; // Skip JSX comments and null children
|
|
188
|
+
}
|
|
189
|
+
// String/number children become text nodes
|
|
190
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
191
|
+
element.appendChild(doc.createTextNode(String(child)));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (child && typeof child === 'object') {
|
|
195
|
+
// If child is already a DOM Node, append safely
|
|
196
|
+
if (child instanceof Node) {
|
|
197
|
+
safeAppendChild(element, child);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// If child is a VNode with an instance (already built), append the instance
|
|
201
|
+
if (child.instance && child.instance instanceof Node) {
|
|
202
|
+
safeAppendChild(element, child.instance);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// If child is a VNode with a type, build it
|
|
206
|
+
if (child.type) {
|
|
207
|
+
// Extract children from VNode, filtering null/undefined (JSX comments)
|
|
208
|
+
const vnodeChildren = [];
|
|
209
|
+
if (Array.isArray(child.children)) {
|
|
210
|
+
vnodeChildren.push(...child.children.filter((c) => c !== null && c !== undefined));
|
|
211
|
+
}
|
|
212
|
+
else if (child.children !== null && child.children !== undefined) {
|
|
213
|
+
vnodeChildren.push(child.children);
|
|
214
|
+
}
|
|
215
|
+
// Build the child VNode - this will create the DOM element
|
|
216
|
+
const built = globalObj.FSComponent.buildComponent(child.type, child.props || {}, ...vnodeChildren);
|
|
217
|
+
// Append the built element's instance
|
|
218
|
+
if (built && built.instance && built.instance instanceof Node) {
|
|
219
|
+
safeAppendChild(element, built.instance);
|
|
220
|
+
}
|
|
221
|
+
else if (built && built.type) {
|
|
222
|
+
// If still a VNode, recursively process
|
|
223
|
+
const processedChildren = [];
|
|
224
|
+
if (Array.isArray(built.children)) {
|
|
225
|
+
processedChildren.push(...built.children.filter((c) => c !== null && c !== undefined));
|
|
226
|
+
}
|
|
227
|
+
else if (built.children !== null && built.children !== undefined) {
|
|
228
|
+
processedChildren.push(built.children);
|
|
229
|
+
}
|
|
230
|
+
const processed = globalObj.FSComponent.buildComponent(built.type, built.props || {}, ...processedChildren);
|
|
231
|
+
if (processed && processed.instance && processed.instance instanceof Node) {
|
|
232
|
+
safeAppendChild(element, processed.instance);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// If child is an array, process recursively
|
|
238
|
+
if (Array.isArray(child)) {
|
|
239
|
+
processChildren(child);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
if (children && children.length > 0) {
|
|
246
|
+
processChildren(children);
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
type,
|
|
250
|
+
props,
|
|
251
|
+
children,
|
|
252
|
+
instance: element
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// If it's a function (component), instantiate it
|
|
256
|
+
if (typeof type === 'function') {
|
|
257
|
+
const component = new type(props);
|
|
258
|
+
// Component refs: set ref.instance to the component instance
|
|
259
|
+
if (props && props.ref && typeof props.ref === 'object' && 'instance' in props.ref) {
|
|
260
|
+
props.ref.instance = component;
|
|
261
|
+
}
|
|
262
|
+
const renderResult = component.render();
|
|
263
|
+
if (renderResult) {
|
|
264
|
+
return renderResult;
|
|
265
|
+
}
|
|
266
|
+
return { type, props, children, instance: null };
|
|
267
|
+
}
|
|
268
|
+
return { type, props, children };
|
|
269
|
+
},
|
|
270
|
+
render: (vnode, container) => {
|
|
271
|
+
if (!vnode)
|
|
272
|
+
return;
|
|
273
|
+
const targetDoc = container.ownerDocument || globalThis.document;
|
|
274
|
+
if (!targetDoc)
|
|
275
|
+
return;
|
|
276
|
+
// Temporarily set global document to target document so buildComponent uses it
|
|
277
|
+
const originalDoc = globalThis.document;
|
|
278
|
+
globalThis.document = targetDoc;
|
|
279
|
+
// Helper to recreate a node in the target document
|
|
280
|
+
const recreateNodeInDocument = (node, doc) => {
|
|
281
|
+
// Check if node is already in the target document
|
|
282
|
+
if (node.ownerDocument === doc) {
|
|
283
|
+
return node;
|
|
284
|
+
}
|
|
285
|
+
// Try to adopt the node first (works in real browsers, may not work in jsdom)
|
|
286
|
+
try {
|
|
287
|
+
if (doc.adoptNode) {
|
|
288
|
+
return doc.adoptNode(node);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
// If adoptNode fails, recreate the node
|
|
293
|
+
}
|
|
294
|
+
// Recreate the element in the target document
|
|
295
|
+
if (node instanceof Element) {
|
|
296
|
+
const tagName = node.tagName.toLowerCase();
|
|
297
|
+
let newElement;
|
|
298
|
+
// Check if it's an SVG element
|
|
299
|
+
if (node instanceof SVGElement || node.namespaceURI === 'http://www.w3.org/2000/svg') {
|
|
300
|
+
newElement = doc.createElementNS('http://www.w3.org/2000/svg', tagName);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
newElement = doc.createElement(tagName);
|
|
304
|
+
}
|
|
305
|
+
// Copy attributes
|
|
306
|
+
Array.from(node.attributes).forEach(attr => {
|
|
307
|
+
newElement.setAttribute(attr.name, attr.value);
|
|
308
|
+
});
|
|
309
|
+
// Recursively recreate children
|
|
310
|
+
Array.from(node.childNodes).forEach(child => {
|
|
311
|
+
const recreatedChild = recreateNodeInDocument(child, doc);
|
|
312
|
+
newElement.appendChild(recreatedChild);
|
|
313
|
+
});
|
|
314
|
+
return newElement;
|
|
315
|
+
}
|
|
316
|
+
else if (node instanceof Text) {
|
|
317
|
+
return doc.createTextNode(node.textContent || '');
|
|
318
|
+
}
|
|
319
|
+
else if (node instanceof DocumentFragment) {
|
|
320
|
+
const fragment = doc.createDocumentFragment();
|
|
321
|
+
Array.from(node.childNodes).forEach(child => {
|
|
322
|
+
const recreatedChild = recreateNodeInDocument(child, doc);
|
|
323
|
+
fragment.appendChild(recreatedChild);
|
|
324
|
+
});
|
|
325
|
+
return fragment;
|
|
326
|
+
}
|
|
327
|
+
// Fallback: try clone
|
|
328
|
+
return node.cloneNode(true);
|
|
329
|
+
};
|
|
330
|
+
// Helper to safely append a node to a parent
|
|
331
|
+
const safeAppendChild = (parent, child) => {
|
|
332
|
+
try {
|
|
333
|
+
// Check if child is in the same document
|
|
334
|
+
if (child.ownerDocument !== targetDoc && child instanceof Node) {
|
|
335
|
+
const recreated = recreateNodeInDocument(child, targetDoc);
|
|
336
|
+
parent.appendChild(recreated);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
parent.appendChild(child);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
// If appendChild still fails, try recreating
|
|
344
|
+
try {
|
|
345
|
+
const recreated = recreateNodeInDocument(child, targetDoc);
|
|
346
|
+
parent.appendChild(recreated);
|
|
347
|
+
}
|
|
348
|
+
catch (e2) {
|
|
349
|
+
// Last resort: log and skip
|
|
350
|
+
console.warn('Failed to append node:', e2);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
// Recursively build and render VNode tree
|
|
355
|
+
const renderVNode = (node) => {
|
|
356
|
+
if (!node)
|
|
357
|
+
return null;
|
|
358
|
+
// If it's already a DOM node, recreate it in target document
|
|
359
|
+
if (node instanceof Node) {
|
|
360
|
+
return recreateNodeInDocument(node, targetDoc);
|
|
361
|
+
}
|
|
362
|
+
// If it has an instance, recreate it in target document
|
|
363
|
+
if (node.instance && node.instance instanceof Node) {
|
|
364
|
+
return recreateNodeInDocument(node.instance, targetDoc);
|
|
365
|
+
}
|
|
366
|
+
// If it's a string or number, create text node
|
|
367
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
368
|
+
return targetDoc.createTextNode(String(node));
|
|
369
|
+
}
|
|
370
|
+
// If it's an array, process each element
|
|
371
|
+
if (Array.isArray(node)) {
|
|
372
|
+
const fragment = targetDoc.createDocumentFragment();
|
|
373
|
+
node.forEach(child => {
|
|
374
|
+
const childNode = renderVNode(child);
|
|
375
|
+
if (childNode) {
|
|
376
|
+
safeAppendChild(fragment, childNode);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return fragment;
|
|
380
|
+
}
|
|
381
|
+
// Build component from VNode
|
|
382
|
+
if (node.type) {
|
|
383
|
+
// Pass children directly to buildComponent - it will handle VNodes, Nodes, strings, etc.
|
|
384
|
+
// Filter out null/undefined (from JSX comments)
|
|
385
|
+
const children = (node.children || []).filter((child) => child !== null && child !== undefined);
|
|
386
|
+
const built = globalObj.FSComponent.buildComponent(node.type, node.props || {}, ...children);
|
|
387
|
+
// If built has instance, recreate it in target document
|
|
388
|
+
if (built && built.instance && built.instance instanceof Node) {
|
|
389
|
+
return recreateNodeInDocument(built.instance, targetDoc);
|
|
390
|
+
}
|
|
391
|
+
// If built is a VNode without instance, recursively process it
|
|
392
|
+
if (built && built.type && !built.instance) {
|
|
393
|
+
return renderVNode(built);
|
|
394
|
+
}
|
|
395
|
+
// If built has children but no instance, process children into a fragment
|
|
396
|
+
if (built && built.children && Array.isArray(built.children)) {
|
|
397
|
+
const fragment = targetDoc.createDocumentFragment();
|
|
398
|
+
built.children.forEach((child) => {
|
|
399
|
+
// If child is already a Node, append it
|
|
400
|
+
if (child instanceof Node) {
|
|
401
|
+
safeAppendChild(fragment, recreateNodeInDocument(child, targetDoc));
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
// Otherwise process as VNode
|
|
405
|
+
const childNode = renderVNode(child);
|
|
406
|
+
if (childNode) {
|
|
407
|
+
safeAppendChild(fragment, childNode);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return fragment;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
};
|
|
416
|
+
// Helper to re-establish refs after node recreation
|
|
417
|
+
const reestablishRefs = (vnode, domNode) => {
|
|
418
|
+
if (!vnode || !domNode)
|
|
419
|
+
return;
|
|
420
|
+
// Update VNode instance to point to the new DOM node
|
|
421
|
+
vnode.instance = domNode;
|
|
422
|
+
// If this VNode has a ref in props, update it to point to the DOM node
|
|
423
|
+
if (vnode.props && vnode.props.ref && typeof vnode.props.ref === 'object' && 'instance' in vnode.props.ref) {
|
|
424
|
+
vnode.props.ref.instance = domNode;
|
|
425
|
+
}
|
|
426
|
+
// Recursively process children - match VNode children to DOM children
|
|
427
|
+
if (vnode.children && Array.isArray(vnode.children) && domNode instanceof Element) {
|
|
428
|
+
// Get all element children from DOM (skip text nodes)
|
|
429
|
+
const domChildren = Array.from(domNode.childNodes).filter(n => n instanceof Element);
|
|
430
|
+
let domIndex = 0;
|
|
431
|
+
vnode.children.forEach((childVNode) => {
|
|
432
|
+
if (!childVNode)
|
|
433
|
+
return;
|
|
434
|
+
// Skip non-VNode children (strings, numbers, nulls)
|
|
435
|
+
if (typeof childVNode !== 'object')
|
|
436
|
+
return;
|
|
437
|
+
// Process VNode children
|
|
438
|
+
if (childVNode.type || childVNode.instance) {
|
|
439
|
+
if (domIndex < domChildren.length) {
|
|
440
|
+
reestablishRefs(childVNode, domChildren[domIndex]);
|
|
441
|
+
domIndex++;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const rootNode = renderVNode(vnode);
|
|
448
|
+
if (rootNode) {
|
|
449
|
+
safeAppendChild(container, rootNode);
|
|
450
|
+
// Re-establish refs after all nodes are in the DOM
|
|
451
|
+
reestablishRefs(vnode, rootNode);
|
|
452
|
+
}
|
|
453
|
+
// Restore original document
|
|
454
|
+
globalThis.document = originalDoc;
|
|
455
|
+
},
|
|
456
|
+
Fragment: (props, ...children) => {
|
|
457
|
+
return { type: 'Fragment', children };
|
|
458
|
+
},
|
|
459
|
+
createRef() {
|
|
460
|
+
return { instance: null };
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
// GameState enum
|
|
465
|
+
if (typeof globalObj.GameState === 'undefined') {
|
|
466
|
+
globalObj.GameState = {
|
|
467
|
+
NONE: 0,
|
|
468
|
+
MENU: 1,
|
|
469
|
+
LOADING: 2,
|
|
470
|
+
FLYING: 3,
|
|
471
|
+
PAUSED: 4,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
// Name_Z type (string alias in MSFS)
|
|
475
|
+
if (typeof globalObj.Name_Z === 'undefined') {
|
|
476
|
+
globalObj.Name_Z = String;
|
|
477
|
+
}
|
|
478
|
+
// RunwayDesignator
|
|
479
|
+
if (typeof globalObj.RunwayDesignator === 'undefined') {
|
|
480
|
+
globalObj.RunwayDesignator = {
|
|
481
|
+
NONE: 0,
|
|
482
|
+
LEFT: 1,
|
|
483
|
+
RIGHT: 2,
|
|
484
|
+
CENTER: 3,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
// AirportClass
|
|
488
|
+
if (typeof globalObj.AirportClass === 'undefined') {
|
|
489
|
+
globalObj.AirportClass = {
|
|
490
|
+
HELIPORT: 0,
|
|
491
|
+
SMALL_AIRPORT: 1,
|
|
492
|
+
MEDIUM_AIRPORT: 2,
|
|
493
|
+
LARGE_AIRPORT: 3,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
// Avionics global
|
|
497
|
+
if (typeof globalObj.Avionics === 'undefined') {
|
|
498
|
+
globalObj.Avionics = {
|
|
499
|
+
getCurrentGpsTime: () => Date.now(),
|
|
500
|
+
getCurrentUtcTime: () => Date.now(),
|
|
501
|
+
Utils: {
|
|
502
|
+
DEG2RAD: Math.PI / 180,
|
|
503
|
+
RAD2DEG: 180 / Math.PI,
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
// Storage functions
|
|
508
|
+
if (typeof globalObj.GetStoredData === 'undefined') {
|
|
509
|
+
globalObj.GetStoredData = () => '';
|
|
510
|
+
}
|
|
511
|
+
if (typeof globalObj.SetStoredData === 'undefined') {
|
|
512
|
+
globalObj.SetStoredData = () => { };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Adapter for Jest testing
|
|
3
|
+
*
|
|
4
|
+
* Provides mock implementations of MSFS SDK classes for testing.
|
|
5
|
+
* This allows components to be tested without the full SDK bundle.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: We don't re-export types from @microsoft/msfs-types because
|
|
8
|
+
* they are declaration files and not modules. Types are resolved
|
|
9
|
+
* through TypeScript's type resolution.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class DisplayComponent<P = any, S = any> {
|
|
12
|
+
props: P;
|
|
13
|
+
state?: S;
|
|
14
|
+
constructor(props: P);
|
|
15
|
+
onBeforeRender?(): void;
|
|
16
|
+
abstract render(): any;
|
|
17
|
+
onAfterRender(vnode?: any): void;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
20
|
+
type FSComponentLike = {
|
|
21
|
+
buildComponent: (type: any, props: any, ...children: any[]) => any;
|
|
22
|
+
render: (vnode: any, container: HTMLElement) => void;
|
|
23
|
+
Fragment: (props: any, ...children: any[]) => any;
|
|
24
|
+
createRef: <T = any>() => {
|
|
25
|
+
instance: T | null;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare const FSComponent: FSComponentLike;
|
|
29
|
+
export interface VNode {
|
|
30
|
+
instance?: any;
|
|
31
|
+
children?: VNode[];
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}
|
|
34
|
+
export declare class Subject<T> {
|
|
35
|
+
private value;
|
|
36
|
+
private subscribers;
|
|
37
|
+
constructor(initialValue: T);
|
|
38
|
+
static create<T>(initialValue: T): Subject<T>;
|
|
39
|
+
get(): T;
|
|
40
|
+
set(value: T): void;
|
|
41
|
+
sub(callback: (value: T) => void, immediate?: boolean): {
|
|
42
|
+
destroy: () => void;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Create a mapped Subscribable from this subject.
|
|
46
|
+
* Mimics MSFS SDK Subject.map() enough for UI binding tests.
|
|
47
|
+
*/
|
|
48
|
+
map<M>(fn: (input: T, previousVal?: M) => M, _equalityFunc?: ((a: M, b: M) => boolean)): Subscribable<M> & Subscription;
|
|
49
|
+
}
|
|
50
|
+
export interface Subscribable<T> {
|
|
51
|
+
get(): T;
|
|
52
|
+
sub(callback: (value: T) => void, immediate?: boolean): {
|
|
53
|
+
destroy: () => void;
|
|
54
|
+
};
|
|
55
|
+
map<M>(fn: (input: T, previousVal?: M) => M, equalityFunc?: ((a: M, b: M) => boolean)): Subscribable<M> & Subscription;
|
|
56
|
+
}
|
|
57
|
+
export type ReadonlyFloat64Array = Readonly<Float64Array>;
|
|
58
|
+
export declare class NumberUnit<U = any> {
|
|
59
|
+
readonly number: number;
|
|
60
|
+
readonly unit: U;
|
|
61
|
+
constructor(number: number, unit: U);
|
|
62
|
+
asUnit(_unit: any): number;
|
|
63
|
+
}
|
|
64
|
+
export declare class UnitTypeClass {
|
|
65
|
+
readonly name: string;
|
|
66
|
+
constructor(name: string);
|
|
67
|
+
createNumber(value: number): NumberUnit<UnitTypeClass>;
|
|
68
|
+
}
|
|
69
|
+
export declare const UnitType: {
|
|
70
|
+
readonly NMILE: UnitTypeClass;
|
|
71
|
+
};
|
|
72
|
+
export type NumberUnitSubject = Subject<NumberUnit<any>>;
|
|
73
|
+
export declare const NumberUnitSubject: {
|
|
74
|
+
readonly create: (initial: NumberUnit<any>) => Subject<NumberUnit<any>>;
|
|
75
|
+
};
|
|
76
|
+
export declare const Vec2Math: {
|
|
77
|
+
create(x?: number, y?: number): Float64Array;
|
|
78
|
+
};
|
|
79
|
+
export type Vec2Subject = Subject<Float64Array>;
|
|
80
|
+
export declare const Vec2Subject: {
|
|
81
|
+
readonly create: (initial: Float64Array) => Subject<Float64Array>;
|
|
82
|
+
};
|
|
83
|
+
export declare const MapSystemKeys: {
|
|
84
|
+
readonly FacilityLoader: "FacilityLoader";
|
|
85
|
+
readonly Weather: "Weather";
|
|
86
|
+
readonly OwnAirplaneIcon: "OwnAirplaneIcon";
|
|
87
|
+
};
|
|
88
|
+
export declare class FacilityRepository {
|
|
89
|
+
static getRepository(_bus: any): any;
|
|
90
|
+
}
|
|
91
|
+
export declare class FacilityLoader {
|
|
92
|
+
constructor(_repo: any);
|
|
93
|
+
}
|
|
94
|
+
export declare class MapProjection {
|
|
95
|
+
project(_lla: {
|
|
96
|
+
lat: number;
|
|
97
|
+
lon: number;
|
|
98
|
+
}, out: Float64Array): Float64Array;
|
|
99
|
+
}
|
|
100
|
+
export declare class BingComponent {
|
|
101
|
+
static createEarthColorsArray(_waterColor: string, _stops: any[], _a: number, _b: number, _c: number): any[];
|
|
102
|
+
}
|
|
103
|
+
export declare class MapIndexedRangeModule {
|
|
104
|
+
readonly nominalRange: Subject<NumberUnit<UnitTypeClass>>;
|
|
105
|
+
}
|
|
106
|
+
export declare class MapOwnAirplaneIconModule {
|
|
107
|
+
}
|
|
108
|
+
export declare class MapOwnAirplanePropsModule {
|
|
109
|
+
}
|
|
110
|
+
export declare class MapWxrModule {
|
|
111
|
+
}
|
|
112
|
+
export type CompiledMapSystem<T = any, U = any, V = any, W = any> = any;
|
|
113
|
+
export declare const MapSystemBuilder: any;
|
|
114
|
+
export declare const SubscribableUtils: any;
|
|
115
|
+
export declare class EventBus {
|
|
116
|
+
private events;
|
|
117
|
+
on<T>(topic: string, callback: (data: T) => void): void;
|
|
118
|
+
off<T>(topic: string, callback: (data: T) => void): void;
|
|
119
|
+
pub<T>(topic: string, data: T): void;
|
|
120
|
+
}
|
|
121
|
+
export interface Subscription {
|
|
122
|
+
destroy(): void;
|
|
123
|
+
}
|
|
124
|
+
export type ComponentProps = any;
|
|
125
|
+
export type DisplayChildren = any;
|
|
126
|
+
export {};
|