Files
ausboss-ComfyUI-AusBoss/tests/test_transform_inputs.py
ausboss beb98ad6a8 Video timeline overhaul, outpaint-ready clip canvas, example styling
Video Crop + Rotate + Pad -> Clip / -> Frame:
- The rail under the preview is a timeline: playhead scrubbing on the node
  face, frame-accurate IN/OUT handles that carry the playhead, Set IN/OUT
  and I/O keys in the editor; js/shared/timeline_math.mjs mirrors the
  backend's inclusion rule and emits hundredth-grid seconds.
- Scrubbing no longer flickers: storyboard tiles only stand in when nearer
  than the frame on stage and are decoded at their target times; the scrub
  session returns the frame it already decoded instead of the next one;
  served previews are cached.
- Aspect lock on the format chips (pad, lock, clear) with lockPadding in
  transform_geometry; Lock aspect in the editor.
- A new source keeps fill, feather, resize, Snap and Limit and re-pads to
  the lit chip; a canvas row (fill, feather, resize) sits on the node face.
- The clip node declares a black fill and feather 0 as its defaults and the
  editor's Reset all follows the node's own declaration, so a fresh node or
  a reset never lands on the grey, feathered canvas the LTX IC-LoRA cannot
  paint; image nodes keep grey/24.

Examples:
- LTX 2.3 Video Outpaint: Select Frame -> core Generate Text writes the
  positive prompt; note rewritten with the tested rules (53 renders over
  six clips: only fill #000000 + feather 0 decide painting).
- Core model loaders coloured and Workflow Notes grown to fit across the
  other examples.
2026-09-09 17:03:01 -05:00

108 lines
4.3 KiB
Python

from __future__ import annotations
import contextlib
import io
import json
from pathlib import Path
import sys
import tempfile
import unittest
import unittest.mock
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
if "nodes" in sys.modules and not hasattr(sys.modules["nodes"], "__path__"):
del sys.modules["nodes"]
from nodes import _transform_inputs
from nodes._transform_inputs import (
ASPECT_RATIOS,
aspect_ratio_options,
load_custom_aspect_ratios,
transform_inputs,
)
class AspectPresetTests(unittest.TestCase):
def setUp(self):
_transform_inputs._warned_presets.clear()
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.path = Path(self._tmp.name) / "ausboss_presets.json"
def write(self, payload):
self.path.write_text(payload, encoding="utf-8")
def test_missing_file_is_silent(self):
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
self.assertEqual(load_custom_aspect_ratios(self.path), [])
self.assertEqual(aspect_ratio_options(self.path), ASPECT_RATIOS)
self.assertEqual(buffer.getvalue(), "")
def test_valid_file_extends_without_duplicates(self):
self.write(json.dumps({"crop_aspect_ratios": ["16:10", "1:1", "5:4", "16:10"]}))
self.assertEqual(load_custom_aspect_ratios(self.path), ["16:10", "1:1", "5:4"])
options = aspect_ratio_options(self.path)
self.assertEqual(options, ASPECT_RATIOS + ["16:10", "5:4"])
self.assertEqual(options.count("1:1"), 1)
def test_malformed_json_warns_once_and_falls_back(self):
self.write("{not json")
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
self.assertEqual(load_custom_aspect_ratios(self.path), [])
self.assertEqual(load_custom_aspect_ratios(self.path), [])
self.assertEqual(aspect_ratio_options(self.path), ASPECT_RATIOS)
output = buffer.getvalue()
self.assertEqual(output.count("[AusBoss]"), 1)
output.encode("ascii") # warning must stay ASCII
def test_wrong_shape_warns_and_falls_back(self):
self.write(json.dumps(["1:1", "16:9"]))
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
self.assertEqual(load_custom_aspect_ratios(self.path), [])
self.assertIn("crop_aspect_ratios", buffer.getvalue())
def test_invalid_entries_are_skipped_with_warning(self):
self.write(json.dumps({"crop_aspect_ratios": ["16:10", "banana", "0:1", "-1:2", 7]}))
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
self.assertEqual(load_custom_aspect_ratios(self.path), ["16:10"])
self.assertIn("banana", buffer.getvalue())
def test_example_file_parses_and_matches_builtins(self):
example = ROOT / "ausboss_presets_example.json"
entries = load_custom_aspect_ratios(example)
self.assertEqual(entries, [item for item in ASPECT_RATIOS if item not in ("free", "source")])
self.assertEqual(aspect_ratio_options(example), ASPECT_RATIOS)
def test_transform_inputs_uses_preset_options(self):
with unittest.mock.patch.object(_transform_inputs, "PRESETS_PATH", self.path):
self.write(json.dumps({"crop_aspect_ratios": ["16:10"]}))
options = transform_inputs()["crop_aspect_ratio"][0]
self.assertEqual(options, ASPECT_RATIOS + ["16:10"])
class CanvasDefaultTests(unittest.TestCase):
def test_shared_defaults_keep_the_soft_grey_canvas(self):
inputs = transform_inputs()
self.assertEqual(inputs["feather"][1]["default"], 24)
self.assertEqual(inputs["fill_color"][1]["default"], "#808080")
def test_a_node_can_declare_an_outpaint_ready_canvas(self):
inputs = transform_inputs(feather=0, fill_color="#000000")
self.assertEqual(inputs["feather"][1]["default"], 0)
self.assertEqual(inputs["fill_color"][1]["default"], "#000000")
# Only the two canvas defaults move; the rest of the block is shared.
shared = transform_inputs()
for name in inputs:
if name in ("feather", "fill_color"):
continue
self.assertEqual(inputs[name], shared[name], name)
if __name__ == "__main__":
unittest.main()