diff --git a/__tests__/feature/searchAndOpenTemplate.test.js b/__tests__/feature/searchAndOpenTemplate.test.js index 51e7b3d81..1b39c555c 100644 --- a/__tests__/feature/searchAndOpenTemplate.test.js +++ b/__tests__/feature/searchAndOpenTemplate.test.js @@ -44,9 +44,11 @@ describe('searching and opening a resource', () => { it('adds the template to recently used template history', async () => { renderApp(store, history) + const queryString = 'title' + // Search for a template const input = screen.getByPlaceholderText('Enter id, label, URI, remark, or author') - await fireEvent.change(input, { target: { value: 'title' } }) + await fireEvent.change(input, { target: { value: queryString } }) await screen.findByText('resourceTemplate:bf2:Title:Note') // open the template @@ -54,10 +56,17 @@ describe('searching and opening a resource', () => { fireEvent.click(link) await act(() => promise) - // return the the RT list + // return to the RT list const rtLink = await screen.findByText('Resource Templates', { selector: 'a' }) fireEvent.click(rtLink) + // confirm RT query is still in place (stored in state and not cleared) + expect(input.value).toEqual(queryString) + + // Clear search button empties the search field + fireEvent.click(screen.getByTestId('Clear query string', { selector: 'button' })) + expect(screen.getByPlaceholderText('Enter id, label, URI, remark, or author').value).toEqual('') + // see the recently used RTs const histTemplateBtn = await screen.findByText('Most recently used templates') fireEvent.click(histTemplateBtn) diff --git a/__tests__/feature/searchAndViewResource.test.js b/__tests__/feature/searchAndViewResource.test.js index 3db5b5db5..963419d76 100644 --- a/__tests__/feature/searchAndViewResource.test.js +++ b/__tests__/feature/searchAndViewResource.test.js @@ -14,11 +14,11 @@ describe('searching and viewing a resource', () => { error: undefined, }) - it('renders a modal without edit controls', async () => { - // Setup search component to return known resource - const uri = 'http://localhost:3000/resource/c7db5404-7d7d-40ac-b38e-c821d2c3ae3f' - sinopiaSearch.getSearchResultsWithFacets.mockResolvedValue(resourceSearchResults(uri)) + // Setup search component to return known resource + const uri = 'http://localhost:3000/resource/c7db5404-7d7d-40ac-b38e-c821d2c3ae3f' + sinopiaSearch.getSearchResultsWithFacets.mockResolvedValue(resourceSearchResults(uri)) + it('renders a modal without edit controls', async () => { renderApp() fireEvent.click(screen.getByText('Linked Data Editor', { selector: 'a' })) @@ -68,5 +68,15 @@ describe('searching and viewing a resource', () => { fireEvent.click(screen.getByLabelText('Edit', { selector: 'button', exact: true })) expect(screen.getByText('Uber template1', { selector: 'h3' })).toBeInTheDocument() expect(screen.getByText('Copy URI', { selector: 'button' })).toBeInTheDocument() + + // Switch back to search page + fireEvent.click(screen.getByText('Search', { selector: 'a' })) + + // Confirm search query is still in place (stored in state and not cleared) + expect(await screen.getByLabelText('Query').value).toEqual(uri) + + // Clear search button empties the search field + fireEvent.click(screen.getByTestId('Clear query string', { selector: 'button' })) + expect(await screen.getByLabelText('Query').value).toEqual('') }) }) diff --git a/cypress/integration/end2end.spec.js b/cypress/integration/end2end.spec.js index 2b038b8f5..267467abf 100644 --- a/cypress/integration/end2end.spec.js +++ b/cypress/integration/end2end.spec.js @@ -69,7 +69,6 @@ describe('End-to-end test', () => { cy.url().should('include', '/templates') cy.get('#searchInput') - .type('resourceTemplate:bf2:WorkTitle') .should('have.value', 'resourceTemplate:bf2:WorkTitle') // eslint-disable-next-line cypress/no-unnecessary-waiting @@ -117,6 +116,9 @@ describe('End-to-end test', () => { cy.get('a').contains('Search').click() cy.url().should('include', '/search') + // Test search clear button + cy.get('button[title="Clear query string"]').click() + // Indexing latency is possible problem here. // Force is necessary because reflow of search inputs is suboptimal. cy.get('input#searchInput') diff --git a/cypress/integration/leftNav.spec.js b/cypress/integration/leftNav.spec.js index 013398be2..ffcf22f39 100644 --- a/cypress/integration/leftNav.spec.js +++ b/cypress/integration/leftNav.spec.js @@ -52,7 +52,6 @@ describe('Left-nav test', () => { cy.url().should('include', '/templates') cy.get('#searchInput') - .type('resourceTemplate:testing:uber1') .should('have.value', 'resourceTemplate:testing:uber1') // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/src/components/search/Search.jsx b/src/components/search/Search.jsx index 136d78544..a519e2861 100644 --- a/src/components/search/Search.jsx +++ b/src/components/search/Search.jsx @@ -10,45 +10,57 @@ import { fetchSinopiaSearchResults as fetchSinopiaSearchResultsCreator, fetchQASearchResults as fetchQASearchResultsCreator, } from 'actionCreators/search' -import { clearSearchResults as clearSearchResultsAction } from 'actions/search' +import { clearSearchResults as clearSearchResultsAction, setSearchResults } from 'actions/search' import SinopiaSearchResults from './SinopiaSearchResults' import QASearchResults from './QASearchResults' import SearchResultsPaging from './SearchResultsPaging' import SearchResultsMessage from './SearchResultsMessage' import Alert from '../Alert' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faSearch } from '@fortawesome/free-solid-svg-icons' +import { faTrashAlt, faSearch } from '@fortawesome/free-solid-svg-icons' import searchConfig from '../../../static/searchConfig.json' -import { selectSearchError, selectSearchUri, selectSearchOptions } from 'selectors/search' +import { + selectSearchError, selectSearchQuery, selectSearchUri, selectSearchOptions, +} from 'selectors/search' const Search = (props) => { const dispatch = useDispatch() - const fetchQASearchResults = (queryString, uri, startOfRange) => dispatch(fetchQASearchResultsCreator(queryString, uri, { ...searchOptions, startOfRange })) const searchOptions = useSelector((state) => selectSearchOptions(state, 'resource')) + const error = useSelector((state) => selectSearchError(state, 'resource')) + const searchUri = useSelector((state) => selectSearchUri(state, 'resource')) + const lastQueryString = useSelector((state) => selectSearchQuery(state, 'resource')) - const fetchSinopiaSearchResults = (queryString, startOfRange) => dispatch(fetchSinopiaSearchResultsCreator( - queryString, { ...searchOptions, startOfRange }, - )) + const clearSearchResults = useCallback(() => dispatch(clearSearchResultsAction('resource')), [dispatch]) - const fetchNewSinopiaSearchResults = (queryString) => dispatch(fetchSinopiaSearchResultsCreator( - queryString, - )) + const topRef = useRef(null) + const defaultUri = 'sinopia' - const clearSearchResults = useCallback(() => dispatch(clearSearchResultsAction('resource')), [dispatch]) + const [queryString, setQueryString] = useState(lastQueryString || '') + const [uri, setUri] = useState(searchUri || defaultUri) - const error = useSelector((state) => selectSearchError(state, 'resource')) - const searchUri = useSelector((state) => selectSearchUri(state, 'resource')) + useEffect(() => { + if (!queryString) clearSearchResults() + }, [clearSearchResults, queryString]) - const topRef = useRef(null) + const fetchQASearchResults = (queryString, uri, startOfRange) => { + dispatch(fetchQASearchResultsCreator(queryString, uri, { ...searchOptions, startOfRange })).then((response) => { + if (response) dispatch(setSearchResults('resource', uri, response.results, response.totalHits, {}, queryString, { startOfRange }, response.error)) + }) + } - const [queryString, setQueryString] = useState('') - const [uri, setUri] = useState('sinopia') + const fetchSinopiaSearchResults = (queryString, startOfRange) => { + dispatch(fetchSinopiaSearchResultsCreator(queryString, { ...searchOptions, startOfRange })).then((response) => { + if (response) dispatch(setSearchResults('resource', null, response.results, response.totalHits, {}, queryString, { startOfRange }, response.error)) + }) + } - useEffect(() => { - clearSearchResults() - }, [clearSearchResults]) + const fetchNewSinopiaSearchResults = (queryString) => { + dispatch(fetchSinopiaSearchResultsCreator(queryString)).then((response) => { + if (response) dispatch(setSearchResults('resource', null, response.results, response.totalHits, {}, queryString, {}, response.error)) + }) + } const handleKeyPress = (event) => { if (event.key === 'Enter') { @@ -66,7 +78,7 @@ const Search = (props) => { if (queryString === '') { return } - if (uri === 'sinopia') { + if (uri === defaultUri) { fetchNewSinopiaSearchResults(queryString) } else { fetchQASearchResults(queryString, uri, 0) @@ -85,7 +97,7 @@ const Search = (props) => { const options = searchConfig.map((config) => ()) let results - if (searchUri === 'sinopia') { + if (searchUri === defaultUri) { results = (