Skip to content

Commit 0cf5e64

Browse files
huchenleigithub-actions
andauthored
Fix copy paste of widget value (#284)
* Fix copy paste of widget value * Fix ui tests * Allow undefined group font size * Update test expectations [skip ci] * nit --------- Co-authored-by: github-actions <github-actions@github.com>
1 parent f0f8674 commit 0cf5e64

File tree

8 files changed

+93
-88
lines changed

8 files changed

+93
-88
lines changed

browser_tests/copyPaste.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ test.describe('Copy Paste', () => {
2222
expect(resultString).toBe(originalString + originalString)
2323
})
2424

25+
test('Can copy and paste widget value', async ({ comfyPage }) => {
26+
// Copy width value (512) from empty latent node to KSampler's seed.
27+
// Empty latent node's width
28+
await comfyPage.canvas.click({
29+
position: {
30+
x: 718,
31+
y: 643
32+
}
33+
})
34+
await comfyPage.ctrlC()
35+
// KSampler's seed
36+
await comfyPage.canvas.click({
37+
position: {
38+
x: 1005,
39+
y: 281
40+
}
41+
})
42+
await comfyPage.ctrlV()
43+
await comfyPage.page.keyboard.press('Enter')
44+
await expect(comfyPage.canvas).toHaveScreenshot('copied-widget-value.png')
45+
})
46+
2547
/**
2648
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/98
2749
*/
Loading

src/scripts/app.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { applyTextReplacements, addStylesheet } from './utils'
1717
import type { ComfyExtension } from '@/types/comfy'
1818
import {
1919
type ComfyWorkflowJSON,
20-
parseComfyWorkflow
20+
validateComfyWorkflow
2121
} from '../types/comfyWorkflow'
2222
import { ComfyNodeDef } from '@/types/apiTypes'
2323
import { lightenColor } from '@/utils/colorUtil'
@@ -1114,12 +1114,12 @@ export class ComfyApp {
11141114
let workflow: ComfyWorkflowJSON
11151115
try {
11161116
data = data.slice(data.indexOf('{'))
1117-
workflow = await parseComfyWorkflow(data)
1117+
workflow = JSON.parse(data)
11181118
} catch (err) {
11191119
try {
11201120
data = data.slice(data.indexOf('workflow\n'))
11211121
data = data.slice(data.indexOf('{'))
1122-
workflow = await parseComfyWorkflow(data)
1122+
workflow = JSON.parse(data)
11231123
} catch (error) {
11241124
console.error(error)
11251125
}
@@ -1906,7 +1906,7 @@ export class ComfyApp {
19061906
try {
19071907
const loadWorkflow = async (json) => {
19081908
if (json) {
1909-
const workflow = await parseComfyWorkflow(json)
1909+
const workflow = JSON.parse(json)
19101910
const workflowName = getStorageValue('Comfy.PreviousWorkflow')
19111911
await this.loadGraphData(workflow, true, true, workflowName)
19121912
return true
@@ -2236,6 +2236,9 @@ export class ComfyApp {
22362236
console.error(error)
22372237
}
22382238

2239+
graphData = await validateComfyWorkflow(graphData, /* onError=*/ alert)
2240+
if (!graphData) return
2241+
22392242
const missingNodeTypes = []
22402243
await this.#invokeExtensionsAsync(
22412244
'beforeConfigureGraph',
@@ -2662,7 +2665,7 @@ export class ComfyApp {
26622665
const pngInfo = await getPngMetadata(file)
26632666
if (pngInfo?.workflow) {
26642667
await this.loadGraphData(
2665-
await parseComfyWorkflow(pngInfo.workflow),
2668+
JSON.parse(pngInfo.workflow),
26662669
true,
26672670
true,
26682671
fileName
@@ -2683,12 +2686,7 @@ export class ComfyApp {
26832686
const prompt = pngInfo?.prompt || pngInfo?.Prompt
26842687

26852688
if (workflow) {
2686-
this.loadGraphData(
2687-
await parseComfyWorkflow(workflow),
2688-
true,
2689-
true,
2690-
fileName
2691-
)
2689+
this.loadGraphData(JSON.parse(workflow), true, true, fileName)
26922690
} else if (prompt) {
26932691
this.loadApiJson(JSON.parse(prompt), fileName)
26942692
} else {
@@ -2700,12 +2698,7 @@ export class ComfyApp {
27002698
const prompt = pngInfo?.prompt || pngInfo?.Prompt
27012699

27022700
if (workflow) {
2703-
this.loadGraphData(
2704-
await parseComfyWorkflow(workflow),
2705-
true,
2706-
true,
2707-
fileName
2708-
)
2701+
this.loadGraphData(JSON.parse(workflow), true, true, fileName)
27092702
} else if (prompt) {
27102703
this.loadApiJson(JSON.parse(prompt), fileName)
27112704
} else {
@@ -2724,11 +2717,7 @@ export class ComfyApp {
27242717
} else if (this.isApiJson(jsonContent)) {
27252718
this.loadApiJson(jsonContent, fileName)
27262719
} else {
2727-
await this.loadGraphData(
2728-
await parseComfyWorkflow(readerResult),
2729-
true,
2730-
fileName
2731-
)
2720+
await this.loadGraphData(JSON.parse(readerResult), true, fileName)
27322721
}
27332722
}
27342723
reader.readAsText(file)
@@ -2742,7 +2731,7 @@ export class ComfyApp {
27422731
if (info.workflow) {
27432732
await this.loadGraphData(
27442733
// @ts-ignore
2745-
await parseComfyWorkflow(info.workflow),
2734+
JSON.parse(info.workflow),
27462735
true,
27472736
true,
27482737
fileName

src/types/comfyWorkflow.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const zGroup = z
7474
title: z.string(),
7575
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
7676
color: z.string(),
77-
font_size: z.number(),
77+
font_size: z.number().optional(),
7878
locked: z.boolean().optional()
7979
})
8080
.passthrough()
@@ -131,16 +131,15 @@ export type ComfyLink = z.infer<typeof zComfyLink>
131131
export type ComfyNode = z.infer<typeof zComfyNode>
132132
export type ComfyWorkflowJSON = z.infer<typeof zComfyWorkflow>
133133

134-
export async function parseComfyWorkflow(
135-
data: string
136-
): Promise<ComfyWorkflowJSON> {
137-
// Validate
138-
const result = await zComfyWorkflow.safeParseAsync(JSON.parse(data))
134+
export async function validateComfyWorkflow(
135+
data: any,
136+
onError: (error: string) => void = console.warn
137+
): Promise<ComfyWorkflowJSON | null> {
138+
const result = await zComfyWorkflow.safeParseAsync(data)
139139
if (!result.success) {
140-
// TODO: Pretty print the error on UI modal.
141140
const error = fromZodError(result.error)
142-
alert(`Invalid workflow against zod schema:\n${error}`)
143-
throw error
141+
onError(`Invalid workflow against zod schema:\n${error}`)
142+
return null
144143
}
145144
return result.data
146145
}

tests-ui/tests/comfyWorkflow.test.ts

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseComfyWorkflow } from '../../src/types/comfyWorkflow'
1+
import { validateComfyWorkflow } from '../../src/types/comfyWorkflow'
22
import { defaultGraph } from '../../src/scripts/defaultGraph'
33
import fs from 'fs'
44

@@ -9,101 +9,83 @@ describe('parseComfyWorkflow', () => {
99
fs.readdirSync(WORKFLOW_DIR).forEach(async (file) => {
1010
if (file.endsWith('.json')) {
1111
const data = fs.readFileSync(`${WORKFLOW_DIR}/${file}`, 'utf-8')
12-
await expect(parseComfyWorkflow(data)).resolves.not.toThrow()
12+
expect(await validateComfyWorkflow(JSON.parse(data))).not.toBeNull()
1313
}
1414
})
1515
})
1616

1717
it('workflow.nodes', async () => {
1818
const workflow = JSON.parse(JSON.stringify(defaultGraph))
1919
workflow.nodes = undefined
20-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
20+
expect(await validateComfyWorkflow(workflow)).toBeNull()
2121

2222
workflow.nodes = null
23-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
23+
expect(await validateComfyWorkflow(workflow)).toBeNull()
2424

2525
workflow.nodes = []
26-
await expect(
27-
parseComfyWorkflow(JSON.stringify(workflow))
28-
).resolves.not.toThrow()
26+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
2927
})
3028

3129
it('workflow.version', async () => {
3230
const workflow = JSON.parse(JSON.stringify(defaultGraph))
3331
workflow.version = undefined
34-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
32+
expect(await validateComfyWorkflow(workflow)).toBeNull()
3533

3634
workflow.version = '1.0.1' // Invalid format.
37-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
35+
expect(await validateComfyWorkflow(workflow)).toBeNull()
3836

3937
workflow.version = 1
40-
await expect(
41-
parseComfyWorkflow(JSON.stringify(workflow))
42-
).resolves.not.toThrow()
38+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
4339
})
4440

4541
it('workflow.extra', async () => {
4642
const workflow = JSON.parse(JSON.stringify(defaultGraph))
4743
workflow.extra = undefined
48-
await expect(
49-
parseComfyWorkflow(JSON.stringify(workflow))
50-
).resolves.not.toThrow()
44+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
5145

5246
workflow.extra = null
53-
await expect(
54-
parseComfyWorkflow(JSON.stringify(workflow))
55-
).resolves.not.toThrow()
47+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
5648

5749
workflow.extra = {}
58-
await expect(
59-
parseComfyWorkflow(JSON.stringify(workflow))
60-
).resolves.not.toThrow()
50+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
6151

6252
workflow.extra = { foo: 'bar' } // Should accept extra fields.
63-
await expect(
64-
parseComfyWorkflow(JSON.stringify(workflow))
65-
).resolves.not.toThrow()
53+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
6654
})
6755

6856
it('workflow.nodes.pos', async () => {
6957
const workflow = JSON.parse(JSON.stringify(defaultGraph))
7058
workflow.nodes[0].pos = [1, 2, 3]
71-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
59+
expect(await validateComfyWorkflow(workflow)).toBeNull()
7260

7361
workflow.nodes[0].pos = [1, 2]
74-
await expect(
75-
parseComfyWorkflow(JSON.stringify(workflow))
76-
).resolves.not.toThrow()
62+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
7763

7864
// Should automatically transform the legacy format object to array.
7965
workflow.nodes[0].pos = { '0': 3, '1': 4 }
80-
let parsedWorkflow = await parseComfyWorkflow(JSON.stringify(workflow))
81-
expect(parsedWorkflow.nodes[0].pos).toEqual([3, 4])
66+
let validatedWorkflow = await validateComfyWorkflow(workflow)
67+
expect(validatedWorkflow.nodes[0].pos).toEqual([3, 4])
8268

8369
workflow.nodes[0].pos = { 0: 3, 1: 4 }
84-
parsedWorkflow = await parseComfyWorkflow(JSON.stringify(workflow))
85-
expect(parsedWorkflow.nodes[0].pos).toEqual([3, 4])
70+
validatedWorkflow = await validateComfyWorkflow(workflow)
71+
expect(validatedWorkflow.nodes[0].pos).toEqual([3, 4])
8672
})
8773

8874
it('workflow.nodes.widget_values', async () => {
8975
const workflow = JSON.parse(JSON.stringify(defaultGraph))
9076
workflow.nodes[0].widgets_values = ['foo', 'bar']
91-
await expect(
92-
parseComfyWorkflow(JSON.stringify(workflow))
93-
).resolves.not.toThrow()
77+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
9478

9579
workflow.nodes[0].widgets_values = 'foo'
96-
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow()
80+
expect(await validateComfyWorkflow(workflow)).toBeNull()
9781

9882
workflow.nodes[0].widgets_values = undefined
99-
await expect(
100-
parseComfyWorkflow(JSON.stringify(workflow))
101-
).resolves.not.toThrow()
83+
expect(await validateComfyWorkflow(workflow)).not.toBeNull()
10284

10385
// The object format of widgets_values is used by VHS nodes to perform
10486
// dynamic widgets display.
10587
workflow.nodes[0].widgets_values = { foo: 'bar' }
106-
const parsedWorkflow = await parseComfyWorkflow(JSON.stringify(workflow))
107-
expect(parsedWorkflow.nodes[0].widgets_values).toEqual({ foo: 'bar' })
88+
const validatedWorkflow = await validateComfyWorkflow(workflow)
89+
expect(validatedWorkflow.nodes[0].widgets_values).toEqual({ foo: 'bar' })
10890
})
10991
})

tests-ui/tests/exampleWorkflows.test.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ describe('example workflows', () => {
3838
lg.teardown(global)
3939
})
4040

41-
for (const file of readdirSync(WORKFLOW_DIR)) {
42-
if (!file.endsWith('.json')) continue
41+
const workflowFiles = readdirSync(WORKFLOW_DIR).filter((file) =>
42+
file.endsWith('.json')
43+
)
44+
45+
const workflows = workflowFiles.map((file) => {
4346
const { workflow, prompt } = JSON.parse(
4447
readFileSync(path.resolve(WORKFLOW_DIR, file), 'utf8')
4548
)
@@ -53,17 +56,24 @@ describe('example workflows', () => {
5356
skip = !!Object.keys(parsedWorkflow?.extra?.groupNodes ?? {}).length
5457
} catch (error) {}
5558

56-
;(skip ? test.skip : test)(
57-
'correctly generates prompt json for ' + file,
58-
async () => {
59-
if (!workflow || !prompt) throw new Error('Invalid example json')
59+
return { file, workflow, prompt, parsedWorkflow, skip }
60+
})
6061

61-
const { app } = await start()
62-
await app.loadGraphData(parsedWorkflow)
62+
describe.each(workflows)(
63+
'Workflow Test: %s',
64+
({ file, workflow, prompt, parsedWorkflow, skip }) => {
65+
;(skip ? test.skip : test)(
66+
'correctly generates prompt json for ' + file,
67+
async () => {
68+
if (!workflow || !prompt) throw new Error('Invalid example json')
6369

64-
const output = await app.graphToPrompt()
65-
expect(output.output).toEqual(fixLegacyPrompt(JSON.parse(prompt)))
66-
}
67-
)
68-
}
70+
const { app } = await start()
71+
await app.loadGraphData(parsedWorkflow)
72+
73+
const output = await app.graphToPrompt()
74+
expect(output.output).toEqual(fixLegacyPrompt(JSON.parse(prompt)))
75+
}
76+
)
77+
}
78+
)
6979
})

tests-ui/tests/groupNode.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,8 @@ describe('group node', () => {
756756
})
757757
)
758758
})
759-
test('shows missing node error on missing internal node when loading graph data', async () => {
759+
// Now reports zod validation error
760+
test.skip('shows missing node error on missing internal node when loading graph data', async () => {
760761
const { graph } = await start()
761762

762763
const dialogShow = jest.spyOn(graph.app.ui.dialog, 'show')

tests-ui/tests/widgetInputs.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ describe('widget inputs', () => {
181181
expect(clone.inputs.ckpt_name).toBeFalsy()
182182
})
183183

184-
test('shows missing node error on custom node with converted input', async () => {
184+
// Invalid workflow against zod schema now.
185+
test.skip('shows missing node error on custom node with converted input', async () => {
185186
const { graph } = await start()
186187

187188
const dialogShow = jest.spyOn(graph.app.ui.dialog, 'show')
@@ -219,6 +220,7 @@ describe('widget inputs', () => {
219220
flags: {},
220221
order: 0,
221222
mode: 0,
223+
// Missing name and type
222224
outputs: [{ links: [4], widget: { name: 'test' } }],
223225
title: 'test',
224226
properties: {}

0 commit comments

Comments
 (0)