Files
ctms/frontend/src/views/subjects/SubjectDetail.test.ts
T
2026-06-10 15:20:06 +08:00

152 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("<div class=\"editor-title\">用药依从性</div>");
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("<el-dialog v-if=\"visitDialogVisible\"");
expect(source).not.toContain("<el-dialog v-if=\"aeDialogVisible\"");
expect(source).not.toContain("<el-dialog v-if=\"pdDialogVisible\"");
});
});
describe("VisitEditorDrawer business form", () => {
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("<template v-if=\"isEditing\">");
expect(source).not.toContain("<template v-else>");
expect(source).toContain("v-model=\"form.actual_date\"");
expect(source).toContain("v-model=\"form.notes\"");
expect(source).toContain("actual_date: form.actual_date || null");
expect(source).toContain("notes: form.notes || null");
});
});