Files
what-to-eat/test/userDataStore.test.ts
T
toor 8ff7b879b9 refactor: 个人自用,移除登录与云同步全链路
站点只给本人用,云乐坊 SSO 授权、收藏/历史云同步、获客归因和账号切换记账都没有收益,却被登录状态检查和同步失败提示持续打扰。

- 删除 useYunlefunAuth / yunlefunAuth / yunlefunSsoConfig / yunlefunAuth 适配器、useUserDataSync / userDataSync / userData 适配器、三个 client 插件与 /auth/callback 回调页
- 抽出 adapters/cloudbase.ts,让「反馈一下」和匿名筛选信号不再依赖登录态;cook-feedback 云函数保留
- 删除 cook-user-data 云函数与 cook_user_data 权限规则,移除 @yunlefun/sso 依赖
- 「我的」页删掉登录按钮、账号卡、四个统计格和退出登录;「关于」页删掉项目作者、致谢名单和上游宣传链接
- 本地收藏/历史与导出导入保留,删除同步专用的 lastUid 记账

取舍见 docs/decisions/0005-drop-login-and-sync.md
2026-10-22 15:52:54 +08:00

88 lines
3.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { favoriteEntries, setFavoriteEntries, toggleFavorite } from '../apps/web/app/composables/store/favorite'
import { addRecipeHistory, clearRecipeHistories, recipeHistories, removeRecipeHistory } from '../apps/web/app/composables/store/history'
import { exportLocalUserData, importLocalUserData } from '../apps/web/app/composables/store/userData'
import { userDataMeta } from '../apps/web/app/composables/store/userDataMeta'
const NOW = 1_800_000_000_000
function recipe(name: string, id?: number) {
return { id, name, stuff: [], tools: [] }
}
beforeEach(() => {
vi.useFakeTimers({ now: NOW })
setFavoriteEntries([])
recipeHistories.value = []
userDataMeta.value = { favoriteRemovals: [], historyRemovals: [], historyClearedAt: 0 }
return () => vi.useRealTimers()
})
describe('favorite tombstones', () => {
it('records a removal and clears it when favorited again', () => {
toggleFavorite(recipe('A', 1))
expect(favoriteEntries.value).toEqual([{ id: 1, time: NOW }])
expect(userDataMeta.value.favoriteRemovals).toEqual([])
vi.advanceTimersByTime(10)
toggleFavorite(recipe('A', 1))
expect(favoriteEntries.value).toEqual([])
expect(userDataMeta.value.favoriteRemovals).toEqual([{ id: 1, time: NOW + 10 }])
vi.advanceTimersByTime(10)
toggleFavorite(recipe('A', 1))
expect(userDataMeta.value.favoriteRemovals).toEqual([])
})
})
describe('history tombstones', () => {
it('records single removals', () => {
addRecipeHistory(recipe('A'))
removeRecipeHistory(recipeHistories.value[0]!)
expect(recipeHistories.value).toEqual([])
expect(userDataMeta.value.historyRemovals).toEqual([{ name: 'A', time: NOW }])
})
it('records a clear watermark and drops single removals', () => {
addRecipeHistory(recipe('A'))
removeRecipeHistory(recipeHistories.value[0]!)
addRecipeHistory(recipe('B'))
clearRecipeHistories()
expect(recipeHistories.value).toEqual([])
expect(userDataMeta.value.historyClearedAt).toBe(NOW)
expect(userDataMeta.value.historyRemovals).toEqual([])
})
})
describe('export / import', () => {
it('merges an export from another device into local data', () => {
toggleFavorite(recipe('A', 1))
addRecipeHistory(recipe('A'))
const file = exportLocalUserData()
setFavoriteEntries([{ id: 2, time: NOW - 5 }])
recipeHistories.value = []
const result = importLocalUserData(JSON.stringify(file))
expect(result).toEqual({ favorites: 2, histories: 1 })
expect(favoriteEntries.value.map(f => f.id)).toEqual([2, 1])
expect(recipeHistories.value.map(h => h.recipe.name)).toEqual(['A'])
})
it('leaves local data alone for an unrecognised file', () => {
toggleFavorite(recipe('A', 1))
expect(importLocalUserData('{"favorites":[]}')).toBeNull()
expect(favoriteEntries.value).toEqual([{ id: 1, time: NOW }])
})
it('applies imported deletions', () => {
toggleFavorite(recipe('A', 1))
const file = exportLocalUserData()
file.favorites = []
file.favoriteRemovals = [{ id: 1, time: NOW + 1 }]
importLocalUserData(JSON.stringify(file))
expect(favoriteEntries.value).toEqual([])
})
})