Wrote end-to-end cypress tests
This commit is contained in:
@@ -25,7 +25,7 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, mergeFilte
|
||||
const areFiltersCleared = !searchTerm && userIds.length === 0 && !myOnly && !recent;
|
||||
|
||||
return (
|
||||
<Filters>
|
||||
<Filters data-testid="board-filters">
|
||||
<SearchInput
|
||||
icon="search"
|
||||
value={searchTerm}
|
||||
|
||||
@@ -25,6 +25,7 @@ const ProjectBoardIssueDetailsAssigneesReporter = ({ issue, updateIssue, project
|
||||
variant="empty"
|
||||
dropdownWidth={343}
|
||||
placeholder="Unassigned"
|
||||
name="assignees"
|
||||
value={issue.userIds}
|
||||
options={userOptions}
|
||||
onChange={userIds => {
|
||||
@@ -41,6 +42,7 @@ const ProjectBoardIssueDetailsAssigneesReporter = ({ issue, updateIssue, project
|
||||
variant="empty"
|
||||
dropdownWidth={343}
|
||||
withClearValue={false}
|
||||
name="reporter"
|
||||
value={issue.reporterId}
|
||||
options={userOptions}
|
||||
onChange={userId => updateIssue({ reporterId: userId })}
|
||||
|
||||
@@ -50,7 +50,7 @@ const ProjectBoardIssueDetailsComment = ({ comment, fetchIssue }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Comment>
|
||||
<Comment data-testid="issue-comment">
|
||||
<UserAvatar name={comment.user.name} avatarUrl={comment.user.avatarUrl} />
|
||||
<Content>
|
||||
<Username>{comment.user.name}</Username>
|
||||
@@ -71,7 +71,7 @@ const ProjectBoardIssueDetailsComment = ({ comment, fetchIssue }) => {
|
||||
<ConfirmModal
|
||||
title="Are you sure you want to delete this comment?"
|
||||
message="Once you delete, it's gone for good."
|
||||
confirmText="Delete Comment"
|
||||
confirmText="Delete comment"
|
||||
onConfirm={handleCommentDelete}
|
||||
renderLink={modal => <DeleteLink onClick={modal.open}>Delete</DeleteLink>}
|
||||
/>
|
||||
|
||||
@@ -28,6 +28,7 @@ const ProjectBoardIssueDetailsEstimateTracking = ({ issue, updateIssue }) => (
|
||||
|
||||
<SectionTitle>Time Tracking</SectionTitle>
|
||||
<Modal
|
||||
testid="modal:tracking"
|
||||
width={400}
|
||||
renderLink={modal => (
|
||||
<TrackingLink onClick={modal.open}>
|
||||
|
||||
@@ -19,6 +19,7 @@ const ProjectBoardIssueDetailsPriority = ({ issue, updateIssue }) => (
|
||||
variant="empty"
|
||||
withClearValue={false}
|
||||
dropdownWidth={343}
|
||||
name="priority"
|
||||
value={issue.priority}
|
||||
options={Object.values(IssuePriority).map(priority => ({
|
||||
value: priority,
|
||||
|
||||
@@ -19,6 +19,7 @@ const ProjectBoardIssueDetailsStatus = ({ issue, updateIssue }) => (
|
||||
variant="empty"
|
||||
dropdownWidth={343}
|
||||
withClearValue={false}
|
||||
name="status"
|
||||
value={issue.status}
|
||||
options={Object.values(IssueStatus).map(status => ({
|
||||
value: status,
|
||||
|
||||
@@ -16,6 +16,7 @@ const ProjectBoardIssueDetailsType = ({ issue, updateIssue }) => (
|
||||
variant="empty"
|
||||
dropdownWidth={150}
|
||||
withClearValue={false}
|
||||
name="type"
|
||||
value={issue.type}
|
||||
options={Object.values(IssueType).map(type => ({
|
||||
value: type,
|
||||
|
||||
@@ -24,6 +24,7 @@ const ProjectBoardListIssue = ({ projectUsers, issue, index }) => {
|
||||
<IssueLink
|
||||
to={`${match.url}/issues/${issue.id}`}
|
||||
ref={provided.innerRef}
|
||||
data-testid="list-issue"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
|
||||
@@ -31,7 +31,11 @@ const ProjectBoardList = ({ status, project, filters }) => {
|
||||
{`${IssueStatusCopy[status]} `}
|
||||
<IssuesCount>{formatIssuesCount(allListIssues, filteredListIssues)}</IssuesCount>
|
||||
</Title>
|
||||
<Issues {...provided.droppableProps} ref={provided.innerRef}>
|
||||
<Issues
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
data-testid={`board-list:${status}`}
|
||||
>
|
||||
{filteredListIssues.map((issue, index) => (
|
||||
<Issue key={issue.id} projectUsers={project.users} issue={issue} index={index} />
|
||||
))}
|
||||
|
||||
@@ -49,6 +49,7 @@ const ProjectBoard = ({ project, fetchProject, updateLocalProjectIssues }) => {
|
||||
render={routeProps => (
|
||||
<Modal
|
||||
isOpen
|
||||
testid="modal:issue-details"
|
||||
width={1040}
|
||||
withCloseIcon={false}
|
||||
onClose={() => history.push(match.url)}
|
||||
|
||||
@@ -30,7 +30,7 @@ const propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const ProjectIssueCreateForm = ({ project, fetchProject, onCreate, modalClose }) => {
|
||||
const ProjectIssueCreate = ({ project, fetchProject, onCreate, modalClose }) => {
|
||||
const [{ isCreating }, createIssue] = useApi.post('/issues');
|
||||
|
||||
const { currentUserId } = useCurrentUser();
|
||||
@@ -168,6 +168,6 @@ const renderUser = project => ({ value: userId, removeOptionValue }) => {
|
||||
);
|
||||
};
|
||||
|
||||
ProjectIssueCreateForm.propTypes = propTypes;
|
||||
ProjectIssueCreate.propTypes = propTypes;
|
||||
|
||||
export default ProjectIssueCreateForm;
|
||||
export default ProjectIssueCreate;
|
||||
@@ -9,10 +9,11 @@ export const FormCont = styled.div`
|
||||
`;
|
||||
|
||||
export const FormElement = styled(Form.Element)`
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
`;
|
||||
|
||||
export const FormHeading = styled.div`
|
||||
export const FormHeading = styled.h1`
|
||||
padding: 6px 0 15px;
|
||||
${font.size(24)}
|
||||
${font.medium}
|
||||
|
||||
@@ -10,7 +10,7 @@ import NavbarLeft from './NavbarLeft';
|
||||
import Sidebar from './Sidebar';
|
||||
import Board from './Board';
|
||||
import IssueSearch from './IssueSearch';
|
||||
import IssueCreateForm from './IssueCreateForm';
|
||||
import IssueCreate from './IssueCreate';
|
||||
import ProjectSettings from './ProjectSettings';
|
||||
import { ProjectPage } from './Styles';
|
||||
|
||||
@@ -49,6 +49,7 @@ const Project = () => {
|
||||
{issueSearchModalHelpers.isOpen() && (
|
||||
<Modal
|
||||
isOpen
|
||||
testid="modal:issue-search"
|
||||
variant="aside"
|
||||
width={600}
|
||||
onClose={issueSearchModalHelpers.close}
|
||||
@@ -59,11 +60,12 @@ const Project = () => {
|
||||
{issueCreateModalHelpers.isOpen() && (
|
||||
<Modal
|
||||
isOpen
|
||||
testid="modal:issue-create"
|
||||
width={800}
|
||||
withCloseIcon={false}
|
||||
onClose={issueCreateModalHelpers.close}
|
||||
renderContent={modal => (
|
||||
<IssueCreateForm
|
||||
<IssueCreate
|
||||
project={project}
|
||||
fetchProject={fetchProject}
|
||||
onCreate={() => history.push(`${match.url}/board`)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import App from 'App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
// QUERY component cache-only doesn't work until first req finishes, look at currentUser on page load
|
||||
// APP IS NOT RESPONSIVE - REDUCE BROWSER HEIGHT, ISSUES DONT SCROLL
|
||||
// TODO: UPDATE FORMIK TO FIX SETFIELDVALUE TO EMPTY ARRAY ISSUE https://github.com/jaredpalmer/formik/pull/2144
|
||||
// REFACTOR HTML TO USE SEMANTIC ELEMENTS
|
||||
|
||||
@@ -18,12 +18,19 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
const Avatar = ({ className, avatarUrl, name, size, ...otherProps }) => {
|
||||
const sharedProps = {
|
||||
className,
|
||||
size,
|
||||
'data-testid': name ? `avatar:${name}` : 'avatar',
|
||||
...otherProps,
|
||||
};
|
||||
|
||||
if (avatarUrl) {
|
||||
return <Image className={className} size={size} avatarUrl={avatarUrl} {...otherProps} />;
|
||||
return <Image avatarUrl={avatarUrl} {...sharedProps} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Letter className={className} size={size} color={getColorFromName(name)} {...otherProps}>
|
||||
<Letter color={getColorFromName(name)} {...sharedProps}>
|
||||
<span>{name.charAt(0)}</span>
|
||||
</Letter>
|
||||
);
|
||||
|
||||
@@ -47,8 +47,9 @@ const ConfirmModal = ({
|
||||
|
||||
return (
|
||||
<StyledConfirmModal
|
||||
withCloseIcon={false}
|
||||
className={className}
|
||||
testid="modal:confirm"
|
||||
withCloseIcon={false}
|
||||
renderLink={renderLink}
|
||||
renderContent={modal => (
|
||||
<>
|
||||
|
||||
@@ -15,23 +15,29 @@ const propTypes = {
|
||||
label: PropTypes.string,
|
||||
tip: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
className: undefined,
|
||||
label: null,
|
||||
tip: null,
|
||||
error: null,
|
||||
label: undefined,
|
||||
tip: undefined,
|
||||
error: undefined,
|
||||
name: undefined,
|
||||
};
|
||||
|
||||
const generateField = FormComponent => {
|
||||
const FieldComponent = ({ className, label, tip, error, ...otherProps }) => {
|
||||
const FieldComponent = ({ className, label, tip, error, name, ...otherProps }) => {
|
||||
const fieldId = uniqueId('form-field-');
|
||||
|
||||
return (
|
||||
<StyledField className={className} hasLabel={!!label}>
|
||||
<StyledField
|
||||
className={className}
|
||||
hasLabel={!!label}
|
||||
data-testid={name ? `form-field:${name}` : 'form-field'}
|
||||
>
|
||||
{label && <FieldLabel htmlFor={fieldId}>{label}</FieldLabel>}
|
||||
<FormComponent id={fieldId} invalid={!!error} {...otherProps} />
|
||||
<FormComponent id={fieldId} invalid={!!error} name={name} {...otherProps} />
|
||||
{tip && <FieldTip>{tip}</FieldTip>}
|
||||
{error && <FieldError>{error}</FieldError>}
|
||||
</StyledField>
|
||||
|
||||
@@ -53,7 +53,9 @@ const defaultProps = {
|
||||
top: 0,
|
||||
};
|
||||
|
||||
const Icon = ({ type, ...iconProps }) => <StyledIcon {...iconProps} code={fontIconCodes[type]} />;
|
||||
const Icon = ({ type, ...iconProps }) => (
|
||||
<StyledIcon {...iconProps} data-testid={`icon:${type}`} code={fontIconCodes[type]} />
|
||||
);
|
||||
|
||||
Icon.propTypes = propTypes;
|
||||
Icon.defaultProps = defaultProps;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ScrollOverlay, ClickableOverlay, StyledModal, CloseIcon } from './Style
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
testid: PropTypes.string,
|
||||
variant: PropTypes.oneOf(['center', 'aside']),
|
||||
width: PropTypes.number,
|
||||
withCloseIcon: PropTypes.bool,
|
||||
@@ -20,6 +21,7 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
className: undefined,
|
||||
testid: 'modal',
|
||||
variant: 'center',
|
||||
width: 600,
|
||||
withCloseIcon: true,
|
||||
@@ -30,6 +32,7 @@ const defaultProps = {
|
||||
|
||||
const Modal = ({
|
||||
className,
|
||||
testid,
|
||||
variant,
|
||||
width,
|
||||
withCloseIcon,
|
||||
@@ -63,9 +66,16 @@ const Modal = ({
|
||||
|
||||
{isOpen &&
|
||||
ReactDOM.createPortal(
|
||||
<ScrollOverlay data-jira-modal="true">
|
||||
<ScrollOverlay>
|
||||
<ClickableOverlay variant={variant} ref={$clickableOverlayRef}>
|
||||
<StyledModal className={className} variant={variant} width={width} ref={$modalRef}>
|
||||
<StyledModal
|
||||
className={className}
|
||||
variant={variant}
|
||||
width={width}
|
||||
data-jira-modal="true"
|
||||
data-testid={testid}
|
||||
ref={$modalRef}
|
||||
>
|
||||
{withCloseIcon && <CloseIcon type="close" variant={variant} onClick={closeModal} />}
|
||||
{renderContent({ close: closeModal })}
|
||||
</StyledModal>
|
||||
|
||||
@@ -192,6 +192,7 @@ const SelectDropdown = ({
|
||||
<Option
|
||||
key={option.value}
|
||||
data-select-option-value={option.value}
|
||||
data-testid={`select-option:${option.label}`}
|
||||
onMouseEnter={handleOptionMouseEnter}
|
||||
onClick={() => selectOptionValue(option.value)}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,10 @@ export const StyledSelect = styled.div`
|
||||
width: 100%;
|
||||
border: 1px solid ${color.borderLightest};
|
||||
background: ${color.backgroundLightest};
|
||||
transition: background 0.1s;
|
||||
&:hover {
|
||||
background: ${color.backgroundLight};
|
||||
}
|
||||
`}
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -20,6 +20,7 @@ const propTypes = {
|
||||
className: PropTypes.string,
|
||||
variant: PropTypes.oneOf(['normal', 'empty']),
|
||||
dropdownWidth: PropTypes.number,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
|
||||
defaultValue: PropTypes.any,
|
||||
placeholder: PropTypes.string,
|
||||
@@ -37,6 +38,7 @@ const defaultProps = {
|
||||
className: undefined,
|
||||
variant: 'normal',
|
||||
dropdownWidth: undefined,
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
defaultValue: undefined,
|
||||
placeholder: 'Select',
|
||||
@@ -52,6 +54,7 @@ const Select = ({
|
||||
className,
|
||||
variant,
|
||||
dropdownWidth,
|
||||
name,
|
||||
value: propsValue,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
@@ -140,7 +143,11 @@ const Select = ({
|
||||
onKeyDown={handleFocusedSelectKeydown}
|
||||
invalid={invalid}
|
||||
>
|
||||
<ValueContainer variant={variant} onClick={activateDropdown}>
|
||||
<ValueContainer
|
||||
variant={variant}
|
||||
data-testid={name ? `select:${name}` : 'select'}
|
||||
onClick={activateDropdown}
|
||||
>
|
||||
{isValueEmpty && <Placeholder>{placeholder}</Placeholder>}
|
||||
|
||||
{!isValueEmpty && !isMulti && propsRenderValue
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
export const KeyCodes = {
|
||||
ESCAPE: 27,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
ESCAPE: 27,
|
||||
SPACE: 32,
|
||||
ARROW_LEFT: 37,
|
||||
ARROW_UP: 38,
|
||||
ARROW_RIGHT: 39,
|
||||
ARROW_DOWN: 40,
|
||||
M: 77,
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ const useQuery = (url, propsVariables = {}, options = {}) => {
|
||||
data: null,
|
||||
error: null,
|
||||
isLoading: !lazy,
|
||||
wasCalled: !lazy,
|
||||
variables: {},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import useDeepCompareMemoize from 'shared/hooks/deepCompareMemoize';
|
||||
|
||||
const useOnOutsideClick = (
|
||||
$ignoredElementRefs,
|
||||
shouldListen,
|
||||
isListening,
|
||||
onOutsideClick,
|
||||
$listeningElementRef,
|
||||
) => {
|
||||
@@ -17,19 +17,19 @@ const useOnOutsideClick = (
|
||||
};
|
||||
|
||||
const handleMouseUp = event => {
|
||||
const isAnyIgnoredElementParentOfTarget = $ignoredElementRefsMemoized.some(
|
||||
const isAnyIgnoredElementAncestorOfTarget = $ignoredElementRefsMemoized.some(
|
||||
$elementRef =>
|
||||
$elementRef.current.contains($mouseDownTargetRef.current) ||
|
||||
$elementRef.current.contains(event.target),
|
||||
);
|
||||
if (event.button === 0 && !isAnyIgnoredElementParentOfTarget) {
|
||||
if (event.button === 0 && !isAnyIgnoredElementAncestorOfTarget) {
|
||||
onOutsideClick();
|
||||
}
|
||||
};
|
||||
|
||||
const $listeningElement = ($listeningElementRef || {}).current || document;
|
||||
|
||||
if (shouldListen) {
|
||||
if (isListening) {
|
||||
$listeningElement.addEventListener('mousedown', handleMouseDown);
|
||||
$listeningElement.addEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ const useOnOutsideClick = (
|
||||
$listeningElement.removeEventListener('mousedown', handleMouseDown);
|
||||
$listeningElement.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
}, [$ignoredElementRefsMemoized, $listeningElementRef, shouldListen, onOutsideClick]);
|
||||
}, [$ignoredElementRefsMemoized, $listeningElementRef, isListening, onOutsideClick]);
|
||||
};
|
||||
|
||||
export default useOnOutsideClick;
|
||||
|
||||
Reference in New Issue
Block a user