pulse-js-framework 1.7.13 → 1.7.15

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/README.md CHANGED
@@ -148,6 +148,7 @@ pulse compile <file> # Compile .pulse file
148
148
 
149
149
  # Code Quality
150
150
  pulse lint [files] # Validate .pulse files
151
+ pulse lint --fix # Auto-fix fixable issues
151
152
  pulse format [files] # Format .pulse files
152
153
  pulse analyze # Analyze bundle
153
154
 
@@ -167,6 +168,8 @@ pulse scaffold page <name> # Generate page
167
168
  pulse scaffold store <name> # Generate store module
168
169
  pulse scaffold hook <name> # Generate custom hook
169
170
  pulse scaffold service <name> # Generate API service
171
+ pulse scaffold context <name> # Generate context provider
172
+ pulse scaffold layout <name> # Generate layout component
170
173
 
171
174
  # Documentation
172
175
  pulse docs --generate # Generate API docs (Markdown)
@@ -328,6 +331,12 @@ const count: Pulse<number> = pulse(0);
328
331
  - [API Reference](docs/api.md) - Complete API documentation
329
332
  - [CLI Commands](docs/cli.md) - Command line interface
330
333
  - [Pulse DSL](docs/pulse-dsl.md) - .pulse file syntax
334
+ - [Accessibility](docs/accessibility.md) - A11y guide and ARIA helpers
335
+ - [HTTP Client](docs/http.md) - Fetch wrapper with interceptors
336
+ - [WebSocket](docs/websocket.md) - Real-time with auto-reconnect
337
+ - [GraphQL](docs/graphql.md) - Queries, mutations, subscriptions
338
+ - [Context API](docs/context.md) - Dependency injection
339
+ - [DevTools](docs/devtools.md) - Debugging and profiling
331
340
  - [Mobile Apps](docs/mobile.md) - Native Android & iOS
332
341
 
333
342
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.7.13",
3
+ "version": "1.7.15",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -88,7 +88,7 @@
88
88
  "./compiler/lexer": "./compiler/lexer.js",
89
89
  "./compiler/parser": "./compiler/parser.js",
90
90
  "./compiler/transformer": "./compiler/transformer.js",
91
- "./core/errors": "./core/errors.js",
91
+ "./core/errors": "./runtime/errors.js",
92
92
  "./vite": {
93
93
  "types": "./types/index.d.ts",
94
94
  "default": "./loader/vite-plugin.js"
@@ -109,14 +109,17 @@
109
109
  "LICENSE"
110
110
  ],
111
111
  "scripts": {
112
- "test": "npm run test:compiler && npm run test:sourcemap && npm run test:pulse && npm run test:dom && npm run test:dom-adapter && npm run test:router && npm run test:store && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze && npm run test:cli && npm run test:cli-ui && npm run test:lru-cache && npm run test:utils && npm run test:docs && npm run test:async && npm run test:form && npm run test:http && npm run test:devtools && npm run test:native && npm run test:a11y && npm run test:logger && npm run test:errors && npm run test:security && npm run test:websocket && npm run test:graphql && npm run test:doctor && npm run test:scaffold && npm run test:test-runner && npm run test:build",
112
+ "test": "npm run test:compiler && npm run test:sourcemap && npm run test:pulse && npm run test:dom && npm run test:dom-element && npm run test:dom-adapter && npm run test:enhanced-mock-adapter && npm run test:router && npm run test:store && npm run test:context && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze && npm run test:cli && npm run test:cli-ui && npm run test:lru-cache && npm run test:utils && npm run test:docs && npm run test:docs-nav && npm run test:async && npm run test:form && npm run test:http && npm run test:devtools && npm run test:native && npm run test:a11y && npm run test:a11y-enhanced && npm run test:logger && npm run test:errors && npm run test:security && npm run test:websocket && npm run test:graphql && npm run test:doctor && npm run test:scaffold && npm run test:test-runner && npm run test:build && npm run test:integration && npm run test:context-stress && npm run test:form-edge-cases && npm run test:graphql-subscriptions && npm run test:http-edge-cases && npm run test:integration-advanced && npm run test:websocket-stress",
113
113
  "test:compiler": "node test/compiler.test.js",
114
114
  "test:sourcemap": "node test/sourcemap.test.js",
115
115
  "test:pulse": "node test/pulse.test.js",
116
116
  "test:dom": "node test/dom.test.js",
117
+ "test:dom-element": "node test/dom-element.test.js",
117
118
  "test:dom-adapter": "node test/dom-adapter.test.js",
119
+ "test:enhanced-mock-adapter": "node test/enhanced-mock-adapter.test.js",
118
120
  "test:router": "node test/router.test.js",
119
121
  "test:store": "node test/store.test.js",
122
+ "test:context": "node test/context.test.js",
120
123
  "test:hmr": "node test/hmr.test.js",
121
124
  "test:lint": "node test/lint.test.js",
122
125
  "test:format": "node test/format.test.js",
@@ -126,12 +129,14 @@
126
129
  "test:lru-cache": "node test/lru-cache.test.js",
127
130
  "test:utils": "node test/utils.test.js",
128
131
  "test:docs": "node test/docs.test.js",
132
+ "test:docs-nav": "node test/docs-navigation.test.js",
129
133
  "test:async": "node test/async.test.js",
130
134
  "test:form": "node test/form.test.js",
131
135
  "test:http": "node test/http.test.js",
132
136
  "test:devtools": "node test/devtools.test.js",
133
137
  "test:native": "node test/native.test.js",
134
138
  "test:a11y": "node test/a11y.test.js",
139
+ "test:a11y-enhanced": "node test/a11y-enhanced.test.js",
135
140
  "test:logger": "node test/logger.test.js",
136
141
  "test:errors": "node test/errors.test.js",
137
142
  "test:security": "node test/security.test.js",
@@ -141,6 +146,13 @@
141
146
  "test:scaffold": "node test/scaffold.test.js",
142
147
  "test:test-runner": "node test/test-runner.test.js",
143
148
  "test:build": "node test/build.test.js",
149
+ "test:integration": "node test/integration.test.js",
150
+ "test:context-stress": "node test/context-stress.test.js",
151
+ "test:form-edge-cases": "node test/form-edge-cases.test.js",
152
+ "test:graphql-subscriptions": "node test/graphql-subscriptions.test.js",
153
+ "test:http-edge-cases": "node test/http-edge-cases.test.js",
154
+ "test:integration-advanced": "node test/integration-advanced.test.js",
155
+ "test:websocket-stress": "node test/websocket-stress.test.js",
144
156
  "build:netlify": "node scripts/build-netlify.js",
145
157
  "version": "node scripts/sync-version.js",
146
158
  "docs": "node cli/index.js dev docs"
@@ -901,6 +901,661 @@ export function withAdapter(adapter, fn) {
901
901
  }
902
902
  }
903
903
 
904
+ // ============================================================================
905
+ // Enhanced Mock Classes for Testing
906
+ // ============================================================================
907
+
908
+ /**
909
+ * Mock Canvas 2D rendering context for color parsing in a11y tests.
910
+ */
911
+ export class MockCanvasContext {
912
+ constructor() {
913
+ this.fillStyle = '#000000';
914
+ this._imageData = new Uint8ClampedArray([0, 0, 0, 255]);
915
+ }
916
+
917
+ fillRect(x, y, width, height) {
918
+ // Parse fillStyle to RGB and store in imageData
919
+ const color = this._parseColor(this.fillStyle);
920
+ this._imageData[0] = color.r;
921
+ this._imageData[1] = color.g;
922
+ this._imageData[2] = color.b;
923
+ this._imageData[3] = 255;
924
+ }
925
+
926
+ getImageData(x, y, width, height) {
927
+ return { data: this._imageData };
928
+ }
929
+
930
+ /**
931
+ * Parse CSS color to RGB values.
932
+ * Supports: hex (#fff, #ffffff), rgb(), rgba(), named colors
933
+ */
934
+ _parseColor(color) {
935
+ if (!color || color === 'transparent') {
936
+ return { r: 0, g: 0, b: 0 };
937
+ }
938
+
939
+ // Hex colors
940
+ if (color.startsWith('#')) {
941
+ let hex = color.slice(1);
942
+ if (hex.length === 3) {
943
+ hex = hex.split('').map(c => c + c).join('');
944
+ }
945
+ return {
946
+ r: parseInt(hex.slice(0, 2), 16),
947
+ g: parseInt(hex.slice(2, 4), 16),
948
+ b: parseInt(hex.slice(4, 6), 16)
949
+ };
950
+ }
951
+
952
+ // rgb() and rgba()
953
+ const rgbMatch = color.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
954
+ if (rgbMatch) {
955
+ return {
956
+ r: parseInt(rgbMatch[1], 10),
957
+ g: parseInt(rgbMatch[2], 10),
958
+ b: parseInt(rgbMatch[3], 10)
959
+ };
960
+ }
961
+
962
+ // Named colors (common subset)
963
+ const namedColors = {
964
+ white: { r: 255, g: 255, b: 255 },
965
+ black: { r: 0, g: 0, b: 0 },
966
+ red: { r: 255, g: 0, b: 0 },
967
+ green: { r: 0, g: 128, b: 0 },
968
+ blue: { r: 0, g: 0, b: 255 },
969
+ yellow: { r: 255, g: 255, b: 0 },
970
+ orange: { r: 255, g: 165, b: 0 },
971
+ gray: { r: 128, g: 128, b: 128 },
972
+ grey: { r: 128, g: 128, b: 128 }
973
+ };
974
+
975
+ return namedColors[color.toLowerCase()] || { r: 0, g: 0, b: 0 };
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Mock MediaQueryList for matchMedia() testing.
981
+ */
982
+ export class MockMediaQueryList {
983
+ constructor(query, matches = false) {
984
+ this.media = query;
985
+ this.matches = matches;
986
+ this._listeners = [];
987
+ }
988
+
989
+ addEventListener(event, listener) {
990
+ if (event === 'change') {
991
+ this._listeners.push(listener);
992
+ }
993
+ }
994
+
995
+ removeEventListener(event, listener) {
996
+ if (event === 'change') {
997
+ const index = this._listeners.indexOf(listener);
998
+ if (index !== -1) {
999
+ this._listeners.splice(index, 1);
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ /**
1005
+ * Simulate a media query change (for testing).
1006
+ */
1007
+ _setMatches(matches) {
1008
+ if (this.matches !== matches) {
1009
+ this.matches = matches;
1010
+ const event = { matches, media: this.media };
1011
+ this._listeners.forEach(listener => listener(event));
1012
+ }
1013
+ }
1014
+
1015
+ // Deprecated but still used in some code
1016
+ addListener(listener) {
1017
+ this.addEventListener('change', listener);
1018
+ }
1019
+
1020
+ removeListener(listener) {
1021
+ this.removeEventListener('change', listener);
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Mock MutationObserver for DOM change tracking.
1027
+ */
1028
+ export class MockMutationObserver {
1029
+ constructor(callback) {
1030
+ this._callback = callback;
1031
+ this._observing = false;
1032
+ this._target = null;
1033
+ this._options = null;
1034
+ this._mutations = [];
1035
+ }
1036
+
1037
+ observe(target, options) {
1038
+ this._observing = true;
1039
+ this._target = target;
1040
+ this._options = options;
1041
+ }
1042
+
1043
+ disconnect() {
1044
+ this._observing = false;
1045
+ this._target = null;
1046
+ this._options = null;
1047
+ }
1048
+
1049
+ takeRecords() {
1050
+ const records = [...this._mutations];
1051
+ this._mutations = [];
1052
+ return records;
1053
+ }
1054
+
1055
+ /**
1056
+ * Simulate a mutation (for testing).
1057
+ */
1058
+ _trigger(mutations) {
1059
+ if (this._observing && this._callback) {
1060
+ this._mutations.push(...mutations);
1061
+ this._callback(mutations, this);
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Mock Performance API.
1068
+ */
1069
+ export class MockPerformance {
1070
+ constructor() {
1071
+ this._startTime = Date.now();
1072
+ this._marks = new Map();
1073
+ this._measures = new Map();
1074
+ }
1075
+
1076
+ now() {
1077
+ return Date.now() - this._startTime;
1078
+ }
1079
+
1080
+ mark(name) {
1081
+ this._marks.set(name, this.now());
1082
+ }
1083
+
1084
+ measure(name, startMark, endMark) {
1085
+ const start = this._marks.get(startMark) || 0;
1086
+ const end = this._marks.get(endMark) || this.now();
1087
+ this._measures.set(name, { name, duration: end - start, startTime: start });
1088
+ }
1089
+
1090
+ getEntriesByName(name) {
1091
+ const measure = this._measures.get(name);
1092
+ return measure ? [measure] : [];
1093
+ }
1094
+
1095
+ clearMarks(name) {
1096
+ if (name) {
1097
+ this._marks.delete(name);
1098
+ } else {
1099
+ this._marks.clear();
1100
+ }
1101
+ }
1102
+
1103
+ clearMeasures(name) {
1104
+ if (name) {
1105
+ this._measures.delete(name);
1106
+ } else {
1107
+ this._measures.clear();
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ /**
1113
+ * Mock computed style object.
1114
+ */
1115
+ export class MockCSSStyleDeclaration {
1116
+ constructor(styles = {}) {
1117
+ // Default visible styles
1118
+ this.display = styles.display || 'block';
1119
+ this.visibility = styles.visibility || 'visible';
1120
+ this.color = styles.color || 'rgb(0, 0, 0)';
1121
+ this.backgroundColor = styles.backgroundColor || 'rgba(0, 0, 0, 0)';
1122
+ this.fontSize = styles.fontSize || '16px';
1123
+ this.fontWeight = styles.fontWeight || '400';
1124
+ this.position = styles.position || 'static';
1125
+ this.width = styles.width || 'auto';
1126
+ this.height = styles.height || 'auto';
1127
+
1128
+ // Allow custom styles
1129
+ Object.assign(this, styles);
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * Mock Window object for global browser APIs.
1135
+ */
1136
+ export class MockWindow {
1137
+ constructor(options = {}) {
1138
+ this._mediaQueryResults = options.mediaQueryResults || {};
1139
+ this._mediaQueryLists = new Map();
1140
+
1141
+ this.innerWidth = options.innerWidth || 1024;
1142
+ this.innerHeight = options.innerHeight || 768;
1143
+ this.location = {
1144
+ href: options.locationHref || 'http://localhost:3000/',
1145
+ pathname: options.locationPathname || '/',
1146
+ search: '',
1147
+ hash: ''
1148
+ };
1149
+
1150
+ this.performance = new MockPerformance();
1151
+ this._eventListeners = new Map();
1152
+ this._animationFrameCallbacks = [];
1153
+ this._animationFrameId = 0;
1154
+ }
1155
+
1156
+ matchMedia(query) {
1157
+ if (!this._mediaQueryLists.has(query)) {
1158
+ const matches = this._evaluateMediaQuery(query);
1159
+ this._mediaQueryLists.set(query, new MockMediaQueryList(query, matches));
1160
+ }
1161
+ return this._mediaQueryLists.get(query);
1162
+ }
1163
+
1164
+ _evaluateMediaQuery(query) {
1165
+ // Check custom results first
1166
+ if (this._mediaQueryResults[query] !== undefined) {
1167
+ return this._mediaQueryResults[query];
1168
+ }
1169
+
1170
+ // Evaluate common media queries
1171
+ if (query.includes('prefers-reduced-motion: reduce')) return false;
1172
+ if (query.includes('prefers-color-scheme: dark')) return false;
1173
+ if (query.includes('prefers-color-scheme: light')) return true;
1174
+ if (query.includes('prefers-contrast: more')) return false;
1175
+ if (query.includes('prefers-reduced-transparency: reduce')) return false;
1176
+ if (query.includes('forced-colors: active')) return false;
1177
+
1178
+ // Width queries
1179
+ const minWidthMatch = query.match(/min-width:\s*(\d+)px/);
1180
+ if (minWidthMatch) {
1181
+ return this.innerWidth >= parseInt(minWidthMatch[1], 10);
1182
+ }
1183
+
1184
+ const maxWidthMatch = query.match(/max-width:\s*(\d+)px/);
1185
+ if (maxWidthMatch) {
1186
+ return this.innerWidth <= parseInt(maxWidthMatch[1], 10);
1187
+ }
1188
+
1189
+ return false;
1190
+ }
1191
+
1192
+ /**
1193
+ * Set media query result (for testing).
1194
+ */
1195
+ setMediaQueryResult(query, matches) {
1196
+ this._mediaQueryResults[query] = matches;
1197
+ if (this._mediaQueryLists.has(query)) {
1198
+ this._mediaQueryLists.get(query)._setMatches(matches);
1199
+ }
1200
+ }
1201
+
1202
+ requestAnimationFrame(callback) {
1203
+ const id = ++this._animationFrameId;
1204
+ this._animationFrameCallbacks.push({ id, callback });
1205
+ return id;
1206
+ }
1207
+
1208
+ cancelAnimationFrame(id) {
1209
+ this._animationFrameCallbacks = this._animationFrameCallbacks.filter(c => c.id !== id);
1210
+ }
1211
+
1212
+ /**
1213
+ * Run all pending animation frame callbacks (for testing).
1214
+ */
1215
+ flushAnimationFrames() {
1216
+ const callbacks = [...this._animationFrameCallbacks];
1217
+ this._animationFrameCallbacks = [];
1218
+ callbacks.forEach(({ callback }) => callback(this.performance.now()));
1219
+ }
1220
+
1221
+ addEventListener(event, handler, options) {
1222
+ if (!this._eventListeners.has(event)) {
1223
+ this._eventListeners.set(event, []);
1224
+ }
1225
+ this._eventListeners.get(event).push({ handler, options });
1226
+ }
1227
+
1228
+ removeEventListener(event, handler, options) {
1229
+ const listeners = this._eventListeners.get(event);
1230
+ if (listeners) {
1231
+ const index = listeners.findIndex(l => l.handler === handler);
1232
+ if (index !== -1) {
1233
+ listeners.splice(index, 1);
1234
+ }
1235
+ }
1236
+ }
1237
+
1238
+ dispatchEvent(event) {
1239
+ const listeners = this._eventListeners.get(event.type);
1240
+ if (listeners) {
1241
+ listeners.forEach(({ handler }) => handler(event));
1242
+ }
1243
+ }
1244
+
1245
+ getComputedStyle(element) {
1246
+ // Return element's stored computed styles or defaults
1247
+ return element._computedStyle || new MockCSSStyleDeclaration();
1248
+ }
1249
+ }
1250
+
1251
+ /**
1252
+ * Enhanced MockElement with additional browser APIs.
1253
+ */
1254
+ export class EnhancedMockElement extends MockElement {
1255
+ constructor(tagName) {
1256
+ super(tagName);
1257
+ this._boundingRect = { top: 0, left: 0, width: 100, height: 50, right: 100, bottom: 50 };
1258
+ this._computedStyle = new MockCSSStyleDeclaration();
1259
+ this._canvas = null;
1260
+ this.hidden = false;
1261
+ this.inert = false;
1262
+ this.labels = [];
1263
+ this.offsetParent = {};
1264
+ }
1265
+
1266
+ getBoundingClientRect() {
1267
+ return { ...this._boundingRect };
1268
+ }
1269
+
1270
+ /**
1271
+ * Set bounding rect (for testing).
1272
+ */
1273
+ setBoundingRect(rect) {
1274
+ this._boundingRect = { ...this._boundingRect, ...rect };
1275
+ }
1276
+
1277
+ /**
1278
+ * Set computed style (for testing).
1279
+ */
1280
+ setComputedStyle(styles) {
1281
+ this._computedStyle = new MockCSSStyleDeclaration(styles);
1282
+ }
1283
+
1284
+ getContext(contextType) {
1285
+ if (contextType === '2d') {
1286
+ if (!this._canvas) {
1287
+ this._canvas = new MockCanvasContext();
1288
+ }
1289
+ return this._canvas;
1290
+ }
1291
+ return null;
1292
+ }
1293
+
1294
+ focus() {
1295
+ // Simulate focus by updating document.activeElement
1296
+ if (this._document) {
1297
+ this._document.activeElement = this;
1298
+ }
1299
+ }
1300
+
1301
+ blur() {
1302
+ if (this._document && this._document.activeElement === this) {
1303
+ this._document.activeElement = null;
1304
+ }
1305
+ }
1306
+
1307
+ contains(other) {
1308
+ if (!other) return false;
1309
+ if (other === this) return true;
1310
+ for (const child of this.childNodes) {
1311
+ if (child === other) return true;
1312
+ if (child.contains && child.contains(other)) return true;
1313
+ }
1314
+ return false;
1315
+ }
1316
+
1317
+ closest(selector) {
1318
+ // Simple implementation - check self and parents
1319
+ let current = this;
1320
+ while (current) {
1321
+ if (this._matchesSelector(current, selector)) {
1322
+ return current;
1323
+ }
1324
+ current = current.parentNode;
1325
+ }
1326
+ return null;
1327
+ }
1328
+
1329
+ _matchesSelector(element, selector) {
1330
+ if (!element.tagName) return false;
1331
+
1332
+ // Tag selector
1333
+ if (selector === element.tagName.toLowerCase()) return true;
1334
+
1335
+ // ID selector
1336
+ if (selector.startsWith('#') && element.id === selector.slice(1)) return true;
1337
+
1338
+ // Class selector
1339
+ if (selector.startsWith('.') && element.classList?.contains(selector.slice(1))) return true;
1340
+
1341
+ return false;
1342
+ }
1343
+
1344
+ querySelectorAll(selector) {
1345
+ const results = [];
1346
+ this._findAll(this, selector, results);
1347
+ return results;
1348
+ }
1349
+
1350
+ querySelector(selector) {
1351
+ const all = this.querySelectorAll(selector);
1352
+ return all[0] || null;
1353
+ }
1354
+
1355
+ _findAll(node, selector, results) {
1356
+ for (const child of node.childNodes || []) {
1357
+ if (this._matchesSelector(child, selector)) {
1358
+ results.push(child);
1359
+ }
1360
+ if (child._findAll) {
1361
+ child._findAll(child, selector, results);
1362
+ } else {
1363
+ this._findAll(child, selector, results);
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * Enhanced Mock DOM Adapter with full browser API simulation.
1371
+ * Provides comprehensive testing support for a11y, devtools, and other
1372
+ * browser-dependent modules.
1373
+ *
1374
+ * @implements {DOMAdapter}
1375
+ */
1376
+ export class EnhancedMockAdapter extends MockDOMAdapter {
1377
+ constructor(options = {}) {
1378
+ super();
1379
+
1380
+ // Replace body with enhanced element
1381
+ this._body = new EnhancedMockElement('body');
1382
+ this._document.appendChild(this._body);
1383
+
1384
+ // Mock window with configurable options
1385
+ this._window = new MockWindow(options);
1386
+
1387
+ // Link document to window
1388
+ this._body._document = this;
1389
+ this.activeElement = null;
1390
+
1391
+ // Expose MutationObserver constructor
1392
+ this.MutationObserver = MockMutationObserver;
1393
+ }
1394
+
1395
+ createElement(tagName) {
1396
+ const el = new EnhancedMockElement(tagName);
1397
+ el._document = this;
1398
+ return el;
1399
+ }
1400
+
1401
+ /**
1402
+ * Get computed style for an element.
1403
+ */
1404
+ getComputedStyle(element) {
1405
+ return this._window.getComputedStyle(element);
1406
+ }
1407
+
1408
+ /**
1409
+ * Get the mock window object.
1410
+ */
1411
+ getWindow() {
1412
+ return this._window;
1413
+ }
1414
+
1415
+ /**
1416
+ * Request animation frame.
1417
+ */
1418
+ requestAnimationFrame(callback) {
1419
+ return this._window.requestAnimationFrame(callback);
1420
+ }
1421
+
1422
+ /**
1423
+ * Cancel animation frame.
1424
+ */
1425
+ cancelAnimationFrame(id) {
1426
+ this._window.cancelAnimationFrame(id);
1427
+ }
1428
+
1429
+ /**
1430
+ * Get performance API.
1431
+ */
1432
+ getPerformance() {
1433
+ return this._window.performance;
1434
+ }
1435
+
1436
+ /**
1437
+ * Match media query.
1438
+ */
1439
+ matchMedia(query) {
1440
+ return this._window.matchMedia(query);
1441
+ }
1442
+
1443
+ /**
1444
+ * Create a MutationObserver.
1445
+ */
1446
+ createMutationObserver(callback) {
1447
+ return new MockMutationObserver(callback);
1448
+ }
1449
+
1450
+ /**
1451
+ * Get document element (html).
1452
+ */
1453
+ getDocumentElement() {
1454
+ return this._document;
1455
+ }
1456
+
1457
+ /**
1458
+ * Get active element.
1459
+ */
1460
+ getActiveElement() {
1461
+ return this.activeElement;
1462
+ }
1463
+
1464
+ /**
1465
+ * Set active element (for testing).
1466
+ */
1467
+ setActiveElement(element) {
1468
+ this.activeElement = element;
1469
+ }
1470
+
1471
+ /**
1472
+ * Get element by ID.
1473
+ */
1474
+ getElementById(id) {
1475
+ return this._findById(this._body, id);
1476
+ }
1477
+
1478
+ // Test helpers
1479
+
1480
+ /**
1481
+ * Set media query result (for testing user preferences).
1482
+ * @param {string} query - Media query string
1483
+ * @param {boolean} matches - Whether the query matches
1484
+ */
1485
+ setMediaQueryResult(query, matches) {
1486
+ this._window.setMediaQueryResult(query, matches);
1487
+ }
1488
+
1489
+ /**
1490
+ * Run all pending animation frames (for testing).
1491
+ */
1492
+ flushAnimationFrames() {
1493
+ this._window.flushAnimationFrames();
1494
+ }
1495
+
1496
+ /**
1497
+ * Reset the mock DOM state.
1498
+ */
1499
+ reset() {
1500
+ super.reset();
1501
+ this._body = new EnhancedMockElement('body');
1502
+ this._body._document = this;
1503
+ this._document.childNodes = [];
1504
+ this._document.appendChild(this._body);
1505
+ this.activeElement = null;
1506
+ }
1507
+
1508
+ /**
1509
+ * Install global mocks for browser testing.
1510
+ * Installs mocks on globalThis for modules that directly access browser APIs.
1511
+ * @returns {Function} Cleanup function to restore original globals
1512
+ */
1513
+ installGlobalMocks() {
1514
+ const originals = {
1515
+ document: globalThis.document,
1516
+ window: globalThis.window,
1517
+ getComputedStyle: globalThis.getComputedStyle,
1518
+ requestAnimationFrame: globalThis.requestAnimationFrame,
1519
+ cancelAnimationFrame: globalThis.cancelAnimationFrame,
1520
+ MutationObserver: globalThis.MutationObserver,
1521
+ performance: globalThis.performance
1522
+ };
1523
+
1524
+ // Create mock document
1525
+ globalThis.document = {
1526
+ body: this._body,
1527
+ documentElement: this._document,
1528
+ activeElement: null,
1529
+ createElement: (tag) => this.createElement(tag),
1530
+ createTextNode: (text) => this.createTextNode(text),
1531
+ createComment: (data) => this.createComment(data),
1532
+ createDocumentFragment: () => this.createDocumentFragment(),
1533
+ querySelector: (sel) => this.querySelector(sel),
1534
+ querySelectorAll: (sel) => this._body.querySelectorAll(sel),
1535
+ getElementById: (id) => this.getElementById(id),
1536
+ addEventListener: (e, h, o) => this._window.addEventListener(e, h, o),
1537
+ removeEventListener: (e, h, o) => this._window.removeEventListener(e, h, o)
1538
+ };
1539
+
1540
+ globalThis.window = this._window;
1541
+ globalThis.getComputedStyle = (el) => this.getComputedStyle(el);
1542
+ globalThis.requestAnimationFrame = (cb) => this.requestAnimationFrame(cb);
1543
+ globalThis.cancelAnimationFrame = (id) => this.cancelAnimationFrame(id);
1544
+ globalThis.MutationObserver = MockMutationObserver;
1545
+ globalThis.performance = this._window.performance;
1546
+
1547
+ return () => {
1548
+ globalThis.document = originals.document;
1549
+ globalThis.window = originals.window;
1550
+ globalThis.getComputedStyle = originals.getComputedStyle;
1551
+ globalThis.requestAnimationFrame = originals.requestAnimationFrame;
1552
+ globalThis.cancelAnimationFrame = originals.cancelAnimationFrame;
1553
+ globalThis.MutationObserver = originals.MutationObserver;
1554
+ globalThis.performance = originals.performance;
1555
+ };
1556
+ }
1557
+ }
1558
+
904
1559
  // ============================================================================
905
1560
  // Exports
906
1561
  // ============================================================================
@@ -908,11 +1563,19 @@ export function withAdapter(adapter, fn) {
908
1563
  export default {
909
1564
  BrowserDOMAdapter,
910
1565
  MockDOMAdapter,
1566
+ EnhancedMockAdapter,
911
1567
  MockNode,
912
1568
  MockElement,
1569
+ EnhancedMockElement,
913
1570
  MockTextNode,
914
1571
  MockCommentNode,
915
1572
  MockDocumentFragment,
1573
+ MockCanvasContext,
1574
+ MockMediaQueryList,
1575
+ MockMutationObserver,
1576
+ MockPerformance,
1577
+ MockCSSStyleDeclaration,
1578
+ MockWindow,
916
1579
  getAdapter,
917
1580
  setAdapter,
918
1581
  resetAdapter,