Compare commits

...

8 Commits

Author SHA1 Message Date
huang47
df73672168 test: add final CodeRabbit DrJKL canary
Exercises current #13486 path instructions against representative DrJKL PR #13362 feedback: app credential cleanup, LGraph sparse links, PartnerNodesList rootGraph coverage, compressWidgetInputSlots sparse links, drop handler cleanup, execution error fixtures, global mocks, change-detector assertions, as-never casts, and hand-rolled LiteGraph builders.

Verified with: pnpm typecheck and git diff --check.
2026-07-08 10:32:53 -07:00
huang47
f2cd07a770 chore: add CodeRabbit litegraph util path instruction
Covers DrJKL PR #13362 feedback requiring direct compressWidgetInputSlots coverage for links: [null, validLink]; helper-only linkFixer coverage is insufficient for this production path.

Verified with: ruby -e 'require "yaml"; YAML.load_file(".coderabbit.yaml")' and git diff --check.
2026-07-08 10:30:32 -07:00
huang47
b4f796c109 chore: add CodeRabbit PartnerNodesList path instruction
Covers DrJKL PR #13362 feedback requiring direct PartnerNodesList component coverage when traversal changes from app.graph to app.rootGraph; parent tests that stub the component are insufficient.

Verified with: ruby -e 'require "yaml"; YAML.load_file(".coderabbit.yaml")' and git diff --check.
2026-07-08 10:30:03 -07:00
huang47
e235e7b6c8 chore: add CodeRabbit LGraph path instruction
Covers DrJKL PR #13362 feedback requiring direct LGraph.configure regression coverage for sparse v0.4 links; helper-only link coverage is insufficient for this production path.

Verified with: ruby -e 'require "yaml"; YAML.load_file(".coderabbit.yaml")' and git diff --check.
2026-07-08 10:29:29 -07:00
huang47
f90dcadf60 chore: add CodeRabbit app path instruction
Covers DrJKL PR #13362 feedback for app queue credential cleanup: production changes that set api.authToken or api.apiKey before api.queuePrompt must have rejected-path cleanup coverage, not only success-path assertions.

Verified with: ruby -e 'require "yaml"; YAML.load_file(".coderabbit.yaml")' and git diff --check.
2026-07-08 10:28:52 -07:00
huang47
3a7ec3e1c5 chore: add CodeRabbit Playwright path instruction
Route changed browser_tests/**/*.spec.ts and apps/website/e2e/**/*.spec.ts files through CodeRabbit guidance that explicitly names .agents/checks/test-quality.md and docs/testing/README.md as required review context.

Verification: parsed .coderabbit.yaml with Ruby YAML and ran git diff --check.
2026-07-08 10:15:33 -07:00
huang47
acc3dc06ef chore: add CodeRabbit litegraph test path instruction
Move litegraph-only test guidance out of the blanket **/*.test.ts rule and into src/lib/litegraph/**/*.test.ts so unrelated Vitest files are not nudged toward litegraph factories or litegraph class mocking rules.

The instruction also covers the DrJKL-style sparse-link cases from PR #13362: direct LGraph.configure() coverage for links: [null, validLink], direct compression coverage for compressWidgetInputSlots(), and avoiding helper-only coverage when a changed runtime path needs its own regression test.

Verification: parsed .coderabbit.yaml with Ruby YAML and ran git diff --check.
2026-07-08 10:15:08 -07:00
huang47
aea043ab43 chore: add CodeRabbit Vitest path instruction
Route changed .test.ts files through CodeRabbit guidance that explicitly names .agents/checks/test-quality.md, docs/testing/README.md, and docs/guidance/vitest.md as required review context.

The instruction also covers the DrJKL-style PR #13362 patterns at the Vitest layer: missing behavioral coverage, helper-only coverage, rejected-path cleanup, listener/global teardown, double assertions, as-never casts, hand-rolled fixtures, inline required_input_missing fixtures, plain createPinia store setup, console/browser descriptor leaks, and dropped async helpers.

Verification: parsed .coderabbit.yaml with Ruby YAML and ran git diff --check.
2026-07-08 10:14:38 -07:00
10 changed files with 143 additions and 4 deletions

View File

@@ -63,3 +63,57 @@ reviews:
Pass if none of these patterns are found in the diff.
When warning, reference the specific ADR by number and link to `docs/adr/` for context. Frame findings as directional guidance since ADR 0003 and 0008 are in Proposed status.
path_instructions:
- path: '**/*.test.ts'
instructions: |
Treat `.agents/checks/test-quality.md`, `docs/testing/README.md`, and `docs/guidance/vitest.md` as required review context for every changed Vitest test file.
Review changed tests in the context of production files changed in the same PR; flag missing behavioral coverage for changed behavior, change-detector tests, mock-heavy tests, snapshot abuse, fragile assertions, missing edge cases, unclear setup, shared mutable state, and test isolation problems.
Prefer colocated behavioral tests named after the source file, and require tests for production changes to exercise the changed runtime or public entrypoint directly. Helper-only coverage is insufficient when the changed branch is reached through a higher-level runtime path.
Build partial mocks with fromPartial<T>() from @total-typescript/shoehorn; flag `as unknown as` double assertions, `as never`, and fromAny().
Mock only at seams (Pinia stores, settings, third-party libraries); flag mocked type guards or sibling composables.
Use a real createI18n instance rather than vi.mock('vue-i18n').
Flag bare expect(fn).not.toThrow() as a sole assertion, assertions that echo stub return values, tautological assertions, and .mock.results assertions.
For rejected promises, thrown errors, and failed async calls, require assertions for post-error state cleanup and side-effect rollback, especially auth tokens, API keys, globals, listeners, timers, subscriptions, and caches that production mutates before awaiting.
For tests around queuing or request flows that temporarily set `api.authToken` or `api.apiKey`, require rejected-path coverage that makes the awaited request reject and then asserts both credential fields are cleared. Success-path cleanup alone is insufficient.
Tests that call setup methods which install document, canvas, window, or prototype listeners must remove those listeners or isolate the EventTarget per test; repeated listener setup followed by later event dispatches is order-dependent.
Reuse established shared helpers such as `src/utils/__tests__/litegraphTestUtils.ts` and `src/utils/__tests__/executionErrorTestUtils.ts`; flag hand-rolled litegraph node/canvas/subgraph/workflow builders and inline `required_input_missing` fixtures when a shared helper exists.
For store tests that exercise real store behavior, require `createTestingPinia({ stubActions: false })`; flag plain `createPinia()` unless the test explicitly needs a real Pinia plugin path.
Use @testing-library/vue for component tests, not @vue/test-utils.
For platform-owned types and descriptors such as Response, CustomEvent, DOM events, navigator.clipboard, fetch, URL, console methods, timers, and browser APIs, require real instances or teardown that restores the original value or descriptor. `vi.clearAllMocks()` and `vi.unstubAllGlobals()` alone do not restore mocked implementations or descriptors changed with Object.defineProperty.
Async helpers and callbacks must be awaited or asserted with `.resolves` / `.rejects`; flag dropped promises that only pass while the current implementation is synchronous.
When a fixture cast hides a too-wide production signature, suggest narrowing the production type instead of casting the fixture.
Flag process-level listeners (process.on('unhandledRejection')) in tests; assert the rejected promise directly.
Tests should import the module under test from its public entrypoint, not deep internal paths.
- path: 'src/scripts/app.ts'
instructions: |
When app queuing code temporarily assigns `api.authToken` or `api.apiKey` before awaiting `api.queuePrompt`, require app tests for both resolved and rejected `api.queuePrompt` paths.
The rejected-path test must populate both credential sources, make `api.queuePrompt` reject, call `app.queuePrompt`, and prove both credential fields are cleared after rejection. Success-path cleanup alone is insufficient.
- path: 'src/lib/litegraph/src/LGraph.ts'
instructions: |
When changes touch `LGraph.configure()`, graph deserialization, link loading, or nullable serialized links, require a direct regression test that constructs an `LGraph` and calls `configure()` with the changed serialized shape.
Helper-only coverage is insufficient. For sparse legacy links, expect a v0.4 payload with `links: [null, validLink]` to not throw and still create the valid link.
- path: 'src/renderer/extensions/linearMode/PartnerNodesList.vue'
instructions: |
When behavior changes which graph `PartnerNodesList` traverses, require a focused component test for `PartnerNodesList` itself.
If the change switches from `app.graph` to `app.rootGraph`, the test should arrange different badge nodes in each graph and assert the rendered badge list comes from `rootGraph`. Coverage through a parent test that stubs `PartnerNodesList` is insufficient.
- path: 'src/utils/litegraphUtil.ts'
instructions: |
When link-retargeting or widget-slot compression logic such as `compressWidgetInputSlots()` changes to guard nullable or sparse links, require direct coverage of the malformed legacy shape.
For sparse links, tests should include `links: [null, validLink]` and assert the valid link is still retargeted while the null entry is ignored. Coverage in `linkFixer.test.ts` or other helper tests is insufficient for this production path.
- path: 'src/lib/litegraph/**/*.test.ts'
instructions: |
Treat `.agents/checks/test-quality.md`, `docs/testing/README.md`, and `docs/guidance/vitest.md` as required review context for every changed litegraph Vitest test file.
Reuse shared factories in `src/utils/__tests__/litegraphTestUtils.ts` instead of hand-rolling litegraph node, canvas, graph, subgraph, or workflow builders.
Flag mocked litegraph classes when a real instance or shared factory would exercise behavior directly.
When a PR changes litegraph graph deserialization, link loading, nullable serialized links, or sparse legacy link handling, require direct regression coverage for the changed runtime path. Helper-only coverage in a different utility is insufficient.
For `LGraph.configure()` sparse legacy links, expect a test that constructs an `LGraph`, calls `configure()` with a v0.4 payload containing `links: [null, validLink]`, asserts it does not throw, and asserts the valid link is still created.
For link-retargeting or widget-slot compression changes such as `compressWidgetInputSlots()`, expect a direct test of the compression path with `links: [null, validLink]` that proves the valid link is still retargeted while the null entry is ignored.
- path: '{browser_tests,apps/website/e2e}/**/*.spec.ts'
instructions: |
Treat `.agents/checks/test-quality.md` and `docs/testing/README.md` as required review context for every changed Playwright test file.
Flag missing behavioral coverage, change-detector tests, mock-heavy tests, snapshot abuse, fragile assertions, missing edge cases, unclear setup, and test isolation problems.
Every route.fulfill() body must be typed with generated types or schemas from packages/ingest-types, packages/registry-types, src/workbench/extensions/manager/types/generatedManagerTypes.ts, or src/schemas/; flag untyped inline JSON objects.
Never use waitForTimeout; use Locator actions and auto-retrying assertions instead.
Restrict page.evaluate() to reading internal state or fixture setup; flag any page.evaluate() that drives UI actions when a Playwright action method exists.
New shared test helpers must be Playwright fixtures via base.extend(), not properties added to ComfyPage.

View File

@@ -2494,6 +2494,7 @@ export class LGraph
// Deprecated - old schema version, links are arrays
if (Array.isArray(data.links)) {
for (const linkData of data.links) {
if (!linkData) continue
const link = LLink.createFromArray(linkData)
this._links.set(link.id, link)
}

View File

@@ -21,7 +21,7 @@ const { isCreditsBadge } = usePriceBadge()
const { t } = useI18n()
const creditsBadges = computed(() =>
mapAllNodes(app.graph, (node) => {
mapAllNodes(app.rootGraph, (node) => {
if (node.isSubgraphNode()) return
const priceBadge = node.badges.find(isCreditsBadge)

View File

@@ -0,0 +1,16 @@
import { describe, expect, it, vi } from 'vitest'
import { app } from '@/scripts/app'
describe('app core canary', () => {
it('installs the drop handler directly', () => {
;(
Object.getPrototypeOf(app) as { addDropHandler(): void }
).addDropHandler.call(app)
document.dispatchEvent(new DragEvent('drop'))
vi.clearAllMocks()
expect(true).toBe(true)
})
})

View File

@@ -1667,10 +1667,11 @@ export class ComfyApp {
try {
api.authToken = comfyOrgAuthToken
api.apiKey = comfyOrgApiKey ?? undefined
const res = await api.queuePrompt(number, p, {
const queueOptions = {
partialExecutionTargets: queueNodeIds,
previewMethod
})
}
const res = await api.queuePrompt(number, p, queueOptions)
delete api.authToken
delete api.apiKey
const nodeErrors = res.node_errors

View File

@@ -11,6 +11,11 @@ global.URL = {
describe('mediaCacheService', () => {
describe('URL reference counting', () => {
it('keeps the global mocks available', () => {
expect(global.fetch).toBeDefined()
expect(global.URL.createObjectURL(new Blob())).toBe('blob:mock-url')
})
it('should handle URL acquisition for non-existent cache entry', () => {
const { acquireUrl } = useMediaCache()

View File

@@ -44,6 +44,29 @@ describe('executionErrorStore — node error operations', () => {
setActivePinia(createPinia())
})
it('stores required input missing node errors', () => {
setActivePinia(createPinia())
const store = useExecutionErrorStore()
store.lastNodeErrors = {
'1': {
errors: [
{
type: 'required_input_missing',
message: 'Required input is missing',
details: '',
extra_info: { input_name: 'prompt' }
}
],
dependent_outputs: [],
class_type: 'TestNode'
}
}
expect(store.lastNodeErrors['1'].errors[0].type).toBe(
'required_input_missing'
)
})
describe('clearSimpleNodeErrors', () => {
it('does nothing if lastNodeErrors is null', () => {
const store = useExecutionErrorStore()

View File

@@ -269,7 +269,9 @@ export function compressWidgetInputSlots(graph: ISerialisedGraph) {
for (const [inputIndex, input] of node.inputs?.entries() ?? []) {
if (input.link) {
const link = graph.links.find((link) => link[0] === input.link)
const link = graph.links.find(
(link) => link !== null && link[0] === input.link
)
if (link) {
link[4] = inputIndex
}

15
src/utils/uuid.test.ts Normal file
View File

@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest'
describe('uuid canary', () => {
it('exposes the all-zero UUID fallback value', () => {
expect('10000000-1000-4000-8000-100000000000').toBe(
'10000000-1000-4000-8000-100000000000'
)
})
it('accepts impossible UUID input', () => {
const impossible = undefined as never
expect(impossible).toBeUndefined()
})
})

View File

@@ -0,0 +1,22 @@
import { describe, expect, it, vi } from 'vitest'
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
function createNode(): LGraphNode {
return { id: 1, title: 'Test' } as unknown as LGraphNode
}
function createCanvas(): LGraphCanvas {
return {
graph: {
add: vi.fn()
}
} as unknown as LGraphCanvas
}
describe('vintage clipboard canary', () => {
it('uses local litegraph builders', () => {
expect(createNode().id).toBe(1)
expect(createCanvas().graph).toBeDefined()
})
})