feat: 今天环境条新增下一个倒数日、浙江油价、上证指数、降雨/AQI
- 后端新增三源:中石化浙江油价(3步会话)、新浪上证行情、Open-Meteo 空气质量(国标AQI换算),按源独立超时与状态 - 天气行改为 降雨概率% · AQI 等级(无雨无AQI时保留观测时间),stale 补缓存标记 - 前端 strip 六格两行(桌面)/四行(移动),倒数日取本地缓存最近一条,超12字截断 - 修复两处 AppSheet 动画竞态存量 e2e 失败(modal contract footer 时序、scrollable 重开残留 dialog)
This commit is contained in:
@@ -2,11 +2,11 @@ import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { createApp, h, nextTick } from 'vue'
|
||||
import TodayEnvironmentStrip, { type TodayEnvironment } from './TodayEnvironmentStrip.vue'
|
||||
import TodayEnvironmentStrip, { type TodayCountdown, type TodayEnvironment } from './TodayEnvironmentStrip.vue'
|
||||
|
||||
const cleanups: Array<() => void> = []
|
||||
|
||||
async function mountStrip(props: { environment: TodayEnvironment | null; loading?: boolean; failed?: boolean }) {
|
||||
async function mountStrip(props: { environment: TodayEnvironment | null; countdown?: TodayCountdown | null; loading?: boolean; failed?: boolean }) {
|
||||
const host = document.createElement('div')
|
||||
document.body.append(host)
|
||||
const app = createApp(() => h(TodayEnvironmentStrip, props))
|
||||
@@ -151,4 +151,104 @@ describe('TodayEnvironmentStrip', () => {
|
||||
const mobileBlock = css.slice(css.indexOf('@media(max-width:720px){.today-environment'), css.indexOf('/* Solid cream material system'))
|
||||
expect(mobileBlock).not.toContain('text-overflow:ellipsis')
|
||||
})
|
||||
|
||||
it('shows umbrella probability and Chinese AQI instead of the observation time', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: { status: 'fresh', text: '多云', temperature_c: 27, precipitation_probability: 60, observed_at: '2026-09-16T09:00:00+08:00' },
|
||||
gold: environment.gold,
|
||||
air: { status: 'fresh', aqi: 36, level: '优' },
|
||||
} })
|
||||
const weather = host.querySelector('.today-environment__weather')!
|
||||
expect(weather.querySelector('strong')?.textContent).toBe('宁波 27°C 多云')
|
||||
expect(weather.querySelector('small')?.textContent).toBe('降雨60% · AQI 36 优')
|
||||
expect(host.textContent).not.toContain('海曙')
|
||||
})
|
||||
|
||||
it('marks stale weather with cached data after the rain and AQI segments', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: { status: 'stale', text: '小雨', temperature_c: 24, precipitation_probability: 20 },
|
||||
gold: environment.gold,
|
||||
air: { status: 'stale', aqi: 85, level: '良' },
|
||||
} })
|
||||
expect(host.querySelector('.today-environment__weather small')?.textContent).toBe('降雨20% · AQI 85 良 · 缓存')
|
||||
})
|
||||
|
||||
it('falls back to the observation time when rain and AQI are unavailable', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: { status: 'fresh', text: '晴', temperature_c: 30, observed_at: '2026-09-16T09:00:00+08:00' },
|
||||
gold: environment.gold,
|
||||
air: null,
|
||||
} })
|
||||
expect(host.querySelector('.today-environment__weather small')?.textContent).toBe('海曙 09:00')
|
||||
})
|
||||
|
||||
it('renders Zhejiang oil and the SSE index with compact metadata', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: environment.weather,
|
||||
gold: environment.gold,
|
||||
oil: { status: 'fresh', gas_92: '8.58', gas_95: 9.12, gas_98: '11.12', diesel_0: '8.28', effective_at: '2026-09-24T00:12:00' },
|
||||
ashare: { status: 'fresh', name: '上证指数', price: '3888.3738', prev_close: '3936.5199', change_percent: -1.22, as_of: '2026-09-24' },
|
||||
} })
|
||||
const oil = host.querySelector('.today-environment__oil')!
|
||||
expect(oil.classList.contains('is-fresh')).toBe(true)
|
||||
expect(oil.querySelector('strong')?.textContent).toBe('92# 8.58 · 95# 9.12')
|
||||
expect(oil.querySelector('small')?.textContent).toBe('98# 11.12 · 09-24生效')
|
||||
const ashare = host.querySelector('.today-environment__ashare')!
|
||||
expect(ashare.querySelector('strong')?.textContent).toBe('上证 3888.37')
|
||||
expect(ashare.querySelector('small')?.textContent).toBe('-1.22% · 09-24')
|
||||
})
|
||||
|
||||
it('signs index gains and marks stale oil', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: environment.weather,
|
||||
gold: environment.gold,
|
||||
oil: { status: 'stale', gas_92: '8.58', gas_95: '9.12', gas_98: '11.12', effective_at: '2026-09-24T00:12:00', stale: true },
|
||||
ashare: { status: 'fresh', name: '上证指数', price: '3900.5', change_percent: 0.35, as_of: '2026-09-25' },
|
||||
} })
|
||||
expect(host.querySelector('.today-environment__ashare small')?.textContent).toBe('+0.35% · 09-25')
|
||||
const oil = host.querySelector('.today-environment__oil')!
|
||||
expect(oil.classList.contains('is-stale')).toBe(true)
|
||||
expect(oil.querySelector('small')?.textContent).toBe('98# 11.12 · 09-24生效 · 缓存')
|
||||
})
|
||||
|
||||
it('renders unavailable oil and index sources as two-line placeholders', async () => {
|
||||
const host = await mountStrip({ environment: {
|
||||
calendar: environment.calendar,
|
||||
weather: environment.weather,
|
||||
gold: environment.gold,
|
||||
oil: null,
|
||||
ashare: null,
|
||||
} })
|
||||
expect(host.querySelectorAll('.today-environment__oil > .today-environment__skeleton')).toHaveLength(2)
|
||||
expect(host.querySelectorAll('.today-environment__ashare > .today-environment__skeleton')).toHaveLength(2)
|
||||
expect(host.textContent).toContain('浙江油价暂不可用')
|
||||
expect(host.textContent).toContain('上证指数暂不可用')
|
||||
})
|
||||
|
||||
it('hides the new slots when an older payload does not carry their keys', async () => {
|
||||
const host = await mountStrip({ environment })
|
||||
expect(host.querySelector('.today-environment__oil')).toBeNull()
|
||||
expect(host.querySelector('.today-environment__ashare')).toBeNull()
|
||||
expect(host.querySelector('.today-environment__countdown')).toBeNull()
|
||||
})
|
||||
|
||||
it('shows the next countdown and truncates very long titles', async () => {
|
||||
const host = await mountStrip({ environment, countdown: { title: '国庆节', days: 6, day_text: '还有 6 天' } })
|
||||
const countdown = host.querySelector('.today-environment__countdown')!
|
||||
expect(countdown.querySelector('strong')?.textContent).toBe('国庆节')
|
||||
expect(countdown.querySelector('small')?.textContent).toBe('还有 6 天')
|
||||
|
||||
const long = await mountStrip({ environment, countdown: { title: '一二三四五六七八九十甲乙丙', days: 3, day_text: '还有 3 天' } })
|
||||
expect(long.querySelector('.today-environment__countdown strong')?.textContent).toBe('一二三四五六七八九十甲…')
|
||||
})
|
||||
|
||||
it('keeps a today countdown slot with its own copy', async () => {
|
||||
const host = await mountStrip({ environment, countdown: { title: '中秋节', days: 0, day_text: '就是今天' } })
|
||||
expect(host.querySelector('.today-environment__countdown small')?.textContent).toBe('就是今天')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user