tor-browser

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

CardGrid.test.jsx (8305B)


      1 import {
      2  _CardGrid as CardGrid,
      3  // eslint-disable-next-line no-shadow
      4  IntersectionObserver,
      5 } from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid";
      6 import { combineReducers, createStore } from "redux";
      7 import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs";
      8 import { Provider } from "react-redux";
      9 import { DSCard } from "content-src/components/DiscoveryStreamComponents/DSCard/DSCard";
     10 import { TopicsWidget } from "content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget";
     11 import React from "react";
     12 import { shallow, mount } from "enzyme";
     13 
     14 // Wrap this around any component that uses useSelector,
     15 // or any mount that uses a child that uses redux.
     16 function WrapWithProvider({ children, state = INITIAL_STATE }) {
     17  let store = createStore(combineReducers(reducers), state);
     18  return <Provider store={store}>{children}</Provider>;
     19 }
     20 
     21 describe("<CardGrid>", () => {
     22  let wrapper;
     23 
     24  beforeEach(() => {
     25    wrapper = shallow(
     26      <CardGrid
     27        Prefs={INITIAL_STATE.Prefs}
     28        DiscoveryStream={INITIAL_STATE.DiscoveryStream}
     29      />
     30    );
     31  });
     32 
     33  it("should render an empty div", () => {
     34    assert.ok(wrapper.exists());
     35    assert.lengthOf(wrapper.children(), 0);
     36  });
     37 
     38  it("should render DSCards", () => {
     39    wrapper.setProps({ items: 2, data: { recommendations: [{}, {}] } });
     40 
     41    assert.lengthOf(wrapper.find(".ds-card-grid").children(), 2);
     42    assert.equal(wrapper.find(".ds-card-grid").children().at(0).type(), DSCard);
     43  });
     44 
     45  it("should add 4 card classname to card grid", () => {
     46    wrapper.setProps({
     47      fourCardLayout: true,
     48      data: { recommendations: [{}, {}] },
     49    });
     50 
     51    assert.ok(wrapper.find(".ds-card-grid-four-card-variant").exists());
     52  });
     53 
     54  it("should add no description classname to card grid", () => {
     55    wrapper.setProps({
     56      hideCardBackground: true,
     57      data: { recommendations: [{}, {}] },
     58    });
     59 
     60    assert.ok(wrapper.find(".ds-card-grid-hide-background").exists());
     61  });
     62 
     63  it("should add/hide description classname to card grid", () => {
     64    wrapper.setProps({
     65      data: { recommendations: [{}, {}] },
     66    });
     67 
     68    assert.ok(wrapper.find(".ds-card-grid-include-descriptions").exists());
     69 
     70    wrapper.setProps({
     71      hideDescriptions: true,
     72      data: { recommendations: [{}, {}] },
     73    });
     74 
     75    assert.ok(!wrapper.find(".ds-card-grid-include-descriptions").exists());
     76  });
     77 
     78  it("should create a widget card", () => {
     79    wrapper.setProps({
     80      widgets: {
     81        positions: [{ index: 1 }],
     82        data: [{ type: "TopicsWidget" }],
     83      },
     84      data: {
     85        recommendations: [{}, {}, {}],
     86      },
     87    });
     88 
     89    assert.ok(wrapper.find(TopicsWidget).exists());
     90  });
     91 
     92  it("should render AdBanner if enabled", () => {
     93    const commonProps = {
     94      ...INITIAL_STATE,
     95      items: 2,
     96      data: { recommendations: [{}, {}] },
     97      Prefs: {
     98        ...INITIAL_STATE.Prefs,
     99        values: {
    100          ...INITIAL_STATE.Prefs.values,
    101          "newtabAdSize.leaderboard": true,
    102          "newtabAdSize.billboard": true,
    103        },
    104      },
    105      DiscoveryStream: {
    106        ...INITIAL_STATE.DiscoveryStream,
    107        spocs: {
    108          ...INITIAL_STATE.DiscoveryStream.spocs,
    109          data: {
    110            newtab_spocs: {
    111              items: [
    112                {
    113                  format: "leaderboard",
    114                },
    115              ],
    116            },
    117          },
    118        },
    119      },
    120    };
    121 
    122    wrapper = mount(
    123      <WrapWithProvider>
    124        <CardGrid {...commonProps} />
    125      </WrapWithProvider>
    126    );
    127 
    128    assert.ok(wrapper.find(".ad-banner-wrapper").exists());
    129  });
    130 
    131  describe("Keyboard navigation", () => {
    132    beforeEach(() => {
    133      const commonProps = {
    134        items: 3,
    135        data: {
    136          recommendations: [{}, {}, {}],
    137        },
    138        Prefs: INITIAL_STATE.Prefs,
    139        DiscoveryStream: INITIAL_STATE.DiscoveryStream,
    140      };
    141 
    142      wrapper = mount(
    143        <WrapWithProvider>
    144          <CardGrid {...commonProps} />
    145        </WrapWithProvider>
    146      );
    147    });
    148 
    149    afterEach(() => {
    150      wrapper.unmount();
    151    });
    152 
    153    it("should pass tabIndex={0} to the first card and tabIndex={-1} to other cards", () => {
    154      const firstCard = wrapper.find(DSCard).at(0);
    155      const secondCard = wrapper.find(DSCard).at(1);
    156      const thirdCard = wrapper.find(DSCard).at(2);
    157 
    158      assert.equal(firstCard.prop("tabIndex"), 0);
    159      assert.equal(secondCard.prop("tabIndex"), -1);
    160      assert.equal(thirdCard.prop("tabIndex"), -1);
    161    });
    162 
    163    it("should update focused index when onFocus is called", () => {
    164      const secondCard = wrapper.find(DSCard).at(1);
    165      const onFocus = secondCard.prop("onFocus");
    166 
    167      onFocus();
    168      wrapper.update();
    169 
    170      assert.equal(wrapper.find(DSCard).at(1).prop("tabIndex"), 0);
    171      assert.equal(wrapper.find(DSCard).at(0).prop("tabIndex"), -1);
    172    });
    173 
    174    describe("handleCardKeyDown", () => {
    175      let sandbox;
    176      let grid;
    177      let mockLink;
    178      let mockTargetCard;
    179      let mockCurrentCard;
    180      let mockEvent;
    181 
    182      beforeEach(() => {
    183        sandbox = sinon.createSandbox();
    184        grid = wrapper.find(".ds-card-grid");
    185 
    186        mockLink = { focus: sandbox.spy() };
    187        mockTargetCard = {
    188          matches: sandbox.stub().returns(true),
    189          querySelector: sandbox.stub().returns(mockLink),
    190        };
    191        mockCurrentCard = {};
    192        mockEvent = {
    193          preventDefault: sandbox.spy(),
    194          target: {
    195            closest: sandbox.stub().returns(mockCurrentCard),
    196          },
    197        };
    198      });
    199 
    200      afterEach(() => {
    201        sandbox.restore();
    202      });
    203 
    204      it("should navigate to next card with ArrowRight", () => {
    205        mockEvent.key = "ArrowRight";
    206        mockCurrentCard.nextElementSibling = mockTargetCard;
    207 
    208        grid.prop("onKeyDown")(mockEvent);
    209 
    210        assert.calledOnce(mockEvent.preventDefault);
    211        assert.calledOnce(mockTargetCard.querySelector);
    212        assert.calledWith(mockTargetCard.querySelector, "a.ds-card-link");
    213        assert.calledOnce(mockLink.focus);
    214      });
    215 
    216      it("should navigate to previous card with ArrowLeft", () => {
    217        mockEvent.key = "ArrowLeft";
    218        mockCurrentCard.previousElementSibling = mockTargetCard;
    219 
    220        grid.prop("onKeyDown")(mockEvent);
    221 
    222        assert.calledOnce(mockEvent.preventDefault);
    223        assert.calledOnce(mockTargetCard.querySelector);
    224        assert.calledWith(mockTargetCard.querySelector, "a.ds-card-link");
    225        assert.calledOnce(mockLink.focus);
    226      });
    227 
    228      it("should return early if no current card found", () => {
    229        mockEvent.key = "ArrowRight";
    230        mockEvent.target.closest.returns(null);
    231 
    232        grid.prop("onKeyDown")(mockEvent);
    233 
    234        assert.calledOnce(mockEvent.preventDefault);
    235        assert.notCalled(mockTargetCard.querySelector);
    236      });
    237 
    238      it("should handle case where no matching sibling card is found", () => {
    239        mockEvent.key = "ArrowRight";
    240        mockCurrentCard.nextElementSibling = {
    241          matches: sandbox.stub().returns(false),
    242        };
    243 
    244        grid.prop("onKeyDown")(mockEvent);
    245 
    246        assert.calledOnce(mockEvent.preventDefault);
    247        assert.notCalled(mockLink.focus);
    248      });
    249    });
    250  });
    251 });
    252 
    253 // Build IntersectionObserver class with the arg `entries` for the intersect callback.
    254 function buildIntersectionObserver(entries) {
    255  return class {
    256    constructor(callback) {
    257      this.callback = callback;
    258    }
    259 
    260    observe() {
    261      this.callback(entries);
    262    }
    263 
    264    unobserve() {}
    265 
    266    disconnect() {}
    267  };
    268 }
    269 
    270 describe("<IntersectionObserver>", () => {
    271  let wrapper;
    272  let fakeWindow;
    273  let intersectEntries;
    274 
    275  beforeEach(() => {
    276    intersectEntries = [{ isIntersecting: true }];
    277    fakeWindow = {
    278      IntersectionObserver: buildIntersectionObserver(intersectEntries),
    279    };
    280    wrapper = mount(<IntersectionObserver windowObj={fakeWindow} />);
    281  });
    282 
    283  it("should render an empty div", () => {
    284    assert.ok(wrapper.exists());
    285    assert.equal(wrapper.children().at(0).type(), "div");
    286  });
    287 
    288  it("should fire onIntersecting", () => {
    289    const onIntersecting = sinon.stub();
    290    wrapper = mount(
    291      <IntersectionObserver
    292        windowObj={fakeWindow}
    293        onIntersecting={onIntersecting}
    294      />
    295    );
    296    assert.calledOnce(onIntersecting);
    297  });
    298 });