useHotkeys.spec.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {reactHooks} from 'sentry-test/reactTestingLibrary';
  2. import {getKeyCode} from 'sentry/utils/getKeyCode';
  3. import {useHotkeys} from 'sentry/utils/useHotkeys';
  4. describe('useHotkeys', function () {
  5. let events: Record<string, (evt: EventListenerOrEventListenerObject) => void> = {};
  6. function makeKeyEventFixture(keyCode, options) {
  7. return {
  8. keyCode: getKeyCode(keyCode),
  9. preventDefault: jest.fn(),
  10. ...options,
  11. };
  12. }
  13. beforeEach(() => {
  14. // Empty our events before each test case
  15. events = {};
  16. // Define the addEventListener method with a Jest mock function
  17. // @ts-expect-error we are overriding the global document object
  18. document.addEventListener = jest.fn((event: string, callback: () => any) => {
  19. events[event] = callback;
  20. });
  21. document.removeEventListener = jest.fn(event => {
  22. delete events[event];
  23. });
  24. });
  25. it('handles a simple match', function () {
  26. const callback = jest.fn();
  27. reactHooks.renderHook(p => useHotkeys(p, []), {
  28. initialProps: [{match: 'ctrl+s', callback}],
  29. });
  30. expect(events.keydown).toBeDefined();
  31. expect(callback).not.toHaveBeenCalled();
  32. const evt = makeKeyEventFixture('s', {ctrlKey: true});
  33. events.keydown(evt);
  34. expect(evt.preventDefault).toHaveBeenCalled();
  35. expect(callback).toHaveBeenCalled();
  36. });
  37. it('handles multiple matches', function () {
  38. const callback = jest.fn();
  39. reactHooks.renderHook(p => useHotkeys(p, []), {
  40. initialProps: [{match: ['ctrl+s', 'command+m'], callback}],
  41. });
  42. expect(events.keydown).toBeDefined();
  43. expect(callback).not.toHaveBeenCalled();
  44. events.keydown(makeKeyEventFixture('s', {ctrlKey: true}));
  45. expect(callback).toHaveBeenCalled();
  46. callback.mockClear();
  47. events.keydown(makeKeyEventFixture('m', {metaKey: true}));
  48. expect(callback).toHaveBeenCalled();
  49. });
  50. it('handles a complex match', function () {
  51. const callback = jest.fn();
  52. reactHooks.renderHook(p => useHotkeys(p, []), {
  53. initialProps: [{match: ['command+ctrl+alt+shift+x'], callback}],
  54. });
  55. expect(events.keydown).toBeDefined();
  56. expect(callback).not.toHaveBeenCalled();
  57. events.keydown(
  58. makeKeyEventFixture('x', {
  59. altKey: true,
  60. metaKey: true,
  61. shiftKey: true,
  62. ctrlKey: true,
  63. })
  64. );
  65. expect(callback).toHaveBeenCalled();
  66. });
  67. it('does not match when extra modifiers are pressed', function () {
  68. const callback = jest.fn();
  69. reactHooks.renderHook(p => useHotkeys(p, []), {
  70. initialProps: [{match: ['command+shift+x'], callback}],
  71. });
  72. expect(events.keydown).toBeDefined();
  73. expect(callback).not.toHaveBeenCalled();
  74. events.keydown(
  75. makeKeyEventFixture('x', {
  76. altKey: true,
  77. metaKey: true,
  78. shiftKey: true,
  79. ctrlKey: true,
  80. })
  81. );
  82. expect(callback).not.toHaveBeenCalled();
  83. });
  84. it('updates with rerender', function () {
  85. const callback = jest.fn();
  86. const {rerender} = reactHooks.renderHook(
  87. p => useHotkeys([{match: p.match, callback}], [p]),
  88. {
  89. initialProps: {match: 'ctrl+s'},
  90. }
  91. );
  92. expect(events.keydown).toBeDefined();
  93. expect(callback).not.toHaveBeenCalled();
  94. events.keydown(makeKeyEventFixture('s', {ctrlKey: true}));
  95. expect(callback).toHaveBeenCalled();
  96. callback.mockClear();
  97. rerender({match: 'command+m'});
  98. events.keydown(makeKeyEventFixture('s', {ctrlKey: true}));
  99. expect(callback).not.toHaveBeenCalled();
  100. events.keydown(makeKeyEventFixture('m', {metaKey: true}));
  101. expect(callback).toHaveBeenCalled();
  102. });
  103. it('skips input and textarea', function () {
  104. const callback = jest.fn();
  105. reactHooks.renderHook(p => useHotkeys(p, []), {
  106. initialProps: [{match: ['/'], callback}],
  107. });
  108. events.keydown(makeKeyEventFixture('/', {target: document.createElement('input')}));
  109. expect(callback).not.toHaveBeenCalled();
  110. });
  111. it('does not skips input and textarea with includesInputs', function () {
  112. const callback = jest.fn();
  113. reactHooks.renderHook(p => useHotkeys(p, []), {
  114. initialProps: [{match: ['/'], callback, includeInputs: true}],
  115. });
  116. events.keydown(makeKeyEventFixture('/', {target: document.createElement('input')}));
  117. expect(callback).toHaveBeenCalled();
  118. });
  119. it('skips preventDefault', function () {
  120. const callback = jest.fn();
  121. reactHooks.renderHook(p => useHotkeys(p, []), {
  122. initialProps: [{match: 'ctrl+s', callback, skipPreventDefault: true}],
  123. });
  124. const evt = makeKeyEventFixture('s', {ctrlKey: true});
  125. events.keydown(evt);
  126. expect(evt.preventDefault).not.toHaveBeenCalled();
  127. expect(callback).toHaveBeenCalled();
  128. });
  129. });