Files
ausboss-ComfyUI-AusBoss/tests/test_saved_fallbacks.py
T
ausboss 8d03e59d2b Open 1.x saves with the preview and Stop on missing LoRA on
Workflows saved by the 1.x releases also end in a panel's empty value: the
preview panel on LaMa Inpaint and Mask Refine (Select Frame from 1.2.0) and
LoRA Loader's rows. The inputs added in 2.0.0 sit in those slots, so preview
opened as "" (the switch showed on, but the prompt ran without a preview)
and on_missing opened as "" (the menu showed Stop on missing LoRA off while
the node still stopped the run). Each now opens on its default, which is
also what an API prompt without the input runs with: the three cards reset
preview through resetUnknown, and LoRA Loader, which has no card, applies a
RESET_UNKNOWN when a workflow opens. On a real 1.x save (pack 1.3.0 saved by
frontend 1.47.12) only those four values change.

The guard's history now reaches back to 1.0.0. Mask Refine's preview was
inserted ahead of an existing socket rather than appended, so the table now
lists each node's last input as of this guard and APPENDED_SINCE declares
everything added since the node first saved an empty value. The fallback
check also reads RESET_UNKNOWN, and tests/test_card_fallbacks.py becomes
tests/test_saved_fallbacks.py, which checks both kinds.
2026-09-29 03:20:45 -05:00

89 lines
3.7 KiB
Python

"""A saved-value fallback is the input's own default.
An input added to a node with a card or panel opens older workflows holding
that card's or panel's empty value (tests/saved_widget_values.test.mjs), and
the node puts a fallback there instead: its card's resetUnknown, or
RESET_UNKNOWN beside NODE_CLASS in its own js/<name>/index.js. The fallback
must be what a new node shows and what an API prompt without the input runs
with, or the same workflow gives two results. Needs ComfyUI's Python:
AUSBOSS_COMFY_ROOT=<ComfyUI> python tests/test_saved_fallbacks.py
"""
from __future__ import annotations
from pathlib import Path
import inspect
import json
import re
import unittest
from test_node_api import COMFY_ROOT, load_pack
ROOT = Path(__file__).resolve().parent.parent
JS = ROOT / "js"
def literal(text: str) -> dict:
return json.loads(re.sub(r"(\w+):", r'"\1":', text))
def saved_fallbacks() -> dict[str, dict]:
"""{node id: {input: fallback}} from the cards and the nodes' own files."""
found = {}
source = (JS / "widget_cards" / "index.js").read_text(encoding="utf-8")
starts = [(m.start(), m.group(1)) for m in re.finditer(r"\n (AUSBOSS_NODES_\w+): \{", source)]
for index, (start, node_id) in enumerate(starts):
end = starts[index + 1][0] if index + 1 < len(starts) else len(source)
match = re.search(r"resetUnknown: (\{[^}]*\})", source[start:end])
if match:
found[node_id] = literal(match.group(1))
for path in sorted(JS.glob("*/index.js")):
text = path.read_text(encoding="utf-8")
node = re.search(r'^const NODE_CLASS = "(\w+)";$', text, re.M)
own = re.search(r"^const RESET_UNKNOWN = (\{[^}]*\});$", text, re.M)
if node and own:
found[node.group(1)] = literal(own.group(1))
return found
def input_default(cls, name):
inputs = cls.INPUT_TYPES()
spec = inputs.get("optional", {}).get(name) or inputs.get("required", {}).get(name)
kind, options = spec[0], (spec[1] if len(spec) > 1 else {})
if "default" in options:
return options["default"]
return kind[0] if isinstance(kind, (list, tuple)) else None
@unittest.skipUnless(COMFY_ROOT, "set AUSBOSS_COMFY_ROOT to a ComfyUI checkout")
class SavedFallbackTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.pack = load_pack()
cls.fallbacks = saved_fallbacks()
def test_the_known_fallbacks_are_read(self):
self.assertEqual(self.fallbacks["AUSBOSS_NODES_StitchInpaint"], {"seam": "classic"})
self.assertEqual(self.fallbacks["AUSBOSS_NODES_CropForInpaint"], {"keep_inside": True})
for node_id in ("AUSBOSS_NODES_LaMaInpaint", "AUSBOSS_NODES_RefineMask", "AUSBOSS_NODES_SelectFrame"):
self.assertEqual(self.fallbacks[node_id], {"preview": True})
self.assertEqual(self.fallbacks["AUSBOSS_NODES_LoraLoader"], {"on_missing": "error"})
def test_each_fallback_is_the_new_node_default_and_the_api_default(self):
for node_id, fallbacks in self.fallbacks.items():
cls = self.pack.NODE_CLASS_MAPPINGS[node_id]
parameters = inspect.signature(getattr(cls, cls.FUNCTION)).parameters
for name, fallback in fallbacks.items():
with self.subTest(node=node_id, input=name):
self.assertEqual(fallback, input_default(cls, name), "a new node's default")
self.assertIn(name, parameters, f"{cls.FUNCTION}() does not take {name}")
self.assertEqual(
fallback, parameters[name].default,
"what an API prompt without the input runs with",
)
if __name__ == "__main__":
unittest.main()