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

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,65 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readProjectMembers = () => readFileSync(resolve(__dirname, "./ProjectMembers.vue"), "utf8");
describe("ProjectMembers user directory access", () => {
it("does not load the admin-only global user directory", () => {
const source = readProjectMembers();
expect(source).not.toContain("fetchUsers");
expect(source).not.toContain("../../api/users");
expect(source).toContain('const canListMembers = computed(() => permission.can("project.members.list"));');
expect(source).toContain('const canListMemberCandidates = computed(() => permission.can("project.members.candidates"));');
expect(source).toContain('const canCreateMember = computed(() => permission.can("project.members.create"));');
expect(source).toContain('const canUpdateMember = computed(() => permission.can("project.members.update"));');
expect(source).toContain('const canDeleteMember = computed(() => permission.can("project.members.delete"));');
expect(source).toContain("if (!canListMembers.value");
expect(source).toContain("if (!canListMemberCandidates.value");
expect(source).toContain("return;");
expect(source).toContain('v-if="canAddMember"');
expect(source).toContain("listMemberCandidates(projectId.value, { limit: 500 })");
expect(source).not.toContain("project.members.manage");
});
it("uses the member API embedded user data when the global directory is unavailable", () => {
const source = readProjectMembers();
expect(source).toContain("users.value = members.value");
expect(source).toContain(".map((member) => member.user)");
expect(source).toContain("users.value.find((u) => u.id === m.user_id) || m.user");
});
it("prevents project managers from editing themselves or assigning higher roles", () => {
const source = readProjectMembers();
expect(source).toContain("const roleRank: Record<string, number>");
expect(source).toContain("if (row.user_id === auth.user?.id) return false;");
expect(source).toContain("if (row.user?.is_admin) return false;");
expect(source).toContain("const canAssignRole = (role: string)");
expect(source).toContain(":disabled=\"!canAssignRole(role.value)\"");
expect(source).toContain(':disabled="!canEditMember(scope.row)');
expect(source).toContain(':disabled="!canDeleteProjectMember(scope.row)"');
});
it("uses permission template names for project role display options", () => {
const source = readProjectMembers();
expect(source).toContain("useRoleTemplateMeta");
expect(source).toContain("const { roleLabel, roleOptionsFor, loadRoleTemplates } = useRoleTemplateMeta();");
expect(source).toContain("const roleOptions = computed(() => roleOptionsFor(ROLE_KEYS));");
expect(source).toContain("{{ roleLabel(scope.row.role_in_study) }}");
expect(source).not.toContain("displayEnum(TEXT.enums.userRole, scope.row.role_in_study)");
expect(source).not.toContain(':label="TEXT.enums.userRole.PM"');
});
it("offers QA and CTA as project member role presets", () => {
const source = readProjectMembers();
expect(source).toContain('"QA"');
expect(source).toContain('"CTA"');
expect(source).toContain("QA: 60");
expect(source).toContain("CTA: 40");
});
});
-31
View File
@@ -1,31 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSiteForm = () => readFileSync(resolve(__dirname, "./SiteForm.vue"), "utf8");
const readSiteTypes = () => readFileSync(resolve(__dirname, "../../types/api.ts"), "utf8");
describe("Admin site form phone field", () => {
it("hydrates and submits the phone field", () => {
const formSource = readSiteForm();
const typeSource = readSiteTypes();
expect(formSource).toContain("form.phone = props.site.phone || (props.site as any)?.contact_phone || \"\"");
expect(formSource).toContain("phone: form.phone");
expect(formSource).toContain('v-model="form.phone"');
expect(typeSource).toContain("phone?: string | null;");
expect(typeSource).toContain("contact_phone?: string | null;");
});
});
describe("Admin site PM user loading", () => {
it("uses project member embedded user data instead of the global user directory", () => {
const sitesSource = readFileSync(resolve(__dirname, "./Sites.vue"), "utf8");
const formSource = readSiteForm();
expect(sitesSource).not.toContain("../../api/users");
expect(sitesSource).not.toContain("fetchUsers");
expect(sitesSource).toContain(".map((member) => member.user)");
expect(formSource).toContain("m.user?.full_name");
});
});
@@ -1,17 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./SystemMonitoringPage.vue"), "utf8");
describe("SystemMonitoringPage", () => {
it("uses a fixed viewport shell so monitoring tabs manage their own scrolling", () => {
const source = readSource();
expect(source).toContain("display: flex");
expect(source).toContain("height: calc(100dvh - 48px)");
expect(source).toContain("min-height: 0");
expect(source).toContain("overflow: hidden");
expect(source).not.toContain("min-height: calc(100dvh - 52px)");
});
});
@@ -1,17 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readResetForm = () => readFileSync(resolve(__dirname, "./UserResetPassword.vue"), "utf8");
describe("Admin reset password autofill", () => {
it("disables browser autofill for confirm username and manual password", () => {
const source = readResetForm();
expect(source).toContain('autocomplete="off" name="ctms-reset-confirm-username"');
expect(source).toContain('autocomplete="new-password"');
expect(source).toContain('name="ctms-reset-temp-password"');
expect(source).toContain('name="ctms-reset-manual-password"');
expect(source).toContain(":prop=\"form.mode === 'auto' ? 'tempPassword' : 'manualPassword'\"");
});
});
-111
View File
@@ -1,111 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readUsersView = () => readFileSync(resolve(__dirname, "./Users.vue"), "utf8");
const readUserForm = () => readFileSync(resolve(__dirname, "./UserForm.vue"), "utf8");
const readLocale = () => readFileSync(resolve(__dirname, "../../locales/zh-CN.ts"), "utf8");
describe("Admin users table labels", () => {
it("shows clinical_department as 科室 in the account management table", () => {
const viewSource = readUsersView();
const localeSource = readLocale();
expect(viewSource).toContain(':label="TEXT.modules.adminUsers.clinicalDepartmentLabel"');
expect(viewSource).not.toContain(':label="TEXT.common.fields.clinicalDepartment"');
expect(localeSource).toContain('clinicalDepartmentLabel: "科室"');
});
});
describe("Admin users table layout", () => {
it("uses a wider name column, even remaining columns, and compact icon actions", () => {
const viewSource = readUsersView();
expect(viewSource).toContain('table-layout="fixed"');
expect(viewSource).toContain(':label="TEXT.common.fields.name" width="360"');
expect(viewSource).not.toContain('width="20%"');
expect(viewSource).not.toContain('width="180"');
expect(viewSource).not.toContain('width="160"');
expect(viewSource).not.toContain('width="100"');
expect(viewSource).not.toContain('fixed="right"');
expect(viewSource).toContain('class="action-btn"');
expect(viewSource).toContain("width: 28px;");
expect(viewSource).toContain("gap: 4px;");
});
it("shows names without avatar icons in the account management table", () => {
const viewSource = readUsersView();
expect(viewSource).not.toContain("user-avatar");
expect(viewSource).not.toContain("getInitials(scope.row.full_name)");
expect(viewSource).not.toContain("linear-gradient(135deg, #3f8f6b, #2d7a5a)");
});
});
describe("Admin users summary header", () => {
it("keeps the management summary header compact", () => {
const viewSource = readUsersView();
expect(viewSource).toContain("padding: 10px 16px;");
expect(viewSource).toContain("padding: 9px 12px;");
expect(viewSource).toContain("width: 32px;");
expect(viewSource).toContain("height: 32px;");
expect(viewSource).toContain("font-size: 20px;");
expect(viewSource).toContain("font-size: 11px;");
});
});
describe("Admin users filters", () => {
it("applies keyword automatically and status filters from the first page", () => {
const viewSource = readUsersView();
expect(viewSource).toContain("@keyup.enter=\"applyFilters\"");
expect(viewSource).toContain("@change=\"applyFilters\"");
expect(viewSource).not.toContain("@click=\"applyFilters\"");
expect(viewSource).not.toContain(":icon=\"Refresh\"");
expect(viewSource).not.toContain("Refresh } from");
expect(viewSource).toContain("watch(searchKeyword, () => {");
expect(viewSource).toContain("scheduleKeywordSearch();");
expect(viewSource).toContain("keywordSearchTimer = setTimeout(() => {");
expect(viewSource).toContain("}, 300);");
expect(viewSource).toContain("page.value = 1");
expect(viewSource).toContain("keyword: searchKeyword.value || undefined");
expect(viewSource).toContain("status: statusFilter.value || undefined");
});
});
describe("Admin user form autocomplete", () => {
it("prevents browser credential autofill when creating a user", () => {
const formSource = readUserForm();
expect(formSource).toContain('autocomplete="off"');
expect(formSource).toContain('name="ctms-create-initial-password"');
expect(formSource).toContain('autocomplete="new-password"');
expect(formSource).not.toContain("current_password");
expect(formSource).not.toContain("confirmPassword");
expect(formSource).not.toContain("TEXT.modules.profile.currentPassword");
expect(formSource).not.toContain("TEXT.modules.profile.confirmPassword");
});
});
describe("Admin user create form password", () => {
it("requires an initial password before creating an account", () => {
const formSource = readUserForm();
expect(formSource).toContain('v-if="!user" :label="TEXT.modules.adminUsers.passwordInitial" prop="password"');
expect(formSource).toContain("TEXT.modules.adminUsers.passwordInitialRequired");
expect(formSource).toContain("/^(?=.*[A-Za-z])(?=.*\\d).{8,}$/");
});
});
describe("Admin user edit form defaults", () => {
it("hydrates the selected user immediately when the dialog is mounted open", () => {
const formSource = readUserForm();
expect(formSource).toContain("immediate: true");
expect(formSource).not.toContain("current_password");
expect(formSource).not.toContain("confirmPassword");
expect(formSource).not.toContain("TEXT.modules.profile.currentPassword");
expect(formSource).not.toContain("TEXT.modules.profile.confirmPassword");
});
});