refactor: add markdown visual editor source (#1088)
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
.env
|
||||
coverage
|
||||
node_modules
|
||||
ext.json
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "es5",
|
||||
"jsxSingleQuote": false
|
||||
}
|
||||
@@ -6,18 +6,8 @@
|
||||
"Standard Notes",
|
||||
"Standard Notes Extensions"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/standardnotes/advanced-checklist.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/standardnotes/advanced-checklist/issues"
|
||||
},
|
||||
"sn": {
|
||||
"main": "build/index.html"
|
||||
},
|
||||
@@ -39,7 +29,7 @@
|
||||
"server-public": "http-server -p 3000 --cors",
|
||||
"server-root": "http-server ./ -p 3000 --cors",
|
||||
"server": "http-server ./build -p 3000 --cors",
|
||||
"pretty": "prettier --write 'src/**/*.{html,css,scss,js,jsx,ts,tsx,json}' README.md",
|
||||
"lint": "prettier --write 'src/**/*.{html,css,scss,js,jsx,ts,tsx,json}' README.md",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -5,10 +5,7 @@ import type { RootState, AppDispatch } from './store'
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>()
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
|
||||
export const useDidMount = (
|
||||
effect: React.EffectCallback,
|
||||
deps?: React.DependencyList
|
||||
) => {
|
||||
export const useDidMount = (effect: React.EffectCallback, deps?: React.DependencyList) => {
|
||||
const [didMount, setDidMount] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -32,7 +32,7 @@ const actionsWithGroup = isAnyOf(
|
||||
tasksGroupAdded,
|
||||
tasksGroupDeleted,
|
||||
tasksGroupMerged,
|
||||
tasksGroupCollapsed
|
||||
tasksGroupCollapsed,
|
||||
)
|
||||
|
||||
listenerMiddleware.startListening({
|
||||
|
||||
@@ -9,8 +9,7 @@ export const store = configureStore({
|
||||
tasks: tasksReducer,
|
||||
settings: settingsReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().prepend(listenerMiddleware),
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware),
|
||||
})
|
||||
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
|
||||
@@ -9,12 +9,8 @@ type CheckBoxInputProps = {
|
||||
|
||||
export const CheckBoxInput = forwardRef<HTMLInputElement, CheckBoxInputProps>(
|
||||
({ checked, disabled, testId, onChange }, ref) => {
|
||||
function onCheckBoxButtonClick({
|
||||
currentTarget,
|
||||
}: React.MouseEvent<SVGElement>) {
|
||||
!checked
|
||||
? currentTarget.classList.add('explode')
|
||||
: currentTarget.classList.remove('explode')
|
||||
function onCheckBoxButtonClick({ currentTarget }: React.MouseEvent<SVGElement>) {
|
||||
!checked ? currentTarget.classList.add('explode') : currentTarget.classList.remove('explode')
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -41,5 +37,5 @@ export const CheckBoxInput = forwardRef<HTMLInputElement, CheckBoxInputProps>(
|
||||
</svg>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -17,10 +17,7 @@ type CircularProgressBarProps = {
|
||||
percentage: number
|
||||
}
|
||||
|
||||
export const CircularProgressBar: React.FC<CircularProgressBarProps> = ({
|
||||
size,
|
||||
percentage,
|
||||
}) => {
|
||||
export const CircularProgressBar: React.FC<CircularProgressBarProps> = ({ size, percentage }) => {
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,18 +31,8 @@ export const CircularProgressBar: React.FC<CircularProgressBarProps> = ({
|
||||
const dash = (progress * circumference) / 100
|
||||
|
||||
return (
|
||||
<svg
|
||||
height={size}
|
||||
viewBox={viewBox}
|
||||
width={size}
|
||||
data-testid="circular-progress-bar"
|
||||
>
|
||||
<ProgressBarBackground
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<svg height={size} viewBox={viewBox} width={size} data-testid="circular-progress-bar">
|
||||
<ProgressBarBackground cx={size / 2} cy={size / 2} r={radius} strokeWidth={strokeWidth} />
|
||||
<ProgressBarStroke
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import '@reach/dialog/styles.css'
|
||||
|
||||
import React, { useRef } from 'react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogLabel,
|
||||
AlertDialogDescription,
|
||||
} from '@reach/alert-dialog'
|
||||
import { AlertDialog, AlertDialogLabel, AlertDialogDescription } from '@reach/alert-dialog'
|
||||
|
||||
import { sanitizeHtmlString } from '@standardnotes/utils'
|
||||
|
||||
@@ -32,11 +28,7 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
||||
const cancelRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
data-testid={testId}
|
||||
onDismiss={cancelButtonCb}
|
||||
leastDestructiveRef={cancelRef}
|
||||
>
|
||||
<AlertDialog data-testid={testId} onDismiss={cancelButtonCb} leastDestructiveRef={cancelRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
|
||||
@@ -17,11 +17,7 @@ type MainTitleProps = {
|
||||
crossed?: boolean
|
||||
}
|
||||
|
||||
export const MainTitle: React.FC<MainTitleProps> = ({
|
||||
children,
|
||||
highlight = false,
|
||||
crossed = false,
|
||||
}) => {
|
||||
export const MainTitle: React.FC<MainTitleProps> = ({ children, highlight = false, crossed = false }) => {
|
||||
return (
|
||||
<Header1 className={`sk-h1 ${highlight ? 'info' : ''}`} crossed={crossed}>
|
||||
{children}
|
||||
|
||||
@@ -3,11 +3,7 @@ type RoundButtonProps = {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const RoundButton: React.FC<RoundButtonProps> = ({
|
||||
testId,
|
||||
onClick,
|
||||
children,
|
||||
}) => {
|
||||
export const RoundButton: React.FC<RoundButtonProps> = ({ testId, onClick, children }) => {
|
||||
return (
|
||||
<button data-testid={testId} className="sn-icon-button" onClick={onClick}>
|
||||
{children}
|
||||
|
||||
@@ -26,24 +26,8 @@ type TextAreaInputProps = {
|
||||
onKeyUp?: (event: KeyboardEvent<HTMLTextAreaElement>) => void
|
||||
}
|
||||
|
||||
export const TextAreaInput = forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
TextAreaInputProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
value,
|
||||
className,
|
||||
dir = 'auto',
|
||||
disabled,
|
||||
spellCheck,
|
||||
testId,
|
||||
onChange,
|
||||
onKeyPress,
|
||||
onKeyUp,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
export const TextAreaInput = forwardRef<HTMLTextAreaElement, TextAreaInputProps>(
|
||||
({ value, className, dir = 'auto', disabled, spellCheck, testId, onChange, onKeyPress, onKeyUp }, ref) => {
|
||||
return (
|
||||
<StyledTextArea
|
||||
className={className}
|
||||
@@ -58,5 +42,5 @@ export const TextAreaInput = forwardRef<
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -11,8 +11,7 @@ const StyledInput = styled.input<StyledInputProps>`
|
||||
background-color: unset;
|
||||
border: none;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
font-size: ${({ textSize }) =>
|
||||
textSize === 'big' ? '1.125rem' : 'var(--sn-stylekit-font-size-h3)'};
|
||||
font-size: ${({ textSize }) => (textSize === 'big' ? '1.125rem' : 'var(--sn-stylekit-font-size-h3)')};
|
||||
font-weight: ${({ textSize }) => (textSize === 'big' ? '500' : '400')};
|
||||
height: auto;
|
||||
margin: 6px 0 6px 0;
|
||||
@@ -35,14 +34,7 @@ type TextInputProps = {
|
||||
autoFocus?: boolean
|
||||
dir?: 'ltr' | 'rtl' | 'auto'
|
||||
disabled?: boolean
|
||||
enterKeyHint?:
|
||||
| 'enter'
|
||||
| 'done'
|
||||
| 'go'
|
||||
| 'next'
|
||||
| 'previous'
|
||||
| 'search'
|
||||
| 'send'
|
||||
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
|
||||
placeholder?: string
|
||||
spellCheck?: boolean
|
||||
testId?: string
|
||||
@@ -68,7 +60,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
onChange,
|
||||
onKeyPress,
|
||||
},
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<StyledInput
|
||||
@@ -88,5 +80,5 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const ChevronDownIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="sn-icon block"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
|
||||
<path d="M6.17622 7.15015L10.0012 10.9751L13.8262 7.15015L15.0012 8.33348L10.0012 13.3335L5.00122 8.33348L6.17622 7.15015Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const ChevronUpIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="sn-icon block"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
|
||||
<path d="M13.826 13.3335L10.001 9.5085L6.17597 13.3335L5.00097 12.1502L10.001 7.15017L15.001 12.1502L13.826 13.3335Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -6,14 +6,7 @@ export const DottedCircleIcon = () => {
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="19"
|
||||
height="19"
|
||||
rx="9.5"
|
||||
strokeDasharray="2 2"
|
||||
/>
|
||||
<rect x="0.5" y="0.5" width="19" height="19" rx="9.5" strokeDasharray="2 2" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const MergeIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="sn-icon block"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
|
||||
<path d="M8 17L12 13H15.2C15.6 14.2 16.7 15 18 15C19.7 15 21 13.7 21 12C21 10.3 19.7 9 18 9C16.7 9 15.6 9.8 15.2 11H12L8 7V3H3V8H6L10.2 12L6 16H3V21H8V17Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const RenameIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="sn-icon block"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
|
||||
<path d="M11.7167 7.5L12.5 8.28333L4.93333 15.8333H4.16667V15.0667L11.7167 7.5ZM14.7167 2.5C14.5083 2.5 14.2917 2.58333 14.1333 2.74167L12.6083 4.26667L15.7333 7.39167L17.2583 5.86667C17.5833 5.54167 17.5833 5 17.2583 4.69167L15.3083 2.74167C15.1417 2.575 14.9333 2.5 14.7167 2.5ZM11.7167 5.15833L2.5 14.375V17.5H5.625L14.8417 8.28333L11.7167 5.15833Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -2,9 +2,7 @@ type ReorderIconProps = {
|
||||
highlight?: boolean
|
||||
}
|
||||
|
||||
export const ReorderIcon: React.FC<ReorderIconProps> = ({
|
||||
highlight = false,
|
||||
}) => {
|
||||
export const ReorderIcon: React.FC<ReorderIconProps> = ({ highlight = false }) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const TrashIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="sn-icon block"
|
||||
>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="sn-icon block">
|
||||
<path d="M7.49992 2.5V3.33333H3.33325V5H4.16659V15.8333C4.16659 16.2754 4.34218 16.6993 4.65474 17.0118C4.9673 17.3244 5.39122 17.5 5.83325 17.5H14.1666C14.6086 17.5 15.0325 17.3244 15.3451 17.0118C15.6577 16.6993 15.8333 16.2754 15.8333 15.8333V5H16.6666V3.33333H12.4999V2.5H7.49992ZM5.83325 5H14.1666V15.8333H5.83325V5ZM7.49992 6.66667V14.1667H9.16658V6.66667H7.49992ZM10.8333 6.66667V14.1667H12.4999V6.66667H10.8333Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -38,8 +38,7 @@ $transition-duration: 750ms;
|
||||
.checkbox-square,
|
||||
.checkbox-mark {
|
||||
cursor: pointer;
|
||||
transition: stroke-dashoffset $transition-duration
|
||||
cubic-bezier(0.9, 0, 0.5, 1);
|
||||
transition: stroke-dashoffset $transition-duration cubic-bezier(0.9, 0, 0.5, 1);
|
||||
}
|
||||
|
||||
.checkbox-circle {
|
||||
|
||||
@@ -2,10 +2,7 @@ import './CheckBoxElementsDefs.scss'
|
||||
|
||||
export const CheckBoxElementsDefs = () => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 0 0"
|
||||
style={{ position: 'absolute', zIndex: -1, opacity: 0 }}
|
||||
>
|
||||
<svg viewBox="0 0 0 0" style={{ position: 'absolute', zIndex: -1, opacity: 0 }}>
|
||||
<defs>
|
||||
<path
|
||||
id="checkbox-square"
|
||||
|
||||
@@ -229,9 +229,7 @@ describe('getPlainPreview', () => {
|
||||
|
||||
expect(getPlainPreview(groupedTasks)).toBe('2/5 tasks completed')
|
||||
expect(getPlainPreview([])).toBe('0/0 tasks completed')
|
||||
expect(getPlainPreview([{ name: 'Test', tasks: [] }])).toBe(
|
||||
'0/0 tasks completed'
|
||||
)
|
||||
expect(getPlainPreview([{ name: 'Test', tasks: [] }])).toBe('0/0 tasks completed')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { GroupPayload, TaskPayload } from '../features/tasks/tasks-slice'
|
||||
|
||||
export function arrayMoveMutable(
|
||||
array: any[],
|
||||
fromIndex: number,
|
||||
toIndex: number
|
||||
) {
|
||||
export function arrayMoveMutable(array: any[], fromIndex: number, toIndex: number) {
|
||||
const startIndex = fromIndex < 0 ? array.length + fromIndex : fromIndex
|
||||
if (startIndex >= 0 && startIndex < array.length) {
|
||||
const endIndex = toIndex < 0 ? array.length + toIndex : toIndex
|
||||
@@ -14,11 +10,7 @@ export function arrayMoveMutable(
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayMoveImmutable(
|
||||
array: any[],
|
||||
fromIndex: number,
|
||||
toIndex: number
|
||||
) {
|
||||
export function arrayMoveImmutable(array: any[], fromIndex: number, toIndex: number) {
|
||||
array = [...array]
|
||||
arrayMoveMutable(array, fromIndex, toIndex)
|
||||
return array
|
||||
@@ -43,9 +35,7 @@ export function groupTasksByCompletedStatus(tasks: TaskPayload[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTaskArrayFromGroupedTasks(
|
||||
groupedTasks: GroupPayload[]
|
||||
): TaskPayload[] {
|
||||
export function getTaskArrayFromGroupedTasks(groupedTasks: GroupPayload[]): TaskPayload[] {
|
||||
let taskArray: TaskPayload[] = []
|
||||
|
||||
groupedTasks.forEach((group) => {
|
||||
@@ -124,10 +114,7 @@ export function isJsonString(rawString: string) {
|
||||
return true
|
||||
}
|
||||
|
||||
export function isLastActiveGroup(
|
||||
allGroups: GroupPayload[],
|
||||
groupName: string
|
||||
): boolean {
|
||||
export function isLastActiveGroup(allGroups: GroupPayload[], groupName: string): boolean {
|
||||
if (allGroups.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import reducer, {
|
||||
setCanEdit,
|
||||
setIsRunningOnMobile,
|
||||
setSpellCheckerEnabled,
|
||||
} from './settings-slice'
|
||||
import reducer, { setCanEdit, setIsRunningOnMobile, setSpellCheckerEnabled } from './settings-slice'
|
||||
import type { SettingsState } from './settings-slice'
|
||||
|
||||
it('should return the initial state', () => {
|
||||
return expect(
|
||||
reducer(undefined, {
|
||||
type: undefined,
|
||||
})
|
||||
}),
|
||||
).toEqual({
|
||||
canEdit: true,
|
||||
isRunningOnMobile: false,
|
||||
|
||||
@@ -28,6 +28,5 @@ const settingsSlice = createSlice({
|
||||
},
|
||||
})
|
||||
|
||||
export const { setCanEdit, setIsRunningOnMobile, setSpellCheckerEnabled } =
|
||||
settingsSlice.actions
|
||||
export const { setCanEdit, setIsRunningOnMobile, setSpellCheckerEnabled } = settingsSlice.actions
|
||||
export default settingsSlice.reducer
|
||||
|
||||
@@ -10,12 +10,8 @@ const group = 'default group'
|
||||
it('renders two buttons', () => {
|
||||
testRender(<CompletedTasksActions groupName={group} />)
|
||||
|
||||
expect(screen.getByTestId('reopen-completed-button')).toHaveTextContent(
|
||||
'Reopen Completed'
|
||||
)
|
||||
expect(screen.getByTestId('delete-completed-button')).toHaveTextContent(
|
||||
'Delete Completed'
|
||||
)
|
||||
expect(screen.getByTestId('reopen-completed-button')).toHaveTextContent('Reopen Completed')
|
||||
expect(screen.getByTestId('delete-completed-button')).toHaveTextContent('Delete Completed')
|
||||
})
|
||||
|
||||
it('should not render buttons if can not edit', () => {
|
||||
@@ -29,12 +25,8 @@ it('should not render buttons if can not edit', () => {
|
||||
|
||||
testRender(<CompletedTasksActions groupName={group} />, {}, defaultState)
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('reopen-completed-button')
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByTestId('delete-completed-button')
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('reopen-completed-button')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('delete-completed-button')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should dispatch openAllCompleted action', () => {
|
||||
@@ -45,18 +37,14 @@ it('should dispatch openAllCompleted action', () => {
|
||||
|
||||
const confirmDialog = screen.getByTestId('reopen-all-tasks-dialog')
|
||||
expect(confirmDialog).toBeInTheDocument()
|
||||
expect(confirmDialog).toHaveTextContent(
|
||||
`Are you sure you want to reopen completed tasks in the '${group}' group?`
|
||||
)
|
||||
expect(confirmDialog).toHaveTextContent(`Are you sure you want to reopen completed tasks in the '${group}' group?`)
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-dialog-button')
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
const dispatchedActions = mockStore.getActions()
|
||||
expect(dispatchedActions).toHaveLength(1)
|
||||
expect(dispatchedActions[0]).toMatchObject(
|
||||
openAllCompleted({ groupName: group })
|
||||
)
|
||||
expect(dispatchedActions[0]).toMatchObject(openAllCompleted({ groupName: group }))
|
||||
})
|
||||
|
||||
it('should dispatch deleteCompleted action', () => {
|
||||
@@ -67,18 +55,14 @@ it('should dispatch deleteCompleted action', () => {
|
||||
|
||||
const confirmDialog = screen.getByTestId('delete-completed-tasks-dialog')
|
||||
expect(confirmDialog).toBeInTheDocument()
|
||||
expect(confirmDialog).toHaveTextContent(
|
||||
`Are you sure you want to delete completed tasks in the '${group}' group?`
|
||||
)
|
||||
expect(confirmDialog).toHaveTextContent(`Are you sure you want to delete completed tasks in the '${group}' group?`)
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-dialog-button')
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
const dispatchedActions = mockStore.getActions()
|
||||
expect(dispatchedActions).toHaveLength(1)
|
||||
expect(dispatchedActions[0]).toMatchObject(
|
||||
deleteAllCompleted({ groupName: group })
|
||||
)
|
||||
expect(dispatchedActions[0]).toMatchObject(deleteAllCompleted({ groupName: group }))
|
||||
})
|
||||
|
||||
it('should dismiss dialogs', () => {
|
||||
|
||||
@@ -30,9 +30,7 @@ type CompletedTasksActionsProps = {
|
||||
groupName: string
|
||||
}
|
||||
|
||||
const CompletedTasksActions: React.FC<CompletedTasksActionsProps> = ({
|
||||
groupName,
|
||||
}) => {
|
||||
const CompletedTasksActions: React.FC<CompletedTasksActionsProps> = ({ groupName }) => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const canEdit = useAppSelector((state) => state.settings.canEdit)
|
||||
@@ -46,16 +44,10 @@ const CompletedTasksActions: React.FC<CompletedTasksActionsProps> = ({
|
||||
|
||||
return (
|
||||
<div data-testid="completed-tasks-actions">
|
||||
<ActionButton
|
||||
onClick={() => setShowReopenDialog(true)}
|
||||
data-testid="reopen-completed-button"
|
||||
>
|
||||
<ActionButton onClick={() => setShowReopenDialog(true)} data-testid="reopen-completed-button">
|
||||
Reopen Completed
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
data-testid="delete-completed-button"
|
||||
>
|
||||
<ActionButton onClick={() => setShowDeleteDialog(true)} data-testid="delete-completed-button">
|
||||
Delete Completed
|
||||
</ActionButton>
|
||||
{showReopenDialog && (
|
||||
@@ -65,8 +57,7 @@ const CompletedTasksActions: React.FC<CompletedTasksActionsProps> = ({
|
||||
confirmButtonCb={() => dispatch(openAllCompleted({ groupName }))}
|
||||
cancelButtonCb={() => setShowReopenDialog(false)}
|
||||
>
|
||||
Are you sure you want to reopen completed tasks in the '
|
||||
<strong>{groupName}</strong>' group?
|
||||
Are you sure you want to reopen completed tasks in the '<strong>{groupName}</strong>' group?
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
{showDeleteDialog && (
|
||||
@@ -76,8 +67,7 @@ const CompletedTasksActions: React.FC<CompletedTasksActionsProps> = ({
|
||||
confirmButtonCb={() => dispatch(deleteAllCompleted({ groupName }))}
|
||||
cancelButtonCb={() => setShowDeleteDialog(false)}
|
||||
>
|
||||
Are you sure you want to delete completed tasks in the '
|
||||
<strong>{groupName}</strong>' group?
|
||||
Are you sure you want to delete completed tasks in the '<strong>{groupName}</strong>' group?
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
ChangeEvent,
|
||||
createRef,
|
||||
FocusEvent,
|
||||
KeyboardEvent,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ChangeEvent, createRef, FocusEvent, KeyboardEvent, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks'
|
||||
@@ -82,9 +76,7 @@ const CreateGroup: React.FC = () => {
|
||||
const [isCreateMode, setIsCreateMode] = useState(false)
|
||||
|
||||
const canEdit = useAppSelector((state) => state.settings.canEdit)
|
||||
const spellCheckerEnabled = useAppSelector(
|
||||
(state) => state.settings.spellCheckerEnabled
|
||||
)
|
||||
const spellCheckerEnabled = useAppSelector((state) => state.settings.spellCheckerEnabled)
|
||||
const groupedTasks = useAppSelector((state) => state.tasks.groups)
|
||||
const taskGroupCount = groupedTasks.length
|
||||
|
||||
@@ -144,9 +136,7 @@ const CreateGroup: React.FC = () => {
|
||||
<TutorialContainer>
|
||||
<Tutorial>
|
||||
<ArrowVector style={{ marginRight: 140, marginBottom: 12 }} />
|
||||
<TutorialText>
|
||||
Get started by naming your first task group
|
||||
</TutorialText>
|
||||
<TutorialText>Get started by naming your first task group</TutorialText>
|
||||
</Tutorial>
|
||||
<EmptyContainer1 />
|
||||
<EmptyContainer2 />
|
||||
|
||||
@@ -79,6 +79,6 @@ test('pressing enter when input box is not empty, should create a new task', ()
|
||||
taskAdded({
|
||||
task: { id: 'my-fake-uuid', description: 'My awesome task' },
|
||||
groupName: defaultGroup.name,
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -29,9 +29,7 @@ const CreateTask: React.FC<CreateTaskProps> = ({ group }) => {
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const spellCheckerEnabled = useAppSelector(
|
||||
(state) => state.settings.spellCheckerEnabled
|
||||
)
|
||||
const spellCheckerEnabled = useAppSelector((state) => state.settings.spellCheckerEnabled)
|
||||
const canEdit = useAppSelector((state) => state.settings.canEdit)
|
||||
const allGroups = useAppSelector((state) => state.tasks.groups)
|
||||
|
||||
@@ -51,9 +49,7 @@ const CreateTask: React.FC<CreateTaskProps> = ({ group }) => {
|
||||
return
|
||||
}
|
||||
|
||||
dispatch(
|
||||
taskAdded({ task: { id: uuidv4(), description: rawString }, groupName })
|
||||
)
|
||||
dispatch(taskAdded({ task: { id: uuidv4(), description: rawString }, groupName }))
|
||||
setTaskDraft('')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,7 @@ const Container = styled.div`
|
||||
|
||||
const InvalidContentError: React.FC = () => {
|
||||
const lastError = useAppSelector((state) => state.tasks.lastError)
|
||||
return (
|
||||
<Container>
|
||||
{lastError ||
|
||||
'An unknown error has occurred, and the content cannot be displayed.'}
|
||||
</Container>
|
||||
)
|
||||
return <Container>{lastError || 'An unknown error has occurred, and the content cannot be displayed.'}</Container>
|
||||
}
|
||||
|
||||
export default InvalidContentError
|
||||
|
||||
@@ -28,20 +28,12 @@ it('renders the alert dialog when no groups are available to merge', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />, {}, defaultState)
|
||||
|
||||
const alertDialog = screen.getByTestId('merge-task-group-dialog')
|
||||
expect(alertDialog).toBeInTheDocument()
|
||||
expect(alertDialog).toHaveTextContent(
|
||||
`There are no other groups to merge '${defaultGroup}' with.`
|
||||
)
|
||||
expect(alertDialog).not.toHaveTextContent(
|
||||
`Select which group you want to merge '${defaultGroup}' into:`
|
||||
)
|
||||
expect(alertDialog).toHaveTextContent(`There are no other groups to merge '${defaultGroup}' with.`)
|
||||
expect(alertDialog).not.toHaveTextContent(`Select which group you want to merge '${defaultGroup}' into:`)
|
||||
|
||||
// There shouldn't be any radio buttons
|
||||
expect(screen.queryAllByRole('radio')).toHaveLength(0)
|
||||
@@ -90,20 +82,12 @@ it('renders the alert dialog when there are groups available to merge', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />, {}, defaultState)
|
||||
|
||||
const alertDialog = screen.getByTestId('merge-task-group-dialog')
|
||||
expect(alertDialog).toBeInTheDocument()
|
||||
expect(alertDialog).toHaveTextContent(
|
||||
`Select which group you want to merge '${defaultGroup}' into:`
|
||||
)
|
||||
expect(alertDialog).not.toHaveTextContent(
|
||||
`There are no other groups to merge '${defaultGroup}' with.`
|
||||
)
|
||||
expect(alertDialog).toHaveTextContent(`Select which group you want to merge '${defaultGroup}' into:`)
|
||||
expect(alertDialog).not.toHaveTextContent(`There are no other groups to merge '${defaultGroup}' with.`)
|
||||
|
||||
const radioButtons = screen.queryAllByRole('radio')
|
||||
expect(radioButtons).toHaveLength(2)
|
||||
@@ -163,7 +147,7 @@ it('should close the dialog if no group is selected and the Merge button is clic
|
||||
const { mockStore } = testRender(
|
||||
<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
defaultState,
|
||||
)
|
||||
|
||||
const buttons = screen.queryAllByRole('button')
|
||||
@@ -225,7 +209,7 @@ it('should dispatch the action to merge groups', () => {
|
||||
const { mockStore } = testRender(
|
||||
<MergeTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
defaultState,
|
||||
)
|
||||
|
||||
const radioButtons = screen.queryAllByRole('radio')
|
||||
@@ -250,8 +234,6 @@ it('should dispatch the action to merge groups', () => {
|
||||
|
||||
dispatchedActions = mockStore.getActions()
|
||||
expect(dispatchedActions).toHaveLength(1)
|
||||
expect(dispatchedActions[0]).toMatchObject(
|
||||
tasksGroupMerged({ groupName: defaultGroup, mergeWith: 'Testing' })
|
||||
)
|
||||
expect(dispatchedActions[0]).toMatchObject(tasksGroupMerged({ groupName: defaultGroup, mergeWith: 'Testing' }))
|
||||
expect(handleClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import '@reach/dialog/styles.css'
|
||||
|
||||
import React, { useRef, useState } from 'react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogLabel,
|
||||
AlertDialogDescription,
|
||||
} from '@reach/alert-dialog'
|
||||
import { AlertDialog, AlertDialogLabel, AlertDialogDescription } from '@reach/alert-dialog'
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks'
|
||||
import { tasksGroupMerged } from './tasks-slice'
|
||||
@@ -15,10 +11,7 @@ type MergeTaskGroupsProps = {
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
const MergeTaskGroups: React.FC<MergeTaskGroupsProps> = ({
|
||||
groupName,
|
||||
handleClose,
|
||||
}) => {
|
||||
const MergeTaskGroups: React.FC<MergeTaskGroupsProps> = ({ groupName, handleClose }) => {
|
||||
const cancelRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -45,39 +38,25 @@ const MergeTaskGroups: React.FC<MergeTaskGroupsProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
data-testid="merge-task-group-dialog"
|
||||
leastDestructiveRef={cancelRef}
|
||||
>
|
||||
<AlertDialog data-testid="merge-task-group-dialog" leastDestructiveRef={cancelRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title">
|
||||
Merging task groups
|
||||
</AlertDialogLabel>
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title">Merging task groups</AlertDialogLabel>
|
||||
|
||||
{mergeableGroups.length > 0 ? (
|
||||
<>
|
||||
<AlertDialogDescription className="sk-panel-row">
|
||||
<p className="color-foreground">
|
||||
Select which group you want to merge '
|
||||
<strong>{groupName}</strong>' into:
|
||||
Select which group you want to merge '<strong>{groupName}</strong>' into:
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
<fieldset className="flex flex-col" onChange={handleChange}>
|
||||
{mergeableGroups.map((item) => (
|
||||
<label
|
||||
key={item.name}
|
||||
className="flex items-center mb-1"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={item.name}
|
||||
checked={item.name === mergeWith}
|
||||
readOnly
|
||||
/>
|
||||
<label key={item.name} className="flex items-center mb-1">
|
||||
<input type="radio" value={item.name} checked={item.name === mergeWith} readOnly />
|
||||
{item.name}
|
||||
</label>
|
||||
))}
|
||||
@@ -86,24 +65,16 @@ const MergeTaskGroups: React.FC<MergeTaskGroupsProps> = ({
|
||||
) : (
|
||||
<AlertDialogDescription>
|
||||
<p className="color-foreground">
|
||||
There are no other groups to merge '
|
||||
<strong>{groupName}</strong>' with.
|
||||
There are no other groups to merge '<strong>{groupName}</strong>' with.
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
)}
|
||||
|
||||
<div className="flex my-1 mt-4">
|
||||
<button
|
||||
className="sn-button small neutral"
|
||||
onClick={handleClose}
|
||||
ref={cancelRef}
|
||||
>
|
||||
<button className="sn-button small neutral" onClick={handleClose} ref={cancelRef}>
|
||||
{!mergeWith ? 'Close' : 'Cancel'}
|
||||
</button>
|
||||
<button
|
||||
className="sn-button small ml-2 info"
|
||||
onClick={handleMergeGroups}
|
||||
>
|
||||
<button className="sn-button small ml-2 info" onClick={handleMergeGroups}>
|
||||
Merge groups
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -20,32 +20,21 @@ const MigrateLegacyContent: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
data-testid="migrate-legacy-content-dialog"
|
||||
leastDestructiveRef={cancelRef}
|
||||
>
|
||||
<AlertDialog data-testid="migrate-legacy-content-dialog" leastDestructiveRef={cancelRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title">
|
||||
Are you sure you want to migrate legacy content to the new
|
||||
format?
|
||||
Are you sure you want to migrate legacy content to the new format?
|
||||
</AlertDialogLabel>
|
||||
|
||||
<div className="flex my-1 mt-4">
|
||||
<button
|
||||
className="sn-button small neutral"
|
||||
onClick={handleCancel}
|
||||
ref={cancelRef}
|
||||
>
|
||||
<button className="sn-button small neutral" onClick={handleCancel} ref={cancelRef}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="sn-button small ml-2 info"
|
||||
onClick={handleMigrate}
|
||||
>
|
||||
<button className="sn-button small ml-2 info" onClick={handleMigrate}>
|
||||
Migrate
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -19,10 +19,7 @@ type GroupSummaryProps = {
|
||||
|
||||
const GroupSummary: React.FC<GroupSummaryProps> = ({ groups }) => {
|
||||
const totalGroups = groups.length
|
||||
const groupsToPreview = groups.slice(
|
||||
0,
|
||||
Math.min(totalGroups, GROUPS_PREVIEW_LIMIT)
|
||||
)
|
||||
const groupsToPreview = groups.slice(0, Math.min(totalGroups, GROUPS_PREVIEW_LIMIT))
|
||||
if (groupsToPreview.length === 0) {
|
||||
return <></>
|
||||
}
|
||||
@@ -35,16 +32,10 @@ const GroupSummary: React.FC<GroupSummaryProps> = ({ groups }) => {
|
||||
<div className="my-2">
|
||||
{groupsToPreview.map((group, index) => {
|
||||
const totalTasks = group.tasks.length
|
||||
const totalCompletedTasks = group.tasks.filter(
|
||||
(task) => task.completed === true
|
||||
).length
|
||||
const totalCompletedTasks = group.tasks.filter((task) => task.completed === true).length
|
||||
|
||||
return (
|
||||
<p
|
||||
data-testid="group-summary"
|
||||
key={`group-${group.name}`}
|
||||
className="mb-1"
|
||||
>
|
||||
<p data-testid="group-summary" key={`group-${group.name}`} className="mb-1">
|
||||
{truncateText(group.name, MAX_GROUP_DESCRIPTION_LENGTH)}
|
||||
<span className="px-2 neutral">
|
||||
{totalCompletedTasks}/{totalTasks}
|
||||
@@ -75,11 +66,7 @@ const NotePreview: React.FC<NotePreviewProps> = ({ groupedTasks }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-grow items-center mb-3">
|
||||
<svg
|
||||
data-testid="circular-progress-bar"
|
||||
className="sk-circular-progress"
|
||||
viewBox="0 0 18 18"
|
||||
>
|
||||
<svg data-testid="circular-progress-bar" className="sk-circular-progress" viewBox="0 0 18 18">
|
||||
<circle className="background" />
|
||||
<circle className={`progress p-${roundedPercentage}`} />
|
||||
</svg>
|
||||
|
||||
@@ -28,11 +28,7 @@ it('renders the alert dialog with an input box', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<RenameTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<RenameTaskGroups groupName={defaultGroup} handleClose={handleClose} />, {}, defaultState)
|
||||
|
||||
const alertDialog = screen.getByTestId('rename-task-group-dialog')
|
||||
expect(alertDialog).toBeInTheDocument()
|
||||
@@ -78,14 +74,12 @@ it('should dispatch the action to merge groups', () => {
|
||||
const { mockStore } = testRender(
|
||||
<RenameTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
defaultState,
|
||||
)
|
||||
|
||||
const newGroupName = 'My new group name'
|
||||
|
||||
const inputBox = screen.getByTestId(
|
||||
'new-group-name-input'
|
||||
) as HTMLInputElement
|
||||
const inputBox = screen.getByTestId('new-group-name-input') as HTMLInputElement
|
||||
|
||||
fireEvent.change(inputBox, { target: { value: newGroupName } })
|
||||
|
||||
@@ -104,9 +98,7 @@ it('should dispatch the action to merge groups', () => {
|
||||
|
||||
const dispatchedActions = mockStore.getActions()
|
||||
expect(dispatchedActions).toHaveLength(1)
|
||||
expect(dispatchedActions[0]).toMatchObject(
|
||||
tasksGroupRenamed({ groupName: defaultGroup, newName: newGroupName })
|
||||
)
|
||||
expect(dispatchedActions[0]).toMatchObject(tasksGroupRenamed({ groupName: defaultGroup, newName: newGroupName }))
|
||||
expect(handleClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -145,14 +137,12 @@ it('should dispatch the action to merge groups on Enter press', () => {
|
||||
const { mockStore } = testRender(
|
||||
<RenameTaskGroups groupName={defaultGroup} handleClose={handleClose} />,
|
||||
{},
|
||||
defaultState
|
||||
defaultState,
|
||||
)
|
||||
|
||||
const newGroupName = 'My new group name'
|
||||
|
||||
const inputBox = screen.getByTestId(
|
||||
'new-group-name-input'
|
||||
) as HTMLInputElement
|
||||
const inputBox = screen.getByTestId('new-group-name-input') as HTMLInputElement
|
||||
|
||||
fireEvent.change(inputBox, { target: { value: newGroupName } })
|
||||
fireEvent.keyPress(inputBox, {
|
||||
@@ -164,8 +154,6 @@ it('should dispatch the action to merge groups on Enter press', () => {
|
||||
|
||||
const dispatchedActions = mockStore.getActions()
|
||||
expect(dispatchedActions).toHaveLength(1)
|
||||
expect(dispatchedActions[0]).toMatchObject(
|
||||
tasksGroupRenamed({ groupName: defaultGroup, newName: newGroupName })
|
||||
)
|
||||
expect(dispatchedActions[0]).toMatchObject(tasksGroupRenamed({ groupName: defaultGroup, newName: newGroupName }))
|
||||
expect(handleClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import '@reach/dialog/styles.css'
|
||||
|
||||
import React, { KeyboardEvent, useRef, useState } from 'react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogLabel,
|
||||
AlertDialogDescription,
|
||||
} from '@reach/alert-dialog'
|
||||
import { AlertDialog, AlertDialogLabel, AlertDialogDescription } from '@reach/alert-dialog'
|
||||
|
||||
import { useAppDispatch } from '../../app/hooks'
|
||||
import { tasksGroupRenamed } from './tasks-slice'
|
||||
@@ -16,10 +12,7 @@ type RenameTaskGroupsProps = {
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
const RenameTaskGroups: React.FC<RenameTaskGroupsProps> = ({
|
||||
groupName,
|
||||
handleClose,
|
||||
}) => {
|
||||
const RenameTaskGroups: React.FC<RenameTaskGroupsProps> = ({ groupName, handleClose }) => {
|
||||
const cancelRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -44,10 +37,7 @@ const RenameTaskGroups: React.FC<RenameTaskGroupsProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
data-testid="rename-task-group-dialog"
|
||||
leastDestructiveRef={cancelRef}
|
||||
>
|
||||
<AlertDialog data-testid="rename-task-group-dialog" leastDestructiveRef={cancelRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
@@ -69,11 +59,7 @@ const RenameTaskGroups: React.FC<RenameTaskGroupsProps> = ({
|
||||
</AlertDialogDescription>
|
||||
|
||||
<div className="flex my-1 mt-4">
|
||||
<button
|
||||
className="sn-button small neutral"
|
||||
onClick={handleClose}
|
||||
ref={cancelRef}
|
||||
>
|
||||
<button className="sn-button small neutral" onClick={handleClose} ref={cancelRef}>
|
||||
{renameTo.length === 0 ? 'Close' : 'Cancel'}
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -31,14 +31,10 @@ it('renders the group name', () => {
|
||||
it('renders the number of completed tasks and total tasks', () => {
|
||||
testRender(<TaskGroup group={defaultGroup} isDragging={false} />)
|
||||
|
||||
const completedTasks = defaultGroup.tasks.filter(
|
||||
(task) => task.completed
|
||||
).length
|
||||
const completedTasks = defaultGroup.tasks.filter((task) => task.completed).length
|
||||
const totalTasks = defaultGroup.tasks.length
|
||||
|
||||
expect(screen.getByTestId('task-group-stats')).toHaveTextContent(
|
||||
`${completedTasks}/${totalTasks}`
|
||||
)
|
||||
expect(screen.getByTestId('task-group-stats')).toHaveTextContent(`${completedTasks}/${totalTasks}`)
|
||||
})
|
||||
|
||||
it('renders the circular progress bar', () => {
|
||||
@@ -96,11 +92,7 @@ it('hides group options if can not edit', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<TaskGroup group={defaultGroup} isDragging={false} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<TaskGroup group={defaultGroup} isDragging={false} />, {}, defaultState)
|
||||
|
||||
expect(screen.queryByTestId('task-group-options')).not.toBeInTheDocument()
|
||||
})
|
||||
@@ -114,11 +106,7 @@ it('shows a reorder icon when on mobile', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<TaskGroup group={defaultGroup} isDragging={false} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<TaskGroup group={defaultGroup} isDragging={false} />, {}, defaultState)
|
||||
|
||||
expect(screen.queryByTestId('reorder-icon')).not.toBeInTheDocument()
|
||||
|
||||
@@ -130,11 +118,7 @@ it('shows a reorder icon when on mobile', () => {
|
||||
},
|
||||
}
|
||||
|
||||
testRender(
|
||||
<TaskGroup group={defaultGroup} isDragging={false} />,
|
||||
{},
|
||||
defaultState
|
||||
)
|
||||
testRender(<TaskGroup group={defaultGroup} isDragging={false} />, {}, defaultState)
|
||||
|
||||
expect(screen.getByTestId('reorder-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -10,17 +10,8 @@ import TaskItemList from './TaskItemList'
|
||||
|
||||
import TaskGroupOptions from './TaskGroupOptions'
|
||||
|
||||
import {
|
||||
CircularProgressBar,
|
||||
GenericInlineText,
|
||||
MainTitle,
|
||||
RoundButton,
|
||||
} from '../../common/components'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ReorderIcon,
|
||||
ChevronUpIcon,
|
||||
} from '../../common/components/icons'
|
||||
import { CircularProgressBar, GenericInlineText, MainTitle, RoundButton } from '../../common/components'
|
||||
import { ChevronDownIcon, ReorderIcon, ChevronUpIcon } from '../../common/components/icons'
|
||||
|
||||
const TaskGroupContainer = styled.div<{ isLast?: boolean }>`
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
@@ -100,10 +91,7 @@ const TaskGroup: React.FC<TaskGroupProps> = ({
|
||||
<ReorderIcon highlight={isDragging} />
|
||||
</div>
|
||||
)}
|
||||
<MainTitle
|
||||
crossed={allTasksCompleted && collapsed}
|
||||
highlight={isDragging}
|
||||
>
|
||||
<MainTitle crossed={allTasksCompleted && collapsed} highlight={isDragging}>
|
||||
{groupName}
|
||||
</MainTitle>
|
||||
<CircularProgressBar size={18} percentage={percentageCompleted} />
|
||||
@@ -119,10 +107,7 @@ const TaskGroup: React.FC<TaskGroupProps> = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-3">
|
||||
<RoundButton
|
||||
testId="collapse-task-group"
|
||||
onClick={handleCollapse}
|
||||
>
|
||||
<RoundButton testId="collapse-task-group" onClick={handleCollapse}>
|
||||
{!collapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</RoundButton>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
DragDropContext,
|
||||
Draggable,
|
||||
Droppable,
|
||||
DropResult,
|
||||
} from 'react-beautiful-dnd'
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks'
|
||||
import { tasksGroupReordered } from './tasks-slice'
|
||||
@@ -32,16 +27,13 @@ const TaskGroupList: React.FC = () => {
|
||||
tasksGroupReordered({
|
||||
swapGroupIndex: source.index,
|
||||
withGroupIndex: destination.index,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropContext data-testid="task-group-list" onDragEnd={onDragEnd}>
|
||||
<Droppable
|
||||
droppableId={'droppable-task-group-list'}
|
||||
isDropDisabled={!canEdit}
|
||||
>
|
||||
<Droppable droppableId={'droppable-task-group-list'} isDropDisabled={!canEdit}>
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{groupedTasks.map((group, index) => {
|
||||
@@ -52,12 +44,8 @@ const TaskGroupList: React.FC = () => {
|
||||
index={index}
|
||||
isDragDisabled={!canEdit}
|
||||
>
|
||||
{(
|
||||
{ innerRef, draggableProps, dragHandleProps },
|
||||
{ isDragging }
|
||||
) => {
|
||||
const { onTransitionEnd, ...restDraggableProps } =
|
||||
draggableProps
|
||||
{({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {
|
||||
const { onTransitionEnd, ...restDraggableProps } = draggableProps
|
||||
return (
|
||||
<TaskGroup
|
||||
key={`group-${group.name}`}
|
||||
|
||||
@@ -43,9 +43,7 @@ it('should dispatch tasksGroupDeleted action', () => {
|
||||
|
||||
const confirmDialog = screen.getByTestId('delete-task-group-dialog')
|
||||
expect(confirmDialog).toBeInTheDocument()
|
||||
expect(confirmDialog).toHaveTextContent(
|
||||
`Are you sure you want to delete the group '${groupName}'?`
|
||||
)
|
||||
expect(confirmDialog).toHaveTextContent(`Are you sure you want to delete the group '${groupName}'?`)
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-dialog-button')
|
||||
fireEvent.click(confirmButton)
|
||||
@@ -103,9 +101,7 @@ it('should close the delete task group dialog', () => {
|
||||
const cancelButton = screen.getByTestId('cancel-dialog-button')
|
||||
clickButton(cancelButton)
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('trash-task-group-dialog')
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('trash-task-group-dialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close the merge task group dialog', () => {
|
||||
@@ -120,9 +116,7 @@ it('should close the merge task group dialog', () => {
|
||||
const cancelButton = screen.queryAllByRole('button')[0]
|
||||
clickButton(cancelButton)
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('merge-task-group-dialog')
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('merge-task-group-dialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close the rename task group dialog', () => {
|
||||
@@ -137,7 +131,5 @@ it('should close the rename task group dialog', () => {
|
||||
const cancelButton = screen.queryAllByRole('button')[0]
|
||||
clickButton(cancelButton)
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('rename-task-group-dialog')
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('rename-task-group-dialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -5,12 +5,7 @@ import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { useAppDispatch } from '../../app/hooks'
|
||||
import { tasksGroupDeleted } from './tasks-slice'
|
||||
|
||||
import {
|
||||
MoreIcon,
|
||||
MergeIcon,
|
||||
TrashIcon,
|
||||
RenameIcon,
|
||||
} from '../../common/components/icons'
|
||||
import { MoreIcon, MergeIcon, TrashIcon, RenameIcon } from '../../common/components/icons'
|
||||
|
||||
import { ConfirmDialog } from '../../common/components'
|
||||
|
||||
@@ -36,24 +31,15 @@ const TaskGroupOptions: React.FC<TaskGroupOptionsProps> = ({ groupName }) => {
|
||||
<MoreIcon />
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
data-testid="delete-task-group"
|
||||
onSelect={() => setShowDeleteDialog(true)}
|
||||
>
|
||||
<MenuItem data-testid="delete-task-group" onSelect={() => setShowDeleteDialog(true)}>
|
||||
<TrashIcon />
|
||||
<span className="px-1">Delete group</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="merge-task-group"
|
||||
onSelect={() => setShowMergeDialog(true)}
|
||||
>
|
||||
<MenuItem data-testid="merge-task-group" onSelect={() => setShowMergeDialog(true)}>
|
||||
<MergeIcon />
|
||||
<span className="px-1">Merge into another group</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="rename-task-group"
|
||||
onSelect={() => setShowRenameDialog(true)}
|
||||
>
|
||||
<MenuItem data-testid="rename-task-group" onSelect={() => setShowRenameDialog(true)}>
|
||||
<RenameIcon />
|
||||
<span className="px-1">Rename</span>
|
||||
</MenuItem>
|
||||
@@ -68,22 +54,11 @@ const TaskGroupOptions: React.FC<TaskGroupOptionsProps> = ({ groupName }) => {
|
||||
confirmButtonCb={() => dispatch(tasksGroupDeleted({ groupName }))}
|
||||
cancelButtonCb={() => setShowDeleteDialog(false)}
|
||||
>
|
||||
Are you sure you want to delete the group '
|
||||
<strong>{groupName}</strong>'?
|
||||
Are you sure you want to delete the group '<strong>{groupName}</strong>'?
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
{showMergeDialog && (
|
||||
<MergeTaskGroups
|
||||
groupName={groupName}
|
||||
handleClose={() => setShowMergeDialog(false)}
|
||||
/>
|
||||
)}
|
||||
{showRenameDialog && (
|
||||
<RenameTaskGroups
|
||||
groupName={groupName}
|
||||
handleClose={() => setShowRenameDialog(false)}
|
||||
/>
|
||||
)}
|
||||
{showMergeDialog && <MergeTaskGroups groupName={groupName} handleClose={() => setShowMergeDialog(false)} />}
|
||||
{showRenameDialog && <RenameTaskGroups groupName={groupName} handleClose={() => setShowRenameDialog(false)} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
|
||||
import {
|
||||
taskDeleted,
|
||||
taskModified,
|
||||
TaskPayload,
|
||||
taskToggled,
|
||||
} from './tasks-slice'
|
||||
import { taskDeleted, taskModified, TaskPayload, taskToggled } from './tasks-slice'
|
||||
import { testRender } from '../../testUtils'
|
||||
import TaskItem from './TaskItem'
|
||||
|
||||
@@ -27,9 +22,7 @@ it('renders a check box and textarea input', async () => {
|
||||
test('clicking the check box should toggle the task as open/completed', () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const { mockStore } = testRender(
|
||||
<TaskItem groupName={groupName} task={task} />
|
||||
)
|
||||
const { mockStore } = testRender(<TaskItem groupName={groupName} task={task} />)
|
||||
|
||||
const checkBox = screen.getByTestId('check-box-input')
|
||||
fireEvent.click(checkBox)
|
||||
@@ -43,7 +36,7 @@ test('clicking the check box should toggle the task as open/completed', () => {
|
||||
taskToggled({
|
||||
id: task.id,
|
||||
groupName,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
fireEvent.click(checkBox)
|
||||
@@ -57,7 +50,7 @@ test('clicking the check box should toggle the task as open/completed', () => {
|
||||
taskToggled({
|
||||
id: task.id,
|
||||
groupName,
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -66,13 +59,9 @@ test('changing the textarea input text should update the task description', asyn
|
||||
|
||||
const newTaskDescription = 'My new task'
|
||||
|
||||
const { mockStore } = testRender(
|
||||
<TaskItem groupName={groupName} task={task} />
|
||||
)
|
||||
const { mockStore } = testRender(<TaskItem groupName={groupName} task={task} />)
|
||||
|
||||
const textAreaInput = screen.getByTestId(
|
||||
'text-area-input'
|
||||
) as HTMLTextAreaElement
|
||||
const textAreaInput = screen.getByTestId('text-area-input') as HTMLTextAreaElement
|
||||
fireEvent.change(textAreaInput, {
|
||||
target: { value: newTaskDescription },
|
||||
})
|
||||
@@ -96,14 +85,12 @@ test('changing the textarea input text should update the task description', asyn
|
||||
description: newTaskDescription,
|
||||
},
|
||||
groupName,
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('clearing the textarea input text should delete the task', () => {
|
||||
const { mockStore } = testRender(
|
||||
<TaskItem groupName={groupName} task={task} />
|
||||
)
|
||||
const { mockStore } = testRender(<TaskItem groupName={groupName} task={task} />)
|
||||
|
||||
const textAreaInput = screen.getByTestId('text-area-input')
|
||||
fireEvent.change(textAreaInput, {
|
||||
@@ -123,14 +110,12 @@ test('clearing the textarea input text should delete the task', () => {
|
||||
taskDeleted({
|
||||
id: task.id,
|
||||
groupName,
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('pressing enter should not update the task description', () => {
|
||||
const { mockStore } = testRender(
|
||||
<TaskItem groupName={groupName} task={task} />
|
||||
)
|
||||
const { mockStore } = testRender(<TaskItem groupName={groupName} task={task} />)
|
||||
|
||||
const textAreaInput = screen.getByTestId('text-area-input')
|
||||
fireEvent.keyPress(textAreaInput, {
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import './TaskItem.scss'
|
||||
|
||||
import {
|
||||
ChangeEvent,
|
||||
createRef,
|
||||
KeyboardEvent,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ChangeEvent, createRef, KeyboardEvent, useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
taskDeleted,
|
||||
taskModified,
|
||||
TaskPayload,
|
||||
taskToggled,
|
||||
} from './tasks-slice'
|
||||
import { taskDeleted, taskModified, TaskPayload, taskToggled } from './tasks-slice'
|
||||
import { useAppDispatch, useAppSelector, useDidMount } from '../../app/hooks'
|
||||
|
||||
import { CheckBoxInput, TextAreaInput } from '../../common/components'
|
||||
@@ -53,20 +42,13 @@ export type TaskItemProps = {
|
||||
innerRef?: (element?: HTMLElement | null | undefined) => any
|
||||
}
|
||||
|
||||
const TaskItem: React.FC<TaskItemProps> = ({
|
||||
task,
|
||||
groupName,
|
||||
innerRef,
|
||||
...props
|
||||
}) => {
|
||||
const TaskItem: React.FC<TaskItemProps> = ({ task, groupName, innerRef, ...props }) => {
|
||||
const textAreaRef = createRef<HTMLTextAreaElement>()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const canEdit = useAppSelector((state) => state.settings.canEdit)
|
||||
const spellCheckEnabled = useAppSelector(
|
||||
(state) => state.settings.spellCheckerEnabled
|
||||
)
|
||||
const spellCheckEnabled = useAppSelector((state) => state.settings.spellCheckerEnabled)
|
||||
|
||||
const [completed, setCompleted] = useState(!!task.completed)
|
||||
const [description, setDescription] = useState(task.description)
|
||||
@@ -96,9 +78,7 @@ const TaskItem: React.FC<TaskItemProps> = ({
|
||||
? textAreaRef.current!.classList.add('cross-out')
|
||||
: textAreaRef.current!.classList.add('no-text-decoration')
|
||||
|
||||
const dispatchDelay = newCompletedState
|
||||
? DISPATCH_COMPLETED_DELAY_MS
|
||||
: DISPATCH_OPENED_DELAY_MS
|
||||
const dispatchDelay = newCompletedState ? DISPATCH_COMPLETED_DELAY_MS : DISPATCH_OPENED_DELAY_MS
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch(taskToggled({ id: task.id, groupName }))
|
||||
@@ -135,9 +115,7 @@ const TaskItem: React.FC<TaskItemProps> = ({
|
||||
useDidMount(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (description !== task.description) {
|
||||
dispatch(
|
||||
taskModified({ task: { id: task.id, description }, groupName })
|
||||
)
|
||||
dispatch(taskModified({ task: { id: task.id, description }, groupName }))
|
||||
}
|
||||
}, 500)
|
||||
|
||||
@@ -145,18 +123,8 @@ const TaskItem: React.FC<TaskItemProps> = ({
|
||||
}, [description, groupName])
|
||||
|
||||
return (
|
||||
<Container
|
||||
data-testid="task-item"
|
||||
completed={completed}
|
||||
ref={innerRef}
|
||||
{...props}
|
||||
>
|
||||
<CheckBoxInput
|
||||
testId="check-box-input"
|
||||
checked={completed}
|
||||
disabled={!canEdit}
|
||||
onChange={onCheckBoxToggle}
|
||||
/>
|
||||
<Container data-testid="task-item" completed={completed} ref={innerRef} {...props}>
|
||||
<CheckBoxInput testId="check-box-input" checked={completed} disabled={!canEdit} onChange={onCheckBoxToggle} />
|
||||
<TextAreaInput
|
||||
testId="text-area-input"
|
||||
className="text-area-input"
|
||||
|
||||
@@ -52,9 +52,7 @@ it('renders the completed tasks container', () => {
|
||||
|
||||
testRender(<TaskItemList group={groupWithCompletedTask} />)
|
||||
|
||||
const completedTasksContainer = screen.getByTestId(
|
||||
'completed-tasks-container'
|
||||
)
|
||||
const completedTasksContainer = screen.getByTestId('completed-tasks-container')
|
||||
|
||||
expect(completedTasksContainer).toBeInTheDocument()
|
||||
expect(completedTasksContainer).toHaveTextContent('completed tasks')
|
||||
|
||||
@@ -39,19 +39,14 @@ const TaskItemList: React.FC<TaskItemListProps> = ({ group }) => {
|
||||
swapTaskIndex: source.index,
|
||||
withTaskIndex: destination.index,
|
||||
isSameSection: source.droppableId === destination.droppableId,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container data-testid="task-list">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<TasksContainer
|
||||
testId="open-tasks-container"
|
||||
type="open"
|
||||
tasks={openTasks}
|
||||
groupName={group.name}
|
||||
/>
|
||||
<TasksContainer testId="open-tasks-container" type="open" tasks={openTasks} groupName={group.name} />
|
||||
|
||||
<TasksContainer
|
||||
testId="completed-tasks-container"
|
||||
@@ -59,9 +54,7 @@ const TaskItemList: React.FC<TaskItemListProps> = ({ group }) => {
|
||||
tasks={completedTasks}
|
||||
groupName={group.name}
|
||||
>
|
||||
{completedTasks.length > 0 && (
|
||||
<CompletedTasksActions groupName={group.name} />
|
||||
)}
|
||||
{completedTasks.length > 0 && <CompletedTasksActions groupName={group.name} />}
|
||||
</TasksContainer>
|
||||
</DragDropContext>
|
||||
</Container>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import './TasksContainer.scss'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
Draggable,
|
||||
DraggingStyle,
|
||||
Droppable,
|
||||
NotDraggingStyle,
|
||||
} from 'react-beautiful-dnd'
|
||||
import { Draggable, DraggingStyle, Droppable, NotDraggingStyle } from 'react-beautiful-dnd'
|
||||
import styled from 'styled-components'
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group'
|
||||
|
||||
@@ -27,13 +22,11 @@ const InnerTasksContainer = styled.div<{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
${({ type, items }) =>
|
||||
type === 'completed' && items > 0 ? 'margin-bottom: 28px' : ''};
|
||||
${({ type, items }) => (type === 'completed' && items > 0 ? 'margin-bottom: 28px' : '')};
|
||||
`
|
||||
|
||||
const OuterContainer = styled.div<{ type: ContainerType; items: number }>`
|
||||
${({ type, items }) =>
|
||||
type === 'open' && items > 0 ? 'margin-bottom: 18px' : ''};
|
||||
${({ type, items }) => (type === 'open' && items > 0 ? 'margin-bottom: 18px' : '')};
|
||||
`
|
||||
|
||||
const SubTitleContainer = styled.div`
|
||||
@@ -49,10 +42,7 @@ const Wrapper = styled.div`
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
`
|
||||
|
||||
const getItemStyle = (
|
||||
isDragging: boolean,
|
||||
draggableStyle?: DraggingStyle | NotDraggingStyle
|
||||
) => ({
|
||||
const getItemStyle = (isDragging: boolean, draggableStyle?: DraggingStyle | NotDraggingStyle) => ({
|
||||
...draggableStyle,
|
||||
...(isDragging && {
|
||||
color: 'var(--sn-stylekit-info-color)',
|
||||
@@ -69,13 +59,7 @@ type TasksContainerProps = {
|
||||
testId?: string
|
||||
}
|
||||
|
||||
const TasksContainer: React.FC<TasksContainerProps> = ({
|
||||
groupName,
|
||||
tasks,
|
||||
type,
|
||||
testId,
|
||||
children,
|
||||
}) => {
|
||||
const TasksContainer: React.FC<TasksContainerProps> = ({ groupName, tasks, type, testId, children }) => {
|
||||
const canEdit = useAppSelector((state) => state.settings.canEdit)
|
||||
const droppableId = `${type}-tasks-droppable`
|
||||
|
||||
@@ -94,10 +78,7 @@ const TasksContainer: React.FC<TasksContainerProps> = ({
|
||||
ref={provided.innerRef}
|
||||
type={type}
|
||||
>
|
||||
<TransitionGroup
|
||||
component={null}
|
||||
childFactory={(child) => React.cloneElement(child)}
|
||||
>
|
||||
<TransitionGroup component={null} childFactory={(child) => React.cloneElement(child)}>
|
||||
{tasks.map((task, index) => {
|
||||
return (
|
||||
<CSSTransition
|
||||
@@ -128,7 +109,7 @@ const TasksContainer: React.FC<TasksContainerProps> = ({
|
||||
() => {
|
||||
node.classList.remove('explode')
|
||||
},
|
||||
false
|
||||
false,
|
||||
)
|
||||
}}
|
||||
onExited={(node: HTMLElement) => {
|
||||
@@ -146,18 +127,10 @@ const TasksContainer: React.FC<TasksContainerProps> = ({
|
||||
index={index}
|
||||
isDragDisabled={!canEdit}
|
||||
>
|
||||
{(
|
||||
{ innerRef, draggableProps, dragHandleProps },
|
||||
{ isDragging }
|
||||
) => {
|
||||
const { style, ...restDraggableProps } =
|
||||
draggableProps
|
||||
{({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {
|
||||
const { style, ...restDraggableProps } = draggableProps
|
||||
return (
|
||||
<div
|
||||
className="task-item"
|
||||
style={getItemStyle(isDragging, style)}
|
||||
{...restDraggableProps}
|
||||
>
|
||||
<div className="task-item" style={getItemStyle(isDragging, style)} {...restDraggableProps}>
|
||||
<TaskItem
|
||||
key={`task-item-${task.id}`}
|
||||
task={task}
|
||||
|
||||
@@ -22,7 +22,7 @@ it('should return the initial state', () => {
|
||||
return expect(
|
||||
reducer(undefined, {
|
||||
type: undefined,
|
||||
})
|
||||
}),
|
||||
).toEqual({ schemaVersion: '1.0.0', groups: [] })
|
||||
})
|
||||
|
||||
@@ -35,8 +35,8 @@ it('should handle a task being added to a non-existing group', () => {
|
||||
taskAdded({
|
||||
task: { id: 'some-id', description: 'A simple task' },
|
||||
groupName: 'Test',
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [],
|
||||
@@ -67,8 +67,8 @@ it('should handle a task being added to the existing tasks store', () => {
|
||||
taskAdded({
|
||||
task: { id: 'another-id', description: 'Another simple task' },
|
||||
groupName: 'Test',
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -117,8 +117,8 @@ it('should handle an existing task being modified', () => {
|
||||
taskModified({
|
||||
task: { id: 'some-id', description: 'Task description changed' },
|
||||
groupName: 'Test',
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -162,8 +162,8 @@ it('should not modify tasks if an invalid id is provided', () => {
|
||||
taskModified({
|
||||
task: { id: 'some-invalid-id', description: 'New description' },
|
||||
groupName: 'Test',
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -209,8 +209,8 @@ it('should keep completed field as-is, if task is modified', () => {
|
||||
description: 'New description',
|
||||
},
|
||||
groupName: 'Test',
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -248,9 +248,7 @@ it('should handle an existing task being toggled', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, taskToggled({ id: 'some-id', groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, taskToggled({ id: 'some-id', groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -300,9 +298,7 @@ test('toggled tasks should be on top of the list', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, taskToggled({ id: 'another-id', groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, taskToggled({ id: 'another-id', groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -352,9 +348,7 @@ it('should handle an existing completed task being toggled', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, taskToggled({ id: 'some-id', groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, taskToggled({ id: 'some-id', groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -397,9 +391,7 @@ it('should handle an existing task being deleted', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, taskDeleted({ id: 'some-id', groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, taskDeleted({ id: 'some-id', groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -447,9 +439,7 @@ it('should handle opening all tasks that are marked as completed', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, openAllCompleted({ groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, openAllCompleted({ groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -509,9 +499,7 @@ it('should handle clear all completed tasks', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, deleteAllCompleted({ groupName: 'Test' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, deleteAllCompleted({ groupName: 'Test' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -548,9 +536,7 @@ it('should handle loading tasks into the tasks store, if an invalid payload is p
|
||||
}
|
||||
|
||||
expect(reducer(previousState, tasksLoaded('null'))).toEqual(previousState)
|
||||
expect(reducer(previousState, tasksLoaded('undefined'))).toEqual(
|
||||
previousState
|
||||
)
|
||||
expect(reducer(previousState, tasksLoaded('undefined'))).toEqual(previousState)
|
||||
})
|
||||
|
||||
it('should initialize the storage with an empty object', () => {
|
||||
@@ -670,9 +656,7 @@ it('should handle loading tasks into the tasks store, with a valid payload', ()
|
||||
it('should handle adding a new task group', () => {
|
||||
const previousState: TasksState = { schemaVersion: '1.0.0', groups: [] }
|
||||
|
||||
expect(
|
||||
reducer(previousState, tasksGroupAdded({ groupName: 'New group' }))
|
||||
).toEqual({
|
||||
expect(reducer(previousState, tasksGroupAdded({ groupName: 'New group' }))).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
{
|
||||
@@ -701,9 +685,7 @@ it('should handle adding an existing task group', () => {
|
||||
],
|
||||
}
|
||||
|
||||
expect(
|
||||
reducer(previousState, tasksGroupAdded({ groupName: 'Existing group' }))
|
||||
).toEqual(previousState)
|
||||
expect(reducer(previousState, tasksGroupAdded({ groupName: 'Existing group' }))).toEqual(previousState)
|
||||
})
|
||||
|
||||
it('should handle reordering tasks from the same section', () => {
|
||||
@@ -744,8 +726,8 @@ it('should handle reordering tasks from the same section', () => {
|
||||
swapTaskIndex: 0,
|
||||
withTaskIndex: 1,
|
||||
isSameSection: true,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -814,8 +796,8 @@ it('should handle reordering tasks from different sections', () => {
|
||||
swapTaskIndex: 0,
|
||||
withTaskIndex: 1,
|
||||
isSameSection: false,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
groups: [
|
||||
@@ -893,7 +875,7 @@ it('should handle reordering task groups', () => {
|
||||
tasksGroupReordered({
|
||||
swapGroupIndex: 0,
|
||||
withGroupIndex: 1,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const expectedState = {
|
||||
@@ -978,10 +960,7 @@ it('should handle deleting groups', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupDeleted({ groupName: 'Testing' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupDeleted({ groupName: 'Testing' }))
|
||||
|
||||
const expectedState = {
|
||||
schemaVersion: '1.0.0',
|
||||
@@ -1054,10 +1033,7 @@ it('should not merge the same group', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupMerged({ groupName: 'Testing', mergeWith: 'Testing' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupMerged({ groupName: 'Testing', mergeWith: 'Testing' }))
|
||||
|
||||
expect(currentState).toEqual(previousState)
|
||||
})
|
||||
@@ -1104,7 +1080,7 @@ it('should handle merging groups', () => {
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupMerged({ groupName: 'Test group #3', mergeWith: 'Test group #2' })
|
||||
tasksGroupMerged({ groupName: 'Test group #3', mergeWith: 'Test group #2' }),
|
||||
)
|
||||
|
||||
expect(currentState).toMatchObject({
|
||||
@@ -1171,10 +1147,7 @@ it('should handle renaming a group', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupRenamed({ groupName: 'Testing', newName: 'Tested' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupRenamed({ groupName: 'Testing', newName: 'Tested' }))
|
||||
|
||||
expect(currentState).toEqual({
|
||||
schemaVersion: '1.0.0',
|
||||
@@ -1245,10 +1218,7 @@ it("should rename a group and preserve it's current order", () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupRenamed({ groupName: '2nd group', newName: 'Middle group' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupRenamed({ groupName: '2nd group', newName: 'Middle group' }))
|
||||
|
||||
expect(currentState).toMatchObject({
|
||||
schemaVersion: '1.0.0',
|
||||
@@ -1330,10 +1300,7 @@ it('should handle collapsing groups', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupCollapsed({ groupName: 'Testing', collapsed: true })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupCollapsed({ groupName: 'Testing', collapsed: true }))
|
||||
|
||||
const expectedState = {
|
||||
schemaVersion: '1.0.0',
|
||||
@@ -1418,10 +1385,7 @@ it('should handle saving task draft for groups', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupDraft({ groupName: 'Tests', draft: 'Remember to ...' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupDraft({ groupName: 'Tests', draft: 'Remember to ...' }))
|
||||
|
||||
const expectedState = {
|
||||
schemaVersion: '1.0.0',
|
||||
@@ -1495,10 +1459,7 @@ it('should handle setting a group as last active', () => {
|
||||
],
|
||||
}
|
||||
|
||||
const currentState = reducer(
|
||||
previousState,
|
||||
tasksGroupLastActive({ groupName: 'Testing' })
|
||||
)
|
||||
const currentState = reducer(previousState, tasksGroupLastActive({ groupName: 'Testing' }))
|
||||
|
||||
expect(currentState).toMatchObject({
|
||||
schemaVersion: '1.0.0',
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import {
|
||||
arrayMoveImmutable,
|
||||
isJsonString,
|
||||
parseMarkdownTasks,
|
||||
} from '../../common/utils'
|
||||
import { arrayMoveImmutable, isJsonString, parseMarkdownTasks } from '../../common/utils'
|
||||
|
||||
export type TasksState = {
|
||||
schemaVersion: string
|
||||
@@ -44,7 +40,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
task: { id: string; description: string }
|
||||
groupName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, task } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -63,7 +59,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
task: { id: string; description: string }
|
||||
groupName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, task } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -77,10 +73,7 @@ const tasksSlice = createSlice({
|
||||
currentTask.description = task.description
|
||||
currentTask.updatedAt = new Date()
|
||||
},
|
||||
taskDeleted(
|
||||
state,
|
||||
action: PayloadAction<{ id: string; groupName: string }>
|
||||
) {
|
||||
taskDeleted(state, action: PayloadAction<{ id: string; groupName: string }>) {
|
||||
const { id, groupName } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
if (!group) {
|
||||
@@ -88,10 +81,7 @@ const tasksSlice = createSlice({
|
||||
}
|
||||
group.tasks = group.tasks.filter((task) => task.id !== id)
|
||||
},
|
||||
taskToggled(
|
||||
state,
|
||||
action: PayloadAction<{ id: string; groupName: string }>
|
||||
) {
|
||||
taskToggled(state, action: PayloadAction<{ id: string; groupName: string }>) {
|
||||
const { id, groupName } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
if (!group) {
|
||||
@@ -140,10 +130,9 @@ const tasksSlice = createSlice({
|
||||
swapTaskIndex: number
|
||||
withTaskIndex: number
|
||||
isSameSection: boolean
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, swapTaskIndex, withTaskIndex, isSameSection } =
|
||||
action.payload
|
||||
const { groupName, swapTaskIndex, withTaskIndex, isSameSection } = action.payload
|
||||
if (!isSameSection) {
|
||||
return
|
||||
}
|
||||
@@ -151,17 +140,13 @@ const tasksSlice = createSlice({
|
||||
if (!group) {
|
||||
return
|
||||
}
|
||||
group.tasks = arrayMoveImmutable(
|
||||
group.tasks,
|
||||
swapTaskIndex,
|
||||
withTaskIndex
|
||||
)
|
||||
group.tasks = arrayMoveImmutable(group.tasks, swapTaskIndex, withTaskIndex)
|
||||
},
|
||||
tasksGroupAdded(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -178,20 +163,16 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
swapGroupIndex: number
|
||||
withGroupIndex: number
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { swapGroupIndex, withGroupIndex } = action.payload
|
||||
state.groups = arrayMoveImmutable(
|
||||
state.groups,
|
||||
swapGroupIndex,
|
||||
withGroupIndex
|
||||
)
|
||||
state.groups = arrayMoveImmutable(state.groups, swapGroupIndex, withGroupIndex)
|
||||
},
|
||||
tasksGroupDeleted(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName } = action.payload
|
||||
state.groups = state.groups.filter((item) => item.name !== groupName)
|
||||
@@ -201,7 +182,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
mergeWith: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, mergeWith } = action.payload
|
||||
if (groupName === mergeWith) {
|
||||
@@ -224,7 +205,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
newName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, newName } = action.payload
|
||||
if (groupName === newName) {
|
||||
@@ -241,7 +222,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
collapsed: boolean
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, collapsed } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -255,7 +236,7 @@ const tasksSlice = createSlice({
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
draft: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName, draft } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -268,7 +249,7 @@ const tasksSlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupName: string
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { groupName } = action.payload
|
||||
const group = state.groups.find((item) => item.name === groupName)
|
||||
@@ -277,10 +258,7 @@ const tasksSlice = createSlice({
|
||||
}
|
||||
group.lastActive = new Date()
|
||||
},
|
||||
tasksLegacyContentMigrated(
|
||||
state,
|
||||
{ payload }: PayloadAction<{ continue: boolean }>
|
||||
) {
|
||||
tasksLegacyContentMigrated(state, { payload }: PayloadAction<{ continue: boolean }>) {
|
||||
if (!state.legacyContent) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,11 +10,7 @@ import styled from 'styled-components'
|
||||
import { store } from './app/store'
|
||||
import { useAppDispatch, useAppSelector } from './app/hooks'
|
||||
import CreateGroup from './features/tasks/CreateGroup'
|
||||
import {
|
||||
setCanEdit,
|
||||
setIsRunningOnMobile,
|
||||
setSpellCheckerEnabled,
|
||||
} from './features/settings/settings-slice'
|
||||
import { setCanEdit, setIsRunningOnMobile, setSpellCheckerEnabled } from './features/settings/settings-slice'
|
||||
import { tasksLoaded } from './features/tasks/tasks-slice'
|
||||
import InvalidContentError from './features/tasks/InvalidContentError'
|
||||
import MigrateLegacyContent from './features/tasks/MigrateLegacyContent'
|
||||
@@ -69,8 +65,7 @@ const TaskEditor: React.FC = () => {
|
||||
onNoteValueChange: async (currentNote: any) => {
|
||||
note.current = currentNote
|
||||
|
||||
const editable =
|
||||
!currentNote.content.appData['org.standardnotes.sn'].locked ?? true
|
||||
const editable = !currentNote.content.appData['org.standardnotes.sn'].locked ?? true
|
||||
const spellCheckEnabled = currentNote.content.spellcheck
|
||||
|
||||
dispatch(setCanEdit(editable))
|
||||
@@ -106,16 +101,10 @@ const TaskEditor: React.FC = () => {
|
||||
|
||||
editorKit.current!.saveItemWithPresave(currentNote, () => {
|
||||
const { schemaVersion, groups } = store.getState().tasks
|
||||
currentNote.content.text = JSON.stringify(
|
||||
{ schemaVersion, groups },
|
||||
null,
|
||||
2
|
||||
)
|
||||
currentNote.content.text = JSON.stringify({ schemaVersion, groups }, null, 2)
|
||||
|
||||
currentNote.content.preview_plain = getPlainPreview(groups)
|
||||
currentNote.content.preview_html = renderToString(
|
||||
<NotePreview groupedTasks={groups} />
|
||||
)
|
||||
currentNote.content.preview_html = renderToString(<NotePreview groupedTasks={groups} />)
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -186,5 +175,5 @@ ReactDOM.render(
|
||||
<TaskEditor />
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
document.getElementById('root'),
|
||||
)
|
||||
|
||||
@@ -17,20 +17,12 @@ const defaultMockState: RootState = {
|
||||
},
|
||||
}
|
||||
|
||||
export function testRender(
|
||||
ui: React.ReactElement,
|
||||
renderOptions?: RenderOptions,
|
||||
state?: Partial<RootState>
|
||||
) {
|
||||
export function testRender(ui: React.ReactElement, renderOptions?: RenderOptions, state?: Partial<RootState>) {
|
||||
const mockStore = configureStore()({
|
||||
...defaultMockState,
|
||||
...state,
|
||||
})
|
||||
function Wrapper({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
}) {
|
||||
function Wrapper({ children }: { children: React.ReactElement<any, string | React.JSXElementConstructor<any>> }) {
|
||||
return <Provider store={mockStore}>{children}</Provider>
|
||||
}
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user