index.spec.tsx 6.1 KB

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