tor-browser

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

selectLayoutRender.test.js (17657B)


      1 import { combineReducers, createStore } from "redux";
      2 import { actionTypes as at } from "common/Actions.mjs";
      3 import { GlobalOverrider } from "test/unit/utils";
      4 import { reducers } from "common/Reducers.sys.mjs";
      5 import { selectLayoutRender } from "content-src/lib/selectLayoutRender";
      6 const FAKE_LAYOUT = [
      7  {
      8    width: 3,
      9    components: [
     10      { type: "foo", feed: { url: "foo.com" }, properties: { items: 2 } },
     11    ],
     12  },
     13 ];
     14 const FAKE_FEEDS = {
     15  "foo.com": { data: { recommendations: [{ id: "foo" }, { id: "bar" }] } },
     16 };
     17 
     18 describe("selectLayoutRender", () => {
     19  let store;
     20  let globals;
     21 
     22  beforeEach(() => {
     23    globals = new GlobalOverrider();
     24    store = createStore(combineReducers(reducers));
     25  });
     26 
     27  afterEach(() => {
     28    globals.restore();
     29  });
     30 
     31  it("should return an empty array given initial state", () => {
     32    const { layoutRender } = selectLayoutRender({
     33      state: store.getState().DiscoveryStream,
     34      prefs: {},
     35      rollCache: [],
     36    });
     37    assert.deepEqual(layoutRender, []);
     38  });
     39 
     40  it("should add .data property from feeds to each component in .layout", () => {
     41    store.dispatch({
     42      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
     43      data: { layout: FAKE_LAYOUT },
     44    });
     45    store.dispatch({
     46      type: at.DISCOVERY_STREAM_FEED_UPDATE,
     47      data: { feed: FAKE_FEEDS["foo.com"], url: "foo.com" },
     48    });
     49    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
     50 
     51    const { layoutRender } = selectLayoutRender({
     52      state: store.getState().DiscoveryStream,
     53    });
     54 
     55    assert.lengthOf(layoutRender, 1);
     56    assert.propertyVal(layoutRender[0], "width", 3);
     57    assert.deepEqual(layoutRender[0].components[0], {
     58      type: "foo",
     59      feed: { url: "foo.com" },
     60      properties: { items: 2 },
     61      data: {
     62        recommendations: [
     63          { id: "foo", pos: 0 },
     64          { id: "bar", pos: 1 },
     65        ],
     66        sections: [],
     67      },
     68    });
     69  });
     70 
     71  it("should return layout with placeholder data if feed doesn't have data", () => {
     72    store.dispatch({
     73      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
     74      data: { layout: FAKE_LAYOUT },
     75    });
     76    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
     77 
     78    const { layoutRender } = selectLayoutRender({
     79      state: store.getState().DiscoveryStream,
     80    });
     81 
     82    assert.lengthOf(layoutRender, 1);
     83    assert.propertyVal(layoutRender[0], "width", 3);
     84    assert.deepEqual(layoutRender[0].components[0].data.recommendations, [
     85      { placeholder: true },
     86      { placeholder: true },
     87    ]);
     88  });
     89 
     90  it("should return layout with empty spocs data if feed isn't defined but spocs is", () => {
     91    const fakeLayout = [
     92      {
     93        width: 3,
     94        components: [{ type: "foo", spocs: { positions: [{ index: 2 }] } }],
     95      },
     96    ];
     97    store.dispatch({
     98      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
     99      data: { layout: fakeLayout },
    100    });
    101    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    102 
    103    const { layoutRender } = selectLayoutRender({
    104      state: store.getState().DiscoveryStream,
    105    });
    106 
    107    assert.lengthOf(layoutRender, 1);
    108    assert.propertyVal(layoutRender[0], "width", 3);
    109    assert.deepEqual(layoutRender[0].components[0].data.spocs, []);
    110  });
    111 
    112  it("should return layout with spocs data if feed isn't defined but spocs is", () => {
    113    const fakeLayout = [
    114      {
    115        width: 3,
    116        components: [{ type: "foo", spocs: { positions: [{ index: 0 }] } }],
    117      },
    118    ];
    119    store.dispatch({
    120      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    121      data: { layout: fakeLayout },
    122    });
    123    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    124    store.dispatch({
    125      type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
    126      data: {
    127        lastUpdated: 0,
    128        spocs: {
    129          newtab_spocs: {
    130            items: [{ id: 1 }, { id: 2 }, { id: 3 }],
    131          },
    132        },
    133      },
    134    });
    135 
    136    const { layoutRender } = selectLayoutRender({
    137      state: store.getState().DiscoveryStream,
    138    });
    139 
    140    assert.lengthOf(layoutRender, 1);
    141    assert.propertyVal(layoutRender[0], "width", 3);
    142    assert.deepEqual(layoutRender[0].components[0].data.spocs, [
    143      { id: 1, pos: 0 },
    144      { id: 2, pos: 1 },
    145      { id: 3, pos: 2 },
    146    ]);
    147  });
    148 
    149  it("should return layout with no spocs data if feed and spocs are unavailable", () => {
    150    const fakeLayout = [
    151      {
    152        width: 3,
    153        components: [{ type: "foo", spocs: { positions: [{ index: 0 }] } }],
    154      },
    155    ];
    156    store.dispatch({
    157      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    158      data: { layout: fakeLayout },
    159    });
    160    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    161    store.dispatch({
    162      type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
    163      data: {
    164        lastUpdated: 0,
    165        spocs: {
    166          spocs: {
    167            items: [],
    168          },
    169        },
    170      },
    171    });
    172 
    173    const { layoutRender } = selectLayoutRender({
    174      state: store.getState().DiscoveryStream,
    175    });
    176 
    177    assert.lengthOf(layoutRender, 1);
    178    assert.propertyVal(layoutRender[0], "width", 3);
    179    assert.equal(layoutRender[0].components[0].data.spocs.length, 0);
    180  });
    181 
    182  it("should return feed data offset by layout set prop", () => {
    183    const fakeLayout = [
    184      {
    185        width: 3,
    186        components: [
    187          { type: "foo", properties: { offset: 1 }, feed: { url: "foo.com" } },
    188        ],
    189      },
    190    ];
    191    store.dispatch({
    192      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    193      data: { layout: fakeLayout },
    194    });
    195    store.dispatch({
    196      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    197      data: { feed: FAKE_FEEDS["foo.com"], url: "foo.com" },
    198    });
    199    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    200 
    201    const { layoutRender } = selectLayoutRender({
    202      state: store.getState().DiscoveryStream,
    203    });
    204 
    205    assert.deepEqual(layoutRender[0].components[0].data, {
    206      recommendations: [{ id: "bar" }],
    207      sections: [],
    208    });
    209  });
    210 
    211  it("should return spoc result when there are more positions than spocs", () => {
    212    const fakeSpocConfig = {
    213      positions: [{ index: 0 }, { index: 1 }, { index: 2 }],
    214    };
    215    const fakeLayout = [
    216      {
    217        width: 3,
    218        components: [
    219          { type: "foo", feed: { url: "foo.com" }, spocs: fakeSpocConfig },
    220        ],
    221      },
    222    ];
    223    const fakeSpocsData = {
    224      lastUpdated: 0,
    225      spocs: { newtab_spocs: { items: ["fooSpoc", "barSpoc"] } },
    226    };
    227 
    228    store.dispatch({
    229      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    230      data: { layout: fakeLayout },
    231    });
    232    store.dispatch({
    233      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    234      data: { feed: FAKE_FEEDS["foo.com"], url: "foo.com" },
    235    });
    236    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    237    store.dispatch({
    238      type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
    239      data: fakeSpocsData,
    240    });
    241 
    242    const { layoutRender } = selectLayoutRender({
    243      state: store.getState().DiscoveryStream,
    244    });
    245 
    246    assert.lengthOf(layoutRender, 1);
    247    assert.deepEqual(
    248      layoutRender[0].components[0].data.recommendations[0],
    249      "fooSpoc"
    250    );
    251    assert.deepEqual(
    252      layoutRender[0].components[0].data.recommendations[1],
    253      "barSpoc"
    254    );
    255    assert.deepEqual(layoutRender[0].components[0].data.recommendations[2], {
    256      id: "foo",
    257    });
    258    assert.deepEqual(layoutRender[0].components[0].data.recommendations[3], {
    259      id: "bar",
    260    });
    261  });
    262 
    263  it("should return a layout with feeds of items length with positions", () => {
    264    const fakeLayout = [
    265      {
    266        width: 3,
    267        components: [
    268          { type: "foo", properties: { items: 3 }, feed: { url: "foo.com" } },
    269        ],
    270      },
    271    ];
    272    const fakeRecommendations = [
    273      { name: "item1" },
    274      { name: "item2" },
    275      { name: "item3" },
    276      { name: "item4" },
    277    ];
    278    const fakeFeeds = {
    279      "foo.com": { data: { recommendations: fakeRecommendations } },
    280    };
    281    store.dispatch({
    282      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    283      data: { layout: fakeLayout },
    284    });
    285    store.dispatch({
    286      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    287      data: { feed: fakeFeeds["foo.com"], url: "foo.com" },
    288    });
    289    store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
    290 
    291    const { layoutRender } = selectLayoutRender({
    292      state: store.getState().DiscoveryStream,
    293    });
    294 
    295    const { recommendations } = layoutRender[0].components[0].data;
    296    assert.equal(recommendations.length, 4);
    297    assert.equal(recommendations[0].pos, 0);
    298    assert.equal(recommendations[1].pos, 1);
    299    assert.equal(recommendations[2].pos, 2);
    300    assert.equal(recommendations[3].pos, undefined);
    301  });
    302 
    303  it("should render everything if everything is ready", () => {
    304    const fakeLayout = [
    305      {
    306        width: 3,
    307        components: [
    308          { type: "foo1" },
    309          { type: "foo2", properties: { items: 3 }, feed: { url: "foo2.com" } },
    310          { type: "foo3", properties: { items: 3 }, feed: { url: "foo3.com" } },
    311          { type: "foo4", properties: { items: 3 }, feed: { url: "foo4.com" } },
    312          { type: "foo5" },
    313        ],
    314      },
    315    ];
    316    store.dispatch({
    317      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    318      data: { layout: fakeLayout },
    319    });
    320    store.dispatch({
    321      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    322      data: { feed: { data: { recommendations: [] } }, url: "foo2.com" },
    323    });
    324    store.dispatch({
    325      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    326      data: { feed: { data: { recommendations: [] } }, url: "foo3.com" },
    327    });
    328    store.dispatch({
    329      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    330      data: { feed: { data: { recommendations: [] } }, url: "foo4.com" },
    331    });
    332 
    333    const { layoutRender } = selectLayoutRender({
    334      state: store.getState().DiscoveryStream,
    335    });
    336 
    337    assert.equal(layoutRender[0].components[0].type, "foo1");
    338    assert.equal(layoutRender[0].components[1].type, "foo2");
    339    assert.equal(layoutRender[0].components[2].type, "foo3");
    340    assert.equal(layoutRender[0].components[3].type, "foo4");
    341    assert.equal(layoutRender[0].components[4].type, "foo5");
    342  });
    343 
    344  it("should stop rendering feeds if we hit a not ready spoc", () => {
    345    const fakeLayout = [
    346      {
    347        width: 3,
    348        components: [
    349          { type: "foo1" },
    350          { type: "foo2", properties: { items: 3 }, feed: { url: "foo2.com" } },
    351          {
    352            type: "foo3",
    353            properties: { items: 3 },
    354            feed: { url: "foo3.com" },
    355            spocs: { positions: [{ index: 0 }] },
    356          },
    357          { type: "foo4", properties: { items: 3 }, feed: { url: "foo4.com" } },
    358          { type: "foo5" },
    359        ],
    360      },
    361    ];
    362    store.dispatch({
    363      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    364      data: { layout: fakeLayout },
    365    });
    366    store.dispatch({
    367      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    368      data: { feed: { data: { recommendations: [] } }, url: "foo2.com" },
    369    });
    370    store.dispatch({
    371      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    372      data: { feed: { data: { recommendations: [] } }, url: "foo3.com" },
    373    });
    374    store.dispatch({
    375      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    376      data: { feed: { data: { recommendations: [] } }, url: "foo4.com" },
    377    });
    378 
    379    const { layoutRender } = selectLayoutRender({
    380      state: store.getState().DiscoveryStream,
    381    });
    382 
    383    assert.equal(layoutRender[0].components[0].type, "foo1");
    384    assert.equal(layoutRender[0].components[1].type, "foo2");
    385    assert.deepEqual(layoutRender[0].components[2].data.recommendations, [
    386      { placeholder: true },
    387      { placeholder: true },
    388      { placeholder: true },
    389    ]);
    390  });
    391 
    392  it("should not render a spoc if there are no available spocs", () => {
    393    const fakeLayout = [
    394      {
    395        width: 3,
    396        components: [
    397          { type: "foo1" },
    398          { type: "foo2", properties: { items: 3 }, feed: { url: "foo2.com" } },
    399          {
    400            type: "foo3",
    401            properties: { items: 3 },
    402            feed: { url: "foo3.com" },
    403            spocs: { positions: [{ index: 0 }] },
    404          },
    405          { type: "foo4", properties: { items: 3 }, feed: { url: "foo4.com" } },
    406          { type: "foo5" },
    407        ],
    408      },
    409    ];
    410    const fakeSpocsData = { lastUpdated: 0, spocs: { spocs: [] } };
    411    store.dispatch({
    412      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    413      data: { layout: fakeLayout },
    414    });
    415    store.dispatch({
    416      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    417      data: { feed: { data: { recommendations: [] } }, url: "foo2.com" },
    418    });
    419    store.dispatch({
    420      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    421      data: {
    422        feed: { data: { recommendations: [{ name: "rec" }] } },
    423        url: "foo3.com",
    424      },
    425    });
    426    store.dispatch({
    427      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    428      data: { feed: { data: { recommendations: [] } }, url: "foo4.com" },
    429    });
    430    store.dispatch({
    431      type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
    432      data: fakeSpocsData,
    433    });
    434 
    435    const { layoutRender } = selectLayoutRender({
    436      state: store.getState().DiscoveryStream,
    437    });
    438 
    439    assert.deepEqual(layoutRender[0].components[2].data.recommendations[0], {
    440      name: "rec",
    441      pos: 0,
    442    });
    443  });
    444 
    445  it("should not render a row if no components exist after filter in that row", () => {
    446    const fakeLayout = [
    447      {
    448        width: 3,
    449        components: [{ type: "TopSites" }],
    450      },
    451      {
    452        width: 3,
    453        components: [{ type: "Message" }],
    454      },
    455    ];
    456    store.dispatch({
    457      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    458      data: { layout: fakeLayout },
    459    });
    460 
    461    const { layoutRender } = selectLayoutRender({
    462      state: store.getState().DiscoveryStream,
    463      prefs: { "feeds.topsites": true },
    464    });
    465 
    466    assert.equal(layoutRender[0].components[0].type, "TopSites");
    467    assert.equal(layoutRender[1], undefined);
    468  });
    469 
    470  it("should not render a component if filtered", () => {
    471    const fakeLayout = [
    472      {
    473        width: 3,
    474        components: [{ type: "Message" }, { type: "TopSites" }],
    475      },
    476    ];
    477    store.dispatch({
    478      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    479      data: { layout: fakeLayout },
    480    });
    481 
    482    const { layoutRender } = selectLayoutRender({
    483      state: store.getState().DiscoveryStream,
    484      prefs: { "feeds.topsites": true },
    485    });
    486 
    487    assert.equal(layoutRender[0].components[0].type, "TopSites");
    488    assert.equal(layoutRender[0].components[1], undefined);
    489  });
    490 
    491  it("should skip rendering a spoc in position if that spoc is blocked for that session", () => {
    492    const fakeLayout = [
    493      {
    494        width: 3,
    495        components: [
    496          {
    497            type: "foo1",
    498            properties: { items: 3 },
    499            feed: { url: "foo1.com" },
    500            spocs: { positions: [{ index: 0 }] },
    501          },
    502        ],
    503      },
    504    ];
    505    const fakeSpocsData = {
    506      lastUpdated: 0,
    507      spocs: {
    508        newtab_spocs: { items: [{ name: "spoc", url: "https://foo.com" }] },
    509      },
    510    };
    511    store.dispatch({
    512      type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
    513      data: { layout: fakeLayout },
    514    });
    515    store.dispatch({
    516      type: at.DISCOVERY_STREAM_FEED_UPDATE,
    517      data: {
    518        feed: { data: { recommendations: [{ name: "rec" }] } },
    519        url: "foo1.com",
    520      },
    521    });
    522    store.dispatch({
    523      type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
    524      data: fakeSpocsData,
    525    });
    526 
    527    const { layoutRender: layout1 } = selectLayoutRender({
    528      state: store.getState().DiscoveryStream,
    529    });
    530 
    531    store.dispatch({
    532      type: at.DISCOVERY_STREAM_SPOC_BLOCKED,
    533      data: { url: "https://foo.com" },
    534    });
    535 
    536    const { layoutRender: layout2 } = selectLayoutRender({
    537      state: store.getState().DiscoveryStream,
    538    });
    539 
    540    assert.deepEqual(layout1[0].components[0].data.recommendations[0], {
    541      name: "spoc",
    542      url: "https://foo.com",
    543      pos: 0,
    544    });
    545    assert.deepEqual(layout2[0].components[0].data.recommendations[0], {
    546      name: "rec",
    547      pos: 0,
    548    });
    549  });
    550 
    551  it("should include Widgets when widgets.system.enabled is true", () => {
    552    const { layoutRender } = selectLayoutRender({
    553      prefs: {
    554        "widgets.system.enabled": true,
    555        "feeds.section.topstories": false,
    556        "feeds.system.topstories": false,
    557      },
    558      state: {
    559        layout: [
    560          {
    561            width: 12,
    562            components: [{ type: "Widgets" }],
    563          },
    564        ],
    565      },
    566    });
    567 
    568    assert.lengthOf(layoutRender, 1);
    569    assert.lengthOf(layoutRender[0].components, 1);
    570    assert.propertyVal(layoutRender[0].components[0], "type", "Widgets");
    571  });
    572 
    573  it("should include Widgets when (Nimbus) widgetsConfig.enabled is true", () => {
    574    const { layoutRender } = selectLayoutRender({
    575      prefs: {
    576        widgetsConfig: { enabled: true },
    577        "feeds.section.topstories": false,
    578        "feeds.system.topstories": false,
    579      },
    580      state: {
    581        layout: [
    582          {
    583            width: 12,
    584            components: [{ type: "Widgets" }],
    585          },
    586        ],
    587      },
    588    });
    589 
    590    assert.lengthOf(layoutRender, 1);
    591    assert.lengthOf(layoutRender[0].components, 1);
    592    assert.propertyVal(layoutRender[0].components[0], "type", "Widgets");
    593  });
    594 
    595  it("should filter out Widgets when both widget prefs are false", () => {
    596    const { layoutRender } = selectLayoutRender({
    597      prefs: {
    598        "widgets.system.enabled": false,
    599        widgetsConfig: { enabled: false },
    600        "feeds.section.topstories": false,
    601        "feeds.system.topstories": false,
    602      },
    603      state: {
    604        layout: [
    605          {
    606            width: 12,
    607            components: [{ type: "Widgets" }],
    608          },
    609        ],
    610      },
    611    });
    612 
    613    assert.lengthOf(layoutRender, 0);
    614  });
    615 });