Add testing identifier to elements

This commit is contained in:
lethemanh
2024-12-09 21:14:46 +07:00
parent df5c472d7b
commit 217e683269
43 changed files with 201 additions and 102 deletions
@@ -12,6 +12,7 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean;
disabled?: boolean;
children?: React.ReactNode;
testClassId?: string;
}
export const Button = (props: ButtonProps) => {
@@ -65,10 +66,12 @@ export const Button = (props: ButtonProps) => {
' inline-flex items-center px-4 py-2 border font-medium rounded-md focus:outline-none ' +
className +
' ' +
props.className
props.className +
' ' +
`testid:${props.testClassId}`
}
disabled={disabled}
{..._.omit(props, 'loading', 'children', 'className', 'icon', 'iconSize')}
{..._.omit(props, 'loading', 'children', 'className', 'icon', 'iconSize', 'testClassId')}
>
{props.loading && (
<>
@@ -1,9 +1,11 @@
import _ from "lodash";
import { isUndefined } from "lodash";
interface CheckboxSliderProps extends React.InputHTMLAttributes<HTMLInputElement> {
checked?: boolean;
disabled?: boolean;
noFocusBorder?: boolean;
testClassId?: string;
}
/** Just classes up a checkbox to look like a slider. Deviates from onChange etc for
* compatibility with `InputHTMLAttributes<HTMLInputElement>`, to expose eg. id to use with htmlFor.
@@ -15,13 +17,17 @@ export const CheckboxSlider = (props: CheckboxSliderProps) => {
return (
<input
{...props}
{..._.omit(props, 'testClassId')}
type="checkbox"
role="switch"
style={styles}
// @author https://tw-elements.com/docs/react/forms/switch/
className="!bg-none mr-2 mt-[0.3rem] h-7 w-14 appearance-none rounded-[0.9rem] bg-neutral-300 before:pointer-events-none before:absolute before:h-7 before:w-5 before:rounded-full before:bg-transparent before:content-[''] after:absolute after:z-[2] after:mt-[0.2rem] after:ml-[0.2rem] after:h-5 after:w-5 after:rounded-full after:border-none after:bg-neutral-100 after:shadow-[0_0px_3px_0_rgb(0_0_0_/_7%),_0_2px_2px_0_rgb(0_0_0_/_4%)] after:transition-[background-color_0.2s,transform_0.2s] after:content-[''] checked:bg-primary checked:after:absolute checked:after:z-[2] checked:after:ml-[1.9625rem] checked:after:w-5 checked:after:rounded-full checked:after:border-none checked:after:bg-primary checked:after:shadow-[0_3px_1px_-2px_rgba(0,0,0,0.2),_0_2px_2px_0_rgba(0,0,0,0.14),_0_1px_5px_0_rgba(0,0,0,0.12)] checked:after:transition-[background-color_0.2s,transform_0.2s] checked:after:content-[''] hover:cursor-pointer dark:bg-neutral-600 dark:after:bg-white dark:checked:bg-primary dark:checked:after:bg-primary disabled:cursor-default disabled:opacity-25 !focus:shadow-none"
className={[
"!bg-none mr-2 mt-[0.3rem] h-7 w-14 appearance-none rounded-[0.9rem] bg-neutral-300 before:pointer-events-none before:absolute before:h-7 before:w-5 before:rounded-full before:bg-transparent before:content-[''] after:absolute after:z-[2] after:mt-[0.2rem] after:ml-[0.2rem] after:h-5 after:w-5 after:rounded-full after:border-none after:bg-neutral-100 after:shadow-[0_0px_3px_0_rgb(0_0_0_/_7%),_0_2px_2px_0_rgb(0_0_0_/_4%)] after:transition-[background-color_0.2s,transform_0.2s] after:content-[''] checked:bg-primary checked:after:absolute checked:after:z-[2] checked:after:ml-[1.9625rem] checked:after:w-5 checked:after:rounded-full checked:after:border-none checked:after:bg-primary checked:after:shadow-[0_3px_1px_-2px_rgba(0,0,0,0.2),_0_2px_2px_0_rgba(0,0,0,0.14),_0_1px_5px_0_rgba(0,0,0,0.12)] checked:after:transition-[background-color_0.2s,transform_0.2s] checked:after:content-[''] hover:cursor-pointer dark:bg-neutral-600 dark:after:bg-white dark:checked:bg-primary dark:checked:after:bg-primary disabled:cursor-default disabled:opacity-25 !focus:shadow-none",
`testid:${props.testClassId}`
].join(' ')}
checked={!!props.checked}
disabled={props.disabled}
data-checkbox-id={props.testClassId}
/>)
}
@@ -17,6 +17,7 @@ interface InputProps
inputClassName?: string;
className?: string;
inputRef?: React.Ref<HTMLInputElement | HTMLTextAreaElement>;
testClassId?: string;
}
export type ThemeName = 'plain' | 'outline' | 'blue' | 'rose';
@@ -66,7 +67,8 @@ export const Input = (props: InputProps) => {
<textarea
ref={props.inputRef as React.Ref<HTMLTextAreaElement>}
className={inputClassName + ' ' + props.inputClassName + ' ' + props.className}
{..._.omit(props as any, 'label', 'inputClassName', 'inputRef', 'className', 'value', 'size')}
{..._.omit(props as any, 'label', 'inputClassName', 'inputRef', 'className', 'value', 'size', 'testClassId')}
data-input-id={props.testClassId}
>
{props.value}
</textarea>
@@ -74,8 +76,8 @@ export const Input = (props: InputProps) => {
<input
ref={props.inputRef as React.Ref<HTMLInputElement>}
type="text"
className={inputClassName + ' ' + props.inputClassName + ' ' + props.className}
{..._.omit(props, 'label', 'inputClassName', 'inputRef', 'className', 'size')}
className={`${inputClassName} ${props.inputClassName} ${props.className} testid:${props.testClassId}`}
{..._.omit(props, 'label', 'inputClassName', 'inputRef', 'className', 'size', 'testClassId')}
/>
))}
</>
+3 -2
View File
@@ -8,6 +8,7 @@ export default function A(
href?: string;
children: React.ReactNode;
noColor?: boolean;
testClassId?: string;
},
) {
const colors = props.noColor ? '' : 'hover:text-blue-700 active:text-blue-800 text-blue-500';
@@ -27,8 +28,8 @@ export default function A(
return (
<a
href={(props.href || '#').replace(/^javascript:/, '')}
className={colors + ' ' + (props.className || '')}
{..._.omit(props, 'children', 'className', 'noColor')}
className={colors + ' ' + (props.className || '') + ' ' + `testid:${props.testClassId}`}
{..._.omit(props, 'children', 'className', 'noColor', 'testClassId')}
>
{props.children}
</a>
@@ -42,13 +42,15 @@ export const ConfirmModal = (props: ConfirmModalProps) => {
className="ml-2"
theme={props.buttonOkTheme || "danger"}
onClick={dialogCloseHandler(true)}
>
testClassId="confirm-modal-button-confirm"
>
{Languages.t(props.buttonOkLabel)}
</Button>
<Button
theme='default'
onClick={dialogCloseHandler(false)}
>
testClassId="confirm-modal-button-cancel"
>
{Languages.t(props.buttonCancelLabel || "general.cancel")}
</Button>
</>
@@ -62,6 +62,7 @@ export default class ButtonWithTimeout extends React.Component {
(this.props.className ? this.props.className : '')
}
onClick={this.props.onClick}
testClassId={this.props.testClassId}
>
{this.props.value}
</Button>
@@ -27,7 +27,7 @@ export default class Button extends React.Component {
<button
ref={this.props.refButton}
{...this.props}
className={'button no-tw ' + className}
className={'button no-tw ' + className + ` testid:${this.props.testClassId}`}
onClick={evt => {
evt.target.blur();
this.props.onClick && this.props.onClick(evt);
@@ -76,7 +76,7 @@ export default class Input extends Component {
}
this.onKeyDown(evt);
}}
className={'input ' + (className || '')}
className={'input ' + (className || '') + ` testid:${this.props.testClassId}`}
onBlur={this.props.onBlur}
onChange={this.props.onChange}
/>
@@ -57,7 +57,8 @@ export default class MenuComponent extends React.Component {
<div
ref={node => (this.original_menu = node)}
className={
'menu-list ' + (this.props.withFrame ? 'as_frame text-black bg-white dark:bg-zinc-900 dark:text-white rounded-lg ' : '') + this.props.animationClass
'menu-list ' + (this.props.withFrame ? 'as_frame text-black bg-white dark:bg-zinc-900 dark:text-white rounded-lg ' : '') + this.props.animationClass + ' ' +
`testid:${this.props.testClassId}`
}
>
{(this.props.menu || [])
@@ -63,6 +63,8 @@ export default class Menu extends React.Component {
this.props.menu,
elementRect,
this.props.position,
{},
this.props.testClassId
);
this.setState({ isMenuOpen: true }, () => this.open = true);
this.open = true;
@@ -210,6 +210,7 @@ export default class MenusBodyLayer extends React.Component {
: 'skew_in_right_nobounce'
: 'fade_in'
}
testClassId={item.dataMenuId}
/>
</div>
</OutsideClickHandler>
@@ -57,7 +57,7 @@ class MenusManager extends Observable {
this.notify();
}
async openMenu(menu, domRect, positionType, options) {
async openMenu(menu, domRect, positionType, options, dataMenuId) {
this.isOpen = 1;
if(typeof menu === 'function') {
menu = await menu();
@@ -85,6 +85,7 @@ class MenusManager extends Observable {
level: 0,
id: Number.unid(),
allowClickOut: options.allowClickOut !== undefined ? options.allowClickOut : true,
dataMenuId,
});
this.last_opened_id = Number.unid();
this.notify();
@@ -191,7 +191,7 @@ export default class UploadZone extends React.Component<PropsType, StateType> {
<div
ref={node => node && (this.node = node)}
style={this.props.style}
className={classNames('upload_drop_zone', this.props.className)}
className={classNames('upload_drop_zone', this.props.className, `testid:${this.props.testClassId}`)}
onClick={() => {
if (!this.props.disableClick) {
this.open();
@@ -232,10 +232,10 @@ export default memo(
inPublicSharing,
};
return isMobile ? (
<DocumentRow {...commonProps} />
<DocumentRow {...commonProps} testClassId="browser-document-row" />
) : (
<Draggable id={index} key={index}>
<DocumentRow {...commonProps} />
<DocumentRow {...commonProps} testClassId="browser-document-row" />
</Draggable>
);
}
@@ -339,6 +339,7 @@ export default memo(
onDragOver={handleDragOver}
onDrop={handleDrop}
disabled={inTrash || access === 'read'}
testClassId="browser-upload-zone"
>
{role == 'admin' && <UsersModal />}
<VersionsModal />
@@ -374,8 +375,11 @@ export default memo(
buildFileTypeContextMenu(),
{ x: evt.clientX, y: evt.clientY },
'center',
{},
"browser-share-with-me-menu-file-type"
);
}}
testClassId="browser-share-with-me-button-file-type"
>
<span>
{filter.mimeType.key && filter.mimeType.key != 'All'
@@ -394,8 +398,11 @@ export default memo(
buildPeopleContextMen(),
{ x: evt.clientX, y: evt.clientY },
'center',
{},
"browser-share-with-me-menu-people"
);
}}
testClassId="browser-share-with-me-button-people"
>
<span>{Languages.t('scenes.app.shared_with_me.people')}</span>
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
@@ -411,8 +418,11 @@ export default memo(
buildDateContextMenu(),
{ x: evt.clientX, y: evt.clientY },
'center',
{},
"browser-share-with-me-menu-last-modified"
);
}}
testClassId="browser-share-with-me-button-last-modified"
>
<span>
{filter.date.key && filter.date.key != 'All'
@@ -440,9 +450,9 @@ export default memo(
</BaseSmall>
)}
<Menu menu={() => onBuildSortContextMenu()} sortData={sortLabel}>
<Menu menu={() => onBuildSortContextMenu()} sortData={sortLabel} testClassId="browser-menu-sorting">
{' '}
<Button theme="outline" className="ml-4 flex flex-row items-center">
<Button theme="outline" className="ml-4 flex flex-row items-center" testClassId="browser-button-sorting">
<SortIcon
className={`h-4 w-4 mr-2 -ml-1 ${
sortLabel.order === 'asc' ? 'transform rotate-180' : ''
@@ -455,9 +465,9 @@ export default memo(
</Button>
</Menu>
{viewId !== 'shared_with_me' && (
<Menu menu={() => onBuildContextMenu(details)}>
<Menu menu={() => onBuildContextMenu(details)} testClassId="browser-menu-context">
{' '}
<Button theme="secondary" className="ml-4 flex flex-row items-center">
<Button theme="secondary" className="ml-4 flex flex-row items-center" testClassId="browser-button-context">
<span>
{selectedCount > 1
? `${selectedCount} items`
@@ -485,6 +495,7 @@ export default memo(
onClick={() => uploadItemModal()}
theme="primary"
className="mt-4"
testClassId="browser-button-add-doc"
>
{Languages.t('scenes.app.drive.add_doc')}
</Button>
@@ -514,6 +525,7 @@ export default memo(
setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity))
}
onBuildContextMenu={() => onBuildContextMenu(details, child)}
testClassId="browser-folder-row"
/>
</Droppable>
) : (
@@ -26,6 +26,7 @@ export const AccessLevelDropdown = ({
className,
size,
noRedWhenLevelNone,
testClassId,
}: {
disabled?: boolean;
level: DriveFileAccessLevel | null;
@@ -35,6 +36,7 @@ export const AccessLevelDropdown = ({
hiddenLevels?: DriveFileAccessLevelOrRemove[] | string[];
size?: SelectSize,
noRedWhenLevelNone?: boolean,
testClassId?: string,
}) => {
const createOption = (level: DriveFileAccessLevelOrRemove) =>
!hiddenLevels?.includes(level) && <option value={level}>{(labelOverrides || {})[level] || translateAccessLevel(level)}</option>;
@@ -42,7 +44,7 @@ export const AccessLevelDropdown = ({
<Select
disabled={disabled}
size={size}
className={className + ' w-auto'}
className={className + ' w-auto' + ` testid:${testClassId}`}
theme={(!noRedWhenLevelNone && level === 'none') ? 'rose' : 'outline'}
value={level || 'none'}
onChange={e => onChange(e.target.value as DriveFileAccessLevel & 'remove')}
@@ -9,6 +9,7 @@ export type DriveItemProps = {
checked: boolean;
onClick?: () => void;
onBuildContextMenu: () => Promise<any[]>;
testClassId?: string;
};
export type DriveItemOverlayProps = {
@@ -1,6 +1,5 @@
import {
DotsHorizontalIcon,
ShieldCheckIcon,
ShieldExclamationIcon,
BanIcon,
} from '@heroicons/react/outline';
@@ -32,6 +31,7 @@ export const DocumentRow = ({
checked,
onClick,
onBuildContextMenu,
testClassId,
}: DriveItemProps) => {
const history = useHistory();
const [hover, setHover] = useState(false);
@@ -52,7 +52,8 @@ export const DocumentRow = ({
(checked
? (notSafe ? 'bg-rose-500' : 'bg-blue-500') + ' bg-opacity-10 hover:bg-opacity-25'
: 'hover:bg-zinc-500 hover:bg-opacity-10 ') +
(className || '')
(className || '') +
` testid:${testClassId}`
}
id={`DR-${item.id}`}
onMouseEnter={() => setHover(true)}
@@ -102,12 +103,13 @@ export const DocumentRow = ({
</div>
)}
<div className="shrink-0 ml-auto md:ml-4">
<Menu menu={onBuildContextMenu}>
<Menu menu={onBuildContextMenu} testClassId="document-row-menu">
<Button
theme={'secondary'}
size="sm"
className={'!rounded-full '}
icon={DotsHorizontalIcon}
testClassId="document-row-button-open-menu"
/>
</Menu>
</div>
@@ -17,6 +17,7 @@ export const FolderRow = ({
checked,
onClick,
onBuildContextMenu,
testClassId,
}: DriveItemProps) => {
const [hover, setHover] = useState(false);
@@ -28,7 +29,8 @@ export const FolderRow = ({
(checked
? 'bg-blue-500 bg-opacity-10 hover:bg-opacity-25 '
: 'hover:bg-zinc-500 hover:bg-opacity-10 ') +
(className || '')
(className || '') +
` testid:${testClassId}`
}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
@@ -58,12 +60,13 @@ export const FolderRow = ({
<BaseSmall>{formatBytes(item.size)}</BaseSmall>
</div>
<div className="shrink-0 ml-4">
<Menu menu={onBuildContextMenu}>
<Menu menu={onBuildContextMenu} testClassId="folder-row-menu">
<Button
theme={'secondary'}
size="sm"
className={'!rounded-full '}
icon={DotsHorizontalIcon}
testClassId="folder-row-button-open-menu"
/>
</Menu>
</div>
@@ -74,7 +74,7 @@ export const PathRender = ({
const pathLength = (pathToRender || []).reduce((acc, curr) => acc + curr.name.length, 0);
return (
<nav className="overflow-hidden whitespace-nowrap mr-2 pl-px inline-flex md:max-w-[50%] sm:max-w-[20%]">
<nav className="overflow-hidden whitespace-nowrap mr-2 pl-px inline-flex md:max-w-[50%] sm:max-w-[20%] testid:header-path">
{pathLength < 70 ? (
(pathToRender || [])?.map((a, i) => (
<PathItem
@@ -136,7 +136,7 @@ const PathItem = ({
<div className={`flex items-center ${!first ? 'overflow-hidden' : ''}`}>
<a
href="#"
className={`text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white ${!first ? 'overflow-hidden' : ''}`}
className={`text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white ${!first ? 'overflow-hidden' : ''} testid:header-path-item`}
onClick={evt => {
evt.preventDefault();
@@ -71,6 +71,7 @@ const ConfirmDeleteModalContent = ({ items }: { items: DriveItem[] }) => {
setSelected({});
setState({ ...state, open: false });
}}
testClassId="confirm-delete-modal-button-delete"
>
Delete
</Button>
@@ -52,6 +52,7 @@ const ConfirmModalContent = () => {
theme="primary"
className="float-right"
onClick={handleClose}
testClassId="confirm-modal-button-move"
>
<>{Languages.t('components.SelectorModalContent_move_to')} '{selected[0]?.name}'</>
</Button>
@@ -74,6 +74,7 @@ const ConfirmTrashModalContent = ({ items }: { items: DriveItem[] }) => {
await refresh("trash");
setState({ ...state, open: false });
}}
testClassId="cofirm-trash-modal-button-remove"
>
{Languages.t('components.ConfirmTrashModalContent_move_to_trash')}
</Button>
@@ -43,6 +43,7 @@ export const CreateFolder = () => {
}
}}
onChange={(e: any) => setName(e.target.value)}
testClassId="create-folder-input"
/>
</div>
<Button
@@ -50,6 +51,7 @@ export const CreateFolder = () => {
loading={loading}
className="mt-4 float-right"
onClick={createFolderHandler}
testClassId="create-folder-button"
>
Create
</Button>
@@ -53,6 +53,7 @@ export const CreateLink = () => {
placeholder={ Languages.t('components.create_link_modal.hint')}
className="w-full mt-4"
onChange={e => setName(e.target.value)}
testClassId="create-link-name-input"
/>
<Input
@@ -60,6 +61,7 @@ export const CreateLink = () => {
placeholder="https://example.com"
className="w-full mt-4"
onChange={e => setLink(e.target.value)}
testClassId="create-link-input"
/>
<Button
@@ -70,6 +72,7 @@ export const CreateLink = () => {
await createLink();
setState({ ...state, open: false });
}}
testClassId="create-link-button"
>
{ Languages.t('components.create_link_modal.button')}
</Button>
@@ -82,11 +82,13 @@ export const CreateModal = ({
icon={<FolderAddIcon className="w-5 h-5" />}
text={Languages.t('components.create_modal.create_folder')}
onClick={() => setState({ ...state, type: 'folder' })}
testClassId="create-folder-option"
/>
<CreateModalOption
icon={<LinkIcon className="w-5 h-5" />}
text={Languages.t('components.create_modal.create_link')}
onClick={() => setState({ ...state, type: 'link' })}
testClassId="create-link-option"
/>
{(applications || [])
@@ -132,6 +134,7 @@ export const CreateModal = ({
onClick={() =>
addFromUrl(app.emptyFile.url, app.emptyFile.filename || app.emptyFile.name)
}
testClassId={app.emptyFile.name}
/>
);
})}
@@ -167,11 +170,11 @@ export const CreateModal = ({
);
};
const CreateModalOption = (props: { icon: ReactNode; text: string; onClick: () => void }) => {
const CreateModalOption = (props: { testClassId?: string; icon: ReactNode; text: string; onClick: () => void }) => {
return (
<div
onClick={props.onClick}
className="flex flex-row p-4 dark:bg-zinc-900 dark:text-white bg-zinc-100 hover:bg-opacity-75 cursor-pointer rounded-md m-2"
className={`flex flex-row p-4 dark:bg-zinc-900 dark:text-white bg-zinc-100 hover:bg-opacity-75 cursor-pointer rounded-md m-2 testid:${props.testClassId}`}
>
<div className="flex items-center justify-center">{props.icon}</div>
<div className="grow flex items-center ml-2">
@@ -105,6 +105,7 @@ const PropertiesModalContent = ({ id, onClose, inPublicSharing }: { id: string;
}
}}
placeholder={Languages.t('components.PropertiesModalContent_place_holder')}
testClassId="properties-modal-input-update-name"
/>
}
/>
@@ -115,6 +116,7 @@ const PropertiesModalContent = ({ id, onClose, inPublicSharing }: { id: string;
theme="primary"
loading={loading}
onClick={doSave}
testClassId="properties-modal-button-update-name"
>
{Languages.t('components.PropertiesModalContent_update_button')}
</Button>
@@ -26,7 +26,8 @@ export const CopyLinkButton = (props: {
disabled={!haveTextToCopy}
theme={didJustCompleteACopy ? "green" : "primary"}
className="justify-center w-64"
>
testClassId="copy-link-button"
>
{ didJustCompleteACopy
? <CheckCircleIcon className="w-5 mr-2" />
: <LinkIcon className="w-5 mr-2" />
@@ -130,6 +130,7 @@ export const ExpiryEditorRow = (props: {
else if (e.key == 'Enter')
confirmSaveExpiry(currentEditedValue);
}}
testClassId="expiry-editor-row-expired-date"
/>
<Button
disabled={disabled}
@@ -137,7 +138,8 @@ export const ExpiryEditorRow = (props: {
size="sm"
className="max-w-xs mr-2 mt-1"
onClick={() => confirmSaveExpiry(currentEditedValue)}
>
testClassId="expiry-editor-row-button-confirm"
>
{Languages.t('components.public-link-security_field_confirm_edit')}
</Button>
</>
@@ -153,7 +155,8 @@ export const ExpiryEditorRow = (props: {
} else
confirmSaveExpiry(0);
}}
/>
testClassId="expiry-editor-row-checkbox"
/>
}
/>
<ConfirmModal
@@ -78,7 +78,8 @@ const ChangePublicLinkAccessLevelRow = (props: {
level={props.level}
hiddenLevels={['remove']}
onChange={props.onChange}
/>
testClassId="public-link-access-level-dropdown"
/>
}
/>
);
@@ -101,7 +102,8 @@ const SwitchToAdvancedSettingsRow = (props: {
if (!props.disabled)
props.onShowAdvancedScreen(true);
}}
>
testClassId="public-link-access-advance-switcher"
>
{Languages.t("components.public-link-security-change")}
</A>
}
@@ -126,58 +128,58 @@ const PublicLinkModalContent = (props: {
return (
<ModalContent
title={
<>
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<div className="rounded-md border dark:border-zinc-700 my-5 mb-8">
<ChangePublicLinkAccessLevelRow
disabled={loading}
level={item?.access_info?.public?.level || 'none'}
onChange={level => {
item && update(changePublicLink(item, { level }));
}}
/>
<SwitchToAdvancedSettingsRow
disabled={!havePublicLink}
onShowAdvancedScreen={props.onShowAdvancedScreen}
<>
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<div className="rounded-md border dark:border-zinc-700 my-5 mb-8">
<ChangePublicLinkAccessLevelRow
disabled={loading}
level={item?.access_info?.public?.level || 'none'}
onChange={level => {
item && update(changePublicLink(item, { level }));
}}
/>
</div>
<SwitchToAdvancedSettingsRow
disabled={!havePublicLink}
onShowAdvancedScreen={props.onShowAdvancedScreen}
/>
</div>
<div className="flex flex-row place-content-end my-4">
<CopyLinkButton textToCopy={havePublicLink && publicLink} />
</div>
<div className="flex flex-row place-content-end my-4">
<CopyLinkButton textToCopy={havePublicLink && publicLink} />
</div>
<Modal
open={props.isOnAdvancedScreen}
onClose={() => { props.onShowAdvancedScreen(false); }}
>
<ModalContent
title={
<>
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<div className='my-4'><Subtitle>{Languages.t('components.public-link-security')}</Subtitle></div>
<div className={Styles.RoundedBorderSection}>
<PublicLinkAccessOptions
disabled={loading || access !== 'manage'}
password={item?.access_info?.public?.password}
expiration={item?.access_info?.public?.expiration}
onChangePassword={async (password: string) => {
item && await update(changePublicLink(item, { password: password || '' }));
}}
onChangeExpiration={async (expiration: number) => {
item && await update(changePublicLink(item, { expiration }));
}}
/>
</div>
</ModalContent>
</Modal>
</ModalContent>
<Modal
open={props.isOnAdvancedScreen}
onClose={() => { props.onShowAdvancedScreen(false); }}
>
<ModalContent
title={
<>
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<div className='my-4'><Subtitle>{Languages.t('components.public-link-security')}</Subtitle></div>
<div className={Styles.RoundedBorderSection}>
<PublicLinkAccessOptions
disabled={loading || access !== 'manage'}
password={item?.access_info?.public?.password}
expiration={item?.access_info?.public?.expiration}
onChangePassword={async (password: string) => {
item && await update(changePublicLink(item, { password: password || '' }));
}}
onChangeExpiration={async (expiration: number) => {
item && await update(changePublicLink(item, { expiration }));
}}
/>
</div>
</ModalContent>
</Modal>
</ModalContent>
);
}
@@ -112,6 +112,7 @@ return <>
else if (e.key == 'Enter')
confirmSavePassword(currentEditedPassword);
}}
testClassId="pasword-editor-row-editting-password"
/>
<Button
disabled={disabled}
@@ -119,7 +120,8 @@ return <>
size="sm"
className="max-w-xs mr-2 mt-1"
onClick={() => confirmSavePassword(currentEditedPassword)}
>
testClassId="password-editor-row-button-confirm"
>
{Languages.t('components.public-link-security_field_confirm_edit')}
</Button>
</>
@@ -135,7 +137,8 @@ return <>
} else
confirmSavePassword("");
}}
/>
testClassId="password-editor-row-checkbox"
/>
}
/>
@@ -145,9 +148,8 @@ return <>
text={Languages.t("components.public-link-security_password_removal_body")}
buttonOkTheme='primary'
buttonOkLabel='components.public-link-security_password_removal_confirm'
onClose={() => setIsConfirmingPasswordRemoval(false)}
onOk={() => savePassword("")}
/>
/>
</>;
}
@@ -14,13 +14,13 @@ export const PublicLinkAccessOptions = (props: {
password={props.password}
onChangePassword={props.onChangePassword}
isLinkExpired={!!props.expiration && props.expiration < Date.now()}
/>
/>
<ExpiryEditorRow
disabled={props.disabled}
value={props.expiration ?? 0}
onChange={props.onChangeExpiration}
isLinkPasswordProtected={!!props.password?.length}
/>
/>
</>
);
@@ -138,6 +138,7 @@ const SelectorModalContent = (key: any) => {
setState({ ...state, open: false });
setLoading(false);
}}
testClassId="selector-modal-button"
>
{selected.length === 0 ? (
<>{Languages.t('components.SelectorModalContent_no_items')}</>
@@ -113,6 +113,7 @@ export const InternalUsersAccessManager = ({
}}
value={searchString}
inputRef={inputElement}
testClassId="access-management-search-users"
/>
)}
/>
@@ -125,6 +126,7 @@ export const InternalUsersAccessManager = ({
hiddenLevels={['remove']}
level={level}
onChange={level => setLevel(level)}
testClassId="access-management-dropdown"
/>
</div>
</div>
@@ -179,7 +181,8 @@ export const InternalUsersAccessManager = ({
size="sm"
className='text-center'
onClick={onCloseModal}
>
testClassId="access-management-public-link-security-field-confirm-edit"
>
{Languages.t('components.public-link-security_field_confirm_edit')}
</Button>
</div>
@@ -209,7 +212,8 @@ const UserAccessLevel = (props: {
? <Button
disabled={loading || props.disabled || user?.id === currentUser?.id}
size="sm"
>
testClassId={`user-access-button-${props.isSearchResultAdd}`}
>
{Languages.t('components.user_picker.modal.result_add.' + props.isSearchResultAdd)}
</Button>
@@ -218,7 +222,8 @@ const UserAccessLevel = (props: {
noRedWhenLevelNone={true}
level={(item && getUserAccessLevel(item, props.userId)) || "none"}
onChange={level => item && update(changeUserAccess(item, props.userId, level === 'remove' ? false : level))}
/>
testClassId="user-access-dropdown"
/>
}
/>
);
@@ -74,11 +74,13 @@ export const UploadModal = ({
icon={<DocumentDownloadIcon className="w-5 h-5" />}
text={Languages.t('components.create_modal.upload_files')}
onClick={() => selectFromDevice()}
testClassId="upload-file-from-device"
/>
<CreateModalOption
icon={<FolderDownloadIcon className="w-5 h-5" />}
text={Languages.t('components.create_modal.upload_folders')}
onClick={() => selectFolderFromDevice()}
testClassId="upload-folder-from-device"
/>
</div>
</Transition>
@@ -88,11 +90,11 @@ export const UploadModal = ({
);
};
const CreateModalOption = (props: { icon: ReactNode; text: string; onClick: () => void }) => {
const CreateModalOption = (props: { icon: ReactNode; text: string; onClick: () => void; testClassId?: string; }) => {
return (
<div
onClick={props.onClick}
className="flex flex-row p-4 dark:bg-zinc-900 dark:text-white bg-zinc-100 hover:bg-opacity-75 cursor-pointer rounded-md m-2"
className={`flex flex-row p-4 dark:bg-zinc-900 dark:text-white bg-zinc-100 hover:bg-opacity-75 cursor-pointer rounded-md m-2 testid:${props.testClassId}`}
>
<div className="flex items-center justify-center">{props.icon}</div>
<div className="grow flex items-center ml-2">
@@ -64,6 +64,7 @@ const VersionModalContent = ({ id }: { id: string }) => {
await uploadVersion(file);
await refresh(id);
}}
testClassId="version-modal-upload-zone"
>
{access !== 'read' && (
<div
@@ -80,6 +81,7 @@ const VersionModalContent = ({ id }: { id: string }) => {
theme="primary"
onClick={() => uploadZoneRef.current?.open()}
loading={loading}
testClassId="button-create-version"
>
{Languages.t('components.VersionModalContent_create')}
</Button>
@@ -113,6 +115,7 @@ const VersionModalContent = ({ id }: { id: string }) => {
<Button
theme="outline"
onClick={() => download(id, item.av_status === 'malicious', version.id)}
testClassId="button-download-version"
>
{Languages.t('components.VersionModalContent_donwload')}
</Button>
@@ -44,6 +44,7 @@ export default (): JSX.Element => {
readOnly
placeholder={Languages.t('scenes.client.main_view.main_header.search_input')}
onClick={() => setOpen()}
testClassId="header-search-input"
/>
)}
/>
@@ -33,6 +33,7 @@ export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentI
<UploadZone
overClassName={'!hidden'}
className="hidden"
testClassId="create-modal-upload-file-zone"
disableClick
parent={''}
multiple={true}
@@ -51,6 +52,7 @@ export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentI
<UploadZone
overClassName={'!hidden'}
className="hidden"
testClassId="create-modal-upload-folder-zone"
disableClick
parent={''}
multiple={true}
@@ -135,6 +137,7 @@ export default () => {
theme="danger"
className="w-full mb-2 justify-center"
disabled={!(trashChildren.length > 0)}
testClassId="create-modal-in-trash-button-empty-trash"
>
<TruckIcon className="w-5 h-5 mr-2" /> { Languages.t('components.side_menu.buttons.empty_trash') }
</Button>
@@ -158,6 +161,7 @@ export default () => {
parentId,
});
}}
testClassId="sidebar-action-upload-zone"
/>
<Button
@@ -166,6 +170,7 @@ export default () => {
theme="primary"
className="w-full mb-2 justify-center"
style={{ boxShadow: '0 0 10px 0 rgba(0, 122, 255, 0.5)' }}
testClassId="sidebar-action-button-empty-trash"
>
<UploadIcon className="w-5 h-5 mr-2" /> {Languages.t('components.side_menu.buttons.upload')}
</Button>
@@ -174,6 +179,7 @@ export default () => {
size="lg"
theme="secondary"
className="w-full mb-2 justify-center"
testClassId="sidebar-action-button-open-modal"
>
<PlusIcon className="w-5 h-5 mr-2" /> {Languages.t('components.side_menu.buttons.create')}
</Button>
@@ -161,6 +161,7 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
size="lg"
icon={ArrowLeftIcon}
onClick={ handleSwitchLeft }
testClassId="drive-preview-button-switch-left"
/>
<Button
iconSize="lg"
@@ -169,6 +170,7 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
size="lg"
icon={ArrowRightIcon}
onClick={ handleSwitchRight }
testClassId="drive-preview-button-switch-right"
/>
</>
}
@@ -181,6 +183,7 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
onClick= {() => {
download && (window.location.href = download);
}}
testClassId="drive-preview-button-download"
/>
</div>
</div>
@@ -12,6 +12,7 @@ export default () => {
size="lg"
icon={ZoomOutIcon}
onClick={() => getImageControls().zoomOut()}
testClassId="image-control-button-zoom-out"
/>
<Button
iconSize="lg"
@@ -20,6 +21,7 @@ export default () => {
size="lg"
icon={ZoomInIcon}
onClick={() => getImageControls().zoomIn()}
testClassId="image-control-button-zoom-in"
/>
<Button
iconSize="lg"
@@ -28,6 +30,7 @@ export default () => {
size="lg"
icon={RotateCwIcon}
onClick={() => getImageControls().rotateCw()}
testClassId="image-control-button-rotate"
/>
</>
);
@@ -43,6 +43,7 @@ export default () => {
);
}
}}
testClassId="other-control-button-edit"
>
{Languages.t('scenes.apps.drive.viewer.edit_with_button', [
candidates[0].app?.identity.name,
@@ -74,6 +75,7 @@ export default () => {
{ margin: 0 },
);
}}
testClassId="other-control-button-open"
>
{Languages.t('scenes.apps.drive.viewer.open_with_button')}
</Button>
@@ -124,6 +124,7 @@ export default (): JSX.Element => {
loading={busy}
type="primary"
onClick={onCreateCompanyBtnClick}
className="testid:button-create-company"
>
{Languages.t('scenes.join.create_the_company_button')}
</Button>
@@ -163,6 +164,7 @@ export default (): JSX.Element => {
loading={busy}
type="primary"
onClick={onJoinAccountBtnClick}
className="testid:button-login-first"
>
{Languages.t('scenes.join.login_first_button')}
</Button>
@@ -172,6 +174,7 @@ export default (): JSX.Element => {
loading={busy}
type="primary"
onClick={onJoinAccountBtnClick}
className="testid:button-join-team"
>
{Languages.t('scenes.join.join_the_team_button')}
</Button>
@@ -80,6 +80,7 @@ export default class LoginView extends Component {
}
}}
onChange={evt => this.setState({ form_login: evt.target.value })}
testClassId="login-input-username"
/>
<Input
@@ -100,6 +101,7 @@ export default class LoginView extends Component {
}
}}
onChange={evt => this.setState({ form_password: evt.target.value })}
testClassId="login-input-password"
/>
{this.state.login.login_error && (
@@ -121,6 +123,7 @@ export default class LoginView extends Component {
remember_me: true,
})
}
testClassId="login-button-submit"
>
{this.state.i18n.t('scenes.login.home.login_btn')}
</Button>
@@ -63,12 +63,12 @@ export default class Signin extends Component {
</div>
{this.state.login.error_subscribe_username && this.state.username && (
<div className={'error text bottom-margin'}>
<div className={'error text bottom-margin testid:signin-username-exist-text'}>
{this.state.i18n.t('scenes.login.create_account.username_already_exist')}
</div>
)}
{this.state.username && this.state.errorUsername && (
<div className={'text error bottom-margin'}>
<div className={'text error bottom-margin testid:signin-fill-in-username'}>
{this.state.i18n.t('scenes.login.create_account.fill_in_username', [])}
</div>
)}
@@ -91,6 +91,7 @@ export default class Signin extends Component {
)}
value={this.state.firstName}
onChange={evt => this.setState({ firstName: evt.target.value })}
testClassId="signin-input-first-name"
/>
<Input
id="last_name_create"
@@ -107,18 +108,19 @@ export default class Signin extends Component {
)}
value={this.state.lastName}
onChange={evt => this.setState({ lastName: evt.target.value })}
testClassId="signin-input-last-name"
/>
<br />
<br />
{this.state.login.error_subscribe_mailalreadyused && this.state.email && (
<div className="text error bottom-margin">
<div className="text error bottom-margin testid:signin-email-used-text">
{this.state.i18n.t('scenes.login.create_account.email_used')}
</div>
)}
{this.state.errorMail && (
<div className={'text error bottom-margin'}>
<div className={'text error bottom-margin testid:signin-fill-in-email-text'}>
{this.state.i18n.t('scenes.login.create_account.fill_in_email')}
</div>
)}
@@ -137,6 +139,7 @@ export default class Signin extends Component {
placeholder={this.state.i18n.t('scenes.login.create_account.email')}
value={this.state.email}
onChange={evt => this.setState({ email: evt.target.value })}
testClassId="signin-input-email"
/>
{this.state.errorPassword && (
@@ -159,6 +162,7 @@ export default class Signin extends Component {
placeholder={this.state.i18n.t('scenes.login.create_account.password')}
value={this.state.password}
onChange={evt => this.setState({ password: evt.target.value })}
testClassId="signin-input-password"
/>
<div className="bottom">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
@@ -173,6 +177,7 @@ export default class Signin extends Component {
value={this.state.i18n.t('general.continue')}
loading={this.state.login.login_loading}
loadingTimeout={2000}
testClassId="signin-button-continue"
/>
</div>
</div>
@@ -207,6 +212,7 @@ export default class Signin extends Component {
value={'Send again'}
loading={this.state.login.login_loading}
loadingTimeout={2000}
testClassId="signin-button-subscribe-mail"
/>
</div>
);