Skip to content

Commit

Permalink
coomit before rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
LiaSolo committed Oct 15, 2024
1 parent c74b967 commit 9688213
Show file tree
Hide file tree
Showing 29 changed files with 347 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type Props = {

const DatasetUploader: FC<Props> = ({ onUpload, isOpen, setIsOpen }) => {
const inputFile = useRef<HTMLInputElement>(null);
//const [isOpen, setIsOpen] = useState(false);
const [filesState, setFiles] = useState<FileList | null>(null);
const [isFileDragged, setIsFileDragged] = useState(false);
const [isDraggedInside, setIsDraggedInside] = useState(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const ModeButton: FC<ModeButtonProps> = ({
}: ModeButtonProps) => (
<Button
variant="secondary"
icon={!tableMode ? <Icon name="grid" /> : <Icon name="list" />}
icon={ <Icon name={!tableMode ? "grid" : "list"} />}
className={classNames(className, styles.wrapper, styles.modeButton)}
aria-label="Change mode"
{...props}
Expand Down
4 changes: 2 additions & 2 deletions web-app/client/src/components/Icon/Icon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Icon from './Icon';

const user = userEvent.setup();

describe('Testing Icon Component', () => {
describe('Icon Component', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('Should render icon', async () => {
render(<Icon name="info" role="img" />);
render(<Icon name="info" />);
expect(screen.getByRole('img'));
});
});
2 changes: 1 addition & 1 deletion web-app/client/src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Icon = <TName extends IconName>(
if (!icons[props.name]) {
throw new Error(`There is no icon with name "${props.name}!"`);
}
return icons[props.name](props);
return icons[props.name]({...props, role: 'img'});
};

export default Icon;
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,50 @@ import MultiSelect from './MultiSelect';
import { countries } from 'countries-list';

const user = userEvent.setup();
const options = [
{ label: 'aaa', value: 1 },
{ label: 'bbb', value: 2 },
{ label: 'ccc', value: 3 },
];

describe('Testing multi select dropdown', () => {
const renderAndOpen = async () => {
render(
<>
<MultiSelect label="Label" placeholder="Placeholder" options={options} />
<div>Outside</div>
</>,
);
const trigger = screen.getByText('Placeholder');
await user.click(trigger);
};

describe('Multiselect with dropdown', () => {
beforeEach(() => {
jest.clearAllMocks();
});

const countryNames = Object.entries(countries).map(
([, country]) => country,
);

test('Should open dropdown, select first and second option, close by click outside', async () => {
render(
<>
<MultiSelect
label="Country"
placeholder="Germany"
options={countryNames.map(({ emoji, native, name }) => ({
label: `${emoji} ${native}`,
value: name,
}))}
/>
<div>Outside</div>
</>,
);
const trigger = screen.getByText('Germany');
await user.click(trigger);
const andorra = () => screen.getByText('🇦🇩 Andorra');
const anguilla = () => screen.getByText('🇦🇮 Anguilla');
const antiguaQuery = () => screen.queryByText('🇦🇬 Antigua and Barbuda');
const anguillaQuery = () => screen.queryByText('🇦🇮 Anguilla');
expect(andorra() && anguilla());
expect(antiguaQuery()).not.toBeNull();

await user.click(andorra());
expect(andorra());
expect(anguillaQuery()).toBeNull();

await user.click(andorra());
await user.click(anguilla());
expect(anguillaQuery()).not.toBeNull();
expect(antiguaQuery()).toBeNull();

test('Should open dropdown', async () => {
await renderAndOpen();
expect(screen.getByText('aaa'));
});
test('Should select first option', async () => {
await renderAndOpen();
await user.click(screen.getByText('aaa'));
expect(screen.queryByText('aaa'));
expect(screen.queryByText('bbb')).toBeNull();
});
test('Should select second option', async () => {
await renderAndOpen();
await user.click(screen.getByText('aaa'));
await user.click(screen.getByText('aaa'));
screen.getByText('bbb');
await user.click(screen.getByText('bbb'));
expect(screen.queryByText('aaa')).toBeTruthy();
expect(screen.queryByText('bbb')).toBeTruthy();
});
test('Should close by click outside', async () => {
await renderAndOpen();
await user.click(screen.getByText('Outside'));
expect(antiguaQuery()).toBeNull();
expect(screen.queryByText('aaa')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,7 @@ const MultiSelect: ForwardRefRenderFunction<RefElement, MultiSelectProps> = (
className={cn(styles.multiSelect, error && styles.error)}
{...props}
ref={ref}
styles={{
menuPortal: (base) => {
return { ...base };
},
...colorStyles,
}}
styles={colorStyles}
menuPortalTarget={portalRoot}
menuPosition="fixed"
components={{ ...customComponents, ...components }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.tooltip {
ul {
list-style: disc;
margin-left: 24px;
}
}
52 changes: 52 additions & 0 deletions web-app/client/src/components/Inputs/Password/Password.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { TestPassword } from './TestPassword';

const user = userEvent.setup();

describe('Variants for password', () => {
beforeEach(() => {
jest.clearAllMocks();
});

const typePassword = async (text: string) => {
render(<TestPassword />);
const errorMessage =
'The password does not match the pattern (see tooltip)';
const input = screen.getByPlaceholderText('admin1234');
const trigger = screen.getByRole('submit');
await user.type(input, text);
await user.click(trigger);
return screen.queryByText(errorMessage);
};
test('Should show error cause too shot password', async () => {
const error = await typePassword('Aa23.0');
expect(error).toBeTruthy();
});

test('Should show error cause password without digit', async () => {
const error = await typePassword('Aaaaaa.a');
expect(error).toBeTruthy();
});

test('Should show error cause password without special symbol', async () => {
const error = await typePassword('Aaaa2312');
expect(error).toBeTruthy();
});

test('Should show error cause password without uppercase letter', async () => {
const error = await typePassword('aaaa23.0');
expect(error).toBeTruthy();
});

test('Should show error cause password without lowercase letter', async () => {
const error = await typePassword('AAAA23.0');
expect(error).toBeTruthy();
});

test('Should not show error cause good password', async () => {
const error = await typePassword('Aaaa23.0');
expect(error).toBeNull();
});
});
86 changes: 86 additions & 0 deletions web-app/client/src/components/Inputs/Password/Password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ComponentProps, FC } from 'react';
import { Text } from '@components/Inputs';
import styles from './Password.module.scss';
import { Control, Controller, FieldValues, Path } from 'react-hook-form';
import isStrongPassword from 'validator/lib/isStrongPassword';

const passwordTooltip = (
<div className={styles.tooltip}>
The password must contain
<ul>
<li>at least 8 characters</li>
<li>at least 1 uppercase letter</li>
<li>at least 1 lowercase letter</li>
<li>at least 1 digit</li>
<li>at least 1 special character</li>
</ul>
</div>
);

const customValidateFunction = (value: string) => {
return (
isStrongPassword(value) ||
'The password does not match the pattern (see tooltip)'
);
};

type ControlRules<T extends FieldValues> = ComponentProps<
typeof Controller<T>
>['rules'];

type TextProps = ComponentProps<typeof Text>;

type ControlProps<T extends FieldValues> = {
controlName: Path<T>;
control: Control<T>;
rules?: ControlRules<T>;
};

type PasswordProps<T extends FieldValues> = TextProps &
ControlProps<T> & {
needStrengthValidation?: boolean;
};

const Password = <T extends FieldValues>({
controlName,
control,
rules,
needStrengthValidation = true,
...rest
}: PasswordProps<T>) => {
let validate;
if (!needStrengthValidation) validate = rules?.validate;
else if (typeof rules?.validate === 'object')
validate = {
...rules.validate,
strongPassword: customValidateFunction,
};
else if (typeof rules?.validate === 'function')
validate = {
validation: rules?.validate,
strongPassword: customValidateFunction,
};
else if (rules?.validate === undefined) validate = customValidateFunction;

return (
<Controller
name={controlName}
control={control}
rules={{
...rules,
validate: validate as any,
}}
render={({ field, fieldState }) => (
<Text
type="password"
tooltip={passwordTooltip}
{...field}
error={fieldState.error?.message}
{...rest}
/>
)}
/>
);
};

export default Password;
49 changes: 49 additions & 0 deletions web-app/client/src/components/Inputs/Password/TestPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { FC } from 'react';
import { useForm } from 'react-hook-form';
import Button from '@components/Button';
import Password from './Password';

type Inputs = {
password: string;
};

const defaultValues: Inputs = {
password: '',
};

export const TestPassword: FC = () => {

const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<Inputs>({
defaultValues,
});

const onSubmit = handleSubmit(async () => {
console.log('submit')
});

return (
<>
<form onSubmit={onSubmit}>
<Password
control={control}
controlName="password"
label="Password"
placeholder="admin1234"
rules={{ required: 'Required' }}
/>
<Button
variant="primary"
type="submit"
disabled={isSubmitting}
role="submit"
>
Test Button
</Button>
</form>
</>
);
};
1 change: 1 addition & 0 deletions web-app/client/src/components/Inputs/Password/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Password';
Loading

0 comments on commit 9688213

Please sign in to comment.