d2-ui
Version:
285 lines (216 loc) • 8.5 kB
JavaScript
import { Observable, TestScheduler } from 'rxjs';
import isEqual from 'lodash/isEqual';
import Store from '../Store';
function eventually(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const value = callback();
resolve(value);
} catch (e) {
reject(e);
}
});
});
}
describe('Store', () => {
let store;
let testScheduler;
let asyncCold;
let expectObs;
beforeEach(() => {
// TODO: Move these to a config file, something like https://github.com/ReactiveX/rxjs/blob/master/spec/helpers/testScheduler-ui.ts but then for jest.
testScheduler = new TestScheduler((a, b) => isEqual(a, b));
expectObs = (testScheduler => (...args) => testScheduler.expectObservable(...args))(testScheduler);
asyncCold = (testScheduler => (...args) => testScheduler.createColdObservable(...args))(testScheduler);
store = Store.create();
});
it('should be a function', () => {
expect(Store).toBeInstanceOf(Function);
});
describe('state', () => {
it('should have a state property that is undefined on default', () => {
expect(store.state).toBe(undefined);
});
it('should be initialized after a value was passed to constructor', () => {
store = new Store({ name: 'Mark' });
return eventually(() => {
expect(store.state).toEqual({ name: 'Mark' });
});
});
});
describe('with Promise as initial value', () => {
it('should resolve the promise and set it as the value', () => {
store = new Store(Promise.resolve({ name: 'Mark' }));
return eventually(() => {
expect(store.state).toEqual({ name: 'Mark' });
});
});
it('should reject the value not set the state', () => {
store = new Store(Promise.reject(new Error('Could not load value')));
return eventually(() => {
expect(store.state).toEqual(undefined);
});
});
});
describe('emitState', () => {
it('should have emitted the intial state when the store was initialized with a state', () => {
const onNextSpy = jest.fn();
store = new Store({ name: 'Mark' });
store.subscribe(onNextSpy);
return eventually(() => {
expect(store.state).toEqual({ name: 'Mark' });
});
});
it('should emit the newly set state', () => {
const onNextSpy = jest.fn();
store = new Store();
store.setState({ name: 'Mark' });
store.subscribe(onNextSpy);
expect(onNextSpy).toHaveBeenCalledWith({ name: 'Mark' });
});
it('should emit an error when the initial promise fails', () => {
const onErrorSpy = jest.fn();
store = new Store(Promise.reject(new Error('Could not load value')));
store.subscribe(undefined, onErrorSpy);
return eventually(() => {
expect(onErrorSpy).toHaveBeenCalled();
});
});
it('should not emit an undefined value if no inital value has been given', () => {
const onNextSpy = jest.fn();
store = new Store();
store.subscribe(onNextSpy);
return eventually(() => {
expect(onNextSpy).not.toHaveBeenCalled();
});
});
});
describe('setState', () => {
it('should be a method', () => {
expect(store.setState).toBeInstanceOf(Function);
});
it('should set the state', () => {
store.setState({ name: 'Mark' });
expect(store.state).toEqual({ name: 'Mark' });
});
});
describe('getState', () => {
it('should be a method', () => {
expect(store.getState).toBeInstanceOf(Function);
});
it('should return the set state', () => {
store.setState({ name: 'Mark' });
expect(store.getState()).toEqual({ name: 'Mark' });
});
});
describe('inheritance', () => {
it('should be able to create a functional subclass', () => {
class UserStore extends Store {
}
const userStore = new UserStore({ name: 'Mark', userName: 'markpo' });
return eventually(() => {
expect(userStore).toBeInstanceOf(Store);
expect(userStore.state).toEqual({ name: 'Mark', userName: 'markpo' });
});
});
});
describe('observable methods', () => {
it('should work as expected', (done) => {
new Store({ name: 'Mark' })
.subscribe((value) => {
expect(value).toEqual({ name: 'Mark' });
done();
});
});
it('should throttle the output', () => {
store = Store.create();
const e1 = asyncCold('--a--b--c---|', { a: 'John', b: 'Mark', c: 'Jim' });
const r1 = '--a----------';
const o1 = e1.flatMap((v) => {
store.setState({ name: v });
return store;
})
.throttleTime(100);
expectObs(o1).toBe(r1, { a: { name: 'John' } });
});
it('should still receive replayed values', (done) => {
store = new Store({ name: 'Mark' });
store.setState({ name: 'John' });
store.subscribe((value) => {
expect(value).toEqual({ name: 'John' });
done();
});
});
it('should not call completed', () => {
const completedSpy = jest.fn();
store = new Store({ name: 'Mark' });
store.setState({ name: 'John' });
store.subscribe(() => {}, () => {}, completedSpy);
expect(completedSpy).not.toHaveBeenCalled();
});
});
describe('setSource()', () => {
it('should be a method', () => {
expect(store.setSource).toBeInstanceOf(Function);
});
it('should call setState with the result of the passed Observable', () => {
jest.spyOn(store, 'setState');
store.setSource(Observable.of({ name: 'John' }));
expect(store.setState).toHaveBeenCalled();
});
it('should emit an error when the source emits an error', (done) => {
jest.spyOn(store, 'setState');
store.setSource(Observable.throw('Failed to load'));
expect(store.setState).not.toHaveBeenCalled();
store.subscribe(() => {
}, (error) => {
expect(error).toBe('Rethrown error from source: Failed to load');
done();
});
});
});
describe('create()', () => {
it('should create a store object', () => {
expect(Store.create()).toBeInstanceOf(Store);
});
it('should have initialzed the Store with the value of geInitialState', () => {
store = Store.create({
getInitialState() {
return { name: 'Mark' };
},
});
store.subscribe((value) => {
expect(value).toEqual({ name: 'Mark' });
});
});
it('should add the passed methods onto the created object', () => {
store = Store.create({
getInitialState() {
return { name: 'Mark' };
},
getMyCustomValue() {
},
});
expect(store.getMyCustomValue).toBeInstanceOf(Function);
});
it('should not add getInitialState to the result object', () => {
store = Store.create({
getInitialState() {
return { name: 'Mark' };
},
getMyCustomValue() {
},
});
expect(store.getInitialState).toBe(undefined);
});
it('should copy properties from the object onto the created object', () => {
store = Store.create({
name: 'MyStore',
getMyCustomValue() {
},
});
expect(store.name).toBe('MyStore');
});
});
});