mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[ADD] runtime/utils: optional validation to EventBus
Currently the event bus allows sending and listening to arbitrary events, I got got by that when I pushed a fix using `addEventListener` on a bus across an events renaming, and on the other side the fix did nothing anymore. Entirely my fault, but if the list of events sent on a bus is known and documented (e.g. a jsdoc has `@emits` tags) it would make sense for both the listening and the dispatching to also be validated. This proposal performs validation only when the eventbus is created: - in dev mode (which also requires being in a component context) - if an iterable of events is passed to the ctor The dev-mode check might be overkill but it seems like a good idea at least for an initial version, as the validation does have a cost however low, and validation errors can occur essentially anywhere.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { ComponentNode, getCurrent } from "./component_node";
|
||||
export type Callback = () => void;
|
||||
|
||||
/**
|
||||
@@ -81,10 +82,52 @@ export function validateTarget(target: HTMLElement | ShadowRoot) {
|
||||
}
|
||||
|
||||
export class EventBus extends EventTarget {
|
||||
constructor(events?: string[]) {
|
||||
if (events) {
|
||||
let node: ComponentNode | null = null;
|
||||
try {
|
||||
node = getCurrent();
|
||||
} catch {}
|
||||
if (node?.app?.dev) {
|
||||
return new DebugEventBus(events);
|
||||
}
|
||||
}
|
||||
super();
|
||||
}
|
||||
trigger(name: string, payload?: any) {
|
||||
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
||||
}
|
||||
}
|
||||
class DebugEventBus extends EventBus {
|
||||
private events: Set<string>;
|
||||
constructor(events: string[]) {
|
||||
super();
|
||||
this.events = new Set(events);
|
||||
}
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject | null,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void {
|
||||
if (!this.events.has(type)) {
|
||||
throw new OwlError(`EventBus: subscribing to unknown event '${type}'`);
|
||||
}
|
||||
super.addEventListener(type, listener, options);
|
||||
}
|
||||
trigger(name: string, payload?: any) {
|
||||
if (!this.events.has(name)) {
|
||||
throw new OwlError(`EventBus: triggering unknown event '${name}'`);
|
||||
}
|
||||
super.trigger(name, payload);
|
||||
}
|
||||
|
||||
dispatchEvent(event: Event): boolean {
|
||||
if (!this.events.has(event.type)) {
|
||||
throw new OwlError(`EventBus: dispatching unknown event '${event.type}'`);
|
||||
}
|
||||
return super.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
export function whenReady(fn?: any): Promise<void> {
|
||||
return new Promise(function (resolve) {
|
||||
|
||||
+63
-1
@@ -1,5 +1,7 @@
|
||||
import { batched, EventBus, htmlEscape, markup } from "../src/runtime/utils";
|
||||
import { nextMicroTick } from "./helpers";
|
||||
import { makeTestFixture, nextMicroTick } from "./helpers";
|
||||
import { getCurrent } from "../src/runtime/component_node";
|
||||
import { Component, mount, xml } from "../src";
|
||||
|
||||
describe("event bus behaviour", () => {
|
||||
test("can subscribe and be notified", () => {
|
||||
@@ -33,6 +35,66 @@ describe("event bus behaviour", () => {
|
||||
bus.addEventListener("event", (ev: any) => expect(ev.detail).toBe("hello world"));
|
||||
bus.trigger("event", "hello world");
|
||||
});
|
||||
|
||||
test("events are not validated if the bus is created outside of dev mode", async () => {
|
||||
let bus_empty: EventBus | null = null;
|
||||
class Root extends Component {
|
||||
static template = xml`<div/>`;
|
||||
|
||||
setup() {
|
||||
getCurrent(); // checks that we're in a component context
|
||||
|
||||
bus_empty = new EventBus([]);
|
||||
}
|
||||
}
|
||||
await mount(Root, makeTestFixture());
|
||||
|
||||
bus_empty!.addEventListener("a", () => {});
|
||||
bus_empty!.trigger("a");
|
||||
bus_empty!.dispatchEvent(new CustomEvent("a"));
|
||||
});
|
||||
test("events are validated if the bus is created in dev mode & events are provided", async () => {
|
||||
let bus: EventBus | null = null;
|
||||
let bus_empty: EventBus | null = null;
|
||||
let bbus_no_validation: EventBus | null = null;
|
||||
class Root extends Component {
|
||||
static template = xml`<div/>`;
|
||||
|
||||
setup() {
|
||||
getCurrent(); // checks that we're in a component context
|
||||
|
||||
bus = new EventBus(["a", "b"]);
|
||||
bus_empty = new EventBus([]);
|
||||
bbus_no_validation = new EventBus();
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Root, makeTestFixture(), { test: true });
|
||||
|
||||
bbus_no_validation!.addEventListener("c", () => {});
|
||||
bbus_no_validation!.trigger("c");
|
||||
bbus_no_validation!.dispatchEvent(new CustomEvent("c"));
|
||||
|
||||
bus!.addEventListener("a", () => {});
|
||||
bus!.trigger("a");
|
||||
bus!.dispatchEvent(new CustomEvent("a"));
|
||||
|
||||
expect(() => bus!.addEventListener("c", () => {})).toThrow(
|
||||
"EventBus: subscribing to unknown event 'c'"
|
||||
);
|
||||
expect(() => bus!.trigger("c")).toThrow("EventBus: triggering unknown event 'c'");
|
||||
expect(() => bus!.dispatchEvent(new CustomEvent("c"))).toThrow(
|
||||
"EventBus: dispatching unknown event 'c'"
|
||||
);
|
||||
|
||||
expect(() => bus_empty!.addEventListener("a", () => {})).toThrow(
|
||||
"EventBus: subscribing to unknown event 'a'"
|
||||
);
|
||||
expect(() => bus_empty!.trigger("a")).toThrow("EventBus: triggering unknown event 'a'");
|
||||
expect(() => bus_empty!.dispatchEvent(new CustomEvent("a"))).toThrow(
|
||||
"EventBus: dispatching unknown event 'a'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("batched", () => {
|
||||
|
||||
Reference in New Issue
Block a user