Set initial value from queryParameters

Add tests for external queryParameter updates

Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
Tim Hansen
2022-02-04 12:47:18 -07:00
parent 680e7c7452
commit 911f01ace3
11 changed files with 245 additions and 43 deletions
@@ -161,4 +161,34 @@ describe('<EntityLifecyclePicker/>', () => {
lifecycles: undefined,
});
});
it('responds to external queryParameters changes', () => {
const updateFilters = jest.fn();
const rendered = render(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { lifecycles: ['experimental'] },
}}
>
<EntityLifecyclePicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
lifecycles: new EntityLifecycleFilter(['experimental']),
});
rendered.rerender(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { lifecycles: ['production'] },
}}
>
<EntityLifecyclePicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
lifecycles: new EntityLifecycleFilter(['production']),
});
});
});
@@ -51,18 +51,24 @@ export const EntityLifecyclePicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const queryParamLifecycles = useMemo(
() => [queryParameters.lifecycles].flat().filter(Boolean) as string[],
[queryParameters],
);
const [selectedLifecycles, setSelectedLifecycles] = useState(
filters.lifecycles?.values ?? [],
queryParamLifecycles.length
? queryParamLifecycles
: filters.lifecycles?.values ?? [],
);
// Set selected lifecycles on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamLifecycles = [queryParameters.lifecycles]
.flat()
.filter(Boolean) as string[];
setSelectedLifecycles(queryParamLifecycles);
}, [queryParameters]);
if (queryParamLifecycles.length) {
setSelectedLifecycles(queryParamLifecycles);
}
}, [queryParamLifecycles]);
useEffect(() => {
updateFilters({
@@ -191,4 +191,34 @@ describe('<EntityOwnerPicker/>', () => {
owner: undefined,
});
});
it('responds to external queryParameters changes', () => {
const updateFilters = jest.fn();
const rendered = render(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-a'] },
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: new EntityOwnerFilter(['team-a']),
});
rendered.rerender(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-b'] },
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: new EntityOwnerFilter(['team-b']),
});
});
});
@@ -53,18 +53,22 @@ export const EntityOwnerPicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const queryParamOwners = useMemo(
() => [queryParameters.owners].flat().filter(Boolean) as string[],
[queryParameters],
);
const [selectedOwners, setSelectedOwners] = useState(
filters.owners?.values ?? [],
queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [],
);
// Set selected owners on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamOwners = [queryParameters.owners]
.flat()
.filter(Boolean) as string[];
setSelectedOwners(queryParamOwners);
}, [queryParameters]);
if (queryParamOwners.length) {
setSelectedOwners(queryParamOwners);
}
}, [queryParamOwners]);
useEffect(() => {
updateFilters({
@@ -149,4 +149,34 @@ describe('<EntityTagPicker/>', () => {
tags: undefined,
});
});
it('responds to external queryParameters changes', () => {
const updateFilters = jest.fn();
const rendered = render(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { tags: ['tag1'] },
}}
>
<EntityTagPicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
tags: new EntityTagFilter(['tag1']),
});
rendered.rerender(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { tags: ['tag2'] },
}}
>
<EntityTagPicker />
</MockEntityListContextProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
tags: new EntityTagFilter(['tag2']),
});
});
});
@@ -51,16 +51,22 @@ export const EntityTagPicker = () => {
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const [selectedTags, setSelectedTags] = useState(filters.tags?.values ?? []);
const queryParamTags = useMemo(
() => [queryParameters.tags].flat().filter(Boolean) as string[],
[queryParameters],
);
const [selectedTags, setSelectedTags] = useState(
queryParamTags.length ? queryParamTags : filters.tags?.values ?? [],
);
// Set selected tags on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamTags = [queryParameters.tags]
.flat()
.filter(Boolean) as string[];
setSelectedTags(queryParamTags);
}, [queryParameters]);
if (queryParamTags.length) {
setSelectedTags(queryParamTags);
}
}, [queryParamTags]);
useEffect(() => {
updateFilters({
@@ -151,4 +151,38 @@ describe('<EntityTypePicker/>', () => {
type: new EntityTypeFilter(['tool']),
});
});
it('responds to external queryParameters changes', async () => {
const updateFilters = jest.fn();
const rendered = await renderWithEffects(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { type: 'service' },
}}
>
<EntityTypePicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
type: new EntityTypeFilter(['service']),
});
rendered.rerender(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { type: 'tool' },
}}
>
<EntityTypePicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
type: new EntityTypeFilter(['tool']),
});
});
});
@@ -253,6 +253,42 @@ describe('<UserListPicker />', () => {
});
});
it('responds to external queryParameters changes', () => {
const updateFilters = jest.fn();
const rendered = render(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{
backendEntities,
updateFilters,
queryParameters: { user: ['all'] },
}}
>
<UserListPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
user: new UserListFilter('all', mockIsOwnedEntity, mockIsStarredEntity),
});
rendered.rerender(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{
backendEntities,
updateFilters,
queryParameters: { user: ['owned'] },
}}
>
<UserListPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
user: new UserListFilter('owned', mockIsOwnedEntity, mockIsStarredEntity),
});
});
describe.each`
type | filterFn
${'owned'} | ${mockIsOwnedEntity}
@@ -161,7 +161,14 @@ export const UserListPicker = ({
[isOwnedEntity, isStarredEntity],
);
const [selectedUserFilter, setSelectedUserFilter] = useState(initialFilter);
const queryParamUserFilter = useMemo(
() => [queryParameters.user].flat()[0],
[queryParameters],
);
const [selectedUserFilter, setSelectedUserFilter] = useState(
queryParamUserFilter ?? initialFilter,
);
// To show proper counts for each section, apply all other frontend filters _except_ the user
// filter that's controlled by this picker.
@@ -191,9 +198,10 @@ export const UserListPicker = ({
// Set selected user filter on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamUserFilter = [queryParameters.user].flat()[0];
setSelectedUserFilter(queryParamUserFilter as UserListFilterKind);
}, [queryParameters]);
if (queryParamUserFilter) {
setSelectedUserFilter(queryParamUserFilter as UserListFilterKind);
}
}, [queryParamUserFilter]);
useEffect(() => {
if (
@@ -42,18 +42,22 @@ export function useEntityTypeFilter(): EntityTypeReturn {
updateFilters,
} = useEntityListProvider();
const queryParamTypes = useMemo(
() => [queryParameters.type].flat().filter(Boolean) as string[],
[queryParameters],
);
const [selectedTypes, setSelectedTypes] = useState(
typeFilter?.getTypes() ?? [],
queryParamTypes.length ? queryParamTypes : typeFilter?.getTypes() ?? [],
);
// Set selected types on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
const queryParamTypes = [queryParameters.type]
.flat()
.filter(Boolean) as string[];
setSelectedTypes(queryParamTypes);
}, [queryParameters]);
if (queryParamTypes.length) {
setSelectedTypes(queryParamTypes);
}
}, [queryParamTypes]);
const [availableTypes, setAvailableTypes] = useState<string[]>([]);
const kind = useMemo(() => kindFilter?.value, [kindFilter]);
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import React, { PropsWithChildren, useCallback, useState } from 'react';
import React, {
PropsWithChildren,
useCallback,
useMemo,
useState,
} from 'react';
import {
DefaultEntityFilters,
EntityListContext,
@@ -32,6 +37,7 @@ export const MockEntityListContextProvider = ({
const [filters, setFilters] = useState<DefaultEntityFilters>(
value?.filters ?? {},
);
const updateFilters = useCallback(
(
update:
@@ -49,23 +55,31 @@ export const MockEntityListContextProvider = ({
[],
);
const defaultContext: EntityListContextProps = {
entities: [],
backendEntities: [],
updateFilters,
filters,
loading: false,
queryParameters: {},
};
// Memoize the default values since pickers have useEffect triggers on these; naively defaulting
// below with `?? <X>` breaks referential equality on subsequent updates.
const defaultValues = useMemo(
() => ({
entities: [],
backendEntities: [],
queryParameters: {},
}),
[],
);
// Extract value.filters to avoid overwriting it; some tests exercise filter updates. The value
// provided is used as the initial seed in useState above.
const { filters: _, ...otherContextFields } = value ?? {};
const resolvedValue: EntityListContextProps = useMemo(
() => ({
entities: value?.entities ?? defaultValues.entities,
backendEntities: value?.backendEntities ?? defaultValues.backendEntities,
updateFilters: value?.updateFilters ?? updateFilters,
filters,
loading: value?.loading ?? false,
queryParameters: value?.queryParameters ?? defaultValues.queryParameters,
}),
[value, defaultValues, filters, updateFilters],
);
return (
<EntityListContext.Provider
value={{ ...defaultContext, ...otherContextFields }}
>
<EntityListContext.Provider value={resolvedValue}>
{children}
</EntityListContext.Provider>
);