ContextMenu.test.jsx (7930B)
1 import { 2 ContextMenu, 3 ContextMenuItem, 4 _ContextMenuItem, 5 } from "content-src/components/ContextMenu/ContextMenu"; 6 import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton"; 7 import { mount, shallow } from "enzyme"; 8 import React from "react"; 9 import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; 10 import { Provider } from "react-redux"; 11 import { combineReducers, createStore } from "redux"; 12 13 const DEFAULT_PROPS = { 14 onUpdate: () => {}, 15 options: [], 16 tabbableOptionsLength: 0, 17 }; 18 19 const DEFAULT_MENU_OPTIONS = [ 20 "MoveUp", 21 "MoveDown", 22 "Separator", 23 "ManageSection", 24 ]; 25 26 const FakeMenu = props => { 27 return <div>{props.children}</div>; 28 }; 29 30 describe("<ContextMenuButton>", () => { 31 // eslint-disable-next-line no-shadow 32 function mountWithProps(options) { 33 const store = createStore(combineReducers(reducers), INITIAL_STATE); 34 return mount( 35 <Provider store={store}> 36 <ContextMenuButton> 37 <ContextMenu options={options} /> 38 </ContextMenuButton> 39 </Provider> 40 ); 41 } 42 43 let sandbox; 44 beforeEach(() => { 45 sandbox = sinon.createSandbox(); 46 }); 47 afterEach(() => { 48 sandbox.restore(); 49 }); 50 it("should call onUpdate when clicked", () => { 51 const onUpdate = sandbox.spy(); 52 const wrapper = mount( 53 <ContextMenuButton onUpdate={onUpdate}> 54 <FakeMenu /> 55 </ContextMenuButton> 56 ); 57 wrapper.find(".context-menu-button").simulate("click"); 58 assert.calledOnce(onUpdate); 59 }); 60 it("should call onUpdate when activated with Enter", () => { 61 const onUpdate = sandbox.spy(); 62 const wrapper = mount( 63 <ContextMenuButton onUpdate={onUpdate}> 64 <FakeMenu /> 65 </ContextMenuButton> 66 ); 67 wrapper.find(".context-menu-button").simulate("keydown", { key: "Enter" }); 68 assert.calledOnce(onUpdate); 69 }); 70 it("should call onClick", () => { 71 const onClick = sandbox.spy(ContextMenuButton.prototype, "onClick"); 72 const wrapper = mount( 73 <ContextMenuButton> 74 <FakeMenu /> 75 </ContextMenuButton> 76 ); 77 wrapper.find("button").simulate("click"); 78 assert.calledOnce(onClick); 79 }); 80 it("should have a default keyboardAccess prop of false", () => { 81 const wrapper = mountWithProps(DEFAULT_MENU_OPTIONS); 82 wrapper.find(ContextMenuButton).setState({ showContextMenu: true }); 83 assert.equal(wrapper.find(ContextMenu).prop("keyboardAccess"), false); 84 }); 85 it("should pass the keyboardAccess prop down to ContextMenu", () => { 86 const wrapper = mountWithProps(DEFAULT_MENU_OPTIONS); 87 wrapper 88 .find(ContextMenuButton) 89 .setState({ showContextMenu: true, contextMenuKeyboard: true }); 90 assert.equal(wrapper.find(ContextMenu).prop("keyboardAccess"), true); 91 }); 92 it("should call focusFirst when keyboardAccess is true", () => { 93 // eslint-disable-next-line no-shadow 94 const options = [{ label: "item1", first: true }]; 95 const wrapper = mountWithProps(options); 96 const focusFirst = sandbox.spy(_ContextMenuItem.prototype, "focusFirst"); 97 wrapper 98 .find(ContextMenuButton) 99 .setState({ showContextMenu: true, contextMenuKeyboard: true }); 100 assert.calledOnce(focusFirst); 101 }); 102 }); 103 104 describe("<ContextMenu>", () => { 105 function mountWithProps(props) { 106 const store = createStore(combineReducers(reducers), INITIAL_STATE); 107 return mount( 108 <Provider store={store}> 109 <ContextMenu {...props} /> 110 </Provider> 111 ); 112 } 113 114 it("should render all the options provided", () => { 115 // eslint-disable-next-line no-shadow 116 const options = [ 117 { label: "item1" }, 118 { type: "separator" }, 119 { label: "item2" }, 120 ]; 121 const wrapper = shallow( 122 <ContextMenu {...DEFAULT_PROPS} options={options} /> 123 ); 124 assert.lengthOf(wrapper.find(".context-menu-list").children(), 3); 125 }); 126 it("should not add a link for a separator", () => { 127 // eslint-disable-next-line no-shadow 128 const options = [{ label: "item1" }, { type: "separator" }]; 129 const wrapper = shallow( 130 <ContextMenu {...DEFAULT_PROPS} options={options} /> 131 ); 132 assert.lengthOf(wrapper.find(".separator"), 1); 133 }); 134 it("should add a link for all types that are not separators", () => { 135 // eslint-disable-next-line no-shadow 136 const options = [{ label: "item1" }, { type: "separator" }]; 137 const wrapper = shallow( 138 <ContextMenu {...DEFAULT_PROPS} options={options} /> 139 ); 140 assert.lengthOf(wrapper.find(ContextMenuItem), 1); 141 }); 142 it("should not add an icon to any items", () => { 143 const props = Object.assign({}, DEFAULT_PROPS, { 144 options: [{ label: "item1", icon: "icon1" }, { type: "separator" }], 145 }); 146 const wrapper = mountWithProps(props); 147 assert.lengthOf(wrapper.find(".icon-icon1"), 0); 148 }); 149 it("should be tabbable", () => { 150 const props = { 151 options: [{ label: "item1", icon: "icon1" }, { type: "separator" }], 152 }; 153 const wrapper = mountWithProps(props); 154 assert.equal( 155 wrapper.find(".context-menu-item").props().role, 156 "presentation" 157 ); 158 }); 159 it("should call onUpdate with false when an option is clicked", () => { 160 const onUpdate = sinon.spy(); 161 const onClick = sinon.spy(); 162 const props = Object.assign({}, DEFAULT_PROPS, { 163 onUpdate, 164 options: [{ label: "item1", onClick }], 165 }); 166 const wrapper = mountWithProps(props); 167 wrapper.find(".context-menu-item button").simulate("click"); 168 assert.calledOnce(onUpdate); 169 assert.calledOnce(onClick); 170 }); 171 it("should not have disabled className by default", () => { 172 const props = Object.assign({}, DEFAULT_PROPS, { 173 options: [{ label: "item1", icon: "icon1" }, { type: "separator" }], 174 }); 175 const wrapper = mountWithProps(props); 176 assert.lengthOf(wrapper.find(".context-menu-item a.disabled"), 0); 177 }); 178 it("should add disabled className to any disabled options", () => { 179 // eslint-disable-next-line no-shadow 180 const options = [ 181 { label: "item1", icon: "icon1", disabled: true }, 182 { type: "separator" }, 183 ]; 184 const props = Object.assign({}, DEFAULT_PROPS, { options }); 185 const wrapper = mountWithProps(props); 186 assert.lengthOf(wrapper.find(".context-menu-item button.disabled"), 1); 187 }); 188 it("should have the context-menu-item class", () => { 189 // eslint-disable-next-line no-shadow 190 const options = [{ label: "item1", icon: "icon1" }]; 191 const props = Object.assign({}, DEFAULT_PROPS, { options }); 192 const wrapper = mountWithProps(props); 193 assert.lengthOf(wrapper.find(".context-menu-item"), 1); 194 }); 195 it("should call onClick when onKeyDown is called with Enter", () => { 196 const onClick = sinon.spy(); 197 const props = Object.assign({}, DEFAULT_PROPS, { 198 options: [{ label: "item1", onClick }], 199 }); 200 const wrapper = mountWithProps(props); 201 wrapper 202 .find(".context-menu-item button") 203 .simulate("keydown", { key: "Enter" }); 204 assert.calledOnce(onClick); 205 }); 206 it("should call focusSibling when onKeyDown is called with ArrowUp", () => { 207 const props = Object.assign({}, DEFAULT_PROPS, { 208 options: [{ label: "item1" }], 209 }); 210 const wrapper = mountWithProps(props); 211 const focusSibling = sinon.stub( 212 wrapper.find(_ContextMenuItem).instance(), 213 "focusSibling" 214 ); 215 wrapper 216 .find(".context-menu-item button") 217 .simulate("keydown", { key: "ArrowUp" }); 218 assert.calledOnce(focusSibling); 219 }); 220 it("should call focusSibling when onKeyDown is called with ArrowDown", () => { 221 const props = Object.assign({}, DEFAULT_PROPS, { 222 options: [{ label: "item1" }], 223 }); 224 const wrapper = mountWithProps(props); 225 const focusSibling = sinon.stub( 226 wrapper.find(_ContextMenuItem).instance(), 227 "focusSibling" 228 ); 229 wrapper 230 .find(".context-menu-item button") 231 .simulate("keydown", { key: "ArrowDown" }); 232 assert.calledOnce(focusSibling); 233 }); 234 });