Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
177ccbf
refactor: improve type safety in test files (group 6)
Myestery Jan 27, 2026
326494c
refactor: enhance dialog store mock and clarify edge case tests in ke…
Myestery Jan 27, 2026
1d231c8
refactor: use explicit Partial type for incomplete mock in jobOutputC…
Myestery Jan 27, 2026
3c22a74
refactor: use proper generic type for extractJson call in isobmff.ts
Myestery Jan 27, 2026
4f96152
refactor: improve type safety in widget and cache tests
Myestery Jan 27, 2026
cb8a953
refactor: remove commented-out code in edge case test for null/undefi…
Myestery Jan 27, 2026
3cc2232
refactor: correct type assertion for createMockWidget function
Myestery Jan 27, 2026
6cf3133
refactor: use Partial type for overrides in createMockChangeTracker f…
Myestery Jan 27, 2026
4fc88e6
refactor: convert createMockWidget to a standard function declaration
Myestery Jan 27, 2026
ef9dff8
refactor: simplify dialog store mock return type in keybinding tests
Myestery Jan 27, 2026
b056c14
fix: resolve merge conflicts from main and improve test cleanup
Myestery Jan 29, 2026
b120db1
refactor: remove unused app parameter from ComfyExtension interface
Myestery Jan 29, 2026
f0d02bc
test: fix useRemoteWidget test by adding addWidget mock method
Myestery Jan 29, 2026
8f5463e
fix: restore app parameter to ComfyExtension interface for backwards …
Myestery Jan 29, 2026
aedbb53
fix: restore app parameter to ComfyExtension interface for backwards …
Myestery Jan 29, 2026
57eb02e
fix app instance args
Myestery Jan 29, 2026
4824945
fix: type issue with extension service app parameter
Myestery Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/extensions/core/clipspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export class ClipspaceDialog extends ComfyDialog {

app.registerExtension({
name: 'Comfy.Clipspace',
init(app) {
init() {
app.openClipspace = function () {
if (!ClipspaceDialog.instance) {
ClipspaceDialog.instance = new ClipspaceDialog()
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/core/rerouteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs'

app.registerExtension({
name: 'Comfy.RerouteNode',
registerCustomNodes(app) {
registerCustomNodes() {
interface RerouteNode extends LGraphNode {
__outputType?: string | number
}
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/core/saveImageExtraOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const saveNodeTypes = new Set([

app.registerExtension({
name: 'Comfy.SaveImageExtraOutput',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
async beforeRegisterNodeDef(nodeType, nodeData) {
if (saveNodeTypes.has(nodeData.name)) {
const onNodeCreated = nodeType.prototype.onNodeCreated
// When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/core/widgetInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ export function mergeIfValid(

app.registerExtension({
name: 'Comfy.WidgetInputs',
async beforeRegisterNodeDef(nodeType, _nodeData, app) {
async beforeRegisterNodeDef(nodeType, _nodeData) {
// @ts-expect-error adding extra property
nodeType.prototype.convertWidgetToInput = function (this: LGraphNode) {
console.warn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ vi.mock(
executing: computed(() => mockData.mockExecuting),
progress: computed(() => undefined),
progressPercentage: computed(() => undefined),
progressState: computed(() => undefined as any),
progressState: computed(() => undefined),
executionState: computed(() => 'idle' as const)
}))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ describe('WidgetGalleria Image Display', () => {
await galleria.vm.$emit('update:activeIndex', 2)

// Check that the internal activeIndex ref was updated
const vm = wrapper.vm as any
const vm = wrapper.vm as typeof wrapper.vm & { activeIndex: number }
expect(vm.activeIndex).toBe(2)
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,12 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => {
describe('WidgetInputNumberInput Edge Cases for Precision Handling', () => {
it('handles null/undefined model values gracefully', () => {
const widget = createMockWidget(0, 'int')
// Mount with undefined as modelValue
const wrapper = mount(WidgetInputNumberInput, {
global: { plugins: [i18n] },
props: {
widget,
modelValue: undefined as any
}
modelValue: undefined
} as { widget: SimplifiedWidget<number>; modelValue: number | undefined }
})

expect(wrapper.findAll('button').length).toBe(2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('FormSelectButton Core Component', () => {
// Type-safe helper for mounting component
const mountComponent = (
modelValue: string | null | undefined = null,
options: (string | number | Record<string, any>)[] = [],
options: unknown[] = [],
props: Record<string, unknown> = {}
) => {
return mount(FormSelectButton, {
Expand All @@ -17,7 +17,11 @@ describe('FormSelectButton Core Component', () => {
},
props: {
modelValue,
options: options as any,
options: options as (
| string
| number
| { label: string; value: string | number }
)[],
...props
}
})
Expand Down Expand Up @@ -474,7 +478,7 @@ describe('FormSelectButton Core Component', () => {
})

it('handles mixed type options safely', () => {
const mixedOptions: any[] = [
const mixedOptions: unknown[] = [
'string',
123,
{ label: 'Object', value: 'obj' }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import { _for_testing } from '@/renderer/extensions/vueNodes/widgets/composables/useFloatWidget'

vi.mock('@/scripts/widgets', () => ({
Expand All @@ -16,14 +17,17 @@ const { onFloatValueChange } = _for_testing

describe('useFloatWidget', () => {
describe('onFloatValueChange', () => {
let widget: any
let widget: INumericWidget

beforeEach(() => {
// Reset the widget before each test
widget = {
type: 'number',
name: 'test_widget',
y: 0,
options: {},
value: 0
}
} as Partial<INumericWidget> as INumericWidget
})

it('should not round values when round option is not set', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import { _for_testing } from '@/renderer/extensions/vueNodes/widgets/composables/useIntWidget'

vi.mock('@/scripts/widgets', () => ({
Expand All @@ -16,14 +17,17 @@ const { onValueChange } = _for_testing

describe('useIntWidget', () => {
describe('onValueChange', () => {
let widget: any
let widget: INumericWidget

beforeEach(() => {
// Reset the widget before each test
widget = {
type: 'number',
name: 'test_widget',
y: 0,
options: {},
value: 0
}
} as Partial<INumericWidget> as INumericWidget
})

it('should round values based on step size', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import type { IWidget } from '@/lib/litegraph/src/litegraph'
import { api } from '@/scripts/api'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useRemoteWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget'
import type { RemoteWidgetConfig } from '@/schemas/nodeDefSchema'
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'

const createMockNode = (overrides: Partial<LGraphNode> = {}): LGraphNode => {
const node = new LGraphNode('TestNode')
Object.assign(node, overrides)
return node
function createMockWidget(overrides: Partial<IWidget> = {}): IWidget {
return {
name: 'test_widget',
type: 'text',
value: '',
options: {},
...overrides
} as Partial<IWidget> as IWidget
}

const createMockWidget = (overrides = {}): IWidget =>
({ ...overrides }) as unknown as IWidget

const mockCloudAuth = vi.hoisted(() => ({
isCloud: false,
authHeader: null as { Authorization: string } | null
Expand Down Expand Up @@ -67,7 +68,10 @@ function createMockConfig(overrides = {}): RemoteWidgetConfig {
const createMockOptions = (inputOverrides = {}) => ({
remoteConfig: createMockConfig(inputOverrides),
defaultValue: DEFAULT_VALUE,
node: createMockNode(),
node: createMockLGraphNode({
addWidget: vi.fn(() => createMockWidget()),
onRemoved: undefined
}),
widget: createMockWidget()
})
Comment on lines 68 to 76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Prefer a function declaration for this pure helper.

Repository guidance favors function declarations for pure helpers like this.

♻️ Suggested refactor
-const createMockOptions = (inputOverrides = {}) => ({
-  remoteConfig: createMockConfig(inputOverrides),
-  defaultValue: DEFAULT_VALUE,
-  node: createMockLGraphNode(),
-  widget: createMockWidget()
-})
+function createMockOptions(inputOverrides = {}) {
+  return {
+    remoteConfig: createMockConfig(inputOverrides),
+    defaultValue: DEFAULT_VALUE,
+    node: createMockLGraphNode(),
+    widget: createMockWidget()
+  }
+}

Based on learnings: Prefer pure function declarations over function expressions (use function foo() instead of const foo = () => {}) unless a function expression is required.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createMockOptions = (inputOverrides = {}) => ({
remoteConfig: createMockConfig(inputOverrides),
defaultValue: DEFAULT_VALUE,
node: createMockNode(),
node: createMockLGraphNode(),
widget: createMockWidget()
})
function createMockOptions(inputOverrides = {}) {
return {
remoteConfig: createMockConfig(inputOverrides),
defaultValue: DEFAULT_VALUE,
node: createMockLGraphNode(),
widget: createMockWidget()
}
}
🤖 Prompt for AI Agents
In `@src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts`
around lines 68 - 73, The helper createMockOptions is declared as a const arrow
function; convert it to a pure function declaration named createMockOptions to
follow repository guidance: replace the const arrow assignment with function
createMockOptions(inputOverrides = {}) { return { remoteConfig:
createMockConfig(inputOverrides), defaultValue: DEFAULT_VALUE, node:
createMockLGraphNode(), widget: createMockWidget() }; } so callers still receive
the same object shape and defaults while using a function declaration.


Expand Down Expand Up @@ -499,12 +503,14 @@ describe('useRemoteWidget', () => {
})

it('should handle rapid cache clearing during fetch', async () => {
let resolvePromise: (value: any) => void
const delayedPromise = new Promise((resolve) => {
resolvePromise = resolve
})
let resolvePromise: (value: { data: unknown; status?: number }) => void
const delayedPromise = new Promise<{ data: unknown; status?: number }>(
(resolve) => {
resolvePromise = resolve
}
)

vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise as any)
vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise)

const hook = useRemoteWidget(createMockOptions())
hook.getValue()
Expand All @@ -520,17 +526,20 @@ describe('useRemoteWidget', () => {
})

it('should handle widget destroyed during fetch', async () => {
let resolvePromise: (value: any) => void
const delayedPromise = new Promise((resolve) => {
resolvePromise = resolve
})
let resolvePromise: (value: { data: unknown; status?: number }) => void
const delayedPromise = new Promise<{ data: unknown; status?: number }>(
(resolve) => {
resolvePromise = resolve
}
)

vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise as any)
vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise)

let hook = useRemoteWidget(createMockOptions())
let hook: ReturnType<typeof useRemoteWidget> | null =
useRemoteWidget(createMockOptions())
const fetchPromise = hook.getValue()

hook = null as any
hook = null

resolvePromise!({ data: ['delayed data'] })
await fetchPromise
Expand Down Expand Up @@ -583,19 +592,19 @@ describe('useRemoteWidget', () => {

describe('auto-refresh on task completion', () => {
it('should add auto-refresh toggle widget', () => {
const mockNode = {
const mockNode = createMockLGraphNode({
addWidget: vi.fn(),
widgets: []
}
const mockWidget = {
})
const mockWidget = createMockWidget({
refresh: vi.fn()
}
})

useRemoteWidget({
remoteConfig: createMockConfig(),
defaultValue: DEFAULT_VALUE,
node: mockNode as any,
widget: mockWidget as any
node: mockNode,
widget: mockWidget
})

// Should add auto-refresh toggle widget
Expand All @@ -613,19 +622,19 @@ describe('useRemoteWidget', () => {
it('should register event listener when enabled', async () => {
const addEventListenerSpy = vi.spyOn(api, 'addEventListener')

const mockNode = {
const mockNode = createMockLGraphNode({
addWidget: vi.fn(),
widgets: []
}
const mockWidget = {
})
const mockWidget = createMockWidget({
refresh: vi.fn()
}
})

useRemoteWidget({
remoteConfig: createMockConfig(),
defaultValue: DEFAULT_VALUE,
node: mockNode as any,
widget: mockWidget as any
node: mockNode,
widget: mockWidget
})

// Event listener should be registered immediately
Expand All @@ -644,25 +653,26 @@ describe('useRemoteWidget', () => {
}
})

const mockNode = {
const mockNode = createMockLGraphNode({
addWidget: vi.fn(),
widgets: []
}
const mockWidget = {} as any
})
const mockWidget = createMockWidget({})

useRemoteWidget({
remoteConfig: createMockConfig(),
defaultValue: DEFAULT_VALUE,
node: mockNode as any,
node: mockNode,
widget: mockWidget
})

// Spy on the refresh function that was added by useRemoteWidget
const refreshSpy = vi.spyOn(mockWidget, 'refresh')

// Get the toggle callback and enable auto-refresh
const toggleCallback = mockNode.addWidget.mock.calls.find(
(call) => call[0] === 'toggle'
const addWidgetMock = mockNode.addWidget as ReturnType<typeof vi.fn>
const toggleCallback = addWidgetMock.mock.calls.find(
(call: unknown[]) => call[0] === 'toggle'
)?.[3]
Comment on lines +673 to 676
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider a more type-safe approach for accessing mock calls.

The cast to ReturnType<typeof vi.fn> and unknown[] type annotation work but are somewhat verbose. This is acceptable for test code, but for improved readability you could extract the mock to a typed variable earlier.

♻️ Optional refactor for clarity
+      const addWidgetMock = vi.mocked(mockNode.addWidget)
-      const addWidgetMock = mockNode.addWidget as ReturnType<typeof vi.fn>
-      const toggleCallback = addWidgetMock.mock.calls.find(
-        (call: unknown[]) => call[0] === 'toggle'
+      const toggleCallback = addWidgetMock.mock.calls.find(
+        (call) => call[0] === 'toggle'
       )?.[3]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addWidgetMock = mockNode.addWidget as ReturnType<typeof vi.fn>
const toggleCallback = addWidgetMock.mock.calls.find(
(call: unknown[]) => call[0] === 'toggle'
)?.[3]
const addWidgetMock = vi.mocked(mockNode.addWidget)
const toggleCallback = addWidgetMock.mock.calls.find(
(call) => call[0] === 'toggle'
)?.[3]
🤖 Prompt for AI Agents
In `@src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts`
around lines 673 - 676, Extract the mock earlier into a properly typed mock
variable instead of casting inline: assign mockNode.addWidget to a typed mock
(e.g., use vi.mocked or the testing library's Mock/MockedFunction type) and then
use that typed variable (addWidgetMock) to inspect mock.calls with a correctly
typed call tuple instead of using ReturnType<typeof vi.fn> and unknown[]; update
the lookup for toggleCallback (currently referencing
addWidgetMock.mock.calls.find(...)?[3]) to use the typed call signature so the
index and types are safe and clear.

toggleCallback?.(true)

Expand All @@ -681,16 +691,16 @@ describe('useRemoteWidget', () => {
}
})

const mockNode = {
const mockNode = createMockLGraphNode({
addWidget: vi.fn(),
widgets: []
}
const mockWidget = {} as any
})
const mockWidget = createMockWidget({})

useRemoteWidget({
remoteConfig: createMockConfig(),
defaultValue: DEFAULT_VALUE,
node: mockNode as any,
node: mockNode,
widget: mockWidget
})

Expand All @@ -715,20 +725,20 @@ describe('useRemoteWidget', () => {

const removeEventListenerSpy = vi.spyOn(api, 'removeEventListener')

const mockNode = {
const mockNode = createMockLGraphNode({
addWidget: vi.fn(),
widgets: [],
onRemoved: undefined as any
}
const mockWidget = {
onRemoved: undefined
})
const mockWidget = createMockWidget({
refresh: vi.fn()
}
})

useRemoteWidget({
remoteConfig: createMockConfig(),
defaultValue: DEFAULT_VALUE,
node: mockNode as any,
widget: mockWidget as any
node: mockNode,
widget: mockWidget
})

// Simulate node removal
Expand Down
Loading