@equinor/fusion-observable
Version:
154 lines (137 loc) • 4.64 kB
text/typescript
import { Observable, throwError } from 'rxjs';
import { filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { ReactiveObservable } from '../ReactiveObservable';
import type { Epic } from '../types';
type State = { value: string; index: number };
type Action = { type: string; payload: string };
const createTest = (initial: string) => {
const subject = new ReactiveObservable<State, Action>(
(state, action) => {
return { value: action.payload, index: state.index + 1 };
},
{ value: initial, index: 0 },
);
return subject;
};
describe('Reactive observable', () => {
it('should allow to subscribe', (done) => {
const expected = ['initial', 'test'];
const subject = createTest(expected[0]);
subject.subscribe({
next: (next) => {
expect(next.value).toEqual(expected[next.index]);
},
complete: () => {
expect.assertions(expected.length);
done();
},
});
subject.next({ type: 'test', payload: expected[1] });
subject.complete();
});
it('should allow multiple subscriptions', (done) => {
const expected = ['initial', 'test'];
const subject = createTest(expected[0]);
const assert = (state: State) => expect(state.value).toEqual(expected[state.index]);
subject.subscribe(assert);
subject.subscribe(assert);
subject.subscribe(assert);
subject.subscribe({
complete: () => {
expect.assertions(expected.length * 3);
done();
},
});
subject.next({ type: 'test', payload: expected[1] });
subject.complete();
});
it('should unsubscribe', (complete) => {
const expected = ['initial', 'test1', 'test2'];
const subject = createTest(expected[0]);
const cache: State[] = [];
subject.subscribe((x) => cache.push(x));
expect(cache).toHaveLength(1);
subject.next({ type: 'test', payload: expected[1] });
expect(cache).toHaveLength(2);
subject.unsubscribe();
try {
subject.next({ type: 'test', payload: expected[2] });
} catch (err) {
expect(err).toBeInstanceOf(Error);
complete();
}
});
it('should failed to add invalid epic', () => {
const subject = createTest('test');
const invalidEpic = () => null;
try {
subject.addEpic(invalidEpic as unknown as Epic<Action, State>);
expect(true).toBeFalsy();
} catch (err) {
expect(err).toBeInstanceOf(TypeError);
}
});
it('it should handle epics that fails', () => {
const subject = createTest('test');
const epic = () => throwError(() => Error('as expected'));
const consoleError = console.error;
console.error = jest.fn();
subject.addEpic(epic);
subject.next({ type: 'test', payload: '' });
expect(console.error).toBeCalledTimes(1);
console.warn = consoleError;
});
it('should reset to initial value', () => {
const expected = ['initial', 'test'];
const subject = createTest(expected[0]);
const initial = subject.value;
subject.next({ type: 'test', payload: expected[1] });
expect(subject.value.value).toEqual(expected[1]);
subject.reset();
expect(subject.value).toEqual(initial);
});
it('should convert to observable', () => {
const subject = createTest('test');
const observable = subject.asObservable();
expect(observable).toBeInstanceOf(Observable);
});
it('should handle side effects', (done) => {
const expectedValues = ['initial', 'dispatched', 'effect'];
const expectedActions = [
{ type: 'dispatch', payload: expectedValues[1] },
{ type: 'effect', payload: expectedValues[2] },
];
const assertState = (state: State) => expect(state.value).toEqual(expectedValues[state.index]);
const subject = createTest(expectedValues[0]);
subject.subscribe({
next: (state) => {
assertState(state);
},
complete: () => {
done();
},
});
subject.addEpic((action$, state$) => {
return action$.pipe(
filter((x) => x === expectedActions[0]),
withLatestFrom(state$),
tap(([action, state]) => {
expect(action).toEqual(expectedActions[0]);
assertState(state);
}),
map(() => expectedActions[1]),
);
});
const consoleWarn = console.warn;
console.warn = jest.fn();
subject.addEffect(expectedActions[0].type, () => {
throw Error('Should not bork code!');
});
subject.addEffect('effect', () => {
expect(console.warn).toBeCalledTimes(1);
console.warn = consoleWarn;
subject.complete();
});
subject.next(expectedActions[0]);
});
});