feat: 支持新版的首页 UI
This commit is contained in:
158
packages/app/preview/home.spec.mjs
Normal file
158
packages/app/preview/home.spec.mjs
Normal file
@@ -0,0 +1,158 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { transform } from 'esbuild'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const source = await readFile(new URL('../src/utils/home.ts', import.meta.url), 'utf8')
|
||||
const { code } = await transform(source, { loader: 'ts', format: 'esm' })
|
||||
const { getHomeMoment, getHomeGreeting, getHomeBookingDate, getHomeMembershipDays, getHomeUpcomingBookings } = await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`)
|
||||
|
||||
test('问候按中国时间在 5、9、12、18 点切换', () => {
|
||||
for (const [time, expected] of [
|
||||
['2026-10-24T20:59:59Z', '晚上好'], ['2026-10-24T21:00:00Z', '早上好'],
|
||||
['2026-10-25T00:59:59Z', '早上好'], ['2026-10-25T01:00:00Z', '上午好'],
|
||||
['2026-10-25T03:59:59Z', '上午好'], ['2026-10-25T04:00:00Z', '下午好'],
|
||||
['2026-10-25T09:59:59Z', '下午好'], ['2026-10-25T10:00:00Z', '晚上好'],
|
||||
]) assert.equal(getHomeMoment(new Date(time)).period, expected, time)
|
||||
})
|
||||
|
||||
test('中国时间跨日更新日期和提醒;同一天刷新不随机跳动', () => {
|
||||
const today = getHomeMoment(new Date('2026-10-25T01:41:00Z'))
|
||||
assert.equal(today.dateLabel, '10月25日 · 星期日')
|
||||
assert.equal(today.reminder, '练习前,给自己一次深呼吸。')
|
||||
assert.equal(getHomeMoment(new Date('2026-10-25T15:59:59Z')).reminder, today.reminder)
|
||||
const tomorrow = getHomeMoment(new Date('2026-10-25T16:00:00Z'))
|
||||
assert.equal(tomorrow.dateLabel, '10月26日 · 星期一')
|
||||
assert.notEqual(tomorrow.reminder, today.reminder)
|
||||
assert.ok(getHomeMoment(new Date('2020-01-01T00:00:00Z')).reminder)
|
||||
})
|
||||
|
||||
test('访客、加载状态、真实昵称和微信默认昵称分别显示合适问候', () => {
|
||||
assert.equal(getHomeGreeting('上午好', 'guest', '晓晓'), '上午好,欢迎你')
|
||||
assert.equal(getHomeGreeting('上午好', 'loading', '晓晓'), '上午好,欢迎回来')
|
||||
assert.equal(getHomeGreeting('上午好', 'member', ' 晓晓 '), '上午好,晓晓')
|
||||
for (const name of [null, '', ' ', '微信用户', '用户12345', '新用户']) {
|
||||
assert.equal(getHomeGreeting('上午好', 'member', name), '上午好,欢迎回来')
|
||||
}
|
||||
})
|
||||
|
||||
test('预约自然日不受设备时区影响,非法日期安全降级', () => {
|
||||
assert.deepEqual(getHomeBookingDate('2026-10-26T00:00:00.000Z'), { day: '26', caption: '10月 周一' })
|
||||
assert.deepEqual(getHomeBookingDate('2026-10-26'), { day: '26', caption: '10月 周一' })
|
||||
assert.deepEqual(getHomeBookingDate('invalid'), { day: '—', caption: '' })
|
||||
})
|
||||
|
||||
test('会员剩余天数不会显示负值或 NaN', () => {
|
||||
const now = new Date('2026-10-25T01:41:00Z')
|
||||
assert.equal(getHomeMembershipDays('2026-10-26T01:41:00Z', now), 1)
|
||||
assert.equal(getHomeMembershipDays('2026-10-25T02:00:00Z', now), 1)
|
||||
assert.equal(getHomeMembershipDays('2026-10-25T00:00:00Z', now), 0)
|
||||
assert.equal(getHomeMembershipDays('invalid', now), 0)
|
||||
})
|
||||
|
||||
test('即将上课排除已开始和取消课程,按中国时间排序并最多显示两节', () => {
|
||||
const make = (id, date, startTime, status = 'CONFIRMED') => ({ id, status, timeSlot: { date, startTime } })
|
||||
const bookings = [
|
||||
make('later', '2026-10-27T00:00:00.000Z', '10:00:00'),
|
||||
make('past', '2026-10-25T00:00:00.000Z', '09:00:00'),
|
||||
make('cancelled', '2026-10-26T00:00:00.000Z', '08:00:00', 'CANCELLED'),
|
||||
make('next', '2026-10-26T00:00:00.000Z', '10:00:00', 'PENDING_CONFIRMATION'),
|
||||
make('today', '2026-10-25T00:00:00.000Z', '10:00:00'),
|
||||
make('invalid', 'invalid', 'invalid'),
|
||||
]
|
||||
const snapshot = bookings.slice()
|
||||
assert.deepEqual(getHomeUpcomingBookings(bookings, Date.parse('2026-10-25T01:41:00Z')).map(item => item.id), ['today', 'next'])
|
||||
assert.deepEqual(bookings, snapshot, '展示排序不得修改 store 数组')
|
||||
assert.deepEqual(getHomeUpcomingBookings([], Date.now()), [])
|
||||
})
|
||||
|
||||
// Bundle actual Pinia stores while replacing only network and unrelated store
|
||||
// dependencies. These checks catch stale responses, rather than mirroring CSS.
|
||||
const { build } = require('esbuild')
|
||||
const piniaPath = require.resolve('pinia')
|
||||
const vuePath = require.resolve('vue')
|
||||
const requestQueue = []
|
||||
globalThis.__homeRequest = () => new Promise((resolve, reject) => requestQueue.push({ resolve, reject }))
|
||||
const storage = new Map([['token', 'test-token']])
|
||||
globalThis.uni = {
|
||||
getStorageSync: key => storage.get(key) || '', setStorageSync: (key, value) => storage.set(key, value),
|
||||
removeStorageSync: key => storage.delete(key), showToast() {},
|
||||
}
|
||||
const dependencies = {
|
||||
'/utils/request': 'export const get = (...args) => globalThis.__homeRequest(...args); export const post = get; export const put = get;',
|
||||
'/stores/body-portrait': 'export const useBodyPortraitStore = () => ({ keepAnonymousOnLogout() {}, claimed: false });',
|
||||
'/stores/invite': 'export const useInviteStore = () => ({ refresh: async () => {}, reset() {} });',
|
||||
'/utils/wechat-subscription': 'export const cacheSubscriptionMessageTemplateConfig = () => {}; export const resetSubscriptionMessageTemplateCache = () => {};',
|
||||
'/utils/auth': "export const isLoggedIn = () => !!uni.getStorageSync('token'); export const wxLogin = async () => ({}); export const logout = () => uni.removeStorageSync('token');",
|
||||
}
|
||||
const output = await build({
|
||||
stdin: { contents: "export { useUserStore } from './src/stores/user'; export { useBookingStore } from './src/stores/booking';", resolveDir: new URL('..', import.meta.url).pathname, loader: 'ts' },
|
||||
bundle: true, write: false, format: 'cjs', platform: 'node',
|
||||
plugins: [{ name: 'test-dependencies', setup(build) {
|
||||
build.onResolve({ filter: /^(vue|pinia)$/ }, args => ({ path: args.path === 'vue' ? vuePath : piniaPath, external: true }))
|
||||
build.onResolve({ filter: /.*/ }, args => {
|
||||
if (!args.path.startsWith('.')) return
|
||||
const path = new URL(args.path, `file://${args.resolveDir}/`).pathname.replace(/\.ts$/, '')
|
||||
const key = Object.keys(dependencies).find(key => path.endsWith(key))
|
||||
if (key) return { path: key, namespace: 'test-stub' }
|
||||
})
|
||||
build.onLoad({ filter: /.*/, namespace: 'test-stub' }, args => ({ contents: dependencies[args.path], loader: 'js' }))
|
||||
} }],
|
||||
})
|
||||
const module = { exports: {} }
|
||||
new Function('require', 'module', 'exports', output.outputFiles[0].text)(require, module, module.exports)
|
||||
const { useUserStore, useBookingStore } = module.exports
|
||||
const { createPinia, setActivePinia } = require('pinia')
|
||||
|
||||
function reset() { requestQueue.length = 0; storage.set('token', 'test-token'); setActivePinia(createPinia()) }
|
||||
|
||||
test('退出登录后,迟到的个人资料不能重新显示上一位学员', async () => {
|
||||
reset()
|
||||
const user = useUserStore()
|
||||
const task = user.fetchProfile()
|
||||
assert.equal(requestQueue.length, 1)
|
||||
user.logout()
|
||||
requestQueue.shift().resolve({ id: 'old-user', nickname: '旧学员' })
|
||||
await task
|
||||
assert.equal(user.user, null)
|
||||
assert.equal(user.loggedIn, false)
|
||||
})
|
||||
|
||||
test('退出后清空预约,并丢弃已在途的旧预约响应', async () => {
|
||||
reset()
|
||||
const bookings = useBookingStore()
|
||||
const task = bookings.fetchUpcomingBookings()
|
||||
bookings.resetUpcomingBookings()
|
||||
requestQueue.shift().resolve([{ id: 'old-booking' }])
|
||||
await task
|
||||
assert.deepEqual(bookings.upcomingBookings, [])
|
||||
assert.equal(bookings.upcomingLoaded, false)
|
||||
assert.equal(bookings.upcomingLoading, false)
|
||||
})
|
||||
|
||||
test('预约刷新失败保留已加载预约;重试后清除错误', async () => {
|
||||
reset()
|
||||
const bookings = useBookingStore()
|
||||
let task = bookings.fetchUpcomingBookings()
|
||||
requestQueue.shift().resolve([{ id: 'booking-a' }]); await task
|
||||
task = bookings.fetchUpcomingBookings()
|
||||
requestQueue.shift().reject(new Error('offline')); await task
|
||||
assert.equal(bookings.upcomingBookings[0].id, 'booking-a')
|
||||
assert.equal(bookings.upcomingError, true)
|
||||
task = bookings.fetchUpcomingBookings()
|
||||
requestQueue.shift().resolve([]); await task
|
||||
assert.equal(bookings.upcomingError, false)
|
||||
assert.equal(bookings.upcomingLoaded, true)
|
||||
})
|
||||
|
||||
test('较晚返回的旧预约请求不能覆盖最新结果', async () => {
|
||||
reset()
|
||||
const bookings = useBookingStore()
|
||||
const old = bookings.fetchUpcomingBookings(), latest = bookings.fetchUpcomingBookings()
|
||||
const olderResponse = requestQueue.shift(), latestResponse = requestQueue.shift()
|
||||
latestResponse.resolve([{ id: 'latest' }]); await latest
|
||||
olderResponse.resolve([{ id: 'stale' }]); await old
|
||||
assert.equal(bookings.upcomingBookings[0].id, 'latest')
|
||||
})
|
||||
Reference in New Issue
Block a user