tor-browser

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

MultiSelect.test.jsx (13669B)


      1 import React from "react";
      2 import { mount } from "enzyme";
      3 import { MultiSelect } from "content-src/components/MultiSelect";
      4 
      5 describe("MultiSelect component", () => {
      6  let sandbox;
      7  let MULTISELECT_SCREEN_PROPS;
      8  let setScreenMultiSelects;
      9  let setActiveMultiSelect;
     10  beforeEach(() => {
     11    sandbox = sinon.createSandbox();
     12    setScreenMultiSelects = sandbox.stub();
     13    setActiveMultiSelect = sandbox.stub();
     14    MULTISELECT_SCREEN_PROPS = {
     15      id: "multiselect-screen",
     16      content: {
     17        position: "split",
     18        split_narrow_bkg_position: "-60px",
     19        image_alt_text: {
     20          string_id: "mr2022-onboarding-default-image-alt",
     21        },
     22        background:
     23          "url('chrome://activity-stream/content/data/content/assets/mr-settodefault.svg') var(--mr-secondary-position) no-repeat var(--mr-screen-background-color)",
     24        progress_bar: true,
     25        logo: {},
     26        title: "Test Title",
     27        tiles: {
     28          type: "multiselect",
     29          label: "Test Subtitle",
     30          data: [
     31            {
     32              id: "checkbox-1",
     33              defaultValue: true,
     34              label: {
     35                string_id: "mr2022-onboarding-set-default-primary-button-label",
     36              },
     37              action: {
     38                type: "SET_DEFAULT_BROWSER",
     39              },
     40            },
     41            {
     42              id: "checkbox-2",
     43              defaultValue: true,
     44              label: "Test Checkbox 2",
     45              action: {
     46                type: "SHOW_MIGRATION_WIZARD",
     47                data: {},
     48              },
     49            },
     50            {
     51              id: "checkbox-3",
     52              defaultValue: false,
     53              label: "Test Checkbox 3",
     54              action: {
     55                type: "SHOW_MIGRATION_WIZARD",
     56                data: {},
     57              },
     58            },
     59          ],
     60        },
     61        primary_button: {
     62          label: "Save and Continue",
     63          action: {
     64            type: "MULTI_ACTION",
     65            collectSelect: true,
     66            navigate: true,
     67            data: { actions: [] },
     68          },
     69        },
     70        secondary_button: {
     71          label: "Skip",
     72          action: {
     73            navigate: true,
     74          },
     75          has_arrow_icon: true,
     76        },
     77      },
     78      setScreenMultiSelects,
     79      setActiveMultiSelect,
     80    };
     81  });
     82  afterEach(() => {
     83    sandbox.restore();
     84  });
     85 
     86  it("should call setScreenMultiSelects with all ids of checkboxes", () => {
     87    mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
     88 
     89    assert.calledOnce(setScreenMultiSelects);
     90    assert.calledWith(setScreenMultiSelects, [
     91      "checkbox-1",
     92      "checkbox-2",
     93      "checkbox-3",
     94    ]);
     95  });
     96 
     97  it("should not call setScreenMultiSelects if it's already set", () => {
     98    let map = sandbox
     99      .stub()
    100      .returns(MULTISELECT_SCREEN_PROPS.content.tiles.data);
    101 
    102    mount(
    103      <MultiSelect screenMultiSelects={{ map }} {...MULTISELECT_SCREEN_PROPS} />
    104    );
    105 
    106    assert.notCalled(setScreenMultiSelects);
    107    assert.calledOnce(map);
    108    assert.calledWith(map, sinon.match.func);
    109  });
    110 
    111  it("should call setActiveMultiSelect with ids of checkboxes with defaultValue true", () => {
    112    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    113 
    114    wrapper.setProps({ activeMultiSelect: null });
    115    assert.calledOnce(setActiveMultiSelect);
    116    assert.calledWith(setActiveMultiSelect, ["checkbox-1", "checkbox-2"]);
    117  });
    118 
    119  it("should use activeMultiSelect ids to set checked state for respective checkbox", () => {
    120    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    121 
    122    wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
    123    const checkBoxes = wrapper.find(".checkbox-container input");
    124    assert.strictEqual(checkBoxes.length, 3);
    125 
    126    assert.strictEqual(checkBoxes.first().props().checked, true);
    127    assert.strictEqual(checkBoxes.at(1).props().checked, true);
    128    assert.strictEqual(checkBoxes.last().props().checked, false);
    129  });
    130 
    131  it("cover the randomize property", async () => {
    132    MULTISELECT_SCREEN_PROPS.content.tiles.data.forEach(
    133      item => (item.randomize = true)
    134    );
    135 
    136    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    137 
    138    const checkBoxes = wrapper.find(".checkbox-container input");
    139    assert.strictEqual(checkBoxes.length, 3);
    140 
    141    // We don't want to actually test the randomization, just that it doesn't
    142    // throw. We _could_ render the component until we get a different order,
    143    // and that should work the vast majority of the time, but it's
    144    // theoretically possible that we get the same order over and over again
    145    // until we hit the 2 second timeout. That would be an extremely low failure
    146    // rate, but we already know Math.random() works, so we don't really need to
    147    // test it anyway. It's not worth the added risk of false failures.
    148  });
    149 
    150  it("should filter out id when checkbox is unchecked", () => {
    151    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    152    wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
    153 
    154    const ckbx1 = wrapper.find(".checkbox-container input").at(0);
    155    assert.strictEqual(ckbx1.prop("value"), "checkbox-1");
    156    ckbx1.getDOMNode().checked = false;
    157    ckbx1.simulate("change");
    158    assert.calledWith(setActiveMultiSelect, ["checkbox-2"]);
    159  });
    160 
    161  it("should add id when checkbox is checked", () => {
    162    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    163    wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
    164 
    165    const ckbx3 = wrapper.find(".checkbox-container input").at(2);
    166    assert.strictEqual(ckbx3.prop("value"), "checkbox-3");
    167    ckbx3.getDOMNode().checked = true;
    168    ckbx3.simulate("change");
    169    assert.calledWith(setActiveMultiSelect, [
    170      "checkbox-1",
    171      "checkbox-2",
    172      "checkbox-3",
    173    ]);
    174  });
    175 
    176  it("should render radios and checkboxes with correct styles", async () => {
    177    const SCREEN_PROPS = { ...MULTISELECT_SCREEN_PROPS };
    178    SCREEN_PROPS.content.tiles.style = { flexDirection: "row", gap: "24px" };
    179    SCREEN_PROPS.content.tiles.data = [
    180      {
    181        id: "checkbox-1",
    182        defaultValue: true,
    183        label: { raw: "Test1" },
    184        action: { type: "OPEN_PROTECTION_REPORT" },
    185        style: { color: "red" },
    186        icon: { style: { color: "blue" } },
    187      },
    188      {
    189        id: "radio-1",
    190        type: "radio",
    191        group: "radios",
    192        defaultValue: true,
    193        label: { raw: "Test3" },
    194        action: { type: "OPEN_PROTECTION_REPORT" },
    195        style: { color: "purple" },
    196        icon: { style: { color: "yellow" } },
    197      },
    198    ];
    199    const wrapper = mount(<MultiSelect {...SCREEN_PROPS} />);
    200 
    201    // wait for effect hook
    202    await new Promise(resolve => queueMicrotask(resolve));
    203    // activeMultiSelect was called on effect hook with default values
    204    assert.calledWith(setActiveMultiSelect, ["checkbox-1", "radio-1"]);
    205 
    206    const container = wrapper.find(".multi-select-container");
    207    assert.strictEqual(container.prop("style").flexDirection, "row");
    208    assert.strictEqual(container.prop("style").gap, "24px");
    209 
    210    // checkboxes/radios are rendered with correct styles
    211    const checkBoxes = wrapper.find(".checkbox-container");
    212    assert.strictEqual(checkBoxes.length, 2);
    213    assert.strictEqual(checkBoxes.first().prop("style").color, "red");
    214    assert.strictEqual(checkBoxes.at(1).prop("style").color, "purple");
    215 
    216    const checks = wrapper.find(".checkbox-container input");
    217    assert.strictEqual(checks.length, 2);
    218    assert.strictEqual(checks.first().prop("style").color, "blue");
    219    assert.strictEqual(checks.at(1).prop("style").color, "yellow");
    220  });
    221 
    222  it("should render picker elements when multiSelectItemDesign is 'picker'", () => {
    223    const PICKER_PROPS = { ...MULTISELECT_SCREEN_PROPS };
    224    PICKER_PROPS.content.tiles.multiSelectItemDesign = "picker";
    225    PICKER_PROPS.content.tiles.data = [
    226      {
    227        id: "picker-option-1",
    228        defaultValue: true,
    229        label: "Picker Option 1",
    230        pickerEmoji: "🙃",
    231        pickerEmojiBackgroundColor: "#c3e0ff",
    232      },
    233      {
    234        id: "picker-option-2",
    235        defaultValue: false,
    236        label: "Picker Option 2",
    237        pickerEmoji: "✨",
    238        pickerEmojiBackgroundColor: "#ffebcc",
    239      },
    240    ];
    241 
    242    const wrapper = mount(<MultiSelect {...PICKER_PROPS} />);
    243    wrapper.setProps({ activeMultiSelect: ["picker-option-1"] });
    244 
    245    // Container should have picker class
    246    const container = wrapper.find(".multi-select-container");
    247    assert.strictEqual(container.hasClass("picker"), true);
    248 
    249    const pickerIcons = wrapper.find(".picker-icon");
    250    assert.strictEqual(pickerIcons.length, 2);
    251 
    252    // First icon should be checked (no emoji, no background color)
    253    const firstIcon = pickerIcons.at(0);
    254    assert.strictEqual(firstIcon.hasClass("picker-checked"), true);
    255    assert.strictEqual(firstIcon.text(), "");
    256    assert.strictEqual(firstIcon.prop("style").backgroundColor, undefined);
    257 
    258    // Second icon should not be checked (should have emoji and background color)
    259    const secondIcon = pickerIcons.at(1);
    260    assert.strictEqual(secondIcon.hasClass("picker-checked"), false);
    261    assert.strictEqual(secondIcon.text(), "✨");
    262    assert.strictEqual(secondIcon.prop("style").backgroundColor, "#ffebcc");
    263  });
    264 
    265  // The picker design adds functionality for checkbox to be checked
    266  // even when click events occur on the container itself, instead of just
    267  // the label or input
    268  it("should handle click events for picker design", () => {
    269    const PICKER_PROPS = { ...MULTISELECT_SCREEN_PROPS };
    270    PICKER_PROPS.content.tiles.multiSelectItemDesign = "picker";
    271    PICKER_PROPS.content.tiles.data = [
    272      {
    273        id: "picker-option-1",
    274        defaultValue: true,
    275        label: "Picker Option 1",
    276        pickerEmoji: "🙃",
    277      },
    278      {
    279        id: "picker-option-2",
    280        defaultValue: false,
    281        label: "Picker Option 2",
    282        pickerEmoji: "✨",
    283      },
    284    ];
    285 
    286    const wrapper = mount(<MultiSelect {...PICKER_PROPS} />);
    287    wrapper.setProps({ activeMultiSelect: ["picker-option-1"] });
    288 
    289    // check the container of the second item
    290    const checkboxContainers = wrapper.find(".checkbox-container");
    291    const secondContainer = checkboxContainers.at(1);
    292    secondContainer.simulate("click");
    293 
    294    // setActiveMultiSelect should be called with both ids
    295    assert.calledWith(setActiveMultiSelect, [
    296      "picker-option-1",
    297      "picker-option-2",
    298    ]);
    299 
    300    // uncheck the first item
    301    const firstContainer = checkboxContainers.at(0);
    302    firstContainer.simulate("click");
    303 
    304    // setActiveMultiSelect should be called with just the second id
    305    assert.calledWith(setActiveMultiSelect, ["picker-option-2"]);
    306  });
    307 
    308  it("should handle keyboard events for picker design", () => {
    309    const PICKER_PROPS = { ...MULTISELECT_SCREEN_PROPS };
    310    PICKER_PROPS.content.tiles.multiSelectItemDesign = "picker";
    311    PICKER_PROPS.content.tiles.data = [
    312      {
    313        id: "picker-option-1",
    314        defaultValue: false,
    315        label: "Picker Option 1",
    316      },
    317    ];
    318 
    319    const wrapper = mount(<MultiSelect {...PICKER_PROPS} />);
    320    wrapper.setProps({ activeMultiSelect: [] });
    321 
    322    const checkboxContainer = wrapper.find(".checkbox-container").first();
    323 
    324    // Test spacebar press
    325    checkboxContainer.simulate("keydown", {
    326      key: " ",
    327    });
    328    assert.calledWith(setActiveMultiSelect, ["picker-option-1"]);
    329 
    330    // Test Enter press
    331    checkboxContainer.simulate("keydown", {
    332      key: "Enter",
    333    });
    334    assert.calledWith(setActiveMultiSelect, []);
    335 
    336    // Test other key press
    337    setActiveMultiSelect.reset();
    338    checkboxContainer.simulate("keydown", {
    339      key: "Tab",
    340    });
    341    assert.notCalled(setActiveMultiSelect);
    342  });
    343 
    344  it("should not use handleCheckboxContainerInteraction when multiSelectItemDesign is not 'picker'", () => {
    345    const wrapper = mount(<MultiSelect {...MULTISELECT_SCREEN_PROPS} />);
    346    wrapper.setProps({ activeMultiSelect: ["checkbox-1"] });
    347 
    348    const checkboxContainer = wrapper.find(".checkbox-container").first();
    349 
    350    assert.strictEqual(checkboxContainer.prop("tabIndex"), null);
    351    assert.strictEqual(checkboxContainer.prop("onClick"), null);
    352    assert.strictEqual(checkboxContainer.prop("onKeyDown"), null);
    353    // Likewise, the extra accessibility attributes should not be present on the container
    354    assert.strictEqual(checkboxContainer.prop("role"), null);
    355    assert.strictEqual(checkboxContainer.prop("aria-checked"), null);
    356  });
    357 
    358  it("should set proper accessibility attributes for picker design when multiSelectItemDesign is 'picker' ", () => {
    359    const PICKER_PROPS = { ...MULTISELECT_SCREEN_PROPS };
    360    PICKER_PROPS.content.tiles.multiSelectItemDesign = "picker";
    361    PICKER_PROPS.content.tiles.data = [
    362      {
    363        id: "picker-option-1",
    364        defaultValue: true,
    365        label: "Picker Option 1",
    366      },
    367    ];
    368 
    369    const wrapper = mount(<MultiSelect {...PICKER_PROPS} />);
    370    wrapper.setProps({ activeMultiSelect: ["picker-option-1"] });
    371 
    372    const checkboxContainer = wrapper.find(".checkbox-container").first();
    373 
    374    // the checkbox-container should have appropriate accessibility attributes
    375    assert.strictEqual(checkboxContainer.prop("tabIndex"), "0");
    376    assert.strictEqual(checkboxContainer.prop("role"), "checkbox");
    377    assert.strictEqual(checkboxContainer.prop("aria-checked"), true);
    378 
    379    // the actual (hidden) checkbox should have tabIndex="-1" (to avoid double focus)
    380    const checkbox = wrapper.find("input[type='checkbox']").first();
    381    assert.strictEqual(checkbox.prop("tabIndex"), "-1");
    382  });
    383 });