import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; const readSubjectDetail = () => readFileSync(resolve(__dirname, "./SubjectDetail.vue"), "utf8"); describe("SubjectDetail medication adherence", () => { it("adds a medication adherence tab with editable actual count", () => { const source = readSubjectDetail(); expect(source).toContain('name="medicationAdherence"'); expect(source).toContain("actual_medication_count"); expect(source).toContain("saveMedicationAdherence"); expect(source).toContain("
用药依从性
"); expect(source).toContain("实际用药次数为试验期间实际药物暴露次数"); expect(source).toContain("75%~125% 范围内(包含边界值)"); }); it("uses baseline and termination visit actual dates for adherence calculation", () => { const source = readSubjectDetail(); expect(source).toContain("const medicationStartDate = computed(() => detail.baseline_date || \"\");"); expect(source).toContain('new Set(["终止治疗", "提前终止"])'); expect(source).toContain("return Math.floor((end.getTime() - start.getTime()) / 86400000) + 1;"); }); it("marks adherence outside 75 to 125 percent as abnormal", () => { const source = readSubjectDetail(); expect(source).toContain("if (rate < 75 || rate > 125) return \"danger\";"); expect(source).not.toContain("if (rate < 80 || rate > 120) return \"danger\";"); }); it("provides a dedicated early termination workflow", () => { const source = readSubjectDetail(); expect(source).toContain("openEarlyTerminationDialog"); expect(source).toContain("createEarlyTerminationVisit"); expect(source).toContain("return \"不适用\";"); expect(source).toContain("getVisitStatusLabel"); expect(source).toContain("ElMessage.success(TEXT.common.messages.saveSuccess);"); expect(source).toContain("提前终止已保存,刷新数据失败,请手动刷新页面"); expect(source).toContain("提前终止表示受试者在方案最后一个计划访视窗口期前停止治疗"); expect(source).toContain("validateEarlyTerminationDateInput"); expect(source).toContain("提前终止日期必须早于最后计划访视窗口期开始日"); expect(source).toContain("getVisitPlannedDateLabel"); expect(source).toContain("getVisitWindowRangeLabel"); expect(source).toContain("~"); expect(source).toContain("row.window_start === row.planned_date && row.window_end === row.planned_date"); expect(source).toContain("return \"不适用\";"); }); it("gates participant subresource mutations by concrete API permissions", () => { const source = readSubjectDetail(); expect(source).toContain("isApiPermissionAllowed"); expect(source).toContain("getProjectRole"); expect(source).toContain("isSystemAdmin"); [ "subjects:update", "subject_histories:read", "subject_histories:create", "subject_histories:update", "subject_histories:delete", "visits:list", "visits:create", "visits:update", "visits:delete", "subject_aes:create", "subject_aes:read", "subject_aes:update", "subject_aes:delete", "subject_pds:create", "subject_pds:list", "subject_pds:update", "subject_pds:delete", ].forEach((operationKey) => { expect(source).toContain(operationKey); }); expect(source).toContain("v-if=\"currentTabAction.allowed\""); expect(source).toContain("v-if=\"canReadHistory\""); expect(source).toContain("v-if=\"canReadVisit\""); expect(source).toContain("v-if=\"canListAe\""); expect(source).toContain("v-if=\"canListPd\""); expect(source).toContain("v-if=\"canUpdateHistory || canDeleteHistory\""); expect(source).toContain("v-if=\"canUpdateVisit || canDeleteVisit\""); expect(source).toContain("v-if=\"canUpdateAe || canDeleteAe\""); expect(source).toContain("v-if=\"canUpdatePd || canDeletePd\""); expect(source).toContain("const canSaveHistory = computed(() => (historyEditingId.value ? canUpdateHistory.value : canCreateHistory.value));"); expect(source).toContain(':can-create="canCreateVisit"'); expect(source).toContain(':can-update="canUpdateVisit"'); expect(source).toContain("const canSaveAe = computed(() => (aeEditingId.value ? canUpdateAe.value : canCreateAe.value));"); expect(source).toContain("const canSavePd = computed(() => (pdEditingId.value ? canUpdatePd.value : canCreatePd.value));"); expect(source).toContain("const canReadHistory = computed(() => canUseApiPermission(\"subject_histories:read\"));"); expect(source).toContain("const canReadVisit = computed(() => canUseApiPermission(\"visits:list\"));"); expect(source).toContain("if (!canReadHistory.value)"); expect(source).toContain("if (!canReadVisit.value)"); expect(source).toContain("const canCreateAe = computed(() => canUseApiPermission(\"subject_aes:create\"));"); expect(source).toContain("const canUpdateAe = computed(() => canUseApiPermission(\"subject_aes:update\"));"); expect(source).toContain("const canDeleteAe = computed(() => canUseApiPermission(\"subject_aes:delete\"));"); expect(source).toContain("const canListAe = computed(() => canUseApiPermission(\"subject_aes:read\"));"); expect(source).toContain("const canListPd = computed(() => canUseApiPermission(\"subject_pds:list\"));"); }); }); describe("SubjectDetail drawer editor", () => { it("opens participant basic information edit in the shared drawer editor", () => { const source = readSubjectDetail(); expect(source).toContain("SubjectEditorDrawer"); expect(source).toContain("subjectEditorVisible"); expect(source).toContain("@success=\"handleSubjectEditorSuccess\""); expect(source).not.toContain("subjectEditing"); expect(source).not.toContain("saveSubjectEdit"); }); it("uses drawer editors for participant subresource create and edit flows", () => { const source = readSubjectDetail(); expect(source).toContain("VisitEditorDrawer"); expect(source).toContain("adherenceDrawerVisible"); expect(source).toContain("aeDrawerVisible"); expect(source).toContain("pdDrawerVisible"); expect(source).toContain("openVisitDrawer"); expect(source).toContain("openAeDrawer"); expect(source).toContain("openPdDrawer"); expect(source).toContain("openMedicationAdherenceDrawer"); expect(source).not.toContain("visitDialogVisible"); expect(source).not.toContain("aeDialogVisible"); expect(source).not.toContain("pdDialogVisible"); expect(source).not.toContain("adherenceEditing"); expect(source).not.toContain(" { const readVisitEditorDrawer = () => readFileSync(resolve(__dirname, "./VisitEditorDrawer.vue"), "utf8"); it("keeps create and edit visit flows on the same business fields", () => { const source = readVisitEditorDrawer(); expect(source).not.toContain("