tor-browser

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

LinkMenu.test.jsx (18737B)


      1 /* eslint-disable no-shadow */
      2 import { ContextMenu } from "content-src/components/ContextMenu/ContextMenu";
      3 import { _LinkMenu as LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
      4 import React from "react";
      5 import { shallow } from "enzyme";
      6 
      7 describe("<LinkMenu>", () => {
      8  let wrapper;
      9  beforeEach(() => {
     10    wrapper = shallow(
     11      <LinkMenu
     12        site={{ url: "" }}
     13        options={["CheckPinTopSite", "CheckBookmark", "OpenInNewWindow"]}
     14        dispatch={() => {}}
     15      />
     16    );
     17  });
     18  it("should render a ContextMenu element", () => {
     19    assert.ok(wrapper.find(ContextMenu).exists());
     20  });
     21  it("should pass onUpdate, and options to ContextMenu", () => {
     22    assert.ok(wrapper.find(ContextMenu).exists());
     23    const contextMenuProps = wrapper.find(ContextMenu).props();
     24    ["onUpdate", "options"].forEach(prop =>
     25      assert.property(contextMenuProps, prop)
     26    );
     27  });
     28  it("should give ContextMenu the correct tabbable options length for a11y", () => {
     29    const { options } = wrapper.find(ContextMenu).props();
     30    const [firstItem] = options;
     31    const lastItem = options[options.length - 1];
     32 
     33    // first item should have {first: true}
     34    assert.isTrue(firstItem.first);
     35    assert.ok(!firstItem.last);
     36 
     37    // last item should have {last: true}
     38    assert.isTrue(lastItem.last);
     39    assert.ok(!lastItem.first);
     40 
     41    // middle items should have neither
     42    for (let i = 1; i < options.length - 1; i++) {
     43      assert.ok(!options[i].first && !options[i].last);
     44    }
     45  });
     46  it("should show the correct options for default sites", () => {
     47    wrapper = shallow(
     48      <LinkMenu
     49        site={{ url: "", isDefault: true }}
     50        options={["CheckBookmark"]}
     51        source={"TOP_SITES"}
     52        isPrivateBrowsingEnabled={true}
     53        dispatch={() => {}}
     54      />
     55    );
     56    const { options } = wrapper.find(ContextMenu).props();
     57    let i = 0;
     58    assert.propertyVal(options[i++], "id", "newtab-menu-pin");
     59    assert.propertyVal(options[i++], "id", "newtab-menu-edit-topsites");
     60    assert.propertyVal(options[i++], "type", "separator");
     61    assert.propertyVal(options[i++], "id", "newtab-menu-open-new-window");
     62    assert.propertyVal(
     63      options[i++],
     64      "id",
     65      "newtab-menu-open-new-private-window"
     66    );
     67    assert.propertyVal(options[i++], "type", "separator");
     68    assert.propertyVal(options[i++], "id", "newtab-menu-dismiss");
     69    assert.propertyVal(options, "length", i);
     70    // Double check that delete options are not included for default top sites
     71    options
     72      .filter(o => o.type !== "separator")
     73      .forEach(o => {
     74        assert.notInclude(["newtab-menu-delete-history"], o.id);
     75      });
     76  });
     77  it("should show Unpin option for a pinned site if CheckPinTopSite in options list", () => {
     78    wrapper = shallow(
     79      <LinkMenu
     80        site={{ url: "", isPinned: true }}
     81        source={"TOP_SITES"}
     82        options={["CheckPinTopSite"]}
     83        dispatch={() => {}}
     84      />
     85    );
     86    const { options } = wrapper.find(ContextMenu).props();
     87    assert.isDefined(options.find(o => o.id && o.id === "newtab-menu-unpin"));
     88  });
     89  it("should show Pin option for an unpinned site if CheckPinTopSite in options list", () => {
     90    wrapper = shallow(
     91      <LinkMenu
     92        site={{ url: "", isPinned: false }}
     93        source={"TOP_SITES"}
     94        options={["CheckPinTopSite"]}
     95        dispatch={() => {}}
     96      />
     97    );
     98    const { options } = wrapper.find(ContextMenu).props();
     99    assert.isDefined(options.find(o => o.id && o.id === "newtab-menu-pin"));
    100  });
    101  it("should show Unbookmark option for a bookmarked site if CheckBookmark in options list", () => {
    102    wrapper = shallow(
    103      <LinkMenu
    104        site={{ url: "", bookmarkGuid: 1234 }}
    105        source={"TOP_SITES"}
    106        options={["CheckBookmark"]}
    107        dispatch={() => {}}
    108      />
    109    );
    110    const { options } = wrapper.find(ContextMenu).props();
    111    assert.isDefined(
    112      options.find(o => o.id && o.id === "newtab-menu-remove-bookmark")
    113    );
    114  });
    115  it("should show Bookmark option for an unbookmarked site if CheckBookmark in options list", () => {
    116    wrapper = shallow(
    117      <LinkMenu
    118        site={{ url: "", bookmarkGuid: 0 }}
    119        source={"TOP_SITES"}
    120        options={["CheckBookmark"]}
    121        dispatch={() => {}}
    122      />
    123    );
    124    const { options } = wrapper.find(ContextMenu).props();
    125    assert.isDefined(
    126      options.find(o => o.id && o.id === "newtab-menu-bookmark")
    127    );
    128  });
    129  it("should show Open File option for a downloaded item", () => {
    130    wrapper = shallow(
    131      <LinkMenu
    132        site={{ url: "", type: "download", path: "foo" }}
    133        source={"HIGHLIGHTS"}
    134        options={["OpenFile"]}
    135        dispatch={() => {}}
    136      />
    137    );
    138    const { options } = wrapper.find(ContextMenu).props();
    139    assert.isDefined(
    140      options.find(o => o.id && o.id === "newtab-menu-open-file")
    141    );
    142  });
    143  it("should show Show File option for a downloaded item on a default platform", () => {
    144    wrapper = shallow(
    145      <LinkMenu
    146        site={{ url: "", type: "download", path: "foo" }}
    147        source={"HIGHLIGHTS"}
    148        options={["ShowFile"]}
    149        platform={"default"}
    150        dispatch={() => {}}
    151      />
    152    );
    153    const { options } = wrapper.find(ContextMenu).props();
    154    assert.isDefined(
    155      options.find(o => o.id && o.id === "newtab-menu-show-file")
    156    );
    157  });
    158  it("should show Copy Downlad Link option for a downloaded item when CopyDownloadLink", () => {
    159    wrapper = shallow(
    160      <LinkMenu
    161        site={{ url: "", type: "download" }}
    162        source={"HIGHLIGHTS"}
    163        options={["CopyDownloadLink"]}
    164        dispatch={() => {}}
    165      />
    166    );
    167    const { options } = wrapper.find(ContextMenu).props();
    168    assert.isDefined(
    169      options.find(o => o.id && o.id === "newtab-menu-copy-download-link")
    170    );
    171  });
    172  it("should show Go To Download Page option for a downloaded item when GoToDownloadPage", () => {
    173    wrapper = shallow(
    174      <LinkMenu
    175        site={{ url: "", type: "download", referrer: "foo" }}
    176        source={"HIGHLIGHTS"}
    177        options={["GoToDownloadPage"]}
    178        dispatch={() => {}}
    179      />
    180    );
    181    const { options } = wrapper.find(ContextMenu).props();
    182    assert.isDefined(
    183      options.find(o => o.id && o.id === "newtab-menu-go-to-download-page")
    184    );
    185    assert.isFalse(options[0].disabled);
    186  });
    187  it("should show Go To Download Page option as disabled for a downloaded item when GoToDownloadPage if no referrer exists", () => {
    188    wrapper = shallow(
    189      <LinkMenu
    190        site={{ url: "", type: "download", referrer: null }}
    191        source={"HIGHLIGHTS"}
    192        options={["GoToDownloadPage"]}
    193        dispatch={() => {}}
    194      />
    195    );
    196    const { options } = wrapper.find(ContextMenu).props();
    197    assert.isDefined(
    198      options.find(o => o.id && o.id === "newtab-menu-go-to-download-page")
    199    );
    200    assert.isTrue(options[0].disabled);
    201  });
    202  it("should show Remove Download Link option for a downloaded item when RemoveDownload", () => {
    203    wrapper = shallow(
    204      <LinkMenu
    205        site={{ url: "", type: "download" }}
    206        source={"HIGHLIGHTS"}
    207        options={["RemoveDownload"]}
    208        dispatch={() => {}}
    209      />
    210    );
    211    const { options } = wrapper.find(ContextMenu).props();
    212    assert.isDefined(
    213      options.find(o => o.id && o.id === "newtab-menu-remove-download")
    214    );
    215  });
    216  it("should show Edit option", () => {
    217    const props = { url: "foo", label: "label" };
    218    const index = 5;
    219    wrapper = shallow(
    220      <LinkMenu
    221        site={props}
    222        index={5}
    223        source={"TOP_SITES"}
    224        options={["EditTopSite"]}
    225        dispatch={() => {}}
    226      />
    227    );
    228    const { options } = wrapper.find(ContextMenu).props();
    229    const option = options.find(
    230      o => o.id && o.id === "newtab-menu-edit-topsites"
    231    );
    232    assert.isDefined(option);
    233    assert.equal(option.action.data.index, index);
    234  });
    235  describe(".onClick", () => {
    236    const FAKE_EVENT = {};
    237    const FAKE_INDEX = 3;
    238    const FAKE_SOURCE = "TOP_SITES";
    239    const FAKE_SITE = {
    240      bookmarkGuid: 1234,
    241      hostname: "foo",
    242      path: "foo",
    243      pocket_id: "1234",
    244      referrer: "https://foo.com/ref",
    245      title: "bar",
    246      type: "bookmark",
    247      typedBonus: true,
    248      url: "https://foo.com",
    249      sponsored_tile_id: 12345,
    250      card_type: "organic",
    251    };
    252    const dispatch = sinon.stub();
    253    const propOptions = [
    254      "ShowFile",
    255      "CopyDownloadLink",
    256      "GoToDownloadPage",
    257      "RemoveDownload",
    258      "Separator",
    259      "ShowPrivacyInfo",
    260      "RemoveBookmark",
    261      "AddBookmark",
    262      "OpenInNewWindow",
    263      "OpenInPrivateWindow",
    264      "BlockUrl",
    265      "DeleteUrl",
    266      "PinTopSite",
    267      "UnpinTopSite",
    268      "WebExtDismiss",
    269    ];
    270    const expectedActionData = {
    271      "newtab-menu-remove-bookmark": FAKE_SITE.bookmarkGuid,
    272      "newtab-menu-bookmark": {
    273        url: FAKE_SITE.url,
    274        title: FAKE_SITE.title,
    275        type: FAKE_SITE.type,
    276      },
    277      "newtab-menu-open-new-window": {
    278        card_type: FAKE_SITE.card_type,
    279        referrer: FAKE_SITE.referrer,
    280        typedBonus: FAKE_SITE.typedBonus,
    281        url: FAKE_SITE.url,
    282        event_source: "CONTEXT_MENU",
    283        topic: undefined,
    284        firstVisibleTimestamp: undefined,
    285        tile_id: undefined,
    286        recommendation_id: undefined,
    287        scheduled_corpus_item_id: undefined,
    288        corpus_item_id: undefined,
    289        received_rank: undefined,
    290        recommended_at: undefined,
    291        format: undefined,
    292        is_pocket_card: false,
    293        is_sponsored: true,
    294      },
    295      "newtab-menu-open-new-private-window": {
    296        url: FAKE_SITE.url,
    297        referrer: FAKE_SITE.referrer,
    298        event_source: "CONTEXT_MENU",
    299      },
    300      "newtab-menu-dismiss": [
    301        {
    302          url: FAKE_SITE.url,
    303          pocket_id: FAKE_SITE.pocket_id,
    304          tile_id: 12345,
    305          recommendation_id: undefined,
    306          scheduled_corpus_item_id: undefined,
    307          corpus_item_id: undefined,
    308          recommended_at: undefined,
    309          received_rank: undefined,
    310          isSponsoredTopSite: undefined,
    311          type: "bookmark",
    312          card_type: FAKE_SITE.card_type,
    313          position: 3,
    314          is_pocket_card: false,
    315        },
    316      ],
    317      menu_action_webext_dismiss: {
    318        source: "TOP_SITES",
    319        url: FAKE_SITE.url,
    320        action_position: 3,
    321      },
    322      "newtab-menu-delete-history": {
    323        url: FAKE_SITE.url,
    324        pocket_id: FAKE_SITE.pocket_id,
    325        forceBlock: FAKE_SITE.bookmarkGuid,
    326      },
    327      "newtab-menu-pin": { site: FAKE_SITE, index: FAKE_INDEX },
    328      "newtab-menu-unpin": { site: { url: FAKE_SITE.url } },
    329      "newtab-menu-show-file": { url: FAKE_SITE.url },
    330      "newtab-menu-copy-download-link": { url: FAKE_SITE.url },
    331      "newtab-menu-go-to-download-page": { url: FAKE_SITE.referrer },
    332      "newtab-menu-remove-download": { url: FAKE_SITE.url },
    333    };
    334    const { options } = shallow(
    335      <LinkMenu
    336        site={FAKE_SITE}
    337        siteInfo={{ value: { card_type: FAKE_SITE.type } }}
    338        dispatch={dispatch}
    339        index={FAKE_INDEX}
    340        isPrivateBrowsingEnabled={true}
    341        platform={"default"}
    342        options={propOptions}
    343        source={FAKE_SOURCE}
    344        shouldSendImpressionStats={true}
    345      />
    346    )
    347      .find(ContextMenu)
    348      .props();
    349    afterEach(() => dispatch.reset());
    350    options
    351      .filter(o => o.type !== "separator")
    352      .forEach(option => {
    353        it(`should fire a ${option.action.type} action for ${option.id} with the expected data`, () => {
    354          option.onClick(FAKE_EVENT);
    355 
    356          if (option.impression && option.userEvent) {
    357            assert.calledThrice(dispatch);
    358          } else if (option.impression || option.userEvent) {
    359            assert.calledTwice(dispatch);
    360          } else {
    361            assert.calledOnce(dispatch);
    362          }
    363 
    364          // option.action is dispatched
    365          assert.ok(dispatch.firstCall.calledWith(option.action));
    366 
    367          // option.action has correct data
    368          // (delete is a special case as it dispatches a nested DIALOG_OPEN-type action)
    369          // in the case of this FAKE_SITE, we send a bookmarkGuid therefore we also want
    370          // to block this if we delete it
    371          if (option.id === "newtab-menu-delete-history") {
    372            assert.deepEqual(
    373              option.action.data.onConfirm[0].data,
    374              expectedActionData[option.id]
    375            );
    376            // Test UserEvent send correct meta about item deleted
    377            assert.propertyVal(
    378              option.action.data.onConfirm[1].data,
    379              "action_position",
    380              FAKE_INDEX
    381            );
    382            assert.propertyVal(
    383              option.action.data.onConfirm[1].data,
    384              "source",
    385              FAKE_SOURCE
    386            );
    387          } else {
    388            assert.deepEqual(option.action.data, expectedActionData[option.id]);
    389          }
    390        });
    391        it(`should fire a UserEvent action for ${option.id} if configured`, () => {
    392          if (option.userEvent) {
    393            option.onClick(FAKE_EVENT);
    394            const [action] = dispatch.secondCall.args;
    395            assert.isUserEventAction(action);
    396            assert.propertyVal(action.data, "source", FAKE_SOURCE);
    397            assert.propertyVal(action.data, "action_position", FAKE_INDEX);
    398            assert.propertyVal(action.data.value, "card_type", FAKE_SITE.type);
    399          }
    400        });
    401        it(`should send impression stats for ${option.id}`, () => {
    402          if (option.impression) {
    403            option.onClick(FAKE_EVENT);
    404            const [action] = dispatch.thirdCall.args;
    405            assert.deepEqual(action, option.impression);
    406          }
    407        });
    408      });
    409    it(`should not send impression stats if not configured`, () => {
    410      const fakeOptions = shallow(
    411        <LinkMenu
    412          site={FAKE_SITE}
    413          dispatch={dispatch}
    414          index={FAKE_INDEX}
    415          options={propOptions}
    416          source={FAKE_SOURCE}
    417          shouldSendImpressionStats={false}
    418        />
    419      )
    420        .find(ContextMenu)
    421        .props().options;
    422 
    423      fakeOptions
    424        .filter(o => o.type !== "separator")
    425        .forEach(option => {
    426          if (option.impression) {
    427            option.onClick(FAKE_EVENT);
    428            assert.calledTwice(dispatch);
    429            assert.notEqual(dispatch.firstCall.args[0], option.impression);
    430            assert.notEqual(dispatch.secondCall.args[0], option.impression);
    431            dispatch.reset();
    432          }
    433        });
    434    });
    435    it(`should pin a SPOC with all of the site details sent`, () => {
    436      const pinSpocTopSite = "PinTopSite";
    437      const { options: spocOptions } = shallow(
    438        <LinkMenu
    439          site={FAKE_SITE}
    440          siteInfo={{ value: { card_type: FAKE_SITE.type } }}
    441          dispatch={dispatch}
    442          index={FAKE_INDEX}
    443          isPrivateBrowsingEnabled={true}
    444          platform={"default"}
    445          options={[pinSpocTopSite]}
    446          source={FAKE_SOURCE}
    447          shouldSendImpressionStats={true}
    448        />
    449      )
    450        .find(ContextMenu)
    451        .props();
    452 
    453      const [pinSpocOption] = spocOptions;
    454      pinSpocOption.onClick(FAKE_EVENT);
    455 
    456      if (pinSpocOption.impression && pinSpocOption.userEvent) {
    457        assert.calledThrice(dispatch);
    458      } else if (pinSpocOption.impression || pinSpocOption.userEvent) {
    459        assert.calledTwice(dispatch);
    460      } else {
    461        assert.calledOnce(dispatch);
    462      }
    463 
    464      // option.action is dispatched
    465      assert.ok(dispatch.firstCall.calledWith(pinSpocOption.action));
    466 
    467      assert.deepEqual(pinSpocOption.action.data, {
    468        site: FAKE_SITE,
    469        index: FAKE_INDEX,
    470      });
    471    });
    472    it(`should create a proper BLOCK_URL action for a sponsored tile`, () => {
    473      const site = {
    474        hostname: "foo",
    475        path: "foo",
    476        referrer: "https://foo.com/ref",
    477        title: "bar",
    478        type: "bookmark",
    479        typedBonus: true,
    480        url: "https://foo.com",
    481        sponsored_position: 1,
    482      };
    483      const { options: blockOptions } = shallow(
    484        <LinkMenu
    485          site={site}
    486          siteInfo={{ value: { card_type: site.type } }}
    487          dispatch={dispatch}
    488          index={FAKE_INDEX}
    489          isPrivateBrowsingEnabled={true}
    490          platform={"default"}
    491          options={["BlockUrl"]}
    492          source={FAKE_SOURCE}
    493          shouldSendImpressionStats={true}
    494        />
    495      )
    496        .find(ContextMenu)
    497        .props();
    498      const [blockUrlOption] = blockOptions;
    499 
    500      blockUrlOption.onClick(FAKE_EVENT);
    501 
    502      assert.calledThrice(dispatch);
    503      assert.ok(dispatch.firstCall.calledWith(blockUrlOption.action));
    504      const expected = {
    505        url: site.url,
    506        pocket_id: undefined,
    507        tile_id: undefined,
    508        recommendation_id: undefined,
    509        scheduled_corpus_item_id: undefined,
    510        corpus_item_id: undefined,
    511        recommended_at: undefined,
    512        received_rank: undefined,
    513        advertiser_name: site.hostname,
    514        isSponsoredTopSite: 1,
    515        type: "bookmark",
    516        card_type: undefined,
    517        position: 3,
    518        is_pocket_card: false,
    519      };
    520      assert.deepEqual(blockUrlOption.action.data[0], expected);
    521    });
    522    it(`should create a proper BLOCK_URL action for a pocket item`, () => {
    523      const site = {
    524        hostname: "foo",
    525        path: "foo",
    526        referrer: "https://foo.com/ref",
    527        title: "bar",
    528        type: "CardGrid",
    529        typedBonus: true,
    530        url: "https://foo.com",
    531      };
    532      const { options: blockOptions } = shallow(
    533        <LinkMenu
    534          site={site}
    535          siteInfo={{ value: { card_type: site.type } }}
    536          dispatch={dispatch}
    537          index={FAKE_INDEX}
    538          isPrivateBrowsingEnabled={true}
    539          platform={"default"}
    540          options={["BlockUrl"]}
    541          source={FAKE_SOURCE}
    542          shouldSendImpressionStats={true}
    543        />
    544      )
    545        .find(ContextMenu)
    546        .props();
    547      const [blockUrlOption] = blockOptions;
    548 
    549      blockUrlOption.onClick(FAKE_EVENT);
    550 
    551      assert.calledThrice(dispatch);
    552      assert.ok(dispatch.firstCall.calledWith(blockUrlOption.action));
    553      const expected = {
    554        url: site.url,
    555        pocket_id: undefined,
    556        tile_id: undefined,
    557        recommendation_id: undefined,
    558        scheduled_corpus_item_id: undefined,
    559        corpus_item_id: undefined,
    560        recommended_at: undefined,
    561        received_rank: undefined,
    562        isSponsoredTopSite: undefined,
    563        type: "CardGrid",
    564        card_type: undefined,
    565        position: 3,
    566        is_pocket_card: true,
    567      };
    568      assert.deepEqual(blockUrlOption.action.data[0], expected);
    569    });
    570  });
    571 });