using System.IO;
using System.Linq;
using System.Threading.Tasks;
using IFSEngine.Model;
using IFSRenderer.Shared;
using Xunit;
namespace IFSRenderer.AvaloniaShell.Tests;
/// <summary>
/// Documents saved before the ColorPalette change must still render.
/// </summary>
/// <remarks>
/// <para>
/// The engine replaced <c>FlamePalette</c> (a flat array of baked samples under <c>Colors</c>)
/// with <c>ColorPalette</c> (key colours under <c>KeyColors</c>). Every .ifsjson written before
/// that carries the old shape — including the templates the app ships and loads at startup — and
/// the new model ignores it silently: the gradient deserializes EMPTY, and
/// <c>SampleGradient</c> then returns the background colour at every position, so the fractal
/// renders flat. No exception, no warning, just a broken picture.
/// </para>
/// <para>
/// That is a compatibility break for every file any user has ever saved, so it is worth a test
/// rather than a comment.
/// </para>
/// </remarks>
public class LegacyPaletteTests
{
private static string TemplatePath =>
Path.Combine(AssetPaths.Library, "Templates", "nebula_sample.ifsjson");
/// <summary>Loads the template with the real plugin library, as the app does.</summary>
private static async Task<IFS> LoadTemplate()
{
var transforms = await Task.WhenAll(Directory
.GetFiles(AssetPaths.Transforms, "*.ifstf", SearchOption.AllDirectories)
.Select(TransformPlugin.FromFile));
return await IfsNodesSerializer.LoadJsonFileAsync(
TemplatePath, transforms, [], ignorePluginVersions: true);
}
[Fact]
public void The_shipped_template_still_carries_the_old_palette_shape()
{
// If this ever fails, upstream has migrated the templates and the shim below is only
// needed for user files.
string json = File.ReadAllText(TemplatePath);
Assert.Contains("\"Colors\"", json);
Assert.DoesNotContain("\"KeyColors\"", json);
}
[Fact]
public async Task A_legacy_document_loads_with_a_usable_gradient()
{
Assert.True(File.Exists(TemplatePath), $"expected a template at {TemplatePath}");
IFS fractal = await LoadTemplate();
Assert.NotNull(fractal.Palette);
Assert.True(fractal.Palette.KeyColors.Count > 1,
$"the palette upgraded to {fractal.Palette.KeyColors.Count} key colour(s); a legacy "
+ "document's gradient was lost, so the fractal will render flat");
}
[Fact]
public async Task The_upgraded_gradient_is_not_a_flat_colour()
{
IFS fractal = await LoadTemplate();
// Sampling across the gradient must yield more than one distinct colour — the exact
// failure mode is a palette that answers every position with the same value.
var samples = Enumerable.Range(0, 16)
.Select(i => fractal.Palette.SampleGradient(i / 15.0))
.Select(v => (v.X, v.Y, v.Z))
.Distinct()
.ToList();
Assert.True(samples.Count > 1,
"every position on the gradient returned the same colour, which is what a lost "
+ "legacy palette looks like");
}
[Fact]
public async Task The_upgraded_gradient_spans_the_full_range()
{
IFS fractal = await LoadTemplate();
// Key positions must run 0..1, or part of the gradient is unreachable by ColorIndex.
Assert.Equal(0.0, fractal.Palette.KeyColors.Keys.First(), 6);
Assert.Equal(1.0, fractal.Palette.KeyColors.Keys.Last(), 6);
}
}