feat: add configurable operator appearance and layout

This commit is contained in:
2026-07-23 11:14:10 +09:00
parent cf117de144
commit f43483533a
34 changed files with 5942 additions and 740 deletions

View File

@@ -134,6 +134,46 @@ public sealed class LegacyPlayoutWebContractTests
Assert.Contains("마지막 항목", Script, StringComparison.Ordinal);
}
[Fact]
public void StatusStateUsesFailSafePriorityAndIsReassignedOnEveryRender()
{
var resolver = ExtractFunction(Script, "function statusState(playout, busy)");
foreach (var state in new[]
{
"outcome-unknown",
"busy",
"error",
"program",
"prepared",
"idle"
})
{
Assert.Contains($"\"{state}\"", resolver, StringComparison.Ordinal);
}
Assert.True(
resolver.IndexOf("playout.outcomeUnknown === true", StringComparison.Ordinal) <
resolver.IndexOf("if (busy)", StringComparison.Ordinal),
"OutcomeUnknown must remain visually dominant while a command is busy.");
Assert.True(
resolver.IndexOf("if (busy)", StringComparison.Ordinal) <
resolver.IndexOf("playout.mode === \"disabled\"", StringComparison.Ordinal),
"Busy must win over an unavailable engine after OutcomeUnknown is excluded.");
Assert.True(
resolver.IndexOf("playout.mode === \"disabled\"", StringComparison.Ordinal) <
resolver.IndexOf("playout.phase === \"program\"", StringComparison.Ordinal),
"Engine errors must win over ordinary phase styling.");
Assert.Contains(
"status.dataset.state = statusState(playout, busy);",
Script,
StringComparison.Ordinal);
Assert.Contains(
"status.dataset.state = \"error\";",
Script,
StringComparison.Ordinal);
}
private static int CountOccurrences(string source, string value)
{
var count = 0;
@@ -156,6 +196,28 @@ public sealed class LegacyPlayoutWebContractTests
return source[start..(end + closingTag.Length)];
}
private static string ExtractFunction(string source, string signature)
{
var start = source.IndexOf(signature, StringComparison.Ordinal);
Assert.True(start >= 0, $"Function '{signature}' was not found.");
var bodyStart = source.IndexOf('{', start);
Assert.True(bodyStart >= 0, $"Function '{signature}' has no body.");
var depth = 0;
for (var index = bodyStart; index < source.Length; index++)
{
if (source[index] == '{')
{
depth++;
}
else if (source[index] == '}' && --depth == 0)
{
return source[start..(index + 1)];
}
}
throw new InvalidDataException($"Function '{signature}' is incomplete.");
}
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);