i18n-content-validation.cy.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
  2. import LoginPage from '../../support/pageObjects/LoginPage';
  3. describe('多语言资源内容验证测试', () => {
  4. const loginPage = new LoginPage();
  5. beforeEach(() => {
  6. cy.clearAllSessionStorage();
  7. cy.clearAllLocalStorage();
  8. // Mock所有必要的API,避免影响页面加载
  9. mockAllRequiredAPIs();
  10. });
  11. it('验证中文翻译内容的正确性', () => {
  12. mockI18nSuccess('zh');
  13. cy.window().then((win) => {
  14. Object.defineProperty(win.navigator, 'language', {
  15. value: 'zh-CN',
  16. writable: false
  17. });
  18. });
  19. loginPage.visit();
  20. cy.wait('@getI18nZHSuccess');
  21. // 验证Redux状态中的中文翻译
  22. cy.window().its('store').invoke('getState').then((state) => {
  23. const messages = state.i18n.messages;
  24. // 验证基础翻译
  25. expect(messages).to.have.property('greeting', '你好,世界!');
  26. expect(messages).to.have.property('name', '张三');
  27. expect(messages).to.have.property('patient', '患者管理');
  28. expect(messages).to.have.property('register', '注册');
  29. expect(messages).to.have.property('worklist', '任务清单');
  30. // 验证嵌套键名翻译
  31. expect(messages).to.have.property('worklist.operationPanel', '操作面板');
  32. expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
  33. expect(messages).to.have.property('worklistTable.patientId', '患者编号');
  34. expect(messages).to.have.property('worklistTable.name', '患者姓名');
  35. // 验证表单字段翻译
  36. expect(messages).to.have.property('register.patientId', '患者编号');
  37. expect(messages).to.have.property('register.patientName', '患者姓名');
  38. expect(messages).to.have.property('register.gender', '性别');
  39. expect(messages).to.have.property('register.gender.male', '男');
  40. expect(messages).to.have.property('register.gender.female', '女');
  41. });
  42. // 验证页面显示的中文内容
  43. cy.get('body').should('contain', '患者管理');
  44. });
  45. it('验证英文翻译内容的正确性', () => {
  46. mockI18nSuccess('en');
  47. cy.window().then((win) => {
  48. Object.defineProperty(win.navigator, 'language', {
  49. value: 'en-US',
  50. writable: false
  51. });
  52. });
  53. loginPage.visit();
  54. cy.wait('@getI18nENSuccess');
  55. // 验证Redux状态中的英文翻译
  56. cy.window().its('store').invoke('getState').then((state) => {
  57. const messages = state.i18n.messages;
  58. // 验证基础翻译
  59. expect(messages).to.have.property('greeting', 'Hello, world!');
  60. expect(messages).to.have.property('name', 'John Doe');
  61. expect(messages).to.have.property('patient', 'Patient Management');
  62. expect(messages).to.have.property('register', 'Register');
  63. expect(messages).to.have.property('worklist', 'Task List');
  64. // 验证嵌套键名翻译
  65. expect(messages).to.have.property('worklist.operationPanel', 'Operation Panel');
  66. expect(messages).to.have.property('register.basicInfoPanel', 'Basic Information Form Area');
  67. expect(messages).to.have.property('worklistTable.patientId', 'Patient ID');
  68. expect(messages).to.have.property('worklistTable.name', 'Patient Name');
  69. // 验证表单字段翻译
  70. expect(messages).to.have.property('register.patientId', 'Patient ID');
  71. expect(messages).to.have.property('register.patientName', 'Patient Name');
  72. expect(messages).to.have.property('register.gender', 'Gender');
  73. expect(messages).to.have.property('register.gender.male', 'Male');
  74. expect(messages).to.have.property('register.gender.female', 'Female');
  75. });
  76. // 验证页面显示的英文内容
  77. cy.get('body').should('contain', 'Patient Management');
  78. });
  79. it('验证中英文翻译的对应关系', () => {
  80. // 定义需要验证的关键翻译对
  81. const translationPairs = [
  82. { key: 'patient', zh: '患者管理', en: 'Patient Management' },
  83. { key: 'register', zh: '注册', en: 'Register' },
  84. { key: 'worklist', zh: '任务清单', en: 'Task List' },
  85. { key: 'register.patientId', zh: '患者编号', en: 'Patient ID' },
  86. { key: 'register.patientName', zh: '患者姓名', en: 'Patient Name' },
  87. { key: 'register.gender.male', zh: '男', en: 'Male' },
  88. { key: 'register.gender.female', zh: '女', en: 'Female' }
  89. ];
  90. // 先验证中文
  91. mockI18nSuccess('zh');
  92. cy.window().then((win) => {
  93. Object.defineProperty(win.navigator, 'language', {
  94. value: 'zh-CN',
  95. writable: false
  96. });
  97. });
  98. loginPage.visit();
  99. cy.wait('@getI18nZHSuccess');
  100. cy.window().its('store').invoke('getState').then((zhState) => {
  101. const zhMessages = zhState.i18n.messages;
  102. // 验证中文翻译
  103. translationPairs.forEach(({ key, zh }) => {
  104. expect(zhMessages).to.have.property(key, zh);
  105. });
  106. });
  107. // 再验证英文
  108. mockI18nSuccess('en');
  109. cy.window().then((win) => {
  110. Object.defineProperty(win.navigator, 'language', {
  111. value: 'en-US',
  112. writable: false
  113. });
  114. });
  115. loginPage.visit();
  116. cy.wait('@getI18nENSuccess');
  117. cy.window().its('store').invoke('getState').then((enState) => {
  118. const enMessages = enState.i18n.messages;
  119. // 验证英文翻译
  120. translationPairs.forEach(({ key, en }) => {
  121. expect(enMessages).to.have.property(key, en);
  122. });
  123. });
  124. });
  125. it('验证嵌套键名的正确解析', () => {
  126. mockI18nSuccess('zh');
  127. cy.window().then((win) => {
  128. Object.defineProperty(win.navigator, 'language', {
  129. value: 'zh-CN',
  130. writable: false
  131. });
  132. });
  133. loginPage.visit();
  134. cy.wait('@getI18nZHSuccess');
  135. cy.window().its('store').invoke('getState').then((state) => {
  136. const messages = state.i18n.messages;
  137. // 验证多层嵌套的键名
  138. expect(messages).to.have.property('worklist.operationPanel', '操作面板');
  139. expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
  140. expect(messages).to.have.property('register.protocolListPanel', '待选择协议列表区域');
  141. expect(messages).to.have.property('register.selectedProtocolListPanel', '已选择协议列表区域');
  142. // 验证表格相关的嵌套键名
  143. expect(messages).to.have.property('worklistTable.patientId', '患者编号');
  144. expect(messages).to.have.property('worklistTable.name', '患者姓名');
  145. expect(messages).to.have.property('worklistTable.birthDate', '出生日期');
  146. expect(messages).to.have.property('worklistTable.gender', '性别');
  147. // 验证表单字段的嵌套键名
  148. expect(messages).to.have.property('register.patientId', '患者编号');
  149. expect(messages).to.have.property('register.patientName', '患者姓名');
  150. expect(messages).to.have.property('register.gender.male', '男');
  151. expect(messages).to.have.property('register.gender.female', '女');
  152. });
  153. });
  154. it('验证特殊字符和格式的处理', () => {
  155. // 创建包含特殊字符的mock数据
  156. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  157. req.reply({
  158. statusCode: 200,
  159. body: {
  160. 'special.chars': '特殊字符:@#$%^&*()',
  161. 'unicode.emoji': '表情符号:😀 🎉 ✅ ❌',
  162. 'html.content': '<strong>粗体文本</strong>',
  163. 'quotes.single': "包含'单引号'的文本",
  164. 'quotes.double': '包含"双引号"的文本',
  165. 'newlines': '第一行\n第二行\n第三行',
  166. 'spaces': ' 前后有空格 ',
  167. 'numbers': '数字:123456789',
  168. 'mixed': '混合内容:123 ABC 中文 @#$ 😀'
  169. }
  170. });
  171. }).as('getI18nZHSpecial');
  172. cy.window().then((win) => {
  173. Object.defineProperty(win.navigator, 'language', {
  174. value: 'zh-CN',
  175. writable: false
  176. });
  177. });
  178. loginPage.visit();
  179. cy.wait('@getI18nZHSpecial');
  180. cy.window().its('store').invoke('getState').then((state) => {
  181. const messages = state.i18n.messages;
  182. // 验证特殊字符正确保存
  183. expect(messages).to.have.property('special.chars', '特殊字符:@#$%^&*()');
  184. expect(messages).to.have.property('unicode.emoji', '表情符号:😀 🎉 ✅ ❌');
  185. expect(messages).to.have.property('html.content', '<strong>粗体文本</strong>');
  186. expect(messages).to.have.property('quotes.single', "包含'单引号'的文本");
  187. expect(messages).to.have.property('quotes.double', '包含"双引号"的文本');
  188. expect(messages).to.have.property('newlines', '第一行\n第二行\n第三行');
  189. expect(messages).to.have.property('spaces', ' 前后有空格 ');
  190. expect(messages).to.have.property('numbers', '数字:123456789');
  191. expect(messages).to.have.property('mixed', '混合内容:123 ABC 中文 @#$ 😀');
  192. });
  193. });
  194. it('验证翻译内容的完整性', () => {
  195. mockI18nSuccess('en');
  196. cy.window().then((win) => {
  197. Object.defineProperty(win.navigator, 'language', {
  198. value: 'en-US',
  199. writable: false
  200. });
  201. });
  202. loginPage.visit();
  203. cy.wait('@getI18nENSuccess');
  204. cy.window().its('store').invoke('getState').then((state) => {
  205. const messages = state.i18n.messages;
  206. // 验证必要的翻译键都存在
  207. const requiredKeys = [
  208. 'greeting',
  209. 'name',
  210. 'patient',
  211. 'register',
  212. 'worklist',
  213. 'register.patientId',
  214. 'register.patientName',
  215. 'register.gender',
  216. 'register.gender.male',
  217. 'register.gender.female',
  218. 'worklistTable.patientId',
  219. 'worklistTable.name'
  220. ];
  221. requiredKeys.forEach(key => {
  222. expect(messages).to.have.property(key);
  223. expect(messages[key]).to.be.a('string');
  224. expect(messages[key]).to.not.be.empty;
  225. });
  226. // 验证翻译内容不包含占位符或错误标记
  227. Object.values(messages).forEach((value: any) => {
  228. expect(value).to.not.include('TODO');
  229. expect(value).to.not.include('FIXME');
  230. expect(value).to.not.include('{{');
  231. expect(value).to.not.include('}}');
  232. expect(value).to.not.include('[MISSING]');
  233. });
  234. });
  235. });
  236. it('验证翻译内容的一致性', () => {
  237. // 验证相同概念在不同上下文中的翻译一致性
  238. mockI18nSuccess('zh');
  239. cy.window().then((win) => {
  240. Object.defineProperty(win.navigator, 'language', {
  241. value: 'zh-CN',
  242. writable: false
  243. });
  244. });
  245. loginPage.visit();
  246. cy.wait('@getI18nZHSuccess');
  247. cy.window().its('store').invoke('getState').then((state) => {
  248. const messages = state.i18n.messages;
  249. // 验证"患者"相关翻译的一致性
  250. expect(messages['register.patientId']).to.include('患者');
  251. expect(messages['register.patientName']).to.include('患者');
  252. expect(messages['worklistTable.patientId']).to.include('患者');
  253. expect(messages['worklistTable.name']).to.include('患者');
  254. // 验证性别翻译的一致性
  255. expect(messages['register.gender.male']).to.equal('男');
  256. expect(messages['register.gender.female']).to.equal('女');
  257. });
  258. });
  259. it('验证翻译内容的长度和格式', () => {
  260. mockI18nSuccess('en');
  261. cy.window().then((win) => {
  262. Object.defineProperty(win.navigator, 'language', {
  263. value: 'en-US',
  264. writable: false
  265. });
  266. });
  267. loginPage.visit();
  268. cy.wait('@getI18nENSuccess');
  269. cy.window().its('store').invoke('getState').then((state) => {
  270. const messages = state.i18n.messages;
  271. // 验证翻译内容的合理长度
  272. Object.entries(messages).forEach(([key, value]: [string, any]) => {
  273. // 翻译内容不应该过长(假设最长不超过200字符)
  274. expect(value.length).to.be.lessThan(200);
  275. // 翻译内容不应该为空
  276. expect(value.trim()).to.not.be.empty;
  277. // 验证特定键的格式
  278. if (key.includes('placeholder')) {
  279. // placeholder应该以适当的提示开始
  280. expect(value).to.match(/^(Enter|Please|Input)/i);
  281. }
  282. if (key.includes('button') || key === 'register' || key === 'print') {
  283. // 按钮文本应该是动词或动作词
  284. expect(value).to.not.include('.');
  285. expect(value.length).to.be.lessThan(50);
  286. }
  287. });
  288. });
  289. });
  290. it('验证动态内容的翻译支持', () => {
  291. // 测试包含变量占位符的翻译(如果有的话)
  292. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  293. req.reply({
  294. statusCode: 200,
  295. body: {
  296. 'dynamic.welcome': '欢迎,{name}!',
  297. 'dynamic.count': '共有 {count} 个项目',
  298. 'dynamic.date': '日期:{date}',
  299. 'validation.required': '{field} 是必填项',
  300. 'validation.minLength': '{field} 至少需要 {min} 个字符'
  301. }
  302. });
  303. }).as('getI18nZHDynamic');
  304. cy.window().then((win) => {
  305. Object.defineProperty(win.navigator, 'language', {
  306. value: 'zh-CN',
  307. writable: false
  308. });
  309. });
  310. loginPage.visit();
  311. cy.wait('@getI18nZHDynamic');
  312. cy.window().its('store').invoke('getState').then((state) => {
  313. const messages = state.i18n.messages;
  314. // 验证动态内容的占位符格式正确
  315. expect(messages).to.have.property('dynamic.welcome', '欢迎,{name}!');
  316. expect(messages).to.have.property('dynamic.count', '共有 {count} 个项目');
  317. expect(messages).to.have.property('dynamic.date', '日期:{date}');
  318. expect(messages).to.have.property('validation.required', '{field} 是必填项');
  319. expect(messages).to.have.property('validation.minLength', '{field} 至少需要 {min} 个字符');
  320. // 验证占位符格式一致性
  321. Object.values(messages).forEach((value: any) => {
  322. if (value.includes('{')) {
  323. // 确保占位符格式正确:{变量名}
  324. const placeholders = value.match(/\{[^}]+\}/g);
  325. if (placeholders) {
  326. placeholders.forEach((placeholder: string) => {
  327. expect(placeholder).to.match(/^\{[a-zA-Z][a-zA-Z0-9]*\}$/);
  328. });
  329. }
  330. }
  331. });
  332. });
  333. });
  334. });