Files
ausboss-ComfyUI-AusBoss/tests/test_saved_fallbacks.py
ausboss 21ca6e9f47 Stitch Inpaint: Paste onto puts a hand crop back on the whole picture
Image Crop + Rotate + Pad's stitcher now remembers the picture and the
crop's position. With Paste onto on original, Stitch Inpaint lays the
result on the whole picture, straight, resized or turned. The default
stays crop, so saved workflows run as before.
2026-11-01 01:24:49 -06:00

90 lines
3.8 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", "paste_onto": "crop"})
self.assertEqual(self.fallbacks["AUSBOSS_NODES_CropForInpaint"], {"keep_inside": True})
self.assertEqual(self.fallbacks["AUSBOSS_NODES_RefineMask"], {"preview": True, "max_hole_size": 0})
for node_id in ("AUSBOSS_NODES_LaMaInpaint", "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()