Add check-in feature and update travel planner UI

This commit is contained in:
周瑞哲
2026-07-31 12:50:14 +08:00
parent ac4179086c
commit 3ef7266591
44 changed files with 2919 additions and 144 deletions
+64
View File
@@ -10,6 +10,7 @@ import {
normalizePlanningRequest,
normalizePlanResponse,
normalizeTravelPreferences,
type Pace,
type PlanningRequest,
type PlanResponse,
resolveCanonicalPoiId,
@@ -17,6 +18,7 @@ import {
} from '../src/domain/travel'
import {
createPlan,
getSessionPlanningOrigin,
loadPlan,
loadPlanningRequest,
loadPreferences,
@@ -191,6 +193,44 @@ describe('canonical POI IDs', () => {
describe('deterministic local POC planner', () => {
const repository = getPoiRepository()
it.each([
[120, 'relaxed', 1],
[120, 'moderate', 1],
[120, 'compact', 2],
[240, 'relaxed', 3],
[240, 'moderate', 3],
[240, 'compact', 3],
[480, 'relaxed', 4],
[480, 'moderate', 5],
[480, 'compact', 6],
[600, 'relaxed', 5],
[600, 'moderate', 6],
[600, 'compact', 7],
] as const)('packs %i-minute %s quick plans into %i POIs without overflow', (durationMinutes, pace, expectedCount) => {
const plan = createLocalPlan({
mode: 'quick',
preferences: preferences({ pace: pace as Pace }),
durationMinutes,
selectedPoiIds: [],
}, repository)
expect(plan.itinerary.items).toHaveLength(expectedCount)
expect(plan.itinerary.items.length).toBeGreaterThanOrEqual(1)
expect(plan.itinerary.items.length).toBeLessThanOrEqual(8)
expect(plan.itinerary.totalMinutes).toBeLessThanOrEqual(durationMinutes)
expect(plan.itinerary.durationFitStatus).not.toBe('overflow')
})
it('rejects a quick plan when even the first stop cannot fit the budget', () => {
expect(() => createLocalPlan({
mode: 'quick',
preferences: preferences({ pace: 'relaxed', transport: 'walking' }),
durationMinutes: 120,
selectedPoiIds: [],
origin: { longitude: 114.3, latitude: 22.3, coordinateSystem: 'GCJ02' },
}, repository)).toThrow('没有可用于规划的地点')
})
it('returns the same route and canonical IDs for equivalent input', () => {
const first = createLocalPlan(preferences({ themes: ['研学', '亲子'], interests: ['摄影', '自然风光'] }), repository)
const second = createLocalPlan(preferences({ themes: ['亲子', '研学'], interests: ['自然风光', '摄影'] }), repository)
@@ -302,6 +342,22 @@ describe('deterministic local POC planner', () => {
expect(request).not.toHaveBeenCalled()
})
it('exposes the exact origin only from the current in-memory planning session', async () => {
const plan = await createPlan(preferences(), {
longitude: 113.94,
latitude: 22.76,
coordinateSystem: 'GCJ02',
})
const first = getSessionPlanningOrigin(plan.conversationId)
expect(first).toEqual({ longitude: 113.94, latitude: 22.76, coordinateSystem: 'GCJ02' })
if (first)
first.longitude = 0
expect(getSessionPlanningOrigin(plan.conversationId)?.longitude).toBe(113.94)
expect(getSessionPlanningOrigin('unknown-session')).toBeNull()
expect(JSON.stringify(loadPlanningRequest())).not.toContain('113.94')
})
it('preserves all custom selections, reports overflow, and retains them during adjustment', () => {
const selectedPoiIds = repository.getPublishedPois().slice(0, 8).map(poi => poi.id)
const request: PlanningRequest = {
@@ -484,6 +540,14 @@ describe('travel storage', () => {
expect(loadPlan()).toBeNull()
})
it('does not restore remote plans while AI planning is frozen', () => {
const plan = createLocalPlan(preferences(), getPoiRepository())
const remotePlan = { ...plan, source: 'remote_ai' as const }
values.set(PLAN_STORAGE_KEY, storageEnvelope(remotePlan))
expect(loadPlan()).toBeNull()
})
it('contains storage read failures and reports write failures', () => {
vi.mocked(uni.getStorageSync).mockImplementation(() => {
throw new Error('read failed')