-
Notifications
You must be signed in to change notification settings - Fork 39
Use EventTarget rather than the Node "events" module #1188
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 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2c2e7cf
Try another approach to typing event target
microbit-matt-hillsdon d6b119d
Fix event name, inline type-safe constants.
microbit-matt-hillsdon d0fb046
Fix simulator event type
microbit-matt-hillsdon 2d18520
Fix mock test
microbit-matt-hillsdon ba30481
Fixes
microbit-matt-hillsdon f188064
Remove module already gone from package.json
microbit-matt-hillsdon 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
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,122 @@ | ||
/** | ||
* Copyright (c) 2022 Jonas "DerZade" Schade | ||
* | ||
* SPDX-License-Identifier: MIT | ||
* | ||
* https://github.com/DerZade/typescript-event-target/blob/master/src/TypedEventTarget.ts | ||
*/ | ||
|
||
/** | ||
* A function that can be passed to the `listener` parameter of {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. | ||
* | ||
* @template M A map of event types to their respective event classes. | ||
* @template T The type of event to listen for (has to be keyof `M`). | ||
*/ | ||
export type TypedEventListener<M, T extends keyof M> = ( | ||
evt: M[T] | ||
) => void | Promise<void>; | ||
|
||
/** | ||
* An object that can be passed to the `listener` parameter of {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. | ||
* | ||
* @template M A map of event types to their respective event classes. | ||
* @template T The type of event to listen for (has to be keyof `M`). | ||
*/ | ||
export interface TypedEventListenerObject<M, T extends keyof M> { | ||
handleEvent: (evt: M[T]) => void | Promise<void>; | ||
} | ||
|
||
/** | ||
* Type of parameter `listener` in {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. | ||
* | ||
* The object that receives a notification (an object that implements the Event interface) when an event of the specified type occurs. | ||
* | ||
* Can be either an object with a handleEvent() method, or a JavaScript function. | ||
* | ||
* @template M A map of event types to their respective event classes. | ||
* @template T The type of event to listen for (has to be keyof `M`). | ||
*/ | ||
export type TypedEventListenerOrEventListenerObject<M, T extends keyof M> = | ||
| TypedEventListener<M, T> | ||
| TypedEventListenerObject<M, T>; | ||
|
||
type ValueIsEvent<T> = { | ||
[key in keyof T]: Event; | ||
}; | ||
|
||
/** | ||
* Typescript friendly version of {@link EventTarget} | ||
* | ||
* @template M A map of event types to their respective event classes. | ||
* | ||
* @example | ||
* ```typescript | ||
* interface MyEventMap { | ||
* hello: Event; | ||
* time: CustomEvent<number>; | ||
* } | ||
* | ||
* const eventTarget = new TypedEventTarget<MyEventMap>(); | ||
* | ||
* eventTarget.addEventListener('time', (event) => { | ||
* // event is of type CustomEvent<number> | ||
* }); | ||
* ``` | ||
*/ | ||
export interface TypedEventTarget<M extends ValueIsEvent<M>> { | ||
/** Appends an event listener for events whose type attribute value is type. | ||
* The callback argument sets the callback that will be invoked when the event | ||
* is dispatched. | ||
* | ||
* The options argument sets listener-specific options. For compatibility this | ||
* can be a boolean, in which case the method behaves exactly as if the value | ||
* was specified as options's capture. | ||
* | ||
* When set to true, options's capture prevents callback from being invoked | ||
* when the event's eventPhase attribute value is BUBBLING_PHASE. When false | ||
* (or not present), callback will not be invoked when event's eventPhase | ||
* attribute value is CAPTURING_PHASE. Either way, callback will be invoked if | ||
* event's eventPhase attribute value is AT_TARGET. | ||
* | ||
* When set to true, options's passive indicates that the callback will not | ||
* cancel the event by invoking preventDefault(). This is used to enable | ||
* performance optimizations described in § 2.8 Observing event listeners. | ||
* | ||
* When set to true, options's once indicates that the callback will only be | ||
* invoked once after which the event listener will be removed. | ||
* | ||
* The event listener is appended to target's event listener list and is not | ||
* appended if it has the same type, callback, and capture. */ | ||
addEventListener: <T extends keyof M & string>( | ||
type: T, | ||
listener: TypedEventListenerOrEventListenerObject<M, T> | null, | ||
options?: boolean | AddEventListenerOptions | ||
) => void; | ||
|
||
/** Removes the event listener in target's event listener list with the same | ||
* type, callback, and options. */ | ||
removeEventListener: <T extends keyof M & string>( | ||
type: T, | ||
callback: TypedEventListenerOrEventListenerObject<M, T> | null, | ||
options?: EventListenerOptions | boolean | ||
) => void; | ||
|
||
/** | ||
* Dispatches a synthetic event event to target and returns true if either | ||
* event's cancelable attribute value is false or its preventDefault() method | ||
* was not invoked, and false otherwise. | ||
* @deprecated To ensure type safety use `dispatchTypedEvent` instead. | ||
*/ | ||
dispatchEvent: (event: Event) => boolean; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging | ||
export class TypedEventTarget<M extends ValueIsEvent<M>> extends EventTarget { | ||
/** | ||
* Dispatches a synthetic event event to target and returns true if either | ||
* event's cancelable attribute value is false or its preventDefault() method | ||
* was not invoked, and false otherwise. | ||
*/ | ||
public dispatchTypedEvent<T extends keyof M>(_type: T, event: M[T]): boolean { | ||
return super.dispatchEvent(event); | ||
} | ||
} |
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
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
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 |
---|---|---|
|
@@ -3,18 +3,19 @@ | |
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
import { TypedEventTarget } from "../common/events"; | ||
import { | ||
BoardVersion, | ||
ConnectionStatus, | ||
DeviceConnection, | ||
EVENT_FLASH, | ||
EVENT_SERIAL_DATA, | ||
EVENT_STATUS, | ||
DeviceConnectionEventMap, | ||
FlashDataSource, | ||
FlashEvent, | ||
SerialDataEvent, | ||
ConnectionStatusEvent, | ||
WebUSBError, | ||
WebUSBErrorCode, | ||
} from "./device"; | ||
import EventEmitter from "events"; | ||
|
||
/** | ||
* A mock device used during end-to-end testing. | ||
|
@@ -24,7 +25,7 @@ import EventEmitter from "events"; | |
* the connected state without a real device. | ||
*/ | ||
export class MockDeviceConnection | ||
extends EventEmitter | ||
extends TypedEventTarget<DeviceConnectionEventMap> | ||
implements DeviceConnection | ||
{ | ||
status: ConnectionStatus = navigator.usb | ||
|
@@ -40,7 +41,7 @@ export class MockDeviceConnection | |
} | ||
|
||
mockSerialWrite(data: string) { | ||
this.emit(EVENT_SERIAL_DATA, data); | ||
this.dispatchTypedEvent("serial_data", new SerialDataEvent(data)); | ||
} | ||
|
||
mockConnect(code: WebUSBErrorCode) { | ||
|
@@ -49,9 +50,7 @@ export class MockDeviceConnection | |
|
||
async initialize(): Promise<void> {} | ||
|
||
dispose() { | ||
this.removeAllListeners(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't do this anymore as EventTarget doesn't have such API. All listener add/removes are correctly paired except that in host.ts and a test, neither of which need to remove their listeners. |
||
} | ||
dispose() {} | ||
|
||
async connect(): Promise<ConnectionStatus> { | ||
const next = this.connectResults.shift(); | ||
|
@@ -90,7 +89,7 @@ export class MockDeviceConnection | |
options.progress(0.5); | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
options.progress(undefined); | ||
this.emit(EVENT_FLASH); | ||
this.dispatchTypedEvent("flash", new FlashEvent()); | ||
} | ||
|
||
async disconnect(): Promise<void> { | ||
|
@@ -103,7 +102,7 @@ export class MockDeviceConnection | |
|
||
private setStatus(newStatus: ConnectionStatus) { | ||
this.status = newStatus; | ||
this.emit(EVENT_STATUS, this.status); | ||
this.dispatchTypedEvent("status", new ConnectionStatusEvent(this.status)); | ||
} | ||
|
||
clearDevice(): void { | ||
|
Oops, something went wrong.
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.