整理测试代码并清理文档引用
This commit is contained in:
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user