import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; const readProjectDetail = () => readFileSync(resolve(__dirname, "./ProjectDetail.vue"), "utf8"); describe("ProjectDetail setup draft publish workflow", () => { it("marks the draft server-synced after a successful server draft save", () => { const source = readProjectDetail(); const functionIndex = source.indexOf("const persistSetupDraft = async"); const successIndex = source.indexOf("applySetupResponseMeta(data);", functionIndex); const syncedIndex = source.indexOf("markSetupServerSynced();", successIndex); expect(functionIndex).toBeGreaterThan(-1); expect(successIndex).toBeGreaterThan(-1); expect(syncedIndex).toBeGreaterThan(successIndex); }); it("reconciles stale dirty flags before opening the publish dialog", () => { const source = readProjectDetail(); const functionIndex = source.indexOf("const publishConfigNow = async"); const reconcileIndex = source.indexOf("reconcileDraftSyncStateBeforePublish();", functionIndex); const unsavedGuardIndex = source.indexOf("if (hasSetupDraftUnsavedChanges.value)", functionIndex); expect(functionIndex).toBeGreaterThan(-1); expect(reconcileIndex).toBeGreaterThan(functionIndex); expect(unsavedGuardIndex).toBeGreaterThan(reconcileIndex); }); it("uses an empty project snapshot as the first publish diff baseline", () => { const source = readProjectDetail(); const computedIndex = source.indexOf("const projectPublishCompareBase = computed"); const emptyBaselineIndex = source.indexOf("return isFirstPublish.value ? emptyProjectInfoDraft() : null;", computedIndex); expect(computedIndex).toBeGreaterThan(-1); expect(emptyBaselineIndex).toBeGreaterThan(computedIndex); }); it("clears the full setup draft including project info", () => { const source = readProjectDetail(); const functionIndex = source.indexOf("const handleClearDraft = async"); const confirmTextIndex = source.indexOf("确认清空当前立项配置草稿的所有数据?该操作不会影响已发布版本。", functionIndex); const applyServerDataIndex = source.indexOf("applySetupDraft(clone(data.data));", functionIndex); const clearProjectDraftIndex = source.indexOf("clearLocalProjectDraft();", applyServerDataIndex); expect(functionIndex).toBeGreaterThan(-1); expect(confirmTextIndex).toBeGreaterThan(functionIndex); expect(applyServerDataIndex).toBeGreaterThan(confirmTextIndex); expect(clearProjectDraftIndex).toBeGreaterThan(applyServerDataIndex); expect(source.indexOf("applySetupDraft(createDefaultSetupDraft(siteOptions.value));", functionIndex)).toBe(-1); }); it("applies setup drafts atomically including empty project info", () => { const source = readProjectDetail(); const functionIndex = source.indexOf("const applySetupDraft = (draft: SetupConfigDraft) => {"); const nextFunctionIndex = source.indexOf("const loadLocalSetupDraft = ", functionIndex); const body = source.slice(functionIndex, nextFunctionIndex); expect(functionIndex).toBeGreaterThan(-1); expect(body).toContain("setupDraft.projectInfo = projectInfo;"); expect(body).toContain("applyProjectInfoDraft(projectInfo);"); expect(body).not.toContain("shouldApplyProjectInfo"); expect(body).not.toContain("isSetupDraftEffectivelyEmpty(setupDraft)"); }); it("requires projectInfo in setup draft shape", () => { const source = readProjectDetail(); const functionIndex = source.indexOf("const isSetupDraftShape = (value: unknown): value is SetupConfigDraft => {"); const nextFunctionIndex = source.indexOf("const isProjectPublishSnapshotShape = ", functionIndex); const body = source.slice(functionIndex, nextFunctionIndex); expect(functionIndex).toBeGreaterThan(-1); expect(body).toContain("isProjectPublishSnapshotShape(data.projectInfo)"); expect(body).not.toContain("!Object.prototype.hasOwnProperty.call(data, \"projectInfo\")"); }); it("keeps early termination out of regular visit schedule options", () => { const source = readProjectDetail(); expect(source).toContain('const specialVisitOptions = ["筛选访视", "基线访视", "终止治疗"];'); expect(source).toContain("提前终止不作为常规计划访视配置"); }); });