tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_Store.js (11433B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  ActivityStreamMessageChannel:
      8    "resource://newtab/lib/ActivityStreamMessageChannel.sys.mjs",
      9  sinon: "resource://testing-common/Sinon.sys.mjs",
     10  Store: "resource://newtab/lib/Store.sys.mjs",
     11 });
     12 
     13 // This creates the Redux top-level object.
     14 /* globals Redux */
     15 Services.scriptloader.loadSubScript(
     16  "chrome://global/content/vendor/redux.js",
     17  this
     18 );
     19 
     20 add_task(async function test_expected_properties() {
     21  let sandbox = sinon.createSandbox();
     22  let store = new Store();
     23 
     24  Assert.equal(store.feeds.constructor.name, "Map", "Should create a Map");
     25  Assert.equal(store.feeds.size, 0, "Store should start without any feeds.");
     26 
     27  Assert.ok(store._store, "Has a ._store");
     28  Assert.ok(store.dispatch, "Has a .dispatch");
     29  Assert.ok(store.getState, "Has a .getState");
     30 
     31  sandbox.restore();
     32 });
     33 
     34 add_task(async function test_messagechannel() {
     35  let sandbox = sinon.createSandbox();
     36  sandbox
     37    .stub(ActivityStreamMessageChannel.prototype, "middleware")
     38    .returns(() => next => action => next(action));
     39  let store = new Store();
     40 
     41  info(
     42    "Store should create a ActivityStreamMessageChannel with the right dispatcher"
     43  );
     44  Assert.ok(store.getMessageChannel(), "Has a message channel");
     45  Assert.equal(
     46    store.getMessageChannel().dispatch,
     47    store.dispatch,
     48    "MessageChannel.dispatch forwards to store.dispatch"
     49  );
     50  Assert.equal(
     51    store.getMessageChannel(),
     52    store._messageChannel,
     53    "_messageChannel is the member for getMessageChannel()"
     54  );
     55 
     56  store.dispatch({ type: "FOO" });
     57  Assert.ok(
     58    ActivityStreamMessageChannel.prototype.middleware.calledOnce,
     59    "Middleware called."
     60  );
     61  sandbox.restore();
     62 });
     63 
     64 add_task(async function test_initFeed_add_feeds() {
     65  info("Store.initFeed should add an instance of the feed to .feeds");
     66 
     67  let sandbox = sinon.createSandbox();
     68  let store = new Store();
     69  class Foo {}
     70  store._prefs.set("foo", true);
     71  await store.init(new Map([["foo", () => new Foo()]]));
     72  store.initFeed("foo");
     73 
     74  Assert.ok(store.feeds.has("foo"), "foo is set");
     75  Assert.ok(store.feeds.get("foo") instanceof Foo, "Got registered class");
     76  sandbox.restore();
     77 });
     78 
     79 add_task(async function test_initFeed_calls_onAction() {
     80  info("Store should call the feed's onAction with uninit action if it exists");
     81 
     82  let sandbox = sinon.createSandbox();
     83  let store = new Store();
     84  let testFeed;
     85  let createTestFeed = () => {
     86    testFeed = { onAction: sandbox.spy() };
     87    return testFeed;
     88  };
     89  const action = { type: "FOO" };
     90  store._feedFactories = new Map([["test", createTestFeed]]);
     91 
     92  store.initFeed("test", action);
     93 
     94  Assert.ok(testFeed.onAction.calledOnce, "onAction called");
     95  Assert.ok(
     96    testFeed.onAction.calledWith(action),
     97    "onAction called with test action"
     98  );
     99 
    100  info("Store should add a .store property to the feed");
    101  Assert.ok(testFeed.store, "Store exists");
    102  Assert.equal(testFeed.store, store, "Feed store is the Store");
    103  sandbox.restore();
    104 });
    105 
    106 add_task(async function test_initFeed_on_init() {
    107  info("Store should call .initFeed with each key");
    108 
    109  let sandbox = sinon.createSandbox();
    110  let store = new Store();
    111 
    112  sandbox.stub(store, "initFeed");
    113  store._prefs.set("foo", true);
    114  store._prefs.set("bar", true);
    115  await store.init(
    116    new Map([
    117      ["foo", () => {}],
    118      ["bar", () => {}],
    119    ])
    120  );
    121  Assert.ok(store.initFeed.calledWith("foo"), "First test feed initted");
    122  Assert.ok(store.initFeed.calledWith("bar"), "Second test feed initted");
    123  sandbox.restore();
    124 });
    125 
    126 add_task(async function test_disabled_feed() {
    127  info("Store should not initialize the feed if the Pref is set to false");
    128 
    129  let sandbox = sinon.createSandbox();
    130  let store = new Store();
    131 
    132  sandbox.stub(store, "initFeed");
    133  store._prefs.set("foo", false);
    134  await store.init(new Map([["foo", () => {}]]));
    135  Assert.ok(store.initFeed.notCalled, ".initFeed not called");
    136 
    137  store._prefs.set("foo", true);
    138 
    139  sandbox.restore();
    140 });
    141 
    142 add_task(async function test_observe_pref_branch() {
    143  info("Store should observe the pref branch");
    144 
    145  let sandbox = sinon.createSandbox();
    146  let store = new Store();
    147 
    148  sandbox.stub(store._prefs, "observeBranch");
    149  await store.init(new Map());
    150  Assert.ok(store._prefs.observeBranch.calledOnce, "observeBranch called once");
    151  Assert.ok(
    152    store._prefs.observeBranch.calledWith(store),
    153    "observeBranch passed the store"
    154  );
    155 
    156  sandbox.restore();
    157 });
    158 
    159 add_task(async function test_emit_initial_event() {
    160  info("Store should emit an initial event if provided");
    161 
    162  let sandbox = sinon.createSandbox();
    163  let store = new Store();
    164 
    165  const action = { type: "FOO" };
    166  sandbox.stub(store, "dispatch");
    167  await store.init(new Map(), action);
    168  Assert.ok(store.dispatch.calledOnce, "Dispatch called once");
    169  Assert.ok(store.dispatch.calledWith(action), "Dispatch called with action");
    170 
    171  sandbox.restore();
    172 });
    173 
    174 add_task(async function test_initialize_telemetry_feed_first() {
    175  info("Store should initialize the telemetry feed first");
    176 
    177  let sandbox = sinon.createSandbox();
    178  let store = new Store();
    179 
    180  store._prefs.set("feeds.foo", true);
    181  store._prefs.set("feeds.telemetry", true);
    182  const telemetrySpy = sandbox.stub().returns({});
    183  const fooSpy = sandbox.stub().returns({});
    184  // Intentionally put the telemetry feed as the second item.
    185  const feedFactories = new Map([
    186    ["feeds.foo", fooSpy],
    187    ["feeds.telemetry", telemetrySpy],
    188  ]);
    189  await store.init(feedFactories);
    190  Assert.ok(telemetrySpy.calledBefore(fooSpy), "Telemetry feed initted first");
    191 
    192  sandbox.restore();
    193 });
    194 
    195 add_task(async function test_dispatch_init_load_events() {
    196  info("Store should dispatch init/load events");
    197 
    198  let sandbox = sinon.createSandbox();
    199  let store = new Store();
    200 
    201  sandbox.stub(store.getMessageChannel(), "simulateMessagesForExistingTabs");
    202  await store.init(new Map(), { type: "FOO" });
    203  Assert.ok(
    204    store.getMessageChannel().simulateMessagesForExistingTabs.calledOnce,
    205    "simulateMessagesForExistingTabs called once"
    206  );
    207 
    208  sandbox.restore();
    209 });
    210 
    211 add_task(async function test_init_before_load() {
    212  info("Store should dispatch INIT before LOAD");
    213 
    214  let sandbox = sinon.createSandbox();
    215  let store = new Store();
    216 
    217  sandbox.stub(store.getMessageChannel(), "simulateMessagesForExistingTabs");
    218  sandbox.stub(store, "dispatch");
    219  const init = { type: "INIT" };
    220  const load = { type: "TAB_LOAD" };
    221  store
    222    .getMessageChannel()
    223    .simulateMessagesForExistingTabs.callsFake(() => store.dispatch(load));
    224  await store.init(new Map(), init);
    225 
    226  Assert.ok(store.dispatch.calledTwice, "Dispatch called twice");
    227  Assert.equal(
    228    store.dispatch.firstCall.args[0],
    229    init,
    230    "First dispatch was for init event"
    231  );
    232  Assert.equal(
    233    store.dispatch.secondCall.args[0],
    234    load,
    235    "Second dispatch was for load event"
    236  );
    237 
    238  sandbox.restore();
    239 });
    240 
    241 add_task(async function test_uninit_feeds() {
    242  info("uninitFeed should not throw if no feed with that name exists");
    243 
    244  let sandbox = sinon.createSandbox();
    245  let store = new Store();
    246 
    247  try {
    248    store.uninitFeed("does-not-exist");
    249    Assert.ok(true, "Didn't throw");
    250  } catch (e) {
    251    Assert.ok(false, "Should not have thrown");
    252  }
    253 
    254  info(
    255    "uninitFeed should call the feed's onAction with uninit action if it exists"
    256  );
    257  let feed;
    258  function createFeed() {
    259    feed = { onAction: sandbox.spy() };
    260    return feed;
    261  }
    262  const action = { type: "BAR" };
    263  store._feedFactories = new Map([["foo", createFeed]]);
    264  store.initFeed("foo");
    265 
    266  store.uninitFeed("foo", action);
    267 
    268  Assert.ok(feed.onAction.calledOnce);
    269  Assert.ok(feed.onAction.calledWith(action));
    270 
    271  info("uninitFeed should remove the feed from .feeds");
    272  Assert.ok(!store.feeds.has("foo"), "foo is not in .feeds");
    273 
    274  sandbox.restore();
    275 });
    276 
    277 add_task(async function test_onPrefChanged() {
    278  let sandbox = sinon.createSandbox();
    279  let store = new Store();
    280  let initFeedStub = sandbox.stub(store, "initFeed");
    281  let uninitFeedStub = sandbox.stub(store, "uninitFeed");
    282  store._prefs.set("foo", false);
    283  store.init(new Map([["foo", () => ({})]]));
    284 
    285  info("onPrefChanged should initialize the feed if called with true");
    286  store.onPrefChanged("foo", true);
    287  Assert.ok(initFeedStub.calledWith("foo"));
    288  Assert.ok(!uninitFeedStub.calledOnce);
    289  initFeedStub.resetHistory();
    290  uninitFeedStub.resetHistory();
    291 
    292  info("onPrefChanged should uninitialize the feed if called with false");
    293  store.onPrefChanged("foo", false);
    294  Assert.ok(uninitFeedStub.calledWith("foo"));
    295  Assert.ok(!initFeedStub.calledOnce);
    296  initFeedStub.resetHistory();
    297  uninitFeedStub.resetHistory();
    298 
    299  info("onPrefChanged should do nothing if not an expected feed");
    300  store.onPrefChanged("bar", false);
    301 
    302  Assert.ok(!initFeedStub.calledOnce);
    303  Assert.ok(!uninitFeedStub.calledOnce);
    304  sandbox.restore();
    305 });
    306 
    307 add_task(async function test_uninit() {
    308  let sandbox = sinon.createSandbox();
    309  let store = new Store();
    310  let dispatchStub = sandbox.stub(store, "dispatch");
    311  const action = { type: "BAR" };
    312  await store.init(new Map(), null, action);
    313  store.uninit();
    314 
    315  Assert.ok(store.dispatch.calledOnce);
    316  Assert.ok(store.dispatch.calledWith(action));
    317 
    318  info("Store.uninit should clear .feeds and ._feedFactories");
    319  store._prefs.set("a", true);
    320  await store.init(
    321    new Map([
    322      ["a", () => ({})],
    323      ["b", () => ({})],
    324      ["c", () => ({})],
    325    ])
    326  );
    327 
    328  store.uninit();
    329 
    330  Assert.equal(store.feeds.size, 0);
    331  Assert.equal(store._feedFactories, null);
    332 
    333  info("Store.uninit should emit an uninit event if provided on init");
    334  dispatchStub.resetHistory();
    335  const uninitAction = { type: "BAR" };
    336  await store.init(new Map(), null, uninitAction);
    337  store.uninit();
    338 
    339  Assert.ok(store.dispatch.calledOnce);
    340  Assert.ok(store.dispatch.calledWith(uninitAction));
    341  sandbox.restore();
    342 });
    343 
    344 add_task(async function test_getState() {
    345  info("Store.getState should return the redux state");
    346  let sandbox = sinon.createSandbox();
    347  let store = new Store();
    348  store._store = Redux.createStore((prevState = 123) => prevState);
    349  const { getState } = store;
    350  Assert.equal(getState(), 123);
    351  sandbox.restore();
    352 });
    353 
    354 /**
    355 * addNumberReducer - a simple dummy reducer for testing that adds a number
    356 */
    357 function addNumberReducer(prevState = 0, action) {
    358  return action.type === "ADD" ? prevState + action.data : prevState;
    359 }
    360 
    361 add_task(async function test_dispatch() {
    362  info("Store.dispatch should call .onAction of each feed");
    363  let sandbox = sinon.createSandbox();
    364  let store = new Store();
    365  const { dispatch } = store;
    366  const sub = { onAction: sinon.spy() };
    367  const action = { type: "FOO" };
    368 
    369  store._prefs.set("sub", true);
    370  await store.init(new Map([["sub", () => sub]]));
    371 
    372  dispatch(action);
    373 
    374  Assert.ok(sub.onAction.calledWith(action));
    375 
    376  info("Sandbox.dispatch should call the reducers");
    377 
    378  store._store = Redux.createStore(addNumberReducer);
    379  dispatch({ type: "ADD", data: 14 });
    380  Assert.equal(store.getState(), 14);
    381 
    382  sandbox.restore();
    383 });
    384 
    385 add_task(async function test_subscribe() {
    386  info("Store.subscribe should subscribe to changes to the store");
    387  let sandbox = sinon.createSandbox();
    388  let store = new Store();
    389  const sub = sandbox.spy();
    390  const action = { type: "FOO" };
    391 
    392  store.subscribe(sub);
    393  store.dispatch(action);
    394 
    395  Assert.ok(sub.calledOnce);
    396  sandbox.restore();
    397 });