整理测试代码并清理文档引用

This commit is contained in:
Cheng Zhou
2026-06-22 17:27:11 +08:00
parent 6056c8364a
commit c351ca59c4
75 changed files with 15 additions and 4415 deletions
@@ -1,22 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readForm = () => readFileSync(resolve(__dirname, "./KickoffForm.vue"), "utf8");
const readDetail = () => readFileSync(resolve(__dirname, "./KickoffDetail.vue"), "utf8");
describe("Kickoff member permissions", () => {
it("does not load project members in the form unless the role can read project members", () => {
const source = readForm();
expect(source).toContain('can("project.members.list")');
expect(source).toContain("if (canReadMembers.value)");
});
it("does not load project members in the detail page unless the role can read project members", () => {
const source = readDetail();
expect(source).toContain('can("project.members.list")');
expect(source).toContain("if (canReadMembers.value)");
});
});
@@ -1,76 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const read = (relativePath: string) => readFileSync(resolve(__dirname, relativePath), "utf8");
describe("startup auth editor drawer attachments", () => {
it("uses upload cards for kickoff attachments instead of full attachment tables", () => {
const source = read("./KickoffEditorDrawer.vue");
expect(source).toContain("AttachmentList");
expect(source).toContain('ref="attachmentPanelRef"');
expect(source).toContain(':mode="\'upload\'"');
expect(source).toContain(":entity-groups=\"kickoffAttachmentGroups\"");
expect(source).toContain("attachments: attachmentPanelRef.value?.pendingSnapshot() || []");
expect(source).toContain("await attachmentPanelRef.value?.uploadPending(props.meetingId)");
expect(source).toContain('entityType: "startup_kickoff_minutes"');
expect(source).toContain('entityType: "startup_kickoff_signin"');
expect(source).toContain('entityType: "startup_kickoff_ppt"');
expect(source).toContain('entityType: "startup_kickoff"');
expect(source).not.toContain("PendingAttachmentUpload");
expect(source).not.toContain("attendees");
expect(source).not.toContain("parseAttendees");
expect(source).not.toContain("attendeesPlaceholder");
});
it("does not expose attachments in the training authorization editor", () => {
const source = read("./TrainingEditorDrawer.vue");
expect(source).not.toContain("PendingAttachmentUpload");
expect(source).not.toContain("AttachmentList");
expect(source).not.toContain("training_authorization");
expect(source).not.toContain("attachments:");
expect(source).not.toContain("uploadPending");
});
it("supports batch creating and single-row updating training authorizations in the same drawer", () => {
const source = read("./TrainingEditorDrawer.vue");
expect(source).toContain("recordId?: string");
expect(source).toContain('editorTitle');
expect(source).toContain("createTrainingAuthorization");
expect(source).toContain("updateTrainingAuthorization");
expect(source).toContain("const isCreateMode = computed(() => !props.recordId)");
expect(source).toContain("const canCreateAuth = computed(() => can(\"startup.auth.create\"))");
expect(source).toContain("const canSaveAuth = computed(() => (isCreateMode.value ? canCreateAuth.value : canUpdateAuth.value))");
expect(source).toContain("batchRows");
expect(source).toContain("addBatchRow");
expect(source).toContain("removeBatchRow");
expect(source).toContain("for (const row of batchRows.value)");
});
it("keeps training authorization editor compact and hides site input while preserving site context", () => {
const source = read("./TrainingEditorDrawer.vue");
expect(source).not.toContain(':label="TEXT.common.fields.site"');
expect(source).not.toContain('prop="site_name"');
expect(source).toContain("site_name: props.initialSiteName || form.site_name || null");
expect(source).toContain('class="compact-row-grid"');
expect(source).toContain('class="compact-field compact-field--name"');
expect(source).toContain('class="compact-field compact-field--status"');
expect(source).toContain('class="compact-field compact-field--remark"');
});
it("uses a single-layer batch editor layout for training authorization creation", () => {
const source = read("./TrainingEditorDrawer.vue");
expect(source).toContain('class="form-section training-batch-editor"');
expect(source).toContain('class="batch-editor"');
expect(source).toContain('class="batch-entry"');
expect(source).toContain('class="status-inline"');
expect(source).toContain('class="status-date"');
expect(source).not.toContain('class="batch-row"');
expect(source).not.toContain(".batch-row {");
});
});
@@ -1,125 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const read = (relativePath: string) => readFileSync(resolve(__dirname, relativePath), "utf8");
describe("startup auth form/detail permissions", () => {
it("maps startup auth actions to backend operation permissions", () => {
const source = read("../../utils/permission.ts");
expect(source).toContain('"startup.auth.create": "startup_auth:create"');
expect(source).toContain('"startup.auth.update": "startup_auth:update"');
expect(source).toContain('"startup.auth.delete": "startup_auth:delete"');
});
it("uses create/update permissions in kickoff and training forms", () => {
const kickoffForm = read("./KickoffForm.vue");
const trainingForm = read("./TrainingForm.vue");
expect(kickoffForm).toContain('const canCreateAuth = computed(() => can("startup.auth.create"))');
expect(kickoffForm).toContain('const canUpdateAuth = computed(() => can("startup.auth.update"))');
expect(kickoffForm).toContain("const canSaveAuth = computed(() => (isEdit.value ? canUpdateAuth.value : canCreateAuth.value))");
expect(kickoffForm).toContain("if (!canSaveAuth.value)");
expect(trainingForm).toContain('const canCreateAuth = computed(() => can("startup.auth.create"))');
expect(trainingForm).toContain('const canUpdateAuth = computed(() => can("startup.auth.update"))');
expect(trainingForm).toContain("const canSaveAuth = computed(() => (isEdit.value ? canUpdateAuth.value : canCreateAuth.value))");
expect(trainingForm).toContain("if (!canSaveAuth.value)");
});
it("opens kickoff detail edits in a drawer and keeps training actions split by backend operations", () => {
const source = read("./KickoffDetail.vue");
expect(source).toContain("<KickoffEditorDrawer");
expect(source).toContain("<TrainingEditorDrawer");
expect(source).toContain("editorVisible.value = true");
expect(source).toContain("trainingEditorVisible.value = true");
expect(source).toContain("const handleEditorSaved = () =>");
expect(source).toContain(":refresh-key=\"attachmentRefreshKey\"");
expect(source).toContain("attachmentRefreshKey.value += 1");
expect(source).toContain("const handleTrainingEditorSaved = () =>");
expect(source).toContain('const canCreateAuth = computed(() => can("startup.auth.create"))');
expect(source).toContain('const canUpdateAuth = computed(() => can("startup.auth.update"))');
expect(source).toContain('const canDeleteAuth = computed(() => can("startup.auth.delete"))');
expect(source).toContain('v-if="canUpdateAuth"');
expect(source).toContain('v-if="canCreateAuth || canUpdateAuth || canDeleteAuth"');
expect(source).toContain("if (!canCreateAuth.value)");
expect(source).toContain("if (!canUpdateAuth.value)");
expect(source).toContain("if (!canDeleteAuth.value)");
expect(source).not.toContain("trainingRowDraft");
expect(source).not.toContain("isTrainingEditing");
expect(source).not.toContain("saveTraining");
expect(source).not.toContain("cancelTrainingEdit");
expect(source).not.toContain("editMode");
expect(source).not.toContain("saveEdit");
});
it("opens training detail edits in a drawer without navigating to the old edit page", () => {
const source = read("./TrainingDetail.vue");
expect(source).toContain("<TrainingEditorDrawer");
expect(source).toContain("editorVisible.value = true");
expect(source).toContain("const handleEditorSaved = () =>");
expect(source).toContain('const canUpdateAuth = computed(() => can("startup.auth.update"))');
expect(source).toContain('v-if="canUpdateAuth"');
expect(source).toContain("if (!canUpdateAuth.value)");
expect(source).not.toContain("AttachmentList");
expect(source).not.toContain("training_authorization");
expect(source).not.toContain("attachmentRefreshKey");
expect(source).not.toContain("router.push(`/startup/training/${recordId}/edit`)");
});
it("renders kickoff attachments as one aggregated table with attachment type labels", () => {
const source = read("./KickoffDetail.vue");
expect(source).toContain(":entity-groups=\"kickoffAttachmentGroups\"");
expect(source).not.toContain("v-for=\"group in kickoffAttachmentGroups\"");
expect(source).toContain('label: TEXT.modules.startupMeetingAuth.kickoffMinutesLabel');
expect(source).toContain('label: TEXT.modules.startupMeetingAuth.kickoffSignInLabel');
expect(source).toContain('label: TEXT.modules.startupMeetingAuth.kickoffPptLabel');
expect(source).toContain('label: TEXT.modules.startupMeetingAuth.kickoffOtherLabel');
});
it("allows legacy kickoff and training forms to queue attachments before saving", () => {
const kickoffForm = read("./KickoffForm.vue");
const trainingForm = read("./TrainingForm.vue");
expect(kickoffForm).toContain("<AttachmentList");
expect(kickoffForm).toContain('ref="attachmentPanelRef"');
expect(kickoffForm).toContain(":entity-groups=\"kickoffAttachmentGroups\"");
expect(kickoffForm).toContain(':entity-id="meetingId || \'\'"');
expect(kickoffForm).toContain(':mode="\'upload\'"');
expect(kickoffForm).toContain("await attachmentPanelRef.value?.uploadPending(savedId)");
expect(kickoffForm).not.toContain('v-for="group in kickoffAttachmentGroups"');
expect(kickoffForm).not.toContain("StateEmpty");
expect(trainingForm).toContain("<AttachmentList");
expect(trainingForm).toContain('ref="attachmentPanelRef"');
expect(trainingForm).toContain('entity-type="training_authorization"');
expect(trainingForm).toContain(':entity-id="recordId || \'\'"');
expect(trainingForm).toContain(':mode="\'upload\'"');
expect(trainingForm).toContain("await attachmentPanelRef.value?.uploadPending(savedId)");
expect(trainingForm).not.toContain('v-if="isEdit && recordId"');
expect(trainingForm).not.toContain("StateEmpty");
});
it("shows training and authorization dates under positive status in the training table", () => {
const source = read("./KickoffDetail.vue");
expect(source).toContain("status-dot--yes");
expect(source).toContain("status-dot--no");
expect(source).toContain("scope.row.trained && scope.row.trained_date");
expect(source).toContain("scope.row.authorized && scope.row.authorized_date");
expect(source).toContain('class="status-date"');
expect(source).toContain("scope.row.trained ? TEXT.common.boolean.yes : TEXT.common.boolean.no");
expect(source).toContain("scope.row.authorized ? TEXT.common.boolean.yes : TEXT.common.boolean.no");
});
it("loads training authorizations for the current kickoff site only", () => {
const source = read("./KickoffDetail.vue");
expect(source).toContain("if (!studyId || !siteInfo.name) return;");
expect(source).toContain("listTrainingAuthorizations(studyId, { site_name: siteInfo.name })");
expect(source).not.toContain("listTrainingAuthorizations(studyId);");
});
});
@@ -1,84 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const read = (relativePath: string) => readFileSync(resolve(__dirname, relativePath), "utf8");
describe("startup initiation and ethics form/detail permissions", () => {
it("uses startup initiation create/update permissions in feasibility form and detail", () => {
const formSource = read("./FeasibilityForm.vue");
const detailSource = read("./FeasibilityDetail.vue");
expect(formSource).toContain('const canCreateInitiation = computed(() => can("startup.initiation.create"))');
expect(formSource).toContain('const canUpdateInitiation = computed(() => can("startup.initiation.update"))');
expect(formSource).toContain("const canSaveInitiation = computed(() => (isEdit.value ? canUpdateInitiation.value : canCreateInitiation.value))");
expect(formSource).toContain("if (!canSaveInitiation.value)");
expect(detailSource).toContain('const canUpdateInitiation = computed(() => can("startup.initiation.update"))');
expect(detailSource).toContain('v-if="canUpdateInitiation"');
expect(detailSource).toContain("if (!canUpdateInitiation.value)");
});
it("uses startup ethics create/update permissions in ethics form and detail", () => {
const formSource = read("./EthicsForm.vue");
const detailSource = read("./EthicsDetail.vue");
expect(formSource).toContain('const canCreateEthics = computed(() => can("startup.ethics.create"))');
expect(formSource).toContain('const canUpdateEthics = computed(() => can("startup.ethics.update"))');
expect(formSource).toContain("const canSaveEthics = computed(() => (isEdit.value ? canUpdateEthics.value : canCreateEthics.value))");
expect(formSource).toContain("if (!canSaveEthics.value)");
expect(detailSource).toContain('const canUpdateEthics = computed(() => can("startup.ethics.update"))');
expect(detailSource).toContain('v-if="canUpdateEthics"');
expect(detailSource).toContain("if (!canUpdateEthics.value)");
});
it("keeps detail attachment areas display-only and moves upload into editor drawers", () => {
const feasibilityDetail = read("./FeasibilityDetail.vue");
const ethicsDetail = read("./EthicsDetail.vue");
const feasibilityDrawer = read("./FeasibilityEditorDrawer.vue");
const ethicsDrawer = read("./EthicsEditorDrawer.vue");
expect(feasibilityDetail).toContain(":hide-uploader=\"true\"");
expect(ethicsDetail).toContain(":hide-uploader=\"true\"");
expect(feasibilityDrawer).toContain("AttachmentList");
expect(feasibilityDrawer).toContain('ref="attachmentPanelRef"');
expect(feasibilityDrawer).toContain('entity-type="startup_feasibility"');
expect(feasibilityDrawer).toContain(":entity-id=\"recordId || ''\"");
expect(feasibilityDrawer).toContain(':mode="\'upload\'"');
expect(feasibilityDrawer).toContain("attachments: attachmentPanelRef.value?.pendingSnapshot() || []");
expect(feasibilityDrawer).toContain("await attachmentPanelRef.value?.uploadPending(id)");
expect(feasibilityDrawer).not.toContain("PendingAttachmentUpload");
expect(ethicsDrawer).toContain("AttachmentList");
expect(ethicsDrawer).toContain('ref="attachmentPanelRef"');
expect(ethicsDrawer).toContain('entity-type="startup_ethics"');
expect(ethicsDrawer).toContain(":entity-id=\"recordId || ''\"");
expect(ethicsDrawer).toContain(':mode="\'upload\'"');
expect(ethicsDrawer).toContain("attachments: attachmentPanelRef.value?.pendingSnapshot() || []");
expect(ethicsDrawer).toContain("await attachmentPanelRef.value?.uploadPending(id)");
expect(ethicsDrawer).not.toContain("PendingAttachmentUpload");
expect(feasibilityDrawer).not.toContain('class="attachment-group"');
expect(ethicsDrawer).not.toContain('class="attachment-group"');
});
it("allows legacy feasibility and ethics forms to queue attachments before the record exists", () => {
const feasibilityForm = read("./FeasibilityForm.vue");
const ethicsForm = read("./EthicsForm.vue");
expect(feasibilityForm).toContain("<AttachmentList");
expect(feasibilityForm).toContain('ref="attachmentPanelRef"');
expect(feasibilityForm).toContain('entity-type="startup_feasibility"');
expect(feasibilityForm).toContain(':entity-id="recordId || \'\'"');
expect(feasibilityForm).toContain(':mode="\'upload\'"');
expect(feasibilityForm).toContain("await attachmentPanelRef.value?.uploadPending(savedId)");
expect(feasibilityForm).not.toContain('v-if="isEdit && recordId"');
expect(feasibilityForm).not.toContain("StateEmpty");
expect(ethicsForm).toContain("<AttachmentList");
expect(ethicsForm).toContain('ref="attachmentPanelRef"');
expect(ethicsForm).toContain('entity-type="startup_ethics"');
expect(ethicsForm).toContain(':entity-id="recordId || \'\'"');
expect(ethicsForm).toContain(':mode="\'upload\'"');
expect(ethicsForm).toContain("await attachmentPanelRef.value?.uploadPending(savedId)");
expect(ethicsForm).not.toContain('v-if="isEdit && recordId"');
expect(ethicsForm).not.toContain("StateEmpty");
});
});