|
@@ -0,0 +1,409 @@
|
|
|
+/**
|
|
|
+ * 测试文件: 患者注册 - 切换患者类型清空已选体位
|
|
|
+ * 功能: 验证切换患者类型时自动清空已选择体位列表
|
|
|
+ *
|
|
|
+ * 测试场景:
|
|
|
+ * 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', '患者类型已变更,已选择体位列表已清空');
|
|
|
+ });
|
|
|
+});
|