Skip to content

Commit

Permalink
feat: reflect search in URL [#16287]
Browse files Browse the repository at this point in the history
  • Loading branch information
jh3y committed Oct 29, 2021
1 parent d8ba74c commit d2b4929
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 2 deletions.
6 changes: 5 additions & 1 deletion lib/ui/src/components/sidebar/Search.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ const baseProps = {
getLastViewed: () => [] as Selection[],
};

export const Simple = () => <Search {...baseProps}>{() => null}</Search>;
export const Simple = (testProps: any) => (
<Search {...baseProps} {...testProps}>
{() => null}
</Search>
);

export const FilledIn = () => (
<Search {...baseProps} initialQuery="Search query">
Expand Down
79 changes: 79 additions & 0 deletions lib/ui/src/components/sidebar/Search.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider, themes, convert } from '@storybook/theming';

import { FILTER_KEY } from './Search';
import { Simple as Search } from './Search.stories';

const TEST_URL = 'http://localhost';
const FILTER_VALUE = 'filter';
const PLACEHOLDER = 'Find components';
const DEFAULT_SEARCH = '?path=story';

beforeEach(() => {
const { history, location } = global.window;
delete global.window.location;
global.window.location = { ...location };
delete global.window.history;
global.window.history = { ...history };
global.window.history.replaceState = (state, title, url) => {
global.window.location.href = url;
global.window.location.search = url.indexOf('?') !== -1 ? url.slice(url.indexOf('?')) : '';
};
});

const setLocation = (url = TEST_URL, search = DEFAULT_SEARCH) => {
global.window.location.href = `${url}${search}`;
global.window.location.search = search;
};

const renderSearch = (props) =>
render(
<ThemeProvider theme={convert(themes.light)}>
<Search {...props} />
</ThemeProvider>
);

describe('Search - reflect search in URL', () => {
it('renders OK', async () => {
setLocation();
renderSearch(TEST_URL, DEFAULT_SEARCH);
const INPUT = await screen.getByPlaceholderText(PLACEHOLDER);
expect(INPUT.value).toBe('');
});
it('prefills input with search params', async () => {
setLocation(TEST_URL, `${DEFAULT_SEARCH}&${FILTER_KEY}=${FILTER_VALUE}`);
renderSearch();
const INPUT = await screen.getByPlaceholderText(PLACEHOLDER);
expect(INPUT.value).toBe(FILTER_VALUE);
});
it('updates location on input update with current query', async () => {
setLocation();
renderSearch();
const INPUT = await screen.getByPlaceholderText(PLACEHOLDER);
userEvent.clear(INPUT);
userEvent.type(INPUT, FILTER_VALUE);
expect(global.window.location.href).toBe(
`${TEST_URL}${DEFAULT_SEARCH}&${FILTER_KEY}=${FILTER_VALUE}`
);
expect(global.window.location.search).toBe(`${DEFAULT_SEARCH}&${FILTER_KEY}=${FILTER_VALUE}`);
});
it('updates location on input update without current query', async () => {
setLocation(TEST_URL, '');
renderSearch();
const INPUT = await screen.getByPlaceholderText(PLACEHOLDER);
userEvent.clear(INPUT);
userEvent.type(INPUT, FILTER_VALUE);
expect(global.window.location.href).toBe(`${TEST_URL}?${FILTER_KEY}=${FILTER_VALUE}`);
expect(global.window.location.search).toBe(`?${FILTER_KEY}=${FILTER_VALUE}`);
});
it('initialQuery updates URL', async () => {
setLocation(TEST_URL, '');
renderSearch({ initialQuery: FILTER_VALUE });
const INPUT = await screen.getByPlaceholderText(PLACEHOLDER);
expect(INPUT.value).toBe(FILTER_VALUE);
expect(global.window.location.href).toBe(`${TEST_URL}?${FILTER_KEY}=${FILTER_VALUE}`);
expect(global.window.location.search).toBe(`?${FILTER_KEY}=${FILTER_VALUE}`);
});
});
24 changes: 23 additions & 1 deletion lib/ui/src/components/sidebar/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { searchItem } from './utils';
const { document } = global;

const DEFAULT_MAX_SEARCH_RESULTS = 50;
export const FILTER_KEY = 'filter';

const options = {
shouldSort: true,
Expand Down Expand Up @@ -165,6 +166,13 @@ export const Search = React.memo<{
const [inputPlaceholder, setPlaceholder] = useState('Find components');
const [allComponents, showAllComponents] = useState(false);

// Grab initialQuery from URLSearchParams
const searchParams = new URLSearchParams(global.window.location.search);
const initialInputValue =
initialQuery === '' && searchParams.has(FILTER_KEY)
? searchParams.get(FILTER_KEY)
: initialQuery;

const selectStory = useCallback(
(id: string, refId: string) => {
if (api) api.selectStory(id, undefined, { ref: refId !== DEFAULT_REF_ID && refId });
Expand Down Expand Up @@ -284,7 +292,8 @@ export const Search = React.memo<{

return (
<Downshift<DownshiftItem>
initialInputValue={initialQuery}
initialInputValue={initialInputValue}
initialIsOpen={initialInputValue !== ''}
stateReducer={stateReducer}
// @ts-ignore
itemToString={(result) => result?.item?.name || ''}
Expand All @@ -305,6 +314,19 @@ export const Search = React.memo<{
const input = inputValue ? inputValue.trim() : '';
let results: DownshiftItem[] = input ? getResults(input) : [];

// Sync URLSearchParams with search filter
if (global.window.history.replaceState) {
if (input !== '') searchParams.set(FILTER_KEY, input);
else searchParams.delete(FILTER_KEY);
global.window.history.replaceState(
{},
'',
`${global.window.location.origin}${
searchParams.toString() !== '' ? '?' : ''
}${searchParams.toString()}`
);
}

const lastViewed = !input && getLastViewed();
if (lastViewed && lastViewed.length) {
results = lastViewed.reduce((acc, { storyId, refId }) => {
Expand Down

0 comments on commit d2b4929

Please sign in to comment.