columnEditModal.spec.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {changeInputValue, openMenu, selectByLabel} from 'sentry-test/select-new';
  4. import TagStore from 'sentry/stores/tagStore';
  5. import ColumnEditModal from 'sentry/views/eventsV2/table/columnEditModal';
  6. const stubEl = props => <div>{props.children}</div>;
  7. function mountModal({columns, onApply}, initialData) {
  8. return mountWithTheme(
  9. <ColumnEditModal
  10. Header={stubEl}
  11. Footer={stubEl}
  12. Body={stubEl}
  13. organization={initialData.organization}
  14. columns={columns}
  15. onApply={onApply}
  16. closeModal={() => void 0}
  17. />,
  18. initialData.routerContext
  19. );
  20. }
  21. describe('EventsV2 -> ColumnEditModal', function () {
  22. enforceActOnUseLegacyStoreHook();
  23. beforeEach(() => {
  24. TagStore.reset();
  25. TagStore.loadTagsSuccess([
  26. {name: 'browser.name', key: 'browser.name', count: 1},
  27. {name: 'custom-field', key: 'custom-field', count: 1},
  28. {name: 'user', key: 'user', count: 1},
  29. ]);
  30. });
  31. const initialData = initializeOrg({
  32. organization: {
  33. features: ['performance-view'],
  34. },
  35. });
  36. const columns = [
  37. {
  38. kind: 'field',
  39. field: 'event.type',
  40. },
  41. {
  42. kind: 'field',
  43. field: 'browser.name',
  44. },
  45. {
  46. kind: 'function',
  47. function: ['count', 'id'],
  48. },
  49. {
  50. kind: 'function',
  51. function: ['count_unique', 'title'],
  52. },
  53. {
  54. kind: 'function',
  55. function: ['p95', ''],
  56. },
  57. {
  58. kind: 'field',
  59. field: 'issue.id',
  60. },
  61. {
  62. kind: 'function',
  63. function: ['count_unique', 'issue.id'],
  64. },
  65. ];
  66. describe('basic rendering', function () {
  67. const wrapper = mountModal(
  68. {
  69. columns,
  70. onApply: () => void 0,
  71. },
  72. initialData
  73. );
  74. it('renders fields and basic controls', function () {
  75. // Should have fields equal to the columns.
  76. expect(wrapper.find('QueryField')).toHaveLength(columns.length);
  77. expect(wrapper.find('button[aria-label="Apply"]')).toHaveLength(1);
  78. expect(wrapper.find('button[aria-label="Add a Column"]')).toHaveLength(1);
  79. });
  80. it('renders delete and grab buttons', function () {
  81. expect(
  82. wrapper.find('RowContainer button[aria-label="Remove column"]').length
  83. ).toEqual(columns.length);
  84. expect(
  85. wrapper.find('RowContainer button[aria-label="Drag to reorder"]').length
  86. ).toEqual(columns.length);
  87. });
  88. });
  89. describe('rendering unknown fields', function () {
  90. const wrapper = mountModal(
  91. {
  92. columns: [
  93. {kind: 'function', function: ['count_unique', 'user-defined']},
  94. {kind: 'field', field: 'user-def'},
  95. ],
  96. onApply: () => void 0,
  97. },
  98. initialData
  99. );
  100. it('renders unknown fields in field and field parameter controls', function () {
  101. const funcRow = wrapper.find('QueryField').first();
  102. expect(
  103. funcRow.find('SelectControl[name="field"] [data-test-id="label"]').text()
  104. ).toBe('count_unique(\u2026)');
  105. expect(
  106. funcRow
  107. .find('SelectControl[name="parameter"] SingleValue span[data-test-id="label"]')
  108. .text()
  109. ).toBe('user-defined');
  110. const fieldRow = wrapper.find('QueryField').last();
  111. expect(
  112. fieldRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  113. ).toBe('user-def');
  114. expect(fieldRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  115. expect(fieldRow.find('BlankSpace')).toHaveLength(1);
  116. });
  117. });
  118. describe('rendering tags that overlap fields & functions', function () {
  119. const wrapper = mountModal(
  120. {
  121. columns: [
  122. {kind: 'field', field: 'tags[project]'},
  123. {kind: 'field', field: 'tags[count]'},
  124. ],
  125. onApply: () => void 0,
  126. },
  127. initialData
  128. );
  129. beforeEach(() => {
  130. TagStore.reset();
  131. TagStore.loadTagsSuccess([
  132. {name: 'project', key: 'project', count: 1},
  133. {name: 'count', key: 'count', count: 1},
  134. ]);
  135. });
  136. it('selects tag expressions that overlap fields', function () {
  137. const funcRow = wrapper.find('QueryField').first();
  138. expect(
  139. funcRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  140. ).toBe('project');
  141. expect(funcRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  142. });
  143. it('selects tag expressions that overlap functions', function () {
  144. const funcRow = wrapper.find('QueryField').last();
  145. expect(
  146. funcRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  147. ).toBe('count');
  148. expect(funcRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  149. });
  150. });
  151. describe('rendering functions', function () {
  152. const wrapper = mountModal(
  153. {
  154. columns: [
  155. {kind: 'function', function: ['count', 'id']},
  156. {kind: 'function', function: ['count_unique', 'title']},
  157. {kind: 'function', function: ['percentile', 'transaction.duration', '0.66']},
  158. ],
  159. onApply: () => void 0,
  160. },
  161. initialData
  162. );
  163. it('renders three columns when needed', function () {
  164. const countRow = wrapper.find('QueryField').first();
  165. // Has a select and 2 disabled inputs
  166. expect(countRow.find('SelectControl')).toHaveLength(1);
  167. expect(countRow.find('BlankSpace')).toHaveLength(2);
  168. const percentileRow = wrapper.find('QueryField').last();
  169. // two select fields, and one number input.
  170. expect(percentileRow.find('SelectControl')).toHaveLength(2);
  171. expect(percentileRow.find('BlankSpace')).toHaveLength(0);
  172. expect(percentileRow.find('StyledInput[inputMode="numeric"]')).toHaveLength(1);
  173. });
  174. });
  175. describe('function & column selection', function () {
  176. let onApply, wrapper;
  177. beforeEach(function () {
  178. onApply = jest.fn();
  179. wrapper = mountModal(
  180. {
  181. columns: [columns[0]],
  182. onApply,
  183. },
  184. initialData
  185. );
  186. });
  187. it('restricts column choices', function () {
  188. selectByLabel(wrapper, 'avg(\u2026)', {name: 'field', at: 0, control: true});
  189. openMenu(wrapper, {name: 'parameter', at: 0, control: true});
  190. const options = wrapper
  191. .find('QueryField SelectControl[name="parameter"] Option')
  192. .map(option => option.props().label);
  193. expect(options).not.toContain('title');
  194. expect(options).toContain('transaction.duration');
  195. });
  196. it('shows no options for parameterless functions', function () {
  197. selectByLabel(wrapper, 'last_seen()', {name: 'field', at: 0, control: true});
  198. expect(wrapper.find('QueryField BlankSpace')).toHaveLength(1);
  199. });
  200. it('shows additional inputs for multi-parameter functions', function () {
  201. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  202. // Parameter select should display and use the default value.
  203. const field = wrapper.find('QueryField SelectControl[name="parameter"]');
  204. expect(field.find('SingleValue span[data-test-id="label"]').text()).toBe(
  205. 'transaction.duration'
  206. );
  207. // Input should show and have default value.
  208. const refinement = wrapper.find('QueryField input[inputMode="numeric"]');
  209. expect(refinement.props().value).toBe('0.5');
  210. });
  211. it('handles scalar field parameters', function () {
  212. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  213. // Parameter select should display and use the default value.
  214. const field = wrapper.find('QueryField input[name="refinement"]');
  215. expect(field.props().value).toBe('300');
  216. // Trigger a blur and make sure the column is not wrong.
  217. field.simulate('blur');
  218. // Apply the changes so we can see the new columns.
  219. wrapper.find('Button[priority="primary"]').simulate('click');
  220. expect(onApply).toHaveBeenCalledWith([
  221. {kind: 'function', function: ['apdex', '300', undefined, undefined]},
  222. ]);
  223. });
  224. it('handles parameter overrides', function () {
  225. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  226. // Parameter select should display and use the default value.
  227. const field = wrapper.find('QueryField input[name="refinement"]');
  228. expect(field.props().value).toBe('300');
  229. expect(field.prop('placeholder')).toBe(undefined);
  230. // Trigger a blur and make sure the column is not wrong.
  231. field.simulate('blur');
  232. });
  233. it('clears unused parameters', function () {
  234. // Choose percentile, then apdex which has fewer parameters and different types.
  235. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  236. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  237. // Apply the changes so we can see the new columns.
  238. wrapper.find('Button[priority="primary"]').simulate('click');
  239. expect(onApply).toHaveBeenCalledWith([
  240. {kind: 'function', function: ['apdex', '300', undefined, undefined]},
  241. ]);
  242. });
  243. it('clears all unused parameters', function () {
  244. // Choose percentile, then failure_rate which has no parameters.
  245. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  246. selectByLabel(wrapper, 'failure_rate()', {name: 'field', at: 0, control: true});
  247. // Apply the changes so we can see the new columns.
  248. wrapper.find('Button[priority="primary"]').simulate('click');
  249. expect(onApply).toHaveBeenCalledWith([
  250. {kind: 'function', function: ['failure_rate', '', undefined, undefined]},
  251. ]);
  252. });
  253. it('clears all unused parameters with count_if to two parameter function', function () {
  254. // Choose percentile, then failure_rate which has no parameters.
  255. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  256. selectByLabel(wrapper, 'user', {name: 'parameter', at: 0, control: true});
  257. selectByLabel(wrapper, 'count_miserable(\u2026)', {
  258. name: 'field',
  259. at: 0,
  260. control: true,
  261. });
  262. // Apply the changes so we can see the new columns.
  263. wrapper.find('Button[priority="primary"]').simulate('click');
  264. expect(onApply).toHaveBeenCalledWith([
  265. {kind: 'function', function: ['count_miserable', 'user', '300', undefined]},
  266. ]);
  267. });
  268. it('clears all unused parameters with count_if to one parameter function', function () {
  269. // Choose percentile, then failure_rate which has no parameters.
  270. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  271. selectByLabel(wrapper, 'user', {name: 'parameter', at: 0, control: true});
  272. selectByLabel(wrapper, 'count_unique(\u2026)', {
  273. name: 'field',
  274. at: 0,
  275. control: true,
  276. });
  277. // Apply the changes so we can see the new columns.
  278. wrapper.find('Button[priority="primary"]').simulate('click');
  279. expect(onApply).toHaveBeenCalledWith([
  280. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  281. ]);
  282. });
  283. it('clears all unused parameters with count_if to parameterless function', function () {
  284. // Choose percentile, then failure_rate which has no parameters.
  285. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  286. selectByLabel(wrapper, 'count()', {
  287. name: 'field',
  288. at: 0,
  289. control: true,
  290. });
  291. // Apply the changes so we can see the new columns.
  292. wrapper.find('Button[priority="primary"]').simulate('click');
  293. expect(onApply).toHaveBeenCalledWith([
  294. {kind: 'function', function: ['count', '', undefined, undefined]},
  295. ]);
  296. });
  297. it('updates equation errors when they change', function () {
  298. const newWrapper = mountModal(
  299. {
  300. columns: [
  301. {
  302. kind: 'equation',
  303. field: '1 / 0',
  304. },
  305. ],
  306. onApply,
  307. },
  308. initialData
  309. );
  310. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  311. expect(newWrapper.find('QueryField ArithmeticError').prop('title')).toBe(
  312. 'Division by 0 is not allowed'
  313. );
  314. const field = newWrapper.find('QueryField input[type="text"]');
  315. changeInputValue(field, '1+1+1+1+1+1+1+1+1+1+1+1');
  316. newWrapper.update();
  317. field.simulate('blur');
  318. expect(newWrapper.find('QueryField ArithmeticError').prop('title')).toBe(
  319. 'Maximum operators exceeded'
  320. );
  321. });
  322. it('resets required field to previous value if cleared', function () {
  323. const initialColumnVal = '0.6';
  324. const newWrapper = mountModal(
  325. {
  326. columns: [
  327. {
  328. kind: 'function',
  329. function: [
  330. 'percentile',
  331. 'transaction.duration',
  332. initialColumnVal,
  333. undefined,
  334. ],
  335. },
  336. ],
  337. onApply,
  338. },
  339. initialData
  340. );
  341. const field = newWrapper.find('QueryField input[name="refinement"]');
  342. changeInputValue(field, '');
  343. newWrapper.update();
  344. field.simulate('blur');
  345. expect(newWrapper.find('QueryField input[name="refinement"]').prop('value')).toBe(
  346. initialColumnVal
  347. );
  348. newWrapper.find('Button[priority="primary"]').simulate('click');
  349. expect(onApply).toHaveBeenCalledWith([
  350. {
  351. kind: 'function',
  352. function: ['percentile', 'transaction.duration', initialColumnVal, undefined],
  353. },
  354. ]);
  355. });
  356. });
  357. describe('equation automatic update', function () {
  358. let onApply;
  359. beforeEach(function () {
  360. onApply = jest.fn();
  361. });
  362. it('update simple equation columns when they change', function () {
  363. const newWrapper = mountModal(
  364. {
  365. columns: [
  366. {
  367. kind: 'function',
  368. function: ['count_unique', 'user'],
  369. },
  370. {
  371. kind: 'function',
  372. function: ['p95', ''],
  373. },
  374. {
  375. kind: 'equation',
  376. field: '(p95() / count_unique(user) ) * 100',
  377. },
  378. ],
  379. onApply,
  380. },
  381. initialData
  382. );
  383. selectByLabel(newWrapper, 'count_if(\u2026)', {
  384. name: 'field',
  385. at: 0,
  386. control: true,
  387. });
  388. // Apply the changes so we can see the new columns.
  389. newWrapper.find('Button[priority="primary"]').simulate('click');
  390. expect(onApply).toHaveBeenCalledWith([
  391. {kind: 'function', function: ['count_if', 'user', 'equals', '300']},
  392. {kind: 'function', function: ['p95', '']},
  393. {kind: 'equation', field: '(p95() / count_if(user,equals,300) ) * 100'},
  394. ]);
  395. });
  396. it('update equation with repeated columns when they change', function () {
  397. const newWrapper = mountModal(
  398. {
  399. columns: [
  400. {
  401. kind: 'function',
  402. function: ['count_unique', 'user'],
  403. },
  404. {
  405. kind: 'equation',
  406. field:
  407. 'count_unique(user) + (count_unique(user) - count_unique(user)) * 5',
  408. },
  409. ],
  410. onApply,
  411. },
  412. initialData
  413. );
  414. selectByLabel(newWrapper, 'count()', {
  415. name: 'field',
  416. at: 0,
  417. control: true,
  418. });
  419. // Apply the changes so we can see the new columns.
  420. newWrapper.find('Button[priority="primary"]').simulate('click');
  421. expect(onApply).toHaveBeenCalledWith([
  422. {kind: 'function', function: ['count', '', undefined, undefined]},
  423. {kind: 'equation', field: 'count() + (count() - count()) * 5'},
  424. ]);
  425. });
  426. it('handles equations with duplicate fields', function () {
  427. const newWrapper = mountModal(
  428. {
  429. columns: [
  430. {
  431. kind: 'field',
  432. field: 'spans.db',
  433. },
  434. {
  435. kind: 'field',
  436. field: 'spans.db',
  437. },
  438. {
  439. kind: 'equation',
  440. field: 'spans.db - spans.db',
  441. },
  442. ],
  443. onApply,
  444. },
  445. initialData
  446. );
  447. selectByLabel(newWrapper, 'count()', {
  448. name: 'field',
  449. at: 0,
  450. control: true,
  451. });
  452. // Apply the changes so we can see the new columns.
  453. newWrapper.find('Button[priority="primary"]').simulate('click');
  454. // Because spans.db is still a selected column it isn't swapped
  455. expect(onApply).toHaveBeenCalledWith([
  456. {kind: 'function', function: ['count', '', undefined, undefined]},
  457. {kind: 'field', field: 'spans.db'},
  458. {kind: 'equation', field: 'spans.db - spans.db'},
  459. ]);
  460. });
  461. it('handles equations with duplicate functions', function () {
  462. const newWrapper = mountModal(
  463. {
  464. columns: [
  465. {
  466. kind: 'function',
  467. function: ['count', '', undefined, undefined],
  468. },
  469. {
  470. kind: 'function',
  471. function: ['count', '', undefined, undefined],
  472. },
  473. {
  474. kind: 'equation',
  475. field: 'count() - count()',
  476. },
  477. ],
  478. onApply,
  479. },
  480. initialData
  481. );
  482. selectByLabel(newWrapper, 'count_unique(\u2026)', {
  483. name: 'field',
  484. at: 0,
  485. control: true,
  486. });
  487. // Apply the changes so we can see the new columns.
  488. newWrapper.find('Button[priority="primary"]').simulate('click');
  489. expect(onApply).toHaveBeenCalledWith([
  490. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  491. {kind: 'function', function: ['count', '', undefined, undefined]},
  492. {kind: 'equation', field: 'count() - count()'},
  493. ]);
  494. });
  495. it('handles incomplete equations', function () {
  496. const newWrapper = mountModal(
  497. {
  498. columns: [
  499. {
  500. kind: 'function',
  501. function: ['count', '', undefined, undefined],
  502. },
  503. {
  504. kind: 'equation',
  505. field: 'count() - count() arst count() ',
  506. },
  507. ],
  508. onApply,
  509. },
  510. initialData
  511. );
  512. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  513. selectByLabel(newWrapper, 'count_unique(\u2026)', {
  514. name: 'field',
  515. at: 0,
  516. control: true,
  517. });
  518. // Apply the changes so we can see the new columns.
  519. newWrapper.find('Button[priority="primary"]').simulate('click');
  520. // With the way the parser works only tokens up to the error will be updated
  521. expect(onApply).toHaveBeenCalledWith([
  522. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  523. {
  524. kind: 'equation',
  525. field: 'count_unique(user) - count_unique(user) arst count() ',
  526. },
  527. ]);
  528. });
  529. });
  530. describe('adding rows', function () {
  531. const wrapper = mountModal(
  532. {
  533. columns: [columns[0]],
  534. onApply: () => void 0,
  535. },
  536. initialData
  537. );
  538. it('allows rows to be added, but only up to 20', function () {
  539. for (let i = 2; i <= 20; i++) {
  540. wrapper.find('button[aria-label="Add a Column"]').simulate('click');
  541. expect(wrapper.find('QueryField')).toHaveLength(i);
  542. }
  543. expect(
  544. wrapper.find('button[aria-label="Add a Column"]').prop('aria-disabled')
  545. ).toBe(true);
  546. });
  547. });
  548. describe('removing rows', function () {
  549. const wrapper = mountModal(
  550. {
  551. columns: [columns[0], columns[1]],
  552. onApply: () => void 0,
  553. },
  554. initialData
  555. );
  556. it('allows rows to be removed, but not the last one', function () {
  557. expect(wrapper.find('QueryField')).toHaveLength(2);
  558. wrapper
  559. .find('RowContainer button[aria-label="Remove column"]')
  560. .first()
  561. .simulate('click');
  562. expect(wrapper.find('QueryField')).toHaveLength(1);
  563. // Last row cannot be removed or dragged.
  564. expect(
  565. wrapper.find('RowContainer button[aria-label="Remove column"]')
  566. ).toHaveLength(0);
  567. expect(
  568. wrapper.find('RowContainer button[aria-label="Drag to reorder"]')
  569. ).toHaveLength(0);
  570. });
  571. it('does not count equations towards the count of rows', function () {
  572. const newWrapper = mountModal(
  573. {
  574. columns: [
  575. columns[0],
  576. columns[1],
  577. {
  578. kind: 'equation',
  579. field: '5 + 5',
  580. },
  581. ],
  582. onApply: () => void 0,
  583. },
  584. initialData
  585. );
  586. expect(newWrapper.find('QueryField')).toHaveLength(3);
  587. newWrapper
  588. .find('RowContainer button[aria-label="Remove column"]')
  589. .first()
  590. .simulate('click');
  591. expect(newWrapper.find('QueryField')).toHaveLength(2);
  592. // Can still remove the equation
  593. expect(
  594. newWrapper.find('RowContainer button[aria-label="Remove column"]')
  595. ).toHaveLength(1);
  596. // And both are draggable
  597. expect(
  598. newWrapper.find('RowContainer button[aria-label="Drag to reorder"]')
  599. ).toHaveLength(2);
  600. });
  601. it('handles equations being deleted', function () {
  602. const newWrapper = mountModal(
  603. {
  604. columns: [
  605. {
  606. kind: 'equation',
  607. field: '1 / 0',
  608. },
  609. columns[0],
  610. columns[1],
  611. ],
  612. onApply: () => void 0,
  613. },
  614. initialData
  615. );
  616. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  617. expect(newWrapper.find('QueryField')).toHaveLength(3);
  618. newWrapper
  619. .find('RowContainer button[aria-label="Remove column"]')
  620. .first()
  621. .simulate('click');
  622. expect(newWrapper.find('QueryField')).toHaveLength(2);
  623. expect(newWrapper.find('ArithmeticError')).toHaveLength(0);
  624. });
  625. });
  626. describe('apply action', function () {
  627. const onApply = jest.fn();
  628. const wrapper = mountModal(
  629. {
  630. columns: [columns[0], columns[1]],
  631. onApply,
  632. },
  633. initialData
  634. );
  635. it('reflects added and removed columns', function () {
  636. // Remove a column, then add a blank one an select a value in it.
  637. wrapper.find('button[aria-label="Remove column"]').first().simulate('click');
  638. wrapper.find('button[aria-label="Add a Column"]').simulate('click');
  639. wrapper.update();
  640. selectByLabel(wrapper, 'title', {name: 'field', at: 1, control: true});
  641. wrapper.find('button[aria-label="Apply"]').simulate('click');
  642. expect(onApply).toHaveBeenCalledWith([columns[1], {kind: 'field', field: 'title'}]);
  643. });
  644. });
  645. });