using Xunit;
using ytLive.Models;
using ytLive.Services;
using ytLive.Services.Compositor;
using ytLive.Services.Encoder;
namespace ytLive.Tests;
///
/// Per-element composite timing + the pump's scene dump (webcam-cadence unit,
/// 2026-10-16). The judder diagnosis ended with a question the log could not answer:
/// the compositor was doing 6.8 composites/s at ~141ms each, and nothing in the stats
/// line said WHICH of the scene's six elements ate the budget — only that the render
/// cost 141ms. This pins the two readings that close that gap: a real composite
/// attributes wall time per element (slowest first) with the paste-cache rasterize
/// split, and the scene dump records the shape a take was made from.
///
public class CompositeTimingTests
{
/// The ONE integration test for this change: a REAL SceneCompositor.Render
/// over a full-canvas element plus a small one, through the real timing collector —
/// the parts line must name both elements, sort the full-canvas one first, and carry
/// the raster sub-stage; the dump must name every element with its z-index, source
/// type and rect, and echo the split point. No 5s stats window is waited on: the
/// window logic only formats what the collector already accumulated.
[Fact]
public void Composite_Timings_Rank_The_Slowest_Element_And_The_Dump_Names_The_Scene_Shape()
{
const int w = 640, h = 480;
var scene = new Scene { Name = "Live" };
var big = new Source { Type = SourceType.Image, Name = "FullCanvas", X = 0, Y = 0, Width = w, Height = h };
var small = new Source { Type = SourceType.Image, Name = "TinyBadge", X = 16, Y = 16, Width = 64, Height = 64 };
scene.Elements.Add(big);
scene.Elements.Add(small);
var bigFrame = SceneCompositorTests.Solid(w, h, 200, 40, 40);
var smallFrame = SceneCompositorTests.Solid(64, 64, 40, 200, 40);
VideoFrame? FrameFor(SceneElement e) => ReferenceEquals(e, big) ? bigFrame : smallFrame;
var options = new CompositorOptions
{
SourceRectX = 0, SourceRectY = 0, SourceRectWidth = w, SourceRectHeight = h,
OutputWidth = w, OutputHeight = h,
};
var timings = new CompositeTimings();
new SceneCompositor().Render(scene, FrameFor, null, options, timings: timings);
// minMs 0: a 640×480 test scene is sub-millisecond per stage, so the production
// 0.1ms floor would hide exactly the entries being asserted.
var parts = timings.Describe(1, minMs: 0);
Assert.Contains("#0 FullCanvas", parts);
Assert.Contains("#1 TinyBadge", parts);
Assert.Contains("#0 FullCanvas raster", parts); // the paste-cache rasterize miss
Assert.Contains("scale", parts);
Assert.Contains("backdrop", parts);
Assert.True(
parts.IndexOf("#0 FullCanvas", StringComparison.Ordinal) < parts.IndexOf("#1 TinyBadge", StringComparison.Ordinal),
$"the full-canvas element must rank above the 64×64 one: {parts}");
var dump = FramePump.FormatSceneDump(scene, split: 1);
Assert.Contains("FramePump scene dump: 'Live' elements=2 split=1", dump[0]);
Assert.Contains(dump, l => l.Contains("[0] FullCanvas") && l.Contains("type=Image")
&& l.Contains("vis=1") && l.Contains("rect=0,0 640x480"));
Assert.Contains(dump, l => l.Contains("[1] TinyBadge") && l.Contains("rect=16,16 64x64"));
// No SceneGraph wired (-1) — the dump says nothing about a split point.
Assert.DoesNotContain("split=", FramePump.FormatSceneDump(scene, split: -1)[0]);
}
}