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