123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- import {addNumberReducer, FakePrefs} from "test/unit/utils";
- import {createStore} from "redux";
- import injector from "inject!lib/Store.jsm";
- describe("Store", () => {
- let Store;
- let sandbox;
- let store;
- let dbStub;
- beforeEach(() => {
- sandbox = sinon.createSandbox();
- function ActivityStreamMessageChannel(options) {
- this.dispatch = options.dispatch;
- this.createChannel = sandbox.spy();
- this.destroyChannel = sandbox.spy();
- this.middleware = sandbox.spy(s => next => action => next(action));
- this.simulateMessagesForExistingTabs = sandbox.stub();
- }
- dbStub = sandbox.stub().resolves();
- function FakeActivityStreamStorage() {
- this.db = {};
- sinon.stub(this, "db").get(dbStub);
- }
- ({Store} = injector({
- "lib/ActivityStreamMessageChannel.jsm": {ActivityStreamMessageChannel},
- "lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
- "lib/ActivityStreamStorage.jsm": {ActivityStreamStorage: FakeActivityStreamStorage},
- }));
- store = new Store();
- sandbox.stub(store, "_initIndexedDB").resolves();
- });
- afterEach(() => {
- sandbox.restore();
- });
- it("should have a .feeds property that is a Map", () => {
- assert.instanceOf(store.feeds, Map);
- assert.equal(store.feeds.size, 0, ".feeds.size");
- });
- it("should have a redux store at ._store", () => {
- assert.ok(store._store);
- assert.property(store, "dispatch");
- assert.property(store, "getState");
- });
- it("should create a ActivityStreamMessageChannel with the right dispatcher", () => {
- assert.ok(store._messageChannel);
- assert.equal(store._messageChannel.dispatch, store.dispatch);
- });
- it("should connect the ActivityStreamMessageChannel's middleware", () => {
- store.dispatch({type: "FOO"});
- assert.calledOnce(store._messageChannel.middleware);
- });
- describe("#initFeed", () => {
- it("should add an instance of the feed to .feeds", () => {
- class Foo {}
- store._prefs.set("foo", true);
- store.init(new Map([["foo", () => new Foo()]]));
- store.initFeed("foo");
- assert.isTrue(store.feeds.has("foo"), "foo is set");
- assert.instanceOf(store.feeds.get("foo"), Foo);
- });
- it("should call the feed's onAction with uninit action if it exists", () => {
- let feed;
- function createFeed() {
- feed = {onAction: sinon.spy()};
- return feed;
- }
- const action = {type: "FOO"};
- store._feedFactories = new Map([["foo", createFeed]]);
- store.initFeed("foo", action);
- assert.calledOnce(feed.onAction);
- assert.calledWith(feed.onAction, action);
- });
- it("should add a .store property to the feed", () => {
- class Foo {}
- store._feedFactories = new Map([["foo", () => new Foo()]]);
- store.initFeed("foo");
- assert.propertyVal(store.feeds.get("foo"), "store", store);
- });
- });
- describe("#uninitFeed", () => {
- it("should not throw if no feed with that name exists", () => {
- assert.doesNotThrow(() => {
- store.uninitFeed("bar");
- });
- });
- it("should call the feed's onAction with uninit action if it exists", () => {
- let feed;
- function createFeed() {
- feed = {onAction: sinon.spy()};
- return feed;
- }
- const action = {type: "BAR"};
- store._feedFactories = new Map([["foo", createFeed]]);
- store.initFeed("foo");
- store.uninitFeed("foo", action);
- assert.calledOnce(feed.onAction);
- assert.calledWith(feed.onAction, action);
- });
- it("should remove the feed from .feeds", () => {
- class Foo {}
- store._feedFactories = new Map([["foo", () => new Foo()]]);
- store.initFeed("foo");
- store.uninitFeed("foo");
- assert.isFalse(store.feeds.has("foo"), "foo is not in .feeds");
- });
- });
- describe("onPrefChanged", () => {
- beforeEach(() => {
- sinon.stub(store, "initFeed");
- sinon.stub(store, "uninitFeed");
- store._prefs.set("foo", false);
- store.init(new Map([["foo", () => ({})]]));
- });
- it("should initialize the feed if called with true", () => {
- store.onPrefChanged("foo", true);
- assert.calledWith(store.initFeed, "foo");
- assert.notCalled(store.uninitFeed);
- });
- it("should uninitialize the feed if called with false", () => {
- store.onPrefChanged("foo", false);
- assert.calledWith(store.uninitFeed, "foo");
- assert.notCalled(store.initFeed);
- });
- it("should do nothing if not an expected feed", () => {
- store.onPrefChanged("bar", false);
- assert.notCalled(store.initFeed);
- assert.notCalled(store.uninitFeed);
- });
- });
- describe("#init", () => {
- it("should call .initFeed with each key", async () => {
- sinon.stub(store, "initFeed");
- store._prefs.set("foo", true);
- store._prefs.set("bar", true);
- await store.init(new Map([["foo", () => {}], ["bar", () => {}]]));
- assert.calledWith(store.initFeed, "foo");
- assert.calledWith(store.initFeed, "bar");
- });
- it("should call _initIndexedDB", async () => {
- await store.init(new Map());
- assert.calledOnce(store._initIndexedDB);
- assert.calledWithExactly(store._initIndexedDB, "feeds.telemetry");
- });
- it("should access the db property of indexedDB", async () => {
- store._initIndexedDB.restore();
- await store.init(new Map());
- assert.calledOnce(dbStub);
- });
- it("should reset ActivityStreamStorage telemetry if opening the db fails", async () => {
- store._initIndexedDB.restore();
- // Force an IndexedDB error
- dbStub.rejects();
- await store.init(new Map());
- assert.calledOnce(dbStub);
- assert.isNull(store.dbStorage.telemetry);
- });
- it("should not initialize the feed if the Pref is set to false", async () => {
- sinon.stub(store, "initFeed");
- store._prefs.set("foo", false);
- await store.init(new Map([["foo", () => {}]]));
- assert.notCalled(store.initFeed);
- });
- it("should observe the pref branch", async () => {
- sinon.stub(store._prefs, "observeBranch");
- await store.init(new Map());
- assert.calledOnce(store._prefs.observeBranch);
- assert.calledWith(store._prefs.observeBranch, store);
- });
- it("should initialize the ActivityStreamMessageChannel channel", async () => {
- await store.init(new Map());
- assert.calledOnce(store._messageChannel.createChannel);
- });
- it("should emit an initial event if provided", async () => {
- sinon.stub(store, "dispatch");
- const action = {type: "FOO"};
- await store.init(new Map(), action);
- assert.calledOnce(store.dispatch);
- assert.calledWith(store.dispatch, action);
- });
- it("should initialize the telemtry feed first", () => {
- store._prefs.set("feeds.foo", true);
- store._prefs.set("feeds.telemetry", true);
- const telemetrySpy = sandbox.stub().returns({});
- const fooSpy = sandbox.stub().returns({});
- // Intentionally put the telemetry feed as the second item.
- const feedFactories = new Map([["feeds.foo", fooSpy],
- ["feeds.telemetry", telemetrySpy]]);
- store.init(feedFactories);
- assert.ok(telemetrySpy.calledBefore(fooSpy));
- });
- it("should dispatch init/load events", async () => {
- await store.init(new Map(), {type: "FOO"});
- assert.calledOnce(store._messageChannel.simulateMessagesForExistingTabs);
- });
- it("should dispatch INIT before LOAD", async () => {
- const init = {type: "INIT"};
- const load = {type: "TAB_LOAD"};
- sandbox.stub(store, "dispatch");
- store._messageChannel.simulateMessagesForExistingTabs.callsFake(() => store.dispatch(load));
- await store.init(new Map(), init);
- assert.calledTwice(store.dispatch);
- assert.equal(store.dispatch.firstCall.args[0], init);
- assert.equal(store.dispatch.secondCall.args[0], load);
- });
- });
- describe("#uninit", () => {
- it("should emit an uninit event if provided on init", () => {
- sinon.stub(store, "dispatch");
- const action = {type: "BAR"};
- store.init(new Map(), null, action);
- store.uninit();
- assert.calledOnce(store.dispatch);
- assert.calledWith(store.dispatch, action);
- });
- it("should clear .feeds and ._feedFactories", () => {
- store._prefs.set("a", true);
- store.init(new Map([
- ["a", () => ({})],
- ["b", () => ({})],
- ["c", () => ({})],
- ]));
- store.uninit();
- assert.equal(store.feeds.size, 0);
- assert.isNull(store._feedFactories);
- });
- it("should destroy the ActivityStreamMessageChannel channel", () => {
- store.uninit();
- assert.calledOnce(store._messageChannel.destroyChannel);
- });
- });
- describe("#getState", () => {
- it("should return the redux state", () => {
- store._store = createStore((prevState = 123) => prevState);
- const {getState} = store;
- assert.equal(getState(), 123);
- });
- });
- describe("#dispatch", () => {
- it("should call .onAction of each feed", async () => {
- const {dispatch} = store;
- const sub = {onAction: sinon.spy()};
- const action = {type: "FOO"};
- store._prefs.set("sub", true);
- await store.init(new Map([["sub", () => sub]]));
- dispatch(action);
- assert.calledWith(sub.onAction, action);
- });
- it("should call the reducers", () => {
- const {dispatch} = store;
- store._store = createStore(addNumberReducer);
- dispatch({type: "ADD", data: 14});
- assert.equal(store.getState(), 14);
- });
- });
- describe("#subscribe", () => {
- it("should subscribe to changes to the store", () => {
- const sub = sinon.spy();
- const action = {type: "FOO"};
- store.subscribe(sub);
- store.dispatch(action);
- assert.calledOnce(sub);
- });
- });
- });
|