diff --git a/package.json b/package.json index 9ae4c15c..139d5aa9 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "rollup-plugin-terser": "^7.0.1", "sass": "^1.28.0", "sass-loader": "^10.0.5", + "sinon": "^18.0.0", "stream-browserify": "^3.0.0", "style-loader": "^2.0.0", "svg-url-loader": "^6.0.0", diff --git a/src/components/Transcript/Transcript.js b/src/components/Transcript/Transcript.js index bd2f94f5..15dc069f 100644 --- a/src/components/Transcript/Transcript.js +++ b/src/components/Transcript/Transcript.js @@ -32,7 +32,7 @@ const buildSpeakerText = (item, isDocx = false) => { } }; -const TranscriptLine = ({ +const TranscriptLine = React.memo(({ item, goToItem, isActive, @@ -185,13 +185,15 @@ const TranscriptLine = ({ isFocused && 'focused' )} data-testid="transcript_untimed_text"> -

+

+

; } else { return null; } -}; +}); const Spinner = () => (
@@ -201,8 +203,7 @@ const Spinner = () => (
); -const TranscriptList = ({ - isSearchable, +const TranscriptList = React.memo(({ seekPlayer, currentTime, searchResults, @@ -239,51 +240,49 @@ const TranscriptList = ({ break; } - if (isSearchable) { - if (!searchResults.results || searchResults.results.length === 0) { - return ( - - ); - } else { - return ( -
- { - searchResults.ids.map((itemId) => ( - - )) - } -
- ); - } - } else { + if (transcriptInfo.tError) { return (

{transcriptInfo.tError}

); + } else if (!searchResults.results || searchResults.results.length === 0) { + return ( + + ); + } else { + return ( +
+ { + searchResults.ids.map((itemId) => ( + + )) + } +
+ ); } -}; +}); /** * @@ -332,18 +331,28 @@ const Transcript = ({ playerID, manifestUrl, showNotes = false, search = {}, tra _setCanvasIndex(c); // force re-render }; - const searchResults = useFilteredTranscripts({ - ...searchOpts, - query: searchQuery, - transcripts: transcript, - canvasIndex: canvasIndexRef.current, - selectedTranscript: selectedTranscript, + const searchResults = + useFilteredTranscripts({ + ...searchOpts, + query: searchQuery, + transcripts: transcript, + canvasIndex: canvasIndexRef.current, + selectedTranscript: selectedTranscript, + }); + + const { + focusedMatchId, + setFocusedMatchId, + focusedMatchIndex, + setFocusedMatchIndex + } = useFocusedMatch({ searchResults }); + + const tanscriptHitCounts = useSearchCounts({ + searchResults, + canvasTranscripts, + searchQuery }); - const { focusedMatchId, setFocusedMatchId, focusedMatchIndex, setFocusedMatchIndex } = useFocusedMatch({ searchResults }); - - const tanscriptHitCounts = useSearchCounts({ searchResults, canvasTranscripts, searchQuery }); - const [isEmpty, setIsEmpty] = React.useState(true); const [_autoScrollEnabled, _setAutoScrollEnabled] = React.useState(true); const autoScrollEnabledRef = React.useRef(_autoScrollEnabled); @@ -579,7 +588,6 @@ const Transcript = ({ playerID, manifestUrl, showNotes = false, search = {}, tra ref={transcriptContainerRef} > + debounce((event) => { + setSearchQuery(event.target.value); + }, 100), + [] + ); + const searchQueryEmpty = searchQuery === null || searchQuery.replace(/\s/g, '') === ''; let resultNavigation = null; @@ -89,7 +98,7 @@ export const TranscriptSearch = ({ if (event.target.value.trim() == '') { setSearchQuery(null); } else { - setSearchQuery(event.target.value); + handleOnChange(event); } }} /> diff --git a/src/components/Transcript/TranscriptMenu/TranscriptSearch.test.js b/src/components/Transcript/TranscriptMenu/TranscriptSearch.test.js index 7414e0ec..52d2dc75 100644 --- a/src/components/Transcript/TranscriptMenu/TranscriptSearch.test.js +++ b/src/components/Transcript/TranscriptMenu/TranscriptSearch.test.js @@ -1,263 +1,278 @@ import React from 'react'; -import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import TranscriptSearch from './TranscriptSearch'; - +import * as sinon from 'sinon'; describe('TranscriptSearch component', () => { - const getBaseProps = () => ({ - searchResults: { - results: {}, - id: [], - matchingIds: [] + const getBaseProps = () => ({ + searchResults: { + results: {}, + id: [], + matchingIds: [] + }, + searchQuery: null, + focusedMatchIndex: null, + setFocusedMatchIndex: jest.fn(), + setSearchQuery: jest.fn() + }); + + describe('with searchResults', () => { + const getPropsWithResults = () => ({ + ...getBaseProps(), + searchResults: { + results: { + 0: { id: 0, text: 'The party has begun.' }, + 1: { id: 1, text: 'I believe that on the first night I went to Gatsby\'s house' }, + 2: { id: 2, text: 'I was one of the few guests who had actually been invited.' }, + 3: { id: 3, text: 'People were not invited-they went there. They got into automobiles which bore them out to Long Island,' }, + 4: { id: 4, text: 'and somehow they ended up at Gatsby\'s door.' }, + 5: { id: 5, text: 'Once there they were introduced by somebody who knew Gatsby,' }, + 6: { id: 6, text: 'and after that they conducted themselves according to the rules of behaviour associated with an amusement park.' }, + 7: { id: 7, text: 'Sometimes they came and went without having met Gatsby at all,' }, + 8: { id: 8, text: 'came for the party with a simplicity of heart that was its own ticket of admission.' } }, - searchQuery: null, - focusedMatchIndex: null, - setFocusedMatchIndex: jest.fn(), - setSearchQuery: jest.fn() + ids: [0, 1, 2, 3, 4, 5, 6, 7, 8], + matchingIds: [] + } }); - - describe('with searchResults', () => { - const getPropsWithResults = () => ({ - ...getBaseProps(), - searchResults: { - results: { - 0: { id: 0, text: 'The party has begun.' }, - 1: { id: 1, text: 'I believe that on the first night I went to Gatsby\'s house' }, - 2: { id: 2, text: 'I was one of the few guests who had actually been invited.' }, - 3: { id: 3, text: 'People were not invited-they went there. They got into automobiles which bore them out to Long Island,' }, - 4: { id: 4, text: 'and somehow they ended up at Gatsby\'s door.' }, - 5: { id: 5, text: 'Once there they were introduced by somebody who knew Gatsby,' }, - 6: { id: 6, text: 'and after that they conducted themselves according to the rules of behaviour associated with an amusement park.' }, - 7: { id: 7, text: 'Sometimes they came and went without having met Gatsby at all,' }, - 8: { id: 8, text: 'came for the party with a simplicity of heart that was its own ticket of admission.' } - }, - ids: [0, 1, 2, 3, 4, 5, 6, 7, 8], - matchingIds: [] + + describe('when there are matches for the search query', () => { + const getPropsWithMatchingResults = () => { + const base = getPropsWithResults(); + return { + ...base, + searchQuery: 'Gatsby', + searchResults: { + ...base.searchResults, + matchingIds: [1, 4, 5, 7], + results: { + ...base.searchResults.results, + 1: { + id: 1, + text: 'I believe that on the first night I went to Gatsby\'s house', + matches: [ + 'I believe that on the first night I went to ', + 'Gatsby', + '\'s house' + ] + }, + 4: { + id: 4, + text: 'and somehow they ended up at Gatsby\'s door.', + matches: [ + 'and somehow they ended up at ', + 'Gatsby', + '\'s door.' + ] + }, + 5: { + id: 5, + text: 'Once there they were introduced by somebody who knew Gatsby,', + matches: [ + 'Once there they were introduced by somebody who knew ', + 'Gatsby', + ',' + ] + }, + 7: { + id: 7, + text: 'Sometimes they came and went without having met Gatsby at all,', + matches: [ + 'Sometimes they came and went without having met ', + 'Gatsby', + ' at all,' + ] + } } + } + }; + }; + + test('result count reads "(focusedMatchIndex + 1) of (searchResults.matchingIds.length)"', async () => { + const props = getPropsWithMatchingResults(); + const { rerender } = render(); + const countEl = screen.getByTestId('transcript-search-count'); + expect(countEl).toHaveTextContent('1 of 4'); + rerender(); + expect(countEl).toHaveTextContent('4 of 4'); + }); + test('when focusedMatchIndex is 0, the prev button is disabled', async () => { + const props = getPropsWithMatchingResults(); + render(); + const prevButtonEl = screen.getByTestId('transcript-search-prev'); + expect(prevButtonEl).toBeDisabled(); + }); + test('when focusedMatchIndex is not 0, the prev button is disabled', async () => { + const props = getPropsWithMatchingResults(); + render(); + const prevButtonEl = screen.getByTestId('transcript-search-prev'); + expect(prevButtonEl).not.toBeDisabled(); + }); + test('when focusedMatchIndex is the last index, the next button is disabled', () => { + const props = getPropsWithMatchingResults(); + render(); + const nextButtonEl = screen.getByTestId('transcript-search-next'); + expect(nextButtonEl).toBeDisabled(); + }); + test('when focusedMatchIndex is not the last index, the next button is not disabled', () => { + const props = getPropsWithMatchingResults(); + render(); + const nextButtonEl = screen.getByTestId('transcript-search-next'); + expect(nextButtonEl).not.toBeDisabled(); + }); + describe('interacting with the prev and next buttons', () => { + test('clicking the prev button decrements focusedMatchIndex', () => { + const props = getPropsWithMatchingResults(); + render(); + const prevButtonEl = screen.getByTestId('transcript-search-prev'); + expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); + fireEvent.click(prevButtonEl); + expect(props.setFocusedMatchIndex).toHaveBeenCalledWith(1); }); + test('clicking the next button increments focusedMatchIndex', () => { + const props = getPropsWithMatchingResults(); + render(); + const nextButtonEl = screen.getByTestId('transcript-search-next'); + expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); + fireEvent.click(nextButtonEl); + expect(props.setFocusedMatchIndex).toHaveBeenCalledWith(3); + }); + test('clicking a disabled button does nothing', () => { + const props = getPropsWithMatchingResults(); + const { rerender } = render(); + const prevButtonEl = screen.getByTestId('transcript-search-prev'); + fireEvent.click(prevButtonEl); + rerender(); + const nextButtonEl = screen.getByTestId('transcript-search-next'); + fireEvent.click(nextButtonEl); + expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); + }); + }); + }); - describe('when there are matches for the search query', () => { - const getPropsWithMatchingResults = () => { - const base = getPropsWithResults(); - return { - ...base, - searchQuery: 'Gatsby', - searchResults: { - ...base.searchResults, - matchingIds: [1, 4, 5, 7], - results: { - ...base.searchResults.results, - 1: { - id: 1, - text: 'I believe that on the first night I went to Gatsby\'s house', - matches: [ - 'I believe that on the first night I went to ', - 'Gatsby', - '\'s house' - ] - }, - 4: { - id: 4, - text: 'and somehow they ended up at Gatsby\'s door.', - matches: [ - 'and somehow they ended up at ', - 'Gatsby', - '\'s door.' - ] - }, - 5: { - id: 5, - text: 'Once there they were introduced by somebody who knew Gatsby,', - matches: [ - 'Once there they were introduced by somebody who knew ', - 'Gatsby', - ',' - ] - }, - 7: { - id: 7, - text: 'Sometimes they came and went without having met Gatsby at all,', - matches: [ - 'Sometimes they came and went without having met ', - 'Gatsby', - ' at all,' - ] - } - } - } - }; - }; + describe('when there are no matches for the search query', () => { + test('a no results found message is displayed', async () => { + const props = getPropsWithResults(); + render(); + const countEl = screen.getByTestId('transcript-search-count'); + expect(countEl).toHaveTextContent('no results found in this transcript'); + }); - test('result count reads "(focusedMatchIndex + 1) of (searchResults.matchingIds.length)"', async () => { - const props = getPropsWithMatchingResults(); - const { rerender } = render(); - const countEl = screen.getByTestId('transcript-search-count'); - expect(countEl).toHaveTextContent('1 of 4'); - rerender(); - expect(countEl).toHaveTextContent('4 of 4'); - }); - test('when focusedMatchIndex is 0, the prev button is disabled', async () => { - const props = getPropsWithMatchingResults(); - render(); - const prevButtonEl = screen.getByTestId('transcript-search-prev'); - expect(prevButtonEl).toBeDisabled(); - }); - test('when focusedMatchIndex is not 0, the prev button is disabled', async () => { - const props = getPropsWithMatchingResults(); - render(); - const prevButtonEl = screen.getByTestId('transcript-search-prev'); - expect(prevButtonEl).not.toBeDisabled(); - }); - test('when focusedMatchIndex is the last index, the next button is disabled', () => { - const props = getPropsWithMatchingResults(); - render(); - const nextButtonEl = screen.getByTestId('transcript-search-next'); - expect(nextButtonEl).toBeDisabled(); - }); - test('when focusedMatchIndex is not the last index, the next button is not disabled', () => { - const props = getPropsWithMatchingResults(); - render(); - const nextButtonEl = screen.getByTestId('transcript-search-next'); - expect(nextButtonEl).not.toBeDisabled(); - }); - describe('interacting with the prev and next buttons', () => { - test('clicking the prev button decrements focusedMatchIndex', () => { - const props = getPropsWithMatchingResults(); - render(); - const prevButtonEl = screen.getByTestId('transcript-search-prev'); - expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); - fireEvent.click(prevButtonEl); - expect(props.setFocusedMatchIndex).toHaveBeenCalledWith(1); - }); - test('clicking the next button increments focusedMatchIndex', () => { - const props = getPropsWithMatchingResults(); - render(); - const nextButtonEl = screen.getByTestId('transcript-search-next'); - expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); - fireEvent.click(nextButtonEl); - expect(props.setFocusedMatchIndex).toHaveBeenCalledWith(3); - }); - test('clicking a disabled button does nothing', () =>{ - const props = getPropsWithMatchingResults(); - const { rerender } = render(); - const prevButtonEl = screen.getByTestId('transcript-search-prev'); - fireEvent.click(prevButtonEl); - rerender(); - const nextButtonEl = screen.getByTestId('transcript-search-next'); - fireEvent.click(nextButtonEl); - expect(props.setFocusedMatchIndex).not.toHaveBeenCalled(); - }); - }); - }); + test('prev and next buttons are not shown', async () => { + const props = getPropsWithResults(); + render(); + const prevButtonEl = screen.queryByTestId('transcript-search-prev'); + const nextButtonEl = screen.queryByTestId('transcript-search-next'); + expect(prevButtonEl).toBeNull(); + expect(nextButtonEl).toBeNull(); + }); + }); + }); - describe('when there are no matches for the search query', () => { - test('a no results found message is displayed', async () => { - const props = getPropsWithResults(); - render(); - const countEl = screen.getByTestId('transcript-search-count'); - expect(countEl).toHaveTextContent('no results found in this transcript'); - }); + describe('modifying the search query', () => { + let clock; + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); - test('prev and next buttons are not shown', async () => { - const props = getPropsWithResults(); - render(); - const prevButtonEl = screen.queryByTestId('transcript-search-prev'); - const nextButtonEl = screen.queryByTestId('transcript-search-next'); - expect(prevButtonEl).toBeNull(); - expect(nextButtonEl).toBeNull(); - }); - }); + afterEach(() => { + clock.restore(); }); + test('setSearchQuery is called when value of search input changes', () => { + const props = getBaseProps(); + render(); + expect(props.setSearchQuery).not.toHaveBeenCalled(); + const searchInputEl = screen.getByTestId('transcript-search-input'); + expect(searchInputEl.value).toBe(''); + fireEvent.change(searchInputEl, { target: { value: 'test' } }); - describe('modifying the search query', () => { - test('setSearchQuery is called when value of search input changes', () => { - const props = getBaseProps(); - render(); - expect(props.setSearchQuery).not.toHaveBeenCalled(); - const searchInputEl = screen.getByTestId('transcript-search-input'); - expect(searchInputEl.value).toBe(''); - fireEvent.change(searchInputEl, { target: { value: 'test' } }); - expect(searchInputEl.value).toBe('test'); - expect(props.setSearchQuery).toHaveBeenCalled(); - }); - test('search query is set to null when input contains nothing but whitespace', () => { - const props = getBaseProps(); - render(); - const searchInputEl = screen.getByTestId('transcript-search-input'); - expect(searchInputEl.value).toBe('test'); - fireEvent.change(searchInputEl, { target: { value: ' ' } }); - expect(searchInputEl.value).toBe(' '); - expect(props.setSearchQuery).toHaveBeenCalledWith(null); - }); - test('result count display is shown when search query is not empty', async () => { - const props = getBaseProps(); - render(); - const countEl = screen.queryByTestId('transcript-search-count'); - expect(countEl).not.toBeNull(); - }); - describe('clear button', () => { - test('clear button becomes visible when there is a searchQuery', async () => { - const props = getBaseProps(); - const { rerender } = render(); - - const searchInputEl = screen.getByTestId('transcript-search-input'); - let clearButtonEl = screen.queryByTestId('transcript-search-clear'); - expect(clearButtonEl).toBeNull(); - - fireEvent.change(searchInputEl, { target: { value: 'test' } }); - - expect(props.setSearchQuery).toHaveBeenCalledWith('test'); - rerender(); - - clearButtonEl = screen.queryByTestId('transcript-search-clear'); - expect(clearButtonEl).not.toBeNull(); - expect(searchInputEl.value).toBe('test'); - }); - test('clicking clear button causes value of input to be cleared and clear button to disappear', () => { - const props = getBaseProps(); - const { rerender } = render(); - const searchInputEl = screen.getByTestId('transcript-search-input'); - expect(searchInputEl.value).toBe('test'); - let clearButtonEl = screen.queryByTestId('transcript-search-clear'); - expect(clearButtonEl).not.toBeNull(); - - fireEvent.click(clearButtonEl); - - expect(props.setSearchQuery).toHaveBeenCalledWith(null); - rerender(); + // Wait 100ms + clock.tick(100); - expect(searchInputEl.value).toBe(''); - clearButtonEl = screen.queryByTestId('transcript-search-clear'); - expect(clearButtonEl).toBeNull(); - }); - }); + expect(searchInputEl.value).toBe('test'); + expect(props.setSearchQuery).toHaveBeenCalled(); }); - describe('with empty search query and no searchResults', () => { - beforeEach(() => { - const props = getBaseProps(); - render(); - }); - test('search input is empty', () => { - const searchInputEl = screen.getByTestId('transcript-search-input'); - expect(searchInputEl.value).toBe(''); - }); - test('the result count display, clear, prev, and next buttons are not shown', () => { - const props = getBaseProps(); - render(); - const countEl = screen.queryByTestId('transcript-search-count'); - const clearButtonEl = screen.queryByTestId('transcript-search-clear'); - const nextButtonEl = screen.queryByTestId('transcript-search-next'); - const prevButtonEl = screen.queryByTestId('transcript-search-prev'); - expect(countEl).toBeNull(); - expect(clearButtonEl).toBeNull(); - expect(prevButtonEl).toBeNull(); - expect(nextButtonEl).toBeNull(); - }); + test('search query is set to null when input contains nothing but whitespace', () => { + const props = getBaseProps(); + render(); + const searchInputEl = screen.getByTestId('transcript-search-input'); + expect(searchInputEl.value).toBe('test'); + fireEvent.change(searchInputEl, { target: { value: ' ' } }); + expect(searchInputEl.value).toBe(' '); + expect(props.setSearchQuery).toHaveBeenCalledWith(null); + }); + test('result count display is shown when search query is not empty', async () => { + const props = getBaseProps(); + render(); + const countEl = screen.queryByTestId('transcript-search-count'); + expect(countEl).not.toBeNull(); }); - test('when searchQuery is passed during components initial render, the search input is pre-populated', () => { + describe('clear button', () => { + test('clear button becomes visible when there is a searchQuery', async () => { + const props = getBaseProps(); + const { rerender } = render(); + + const searchInputEl = screen.getByTestId('transcript-search-input'); + let clearButtonEl = screen.queryByTestId('transcript-search-clear'); + expect(clearButtonEl).toBeNull(); + + fireEvent.change(searchInputEl, { target: { value: 'test' } }); + + // Wait 100ms + clock.tick(100); + + expect(props.setSearchQuery).toHaveBeenCalledWith('test'); + rerender(); + + clearButtonEl = screen.queryByTestId('transcript-search-clear'); + expect(clearButtonEl).not.toBeNull(); + expect(searchInputEl.value).toBe('test'); + }); + test('clicking clear button causes value of input to be cleared and clear button to disappear', () => { const props = getBaseProps(); const { rerender } = render(); const searchInputEl = screen.getByTestId('transcript-search-input'); expect(searchInputEl.value).toBe('test'); + let clearButtonEl = screen.queryByTestId('transcript-search-clear'); + expect(clearButtonEl).not.toBeNull(); + + fireEvent.click(clearButtonEl); + + expect(props.setSearchQuery).toHaveBeenCalledWith(null); + rerender(); + + expect(searchInputEl.value).toBe(''); + clearButtonEl = screen.queryByTestId('transcript-search-clear'); + expect(clearButtonEl).toBeNull(); + }); + }); + }); + describe('with empty search query and no searchResults', () => { + beforeEach(() => { + const props = getBaseProps(); + render(); + }); + test('search input is empty', () => { + const searchInputEl = screen.getByTestId('transcript-search-input'); + expect(searchInputEl.value).toBe(''); + }); + test('the result count display, clear, prev, and next buttons are not shown', () => { + const props = getBaseProps(); + render(); + const countEl = screen.queryByTestId('transcript-search-count'); + const clearButtonEl = screen.queryByTestId('transcript-search-clear'); + const nextButtonEl = screen.queryByTestId('transcript-search-next'); + const prevButtonEl = screen.queryByTestId('transcript-search-prev'); + expect(countEl).toBeNull(); + expect(clearButtonEl).toBeNull(); + expect(prevButtonEl).toBeNull(); + expect(nextButtonEl).toBeNull(); }); -}) \ No newline at end of file + }); + test('when searchQuery is passed during components initial render, the search input is pre-populated', () => { + const props = getBaseProps(); + const { rerender } = render(); + const searchInputEl = screen.getByTestId('transcript-search-input'); + expect(searchInputEl.value).toBe('test'); + }); +}); diff --git a/src/services/search.js b/src/services/search.js index a53f5014..45718770 100644 --- a/src/services/search.js +++ b/src/services/search.js @@ -37,8 +37,21 @@ export const defaultMatcherFactory = (items) => { export const contentSearchFactory = (searchService, items, selectedTranscript) => { return async (query, abortController) => { try { + /** + * Prevent caching the response as this slows down the search within function by + * giving the ability to race the cache with the network when the cache is slow. + * pragma: HTTP/1.0 implementation for older clients + * cache-control: HTTP/1.1 implementation + */ + var fetchHeaders = new Headers(); + fetchHeaders.append('pragma', 'no-cache'); + fetchHeaders.append('cache-control', 'no-cache'); + const res = await fetch(`${searchService}?q=${query}`, - { signal: abortController.signal } + { + signal: abortController.signal, + headers: fetchHeaders + } ); const json = await res.json(); if (json.items?.length > 0) { @@ -138,7 +151,7 @@ export function useFilteredTranscripts({ // Update searchResult instead of replacing to preserve the hit count setSearchResults({ ...searchResults, - results: {}, matchingIds: [], ids: [], sortedMatchCounts: [] + results: {}, matchingIds: [], ids: [] }); return; } else if (!enabled || !query) { @@ -148,7 +161,6 @@ export function useFilteredTranscripts({ ...searchResults, results: itemsIndexed, matchingIds: [], - sortedMatchCounts: [], ids: sortedIds }); // When query is cleared; clear cached search results @@ -240,14 +252,11 @@ export function useFilteredTranscripts({ } }); - const sortedMatchCounts = sortedMatchedLines.map((t) => ({ id: t.id, matchCount: t.matchCount })); - if (matchesOnly) { setSearchResults({ ...searchResults, results: matchingItemsIndexed, ids: sortedMatchIds, - sortedMatchCounts: sortedMatchCounts, matchingIds: sortedMatchIds, }); } else { @@ -261,7 +270,6 @@ export function useFilteredTranscripts({ ...searchResults, results: joinedIndexed, ids: sortedItemIds, - sortedMatchCounts: sortedMatchCounts, matchingIds: sortedMatchIds, }; setSearchResults(searchResults); diff --git a/src/services/search.test.js b/src/services/search.test.js index 3fc37822..2bdf5f56 100644 --- a/src/services/search.test.js +++ b/src/services/search.test.js @@ -23,7 +23,6 @@ const fixture = { ids: [0, 1, 2, 3, 4, 5, 6, 7, 8], matchingIds: [], counts: [], - sortedMatchCounts: [] }; const transcriptListFixture = [ { @@ -116,7 +115,6 @@ const untimedFixture = { ids: [0, 1, 2, 3, 4, 5, 6, 7, 8], matchingIds: [], counts: [], - sortedMatchCounts: [] }; describe('useFilteredTranscripts', () => { diff --git a/yarn.lock b/yarn.lock index 0639a7d3..e052bdad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1542,6 +1542,27 @@ dependencies: type-detect "4.0.8" +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" @@ -1549,6 +1570,20 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/samsam@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" + integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== + dependencies: + "@sinonjs/commons" "^2.0.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" + integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== + "@testing-library/dom@^7.28.1": version "7.31.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" @@ -4040,6 +4075,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + dingbat-to-unicode@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz#5091dd673241453e6b5865e26e5a4452cdef5c83" @@ -7108,6 +7148,11 @@ jszip@^3.7.1: readable-stream "~2.3.6" setimmediate "^1.0.5" +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + keycode@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" @@ -7250,6 +7295,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7852,6 +7902,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.0.0.tgz#ae56fccb5d912037363c3b3f29ebbfa28bde8b48" + integrity sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/text-encoding" "^0.7.2" + just-extend "^6.2.0" + path-to-regexp "^6.2.1" + no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -8418,6 +8479,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" + integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -10122,6 +10188,18 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sinon@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.0.tgz#69ca293dbc3e82590a8b0d46c97f63ebc1e5fc01" + integrity sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.2.0" + nise "^6.0.0" + supports-color "^7" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -10592,7 +10670,7 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7, supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==