fix: unify task detail saving
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 3m32s

This commit is contained in:
2026-09-11 14:53:12 +08:00
parent 4ae068e8c1
commit 8efe982e1e
2 changed files with 98 additions and 55 deletions
+31 -10
View File
@@ -275,11 +275,12 @@ describe('completion feedback motion', () => {
expect(authBlock.indexOf("await syncBrowserTimezone(data.user?.timezone)")).toBeLessThan(authBlock.indexOf('await loadRestoredView()'))
})
it('saves due removal atomically without deleting recurrence a second time', () => {
const saveTaskBlock = app.slice(app.indexOf('async function saveTask()'), app.indexOf('async function removeTask('))
expect(saveTaskBlock).not.toContain("method: 'DELETE'")
expect(saveTaskBlock).toContain('selectedTaskRecurrence.value = null')
expect(saveTaskBlock).toContain("selectedTaskRepeat.value = 'none'")
it('relies on the task PATCH for atomic due and recurrence synchronization', () => {
const saveTaskBlock = app.slice(app.indexOf('async function saveTask('), app.indexOf('async function saveSelectedTaskChanges('))
expect(saveTaskBlock).not.toContain('/recurrences/')
expect(saveTaskBlock).toContain('as Partial<Task>, false')
const unifiedSaveBlock = app.slice(app.indexOf('async function saveSelectedTaskChanges('), app.indexOf('async function removeTask('))
expect(unifiedSaveBlock).toContain('const repeatRecurrence = taskSaved.due_at ? recurrence : null')
})
it('keeps all task composer touch controls at least 44px on mobile without horizontal overflow', () => {
@@ -739,8 +740,8 @@ describe('task and habit row decoration', () => {
})
it('reclassifies Today tasks after due edits without toggling page loading', () => {
const saveBlock = app.slice(app.indexOf('async function saveTask()'), app.indexOf('async function removeTask'))
const refreshBlock = app.slice(app.indexOf('async function refreshTodayAfterTaskSave()'), app.indexOf('async function saveTask()'))
const saveBlock = app.slice(app.indexOf('async function saveTask('), app.indexOf('async function removeTask'))
const refreshBlock = app.slice(app.indexOf('async function refreshTodayAfterTaskSave()'), app.indexOf('async function saveTask('))
expect(saveBlock).toContain('await refreshTodayAfterTaskSave()')
expect(refreshBlock).toContain('loadTasksPage(request)')
expect(refreshBlock).toContain('loadOverdueTasks(request)')
@@ -966,13 +967,33 @@ describe('unified floating add interaction', () => {
expect(app).toContain('完成后 <input v-model="selectedAfterCompletionDays"')
expect(app).toContain('每次完成后,将截止时间顺延对应天数;首版永不结束')
expect(app).toContain('buildTaskRecurrencePayload(composeRepeat.value')
expect(app).toContain('buildTaskRecurrencePayload(value, { afterCompletionDays: selectedAfterCompletionDays.value')
expect(app).toContain('buildTaskRecurrencePayload(value, { afterCompletionDays, repeatConfig: config })')
expect(app).toContain("api(`/tasks/${task.id}/recurrence`)")
const createBlock = app.slice(app.indexOf('async function submitTaskCompose()'), app.indexOf('function toggleSidebar()'))
expect(createBlock).toContain("api('/tasks',")
expect(createBlock).not.toContain("api('/recurrences'")
const saveBlock = app.slice(app.indexOf('async function updateSelectedTaskRepeat()'), app.indexOf('async function submitTaskCompose()'))
expect(saveBlock).not.toContain('selectedTaskRepeat.value = parseTaskRecurrence')
const saveBlock = app.slice(app.indexOf('async function saveSelectedTaskChanges()'), app.indexOf('async function removeTask'))
expect(saveBlock).toContain("const taskId = selectedTask.value?.id")
expect(saveBlock).toContain('const selectionToken = recurrenceLoadToken')
expect(saveBlock).toContain("const repeatValue = selectedTask.value?.due_at ? selectedTaskRepeat.value : 'none'")
expect(saveBlock).toContain('structuredClone(selectedRepeatConfig.value)')
expect(saveBlock).toContain("const taskSaved = await saveTask({ showSuccess: false, expectedTaskId: taskId, expectedSelectionToken: selectionToken })")
expect(saveBlock).toContain("recurrenceLoadToken !== selectionToken")
expect(saveBlock).toContain('const repeatRecurrence = taskSaved.due_at ? recurrence : null')
expect(saveBlock).toContain('await saveRepeat(taskSaved, repeatValue, repeatConfig, afterCompletionDays, repeatRecurrence)')
expect(saveBlock).toContain("selectedRepeatError.value = ''")
expect(saveBlock).toContain("selectedRepeatError.value = reason instanceof Error ? reason.message : '保存失败'")
expect(saveBlock.indexOf("toast('已保存')")).toBeGreaterThan(saveBlock.indexOf('await saveRepeat(taskSaved, repeatValue, repeatConfig, afterCompletionDays, repeatRecurrence)'))
expect(saveBlock).toContain('if (savingSelectedTask.value || recurrenceLoading.value) return')
expect(saveBlock).toContain('savingSelectedTask.value = true')
expect(saveBlock).toContain('savingSelectedTask.value = false')
expect(saveBlock).toContain("const repeatValue = selectedTask.value?.due_at ? selectedTaskRepeat.value : 'none'")
expect(app).toContain(':disabled="savingSelectedTask || recurrenceLoading" @click="saveSelectedTaskChanges"')
expect(app).not.toContain('@blur="saveTask()"')
expect(app).not.toContain('@change="saveTask()"')
expect(app).not.toContain('value;saveTask()')
expect(app).not.toContain('async function updateSelectedTaskRepeat()')
expect(app).not.toContain('保存重复设置')
expect(css).toContain('.after-completion-fields input{width:76px;min-height:44px')
expect(app).toContain('<option value="custom">自定义…</option>')
expect(app).toContain('class="repeat-custom-fields"')