index.spec.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import {routerContext} from 'fixtures/js-stubs/routerContext';
  2. import Fuse from 'fuse.js';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import {textWithMarkupMatcher} from 'sentry-test/utils';
  5. import {Search, SearchProps} from 'sentry/components/search';
  6. import {ChildProps, Result, ResultItem} from 'sentry/components/search/sources/types';
  7. function makeSearchResultsMock(items?: ResultItem[], threshold?: number) {
  8. return function SearchResultsMock({
  9. loading,
  10. children,
  11. query,
  12. }: {
  13. children: (props: ChildProps) => React.ReactElement | null;
  14. loading: boolean;
  15. query: string;
  16. }): React.ReactElement<any, any> | null {
  17. const searchableItems: ResultItem[] = items ?? [
  18. {
  19. resultType: 'integration',
  20. sourceType: 'organization',
  21. title: 'Vandelay Industries - Import',
  22. model: {slug: 'vdl-imp'},
  23. },
  24. {
  25. resultType: 'integration',
  26. model: {slug: 'vdl-exp'},
  27. sourceType: 'organization',
  28. title: 'Vandelay Industries - Export',
  29. },
  30. ];
  31. const results = new Fuse(searchableItems, {
  32. keys: ['title'],
  33. includeMatches: true,
  34. includeScore: true,
  35. threshold: threshold ?? 0.3,
  36. })
  37. .search(query)
  38. .map(item => {
  39. const result: Result = {
  40. item: item.item,
  41. score: item.score,
  42. matches: item.matches,
  43. refIndex: 0,
  44. };
  45. return result;
  46. });
  47. return children({
  48. isLoading: loading,
  49. results,
  50. });
  51. } as React.ComponentType;
  52. }
  53. const makeSearchProps = (partial: Partial<SearchProps> = {}): SearchProps => {
  54. return {
  55. renderInput: ({getInputProps}) => {
  56. return <input {...getInputProps({placeholder: 'Search Input'})} />;
  57. },
  58. sources: [makeSearchResultsMock()],
  59. caseSensitive: false,
  60. minSearch: 0,
  61. ...partial,
  62. } as SearchProps;
  63. };
  64. describe('Search', () => {
  65. beforeEach(() => {
  66. jest.clearAllMocks();
  67. jest.useFakeTimers();
  68. });
  69. afterEach(() => {
  70. jest.useRealTimers();
  71. });
  72. it('renders search results from source', () => {
  73. render(<Search {...makeSearchProps()} />, {
  74. context: routerContext(),
  75. });
  76. userEvent.click(screen.getByPlaceholderText('Search Input'));
  77. userEvent.keyboard('Export');
  78. jest.advanceTimersByTime(500);
  79. expect(
  80. screen.getByText(textWithMarkupMatcher(/Vandelay Industries - Export/))
  81. ).toBeInTheDocument();
  82. expect(
  83. screen.queryByText(textWithMarkupMatcher(/Vandelay Industries - Import/))
  84. ).not.toBeInTheDocument();
  85. });
  86. it('navigates to a route when item has to prop', () => {
  87. render(
  88. <Search
  89. {...makeSearchProps({
  90. sources: [
  91. makeSearchResultsMock([
  92. {
  93. resultType: 'integration',
  94. sourceType: 'organization',
  95. title: 'Vandelay Industries - Import',
  96. to: 'https://vandelayindustries.io/import',
  97. model: {slug: 'vdl-imp'},
  98. },
  99. ]),
  100. ],
  101. })}
  102. />,
  103. {
  104. context: routerContext(),
  105. }
  106. );
  107. const opener = {opener: 'Sentry.io', location: {href: null}};
  108. // @ts-ignore this is a partial mock of the window object
  109. const windowSpy = jest.spyOn(window, 'open').mockReturnValue(opener);
  110. userEvent.click(screen.getByPlaceholderText('Search Input'));
  111. userEvent.keyboard('Import');
  112. userEvent.click(
  113. screen.getByText(textWithMarkupMatcher(/Vandelay Industries - Import/))
  114. );
  115. expect(windowSpy).toHaveBeenCalledTimes(1);
  116. expect(opener.opener).not.toBe('Sentry.io');
  117. expect(opener.location.href).toBe('https://vandelayindustries.io/import');
  118. });
  119. it('calls item action when it is a function', () => {
  120. render(
  121. <Search
  122. {...makeSearchProps({
  123. sources: [
  124. makeSearchResultsMock([
  125. {
  126. resultType: 'integration',
  127. sourceType: 'organization',
  128. title: 'Vandelay Industries - Import',
  129. to: 'https://vandelayindustries.io/import',
  130. model: {slug: 'vdl-imp'},
  131. },
  132. ]),
  133. ],
  134. })}
  135. />,
  136. {
  137. context: routerContext(),
  138. }
  139. );
  140. const opener = {opener: 'Sentry.io', location: {href: null}};
  141. // @ts-ignore this is a partial mock of the window object
  142. const windowSpy = jest.spyOn(window, 'open').mockReturnValue(opener);
  143. userEvent.click(screen.getByPlaceholderText('Search Input'));
  144. userEvent.keyboard('Import');
  145. userEvent.click(
  146. screen.getByText(textWithMarkupMatcher(/Vandelay Industries - Import/))
  147. );
  148. expect(windowSpy).toHaveBeenCalledTimes(1);
  149. expect(opener.opener).not.toBe('Sentry.io');
  150. expect(opener.location.href).toBe('https://vandelayindustries.io/import');
  151. });
  152. it('renders max search results', async () => {
  153. const results: ResultItem[] = new Array(10).fill(0).map((_, i) => ({
  154. resultType: 'integration',
  155. sourceType: 'organization',
  156. title: `${i} Vandelay Industries - Import`,
  157. to: 'https://vandelayindustries.io/import',
  158. model: {slug: 'vdl-imp'},
  159. }));
  160. render(
  161. <Search
  162. {...makeSearchProps({
  163. maxResults: 5,
  164. sources: [makeSearchResultsMock(results)],
  165. })}
  166. />,
  167. {
  168. context: routerContext(),
  169. }
  170. );
  171. userEvent.click(screen.getByPlaceholderText('Search Input'));
  172. userEvent.keyboard('Vandelay');
  173. expect(await screen.findAllByText(/Vandelay/)).toHaveLength(5);
  174. for (let i = 0; i < 5; i++) {
  175. expect(
  176. screen.getByText(textWithMarkupMatcher(`${i} Vandelay Industries - Import`))
  177. ).toBeInTheDocument();
  178. }
  179. });
  180. it('shows no search result', () => {
  181. render(
  182. <Search
  183. {...makeSearchProps({
  184. maxResults: 5,
  185. sources: [makeSearchResultsMock([])],
  186. })}
  187. />,
  188. {
  189. context: routerContext(),
  190. }
  191. );
  192. userEvent.click(screen.getByPlaceholderText('Search Input'));
  193. userEvent.keyboard('Vandelay');
  194. expect(screen.getByText(/No results/)).toBeInTheDocument();
  195. });
  196. });