fix(web): isolate project refresh recovery
This commit is contained in:
@@ -38,5 +38,7 @@ test('project settings and Inbox capture keep save success separate from refresh
|
||||
test('refresh recovery remains actionable after a save', () => {
|
||||
assert.match(appSource, /数据已保存,但页面刷新失败/)
|
||||
assert.match(appSource, />刷新数据<\/Button>/)
|
||||
assert.match(appSource, /refreshRecovery\.retry\(\)/)
|
||||
assert.match(appSource, /pendingRefreshProjectIDs/)
|
||||
assert.match(appSource, /refreshProjectWorkspaces\(requireSession\(\), targets\)/)
|
||||
assert.match(appSource, /clearPendingRefreshProjects\(current, \[projectID\]\)/)
|
||||
})
|
||||
|
||||
57
apps/web_v1/scripts/project-refresh-coordinator.test.mjs
Normal file
57
apps/web_v1/scripts/project-refresh-coordinator.test.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import {
|
||||
clearPendingRefreshProjects,
|
||||
createProjectRefreshCoordinator,
|
||||
mergePendingRefreshProjects,
|
||||
} from '../src/app/project-refresh-coordinator.ts'
|
||||
|
||||
test('project A and B refresh generations do not abort each other', () => {
|
||||
const coordinator = createProjectRefreshCoordinator()
|
||||
const projectA = coordinator.beginProject('project-a')
|
||||
const projectB = coordinator.beginProject('project-b')
|
||||
|
||||
assert.equal(projectA.signal.aborted, false)
|
||||
assert.equal(projectA.isCurrent(), true)
|
||||
assert.equal(projectB.signal.aborted, false)
|
||||
assert.equal(projectB.isCurrent(), true)
|
||||
|
||||
const newerProjectA = coordinator.beginProject('project-a')
|
||||
assert.equal(projectA.signal.aborted, true)
|
||||
assert.equal(projectB.signal.aborted, false)
|
||||
assert.equal(newerProjectA.isCurrent(), true)
|
||||
})
|
||||
|
||||
test('project B success cannot clear project A pending recovery', () => {
|
||||
let pending = mergePendingRefreshProjects([], ['project-a'])
|
||||
|
||||
pending = clearPendingRefreshProjects(pending, ['project-b'])
|
||||
|
||||
assert.deepEqual(pending, ['project-a'])
|
||||
})
|
||||
|
||||
test('recovery clears only successful targets and full success clears all', () => {
|
||||
let pending = mergePendingRefreshProjects([], ['project-a', 'project-b'])
|
||||
|
||||
pending = clearPendingRefreshProjects(pending, ['project-a'])
|
||||
assert.deepEqual(pending, ['project-b'])
|
||||
|
||||
pending = clearPendingRefreshProjects(pending)
|
||||
assert.deepEqual(pending, [])
|
||||
})
|
||||
|
||||
test('full and targeted refreshes supersede only when their coverage overlaps globally', () => {
|
||||
const coordinator = createProjectRefreshCoordinator()
|
||||
const projectA = coordinator.beginProject('project-a')
|
||||
const projectB = coordinator.beginProject('project-b')
|
||||
const full = coordinator.beginFull()
|
||||
|
||||
assert.equal(projectA.signal.aborted, true)
|
||||
assert.equal(projectB.signal.aborted, true)
|
||||
assert.equal(full.isCurrent(), true)
|
||||
|
||||
const newerProjectA = coordinator.beginProject('project-a')
|
||||
assert.equal(full.signal.aborted, true)
|
||||
assert.equal(newerProjectA.isCurrent(), true)
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
||||
const requiredFiles = [
|
||||
'src/app/App.tsx',
|
||||
'src/app/mutation-refresh.ts',
|
||||
'src/app/project-refresh-coordinator.ts',
|
||||
'src/pages/login.tsx',
|
||||
'src/pages/workspace-body.tsx',
|
||||
'src/pages/workspace-home.tsx',
|
||||
@@ -31,6 +32,7 @@ const requiredFiles = [
|
||||
'scripts/workspace-refresh-gate.test.mjs',
|
||||
'scripts/mutation-refresh.test.mjs',
|
||||
'scripts/mutation-wiring.test.mjs',
|
||||
'scripts/project-refresh-coordinator.test.mjs',
|
||||
]
|
||||
|
||||
const failures = requiredFiles.filter((file) => !existsSync(file)).map((file) => `missing ${file}`)
|
||||
@@ -62,7 +64,7 @@ if (existsSync('src/app/App.tsx')) {
|
||||
failures.push(`src/app/App.tsx still contains ${forbidden}`)
|
||||
}
|
||||
}
|
||||
for (const required of ['createWorkspaceRefreshGate', 'captureProjectInbox', 'refreshProjectWorkspace']) {
|
||||
for (const required of ['createProjectRefreshCoordinator', 'pendingRefreshProjectIDs', 'captureProjectInbox', 'refreshProjectWorkspace']) {
|
||||
if (!appSource.includes(required)) failures.push(`src/app/App.tsx must include ${required}`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user