Compare commits

..

1 Commits

Author SHA1 Message Date
CodeRabbit Fixer
ce6588b523 fix: test(changeTracker): add regression test for multilineOrOptions forwarding in wrapped prompt (#9418)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:22:21 +01:00
3 changed files with 103 additions and 84 deletions

View File

@@ -1,14 +1,17 @@
import type { CurvePoint } from './types'
interface MonotoneSpline {
n: number
xs: number[]
ys: number[]
slopes: number[]
}
function computeMonotoneSpline(points: CurvePoint[]): MonotoneSpline | null {
if (points.length < 2) return null
/**
* Monotone cubic Hermite interpolation.
* Produces a smooth curve that passes through all control points
* without overshooting (monotone property).
*
* Returns a function that evaluates y for any x in [0, 1].
*/
export function createMonotoneInterpolator(
points: CurvePoint[]
): (x: number) => number {
if (points.length === 0) return () => 0
if (points.length === 1) return () => points[0][1]
const sorted = [...points].sort((a, b) => a[0] - b[0])
const n = sorted.length
@@ -48,51 +51,6 @@ function computeMonotoneSpline(points: CurvePoint[]): MonotoneSpline | null {
}
}
return { n, xs, ys, slopes }
}
function hermiteEval(
spline: MonotoneSpline,
lo: number,
hi: number,
x: number
): number {
const dx = spline.xs[hi] - spline.xs[lo]
if (dx === 0) return spline.ys[lo]
const t = (x - spline.xs[lo]) / dx
const t2 = t * t
const t3 = t2 * t
const h00 = 2 * t3 - 3 * t2 + 1
const h10 = t3 - 2 * t2 + t
const h01 = -2 * t3 + 3 * t2
const h11 = t3 - t2
return (
h00 * spline.ys[lo] +
h10 * dx * spline.slopes[lo] +
h01 * spline.ys[hi] +
h11 * dx * spline.slopes[hi]
)
}
/**
* Monotone cubic Hermite interpolation.
* Produces a smooth curve that passes through all control points
* without overshooting (monotone property).
*
* Returns a function that evaluates y for any x in [0, 1].
*/
export function createMonotoneInterpolator(
points: CurvePoint[]
): (x: number) => number {
if (points.length === 0) return () => 0
if (points.length === 1) return () => points[0][1]
const spline = computeMonotoneSpline(points)!
const { n, xs, ys } = spline
return (x: number): number => {
if (x <= xs[0]) return ys[0]
if (x >= xs[n - 1]) return ys[n - 1]
@@ -105,7 +63,24 @@ export function createMonotoneInterpolator(
else hi = mid
}
return hermiteEval(spline, lo, hi, x)
const dx = xs[hi] - xs[lo]
if (dx === 0) return ys[lo]
const t = (x - xs[lo]) / dx
const t2 = t * t
const t3 = t2 * t
const h00 = 2 * t3 - 3 * t2 + 1
const h10 = t3 - 2 * t2 + t
const h01 = -2 * t3 + 3 * t2
const h11 = t3 - t2
return (
h00 * ys[lo] +
h10 * dx * slopes[lo] +
h01 * ys[hi] +
h11 * dx * slopes[hi]
)
}
}
@@ -133,36 +108,11 @@ export function histogramToPath(histogram: Uint32Array): string {
export function curvesToLUT(points: CurvePoint[]): Uint8Array {
const lut = new Uint8Array(256)
if (points.length === 0) return lut
if (points.length === 1) {
const v = Math.max(0, Math.min(255, Math.round(points[0][1] * 255)))
lut.fill(v)
return lut
}
const spline = computeMonotoneSpline(points)!
const { n, xs, ys } = spline
let lo = 0
let hi = 1
const interpolate = createMonotoneInterpolator(points)
for (let i = 0; i < 256; i++) {
const x = i / 255
let y: number
if (x <= xs[0]) {
y = ys[0]
} else if (x >= xs[n - 1]) {
y = ys[n - 1]
} else {
while (hi < n - 1 && xs[hi] < x) {
lo++
hi++
}
y = hermiteEval(spline, lo, hi, x)
}
const y = interpolate(x)
lut[i] = Math.max(0, Math.min(255, Math.round(y * 255)))
}

View File

@@ -0,0 +1,62 @@
import { describe, expect, it, vi } from 'vitest'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { ChangeTracker } from './changeTracker'
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: vi.fn(() => ({
activeWorkflow: null
})),
ComfyWorkflow: vi.fn()
}))
vi.mock('./api', () => ({
api: {
addEventListener: vi.fn(),
apiURL: vi.fn((path: string) => path)
}
}))
vi.mock('./app', () => ({
app: {
ui: { autoQueueEnabled: false, autoQueueMode: 'instant' },
canvas: { ds: { scale: 1, offset: [0, 0] } },
constructor: { maskeditor_is_opended: undefined }
},
ComfyApp: vi.fn()
}))
describe('ChangeTracker.init', () => {
it('forwards multiline argument to original prompt', () => {
const originalPrompt = vi.fn()
LGraphCanvas.prototype.prompt = originalPrompt
ChangeTracker.init()
const wrappedPrompt = LGraphCanvas.prototype.prompt
expect(wrappedPrompt).not.toBe(originalPrompt)
const mockCallback = vi.fn()
const mockEvent = {} as CanvasPointerEvent
const multilineValue = true
wrappedPrompt.call(
{} as LGraphCanvas,
'Title',
'value',
mockCallback,
mockEvent,
multilineValue
)
expect(originalPrompt).toHaveBeenCalledWith(
'Title',
'value',
expect.any(Function),
mockEvent,
true
)
})
})

View File

@@ -307,14 +307,21 @@ export class ChangeTracker {
title: string,
value: string | number,
callback: (v: string) => void,
event: CanvasPointerEvent
event: CanvasPointerEvent,
multiline?: boolean
) {
const extendedCallback = (v: string) => {
callback(v)
checkState()
}
logger.debug('checkState on prompt')
return prompt.apply(this, [title, value, extendedCallback, event])
return prompt.apply(this, [
title,
value,
extendedCallback,
event,
multiline
])
}
// Handle litegraph context menu for COMBO widgets