refactor: remove manual refresh and search controls
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
clampSearchPullDistance,
|
||||
isSearchShortcut,
|
||||
isAtSearchPullOrigin,
|
||||
shouldHideSearchAfterSwipe,
|
||||
shouldRevealSearchAfterPull,
|
||||
} from './mobile-search'
|
||||
|
||||
describe('mobile task search gestures', () => {
|
||||
it('clamps downward pull distance without treating upward motion as pull', () => {
|
||||
expect(clampSearchPullDistance(-20)).toBe(0)
|
||||
expect(clampSearchPullDistance(24)).toBe(24)
|
||||
expect(clampSearchPullDistance(100)).toBe(72)
|
||||
})
|
||||
|
||||
it('reveals only after crossing the pull threshold', () => {
|
||||
expect(shouldRevealSearchAfterPull(55)).toBe(false)
|
||||
expect(shouldRevealSearchAfterPull(56)).toBe(true)
|
||||
})
|
||||
|
||||
it('requires every relevant scroll container to be at the top', () => {
|
||||
expect(isAtSearchPullOrigin(0, 0, 0)).toBe(true)
|
||||
expect(isAtSearchPullOrigin(0, 12, 0)).toBe(false)
|
||||
})
|
||||
|
||||
it('hides only after an upward swipe when the query is empty', () => {
|
||||
expect(shouldHideSearchAfterSwipe(-36, '')).toBe(true)
|
||||
expect(shouldHideSearchAfterSwipe(-35, '')).toBe(false)
|
||||
expect(shouldHideSearchAfterSwipe(-80, 'dodo')).toBe(false)
|
||||
})
|
||||
|
||||
it('recognizes command/control K without hijacking plain typing', () => {
|
||||
expect(isSearchShortcut({ key: 'k', metaKey: true, ctrlKey: false })).toBe(true)
|
||||
expect(isSearchShortcut({ key: 'K', metaKey: false, ctrlKey: true })).toBe(true)
|
||||
expect(isSearchShortcut({ key: 'k', metaKey: false, ctrlKey: false })).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
export const SEARCH_PULL_THRESHOLD = 56
|
||||
export const SEARCH_PULL_LIMIT = 72
|
||||
export const SEARCH_HIDE_SWIPE_THRESHOLD = 36
|
||||
|
||||
export function clampSearchPullDistance(distance: number) {
|
||||
return Math.min(SEARCH_PULL_LIMIT, Math.max(0, distance))
|
||||
}
|
||||
|
||||
export function shouldRevealSearchAfterPull(distance: number) {
|
||||
return distance >= SEARCH_PULL_THRESHOLD
|
||||
}
|
||||
|
||||
export function isAtSearchPullOrigin(...scrollPositions: number[]) {
|
||||
return scrollPositions.every((position) => position <= 0)
|
||||
}
|
||||
|
||||
export function shouldHideSearchAfterSwipe(deltaY: number, query: string) {
|
||||
return !query.trim() && deltaY <= -SEARCH_HIDE_SWIPE_THRESHOLD
|
||||
}
|
||||
|
||||
export function isSearchShortcut(event: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey'>) {
|
||||
return (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k'
|
||||
}
|
||||
@@ -1,35 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { applyMarkdownFormat, buildTaskDueDraft, buildTaskRecurrencePayload, buildTaskRrule, classifyTaskForToday, defaultTaskDueAt, filterTasks, groupTaskTree, isSameTaskSortTier, mergeReorderedSubset, moveItemWithinScope, parseTaskDueDraft, parseTaskRecurrence, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils'
|
||||
import { applyMarkdownFormat, buildTaskDueDraft, buildTaskRecurrencePayload, buildTaskRrule, classifyTaskForToday, defaultTaskDueAt, groupTaskTree, isSameTaskSortTier, mergeReorderedSubset, moveItemWithinScope, parseTaskDueDraft, parseTaskRecurrence, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils'
|
||||
|
||||
type SearchTask = {
|
||||
type TaskFixture = {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
parent_id?: string | null
|
||||
completed?: boolean
|
||||
list_name?: string
|
||||
subtasks?: SearchTask[]
|
||||
subtasks?: TaskFixture[]
|
||||
}
|
||||
|
||||
const tasks: SearchTask[] = [
|
||||
const tasks: TaskFixture[] = [
|
||||
{ id: '1', title: 'Write release notes', description: 'mention **API**', parent_id: null, completed: false },
|
||||
{ id: '2', title: 'Check links', description: '', parent_id: '1', completed: false },
|
||||
{ id: '3', title: 'Buy milk', description: '', parent_id: null, completed: true },
|
||||
]
|
||||
|
||||
describe('task utilities', () => {
|
||||
it('filters task titles and markdown descriptions case-insensitively', () => {
|
||||
expect(filterTasks(tasks, 'api').map((task) => task.id)).toEqual(['1'])
|
||||
expect(filterTasks(tasks, 'WRITE').map((task) => task.id)).toEqual(['1'])
|
||||
})
|
||||
|
||||
it('keeps a one-level subtask tree without duplicating children', () => {
|
||||
expect(groupTaskTree(tasks)).toEqual([{ task: tasks[0], subtasks: [tasks[1]] }, { task: tasks[2], subtasks: [] }])
|
||||
})
|
||||
|
||||
it('preserves subtasks already nested by the task API', () => {
|
||||
const child: SearchTask = { id: 'nested-child', title: 'Nested child', parent_id: 'nested-parent' }
|
||||
const parent: SearchTask = { id: 'nested-parent', title: 'Nested parent', parent_id: null, subtasks: [child] }
|
||||
const child: TaskFixture = { id: 'nested-child', title: 'Nested child', parent_id: 'nested-parent' }
|
||||
const parent: TaskFixture = { id: 'nested-parent', title: 'Nested parent', parent_id: null, subtasks: [child] }
|
||||
expect(groupTaskTree([parent])).toEqual([{ task: parent, subtasks: [child] }])
|
||||
})
|
||||
|
||||
@@ -116,14 +111,6 @@ describe('task utilities', () => {
|
||||
expect(applyMarkdownFormat('work', 0, 4, 'task')).toEqual({ value: '- [ ] work', start: 6, end: 10 })
|
||||
})
|
||||
|
||||
it('filters titles, descriptions, and list names', () => {
|
||||
const searchable: SearchTask[] = [
|
||||
...tasks,
|
||||
{ id: '4', title: 'Plan', list_name: '工作清单' },
|
||||
]
|
||||
expect(filterTasks(searchable, '工作').map((task) => task.id)).toEqual(['4'])
|
||||
})
|
||||
|
||||
it('defaults new tasks to today without a time', () => {
|
||||
const due = defaultTaskDueAt(new Date(2026, 8, 8, 21, 30))
|
||||
expect(due).toBe('2026-09-08')
|
||||
|
||||
@@ -72,18 +72,6 @@ export function renderMarkdown(markdown = '') {
|
||||
return markdownRenderer.render(markdown)
|
||||
}
|
||||
|
||||
export function filterTasks<T extends MinimalTask>(tasks: T[], query: string) {
|
||||
const q = query.trim().toLowerCase()
|
||||
if (!q) return tasks
|
||||
return tasks.filter((task) => {
|
||||
const haystack = [
|
||||
task.title,
|
||||
task.description ?? '',
|
||||
task.list_name ?? '',
|
||||
].join(' ')
|
||||
return haystack.toLowerCase().includes(q)
|
||||
})
|
||||
}
|
||||
|
||||
export function groupTaskTree<T extends MinimalTask>(tasks: T[]) {
|
||||
const children = new Map<string, T[]>()
|
||||
|
||||
Reference in New Issue
Block a user