Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
nit
  • Loading branch information
benceruleanlu committed Jul 23, 2025
commit 3524e66e612b61e91089274b595707bc04f89d17
13 changes: 1 addition & 12 deletions src/LGraphNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import type {
Size,
} from "./interfaces"
import type { LGraph } from "./LGraph"
import type { PropertyConfig } from "./LGraphNodeProperties"
import type { Reroute, RerouteId } from "./Reroute"
import type { SubgraphInputNode } from "./subgraph/SubgraphInputNode"
import type { SubgraphOutputNode } from "./subgraph/SubgraphOutputNode"
Expand Down Expand Up @@ -694,17 +693,7 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable
}

// Initialize property manager with tracked properties
this.propertyManager = new LGraphNodeProperties(this, {
trackedProperties: this.getDefaultTrackedProperties(),
})
}

/**
* Gets the default tracked properties for this node type.
* Can be overridden in subclasses to customize which properties are tracked.
*/
protected getDefaultTrackedProperties(): PropertyConfig[] {
return LGraphNodeProperties.getDefaultTrackedProperties()
this.propertyManager = new LGraphNodeProperties(this)
}

/** Internal callback for subgraph nodes. Do not implement externally. */
Expand Down
154 changes: 44 additions & 110 deletions src/LGraphNodeProperties.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { PropertyConfig } from "./LGraphNodeProperties"

import { beforeEach, describe, expect, it, vi } from "vitest"

import { LGraphNodeProperties } from "./LGraphNodeProperties"
Expand Down Expand Up @@ -27,20 +25,8 @@ describe("LGraphNodeProperties", () => {
const tracked = propManager.getTrackedProperties()

expect(tracked).toHaveLength(2)
expect(tracked).toContainEqual({ path: "title", type: "string" })
expect(tracked).toContainEqual({ path: "flags.collapsed", type: "boolean" })
})

it("should initialize with custom tracked properties", () => {
const customProps: PropertyConfig[] = [
{ path: "customProp", type: "string" },
{ path: "nested.prop", type: "number" },
]

const propManager = new LGraphNodeProperties(mockNode, { trackedProperties: customProps })
const tracked = propManager.getTrackedProperties()

expect(tracked).toEqual(customProps)
expect(tracked).toContainEqual({ path: "title" })
expect(tracked).toContainEqual({ path: "flags.collapsed" })
})
})

Expand Down Expand Up @@ -74,8 +60,7 @@ describe("LGraphNodeProperties", () => {
it("should not emit events when value doesn't change", () => {
new LGraphNodeProperties(mockNode)

mockNode.title = "Test Node"
mockNode.title = "Test Node" // Same value
mockNode.title = "Test Node" // Same value as original

expect(mockGraph.trigger).toHaveBeenCalledTimes(0)
})
Expand All @@ -91,122 +76,71 @@ describe("LGraphNodeProperties", () => {
})
})

describe("addTrackedProperty", () => {
it("should add new tracked properties dynamically", () => {
const propManager = new LGraphNodeProperties(mockNode, { trackedProperties: [] })

propManager.addTrackedProperty({ path: "color", type: "string" })
mockNode.color = "#FF0000"

expect(mockGraph.trigger).toHaveBeenCalledWith("node:property:changed", {
nodeId: mockNode.id,
property: "color",
oldValue: undefined,
newValue: "#FF0000",
})
})

it("should not add duplicate tracked properties", () => {
describe("isTracked", () => {
it("should correctly identify tracked properties", () => {
const propManager = new LGraphNodeProperties(mockNode)
const initialCount = propManager.getTrackedProperties().length

propManager.addTrackedProperty({ path: "title", type: "string" })

expect(propManager.getTrackedProperties()).toHaveLength(initialCount)
expect(propManager.isTracked("title")).toBe(true)
expect(propManager.isTracked("flags.collapsed")).toBe(true)
expect(propManager.isTracked("untracked")).toBe(false)
})
})

describe("property access methods", () => {
it("should get property values by path", () => {
const propManager = new LGraphNodeProperties(mockNode)
mockNode.title = "Test Title"
mockNode.flags.collapsed = true
describe("serialization behavior", () => {
it("should not make non-existent properties enumerable", () => {
new LGraphNodeProperties(mockNode)

expect(propManager.getProperty("title")).toBe("Test Title")
expect(propManager.getProperty("flags.collapsed")).toBe(true)
expect(propManager.getProperty("nonexistent")).toBeUndefined()
// flags.collapsed doesn't exist initially
const descriptor = Object.getOwnPropertyDescriptor(mockNode.flags, "collapsed")
expect(descriptor?.enumerable).toBe(false)
})

it("should set property values by path", () => {
const propManager = new LGraphNodeProperties(mockNode)

propManager.setProperty("title", "New Title")
propManager.setProperty("flags.collapsed", true)
propManager.setProperty("deep.nested.prop", "value")

expect(mockNode.title).toBe("New Title")
expect(mockNode.flags.collapsed).toBe(true)
expect(mockNode.deep.nested.prop).toBe("value")
})
it("should make properties enumerable when set to non-default values", () => {
new LGraphNodeProperties(mockNode)

it("should get all tracked values", () => {
const propManager = new LGraphNodeProperties(mockNode)
mockNode.title = "Test Title"
mockNode.flags.collapsed = true

const values = propManager.getAllTrackedValues()

expect(values).toEqual({
"title": "Test Title",
"flags.collapsed": true,
})
const descriptor = Object.getOwnPropertyDescriptor(mockNode.flags, "collapsed")
expect(descriptor?.enumerable).toBe(true)
})
})

describe("isTracked", () => {
it("should correctly identify tracked properties", () => {
const propManager = new LGraphNodeProperties(mockNode)

expect(propManager.isTracked("title")).toBe(true)
expect(propManager.isTracked("flags.collapsed")).toBe(true)
expect(propManager.isTracked("untracked")).toBe(false)
})
})
it("should make properties non-enumerable when set back to default", () => {
new LGraphNodeProperties(mockNode)

describe("static methods", () => {
it("should get default tracked properties", () => {
const defaults = LGraphNodeProperties.getDefaultTrackedProperties()
mockNode.flags.collapsed = true
mockNode.flags.collapsed = undefined

expect(defaults).toContainEqual({ path: "title", type: "string" })
expect(defaults).toContainEqual({ path: "flags.collapsed", type: "boolean" })
const descriptor = Object.getOwnPropertyDescriptor(mockNode.flags, "collapsed")
expect(descriptor?.enumerable).toBe(false)
})

it("should allow extending default tracked properties", () => {
const initialDefaults = LGraphNodeProperties.getDefaultTrackedProperties()
const initialLength = initialDefaults.length
it("should keep existing properties enumerable", () => {
// title exists initially
const initialDescriptor = Object.getOwnPropertyDescriptor(mockNode, "title")
expect(initialDescriptor?.enumerable).toBe(true)

LGraphNodeProperties.addDefaultTrackedProperty({ path: "testProp", type: "string" })

const newDefaults = LGraphNodeProperties.getDefaultTrackedProperties()
expect(newDefaults).toHaveLength(initialLength + 1)
expect(newDefaults).toContainEqual({ path: "testProp", type: "string" })
new LGraphNodeProperties(mockNode)

// Clean up - remove the added property
const index = newDefaults.findIndex(p => p.path === "testProp")
if (index !== -1) {
newDefaults.splice(index, 1)
}
const afterDescriptor = Object.getOwnPropertyDescriptor(mockNode, "title")
expect(afterDescriptor?.enumerable).toBe(true)
})
})

describe("property instrumentation edge cases", () => {
it("should handle properties with default values", () => {
new LGraphNodeProperties(mockNode, {
trackedProperties: [{ path: "newProp", defaultValue: "default", type: "string" }],
})

expect(mockNode.newProp).toBe("default")
})
it("should only include non-default values in JSON.stringify", () => {
new LGraphNodeProperties(mockNode)

it("should handle deeply nested property creation", () => {
new LGraphNodeProperties(mockNode, {
trackedProperties: [{ path: "a.b.c.d", type: "string" }],
})
// Initially, flags.collapsed shouldn't appear
let json = JSON.parse(JSON.stringify(mockNode))
expect(json.flags.collapsed).toBeUndefined()

expect(mockNode.a.b.c).toBeDefined()
// After setting to true, it should appear
mockNode.flags.collapsed = true
json = JSON.parse(JSON.stringify(mockNode))
expect(json.flags.collapsed).toBe(true)

mockNode.a.b.c.d = "deep value"
expect(mockNode.a.b.c.d).toBe("deep value")
// After setting back to undefined, it should disappear
mockNode.flags.collapsed = undefined
json = JSON.parse(JSON.stringify(mockNode))
expect(json.flags.collapsed).toBeUndefined()
})
})
})
Loading