-
Notifications
You must be signed in to change notification settings - Fork 36
Implements command interpreter for event handling #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,4 +35,5 @@ yarn-error.log* | |
| *.pem | ||
|
|
||
| # System files | ||
| .vscode/* | ||
| .vscode/* | ||
| .idea/* | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| import { describe, it, expect, beforeEach, vi } from 'vitest'; | ||
| import { CoreCommandInterpreter } from './command-interpreter'; | ||
| import type { SystemEvent } from './types/command-interpreter.interface'; | ||
|
|
||
| describe('CoreCommandInterpreter', () => { | ||
| let interpreter: CoreCommandInterpreter; | ||
|
|
||
| beforeEach(() => { | ||
| interpreter = new CoreCommandInterpreter(); | ||
| }); | ||
|
|
||
| describe('emit', () => { | ||
| it('should call all registered callbacks for the event type', () => { | ||
| const commandCallback = vi.fn(); | ||
| const modelChangeCallback = vi.fn(); | ||
| const otherCommandCallback = vi.fn(); | ||
|
|
||
| interpreter.register('command', commandCallback); | ||
| interpreter.register('modelChange', modelChangeCallback); | ||
| interpreter.register('command', otherCommandCallback); | ||
|
|
||
| const commandEvent: SystemEvent = { type: 'command', name: 'test' }; | ||
| interpreter.emit(commandEvent); | ||
|
|
||
| expect(commandCallback).toHaveBeenCalledWith(commandEvent); | ||
| expect(otherCommandCallback).toHaveBeenCalledWith(commandEvent); | ||
| expect(modelChangeCallback).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should not call any callbacks if none are registered for the event type', () => { | ||
| const callback = vi.fn(); | ||
| interpreter.register('modelChange', callback); | ||
|
|
||
| const commandEvent: SystemEvent = { type: 'command', name: 'test' }; | ||
| interpreter.emit(commandEvent); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('register', () => { | ||
| it('should allow registering multiple callbacks for the same event type', () => { | ||
| const callback1 = vi.fn(); | ||
| const callback2 = vi.fn(); | ||
|
|
||
| interpreter.register('command', callback1); | ||
| interpreter.register('command', callback2); | ||
|
|
||
| const event: SystemEvent = { type: 'command', name: 'test' }; | ||
| interpreter.emit(event); | ||
|
|
||
| expect(callback1).toHaveBeenCalledWith(event); | ||
| expect(callback2).toHaveBeenCalledWith(event); | ||
| }); | ||
|
|
||
| it('should allow registering the same callback multiple times', () => { | ||
| const callback = vi.fn(); | ||
|
|
||
| interpreter.register('command', callback); | ||
| interpreter.register('command', callback); | ||
|
|
||
| const event: SystemEvent = { type: 'command', name: 'test' }; | ||
| interpreter.emit(event); | ||
|
|
||
| expect(callback).toHaveBeenCalledTimes(2); | ||
| }); | ||
|
|
||
| it('should preserve callback order', () => { | ||
| const calls: string[] = []; | ||
| const callback1 = () => calls.push('1'); | ||
| const callback2 = () => calls.push('2'); | ||
|
|
||
| interpreter.register('command', callback1); | ||
| interpreter.register('command', callback2); | ||
|
|
||
| interpreter.emit({ type: 'command', name: 'test' }); | ||
|
|
||
| expect(calls).toEqual(['1', '2']); | ||
| }); | ||
| }); | ||
|
|
||
| describe('unregister', () => { | ||
| it('should remove the callback when unregister is called', () => { | ||
| const callback = vi.fn(); | ||
| const unregister = interpreter.register('command', callback); | ||
|
|
||
| unregister(); | ||
| interpreter.emit({ type: 'command', name: 'test' }); | ||
|
|
||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should only remove the specific callback that was unregistered', () => { | ||
| const callback1 = vi.fn(); | ||
| const callback2 = vi.fn(); | ||
|
|
||
| const unregister1 = interpreter.register('command', callback1); | ||
| interpreter.register('command', callback2); | ||
|
|
||
| unregister1(); | ||
| interpreter.emit({ type: 'command', name: 'test' }); | ||
|
|
||
| expect(callback1).not.toHaveBeenCalled(); | ||
| expect(callback2).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should remove the event type from the map when no callbacks remain', () => { | ||
| const callback = vi.fn(); | ||
| const unregister = interpreter.register('command', callback); | ||
|
|
||
| unregister(); | ||
| interpreter.emit({ type: 'command', name: 'test' }); | ||
|
|
||
| // @ts-expect-error - accessing private property for testing | ||
| expect(interpreter.callbacks.has('command')).toBe(false); | ||
| }); | ||
|
|
||
| it('should handle unregistering a callback that was already removed', () => { | ||
| const callback = vi.fn(); | ||
| const unregister = interpreter.register('command', callback); | ||
|
|
||
| unregister(); | ||
| unregister(); // Call again, should not throw | ||
|
|
||
| interpreter.emit({ type: 'command', name: 'test' }); | ||
| expect(callback).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('event data', () => { | ||
| it('should pass command event data to callbacks', () => { | ||
| const callback = vi.fn(); | ||
| interpreter.register('command', callback); | ||
|
|
||
| const eventData = { some: 'data' }; | ||
| interpreter.emit({ type: 'command', name: 'test', data: eventData }); | ||
|
|
||
| expect(callback).toHaveBeenCalledWith({ | ||
| type: 'command', | ||
| name: 'test', | ||
| data: eventData | ||
| }); | ||
| }); | ||
|
|
||
| it('should pass model change event data to callbacks', () => { | ||
| const callback = vi.fn(); | ||
| interpreter.register('modelChange', callback); | ||
|
|
||
| const eventData = { some: 'data' }; | ||
| interpreter.emit({ type: 'modelChange', action: 'update', data: eventData }); | ||
|
|
||
| expect(callback).toHaveBeenCalledWith({ | ||
| type: 'modelChange', | ||
| action: 'update', | ||
| data: eventData | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import type { CommandInterpreter, SystemEvent, SystemEventCallback } from './types/command-interpreter.interface'; | ||
|
|
||
| /** | ||
| * Core implementation of CommandInterpreter interface | ||
| * Handles event emission and registration of callbacks for system events | ||
| */ | ||
| export class CoreCommandInterpreter implements CommandInterpreter { | ||
| private callbacks: Map<SystemEvent['type'], SystemEventCallback[]> = new Map(); | ||
|
|
||
| /** | ||
| * Emit a system event to all registered callbacks for the event type | ||
| * @param event Event to emit | ||
| */ | ||
| emit(event: SystemEvent): void { | ||
| const callbacks = this.callbacks.get(event.type); | ||
| if (callbacks) { | ||
| for (const callback of callbacks) { | ||
| callback(event); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Register a callback for specific event types | ||
| * @param eventType Type of event to listen for | ||
| * @param callback Function to be called when event occurs | ||
| * @returns Function to unregister the callback | ||
| */ | ||
| register(eventType: SystemEvent['type'], callback: SystemEventCallback): () => void { | ||
| if (!this.callbacks.has(eventType)) { | ||
| this.callbacks.set(eventType, []); | ||
| } | ||
|
|
||
| const callbacks = this.callbacks.get(eventType) as SystemEventCallback[]; | ||
| callbacks.push(callback); | ||
|
|
||
| // Return unregister function | ||
| return () => { | ||
| const callbacks = this.callbacks.get(eventType); | ||
| if (callbacks) { | ||
| const index = callbacks.indexOf(callback); | ||
| if (index !== -1) { | ||
| callbacks.splice(index, 1); | ||
| if (callbacks.length === 0) { | ||
| this.callbacks.delete(eventType); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.