/** * 测试文件: 患者注册 - 切换患者类型清空已选体位 * 功能: 验证切换患者类型时自动清空已选择体位列表 * * 测试场景: * 1. 切换患者类型时清空已选体位列表 * 2. 切换协议/体位模式时保持已选体位 * 3. 切换身体部位时保持已选体位 * * 相关需求文档: docs/实现/注册时体位过滤-需求-实现.md */ import { mockLoginSuccess } from '../../support/mock/handlers/user'; import { mockGetMultiplePatientTypes, mockGetBodyPartForHuman, mockGetBodyPartForSpecialType, mockGetViewsForHumanSkull, mockGetProceduresForHumanSkull, mockGetViewsByProcedure, } from '../../support/mock/handlers/patientRegistration'; describe('患者注册:切换患者类型清空已选体位列表', () => { beforeEach(() => { // 设置所有必要的 Mock mockLoginSuccess(); mockGetMultiplePatientTypes(); mockGetBodyPartForHuman(); mockGetBodyPartForSpecialType(); mockGetViewsForHumanSkull(); mockGetProceduresForHumanSkull(); mockGetViewsByProcedure(); // 登录并导航到患者注册页面 cy.visit('/'); cy.get('[data-testid="username"]').type('admin'); cy.get('[data-testid="password"]').type('123456'); cy.get('[data-testid="login-button"]').click(); cy.wait('@loginSuccess'); // 导航到患者管理 -> 注册页面 cy.get('[data-testid="patient-management"]').click(); cy.get('[data-testid="register"]').click(); }); /** * 测试场景 1:切换患者类型时清空已选体位列表 * * Given: 用户已在患者注册页面,已选择患者类型、身体部位并添加了体位 * When: 用户切换到不同的患者类型 * Then: 已选择体位列表应该被清空 */ it('应该在切换患者类型时清空已选择的体位列表', () => { /** * Given: 用户在患者注册页面 * 步骤1: 页面已加载患者类型列表 */ cy.wait('@getMultiplePatientTypes'); /** * When: 选择患者类型 "Human" * 步骤2: 点击患者类型选择器 * 步骤3: 选择 "Human" 类型 */ cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); /** * When: 选择身体部位 "颅骨" * 步骤4: 点击身体部位选择器 * 步骤5: 选择 "颅骨" */ cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_SKULL"]').click(); /** * When: 切换到 "体位" 选择模式 * 步骤6: 点击 "体位" 标签 */ cy.get('[data-testid="selection-mode-view"]').click(); cy.wait('@getViewsForHumanSkull'); /** * When: 添加体位到已选列表 * 步骤7: 点击第一个体位(颅骨前后位) */ cy.get('[data-testid="view-item-View_DX_H_SKULL_AP"]').click(); /** * Then: 验证已选列表包含 1 个体位 * 步骤8: 检查已选列表 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 1); cy.get('[data-testid="selected-view-count"]') .should('contain', '1'); /** * When: 切换患者类型到 "SpecialType" * 步骤9: 打开患者类型选择器 * 步骤10: 选择 "SpecialType" */ cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-SpecialType"]').click(); cy.wait('@getBodyPartForSpecialType'); /** * Then: 已选择体位列表应该被清空 * 步骤11: 验证已选列表为空 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 0); cy.get('[data-testid="selected-view-count"]') .should('contain', '0'); /** * Then: 验证 Redux store 中的状态 * 步骤12: 检查 Redux store */ cy.window().its('store').invoke('getState') .its('viewSelection') .its('selectedViews') .should('have.length', 0); }); /** * 测试场景 2:切换协议/体位模式时保持已选体位 * * Given: 用户已选择患者类型、身体部位,并在协议模式下添加了协议 * When: 用户切换到体位选择模式 * Then: 已选择体位列表应该保持不变 */ it('应该在切换协议/体位模式时保持已选择的体位列表', () => { /** * Given: 选择患者类型和身体部位 */ cy.wait('@getMultiplePatientTypes'); cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_SKULL"]').click(); /** * When: 在协议模式下添加协议 * 步骤1: 确保在协议模式(默认) * 步骤2: 等待协议列表加载 */ cy.get('[data-testid="selection-mode-protocol"]').should('have.class', 'active'); cy.wait('@getProceduresForHumanSkull'); /** * When: 点击协议添加体位 * 步骤3: 点击协议 */ cy.get('[data-testid="procedure-item-P_SKULL_AP_LAT"]').click(); cy.wait('@getViewsByProcedure'); /** * Then: 验证已选列表包含体位 * 步骤4: 检查已选列表(协议包含2个体位) */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 2); /** * When: 切换到体位模式 * 步骤5: 点击体位标签 */ cy.get('[data-testid="selection-mode-view"]').click(); cy.wait('@getViewsForHumanSkull'); /** * Then: 已选列表应该保持不变 * 步骤6: 验证已选列表仍然包含 2 个体位 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 2); cy.get('[data-testid="selected-view-count"]') .should('contain', '2'); /** * Then: 验证 Redux store 状态 */ cy.window().its('store').invoke('getState') .its('viewSelection') .its('selectedViews') .should('have.length', 2); }); /** * 测试场景 3:切换身体部位时保持已选体位 * * Given: 用户已选择患者类型、身体部位并添加了体位 * When: 用户切换到不同的身体部位 * Then: 已选择体位列表应该保持不变 */ it('应该在切换身体部位时保持已选择的体位列表', () => { /** * Given: 选择患者类型 "Human" */ cy.wait('@getMultiplePatientTypes'); cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); /** * Given: 选择身体部位 "颅骨" */ cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_SKULL"]').click(); /** * Given: 切换到体位模式并添加体位 */ cy.get('[data-testid="selection-mode-view"]').click(); cy.wait('@getViewsForHumanSkull'); cy.get('[data-testid="view-item-View_DX_H_SKULL_AP"]').click(); /** * Then: 验证已选列表包含 1 个体位 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 1); /** * When: 切换身体部位到 "颈部" * 步骤1: 打开身体部位选择器 * 步骤2: 选择 "颈部" */ cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_NECK"]').click(); /** * Then: 已选列表应该保持不变 * 步骤3: 验证已选列表仍然包含 1 个体位 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 1); cy.get('[data-testid="selected-view-count"]') .should('contain', '1'); /** * Then: 验证 Redux store 状态 */ cy.window().its('store').invoke('getState') .its('viewSelection') .its('selectedViews') .should('have.length', 1); }); /** * 测试场景 4:边界情况 - 已选列表为空时切换患者类型 * * Given: 用户已选择患者类型但未添加任何体位 * When: 用户切换到不同的患者类型 * Then: 不应该产生错误 */ it('应该在已选列表为空时安全地切换患者类型', () => { /** * Given: 选择患者类型但不添加体位 */ cy.wait('@getMultiplePatientTypes'); cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); /** * Then: 验证已选列表为空 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 0); /** * When: 切换患者类型 */ cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-SpecialType"]').click(); cy.wait('@getBodyPartForSpecialType'); /** * Then: 应该没有错误,已选列表仍为空 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 0); // 验证没有错误提示 cy.get('[data-testid="error-message"]').should('not.exist'); }); /** * 测试场景 5:边界情况 - 已选多个体位时切换患者类型 * * Given: 用户已添加多个体位到已选列表 * When: 用户切换患者类型 * Then: 所有体位都应该被清空 */ it('应该在切换患者类型时清空所有已选择的体位', () => { /** * Given: 选择患者类型和身体部位 */ cy.wait('@getMultiplePatientTypes'); cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_SKULL"]').click(); /** * Given: 切换到体位模式并添加多个体位 */ cy.get('[data-testid="selection-mode-view"]').click(); cy.wait('@getViewsForHumanSkull'); // 添加第一个体位 cy.get('[data-testid="view-item-View_DX_H_SKULL_AP"]').click(); // 添加第二个体位 cy.get('[data-testid="view-item-View_DX_H_SKULL_LAT"]').click(); /** * Then: 验证已选列表包含 2 个体位 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 2); /** * When: 切换患者类型 */ cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-SpecialType"]').click(); cy.wait('@getBodyPartForSpecialType'); /** * Then: 所有体位都应该被清空 */ cy.get('[data-testid="selected-views-list"]') .find('[data-testid^="selected-view-"]') .should('have.length', 0); cy.get('[data-testid="selected-view-count"]') .should('contain', '0'); }); /** * 测试场景 6:验证控制台日志输出 * * Given: 用户已添加体位 * When: 切换患者类型 * Then: 应该在控制台输出清空体位的日志 */ it('应该在切换患者类型时输出正确的控制台日志', () => { /** * 监听控制台日志 */ cy.window().then((win) => { cy.spy(win.console, 'log').as('consoleLog'); }); /** * Given: 添加体位到已选列表 */ cy.wait('@getMultiplePatientTypes'); cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-Human"]').click(); cy.wait('@getBodyPartForHuman'); cy.get('[data-testid="body-part-selector"]').click(); cy.get('[data-testid="body-part-option-Human_SKULL"]').click(); cy.get('[data-testid="selection-mode-view"]').click(); cy.wait('@getViewsForHumanSkull'); cy.get('[data-testid="view-item-View_DX_H_SKULL_AP"]').click(); /** * When: 切换患者类型 */ cy.get('[data-testid="patient-type-selector"]').click(); cy.get('[data-testid="patient-type-option-SpecialType"]').click(); cy.wait('@getBodyPartForSpecialType'); /** * Then: 验证控制台输出了清空日志 */ cy.get('@consoleLog').should('be.calledWith', '患者类型已变更,已选择体位列表已清空'); }); });