|
@@ -0,0 +1,413 @@
|
|
|
+import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
|
|
|
+import LoginPage from '../../support/pageObjects/LoginPage';
|
|
|
+
|
|
|
+describe('多语言资源内容验证测试', () => {
|
|
|
+ const loginPage = new LoginPage();
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ cy.clearAllSessionStorage();
|
|
|
+ cy.clearAllLocalStorage();
|
|
|
+
|
|
|
+ // Mock所有必要的API,避免影响页面加载
|
|
|
+ mockAllRequiredAPIs();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证中文翻译内容的正确性', () => {
|
|
|
+ mockI18nSuccess('zh');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHSuccess');
|
|
|
+
|
|
|
+ // 验证Redux状态中的中文翻译
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证基础翻译
|
|
|
+ expect(messages).to.have.property('greeting', '你好,世界!');
|
|
|
+ expect(messages).to.have.property('name', '张三');
|
|
|
+ expect(messages).to.have.property('patient', '患者管理');
|
|
|
+ expect(messages).to.have.property('register', '注册');
|
|
|
+ expect(messages).to.have.property('worklist', '任务清单');
|
|
|
+
|
|
|
+ // 验证嵌套键名翻译
|
|
|
+ expect(messages).to.have.property('worklist.operationPanel', '操作面板');
|
|
|
+ expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
|
|
|
+ expect(messages).to.have.property('worklistTable.patientId', '患者编号');
|
|
|
+ expect(messages).to.have.property('worklistTable.name', '患者姓名');
|
|
|
+
|
|
|
+ // 验证表单字段翻译
|
|
|
+ expect(messages).to.have.property('register.patientId', '患者编号');
|
|
|
+ expect(messages).to.have.property('register.patientName', '患者姓名');
|
|
|
+ expect(messages).to.have.property('register.gender', '性别');
|
|
|
+ expect(messages).to.have.property('register.gender.male', '男');
|
|
|
+ expect(messages).to.have.property('register.gender.female', '女');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证页面显示的中文内容
|
|
|
+ cy.get('body').should('contain', '患者管理');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证英文翻译内容的正确性', () => {
|
|
|
+ mockI18nSuccess('en');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'en-US',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nENSuccess');
|
|
|
+
|
|
|
+ // 验证Redux状态中的英文翻译
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证基础翻译
|
|
|
+ expect(messages).to.have.property('greeting', 'Hello, world!');
|
|
|
+ expect(messages).to.have.property('name', 'John Doe');
|
|
|
+ expect(messages).to.have.property('patient', 'Patient Management');
|
|
|
+ expect(messages).to.have.property('register', 'Register');
|
|
|
+ expect(messages).to.have.property('worklist', 'Task List');
|
|
|
+
|
|
|
+ // 验证嵌套键名翻译
|
|
|
+ expect(messages).to.have.property('worklist.operationPanel', 'Operation Panel');
|
|
|
+ expect(messages).to.have.property('register.basicInfoPanel', 'Basic Information Form Area');
|
|
|
+ expect(messages).to.have.property('worklistTable.patientId', 'Patient ID');
|
|
|
+ expect(messages).to.have.property('worklistTable.name', 'Patient Name');
|
|
|
+
|
|
|
+ // 验证表单字段翻译
|
|
|
+ expect(messages).to.have.property('register.patientId', 'Patient ID');
|
|
|
+ expect(messages).to.have.property('register.patientName', 'Patient Name');
|
|
|
+ expect(messages).to.have.property('register.gender', 'Gender');
|
|
|
+ expect(messages).to.have.property('register.gender.male', 'Male');
|
|
|
+ expect(messages).to.have.property('register.gender.female', 'Female');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证页面显示的英文内容
|
|
|
+ cy.get('body').should('contain', 'Patient Management');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证中英文翻译的对应关系', () => {
|
|
|
+ // 定义需要验证的关键翻译对
|
|
|
+ const translationPairs = [
|
|
|
+ { key: 'patient', zh: '患者管理', en: 'Patient Management' },
|
|
|
+ { key: 'register', zh: '注册', en: 'Register' },
|
|
|
+ { key: 'worklist', zh: '任务清单', en: 'Task List' },
|
|
|
+ { key: 'register.patientId', zh: '患者编号', en: 'Patient ID' },
|
|
|
+ { key: 'register.patientName', zh: '患者姓名', en: 'Patient Name' },
|
|
|
+ { key: 'register.gender.male', zh: '男', en: 'Male' },
|
|
|
+ { key: 'register.gender.female', zh: '女', en: 'Female' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 先验证中文
|
|
|
+ mockI18nSuccess('zh');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((zhState) => {
|
|
|
+ const zhMessages = zhState.i18n.messages;
|
|
|
+
|
|
|
+ // 验证中文翻译
|
|
|
+ translationPairs.forEach(({ key, zh }) => {
|
|
|
+ expect(zhMessages).to.have.property(key, zh);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 再验证英文
|
|
|
+ mockI18nSuccess('en');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'en-US',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nENSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((enState) => {
|
|
|
+ const enMessages = enState.i18n.messages;
|
|
|
+
|
|
|
+ // 验证英文翻译
|
|
|
+ translationPairs.forEach(({ key, en }) => {
|
|
|
+ expect(enMessages).to.have.property(key, en);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证嵌套键名的正确解析', () => {
|
|
|
+ mockI18nSuccess('zh');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证多层嵌套的键名
|
|
|
+ expect(messages).to.have.property('worklist.operationPanel', '操作面板');
|
|
|
+ expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
|
|
|
+ expect(messages).to.have.property('register.protocolListPanel', '待选择协议列表区域');
|
|
|
+ expect(messages).to.have.property('register.selectedProtocolListPanel', '已选择协议列表区域');
|
|
|
+
|
|
|
+ // 验证表格相关的嵌套键名
|
|
|
+ expect(messages).to.have.property('worklistTable.patientId', '患者编号');
|
|
|
+ expect(messages).to.have.property('worklistTable.name', '患者姓名');
|
|
|
+ expect(messages).to.have.property('worklistTable.birthDate', '出生日期');
|
|
|
+ expect(messages).to.have.property('worklistTable.gender', '性别');
|
|
|
+
|
|
|
+ // 验证表单字段的嵌套键名
|
|
|
+ expect(messages).to.have.property('register.patientId', '患者编号');
|
|
|
+ expect(messages).to.have.property('register.patientName', '患者姓名');
|
|
|
+ expect(messages).to.have.property('register.gender.male', '男');
|
|
|
+ expect(messages).to.have.property('register.gender.female', '女');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证特殊字符和格式的处理', () => {
|
|
|
+ // 创建包含特殊字符的mock数据
|
|
|
+ cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
|
|
|
+ req.reply({
|
|
|
+ statusCode: 200,
|
|
|
+ body: {
|
|
|
+ 'special.chars': '特殊字符:@#$%^&*()',
|
|
|
+ 'unicode.emoji': '表情符号:😀 🎉 ✅ ❌',
|
|
|
+ 'html.content': '<strong>粗体文本</strong>',
|
|
|
+ 'quotes.single': "包含'单引号'的文本",
|
|
|
+ 'quotes.double': '包含"双引号"的文本',
|
|
|
+ 'newlines': '第一行\n第二行\n第三行',
|
|
|
+ 'spaces': ' 前后有空格 ',
|
|
|
+ 'numbers': '数字:123456789',
|
|
|
+ 'mixed': '混合内容:123 ABC 中文 @#$ 😀'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }).as('getI18nZHSpecial');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHSpecial');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证特殊字符正确保存
|
|
|
+ expect(messages).to.have.property('special.chars', '特殊字符:@#$%^&*()');
|
|
|
+ expect(messages).to.have.property('unicode.emoji', '表情符号:😀 🎉 ✅ ❌');
|
|
|
+ expect(messages).to.have.property('html.content', '<strong>粗体文本</strong>');
|
|
|
+ expect(messages).to.have.property('quotes.single', "包含'单引号'的文本");
|
|
|
+ expect(messages).to.have.property('quotes.double', '包含"双引号"的文本');
|
|
|
+ expect(messages).to.have.property('newlines', '第一行\n第二行\n第三行');
|
|
|
+ expect(messages).to.have.property('spaces', ' 前后有空格 ');
|
|
|
+ expect(messages).to.have.property('numbers', '数字:123456789');
|
|
|
+ expect(messages).to.have.property('mixed', '混合内容:123 ABC 中文 @#$ 😀');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证翻译内容的完整性', () => {
|
|
|
+ mockI18nSuccess('en');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'en-US',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nENSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证必要的翻译键都存在
|
|
|
+ const requiredKeys = [
|
|
|
+ 'greeting',
|
|
|
+ 'name',
|
|
|
+ 'patient',
|
|
|
+ 'register',
|
|
|
+ 'worklist',
|
|
|
+ 'register.patientId',
|
|
|
+ 'register.patientName',
|
|
|
+ 'register.gender',
|
|
|
+ 'register.gender.male',
|
|
|
+ 'register.gender.female',
|
|
|
+ 'worklistTable.patientId',
|
|
|
+ 'worklistTable.name'
|
|
|
+ ];
|
|
|
+
|
|
|
+ requiredKeys.forEach(key => {
|
|
|
+ expect(messages).to.have.property(key);
|
|
|
+ expect(messages[key]).to.be.a('string');
|
|
|
+ expect(messages[key]).to.not.be.empty;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证翻译内容不包含占位符或错误标记
|
|
|
+ Object.values(messages).forEach((value: any) => {
|
|
|
+ expect(value).to.not.include('TODO');
|
|
|
+ expect(value).to.not.include('FIXME');
|
|
|
+ expect(value).to.not.include('{{');
|
|
|
+ expect(value).to.not.include('}}');
|
|
|
+ expect(value).to.not.include('[MISSING]');
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证翻译内容的一致性', () => {
|
|
|
+ // 验证相同概念在不同上下文中的翻译一致性
|
|
|
+ mockI18nSuccess('zh');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证"患者"相关翻译的一致性
|
|
|
+ expect(messages['register.patientId']).to.include('患者');
|
|
|
+ expect(messages['register.patientName']).to.include('患者');
|
|
|
+ expect(messages['worklistTable.patientId']).to.include('患者');
|
|
|
+ expect(messages['worklistTable.name']).to.include('患者');
|
|
|
+
|
|
|
+ // 验证性别翻译的一致性
|
|
|
+ expect(messages['register.gender.male']).to.equal('男');
|
|
|
+ expect(messages['register.gender.female']).to.equal('女');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证翻译内容的长度和格式', () => {
|
|
|
+ mockI18nSuccess('en');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'en-US',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nENSuccess');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证翻译内容的合理长度
|
|
|
+ Object.entries(messages).forEach(([key, value]: [string, any]) => {
|
|
|
+ // 翻译内容不应该过长(假设最长不超过200字符)
|
|
|
+ expect(value.length).to.be.lessThan(200);
|
|
|
+
|
|
|
+ // 翻译内容不应该为空
|
|
|
+ expect(value.trim()).to.not.be.empty;
|
|
|
+
|
|
|
+ // 验证特定键的格式
|
|
|
+ if (key.includes('placeholder')) {
|
|
|
+ // placeholder应该以适当的提示开始
|
|
|
+ expect(value).to.match(/^(Enter|Please|Input)/i);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (key.includes('button') || key === 'register' || key === 'print') {
|
|
|
+ // 按钮文本应该是动词或动作词
|
|
|
+ expect(value).to.not.include('.');
|
|
|
+ expect(value.length).to.be.lessThan(50);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('验证动态内容的翻译支持', () => {
|
|
|
+ // 测试包含变量占位符的翻译(如果有的话)
|
|
|
+ cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
|
|
|
+ req.reply({
|
|
|
+ statusCode: 200,
|
|
|
+ body: {
|
|
|
+ 'dynamic.welcome': '欢迎,{name}!',
|
|
|
+ 'dynamic.count': '共有 {count} 个项目',
|
|
|
+ 'dynamic.date': '日期:{date}',
|
|
|
+ 'validation.required': '{field} 是必填项',
|
|
|
+ 'validation.minLength': '{field} 至少需要 {min} 个字符'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }).as('getI18nZHDynamic');
|
|
|
+
|
|
|
+ cy.window().then((win) => {
|
|
|
+ Object.defineProperty(win.navigator, 'language', {
|
|
|
+ value: 'zh-CN',
|
|
|
+ writable: false
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ loginPage.visit();
|
|
|
+
|
|
|
+ cy.wait('@getI18nZHDynamic');
|
|
|
+
|
|
|
+ cy.window().its('store').invoke('getState').then((state) => {
|
|
|
+ const messages = state.i18n.messages;
|
|
|
+
|
|
|
+ // 验证动态内容的占位符格式正确
|
|
|
+ expect(messages).to.have.property('dynamic.welcome', '欢迎,{name}!');
|
|
|
+ expect(messages).to.have.property('dynamic.count', '共有 {count} 个项目');
|
|
|
+ expect(messages).to.have.property('dynamic.date', '日期:{date}');
|
|
|
+ expect(messages).to.have.property('validation.required', '{field} 是必填项');
|
|
|
+ expect(messages).to.have.property('validation.minLength', '{field} 至少需要 {min} 个字符');
|
|
|
+
|
|
|
+ // 验证占位符格式一致性
|
|
|
+ Object.values(messages).forEach((value: any) => {
|
|
|
+ if (value.includes('{')) {
|
|
|
+ // 确保占位符格式正确:{变量名}
|
|
|
+ const placeholders = value.match(/\{[^}]+\}/g);
|
|
|
+ if (placeholders) {
|
|
|
+ placeholders.forEach((placeholder: string) => {
|
|
|
+ expect(placeholder).to.match(/^\{[a-zA-Z][a-zA-Z0-9]*\}$/);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|