mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add simple pubsub
This commit is contained in:
parent
b6d845a4d0
commit
54f2acf5b9
117
invokeai/frontend/web/src/common/util/PubSub/PubSub.test.ts
Normal file
117
invokeai/frontend/web/src/common/util/PubSub/PubSub.test.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { PubSub } from 'common/util/PubSub/PubSub';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
describe('PubSub', () => {
|
||||||
|
it('should call listener when value is published and value changes', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener);
|
||||||
|
pubsub.publish(42);
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith(42, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call listener if value does not change', () => {
|
||||||
|
const pubsub = new PubSub<number>(42);
|
||||||
|
const listener = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener);
|
||||||
|
pubsub.publish(42);
|
||||||
|
|
||||||
|
expect(listener).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-primitive values', () => {
|
||||||
|
const pubsub = new PubSub<{ foo: string }>({ foo: 'bar' });
|
||||||
|
const listener = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener);
|
||||||
|
pubsub.publish({ foo: 'bar' });
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call listener with old and new value', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener);
|
||||||
|
pubsub.publish(2);
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith(2, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow unsubscribing', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener1 = vi.fn();
|
||||||
|
const listener2 = vi.fn();
|
||||||
|
|
||||||
|
const unsubscribe = pubsub.subscribe(listener1);
|
||||||
|
pubsub.subscribe(listener2);
|
||||||
|
unsubscribe();
|
||||||
|
pubsub.publish(42);
|
||||||
|
|
||||||
|
expect(listener1).not.toHaveBeenCalled();
|
||||||
|
expect(listener2).toHaveBeenCalled();
|
||||||
|
expect(pubsub.getListeners().size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear all listeners', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener1 = vi.fn();
|
||||||
|
const listener2 = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener1);
|
||||||
|
pubsub.subscribe(listener2);
|
||||||
|
pubsub.off();
|
||||||
|
pubsub.publish(42);
|
||||||
|
|
||||||
|
expect(listener1).not.toHaveBeenCalled();
|
||||||
|
expect(listener2).not.toHaveBeenCalled();
|
||||||
|
expect(pubsub.getListeners().size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom compareFn', () => {
|
||||||
|
const compareFn = (a: number, b: number) => Math.abs(a) === Math.abs(b);
|
||||||
|
const pubsub = new PubSub<number>(1, compareFn);
|
||||||
|
const listener = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener);
|
||||||
|
pubsub.publish(-1);
|
||||||
|
|
||||||
|
expect(listener).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple listeners', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener1 = vi.fn();
|
||||||
|
const listener2 = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener1);
|
||||||
|
pubsub.subscribe(listener2);
|
||||||
|
pubsub.publish(42);
|
||||||
|
|
||||||
|
expect(listener1).toHaveBeenCalledWith(42, 1);
|
||||||
|
expect(listener2).toHaveBeenCalledWith(42, 1);
|
||||||
|
expect(pubsub.getListeners().size).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the current value', () => {
|
||||||
|
const pubsub = new PubSub<number>(42);
|
||||||
|
expect(pubsub.getValue()).toBe(42);
|
||||||
|
pubsub.publish(43);
|
||||||
|
expect(pubsub.getValue()).toBe(43);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the listeners', () => {
|
||||||
|
const pubsub = new PubSub<number>(1);
|
||||||
|
const listener1 = vi.fn();
|
||||||
|
const listener2 = vi.fn();
|
||||||
|
|
||||||
|
pubsub.subscribe(listener1);
|
||||||
|
pubsub.subscribe(listener2);
|
||||||
|
|
||||||
|
expect(pubsub.getListeners()).toEqual(new Set([listener1, listener2]));
|
||||||
|
});
|
||||||
|
});
|
76
invokeai/frontend/web/src/common/util/PubSub/PubSub.ts
Normal file
76
invokeai/frontend/web/src/common/util/PubSub/PubSub.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
export type Listener<T> = (newValue: T, oldValue: T) => void;
|
||||||
|
export type CompareFn<T> = (a: T, b: T) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple PubSub implementation.
|
||||||
|
*
|
||||||
|
* @template T The type of the value to be published.
|
||||||
|
* @param initialValue The initial value to publish.
|
||||||
|
*/
|
||||||
|
export class PubSub<T> {
|
||||||
|
private _listeners: Set<Listener<T>> = new Set();
|
||||||
|
private _oldValue: T;
|
||||||
|
private _compareFn: CompareFn<T>;
|
||||||
|
|
||||||
|
public constructor(initialValue: T, compareFn?: CompareFn<T>) {
|
||||||
|
this._oldValue = initialValue;
|
||||||
|
this._compareFn = compareFn || ((a, b) => a === b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to the PubSub.
|
||||||
|
* @param listener The listener to be called when the value is published.
|
||||||
|
* @returns A function that can be called to unsubscribe the listener.
|
||||||
|
*/
|
||||||
|
public subscribe = (listener: Listener<T>): (() => void) => {
|
||||||
|
this._listeners.add(listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.unsubscribe(listener);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes a listener from the PubSub.
|
||||||
|
* @param listener The listener to unsubscribe.
|
||||||
|
*/
|
||||||
|
public unsubscribe = (listener: Listener<T>): void => {
|
||||||
|
this._listeners.delete(listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes a new value to the PubSub.
|
||||||
|
* @param newValue The new value to publish.
|
||||||
|
*/
|
||||||
|
public publish = (newValue: T): void => {
|
||||||
|
if (!this._compareFn(this._oldValue, newValue)) {
|
||||||
|
for (const listener of this._listeners) {
|
||||||
|
listener(newValue, this._oldValue);
|
||||||
|
}
|
||||||
|
this._oldValue = newValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all listeners from the PubSub.
|
||||||
|
*/
|
||||||
|
public off = (): void => {
|
||||||
|
this._listeners.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current value of the PubSub.
|
||||||
|
* @returns The current value of the PubSub.
|
||||||
|
*/
|
||||||
|
public getValue = (): T | undefined => {
|
||||||
|
return this._oldValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the listeners of the PubSub.
|
||||||
|
* @returns The listeners of the PubSub.
|
||||||
|
*/
|
||||||
|
public getListeners = (): Set<Listener<T>> => {
|
||||||
|
return this._listeners;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user