mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-14 01:20:03 +00:00
## Summary - Fixes #9319 - Add [fast-check](https://github.com/dubzzz/fast-check) property-based testing with FSM (Finite State Machine) traversal to automatically explore state combinations in the workflow persistence system - Fix a real bug in `saveDraft()` discovered by the FSM test: orphan cleanup in `loadIndex()` could delete a just-written payload when the in-memory cache was empty ## Why this is needed #9317 exposed a class of bug where two independently correct changes interact to cause workflow loss. Conventional unit tests verify specific, hand-picked scenarios and cannot catch these cross-PR interaction bugs. ### AS IS (before) | Aspect | Status | |---|---| | Testing approach | Example-based: developer picks specific inputs and expected outputs | | State coverage | Only explicitly written scenarios are tested | | Cross-interaction bugs | Not detectable — each test runs one isolated path | | Bug in `saveDraft` | Undetected — `loadIndex()` orphan cleanup could delete a just-written payload after `reset()` | ### TO BE (after) | Aspect | Status | |---|---| | Testing approach | Property-based: fast-check generates **200 random command sequences** per run | | State coverage | Random exploration of `SaveDraft → GetDraft → RemoveDraft → MoveDraft → GetMostRecentPath → Reset` combinations | | Cross-interaction bugs | Detected automatically — fast-check shrinks failing sequences to minimal reproductions | | Bug in `saveDraft` | Found and fixed — `loadIndex()` now runs **before** `writePayload()` to prevent orphan cleanup race | ## What fast-check does fast-check is a property-based testing library. Instead of testing "does this specific input produce this specific output?", it tests "does this **property** hold for **all possible inputs**?" For FSM testing specifically, fast-check: 1. Takes a set of **commands** (SaveDraft, GetDraft, RemoveDraft, MoveDraft, GetMostRecentPath, Reset) 2. Generates **random sequences** of these commands 3. Runs each sequence against both a **model** (simplified oracle) and the **real system** (store + localStorage) 4. Verifies **invariants** after every mutating command (index/payload consistency, no orphans, LRU correctness, model agreement) 5. When a failure is found, **shrinks** the sequence to the minimal reproduction Example: the bug this PR fixes was shrunk to just 4 commands: ``` SaveDraft(d.json) → RemoveDraft(d.json) → Reset() → SaveDraft(a.json) ✗ ``` ## Changes | File | Change | |---|---| | `package.json` / `pnpm-workspace.yaml` | Add `fast-check` devDependency | | `draftCacheV2.property.test.ts` | 7 property tests for pure index functions | | `workflowDraftStoreV2.fsm.test.ts` | FSM test: 6 commands, invariant checking, 200 runs | | `workflowDraftStoreV2.ts` | Fix: move `loadIndex()` before `writePayload()` in `saveDraft()` | ## Test plan - [x] `pnpm test:unit` — all 117 persistence tests pass (including 7 property + 1 FSM) - [x] `pnpm typecheck` — clean - [x] `pnpm lint` — clean - [x] Pre-commit hooks pass (format, lint, typecheck) - [x] Pre-push hook passes (knip) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9370-test-add-property-based-FSM-tests-for-workflow-persistence-3196d73d3650813daa98cdd8bef7e975) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
208 lines
7.7 KiB
JSON
208 lines
7.7 KiB
JSON
{
|
|
"name": "@comfyorg/comfyui-frontend",
|
|
"version": "1.42.4",
|
|
"private": true,
|
|
"description": "Official front-end implementation of ComfyUI",
|
|
"homepage": "https://comfy.org",
|
|
"license": "GPL-3.0-only",
|
|
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
|
"type": "module",
|
|
"scripts": {
|
|
"build:desktop": "nx build @comfyorg/desktop-ui",
|
|
"build-storybook": "storybook build",
|
|
"build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js",
|
|
"build:analyze": "cross-env ANALYZE_BUNDLE=true pnpm build",
|
|
"build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' pnpm typecheck && nx build",
|
|
"size:collect": "node scripts/size-collect.js",
|
|
"size:report": "node scripts/size-report.js",
|
|
"collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts",
|
|
"dev:cloud": "cross-env DEV_SERVER_COMFYUI_URL='https://testcloud.comfy.org/' nx serve",
|
|
"dev:desktop": "nx dev @comfyorg/desktop-ui",
|
|
"dev:electron": "cross-env DISTRIBUTION=desktop nx serve --config vite.electron.config.mts",
|
|
"dev:no-vue": "cross-env DISABLE_VUE_PLUGINS=true nx serve",
|
|
"dev": "nx serve",
|
|
"devtools:pycheck": "python3 -m compileall -q tools/devtools",
|
|
"format:check": "oxfmt --check",
|
|
"format": "oxfmt --write",
|
|
"json-schema": "tsx scripts/generate-json-schema.ts",
|
|
"knip:no-cache": "knip",
|
|
"knip": "knip --cache",
|
|
"lint:fix:no-cache": "oxlint src --type-aware --fix && eslint src --fix",
|
|
"lint:fix": "oxlint src --type-aware --fix && eslint src --cache --fix",
|
|
"lint:no-cache": "pnpm exec stylelint '{apps,packages,src}/**/*.{css,vue}' && oxlint src --type-aware && eslint src",
|
|
"lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix",
|
|
"lint:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache",
|
|
"lint": "pnpm stylelint && oxlint src --type-aware && eslint src --cache",
|
|
"lint:desktop": "nx run @comfyorg/desktop-ui:lint",
|
|
"locale": "lobe-i18n locale",
|
|
"oxlint": "oxlint src --type-aware",
|
|
"preinstall": "pnpm dlx only-allow pnpm",
|
|
"prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true",
|
|
"preview": "nx preview",
|
|
"storybook": "nx storybook",
|
|
"storybook:desktop": "nx run @comfyorg/desktop-ui:storybook",
|
|
"stylelint:fix": "stylelint --cache --fix '{apps,packages,src}/**/*.{css,vue}'",
|
|
"stylelint": "stylelint --cache '{apps,packages,src}/**/*.{css,vue}'",
|
|
"test:browser": "pnpm exec nx e2e",
|
|
"test:browser:local": "cross-env PLAYWRIGHT_LOCAL=1 pnpm test:browser",
|
|
"test:unit": "nx run test",
|
|
"typecheck": "vue-tsc --noEmit",
|
|
"typecheck:browser": "vue-tsc --project browser_tests/tsconfig.json",
|
|
"typecheck:desktop": "nx run @comfyorg/desktop-ui:typecheck",
|
|
"zipdist": "node scripts/zipdist.js",
|
|
"clean": "nx reset"
|
|
},
|
|
"dependencies": {
|
|
"@alloc/quick-lru": "catalog:",
|
|
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
|
"@comfyorg/comfyui-electron-types": "catalog:",
|
|
"@comfyorg/design-system": "workspace:*",
|
|
"@comfyorg/registry-types": "workspace:*",
|
|
"@comfyorg/shared-frontend-utils": "workspace:*",
|
|
"@comfyorg/tailwind-utils": "workspace:*",
|
|
"@formkit/auto-animate": "catalog:",
|
|
"@iconify/json": "catalog:",
|
|
"@primeuix/forms": "catalog:",
|
|
"@primeuix/styled": "catalog:",
|
|
"@primeuix/utils": "catalog:",
|
|
"@primevue/core": "catalog:",
|
|
"@primevue/forms": "catalog:",
|
|
"@primevue/icons": "catalog:",
|
|
"@primevue/themes": "catalog:",
|
|
"@sentry/vue": "catalog:",
|
|
"@sparkjsdev/spark": "catalog:",
|
|
"@tiptap/core": "catalog:",
|
|
"@tiptap/extension-link": "catalog:",
|
|
"@tiptap/extension-table": "catalog:",
|
|
"@tiptap/extension-table-cell": "catalog:",
|
|
"@tiptap/extension-table-header": "catalog:",
|
|
"@tiptap/extension-table-row": "catalog:",
|
|
"@tiptap/pm": "catalog:",
|
|
"@tiptap/starter-kit": "catalog:",
|
|
"@vueuse/core": "catalog:",
|
|
"@vueuse/integrations": "catalog:",
|
|
"@xterm/addon-fit": "^0.10.0",
|
|
"@xterm/addon-serialize": "^0.13.0",
|
|
"@xterm/xterm": "^5.5.0",
|
|
"algoliasearch": "catalog:",
|
|
"axios": "catalog:",
|
|
"chart.js": "^4.5.0",
|
|
"cva": "catalog:",
|
|
"dompurify": "^3.2.5",
|
|
"dotenv": "catalog:",
|
|
"es-toolkit": "^1.39.9",
|
|
"extendable-media-recorder": "^9.2.27",
|
|
"extendable-media-recorder-wav-encoder": "^7.0.129",
|
|
"firebase": "catalog:",
|
|
"fuse.js": "^7.0.0",
|
|
"glob": "catalog:",
|
|
"jsonata": "catalog:",
|
|
"jsondiffpatch": "catalog:",
|
|
"loglevel": "^1.9.2",
|
|
"marked": "^15.0.11",
|
|
"pinia": "catalog:",
|
|
"posthog-js": "catalog:",
|
|
"primeicons": "catalog:",
|
|
"primevue": "catalog:",
|
|
"reka-ui": "catalog:",
|
|
"semver": "^7.7.2",
|
|
"three": "^0.170.0",
|
|
"tiptap-markdown": "^0.8.10",
|
|
"typegpu": "catalog:",
|
|
"vue": "catalog:",
|
|
"vue-i18n": "catalog:",
|
|
"vue-router": "catalog:",
|
|
"vuefire": "catalog:",
|
|
"wwobjloader2": "catalog:",
|
|
"yjs": "catalog:",
|
|
"zod": "catalog:",
|
|
"zod-validation-error": "catalog:"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "catalog:",
|
|
"@intlify/eslint-plugin-vue-i18n": "catalog:",
|
|
"@lobehub/i18n-cli": "catalog:",
|
|
"@nx/eslint": "catalog:",
|
|
"@nx/playwright": "catalog:",
|
|
"@nx/storybook": "catalog:",
|
|
"@nx/vite": "catalog:",
|
|
"@pinia/testing": "catalog:",
|
|
"@playwright/test": "catalog:",
|
|
"@sentry/vite-plugin": "catalog:",
|
|
"@storybook/addon-docs": "catalog:",
|
|
"@storybook/addon-mcp": "catalog:",
|
|
"@storybook/vue3": "catalog:",
|
|
"@storybook/vue3-vite": "catalog:",
|
|
"@tailwindcss/vite": "catalog:",
|
|
"@types/fs-extra": "catalog:",
|
|
"@types/jsdom": "catalog:",
|
|
"@types/node": "catalog:",
|
|
"@types/semver": "catalog:",
|
|
"@types/three": "catalog:",
|
|
"@vitejs/plugin-vue": "catalog:",
|
|
"@vitest/coverage-v8": "catalog:",
|
|
"@vitest/ui": "catalog:",
|
|
"@vue/test-utils": "catalog:",
|
|
"@webgpu/types": "catalog:",
|
|
"cross-env": "catalog:",
|
|
"eslint": "catalog:",
|
|
"eslint-config-prettier": "catalog:",
|
|
"eslint-import-resolver-typescript": "catalog:",
|
|
"eslint-plugin-better-tailwindcss": "catalog:",
|
|
"eslint-plugin-import-x": "catalog:",
|
|
"eslint-plugin-oxlint": "catalog:",
|
|
"eslint-plugin-storybook": "catalog:",
|
|
"eslint-plugin-unused-imports": "catalog:",
|
|
"eslint-plugin-vue": "catalog:",
|
|
"fast-check": "catalog:",
|
|
"fs-extra": "^11.2.0",
|
|
"globals": "catalog:",
|
|
"happy-dom": "catalog:",
|
|
"husky": "catalog:",
|
|
"jiti": "catalog:",
|
|
"jsdom": "catalog:",
|
|
"knip": "catalog:",
|
|
"lint-staged": "catalog:",
|
|
"markdown-table": "catalog:",
|
|
"mixpanel-browser": "catalog:",
|
|
"nx": "catalog:",
|
|
"oxfmt": "catalog:",
|
|
"oxlint": "catalog:",
|
|
"oxlint-tsgolint": "catalog:",
|
|
"picocolors": "catalog:",
|
|
"postcss-html": "catalog:",
|
|
"pretty-bytes": "catalog:",
|
|
"rollup-plugin-visualizer": "catalog:",
|
|
"storybook": "catalog:",
|
|
"stylelint": "catalog:",
|
|
"tailwindcss": "catalog:",
|
|
"tailwindcss-primeui": "catalog:",
|
|
"tsx": "catalog:",
|
|
"tw-animate-css": "catalog:",
|
|
"typescript": "catalog:",
|
|
"typescript-eslint": "catalog:",
|
|
"unplugin-icons": "catalog:",
|
|
"unplugin-typegpu": "catalog:",
|
|
"unplugin-vue-components": "catalog:",
|
|
"uuid": "^11.1.0",
|
|
"vite": "catalog:",
|
|
"vite-plugin-dts": "catalog:",
|
|
"vite-plugin-html": "catalog:",
|
|
"vite-plugin-vue-devtools": "catalog:",
|
|
"vitest": "catalog:",
|
|
"vue-component-type-helpers": "catalog:",
|
|
"vue-eslint-parser": "catalog:",
|
|
"vue-tsc": "catalog:",
|
|
"zip-dir": "^2.0.0",
|
|
"zod-to-json-schema": "catalog:"
|
|
},
|
|
"engines": {
|
|
"node": "24.x"
|
|
},
|
|
"pnpm": {
|
|
"overrides": {
|
|
"vite": "catalog:"
|
|
}
|
|
}
|
|
}
|