refactor(i18n): 全工程面向国际用户与全球现代商务重构,彻底清理出海跨境等单向视角表达并对齐自动化测试
这个提交包含在:
@@ -34,6 +34,15 @@ type ArticleDetailState = {
|
||||
description: string;
|
||||
image: string;
|
||||
};
|
||||
authorName?: string;
|
||||
authorTitle?: string;
|
||||
authorCompany?: string;
|
||||
authorAvatar?: string;
|
||||
readTimeMinutes?: number;
|
||||
summaryText?: string;
|
||||
scrollDepth?: number;
|
||||
enterTime?: number;
|
||||
visitorId?: string;
|
||||
};
|
||||
|
||||
type ArticleShareFields = {
|
||||
@@ -65,6 +74,15 @@ type ArticleDetailModule = {
|
||||
getArticleShareFields: (state: ArticleDetailState, pageUrl: string) => ArticleShareFields;
|
||||
loadArticleDetail: (state: ArticleDetailState) => Promise<BentoBlockFixture | null>;
|
||||
copyArticleShareLink: (state: ArticleDetailState, pageUrl?: string) => boolean;
|
||||
resolveArticleDetailText: (key: string, locale?: string) => string;
|
||||
updateArticleScrollDepth: (
|
||||
state: ArticleDetailState,
|
||||
scrollTop: number,
|
||||
scrollHeight: number,
|
||||
windowHeight: number
|
||||
) => number;
|
||||
reportArticleStay: (state: ArticleDetailState, scrollDepth?: number) => Promise<unknown>;
|
||||
navToAuthorPortal: (state: ArticleDetailState) => void;
|
||||
};
|
||||
|
||||
type UniToastOption = {
|
||||
@@ -453,4 +471,112 @@ describe('OmniCard AI 用户端文章详情业务逻辑测试 (useArticleDetail.
|
||||
expect(toastCalls.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. 专栏作者微展台与核心摘要导读', () => {
|
||||
it('4.1 加载文章时能一并解析名片作者信息与核心摘要', async () => {
|
||||
const {
|
||||
createArticleDetailState,
|
||||
applyArticleDetailOptions,
|
||||
loadArticleDetail,
|
||||
} = loadUseArticleDetail();
|
||||
|
||||
const state = createArticleDetailState();
|
||||
applyArticleDetailOptions(state, { cardId: 'card-101', blockId: '3' });
|
||||
await loadArticleDetail(state);
|
||||
|
||||
expect(state.authorName).toBe('Alex Vance');
|
||||
expect(state.authorTitle).toBe('Managing Director & Global Partner');
|
||||
expect(state.authorCompany).toBe('Nexus Tech Global Ltd');
|
||||
expect(state.readTimeMinutes).toBeGreaterThanOrEqual(1);
|
||||
expect(state.summaryText?.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. 阅读滑屏深度计算与雷达停留时长上报', () => {
|
||||
it('5.1 updateArticleScrollDepth 正确计算并单调递增滑屏百分比', () => {
|
||||
const { createArticleDetailState, updateArticleScrollDepth } = loadUseArticleDetail();
|
||||
const state = createArticleDetailState();
|
||||
|
||||
expect(state.scrollDepth).toBe(0);
|
||||
// 顶部
|
||||
updateArticleScrollDepth(state, 0, 2000, 600);
|
||||
expect(state.scrollDepth).toBe(0);
|
||||
|
||||
// 中间 (scrollTop = 700 / maxScroll = 1400 => 50%)
|
||||
updateArticleScrollDepth(state, 700, 2000, 600);
|
||||
expect(state.scrollDepth).toBe(50);
|
||||
|
||||
// 向上回滚时保持最大深度
|
||||
updateArticleScrollDepth(state, 200, 2000, 600);
|
||||
expect(state.scrollDepth).toBe(50);
|
||||
|
||||
// 触底 (100%)
|
||||
updateArticleScrollDepth(state, 1400, 2000, 600);
|
||||
expect(state.scrollDepth).toBe(100);
|
||||
});
|
||||
|
||||
it('5.2 reportArticleStay 将停留时长与阅读深度正确上报至雷达', async () => {
|
||||
const {
|
||||
createArticleDetailState,
|
||||
applyArticleDetailOptions,
|
||||
loadArticleDetail,
|
||||
reportArticleStay,
|
||||
} = loadUseArticleDetail();
|
||||
|
||||
const state = createArticleDetailState();
|
||||
applyArticleDetailOptions(state, { cardId: 'card-101', blockId: '3' });
|
||||
await loadArticleDetail(state);
|
||||
|
||||
mockRequestResponse = {
|
||||
statusCode: 200,
|
||||
data: {
|
||||
code: 1000,
|
||||
message: 'success',
|
||||
data: {
|
||||
id: 'track-1',
|
||||
cardId: 'card-101',
|
||||
visitorId: 'vis-test',
|
||||
touchType: 'link',
|
||||
stayDurationMs: 1200,
|
||||
scrollDepth: 75,
|
||||
viewedBlocks: ['3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
state.enterTime = Date.now() - 1500;
|
||||
state.scrollDepth = 75;
|
||||
|
||||
await reportArticleStay(state, 75);
|
||||
|
||||
expect(lastRequestOptions?.url).toContain('/open/crm/radar/track');
|
||||
const body = lastRequestOptions?.data as Record<string, unknown>;
|
||||
expect(body.cardId).toBe('card-101');
|
||||
expect(body.touchType).toBe('link');
|
||||
expect(body.scrollDepth).toBe(75);
|
||||
expect(body.viewedBlocks).toEqual(['3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. 多语言本地化文案体系', () => {
|
||||
it('6.1 覆盖 zh-CN, en-US, ar-SA, es-ES 等语种的核心文案', () => {
|
||||
const { resolveArticleDetailText } = loadUseArticleDetail();
|
||||
|
||||
expect(resolveArticleDetailText('kicker', 'zh-CN')).toBe('深度洞见');
|
||||
expect(resolveArticleDetailText('summary', 'zh-CN')).toBe('核心导读');
|
||||
expect(resolveArticleDetailText('author', 'zh-CN')).toBe('专栏作者');
|
||||
expect(resolveArticleDetailText('viewPortal', 'zh-CN')).toBe('查看微官网');
|
||||
|
||||
expect(resolveArticleDetailText('kicker', 'en-US')).toBe('Thought Leadership');
|
||||
expect(resolveArticleDetailText('summary', 'en-US')).toBe('Key Takeaways');
|
||||
expect(resolveArticleDetailText('author', 'en-US')).toBe('Author');
|
||||
expect(resolveArticleDetailText('viewPortal', 'en-US')).toBe('View Profile');
|
||||
|
||||
expect(resolveArticleDetailText('kicker', 'ar-SA')).toBe('رؤى متعمقة');
|
||||
expect(resolveArticleDetailText('summary', 'ar-SA')).toBe('أبرز النقاط');
|
||||
|
||||
expect(resolveArticleDetailText('kicker', 'es-ES')).toBe('Perspectivas');
|
||||
expect(resolveArticleDetailText('summary', 'es-ES')).toBe('Puntos clave');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,12 @@ type MockBentoBlock = {
|
||||
metrics?: CaseKpi[];
|
||||
pdfUrl?: string;
|
||||
youtubeUrl?: string;
|
||||
client?: string;
|
||||
industry?: string;
|
||||
region?: string;
|
||||
challenge?: string;
|
||||
solution?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -89,6 +95,8 @@ type UseCaseDetailModule = {
|
||||
buildExchangePath: (cardId: string) => string;
|
||||
openCaseExchange: (cardId: string) => void;
|
||||
goCaseBack: () => void;
|
||||
copyCaseExternalLink: (url: string) => boolean;
|
||||
resolveCaseDetailText: (key: string) => string;
|
||||
};
|
||||
|
||||
function loadUseCaseDetail(): UseCaseDetailModule {
|
||||
@@ -371,4 +379,72 @@ describe('OmniCard AI 用户端案例详情业务逻辑测试 (useCaseDetail.uts
|
||||
expect(navigateBackCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. Bento Grid 模块化结构化数据与国际化解析', () => {
|
||||
it('5.1 支持从 metaJson 解析 client, industry, region, challenge, solution', () => {
|
||||
const { createCaseDetailState, applyCaseDetailOptions, applyPortalCase } =
|
||||
loadUseCaseDetail();
|
||||
const card = buildMockPortal();
|
||||
card.bentoBlocks[1].metaJson = {
|
||||
client: 'Global Logistics Corp',
|
||||
industry: 'Supply Chain IoT',
|
||||
region: 'Middle East & North Africa',
|
||||
challenge: 'High latency in cross-border tracking and data silos.',
|
||||
solution: 'Deployed dual-frequency NFC sensors and cloud-native mesh.',
|
||||
kpis: [{ label: 'On-time Delivery', value: '99.8%' }],
|
||||
};
|
||||
|
||||
const state = createCaseDetailState();
|
||||
applyCaseDetailOptions(state, { cardId: 'card-101', blockId: '2' });
|
||||
applyPortalCase(state, card);
|
||||
|
||||
expect((state as any).client).toBe('Global Logistics Corp');
|
||||
expect((state as any).industry).toBe('Supply Chain IoT');
|
||||
expect((state as any).region).toBe('Middle East & North Africa');
|
||||
expect((state as any).challenge).toBe(
|
||||
'High latency in cross-border tracking and data silos.'
|
||||
);
|
||||
expect((state as any).solution).toBe(
|
||||
'Deployed dual-frequency NFC sensors and cloud-native mesh.'
|
||||
);
|
||||
expect(state.kpis).toEqual([{ label: 'On-time Delivery', value: '99.8%' }]);
|
||||
});
|
||||
|
||||
it('5.2 resolveCaseDetailText 正确解析新增的 Bento 模组词条', () => {
|
||||
const { resolveCaseDetailText } = loadUseCaseDetail();
|
||||
|
||||
expect(resolveCaseDetailText('badge')).toBeDefined();
|
||||
expect(resolveCaseDetailText('overview')).toBeDefined();
|
||||
expect(resolveCaseDetailText('challenge')).toBeDefined();
|
||||
expect(resolveCaseDetailText('solution')).toBeDefined();
|
||||
expect(resolveCaseDetailText('kpis')).toBeDefined();
|
||||
expect(resolveCaseDetailText('resources')).toBeDefined();
|
||||
expect(resolveCaseDetailText('pdfAction')).toBeDefined();
|
||||
expect(resolveCaseDetailText('videoAction')).toBeDefined();
|
||||
});
|
||||
|
||||
it('5.3 copyCaseExternalLink 触发 uni.setClipboardData 与 Toast 提示', () => {
|
||||
let clipboardText = '';
|
||||
let toastCalled = false;
|
||||
(global as { uni?: Record<string, unknown> }).uni = {
|
||||
setClipboardData: (opts: { data: string; success?: () => void }) => {
|
||||
clipboardText = opts.data;
|
||||
opts.success?.();
|
||||
},
|
||||
showToast: () => {
|
||||
toastCalled = true;
|
||||
},
|
||||
};
|
||||
|
||||
const { copyCaseExternalLink } = loadUseCaseDetail();
|
||||
const res = copyCaseExternalLink('https://cdn.omnicard.ai/doc.pdf');
|
||||
|
||||
expect(res).toBe(true);
|
||||
expect(clipboardText).toBe('https://cdn.omnicard.ai/doc.pdf');
|
||||
expect(toastCalled).toBe(true);
|
||||
|
||||
const emptyRes = copyCaseExternalLink('');
|
||||
expect(emptyRes).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 严格遵循 TDD 测试驱动规范
|
||||
*/
|
||||
|
||||
describe('Point 56: 移动端 App / H5 出海名片 Universal Link 深度链接路由分发解析器', () => {
|
||||
describe('Point 56: 移动端 App / H5 智能数字名片 Universal Link 深度链接路由分发解析器', () => {
|
||||
let router: any;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* OmniCard AI (CRMmp) - 访客端多语言动态切换与 RTL 镜像契约测试 (TDD)
|
||||
* 严格按照 AGENTS.md 与出海国际化 WCAG 标准规范
|
||||
* 严格按照 AGENTS.md 与国际化 WCAG 标准规范
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
@@ -173,7 +173,7 @@ describe('Point 04: 访客端多语言动态切换与 RTL 镜像无感适配', (
|
||||
});
|
||||
|
||||
describe('4. 默认 GeoIP 语言回退 (resolveGeoLocale)', () => {
|
||||
it('根据出海访客的国家码准确映射主流语言或回退 en-US', () => {
|
||||
it('根据访客的国家码准确映射主流语言或回退 en-US', () => {
|
||||
const { resolveGeoLocale } = require(LOCALE_UTS) as {
|
||||
resolveGeoLocale: (countryCode?: string, fallback?: string) => string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* OmniCard AI (CRMmp) - 移动微官网离线 LocalStorage 极速预加载与骨架屏单测套件 (TDD)
|
||||
* 严格遵循 AGENTS.md 规范与出海高性能架构契约
|
||||
* 严格遵循 AGENTS.md 规范与高性能架构契约
|
||||
*/
|
||||
|
||||
export interface MockBentoBlock {
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('uni-app 框架自带原生 tabBar 规范测试 (TDD Red -> Green)', ()
|
||||
|
||||
// 关键规范:tabBar.list[0].pagePath 必须与 pages[0].path 一致,原生 tabBar 才能在首屏正常加载
|
||||
expect(config.tabBar.list[0].pagePath).toBe(config.pages[0].path);
|
||||
expect(config.tabBar.list[0].text).toBe('官网');
|
||||
expect(config.tabBar.list[0].text).toBe('名片');
|
||||
|
||||
// 校验颜色 Token
|
||||
expect(config.tabBar.color.toLowerCase()).toBe('#667085');
|
||||
|
||||
在新工单中引用
屏蔽一个用户