feat(FEAT-MO-004): 实现指数板块详情分析页

This commit is contained in:
2026-08-25 00:16:43 +08:00
parent f8f4da93dc
commit 0131f863b6
32 changed files with 2262 additions and 8 deletions
@@ -0,0 +1,115 @@
import { mount } from '@vue/test-utils';
import { createMemoryHistory, createRouter } from 'vue-router';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import MarketDetailPage from './MarketDetailPage.vue';
const { getIndexDetail, getSectorDetail } = vi.hoisted(() => ({
getIndexDetail: vi.fn(),
getSectorDetail: vi.fn(),
}));
vi.mock('@/api/market-overview/detail', () => ({
default: { getIndexDetail, getSectorDetail },
}));
function createTestRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{
path: '/market-overview/indices/:code',
component: MarketDetailPage,
meta: { detailType: 'index' },
},
],
});
}
const freshResponse = {
snapshotId: '20260818-030000',
asOf: '2026-08-18T00:00:00.000Z',
source: 'tdx' as const,
status: 'fresh' as const,
warnings: [],
data: {
instrument: { code: '000001.SH', name: '上证指数', type: 'index' as const },
quote: { price: 3380, changePercent: 0.89, direction: 'up' as const, tradeDate: '20260818' },
series: [
{
time: '20260817',
open: 3350,
high: 3385,
low: 3340,
close: 3350,
amount: 100,
volume: 1000,
},
{
time: '20260818',
open: 3370,
high: 3400,
low: 3360,
close: 3380,
amount: 120,
volume: 1100,
},
],
availableViews: { kline: true, intraday: false },
},
};
describe('市场详情页', () => {
beforeEach(() => {
getIndexDetail.mockReset();
getSectorDetail.mockReset();
});
it('按路由加载指数详情并显示上海时间、K 线和分时边界', async () => {
getIndexDetail.mockResolvedValue(freshResponse);
const router = createTestRouter();
await router.push('/market-overview/indices/000001.SH');
await router.isReady();
const wrapper = mount(MarketDetailPage, { global: { plugins: [router] } });
await vi.waitFor(() =>
expect(getIndexDetail).toHaveBeenCalledWith('000001.SH', { period: '1d', count: 120 }),
);
await vi.waitFor(() => expect(wrapper.text()).toContain('上证指数'));
expect(wrapper.text()).toContain('上证指数');
expect(wrapper.text()).toContain('2026-08-18 08:00:00');
expect(wrapper.text()).toContain('日 K 线');
expect(wrapper.text()).toContain('当前数据源未提供分时数据');
});
it('路由代码格式非法时不调用 API 并显示不可用边界', async () => {
const router = createTestRouter();
await router.push('/market-overview/indices/not-a-code');
await router.isReady();
const wrapper = mount(MarketDetailPage, { global: { plugins: [router] } });
expect(getIndexDetail).not.toHaveBeenCalled();
await vi.waitFor(() => expect(wrapper.text()).toContain('详情代码不可用'));
});
it('TDX 返回 unavailable 时显示 warning 而不是伪造图表', async () => {
getIndexDetail.mockResolvedValue({
...freshResponse,
status: 'unavailable',
data: null,
warnings: ['指数数据不可用'],
});
const router = createTestRouter();
await router.push('/market-overview/indices/000001.SH');
await router.isReady();
const wrapper = mount(MarketDetailPage, { global: { plugins: [router] } });
await vi.waitFor(() => expect(getIndexDetail).toHaveBeenCalled());
await vi.waitFor(() => expect(wrapper.text()).toContain('指数数据不可用'));
expect(wrapper.text()).toContain('指数数据不可用');
expect(wrapper.find('svg').exists()).toBe(false);
});
});