* Removes regex from compareVersions * Replaces regexes in reducers * Rewrite Version and compareVersions to expect undefined * Adds safeguard against strange cases * Fixes regression from another PR Co-authored-by: Damir Abdulganiev <dabdulganiev@provectus.com>
This commit is contained in:
parent
205d8d000d
commit
31954ceb55
10 changed files with 106 additions and 121 deletions
|
@ -61,7 +61,7 @@ const App: React.FC = () => {
|
|||
<S.Hyperlink href="/ui">UI for Apache Kafka</S.Hyperlink>
|
||||
|
||||
<S.NavbarItem>
|
||||
<Version tag={GIT_TAG} commit={GIT_COMMIT} />
|
||||
{GIT_TAG && <Version tag={GIT_TAG} commit={GIT_COMMIT} />}
|
||||
</S.NavbarItem>
|
||||
</S.NavbarBrand>
|
||||
</S.Navbar>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import {screen, waitFor, within} from '@testing-library/react';
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import CustomParamsField, {
|
||||
Props,
|
||||
|
|
|
@ -9,8 +9,10 @@ import userEvent from '@testing-library/user-event';
|
|||
import { TOPIC_CUSTOM_PARAMS } from 'lib/constants';
|
||||
|
||||
const selectOption = async (listbox: HTMLElement, option: string) => {
|
||||
await waitFor(() => userEvent.click(listbox));
|
||||
await waitFor(() => userEvent.click(screen.getByText(option)));
|
||||
await waitFor(() => {
|
||||
userEvent.click(listbox);
|
||||
userEvent.click(screen.getByText(option));
|
||||
});
|
||||
};
|
||||
|
||||
const expectOptionIsSelected = (listbox: HTMLElement, option: string) => {
|
||||
|
@ -19,17 +21,27 @@ const expectOptionIsSelected = (listbox: HTMLElement, option: string) => {
|
|||
expect(selectedOption[0]).toHaveTextContent(option);
|
||||
};
|
||||
|
||||
const expectOptionIsDisabled = async (
|
||||
const expectOptionAvailability = async (
|
||||
listbox: HTMLElement,
|
||||
option: string,
|
||||
disabled: boolean
|
||||
) => {
|
||||
await waitFor(() => userEvent.click(listbox));
|
||||
const selectedOption = within(listbox).getAllByText(option);
|
||||
expect(selectedOption[1]).toHaveStyleRule(
|
||||
const selectedOptions = within(listbox).getAllByText(option).reverse();
|
||||
// its either two or one nodes, we only need last one
|
||||
const selectedOption = selectedOptions[0];
|
||||
|
||||
if (disabled) {
|
||||
expect(selectedOption).toHaveAttribute('disabled');
|
||||
} else {
|
||||
expect(selectedOption).toBeEnabled();
|
||||
}
|
||||
|
||||
expect(selectedOption).toHaveStyleRule(
|
||||
'cursor',
|
||||
disabled ? 'not-allowed' : 'pointer'
|
||||
);
|
||||
await waitFor(() => userEvent.click(listbox));
|
||||
};
|
||||
|
||||
describe('CustomParams', () => {
|
||||
|
@ -51,15 +63,15 @@ describe('CustomParams', () => {
|
|||
});
|
||||
|
||||
it('renders with props', () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
expect(addParamButton).toBeInTheDocument();
|
||||
expect(addParamButton).toHaveTextContent('Add Custom Parameter');
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveTextContent('Add Custom Parameter');
|
||||
});
|
||||
|
||||
describe('works with user inputs correctly', () => {
|
||||
it('button click creates custom param fieldset', async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
|
||||
const listbox = screen.getByRole('listbox');
|
||||
expect(listbox).toBeInTheDocument();
|
||||
|
@ -69,38 +81,38 @@ describe('CustomParams', () => {
|
|||
});
|
||||
|
||||
it('can select option', async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
const listbox = screen.getByRole('listbox');
|
||||
|
||||
await selectOption(listbox, 'compression.type');
|
||||
expectOptionIsSelected(listbox, 'compression.type');
|
||||
expectOptionIsDisabled(listbox, 'compression.type', true);
|
||||
await expectOptionAvailability(listbox, 'compression.type', true);
|
||||
|
||||
const textbox = screen.getByRole('textbox');
|
||||
expect(textbox).toHaveValue(TOPIC_CUSTOM_PARAMS['compression.type']);
|
||||
});
|
||||
|
||||
it('when selected option changes disabled options update correctly', async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
|
||||
const listbox = screen.getByRole('listbox');
|
||||
|
||||
await selectOption(listbox, 'compression.type');
|
||||
expectOptionIsDisabled(listbox, 'compression.type', true);
|
||||
expectOptionIsSelected(listbox, 'compression.type');
|
||||
await expectOptionAvailability(listbox, 'compression.type', true);
|
||||
|
||||
await selectOption(listbox, 'delete.retention.ms');
|
||||
expectOptionIsDisabled(listbox, 'delete.retention.ms', true);
|
||||
expectOptionIsDisabled(listbox, 'compression.type', false);
|
||||
await expectOptionAvailability(listbox, 'delete.retention.ms', true);
|
||||
await expectOptionAvailability(listbox, 'compression.type', false);
|
||||
});
|
||||
|
||||
it('multiple button clicks create multiple fieldsets', async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
await waitFor(() => userEvent.click(addParamButton));
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
await waitFor(() => userEvent.click(button));
|
||||
await waitFor(() => userEvent.click(button));
|
||||
|
||||
const listboxes = screen.getAllByRole('listbox');
|
||||
expect(listboxes.length).toBe(3);
|
||||
|
@ -110,48 +122,64 @@ describe('CustomParams', () => {
|
|||
});
|
||||
|
||||
it("can't select already selected option", async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
userEvent.click(addParamButton);
|
||||
userEvent.click(addParamButton);
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
await waitFor(() => userEvent.click(button));
|
||||
|
||||
const listboxes = screen.getAllByRole('listbox');
|
||||
|
||||
const firstListbox = listboxes[0];
|
||||
await selectOption(firstListbox, 'compression.type');
|
||||
expectOptionIsDisabled(firstListbox, 'compression.type', true);
|
||||
await expectOptionAvailability(firstListbox, 'compression.type', true);
|
||||
|
||||
const secondListbox = listboxes[1];
|
||||
expectOptionIsDisabled(secondListbox, 'compression.type', true);
|
||||
await expectOptionAvailability(secondListbox, 'compression.type', true);
|
||||
});
|
||||
|
||||
it('when fieldset with selected custom property type is deleted disabled options update correctly', async () => {
|
||||
const addParamButton = screen.getByRole('button');
|
||||
userEvent.click(addParamButton);
|
||||
userEvent.click(addParamButton);
|
||||
userEvent.click(addParamButton);
|
||||
const button = screen.getByRole('button');
|
||||
await waitFor(() => userEvent.click(button));
|
||||
await waitFor(() => userEvent.click(button));
|
||||
await waitFor(() => userEvent.click(button));
|
||||
|
||||
const listboxes = screen.getAllByRole('listbox');
|
||||
|
||||
const firstListbox = listboxes[0];
|
||||
await selectOption(firstListbox, 'compression.type');
|
||||
expectOptionIsDisabled(firstListbox, 'compression.type', true);
|
||||
await expectOptionAvailability(firstListbox, 'compression.type', true);
|
||||
|
||||
const secondListbox = listboxes[1];
|
||||
await selectOption(secondListbox, 'delete.retention.ms');
|
||||
expectOptionIsDisabled(secondListbox, 'delete.retention.ms', true);
|
||||
await expectOptionAvailability(
|
||||
secondListbox,
|
||||
'delete.retention.ms',
|
||||
true
|
||||
);
|
||||
|
||||
const thirdListbox = listboxes[2];
|
||||
await selectOption(thirdListbox, 'file.delete.delay.ms');
|
||||
expectOptionIsDisabled(thirdListbox, 'file.delete.delay.ms', true);
|
||||
await expectOptionAvailability(
|
||||
thirdListbox,
|
||||
'file.delete.delay.ms',
|
||||
true
|
||||
);
|
||||
|
||||
const deleteSecondFieldsetButton = screen.getByTitle(
|
||||
'Delete customParam field 1'
|
||||
);
|
||||
userEvent.click(deleteSecondFieldsetButton);
|
||||
await waitFor(() => userEvent.click(deleteSecondFieldsetButton));
|
||||
expect(secondListbox).not.toBeInTheDocument();
|
||||
|
||||
expectOptionIsDisabled(firstListbox, 'delete.retention.ms', false);
|
||||
expectOptionIsDisabled(thirdListbox, 'delete.retention.ms', false);
|
||||
await expectOptionAvailability(
|
||||
firstListbox,
|
||||
'delete.retention.ms',
|
||||
false
|
||||
);
|
||||
await expectOptionAvailability(
|
||||
thirdListbox,
|
||||
'delete.retention.ms',
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { GIT_REPO_LATEST_RELEASE_LINK } from 'lib/constants';
|
|||
import compareVersions from './compareVersions';
|
||||
|
||||
export interface VesionProps {
|
||||
tag?: string;
|
||||
tag: string;
|
||||
commit?: string;
|
||||
}
|
||||
|
||||
|
@ -15,20 +15,15 @@ const Version: React.FC<VesionProps> = ({ tag, commit }) => {
|
|||
latestTag: '',
|
||||
});
|
||||
useEffect(() => {
|
||||
if (tag) {
|
||||
fetch(GIT_REPO_LATEST_RELEASE_LINK)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setLatestVersionInfo({
|
||||
outdated: compareVersions(tag, data.tag_name) === -1,
|
||||
latestTag: data.tag_name,
|
||||
});
|
||||
fetch(GIT_REPO_LATEST_RELEASE_LINK)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setLatestVersionInfo({
|
||||
outdated: compareVersions(tag, data.tag_name) === -1,
|
||||
latestTag: data.tag_name,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [tag]);
|
||||
if (!tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { outdated, latestTag } = latestVersionInfo;
|
||||
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Version from 'components/Version/Version';
|
||||
import Version, { VesionProps } from 'components/Version/Version';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
|
||||
const tag = 'v1.0.1-SHAPSHOT';
|
||||
const commit = '123sdf34';
|
||||
|
||||
describe('Version', () => {
|
||||
it('shows nothing if tag is not defined', () => {
|
||||
const component = mount(<Version />);
|
||||
expect(component.html()).toEqual(null);
|
||||
});
|
||||
const setupComponent = (props: VesionProps) => render(<Version {...props} />);
|
||||
|
||||
it('shows current tag when only tag is defined', () => {
|
||||
const component = mount(<Version tag={tag} />);
|
||||
expect(component.text()).toContain(tag);
|
||||
it('renders', () => {
|
||||
setupComponent({ tag });
|
||||
expect(screen.getByText('Version:')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows current tag and commit', () => {
|
||||
const component = mount(<Version tag={tag} commit={commit} />);
|
||||
expect(component.text()).toContain(tag);
|
||||
expect(component.text()).toContain(commit);
|
||||
});
|
||||
|
||||
it('matches snapshot', () => {
|
||||
const component = mount(<Version tag={tag} commit={commit} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
setupComponent({ tag, commit });
|
||||
expect(screen.getByText(tag)).toBeInTheDocument();
|
||||
expect(screen.getByText(commit)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Version matches snapshot 1`] = `
|
||||
<Version
|
||||
commit="123sdf34"
|
||||
tag="v1.0.1-SHAPSHOT"
|
||||
>
|
||||
<div
|
||||
className="is-size-8 has-text-grey"
|
||||
>
|
||||
<span
|
||||
className="has-text-grey-light mr-1"
|
||||
>
|
||||
Version:
|
||||
</span>
|
||||
<span
|
||||
className="mr-1"
|
||||
>
|
||||
v1.0.1-SHAPSHOT
|
||||
</span>
|
||||
<span>
|
||||
(
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/provectus/kafka-ui/commit/123sdf34"
|
||||
target="__blank"
|
||||
title="Current commit"
|
||||
>
|
||||
123sdf34
|
||||
</a>
|
||||
<span>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</Version>
|
||||
`;
|
|
@ -1,6 +1,3 @@
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
|
||||
import compareVersions from 'components/Version/compareVersions';
|
||||
|
||||
const runTests = (dataSet: [string, string, number][]) => {
|
||||
|
@ -63,10 +60,11 @@ describe('compareVersions function', () => {
|
|||
});
|
||||
|
||||
it('returns valid result (negative test cases)', () => {
|
||||
expect(compareVersions()).toEqual(0);
|
||||
expect(compareVersions('v0.0.0')).toEqual(0);
|
||||
// @ts-expect-error first arg is number
|
||||
expect(compareVersions(123, 'v0.0.0')).toEqual(0);
|
||||
expect(compareVersions(undefined, 'v0.0.0')).toEqual(0);
|
||||
// @ts-expect-error second arg is number
|
||||
expect(compareVersions('v0.0.0', 123)).toEqual(0);
|
||||
expect(compareVersions('v0.0.0', undefined)).toEqual(0);
|
||||
expect(compareVersions(undefined, undefined)).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
const split = (v: string): string[] => {
|
||||
const c = v.replace(/^v/, '').replace(/\+.*$/, '');
|
||||
return c.split('-')[0].split('.');
|
||||
const c = v.replace('v', '').split('-')[0];
|
||||
return c.split('.');
|
||||
};
|
||||
|
||||
const compareVersions = (v1: string, v2: string): number => {
|
||||
const compareVersions = (v1?: string, v2?: string): number => {
|
||||
if (!v1 || !v2) return 0;
|
||||
|
||||
// try..catch - is our safeguard for strange git tags (or usecases without network)
|
||||
try {
|
||||
const s1 = split(v1);
|
||||
const s2 = split(v2);
|
||||
|
|
|
@ -9,8 +9,7 @@ export const initialState: AlertsState = {};
|
|||
const reducer = (state = initialState, action: Action): AlertsState => {
|
||||
const { type } = action;
|
||||
|
||||
const matches = /(.*)__(FAILURE)$/.exec(type);
|
||||
if (matches && matches[2]) return addError(state, action);
|
||||
if (type.endsWith('__FAILURE')) return addError(state, action);
|
||||
|
||||
if (type === getType(dismissAlert)) {
|
||||
return removeAlert(state, action);
|
||||
|
|
|
@ -4,12 +4,17 @@ export const initialState: LoaderState = {};
|
|||
|
||||
const reducer = (state = initialState, action: Action): LoaderState => {
|
||||
const { type } = action;
|
||||
const matches = /(.*)__(REQUEST|SUCCESS|FAILURE|CANCEL)$/.exec(type);
|
||||
const splitType = type.split('__');
|
||||
const requestState = splitType.pop();
|
||||
const requestName = splitType.join('__');
|
||||
|
||||
// not a *__REQUEST / *__SUCCESS / *__FAILURE / *__CANCEL actions, so we ignore them
|
||||
if (!matches) return state;
|
||||
|
||||
const [, requestName, requestState] = matches;
|
||||
if (
|
||||
requestState &&
|
||||
!['REQUEST', 'SUCCESS', 'FAILURE', 'CANCEL'].includes(requestState)
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (requestState) {
|
||||
case 'REQUEST':
|
||||
|
|
Loading…
Add table
Reference in a new issue