MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

event listeners

+680 -5
+6
include/modules/events.h
··· 1 + #ifndef EVENTS_H 2 + #define EVENTS_H 3 + 4 + void init_events_module(void); 5 + 6 + #endif
+1 -1
meson.build
··· 74 74 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 75 75 76 76 version_conf = configuration_data() 77 - version_conf.set('ANT_VERSION', '0.0.8.6') 77 + version_conf.set('ANT_VERSION', '0.0.8.7') 78 78 version_conf.set('ANT_GIT_HASH', git_hash) 79 79 version_conf.set('ANT_BUILD_DATE', build_date) 80 80
+7
src/core/events.js
··· 1 + this.createEventTarget = function () { 2 + const obj = {}; 3 + obj.addEventListener = EventTargetPrototype.addEventListener; 4 + obj.removeEventListener = EventTargetPrototype.removeEventListener; 5 + obj.dispatchEvent = EventTargetPrototype.dispatchEvent; 6 + return obj; 7 + };
+2 -2
src/core/index.js
··· 1 + snapshot_inline('./events.js'); 2 + 1 3 Ant.version = '{{VERSION}}'; 2 4 Ant.revision = '{{GIT_HASH}}'; 3 5 Ant.buildDate = '{{BUILD_DATE}}'; 4 - 5 - Ant.meow = snapshot_include('./meow.js').meow;
-1
src/core/meow.js
··· 1 - export const meow = 'meow';
+2
src/main.c
··· 24 24 #include "modules/process.h" 25 25 #include "modules/path.h" 26 26 #include "modules/ffi.h" 27 + #include "modules/events.h" 27 28 28 29 int js_result = EXIT_SUCCESS; 29 30 ··· 165 166 init_server_module(); 166 167 init_timer_module(); 167 168 init_process_module(); 169 + init_events_module(); 168 170 169 171 ant_register_library("ant:fs", fs_library); 170 172 ant_register_library("ant:shell", shell_library);
+374
src/modules/events.c
··· 1 + #include <stdlib.h> 2 + #include <stdio.h> 3 + #include <string.h> 4 + #include <inttypes.h> 5 + 6 + #include "ant.h" 7 + #include "runtime.h" 8 + #include "modules/events.h" 9 + #include "uthash.h" 10 + 11 + #define MAX_LISTENERS_PER_EVENT 32 12 + 13 + typedef struct { 14 + jsval_t listener; 15 + bool once; 16 + } EventListener; 17 + 18 + typedef struct { 19 + char *event_type; 20 + EventListener listeners[MAX_LISTENERS_PER_EVENT]; 21 + int listener_count; 22 + UT_hash_handle hh; 23 + } EventType; 24 + 25 + typedef struct { 26 + uint64_t target_id; 27 + EventType *events; 28 + UT_hash_handle hh; 29 + } TargetEvents; 30 + 31 + static TargetEvents *target_events_map = NULL; 32 + 33 + static TargetEvents *get_or_create_target_events(jsval_t target) { 34 + uint64_t target_id = target; 35 + TargetEvents *te = NULL; 36 + 37 + HASH_FIND(hh, target_events_map, &target_id, sizeof(uint64_t), te); 38 + 39 + if (te == NULL) { 40 + te = malloc(sizeof(TargetEvents)); 41 + te->target_id = target_id; 42 + te->events = NULL; 43 + HASH_ADD(hh, target_events_map, target_id, sizeof(uint64_t), te); 44 + } 45 + 46 + return te; 47 + } 48 + 49 + static EventType *find_or_create_event_type(jsval_t target, const char *event_type) { 50 + TargetEvents *te = get_or_create_target_events(target); 51 + EventType *evt = NULL; 52 + 53 + HASH_FIND_STR(te->events, event_type, evt); 54 + 55 + if (evt == NULL) { 56 + evt = malloc(sizeof(EventType)); 57 + evt->event_type = strdup(event_type); 58 + evt->listener_count = 0; 59 + HASH_ADD_KEYPTR(hh, te->events, evt->event_type, strlen(evt->event_type), evt); 60 + } 61 + 62 + return evt; 63 + } 64 + 65 + static EventType *find_event_type(jsval_t target, const char *event_type) { 66 + uint64_t target_id = target; 67 + TargetEvents *te = NULL; 68 + 69 + HASH_FIND(hh, target_events_map, &target_id, sizeof(uint64_t), te); 70 + 71 + if (te == NULL) { 72 + return NULL; 73 + } 74 + 75 + EventType *evt = NULL; 76 + HASH_FIND_STR(te->events, event_type, evt); 77 + 78 + return evt; 79 + } 80 + 81 + // addEventListener(eventType, listener, options) 82 + static jsval_t js_add_event_listener_method(struct js *js, jsval_t *args, int nargs) { 83 + jsval_t this_obj = js_getthis(js); 84 + 85 + if (nargs < 2) { 86 + return js_mkerr(js, "addEventListener requires at least 2 arguments (eventType, listener)"); 87 + } 88 + 89 + char *event_type = js_getstr(js, args[0], NULL); 90 + if (event_type == NULL) { 91 + return js_mkerr(js, "eventType must be a string"); 92 + } 93 + 94 + if (js_type(args[1]) != JS_PRIV) { 95 + return js_mkerr(js, "listener must be a function"); 96 + } 97 + 98 + EventType *evt = find_or_create_event_type(this_obj, event_type); 99 + if (evt == NULL) { 100 + return js_mkerr(js, "failed to create event type"); 101 + } 102 + 103 + if (evt->listener_count >= MAX_LISTENERS_PER_EVENT) { 104 + return js_mkerr(js, "maximum number of listeners for event type '%s' reached", event_type); 105 + } 106 + 107 + bool once = false; 108 + if (nargs >= 3 && js_type(args[2]) != JS_UNDEF) { 109 + jsval_t once_val = js_get(js, args[2], "once"); 110 + if (js_type(once_val) != JS_UNDEF) once = js_truthy(js, once_val); 111 + } 112 + 113 + EventListener *listener = &evt->listeners[evt->listener_count++]; 114 + listener->listener = args[1]; 115 + listener->once = once; 116 + 117 + return js_mkundef(); 118 + } 119 + 120 + // addEventListener(eventType, listener, options) 121 + static jsval_t js_add_event_listener(struct js *js, jsval_t *args, int nargs) { 122 + jsval_t global = js_glob(js); 123 + 124 + if (nargs < 2) { 125 + return js_mkerr(js, "addEventListener requires at least 2 arguments (eventType, listener)"); 126 + } 127 + 128 + char *event_type = js_getstr(js, args[0], NULL); 129 + if (event_type == NULL) { 130 + return js_mkerr(js, "eventType must be a string"); 131 + } 132 + 133 + if (js_type(args[1]) != JS_PRIV) { 134 + return js_mkerr(js, "listener must be a function"); 135 + } 136 + 137 + EventType *evt = find_or_create_event_type(global, event_type); 138 + if (evt == NULL) { 139 + return js_mkerr(js, "failed to create event type"); 140 + } 141 + 142 + if (evt->listener_count >= MAX_LISTENERS_PER_EVENT) { 143 + return js_mkerr(js, "maximum number of listeners for event type '%s' reached", event_type); 144 + } 145 + 146 + bool once = false; 147 + if (nargs >= 3 && js_type(args[2]) != JS_UNDEF) { 148 + jsval_t once_val = js_get(js, args[2], "once"); 149 + if (js_type(once_val) != JS_UNDEF) once = js_truthy(js, once_val); 150 + } 151 + 152 + EventListener *listener = &evt->listeners[evt->listener_count++]; 153 + listener->listener = args[1]; 154 + listener->once = once; 155 + 156 + return js_mkundef(); 157 + } 158 + 159 + // removeEventListener(eventType, listener) 160 + static jsval_t js_remove_event_listener_method(struct js *js, jsval_t *args, int nargs) { 161 + jsval_t this_obj = js_getthis(js); 162 + 163 + if (nargs < 2) { 164 + return js_mkerr(js, "removeEventListener requires 2 arguments (eventType, listener)"); 165 + } 166 + 167 + char *event_type = js_getstr(js, args[0], NULL); 168 + if (event_type == NULL) { 169 + return js_mkerr(js, "eventType must be a string"); 170 + } 171 + 172 + EventType *evt = find_event_type(this_obj, event_type); 173 + if (evt == NULL) { 174 + return js_mkundef(); 175 + } 176 + 177 + for (int i = 0; i < evt->listener_count; i++) { 178 + if (evt->listeners[i].listener == args[1]) { 179 + for (int j = i; j < evt->listener_count - 1; j++) { 180 + evt->listeners[j] = evt->listeners[j + 1]; 181 + } 182 + evt->listener_count--; 183 + break; 184 + } 185 + } 186 + 187 + return js_mkundef(); 188 + } 189 + 190 + // removeEventListener(eventType, listener) 191 + static jsval_t js_remove_event_listener(struct js *js, jsval_t *args, int nargs) { 192 + jsval_t global = js_glob(js); 193 + 194 + if (nargs < 2) { 195 + return js_mkerr(js, "removeEventListener requires 2 arguments (eventType, listener)"); 196 + } 197 + 198 + char *event_type = js_getstr(js, args[0], NULL); 199 + if (event_type == NULL) { 200 + return js_mkerr(js, "eventType must be a string"); 201 + } 202 + 203 + EventType *evt = find_event_type(global, event_type); 204 + if (evt == NULL) { 205 + return js_mkundef(); 206 + } 207 + 208 + for (int i = 0; i < evt->listener_count; i++) { 209 + if (evt->listeners[i].listener == args[1]) { 210 + for (int j = i; j < evt->listener_count - 1; j++) { 211 + evt->listeners[j] = evt->listeners[j + 1]; 212 + } 213 + evt->listener_count--; 214 + break; 215 + } 216 + } 217 + 218 + return js_mkundef(); 219 + } 220 + 221 + // dispatchEvent(eventType, eventData) 222 + static jsval_t js_dispatch_event_method(struct js *js, jsval_t *args, int nargs) { 223 + jsval_t this_obj = js_getthis(js); 224 + 225 + if (nargs < 1) { 226 + return js_mkerr(js, "dispatchEvent requires at least 1 argument (eventType)"); 227 + } 228 + 229 + char *event_type = js_getstr(js, args[0], NULL); 230 + if (event_type == NULL) { 231 + return js_mkerr(js, "eventType must be a string"); 232 + } 233 + 234 + EventType *evt = find_event_type(this_obj, event_type); 235 + if (evt == NULL || evt->listener_count == 0) { 236 + return js_mktrue(); 237 + } 238 + 239 + jsval_t event_obj = js_mkobj(js); 240 + js_set(js, event_obj, "type", args[0]); 241 + js_set(js, event_obj, "target", this_obj); 242 + js_set(js, event_obj, "@@toStringTag", js_mkstr(js, "Event", 5)); 243 + 244 + if (nargs >= 2 && js_type(args[1]) != JS_UNDEF) { 245 + js_set(js, event_obj, "detail", args[1]); 246 + } 247 + 248 + jsval_t listener_args[1] = {event_obj}; 249 + int i = 0; 250 + while (i < evt->listener_count) { 251 + EventListener *listener = &evt->listeners[i]; 252 + jsval_t result = js_call(js, listener->listener, listener_args, 1); 253 + 254 + if (js_type(result) == JS_ERR) { 255 + fprintf(stderr, "Error in event listener for '%s': %s\n", event_type, js_str(js, result)); 256 + } 257 + 258 + if (listener->once) { 259 + for (int j = i; j < evt->listener_count - 1; j++) { 260 + evt->listeners[j] = evt->listeners[j + 1]; 261 + } 262 + evt->listener_count--; 263 + } else { 264 + i++; 265 + } 266 + } 267 + 268 + return js_mktrue(); 269 + } 270 + 271 + // dispatchEvent(eventType, eventData) 272 + static jsval_t js_dispatch_event(struct js *js, jsval_t *args, int nargs) { 273 + jsval_t global = js_glob(js); 274 + 275 + if (nargs < 1) { 276 + return js_mkerr(js, "dispatchEvent requires at least 1 argument (eventType)"); 277 + } 278 + 279 + char *event_type = js_getstr(js, args[0], NULL); 280 + if (event_type == NULL) { 281 + return js_mkerr(js, "eventType must be a string"); 282 + } 283 + 284 + EventType *evt = find_event_type(global, event_type); 285 + if (evt == NULL || evt->listener_count == 0) { 286 + return js_mktrue(); 287 + } 288 + 289 + jsval_t event_obj = js_mkobj(js); 290 + js_set(js, event_obj, "type", args[0]); 291 + js_set(js, event_obj, "@@toStringTag", js_mkstr(js, "Event", 5)); 292 + 293 + if (nargs >= 2 && js_type(args[1]) != JS_UNDEF) { 294 + js_set(js, event_obj, "detail", args[1]); 295 + } 296 + 297 + jsval_t listener_args[1] = {event_obj}; 298 + int i = 0; 299 + while (i < evt->listener_count) { 300 + EventListener *listener = &evt->listeners[i]; 301 + jsval_t result = js_call(js, listener->listener, listener_args, 1); 302 + 303 + if (js_type(result) == JS_ERR) { 304 + fprintf(stderr, "Error in event listener for '%s': %s\n", event_type, js_str(js, result)); 305 + } 306 + 307 + if (listener->once) { 308 + for (int j = i; j < evt->listener_count - 1; j++) { 309 + evt->listeners[j] = evt->listeners[j + 1]; 310 + } 311 + evt->listener_count--; 312 + } else { 313 + i++; 314 + } 315 + } 316 + 317 + return js_mktrue(); 318 + } 319 + 320 + static jsval_t js_get_event_listeners(struct js *js, jsval_t *args, int nargs) { 321 + jsval_t target = (nargs > 0) ? args[0] : js_glob(js); 322 + 323 + EventType *evt, *tmp; 324 + jsval_t result = js_mkobj(js); 325 + 326 + uint64_t target_id = target; 327 + TargetEvents *te = NULL; 328 + 329 + HASH_FIND(hh, target_events_map, &target_id, sizeof(uint64_t), te); 330 + 331 + if (te == NULL) { 332 + return result; 333 + } 334 + 335 + HASH_ITER(hh, te->events, evt, tmp) { 336 + jsval_t listeners_array = js_mkobj(js); 337 + 338 + for (int j = 0; j < evt->listener_count; j++) { 339 + char key[16]; 340 + snprintf(key, sizeof(key), "%d", j); 341 + 342 + jsval_t listener_info = js_mkobj(js); 343 + js_set(js, listener_info, "listener", evt->listeners[j].listener); 344 + js_set(js, listener_info, "once", evt->listeners[j].once ? js_mktrue() : js_mkfalse()); 345 + 346 + js_set(js, listeners_array, key, listener_info); 347 + } 348 + 349 + js_set(js, listeners_array, "length", js_mknum(evt->listener_count)); 350 + js_set(js, result, evt->event_type, listeners_array); 351 + } 352 + 353 + return result; 354 + } 355 + 356 + void init_events_module() { 357 + struct js *js = rt->js; 358 + jsval_t global = js_glob(js); 359 + 360 + js_set(js, global, "addEventListener", js_mkfun(js_add_event_listener)); 361 + js_set(js, global, "removeEventListener", js_mkfun(js_remove_event_listener)); 362 + js_set(js, global, "dispatchEvent", js_mkfun(js_dispatch_event)); 363 + js_set(js, global, "getEventListeners", js_mkfun(js_get_event_listeners)); 364 + 365 + jsval_t event_target_proto = js_mkobj(js); 366 + js_set(js, event_target_proto, "addEventListener", js_mkfun(js_add_event_listener_method)); 367 + js_set(js, event_target_proto, "removeEventListener", js_mkfun(js_remove_event_listener_method)); 368 + js_set(js, event_target_proto, "dispatchEvent", js_mkfun(js_dispatch_event_method)); 369 + js_set(js, event_target_proto, "@@toStringTag", js_mkstr(js, "EventTarget", 11)); 370 + 371 + js_set(js, global, "EventTargetPrototype", event_target_proto); 372 + } 373 + 374 +
+97 -1
src/tools/gen_snapshot.c
··· 216 216 return output; 217 217 } 218 218 219 + static char *process_snapshot_inlines(const char *file_path, const char *content, size_t content_len, module_cache_t *cache, size_t *output_len) { 220 + size_t output_capacity = content_len * 2; 221 + char *output = malloc(output_capacity); 222 + if (!output) return NULL; 223 + 224 + size_t output_pos = 0; 225 + const char *pos = content; 226 + const char *end = content + content_len; 227 + 228 + while (pos < end) { 229 + const char *inline_start = strstr(pos, "snapshot_inline("); 230 + if (!inline_start || inline_start >= end) { 231 + size_t remaining = end - pos; 232 + if (output_pos + remaining >= output_capacity) { 233 + output_capacity = (output_pos + remaining) * 2; 234 + output = realloc(output, output_capacity); 235 + } 236 + memcpy(output + output_pos, pos, remaining); 237 + output_pos += remaining; 238 + break; 239 + } 240 + 241 + memcpy(output + output_pos, pos, inline_start - pos); 242 + output_pos += inline_start - pos; 243 + 244 + const char *quote_start = strchr(inline_start, '\''); 245 + if (!quote_start) quote_start = strchr(inline_start, '"'); 246 + if (!quote_start) { 247 + pos = inline_start + 16; 248 + continue; 249 + } 250 + 251 + char quote_char = *quote_start; 252 + const char *quote_end = strchr(quote_start + 1, quote_char); 253 + if (!quote_end) { 254 + pos = inline_start + 16; 255 + continue; 256 + } 257 + 258 + size_t path_len = quote_end - quote_start - 1; 259 + char *import_path = malloc(path_len + 1); 260 + memcpy(import_path, quote_start + 1, path_len); 261 + import_path[path_len] = '\0'; 262 + 263 + char *resolved_path = resolve_path(file_path, import_path); 264 + free(import_path); 265 + 266 + if (!resolved_path) { 267 + pos = inline_start + 16; 268 + continue; 269 + } 270 + 271 + size_t module_len; 272 + char *module_content = read_file(resolved_path, &module_len); 273 + 274 + if (!module_content) { 275 + fprintf(stderr, "Error: Cannot read inline file: %s\n", resolved_path); 276 + free(resolved_path); 277 + free(output); 278 + return NULL; 279 + } 280 + 281 + free(resolved_path); 282 + 283 + if (output_pos + module_len >= output_capacity) { 284 + output_capacity = (output_pos + module_len) * 2; 285 + output = realloc(output, output_capacity); 286 + } 287 + 288 + memcpy(output + output_pos, module_content, module_len); 289 + output_pos += module_len; 290 + free(module_content); 291 + 292 + const char *paren_close = strchr(quote_end, ')'); 293 + if (paren_close) { 294 + pos = paren_close + 1; 295 + } else { 296 + pos = quote_end + 1; 297 + } 298 + } 299 + 300 + output[output_pos] = '\0'; 301 + *output_len = output_pos; 302 + return output; 303 + } 304 + 219 305 static char *process_snapshot_includes(const char *file_path, const char *content, size_t content_len, module_cache_t *cache, size_t *output_len) { 220 306 size_t output_capacity = content_len * 2; 221 307 char *output = malloc(output_capacity); ··· 448 534 449 535 module_cache_t cache = {0}; 450 536 537 + size_t inlined_len; 538 + char *inlined_code = process_snapshot_inlines(input_file, js_code_original, file_size, &cache, &inlined_len); 539 + 540 + if (!inlined_code) { 541 + fprintf(stderr, "Error: Inline processing failed\n"); 542 + free(js_code_original); 543 + return 1; 544 + } 545 + 451 546 size_t bundled_len; 452 - char *bundled_code = process_snapshot_includes(input_file, js_code_original, file_size, &cache, &bundled_len); 547 + char *bundled_code = process_snapshot_includes(input_file, inlined_code, inlined_len, &cache, &bundled_len); 548 + free(inlined_code); 453 549 454 550 if (!bundled_code) { 455 551 fprintf(stderr, "Error: Module bundling failed\n");
+191
tests/test_events.cjs
··· 1 + // Test Event Listeners 2 + 3 + console.log('Testing Event Listeners...\n'); 4 + 5 + // Test 1: Basic addEventListener and dispatchEvent (global) 6 + console.log('Test 1: Basic global event listener'); 7 + let test1Called = false; 8 + addEventListener('test', (event) => { 9 + console.log(' Event received:', event.type); 10 + test1Called = true; 11 + }); 12 + dispatchEvent('test'); 13 + console.log(' Result:', test1Called ? 'PASS' : 'FAIL'); 14 + 15 + // Test 2: Event with custom data (global) 16 + console.log('\nTest 2: Event with custom data (global)'); 17 + addEventListener('customEvent', (event) => { 18 + console.log(' Event type:', event.type); 19 + console.log(' Event detail:', event.detail); 20 + console.log(' Result: PASS'); 21 + }); 22 + dispatchEvent('customEvent', { message: 'Hello from event!', value: 42 }); 23 + 24 + // Test 3: Multiple listeners for same event (global) 25 + console.log('\nTest 3: Multiple listeners for same event (global)'); 26 + let callCount = 0; 27 + addEventListener('multiEvent', () => { 28 + callCount++; 29 + console.log(' Listener 1 called'); 30 + }); 31 + addEventListener('multiEvent', () => { 32 + callCount++; 33 + console.log(' Listener 2 called'); 34 + }); 35 + addEventListener('multiEvent', () => { 36 + callCount++; 37 + console.log(' Listener 3 called'); 38 + }); 39 + dispatchEvent('multiEvent'); 40 + console.log(' Total calls:', callCount); 41 + console.log(' Result:', callCount === 3 ? 'PASS' : 'FAIL'); 42 + 43 + // Test 4: Once option (global) 44 + console.log('\nTest 4: Once option (listener should only fire once)'); 45 + let onceCount = 0; 46 + addEventListener('onceEvent', () => { 47 + onceCount++; 48 + console.log(' Once listener called (count:', onceCount + ')'); 49 + }, { once: true }); 50 + dispatchEvent('onceEvent'); 51 + dispatchEvent('onceEvent'); 52 + dispatchEvent('onceEvent'); 53 + console.log(' Result:', onceCount === 1 ? 'PASS' : 'FAIL'); 54 + 55 + // Test 5: removeEventListener (global) 56 + console.log('\nTest 5: removeEventListener (global)'); 57 + let removedCount = 0; 58 + function removableListener() { 59 + removedCount++; 60 + console.log(' Listener called'); 61 + } 62 + addEventListener('removeTest', removableListener); 63 + dispatchEvent('removeTest'); 64 + console.log(' First dispatch count:', removedCount); 65 + removeEventListener('removeTest', removableListener); 66 + dispatchEvent('removeTest'); 67 + console.log(' After removal count:', removedCount); 68 + console.log(' Result:', removedCount === 1 ? 'PASS' : 'FAIL'); 69 + 70 + // Test 6: Different event types don't interfere 71 + console.log('\nTest 6: Event type isolation'); 72 + let event1Called = false; 73 + let event2Called = false; 74 + addEventListener('eventType1', () => { event1Called = true; }); 75 + addEventListener('eventType2', () => { event2Called = true; }); 76 + dispatchEvent('eventType1'); 77 + console.log(' After eventType1 - type1:', event1Called, 'type2:', event2Called); 78 + dispatchEvent('eventType2'); 79 + console.log(' After eventType2 - type1:', event1Called, 'type2:', event2Called); 80 + console.log(' Result:', (event1Called && event2Called) ? 'PASS' : 'FAIL'); 81 + 82 + // Test 7: Object-specific event listeners 83 + console.log('\nTest 7: Object-specific event listeners'); 84 + 85 + const emitter1 = createEventTarget(); 86 + const emitter2 = createEventTarget(); 87 + 88 + let emitter1Count = 0; 89 + let emitter2Count = 0; 90 + 91 + emitter1.addEventListener('click', (event) => { 92 + emitter1Count++; 93 + console.log(' Emitter1 clicked! Count:', emitter1Count); 94 + console.log(' Event target:', event.target === emitter1 ? 'correct' : 'wrong'); 95 + }); 96 + 97 + emitter2.addEventListener('click', (event) => { 98 + emitter2Count++; 99 + console.log(' Emitter2 clicked! Count:', emitter2Count); 100 + console.log(' Event target:', event.target === emitter2 ? 'correct' : 'wrong'); 101 + }); 102 + 103 + emitter1.dispatchEvent('click'); 104 + emitter1.dispatchEvent('click'); 105 + emitter2.dispatchEvent('click'); 106 + 107 + console.log(' Emitter1 count:', emitter1Count); 108 + console.log(' Emitter2 count:', emitter2Count); 109 + console.log(' Result:', (emitter1Count === 2 && emitter2Count === 1) ? 'PASS' : 'FAIL'); 110 + 111 + // Test 8: Object events with custom data 112 + console.log('\nTest 8: Object events with custom data'); 113 + const dataEmitter = createEventTarget(); 114 + 115 + dataEmitter.addEventListener('message', (event) => { 116 + console.log(' Message received:', event.detail.text); 117 + console.log(' Sender:', event.detail.sender); 118 + console.log(' Result: PASS'); 119 + }); 120 + 121 + dataEmitter.dispatchEvent('message', { text: 'Hello Object!', sender: 'test' }); 122 + 123 + // Test 9: Object event with once option 124 + console.log('\nTest 9: Object event with once option'); 125 + const onceEmitter = createEventTarget(); 126 + let objOnceCount = 0; 127 + 128 + onceEmitter.addEventListener('onceEvent', () => { 129 + objOnceCount++; 130 + console.log(' Object once listener called (count:', objOnceCount + ')'); 131 + }, { once: true }); 132 + 133 + onceEmitter.dispatchEvent('onceEvent'); 134 + onceEmitter.dispatchEvent('onceEvent'); 135 + console.log(' Result:', objOnceCount === 1 ? 'PASS' : 'FAIL'); 136 + 137 + // Test 10: Object removeEventListener 138 + console.log('\nTest 10: Object removeEventListener'); 139 + const removeEmitter = createEventTarget(); 140 + let objRemoveCount = 0; 141 + 142 + function objRemovableListener() { 143 + objRemoveCount++; 144 + console.log(' Object listener called'); 145 + } 146 + 147 + removeEmitter.addEventListener('remove', objRemovableListener); 148 + removeEmitter.dispatchEvent('remove'); 149 + console.log(' Before removal count:', objRemoveCount); 150 + removeEmitter.removeEventListener('remove', objRemovableListener); 151 + removeEmitter.dispatchEvent('remove'); 152 + console.log(' After removal count:', objRemoveCount); 153 + console.log(' Result:', objRemoveCount === 1 ? 'PASS' : 'FAIL'); 154 + 155 + // Test 11: Multiple objects don't interfere 156 + console.log('\nTest 11: Multiple objects event isolation'); 157 + const objA = createEventTarget(); 158 + const objB = createEventTarget(); 159 + 160 + let objACalled = false; 161 + let objBCalled = false; 162 + 163 + objA.addEventListener('test', () => { 164 + objACalled = true; 165 + console.log(' ObjA test fired'); 166 + }); 167 + objB.addEventListener('test', () => { 168 + objBCalled = true; 169 + console.log(' ObjB test fired'); 170 + }); 171 + 172 + objA.dispatchEvent('test'); 173 + console.log(' After objA dispatch - A:', objACalled, 'B:', objBCalled); 174 + 175 + objBCalled = false; // Reset 176 + objB.dispatchEvent('test'); 177 + console.log(' After objB dispatch - A:', objACalled, 'B:', objBCalled); 178 + console.log(' Result:', (objACalled && objBCalled) ? 'PASS' : 'FAIL'); 179 + 180 + // Test 12: getEventListeners with objects 181 + console.log('\nTest 12: getEventListeners for objects'); 182 + const debugObj = createEventTarget(); 183 + debugObj.addEventListener('debugEvent', () => {}); 184 + debugObj.addEventListener('debugEvent', () => {}); 185 + 186 + const listeners = getEventListeners(debugObj); 187 + console.log(' Has debugEvent listeners:', listeners.debugEvent !== undefined); 188 + console.log(' Number of debugEvent listeners:', listeners.debugEvent?.length || 0); 189 + console.log(' Result:', listeners.debugEvent?.length === 2 ? 'PASS' : 'FAIL'); 190 + 191 + console.log('\n=== All event listener tests completed! ===');