mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:11:53 +08:00
Big refactoring + testing
This commit is contained in:
parent
22c2139dd9
commit
f67bf03cd2
@ -40,120 +40,193 @@ describe('Channel', () => {
|
||||
|
||||
describe('method:addListener', () => {
|
||||
it('should create one listener', () => {
|
||||
const keyValue = 'stringAsKey';
|
||||
const eventName = 'event1';
|
||||
|
||||
channel.addListener(keyValue, jest.fn());
|
||||
expect(channel.listeners(keyValue).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:emit', () => {
|
||||
// todo check if [] or string is returned
|
||||
it('should emit the added listener', () => {
|
||||
const keyValue = 'stringAsKey';
|
||||
const mockListener: Listener = (data: string) => {
|
||||
return data;
|
||||
};
|
||||
const mockReturnValue = ['string1', 'string2', 'string3'];
|
||||
|
||||
channel.addListener(keyValue, mockListener);
|
||||
channel.emit<string>(keyValue, ...mockReturnValue);
|
||||
expect(mockListener).toReturnWith(mockReturnValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:addPeerListener', () => {});
|
||||
|
||||
// todo before, addListener was called with numbers; is this still the correct test?
|
||||
describe('method:addListener', () => {
|
||||
it('should call channel.on with args', () => {
|
||||
const testFn = jest.fn();
|
||||
channel.on = jest.fn();
|
||||
channel.addListener('A', testFn);
|
||||
expect(channel.on).toHaveBeenCalled();
|
||||
expect(channel.on).toHaveBeenCalledWith('A', testFn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:emit', () => {
|
||||
it('should call transport.send', () => {
|
||||
transport.send = jest.fn();
|
||||
const type = 'test-type';
|
||||
const args = [1, 2, 3];
|
||||
const expected = { type, args };
|
||||
|
||||
channel.emit(type, ...args);
|
||||
expect(transport.send).toHaveBeenCalled();
|
||||
|
||||
const event: ChannelEvent = transport.send.mock.calls[0][0];
|
||||
expect(typeof event.from).toEqual('string');
|
||||
|
||||
delete event.from;
|
||||
expect(event).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should be type safe', () => {
|
||||
transport.send = jest.fn();
|
||||
const type = 'test-type';
|
||||
const args = [1, 2, 3];
|
||||
const expected = { type, args };
|
||||
|
||||
// todo check if generic argument typing works
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
|
||||
it('should call handle async option', () => {
|
||||
transport.send = jest.fn();
|
||||
const type = 'test-type';
|
||||
const args = [1, 2, 3];
|
||||
|
||||
channel = new Channel({ async: true, transport });
|
||||
|
||||
channel.emit(type, ...args);
|
||||
expect(transport.send).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllImmediates();
|
||||
expect(transport.send).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:eventNames', () => {
|
||||
it('should return an array of strings', () => {
|
||||
channel.on('type-1', jest.fn());
|
||||
channel.on('type-2', jest.fn());
|
||||
channel.on('type-2', jest.fn());
|
||||
const expected = ['type-1', 'type-2'];
|
||||
expect(channel.eventNames()).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:listenerCount', () => {
|
||||
it('should return the correct count', () => {
|
||||
channel.on('type-1', jest.fn());
|
||||
channel.on('type-2', jest.fn());
|
||||
channel.on('type-2', jest.fn());
|
||||
expect(channel.listenerCount('type-1')).toEqual(1);
|
||||
expect(channel.listenerCount('type-2')).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:listeners', () => {
|
||||
const fn1 = jest.fn();
|
||||
const fn2 = jest.fn();
|
||||
const fn3 = jest.fn();
|
||||
|
||||
it('should return an array of listeners', () => {
|
||||
channel.on('type-1', fn1);
|
||||
channel.on('type-2', fn2);
|
||||
channel.on('type-2', fn3);
|
||||
expect(channel.listeners('type-1')).toEqual([fn1]);
|
||||
expect(channel.listeners('type-2')).toEqual([fn2, fn3]);
|
||||
channel.addListener(eventName, jest.fn());
|
||||
expect(channel.listeners(eventName).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:on', () => {
|
||||
const fn1 = jest.fn();
|
||||
const fn2 = jest.fn();
|
||||
const fn3 = jest.fn();
|
||||
it('should do the same as addListener', () => {
|
||||
const eventName = 'event1';
|
||||
|
||||
channel.addListener(eventName, jest.fn());
|
||||
expect(channel.listeners(eventName).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:emit', () => {
|
||||
it('should execute the callback fn of a listener', () => {
|
||||
const eventName = 'event1';
|
||||
const listenerInputData = ['string1', 'string2', 'string3'];
|
||||
let listenerOutputData: string[] = null;
|
||||
const mockListener: Listener<string[]> = data => {
|
||||
listenerOutputData = data;
|
||||
};
|
||||
|
||||
channel.addListener(eventName, mockListener);
|
||||
channel.emit<string[]>(eventName, listenerInputData);
|
||||
expect(listenerOutputData).toBe(listenerInputData);
|
||||
});
|
||||
|
||||
it('should be callable with a spread operator as event arguments', () => {
|
||||
const eventName = 'event1';
|
||||
const listenerInputData = ['string1', 'string2', 'string3'];
|
||||
let listenerOutputData: string[] = null;
|
||||
|
||||
channel.addListener<string>(eventName, (...data) => {
|
||||
listenerOutputData = data;
|
||||
});
|
||||
channel.emit<string>(eventName, ...listenerInputData);
|
||||
expect(listenerOutputData).toEqual(listenerInputData);
|
||||
});
|
||||
|
||||
it('should use setImmediate if async is true', () => {
|
||||
channel = new Channel({ async: true, transport });
|
||||
channel.addListener('event1', jest.fn());
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:addPeerListener', () => {
|
||||
it('should add a listener and set ignorePeer to true', () => {
|
||||
const eventName = 'event1';
|
||||
|
||||
channel.addPeerListener(eventName, jest.fn());
|
||||
expect(channel.listeners(eventName)[0].ignorePeer).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:eventNames', () => {
|
||||
it('should return a list of all registered events', () => {
|
||||
const eventNames = ['event1', 'event2', 'event3'];
|
||||
eventNames.forEach(eventName => channel.addListener(eventName, jest.fn()));
|
||||
|
||||
expect(channel.eventNames()).toEqual(eventNames);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:listenerCount', () => {
|
||||
it('should return a list of all registered events', () => {
|
||||
const events = [
|
||||
{ eventName: 'event1', listeners: [jest.fn(), jest.fn(), jest.fn()], listenerCount: 0 },
|
||||
{ eventName: 'event2', listeners: [jest.fn()], listenerCount: 0 },
|
||||
];
|
||||
events.forEach(event => {
|
||||
event.listeners.forEach(listener => {
|
||||
channel.addListener(event.eventName, listener);
|
||||
event.listenerCount++;
|
||||
});
|
||||
});
|
||||
|
||||
events.forEach(event => {
|
||||
expect(channel.listenerCount(event.eventName)).toBe(event.listenerCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:once', () => {
|
||||
it('should execute a listener once and remove it afterwards', () => {
|
||||
const eventName = 'event1';
|
||||
channel.once(eventName, jest.fn());
|
||||
channel.emit(eventName);
|
||||
|
||||
expect(channel.listenerCount(eventName)).toBe(0);
|
||||
});
|
||||
|
||||
it('should pass all event arguments correctly to the listener', () => {
|
||||
const eventName = 'event1';
|
||||
const listenerInputData = ['string1', 'string2', 'string3'];
|
||||
let listenerOutputData = null;
|
||||
const mockListener: Listener<string[]> = (data: string[]) => {
|
||||
listenerOutputData = data;
|
||||
};
|
||||
|
||||
channel.once<string[]>(eventName, args => mockListener(args));
|
||||
channel.emit<string[]>(eventName, listenerInputData);
|
||||
|
||||
expect(listenerOutputData).toEqual(listenerInputData);
|
||||
});
|
||||
|
||||
it('should be removable', () => {
|
||||
const eventName = 'event1';
|
||||
const listenerToBeRemoved = jest.fn();
|
||||
|
||||
channel.once(eventName, listenerToBeRemoved);
|
||||
channel.removeListener(eventName, listenerToBeRemoved);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:prependListener', () => {
|
||||
it('should prepend listener', () => {
|
||||
const eventName = 'event1';
|
||||
const prependFn = jest.fn();
|
||||
channel.addListener(eventName, jest.fn());
|
||||
channel.prependListener(eventName, prependFn);
|
||||
|
||||
expect(channel.listeners(eventName)[0]).toBe(prependFn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:prependOnceListener', () => {
|
||||
it('should prepend listener and remove it after one execution', () => {
|
||||
const eventName = 'event1';
|
||||
const prependFn = jest.fn();
|
||||
const otherFns = [jest.fn(), jest.fn(), jest.fn()];
|
||||
|
||||
otherFns.forEach(fn => channel.addListener(eventName, fn));
|
||||
channel.prependOnceListener(eventName, prependFn);
|
||||
channel.emit(eventName);
|
||||
|
||||
otherFns.forEach(listener => {
|
||||
expect(listener).toBe(
|
||||
channel.listeners(eventName).find(_listener => _listener === listener)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:removeAllListeners', () => {
|
||||
it('should remove all listeners', () => {
|
||||
const eventName1 = 'event1';
|
||||
const eventName2 = 'event2';
|
||||
const listeners1 = [jest.fn(), jest.fn(), jest.fn()];
|
||||
const listeners2 = [jest.fn()];
|
||||
|
||||
listeners1.forEach(fn => channel.addListener(eventName1, fn));
|
||||
listeners2.forEach(fn => channel.addListener(eventName2, fn));
|
||||
channel.removeAllListeners();
|
||||
|
||||
expect(channel.listenerCount(eventName1)).toBe(0);
|
||||
expect(channel.listenerCount(eventName2)).toBe(0);
|
||||
});
|
||||
|
||||
it('should remove all listeners of a certain event', () => {
|
||||
const eventName = 'event1';
|
||||
const listeners = [jest.fn(), jest.fn(), jest.fn()];
|
||||
|
||||
listeners.forEach(fn => channel.addListener(eventName, fn));
|
||||
expect(channel.listenerCount(eventName)).toBe(listeners.length);
|
||||
|
||||
channel.removeAllListeners(eventName);
|
||||
expect(channel.listenerCount(eventName)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('method:removeListener', () => {
|
||||
it('should remove one listener', () => {
|
||||
const eventName = 'event1';
|
||||
const listenerToBeRemoved = jest.fn();
|
||||
const listeners = [jest.fn(), jest.fn()];
|
||||
const findListener = (listener: Listener) =>
|
||||
channel.listeners(eventName).find(_listener => _listener === listener);
|
||||
|
||||
listeners.forEach(fn => channel.addListener(eventName, fn));
|
||||
channel.addListener(eventName, listenerToBeRemoved);
|
||||
expect(findListener(listenerToBeRemoved)).toBe(listenerToBeRemoved);
|
||||
|
||||
channel.removeListener(eventName, listenerToBeRemoved);
|
||||
expect(findListener(listenerToBeRemoved)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,9 +4,9 @@ export interface ChannelTransport {
|
||||
}
|
||||
|
||||
export interface ChannelEvent<TEventArgs = any> {
|
||||
type: string; // todo deprecate in favor of prop name eventName? type totally confused me after I saw eventNames()
|
||||
type: string; // eventName
|
||||
from: string;
|
||||
args: TEventArgs[];
|
||||
args: TEventArgs;
|
||||
}
|
||||
|
||||
export interface Listener<TEventArgs = any> {
|
||||
@ -49,12 +49,12 @@ export class Channel {
|
||||
return !!this._transport;
|
||||
}
|
||||
|
||||
addListener(eventName: string, listener: Listener) {
|
||||
addListener<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
this._events[eventName] = this._events[eventName] || [];
|
||||
this._events[eventName].push(listener);
|
||||
}
|
||||
|
||||
addPeerListener(eventName: string, listener: Listener) {
|
||||
addPeerListener<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs[]>) {
|
||||
const peerListener = listener;
|
||||
peerListener.ignorePeer = true;
|
||||
this.addListener(eventName, peerListener);
|
||||
@ -71,6 +71,7 @@ export class Channel {
|
||||
};
|
||||
|
||||
if (this.isAsync) {
|
||||
// todo I'm not sure how to test this
|
||||
setImmediate(handler);
|
||||
} else {
|
||||
handler();
|
||||
@ -91,22 +92,23 @@ export class Channel {
|
||||
return listeners ? listeners : undefined;
|
||||
}
|
||||
|
||||
once(eventName: string, listener: Listener) {
|
||||
const onceListener = this._onceListener(eventName, listener);
|
||||
this.addListener(eventName, onceListener);
|
||||
once<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
const onceListener: Listener = this._onceListener<TEventArgs>(eventName, listener);
|
||||
this.addListener<TEventArgs>(eventName, onceListener);
|
||||
}
|
||||
|
||||
prependListener(eventName: string, listener: Listener) {
|
||||
prependListener<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
this._events[eventName] = this._events[eventName] || [];
|
||||
this._events[eventName].unshift(listener);
|
||||
}
|
||||
|
||||
prependOnceListener(eventName: string, listener: Listener) {
|
||||
const onceListener = this._onceListener(eventName, listener);
|
||||
// todo 'listener' is getting mutated by _onceListener, therefore: Input fn() !== Output fn(). This makes testing more difficult
|
||||
prependOnceListener<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
const onceListener: Listener = this._onceListener<TEventArgs>(eventName, listener);
|
||||
this.prependListener(eventName, onceListener);
|
||||
}
|
||||
|
||||
removeAllListeners(eventName: string) {
|
||||
removeAllListeners(eventName?: string) {
|
||||
if (!eventName) {
|
||||
this._events = {};
|
||||
} else if (this._events[eventName]) {
|
||||
@ -115,7 +117,7 @@ export class Channel {
|
||||
}
|
||||
|
||||
removeListener(eventName: string, listener: Listener) {
|
||||
const listeners = this._events[eventName];
|
||||
const listeners = this.listeners(eventName);
|
||||
if (listeners) {
|
||||
this._events[eventName] = listeners.filter(l => l !== listener);
|
||||
}
|
||||
@ -124,19 +126,19 @@ export class Channel {
|
||||
/**
|
||||
* @deprecated use addListener
|
||||
*/
|
||||
on(eventName: string, listener: Listener) {
|
||||
this.addListener(eventName, listener);
|
||||
on<TEventArgs = any>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
this.addListener<TEventArgs>(eventName, listener);
|
||||
}
|
||||
|
||||
private _handleEvent(event: ChannelEvent, isPeer = false) {
|
||||
private _handleEvent<TEventArgs = any>(event: ChannelEvent<TEventArgs[]>, isPeer = false) {
|
||||
const listeners = this._events[event.type];
|
||||
if (listeners && (isPeer || event.from !== this._sender)) {
|
||||
listeners.forEach(fn => !(isPeer && fn.ignorePeer) && fn(...event.args));
|
||||
}
|
||||
}
|
||||
|
||||
private _onceListener(eventName: string, listener: Listener) {
|
||||
const onceListener = (...args: any[]) => {
|
||||
private _onceListener<TEventArgs>(eventName: string, listener: Listener<TEventArgs>) {
|
||||
const onceListener: Listener<TEventArgs> = (...args: TEventArgs[]) => {
|
||||
this.removeListener(eventName, onceListener);
|
||||
return listener(...args);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user