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

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,16 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./EtmfPlaceholder.vue"), "utf8");
describe("EtmfPlaceholder project permissions", () => {
it("hides eTMF create controls and guards dialog submissions with document create permission", () => {
const source = readSource();
expect(source).toContain('v-if="canCreate"');
expect(source).toContain('v-if="canCreate && selectedNode"');
expect(source).toContain("if (!canCreate.value)");
expect(source).not.toContain(':disabled="!canCreate"');
});
});
@@ -1,71 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./MaterialEquipment.vue"), "utf8");
describe("MaterialEquipment project permissions", () => {
it("projects material equipment create update and delete permissions into all write actions", () => {
const source = readSource();
expect(source).toContain("useAuthStore");
expect(source).toContain("isSystemAdmin");
expect(source).toContain("isApiPermissionAllowed");
expect(source).toContain('["material_equipments:create"]');
expect(source).toContain('["material_equipments:update"]');
expect(source).toContain('["material_equipments:delete"]');
expect(source).toContain('v-if="canCreate"');
expect(source).toContain('v-if="canUpdate"');
expect(source).toContain('v-if="canDelete"');
expect(source).toContain("if (!canDelete.value)");
});
it("keeps the visible table columns evenly distributed", () => {
const source = readSource();
expect(source).toContain('class="equipment-table"');
expect(source).toContain('style="width: 100%"');
expect(source).toContain('table-layout="fixed"');
expect(source).toContain('label="操作"');
expect(source).not.toMatch(/<el-table-column[^>]+prop="(?:name|specModel|unit|brand)"[^>]+(?:width|min-width)=/);
});
it("opens the detail page from row clicks instead of a detail action button", () => {
const source = readSource();
expect(source).toContain('@row-click="onRowClick"');
expect(source).toContain(':row-class-name="equipmentRowClass"');
expect(source).toContain('name: "MaterialEquipmentDetail"');
expect(source).toContain("equipmentId: row.id");
expect(source).not.toContain(">详情</el-button>");
expect(source).toContain('@click.stop="openEdit(row)"');
expect(source).toContain('@click.stop="removeRow(row)"');
});
it("uploads qualification files as equipment attachments after saving the drawer form", () => {
const source = readSource();
const calibrationIndex = source.indexOf("校准设置");
const qualificationIndex = source.indexOf("附件");
expect(calibrationIndex).toBeGreaterThan(-1);
expect(qualificationIndex).toBeGreaterThan(calibrationIndex);
expect(source).toContain("AttachmentList");
expect(source).toContain('ref="attachmentPanelRef"');
expect(source).toContain('entity-type="material_equipment"');
expect(source).toContain(':entity-id="editingId"');
expect(source).toContain(':mode="\'upload\'"');
expect(source).toContain("attachmentPanelRef.value?.pendingSnapshot() || []");
expect(source).toContain("await attachmentPanelRef.value?.uploadPending(equipmentId)");
expect(source).toContain("uploadPending");
expect(source).toContain("material_equipment");
expect(source).not.toContain("uploadAttachment");
expect(source).not.toContain("pendingFiles");
expect(source).not.toContain("qualificationRows");
expect(source).not.toContain("material_equipment_production_permit");
expect(source).not.toContain("material_equipment_tech_index");
expect(source).not.toContain("资质文件");
expect(source).not.toContain("生产许可证");
expect(source).not.toContain("技术指标");
expect(source).not.toContain("handleUploadChange");
});
});
-56
View File
@@ -1,56 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = (relativePath: string) => readFileSync(resolve(__dirname, relativePath), "utf8");
describe("precautions project permissions", () => {
it("splits list create and delete controls by backend operation permissions", () => {
const source = readSource("./Precautions.vue");
expect(source).toContain('const canCreatePrecaution = computed(() => can("precautions.create"))');
expect(source).toContain('const canDeletePrecaution = computed(() => can("precautions.delete"))');
expect(source).toContain('v-if="canCreatePrecaution"');
expect(source).toContain('v-if="canDeletePrecaution"');
expect(source).toContain("if (!canDeletePrecaution.value)");
expect(source).not.toContain("canWritePrecautions");
});
});
describe("precautions drawer editor", () => {
it("opens create form in a drawer instead of routing to the form page", () => {
const source = readSource("./Precautions.vue");
expect(source).toContain("PrecautionEditorDrawer");
expect(source).toContain("precautionDrawerVisible");
expect(source).not.toContain('router.push("/knowledge/precautions/new")');
});
it("uses selects for site and level fields in the create drawer", () => {
const source = readSource("../knowledge/PrecautionEditorDrawer.vue");
expect(source).toContain('<el-select v-model="form.site_name"');
expect(source).toContain('v-for="site in sites"');
expect(source).toContain(':value="site.name"');
expect(source).toContain('<el-select v-model="form.level"');
expect(source).toContain("const levelOptions = precautionLevelOptions");
expect(source).toContain('v-for="level in levelOptions"');
expect(source).not.toContain('<el-input v-model="form.site_name"');
expect(source).not.toContain('<el-input v-model="form.level"');
});
it("moves precaution attachment upload into the editor drawer", () => {
const drawer = readSource("../knowledge/PrecautionEditorDrawer.vue");
const detail = readSource("../knowledge/PrecautionDetail.vue");
expect(drawer).toContain("AttachmentList");
expect(drawer).toContain('ref="attachmentPanelRef"');
expect(drawer).toContain(':entity-type="\'precaution\'"');
expect(drawer).toContain(':mode="\'upload\'"');
expect(drawer).toContain("attachments: attachmentPanelRef.value?.pendingSnapshot() || []");
expect(drawer).toContain("await attachmentPanelRef.value?.uploadPending(id)");
expect(detail).toContain(":hide-uploader=\"true\"");
expect(detail).toContain(":refresh-key=\"attachmentRefreshKey\"");
expect(detail).toContain("attachmentRefreshKey.value += 1");
});
});
@@ -1,15 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./ProjectMilestones.vue"), "utf8");
describe("ProjectMilestones project permissions", () => {
it("hides edit controls and blocks saves without project milestone update permission", () => {
const source = readSource();
expect(source).toContain('const canUpdateMilestones = computed(() => can("project.milestones.update"))');
expect(source).toContain('v-if="canUpdateMilestones"');
expect(source).toContain("if (!canUpdateMilestones.value)");
});
});
@@ -1,34 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readRiskIssueMonitoringVisits = () => readFileSync(resolve(__dirname, "./RiskIssueMonitoringVisits.vue"), "utf8");
describe("RiskIssueMonitoringVisits permissions", () => {
it("gates monitoring visit issue actions by concrete API permissions", () => {
const source = readRiskIssueMonitoringVisits();
expect(source).toContain("useAuthStore");
expect(source).toContain("isSystemAdmin");
expect(source).toContain("getProjectRole");
expect(source).toContain("isApiPermissionAllowed");
[
"monitoring_issues:read",
"monitoring_issues:read",
"monitoring_issues:create",
"monitoring_issues:update",
"monitoring_issues:delete",
].forEach((operationKey) => {
expect(source).toContain(operationKey);
});
expect(source).toContain('v-if="canCreateIssue"');
expect(source).toContain('v-if="canListIssues"');
expect(source).toContain('v-if="canReadIssue"');
expect(source).toContain('v-if="canUpdateIssue"');
expect(source).toContain('v-if="canDeleteIssue"');
expect(source).toContain('v-if="canSaveIssue"');
expect(source).not.toContain(':disabled="!canSaveIssue"');
expect(source).toContain("const canSaveIssue = computed(() => (formMode.value === \"edit\" ? canUpdateIssue.value : canCreateIssue.value));");
expect(source).toContain('ElMessage.warning("权限不足")');
});
});
@@ -1,17 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readRiskIssueSae = () => readFileSync(resolve(__dirname, "./RiskIssueSae.vue"), "utf8");
describe("RiskIssueSae status", () => {
it("only exposes follow-up and closed statuses in the risk issue status column", () => {
const source = readRiskIssueSae();
expect(source).toContain("const aeStatusOptions = [");
expect(source).toContain('{ value: "FOLLOW_UP", label: TEXT.enums.aeStatus.FOLLOW_UP }');
expect(source).toContain('{ value: "CLOSED", label: TEXT.enums.aeStatus.CLOSED }');
expect(source).toContain("normalizeAeStatus");
expect(source).not.toContain("Object.entries(TEXT.enums.aeStatus)");
});
});
@@ -1,56 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./StartupFeasibilityEthics.vue"), "utf8");
describe("StartupFeasibilityEthics project permissions", () => {
it("hides initiation and ethics create/delete actions by their backend operation permissions", () => {
const source = readSource();
expect(source).toContain('canUseApiPermission("startup_initiation:create")');
expect(source).toContain('canUseApiPermission("startup_initiation:read")');
expect(source).toContain('canUseApiPermission("startup_initiation:update")');
expect(source).toContain('canUseApiPermission("startup_initiation:delete")');
expect(source).toContain('canUseApiPermission("startup_ethics:read")');
expect(source).toContain('canUseApiPermission("startup_ethics:create")');
expect(source).toContain('canUseApiPermission("startup_ethics:update")');
expect(source).toContain('canUseApiPermission("startup_ethics:delete")');
expect(source).toContain('v-if="canReadFeasibility"');
expect(source).toContain('v-if="canReadEthics"');
expect(source).toContain("if (!canReadFeasibility.value)");
expect(source).toContain("if (!canReadEthics.value)");
expect(source).toContain('v-if="activeTab === \'feasibility\' && canCreateFeasibility"');
expect(source).toContain('v-if="canUpdateFeasibility"');
expect(source).toContain('v-if="canDeleteFeasibility"');
expect(source).toContain('v-if="activeTab === \'ethics\' && canCreateEthics"');
expect(source).toContain('v-if="canUpdateEthics"');
expect(source).toContain('v-if="canDeleteEthics"');
});
});
describe("StartupFeasibilityEthics drawer editors", () => {
it("opens initiation and ethics create forms in drawers instead of routing to form pages", () => {
const source = readSource();
expect(source).toContain("FeasibilityEditorDrawer");
expect(source).toContain("EthicsEditorDrawer");
expect(source).toContain("feasibilityDrawerVisible");
expect(source).toContain("ethicsDrawerVisible");
expect(source).not.toContain('router.push("/startup/feasibility/new")');
expect(source).not.toContain('router.push("/startup/ethics/new")');
});
it("opens initiation and ethics edit drawers from table action columns", () => {
const source = readSource();
expect(source).toContain(':record-id="editingFeasibilityId"');
expect(source).toContain(':record-id="editingEthicsId"');
expect(source).toContain("const editingFeasibilityId = ref<string | undefined>();");
expect(source).toContain("const editingEthicsId = ref<string | undefined>();");
expect(source).toContain("@click.stop=\"openFeasibilityEditor(scope.row)\"");
expect(source).toContain("@click.stop=\"openEthicsEditor(scope.row)\"");
expect(source).toContain("editingFeasibilityId.value = row?.id;");
expect(source).toContain("editingEthicsId.value = row?.id;");
});
});
@@ -1,32 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readStageNode = () => readFileSync(resolve(__dirname, "./StageNode.vue"), "utf8");
const readCenterProgressRow = () => readFileSync(resolve(__dirname, "./CenterProgressRow.vue"), "utf8");
const readProjectOverview = () => readFileSync(resolve(__dirname, "../ProjectOverview.vue"), "utf8");
describe("project overview stage progress styling", () => {
it("makes the in-progress stage visually distinct from pending stages", () => {
const source = readStageNode();
expect(source).toContain(".stage-in_progress .stage-dot");
expect(source).toContain("border: 3px solid #2f5f86");
expect(source).toContain(".stage-in_progress .stage-dot::after");
expect(source).toContain(".stage-in_progress .stage-dot::before");
expect(source).toContain("animation: progress-halo");
expect(source).toContain(".stage-in_progress .stage-label");
expect(source).toContain("font-weight: 800");
});
it("highlights the active connector and legend consistently", () => {
const rowSource = readCenterProgressRow();
const overviewSource = readProjectOverview();
expect(rowSource).toContain(".connector-in_progress");
expect(rowSource).toContain("height: 3px");
expect(rowSource).toContain("linear-gradient(90deg, #2f5f86");
expect(overviewSource).toContain(".legend-dot.active::after");
expect(overviewSource).toContain("background: #2f5f86");
});
});