Replace useQuery & getNewQueryParams with single hook useQueryHandler
Add tests for isProjectValid & useQueryHandler Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
@@ -32,18 +32,17 @@ import {
|
||||
import { CenteredCircularProgress } from './components/CenteredCircularProgress';
|
||||
import { CreateRc } from './cards/createRc/CreateRc';
|
||||
import { getGitHubBatchInfo } from './sideEffects/getGitHubBatchInfo';
|
||||
import { getParsedQuery } from './helpers/getNewQueryParams';
|
||||
import { githubReleaseManagerApiRef } from './api/serviceApiRef';
|
||||
import { Info } from './cards/info/Info';
|
||||
import { InfoCardPlus } from './components/InfoCardPlus';
|
||||
import { isProjectValid } from './cards/projectForm/isProjectValid';
|
||||
import { isProjectValid } from './helpers/isProjectValid';
|
||||
import { Patch } from './cards/patchRc/Patch';
|
||||
import { ProjectContext, Project } from './contexts/ProjectContext';
|
||||
import { PromoteRc } from './cards/promoteRc/PromoteRc';
|
||||
import { RefetchContext } from './contexts/RefetchContext';
|
||||
import { RepoDetailsForm } from './cards/projectForm/RepoDetailsForm';
|
||||
import { useQuery } from './helpers/useQuery';
|
||||
import { useVersioningStrategyMatchesRepoTags } from './helpers/useVersioningStrategyMatchesRepoTags';
|
||||
import { useQueryHandler } from './helpers/useQueryHandler';
|
||||
|
||||
interface GitHubReleaseManagerProps {
|
||||
components?: {
|
||||
@@ -66,13 +65,15 @@ export function GitHubReleaseManager({
|
||||
}: GitHubReleaseManagerProps) {
|
||||
const pluginApiClient = useApi(githubReleaseManagerApiRef);
|
||||
const classes = useStyles();
|
||||
const query = useQuery();
|
||||
const parsedQuery = getParsedQuery({ query });
|
||||
|
||||
const { getParsedQuery } = useQueryHandler();
|
||||
const { parsedQuery } = getParsedQuery();
|
||||
const project: Project = {
|
||||
owner: parsedQuery.owner ?? '',
|
||||
repo: parsedQuery.repo ?? '',
|
||||
versioningStrategy: parsedQuery.versioningStrategy ?? 'semver',
|
||||
};
|
||||
|
||||
const usernameResponse = useAsync(() =>
|
||||
pluginApiClient.getUsername({ owner: project.owner, repo: project.repo }),
|
||||
);
|
||||
@@ -157,19 +158,23 @@ function Cards({
|
||||
);
|
||||
}
|
||||
|
||||
if (!versioningStrategyMatches) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Versioning mismatch, expected {project.versioningStrategy} version, got{' '}
|
||||
{gitHubBatchInfo.value.latestRelease?.tagName}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={project}>
|
||||
<RefetchContext.Provider value={{ refetchTrigger, setRefetchTrigger }}>
|
||||
<ErrorBoundary>
|
||||
{gitHubBatchInfo.value.latestRelease && !versioningStrategyMatches && (
|
||||
<Alert severity="warning" style={{ marginBottom: 20 }}>
|
||||
Versioning mismatch, expected {project.versioningStrategy}{' '}
|
||||
version, got "{gitHubBatchInfo.value.latestRelease.tagName}"
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!gitHubBatchInfo.value.latestRelease && (
|
||||
<Alert severity="info" style={{ marginBottom: 20 }}>
|
||||
This repository has not releases yet
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Info
|
||||
latestRelease={gitHubBatchInfo.value.latestRelease}
|
||||
releaseBranch={gitHubBatchInfo.value.releaseBranch}
|
||||
|
||||
@@ -25,12 +25,11 @@ import {
|
||||
Select,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
|
||||
import { useFormClasses } from './styles';
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { getNewQueryParams } from '../../helpers/getNewQueryParams';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
import { useFormClasses } from './styles';
|
||||
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
|
||||
import { useQueryHandler } from '../../helpers/useQueryHandler';
|
||||
|
||||
export function Owner({
|
||||
username,
|
||||
@@ -39,10 +38,10 @@ export function Owner({
|
||||
username: string;
|
||||
project: Project;
|
||||
}) {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const formClasses = useFormClasses();
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const { getQueryParamsWithUpdates } = useQueryHandler();
|
||||
|
||||
const { loading, error, value } = useAsync(() => pluginApiClient.getOwners());
|
||||
const owners = value?.owners ?? [];
|
||||
@@ -63,8 +62,7 @@ export function Owner({
|
||||
value={project.owner}
|
||||
defaultValue=""
|
||||
onChange={event => {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
const { queryParams } = getQueryParamsWithUpdates({
|
||||
updates: [
|
||||
{ key: 'repo', value: '' },
|
||||
{ key: 'owner', value: event.target.value as string },
|
||||
|
||||
@@ -29,14 +29,13 @@ import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext
|
||||
import { useFormClasses } from './styles';
|
||||
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { getNewQueryParams } from '../../helpers/getNewQueryParams';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
import { useQueryHandler } from '../../helpers/useQueryHandler';
|
||||
|
||||
export function Repo({ project }: { project: Project }) {
|
||||
const pluginApiClient = usePluginApiClientContext();
|
||||
const navigate = useNavigate();
|
||||
const formClasses = useFormClasses();
|
||||
const query = useQuery();
|
||||
const { getQueryParamsWithUpdates } = useQueryHandler();
|
||||
|
||||
const { loading, error, value } = useAsync(
|
||||
async () => pluginApiClient.getRepositories({ owner: project.owner }),
|
||||
@@ -59,8 +58,7 @@ export function Repo({ project }: { project: Project }) {
|
||||
value={project.repo}
|
||||
defaultValue=""
|
||||
onChange={event => {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
const { queryParams } = getQueryParamsWithUpdates({
|
||||
updates: [{ key: 'repo', value: event.target.value as string }],
|
||||
});
|
||||
|
||||
|
||||
@@ -25,22 +25,17 @@ import {
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { useQuery } from '../../helpers/useQuery';
|
||||
import {
|
||||
getNewQueryParams,
|
||||
getParsedQuery,
|
||||
} from '../../helpers/getNewQueryParams';
|
||||
import { useQueryHandler } from '../../helpers/useQueryHandler';
|
||||
|
||||
export function VersioningStrategy({ project }: { project: Project }) {
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler();
|
||||
|
||||
useEffect(() => {
|
||||
const parsedQuery = getParsedQuery({ query });
|
||||
const { parsedQuery } = getParsedQuery();
|
||||
|
||||
if (!parsedQuery.versioningStrategy) {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
const { queryParams } = getQueryParamsWithUpdates({
|
||||
updates: [
|
||||
{ key: 'versioningStrategy', value: project.versioningStrategy },
|
||||
],
|
||||
@@ -59,8 +54,7 @@ export function VersioningStrategy({ project }: { project: Project }) {
|
||||
value={project.versioningStrategy}
|
||||
defaultValue="semver"
|
||||
onChange={event => {
|
||||
const queryParams = getNewQueryParams({
|
||||
query,
|
||||
const queryParams = getQueryParamsWithUpdates({
|
||||
updates: [{ key: 'versioningStrategy', value: event.target.value }],
|
||||
});
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import qs from 'qs';
|
||||
|
||||
import { Project } from '../contexts/ProjectContext';
|
||||
|
||||
export function getParsedQuery({ query }: { query: URLSearchParams }) {
|
||||
const parsedQuery: Partial<Project> = qs.parse(query.toString());
|
||||
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
export function getNewQueryParams({
|
||||
query,
|
||||
updates,
|
||||
}: {
|
||||
query: URLSearchParams;
|
||||
updates: {
|
||||
key: keyof Project;
|
||||
value: string;
|
||||
}[];
|
||||
}) {
|
||||
const queryParams = qs.parse(query.toString());
|
||||
|
||||
for (const { key, value } of updates) {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
|
||||
return qs.stringify(queryParams);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { mockSemverProject } from '../test-helpers/test-helpers';
|
||||
import { isProjectValid } from './isProjectValid';
|
||||
|
||||
describe('isProjectValid', () => {
|
||||
it('should return true for valid project', () => {
|
||||
const result = isProjectValid(mockSemverProject);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid project (undefined argument)', () => {
|
||||
const result = isProjectValid(undefined);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false for invalid project (empty object argument)', () => {
|
||||
const result = isProjectValid({});
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false for invalid project (invalid versioningStrategy argument)', () => {
|
||||
const result = isProjectValid({
|
||||
...mockSemverProject,
|
||||
versioningStrategy: 'banana',
|
||||
});
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
+4
-2
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Project } from '../../contexts/ProjectContext';
|
||||
import { Project } from '../contexts/ProjectContext';
|
||||
|
||||
export function isProjectValid(project: any): project is Project {
|
||||
return (
|
||||
project?.owner?.length > 0 &&
|
||||
project?.repo?.length > 0 &&
|
||||
project?.versioningStrategy?.length > 0
|
||||
(['semver', 'calver'] as Project['versioningStrategy'][]).includes(
|
||||
project?.versioningStrategy,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
export function useQuery(): URLSearchParams {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { mockSemverProject } from '../test-helpers/test-helpers';
|
||||
|
||||
jest.mock('react-router', () => ({
|
||||
useLocation: jest.fn(() => ({
|
||||
search: `?versioningStrategy=${mockSemverProject.versioningStrategy}&owner=${mockSemverProject.owner}&repo=${mockSemverProject.repo}`,
|
||||
})),
|
||||
}));
|
||||
|
||||
import { useQueryHandler } from './useQueryHandler';
|
||||
|
||||
const TEST_ID = 'grm--use-query-handler';
|
||||
|
||||
const MockComponent = () => {
|
||||
const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler();
|
||||
|
||||
const { parsedQuery } = getParsedQuery();
|
||||
const { queryParams } = getQueryParamsWithUpdates({
|
||||
updates: [{ key: 'repo', value: 'updated_mock_repo' }],
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-testid={TEST_ID}>
|
||||
{JSON.stringify({ parsedQuery, queryParams }, null, 2)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useQueryHandler', () => {
|
||||
it('should get parsedQuery and queryParams', () => {
|
||||
const { getByTestId } = render(<MockComponent />);
|
||||
|
||||
const smt = getByTestId(TEST_ID).innerHTML;
|
||||
|
||||
expect(smt).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"parsedQuery\\": {
|
||||
\\"versioningStrategy\\": \\"semver\\",
|
||||
\\"owner\\": \\"mock_owner\\",
|
||||
\\"repo\\": \\"mock_repo\\"
|
||||
},
|
||||
\\"queryParams\\": \\"versioningStrategy=semver&owner=mock_owner&repo=updated_mock_repo\\"
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useLocation } from 'react-router';
|
||||
import qs from 'qs';
|
||||
|
||||
import { Project } from '../contexts/ProjectContext';
|
||||
|
||||
export function useQueryHandler() {
|
||||
const location = useLocation();
|
||||
|
||||
function getParsedQuery() {
|
||||
const { decodedSearch } = getDecodedSearch(location);
|
||||
const parsedQuery: Partial<Project> = qs.parse(decodedSearch);
|
||||
|
||||
return {
|
||||
parsedQuery,
|
||||
};
|
||||
}
|
||||
|
||||
function getQueryParamsWithUpdates({
|
||||
updates,
|
||||
}: {
|
||||
updates: {
|
||||
key: keyof Project;
|
||||
value: string;
|
||||
}[];
|
||||
}) {
|
||||
const { decodedSearch } = getDecodedSearch(location);
|
||||
const queryParams = qs.parse(decodedSearch);
|
||||
|
||||
for (const { key, value } of updates) {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
|
||||
return {
|
||||
queryParams: qs.stringify(queryParams),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
getParsedQuery,
|
||||
getQueryParamsWithUpdates,
|
||||
};
|
||||
}
|
||||
|
||||
function getDecodedSearch(location: ReturnType<typeof useLocation>) {
|
||||
return {
|
||||
decodedSearch: new URLSearchParams(location.search).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
export const testables = {
|
||||
getDecodedSearch,
|
||||
};
|
||||
@@ -24,15 +24,18 @@ import {
|
||||
IPluginApiClient,
|
||||
} from '../api/PluginApiClient';
|
||||
|
||||
const mockOwner = 'mock_owner';
|
||||
const mockRepo = 'mock_repo';
|
||||
|
||||
export const mockSemverProject: Project = {
|
||||
owner: 'mock_owner',
|
||||
repo: 'mock_repo',
|
||||
owner: mockOwner,
|
||||
repo: mockRepo,
|
||||
versioningStrategy: 'semver',
|
||||
};
|
||||
|
||||
export const mockCalverProject: Project = {
|
||||
owner: 'mock_owner',
|
||||
repo: 'mock_repo',
|
||||
owner: mockOwner,
|
||||
repo: mockRepo,
|
||||
versioningStrategy: 'calver',
|
||||
};
|
||||
|
||||
@@ -130,18 +133,18 @@ export const mockSelectedPatchCommit = createMockRecentCommit({
|
||||
export const mockApiClient: IPluginApiClient = {
|
||||
getHost: jest.fn(() => 'github.com'),
|
||||
|
||||
getRepoPath: jest.fn(() => 'erikengervall/playground'),
|
||||
getRepoPath: jest.fn(() => `${mockOwner}/${mockRepo}`),
|
||||
|
||||
getOwners: jest.fn(async () => ({
|
||||
owners: ['owner1', 'owner2'],
|
||||
owners: [mockOwner, `${mockOwner}2`],
|
||||
})),
|
||||
|
||||
getRepositories: jest.fn(async () => ({
|
||||
repositories: ['repo1', 'repo2'],
|
||||
repositories: [mockRepo, `${mockRepo}2`],
|
||||
})),
|
||||
|
||||
getUsername: jest.fn(async () => ({
|
||||
username: 'erikengervall',
|
||||
username: mockOwner,
|
||||
})),
|
||||
|
||||
getRecentCommits: jest.fn(async () => [
|
||||
|
||||
Reference in New Issue
Block a user