diff --git a/src/FlexRender.Cli/Commands/DebugLayoutCommand.cs b/src/FlexRender.Cli/Commands/DebugLayoutCommand.cs
index 720f9ce..a1acd43 100644
--- a/src/FlexRender.Cli/Commands/DebugLayoutCommand.cs
+++ b/src/FlexRender.Cli/Commands/DebugLayoutCommand.cs
@@ -63,7 +63,7 @@ public static Command Create()
/// Whether to enable verbose output.
/// Optional fonts directory.
/// Exit code (0 for success, non-zero for failure).
- private static Task Execute(
+ private static async Task Execute(
FileInfo templateFile,
FileInfo? dataFile,
FileInfo? outputFile,
@@ -74,21 +74,21 @@ private static Task Execute(
if (!templateFile.Exists)
{
Console.Error.WriteLine($"Error: Template file not found: {templateFile.FullName}");
- return Task.FromResult(1);
+ return 1;
}
// Validate data file if specified
if (dataFile is not null && !dataFile.Exists)
{
Console.Error.WriteLine($"Error: Data file not found: {dataFile.FullName}");
- return Task.FromResult(1);
+ return 1;
}
// Validate fonts directory if specified
if (fontsDir is not null && !fontsDir.Exists)
{
Console.Error.WriteLine($"Error: Fonts directory not found: {fontsDir.FullName}");
- return Task.FromResult(1);
+ return 1;
}
try
@@ -137,7 +137,7 @@ private static Task Execute(
}
// Compute layout using the same renderer as actual rendering
- var root = skiaRender.ComputeLayout(template, templateData);
+ var root = await skiaRender.ComputeLayout(template, templateData);
// Print registered fonts
Console.WriteLine("Fonts:");
@@ -158,17 +158,17 @@ private static Task Execute(
// Optionally render debug image
if (outputFile is not null)
{
- RenderDebugImage(template, root, templateData, outputFile.FullName, skiaRender);
+ await RenderDebugImage(template, root, templateData, outputFile.FullName, skiaRender);
Console.WriteLine();
Console.WriteLine($"Debug image: {outputFile.FullName}");
}
- return Task.FromResult(0);
+ return 0;
}
catch (TemplateParseException ex)
{
Console.Error.WriteLine($"Template error: {ex.Message}");
- return Task.FromResult(1);
+ return 1;
}
catch (Exception ex)
{
@@ -177,7 +177,7 @@ private static Task Execute(
{
Console.Error.WriteLine(ex.StackTrace);
}
- return Task.FromResult(1);
+ return 1;
}
}
@@ -281,20 +281,22 @@ private static string GetComputedExtra(LayoutNode node)
/// The template data.
/// The output file path.
/// The SkiaRender instance with fonts already registered.
- private static void RenderDebugImage(
+ private static async Task RenderDebugImage(
Template template,
LayoutNode root,
ObjectValue data,
string outputPath,
FlexRender.Skia.SkiaRender skiaRender)
{
- var size = skiaRender.Measure(template, data);
+ var size = await skiaRender.Measure(template, data);
using var bitmap = new SKBitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
using var canvas = new SKCanvas(bitmap);
- // Render template normally
- skiaRender.Render(canvas, template, data);
+ // Render template to PNG and decode back to draw onto debug canvas
+ var pngBytes = await skiaRender.RenderToPng(template, data);
+ using var rendered = SKBitmap.Decode(pngBytes);
+ canvas.DrawBitmap(rendered, 0, 0);
// Draw debug overlay
DrawDebugOverlay(canvas, root, 0, 0, skiaRender.FontManager);
diff --git a/src/FlexRender.Core/Parsing/Ast/ExprValue.cs b/src/FlexRender.Core/Parsing/Ast/ExprValue.cs
index 4e7b591..ad0770d 100644
--- a/src/FlexRender.Core/Parsing/Ast/ExprValue.cs
+++ b/src/FlexRender.Core/Parsing/Ast/ExprValue.cs
@@ -245,7 +245,7 @@ public override string ToString()
if (IsExpression)
return $"Expr({RawValue})";
if (RawValue is not null)
- return $"Raw({RawValue})={Value}";
- return $"{Value}";
+ return string.Create(CultureInfo.InvariantCulture, $"Raw({RawValue})={Value}");
+ return string.Create(CultureInfo.InvariantCulture, $"{Value}");
}
}
diff --git a/src/FlexRender.Core/TemplateEngine/TemplateExpander.cs b/src/FlexRender.Core/TemplateEngine/TemplateExpander.cs
index 4db9072..c6b3741 100644
--- a/src/FlexRender.Core/TemplateEngine/TemplateExpander.cs
+++ b/src/FlexRender.Core/TemplateEngine/TemplateExpander.cs
@@ -77,40 +77,6 @@ public TemplateExpander(ResourceLimits limits, FilterRegistry filterRegistry, Cu
_resourceLoaders = resourceLoaders;
}
- ///
- /// Expands EachElement and IfElement instances into concrete elements based on data.
- /// Returns a new Template with all control flow elements resolved.
- ///
- /// The template containing control flow elements.
- /// The data for evaluating conditions and iterating arrays.
- /// A new Template with expanded elements.
- /// Thrown when template or data is null.
- /// Thrown when maximum expansion depth is exceeded.
- public Template Expand(Template template, ObjectValue data)
- {
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- var context = new TemplateContext(data);
- var expandedElements = ExpandElements(template.Elements, context, 0, template);
-
- var result = new Template
- {
- Name = template.Name,
- Version = template.Version,
- Canvas = template.Canvas,
- Elements = expandedElements
- };
-
- // Copy fonts
- foreach (var font in template.Fonts)
- {
- result.Fonts[font.Key] = font.Value;
- }
-
- return result;
- }
-
///
/// Asynchronously expands EachElement and IfElement instances into concrete elements based on data.
/// Returns a new Template with all control flow elements resolved.
diff --git a/src/FlexRender.Core/TemplateEngine/TemplatePipeline.cs b/src/FlexRender.Core/TemplateEngine/TemplatePipeline.cs
index 3bbd1d2..a035b6a 100644
--- a/src/FlexRender.Core/TemplateEngine/TemplatePipeline.cs
+++ b/src/FlexRender.Core/TemplateEngine/TemplatePipeline.cs
@@ -25,30 +25,6 @@ public TemplatePipeline(TemplateExpander expander, TemplateProcessor templatePro
_templateProcessor = templateProcessor;
}
- ///
- /// Processes a template through the full pipeline: Expand, Resolve, Materialize.
- ///
- /// The parsed template to process.
- /// The data context for expression evaluation.
- /// The expanded and resolved template with all expressions materialized.
- /// Thrown when or is null.
- public Template Process(Template template, ObjectValue data)
- {
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- // Phase 1: Expand control flow (#if, #each, table)
- var expanded = _expander.Expand(template, data);
-
- // Phase 2: Resolve expressions in all ExprValue properties
- ResolveAll(expanded, data);
-
- // Phase 3: Materialize resolved strings into typed values
- MaterializeAll(expanded);
-
- return expanded;
- }
-
///
/// Asynchronously processes a template through the full pipeline: Expand, Resolve, Materialize.
/// Uses await for the expansion phase to support async content source resolution.
diff --git a/src/FlexRender.ImageSharp.Render/ImageSharpRender.cs b/src/FlexRender.ImageSharp.Render/ImageSharpRender.cs
index 382c9fb..aaf04e4 100644
--- a/src/FlexRender.ImageSharp.Render/ImageSharpRender.cs
+++ b/src/FlexRender.ImageSharp.Render/ImageSharpRender.cs
@@ -190,8 +190,7 @@ public async Task RenderToPng(
try
{
- using var image = _engine.RenderToImage(
- layoutTemplate, effectiveData, _filterRegistry, imageCache, processedTemplate, _contentParserRegistry);
+ using var image = _engine.RenderToImage(processedTemplate, imageCache);
var encoder = new PngEncoder();
await image.SaveAsync(output, encoder, cancellationToken).ConfigureAwait(false);
@@ -241,8 +240,7 @@ public async Task RenderToJpeg(
try
{
- using var image = _engine.RenderToImage(
- layoutTemplate, effectiveData, _filterRegistry, imageCache, processedTemplate, _contentParserRegistry);
+ using var image = _engine.RenderToImage(processedTemplate, imageCache);
var encoder = new JpegEncoder { Quality = effectiveOptions.Quality };
await image.SaveAsync(output, encoder, cancellationToken).ConfigureAwait(false);
@@ -291,8 +289,7 @@ public async Task RenderToBmp(
try
{
- using var image = _engine.RenderToImage(
- layoutTemplate, effectiveData, _filterRegistry, imageCache, processedTemplate, _contentParserRegistry);
+ using var image = _engine.RenderToImage(processedTemplate, imageCache);
var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 };
await image.SaveAsync(output, encoder, cancellationToken).ConfigureAwait(false);
@@ -339,8 +336,7 @@ public async Task RenderToRaw(
try
{
- using var image = _engine.RenderToImage(
- layoutTemplate, effectiveData, _filterRegistry, imageCache, processedTemplate, _contentParserRegistry);
+ using var image = _engine.RenderToImage(processedTemplate, imageCache);
// Write raw RGBA pixel data
var pixelCount = checked(image.Width * image.Height);
@@ -360,26 +356,23 @@ public async Task RenderToRaw(
// ========================================================================
///
- /// Pre-loads all images referenced in the template using the configured resource loaders.
- /// Also returns the processed template so callers can skip redundant expand+preprocess steps.
+ /// Expands and processes the template asynchronously, then pre-loads all images
+ /// referenced in it using the configured resource loaders.
///
- /// The template to scan for image references.
+ /// The template to process and scan for image references.
/// The data context for expression evaluation.
/// Cancellation token for async operations.
///
- /// A tuple of the processed template and an image cache. The processed template is non-null
- /// when resource loaders are configured (since expand+preprocess was already performed).
- /// The image cache maps URIs to pre-loaded images, or is null when no images were found.
+ /// A tuple of the fully processed template and an image cache. The processed template
+ /// is always non-null. The image cache maps URIs to pre-loaded images, or is null
+ /// when no images were found or no resource loaders are configured.
/// Caller is responsible for disposing images via .
///
- private async Task<(Template? processedTemplate, Dictionary>? imageCache)> PreloadImages(
+ private async Task<(Template processedTemplate, Dictionary>? imageCache)> PreloadImages(
Template template,
ObjectValue data,
CancellationToken cancellationToken)
{
- if (_resourceLoaders.Count == 0)
- return (null, null);
-
// Expand, resolve, and materialize template to resolve expressions in image src attributes
var expander = _filterRegistry is not null
? new TemplateExpander(_limits, _filterRegistry, _contentParserRegistry, _resourceLoaders)
@@ -391,6 +384,9 @@ public async Task RenderToRaw(
var pipeline = new TemplatePipeline(expander, templateProcessor);
var processedTemplate = await pipeline.ProcessAsync(template, data).ConfigureAwait(false);
+ if (_resourceLoaders.Count == 0)
+ return (processedTemplate, null);
+
var uris = ImageSharpRenderingEngine.CollectImageUris(processedTemplate);
if (uris.Count == 0)
return (processedTemplate, null);
diff --git a/src/FlexRender.ImageSharp.Render/Rendering/ImageSharpRenderingEngine.cs b/src/FlexRender.ImageSharp.Render/Rendering/ImageSharpRenderingEngine.cs
index 01851ba..ea1180c 100644
--- a/src/FlexRender.ImageSharp.Render/Rendering/ImageSharpRenderingEngine.cs
+++ b/src/FlexRender.ImageSharp.Render/Rendering/ImageSharpRenderingEngine.cs
@@ -6,7 +6,6 @@
using FlexRender.Parsing.Ast;
using FlexRender.Providers;
using FlexRender.Rendering;
-using FlexRender.TemplateEngine;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
@@ -67,52 +66,25 @@ internal ImageSharpRenderingEngine(
}
///
- /// Renders a template to a new Image<Rgba32>.
+ /// Renders a pre-processed template to a new Image<Rgba32>.
+ /// The caller must run the template through TemplatePipeline.ProcessAsync
+ /// before invoking this method.
///
- /// The template to render.
- /// The data context for expression evaluation.
- /// Optional filter registry.
+ ///
+ /// A fully expanded and processed template. Must have all expressions resolved
+ /// and content materialised via TemplatePipeline.ProcessAsync.
+ ///
///
/// Optional pre-loaded image cache for HTTP and other async sources.
/// When provided, images are resolved from the cache before falling back to
/// inline loading (file and base64 only).
///
- ///
- /// Optional pre-processed template from image preloading. When provided, the
- /// expand+preprocess steps are skipped to avoid redundant work.
- ///
- /// Optional content parser registry for custom content type parsing.
/// A new image containing the rendered template. Caller owns disposal.
internal Image RenderToImage(
- Template template,
- ObjectValue data,
- FilterRegistry? filterRegistry = null,
- IReadOnlyDictionary>? imageCache = null,
- Template? preprocessedTemplate = null,
- ContentParserRegistry? contentParserRegistry = null)
+ Template processedTemplate,
+ IReadOnlyDictionary>? imageCache = null)
{
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- Template processedTemplate;
-
- if (preprocessedTemplate is not null)
- {
- processedTemplate = preprocessedTemplate;
- }
- else
- {
- // Expand, resolve, and materialize template via the Core pipeline
- var expander = filterRegistry is not null
- ? new TemplateExpander(_limits, filterRegistry, contentParserRegistry, _resourceLoaders)
- : new TemplateExpander(_limits, contentParserRegistry, _resourceLoaders);
- var templateProcessor = filterRegistry is not null
- ? new TemplateProcessor(_limits, filterRegistry)
- : new TemplateProcessor(_limits);
-
- var pipeline = new TemplatePipeline(expander, templateProcessor);
- processedTemplate = pipeline.Process(template, data);
- }
+ ArgumentNullException.ThrowIfNull(processedTemplate);
// Register fonts from the processed template (backend-specific)
_preprocessor.RegisterFonts(processedTemplate);
diff --git a/src/FlexRender.Skia.Render/Rendering/RenderingEngine.cs b/src/FlexRender.Skia.Render/Rendering/RenderingEngine.cs
index 0c96458..8f7529e 100644
--- a/src/FlexRender.Skia.Render/Rendering/RenderingEngine.cs
+++ b/src/FlexRender.Skia.Render/Rendering/RenderingEngine.cs
@@ -91,29 +91,25 @@ internal RenderingEngine(
}
///
- /// Core canvas rendering logic. Accepts an optional pre-loaded image cache
- /// so that async render paths can pass it through without storing mutable state
- /// on the renderer instance (thread safety).
+ /// Core canvas rendering logic. Accepts a pre-processed template and computed layout node.
+ /// The caller is responsible for calling and
+ /// before invoking this method.
///
/// The canvas to render to.
- /// The template to render.
- /// The data for variable substitution.
+ /// The already-processed template (after pipeline expansion).
+ /// The pre-computed root layout node.
/// Optional offset for rendering position.
/// Optional pre-loaded image cache.
/// Per-call rendering options.
internal void RenderToCanvas(
SKCanvas canvas,
- Template template,
- ObjectValue data,
+ Template processedTemplate,
+ LayoutNode rootNode,
SKPoint offset,
IReadOnlyDictionary? imageCache,
RenderOptions? renderOptions = null)
{
var effectiveRenderOptions = renderOptions ?? RenderOptions.Default;
- var pipeline = ResolvePipeline(effectiveRenderOptions, template);
- var processedTemplate = pipeline.Process(template, data);
- _preprocessor.RegisterFonts(processedTemplate);
- var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
// Save canvas state
canvas.Save();
@@ -138,47 +134,42 @@ internal void RenderToCanvas(
}
///
- /// Core bitmap rendering logic. Accepts an optional pre-loaded image cache
- /// so that async render paths can pass it through without storing mutable state
- /// on the renderer instance (thread safety).
+ /// Core bitmap rendering logic. Accepts a pre-processed template and computed layout node.
+ /// The caller is responsible for calling and
+ /// before invoking this method.
///
/// The bitmap to render to.
- /// The template to render.
- /// The data for variable substitution.
+ /// The already-processed template (after pipeline expansion).
+ /// The pre-computed root layout node.
/// Optional offset for rendering position.
/// Optional pre-loaded image cache.
/// Per-call rendering options.
internal void RenderToBitmapCore(
SKBitmap bitmap,
- Template template,
- ObjectValue data,
+ Template processedTemplate,
+ LayoutNode rootNode,
SKPoint offset,
IReadOnlyDictionary? imageCache,
RenderOptions? renderOptions = null)
{
- var rotationDegrees = RotationHelper.ParseRotation(template.Canvas.Rotate.Value);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
// If no rotation needed, render directly to bitmap
if (!RotationHelper.HasRotation(rotationDegrees))
{
using var canvas = new SKCanvas(bitmap);
- RenderToCanvas(canvas, template, data, offset, imageCache, renderOptions);
+ RenderToCanvas(canvas, processedTemplate, rootNode, offset, imageCache, renderOptions);
return;
}
// Render to temporary bitmap first, then rotate
- var effectiveRenderOptions = renderOptions ?? RenderOptions.Default;
- var pipeline = ResolvePipeline(effectiveRenderOptions, template);
- var processedTemplate = pipeline.Process(template, data);
- _preprocessor.RegisterFonts(processedTemplate);
- var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
var originalWidth = (int)rootNode.Width;
var originalHeight = (int)rootNode.Height;
using var tempBitmap = new SKBitmap(originalWidth, originalHeight);
using (var tempCanvas = new SKCanvas(tempBitmap))
{
- RenderToCanvas(tempCanvas, template, data, offset, imageCache, renderOptions);
+ RenderToCanvas(tempCanvas, processedTemplate, rootNode, offset, imageCache, renderOptions);
}
// Rotate the bitmap
@@ -673,6 +664,34 @@ internal async Task> PreloadImagesAsync(
return cache;
}
+ ///
+ /// Pre-loads all images from an already-processed template asynchronously using the image loader.
+ /// Unlike , this method skips the pipeline processing step
+ /// and works directly with the provided template.
+ ///
+ /// The already-processed template containing resolved image references.
+ /// Cancellation token.
+ /// A dictionary mapping image URIs to loaded bitmaps.
+ internal async Task> PreloadImagesFromProcessedAsync(
+ Template processedTemplate,
+ CancellationToken cancellationToken)
+ {
+ var uris = CollectImageUris(processedTemplate);
+ var cache = new Dictionary(uris.Count, StringComparer.Ordinal);
+
+ foreach (var uri in uris)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var bitmap = await _imageLoader!.Load(uri, cancellationToken).ConfigureAwait(false);
+ if (bitmap is not null)
+ {
+ cache[uri] = bitmap;
+ }
+ }
+
+ return cache;
+ }
+
///
/// Sets the SVG content cache on the SVG provider if it implements .
///
@@ -714,6 +733,34 @@ internal async Task> PreloadSvgContentAsync(
return cache;
}
+ ///
+ /// Pre-loads all SVG content from an already-processed template asynchronously using the resource loaders.
+ /// Unlike , this method skips the pipeline processing step
+ /// and works directly with the provided template.
+ ///
+ /// The already-processed template containing resolved SVG element references.
+ /// Cancellation token.
+ /// A dictionary mapping SVG source URIs to loaded and sanitized SVG content.
+ internal async Task> PreloadSvgContentFromProcessedAsync(
+ Template processedTemplate,
+ CancellationToken cancellationToken)
+ {
+ var uris = SvgContentLoader.CollectSvgUris(processedTemplate);
+ var cache = new Dictionary(uris.Count, StringComparer.Ordinal);
+
+ foreach (var uri in uris)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var content = await SvgContentLoader.LoadFromLoaders(_resourceLoaders, uri).ConfigureAwait(false);
+ if (content is not null)
+ {
+ cache[uri] = content;
+ }
+ }
+
+ return cache;
+ }
+
///
/// Resolves the border-radius for an element, clamped to half the smaller dimension.
///
diff --git a/src/FlexRender.Skia.Render/Rendering/SkiaRenderer.cs b/src/FlexRender.Skia.Render/Rendering/SkiaRenderer.cs
index ee7b3a9..0c49113 100644
--- a/src/FlexRender.Skia.Render/Rendering/SkiaRenderer.cs
+++ b/src/FlexRender.Skia.Render/Rendering/SkiaRenderer.cs
@@ -144,40 +144,46 @@ public SkiaRenderer(
public FontManager FontManager => _fontManager;
///
- /// Computes the layout tree for a template with data.
+ /// Computes the layout tree for a template with data asynchronously.
/// Uses the same layout engine configuration as rendering (including text measurement).
///
/// The template to lay out.
/// The data for variable substitution.
/// The root layout node with computed positions and sizes.
- public LayoutNode ComputeLayout(Template template, ObjectValue data)
+ /// Thrown when the renderer has been disposed.
+ /// Thrown when or is null.
+ public async Task ComputeLayoutAsync(Template template, ObjectValue data)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) == 1, this);
ArgumentNullException.ThrowIfNull(template);
ArgumentNullException.ThrowIfNull(data);
- var processedTemplate = _pipeline.Process(template, data);
+ var processedTemplate = await _pipeline.ProcessAsync(template, data).ConfigureAwait(false);
_preprocessor.RegisterFonts(processedTemplate);
return _layoutEngine.ComputeLayout(processedTemplate);
}
///
- /// Measures the size required to render the template.
+ /// Measures the size required to render the template asynchronously.
/// Takes into account canvas rotation which may swap width and height.
///
/// The template to measure.
/// The data for variable substitution.
+ /// Cancellation token for async operation.
/// The required size after rotation is applied.
- public SKSize Measure(Template template, ObjectValue data)
+ /// Thrown when the renderer has been disposed.
+ /// Thrown when or is null.
+ /// Thrown when the operation is cancelled.
+ public async Task MeasureAsync(Template template, ObjectValue data, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) == 1, this);
ArgumentNullException.ThrowIfNull(template);
ArgumentNullException.ThrowIfNull(data);
+ cancellationToken.ThrowIfCancellationRequested();
- var processedTemplate = _pipeline.Process(template, data);
+ var processedTemplate = await _pipeline.ProcessAsync(template, data).ConfigureAwait(false);
_preprocessor.RegisterFonts(processedTemplate);
- // Use LayoutEngine to compute accurate sizes
var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
var width = rootNode.Width;
@@ -194,70 +200,9 @@ public SKSize Measure(Template template, ObjectValue data)
}
///
- /// Renders the template to a canvas.
- ///
- /// The canvas to render to.
- /// The template to render.
- /// The data for variable substitution.
- /// Optional offset for rendering position.
- public void Render(SKCanvas canvas, Template template, ObjectValue data, SKPoint offset = default)
- {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) == 1, this);
- ArgumentNullException.ThrowIfNull(canvas);
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- _renderingEngine.RenderToCanvas(canvas, template, data, offset, imageCache: null, _defaultRenderOptions);
- }
-
- ///
- /// Renders the template to a bitmap.
- /// Applies canvas rotation after rendering if specified in template settings.
- ///
- /// The bitmap to render to.
- /// The template to render.
- /// The data for variable substitution.
- /// Optional offset for rendering position.
- public void Render(SKBitmap bitmap, Template template, ObjectValue data, SKPoint offset = default)
- {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) == 1, this);
- ArgumentNullException.ThrowIfNull(bitmap);
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- _renderingEngine.RenderToBitmapCore(bitmap, template, data, offset, imageCache: null, _defaultRenderOptions);
- }
-
- ///
- /// Renders the template using typed data.
- ///
- /// The data type implementing ITemplateData.
- /// The canvas to render to.
- /// The template to render.
- /// The typed data.
- /// Optional offset for rendering position.
- public void Render(SKCanvas canvas, Template template, T data, SKPoint offset = default)
- where T : ITemplateData
- {
- Render(canvas, template, data.ToTemplateValue(), offset);
- }
-
- ///
- /// Renders the template using typed data to a bitmap.
- ///
- /// The data type implementing ITemplateData.
- /// The bitmap to render to.
- /// The template to render.
- /// The typed data.
- /// Optional offset for rendering position.
- public void Render(SKBitmap bitmap, Template template, T data, SKPoint offset = default)
- where T : ITemplateData
- {
- Render(bitmap, template, data.ToTemplateValue(), offset);
- }
-
- ///
- /// Renders a template to a new bitmap.
+ /// Renders a template to a new bitmap asynchronously.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The template to render.
/// The data context for template expressions.
@@ -275,21 +220,28 @@ public async Task Render(
ArgumentNullException.ThrowIfNull(data);
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- // Measure returns the final size after rotation
- var size = Measure(layoutTemplate, data);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
+ var size = RotationHelper.SwapsDimensions(rotationDegrees)
+ ? new SKSize(rootNode.Height, rootNode.Width)
+ : new SKSize(rootNode.Width, rootNode.Height);
+
var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
try
{
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, default, imageCache);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, default, imageCache, _defaultRenderOptions);
return bitmap;
}
catch
@@ -311,6 +263,8 @@ public async Task Render(
///
/// Renders a template to an existing bitmap asynchronously.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The target bitmap to render onto.
/// The template to render.
@@ -332,16 +286,20 @@ public async Task Render(
ArgumentNullException.ThrowIfNull(data);
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, offset, imageCache);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, offset, imageCache, _defaultRenderOptions);
}
finally
{
@@ -356,6 +314,8 @@ public async Task Render(
///
/// Renders a template to a PNG stream.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The output stream to write PNG data to.
/// The template to render.
@@ -379,20 +339,26 @@ public async Task RenderToPng(
ArgumentNullException.ThrowIfNull(data);
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- // Measure returns the final size after rotation
- var size = Measure(layoutTemplate, data);
- using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
+ var size = RotationHelper.SwapsDimensions(rotationDegrees)
+ ? new SKSize(rootNode.Height, rootNode.Width)
+ : new SKSize(rootNode.Width, rootNode.Height);
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, default, imageCache, renderOptions);
+ using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, default, imageCache, renderOptions);
using var image = SKImage.FromBitmap(bitmap);
using var encodedData = image.Encode(SKEncodedImageFormat.Png, compressionLevel);
@@ -411,6 +377,8 @@ public async Task RenderToPng(
///
/// Renders a template to a JPEG stream.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The output stream to write JPEG data to.
/// The template to render.
@@ -441,20 +409,26 @@ public async Task RenderToJpeg(
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- // Measure returns the final size after rotation
- var size = Measure(layoutTemplate, data);
- using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
+ var size = RotationHelper.SwapsDimensions(rotationDegrees)
+ ? new SKSize(rootNode.Height, rootNode.Width)
+ : new SKSize(rootNode.Width, rootNode.Height);
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, default, imageCache, renderOptions);
+ using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, default, imageCache, renderOptions);
using var image = SKImage.FromBitmap(bitmap);
using var encodedData = image.Encode(SKEncodedImageFormat.Jpeg, quality);
@@ -473,6 +447,8 @@ public async Task RenderToJpeg(
///
/// Renders a template to a BMP stream.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The output stream to write BMP data to.
/// The template to render.
@@ -496,20 +472,26 @@ public async Task RenderToBmp(
ArgumentNullException.ThrowIfNull(data);
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- // Measure returns the final size after rotation
- var size = Measure(layoutTemplate, data);
- using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
+ var size = RotationHelper.SwapsDimensions(rotationDegrees)
+ ? new SKSize(rootNode.Height, rootNode.Width)
+ : new SKSize(rootNode.Width, rootNode.Height);
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, default, imageCache, renderOptions);
+ using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, default, imageCache, renderOptions);
BmpEncoder.Encode(bitmap, output, colorMode);
}
@@ -526,6 +508,8 @@ public async Task RenderToBmp(
///
/// Renders a template to raw pixel data in BGRA8888 format.
+ /// Processes the template once through the async pipeline and pre-loads all images and SVG content
+ /// before rendering.
///
/// The output stream to write raw pixel data to.
/// The template to render.
@@ -547,20 +531,26 @@ public async Task RenderToRaw(
ArgumentNullException.ThrowIfNull(data);
cancellationToken.ThrowIfCancellationRequested();
+ var processedTemplate = await _pipeline.ProcessAsync(layoutTemplate, data).ConfigureAwait(false);
+ _preprocessor.RegisterFonts(processedTemplate);
+
var imageCache = _imageLoader is not null
- ? await _renderingEngine.PreloadImagesAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false)
+ ? await _renderingEngine.PreloadImagesFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false)
: null;
- var svgContentCache = await _renderingEngine.PreloadSvgContentAsync(layoutTemplate, data, cancellationToken).ConfigureAwait(false);
+ var svgContentCache = await _renderingEngine.PreloadSvgContentFromProcessedAsync(processedTemplate, cancellationToken).ConfigureAwait(false);
_renderingEngine.SetSvgContentCache(svgContentCache);
try
{
- // Measure returns the final size after rotation
- var size = Measure(layoutTemplate, data);
- using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
+ var rotationDegrees = RotationHelper.ParseRotation(processedTemplate.Canvas.Rotate.Value);
+ var size = RotationHelper.SwapsDimensions(rotationDegrees)
+ ? new SKSize(rootNode.Height, rootNode.Width)
+ : new SKSize(rootNode.Width, rootNode.Height);
- _renderingEngine.RenderToBitmapCore(bitmap, layoutTemplate, data, default, imageCache, renderOptions);
+ using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
+ _renderingEngine.RenderToBitmapCore(bitmap, processedTemplate, rootNode, default, imageCache, renderOptions);
// Copy raw pixel bytes directly from the bitmap
var pixels = bitmap.Bytes;
@@ -577,24 +567,6 @@ public async Task RenderToRaw(
}
}
- ///
- /// Measures template size without rendering.
- ///
- /// The template to measure.
- /// The data context for template expressions.
- /// Cancellation token for async operation.
- /// The size of the template in pixels.
- /// Thrown when or is null.
- /// Thrown when the operation is cancelled.
- public Task Measure(
- Template layoutTemplate,
- ObjectValue data,
- CancellationToken cancellationToken = default)
- {
- cancellationToken.ThrowIfCancellationRequested();
- return Task.FromResult(Measure(layoutTemplate, data));
- }
-
///
/// Disposes the renderer and releases resources.
///
diff --git a/src/FlexRender.Skia.Render/SkiaRender.cs b/src/FlexRender.Skia.Render/SkiaRender.cs
index e3ce86e..95de212 100644
--- a/src/FlexRender.Skia.Render/SkiaRender.cs
+++ b/src/FlexRender.Skia.Render/SkiaRender.cs
@@ -63,48 +63,33 @@ public sealed class SkiaRender : IFlexRender
public FontManager FontManager => _renderer.FontManager;
///
- /// Computes layout for a template without rendering.
+ /// Asynchronously computes layout for a template without rendering.
/// Intended for diagnostic and debugging tools.
///
/// The parsed template.
/// The template data.
- /// The root layout node.
+ /// A task that represents the asynchronous operation, containing the root layout node.
///
/// This method is intended for debugging and testing only. For production rendering,
/// use
/// or one of the format-specific RenderTo* methods instead.
///
- public LayoutNode ComputeLayout(Template template, ObjectValue data) =>
- _renderer.ComputeLayout(template, data);
+ public Task ComputeLayout(Template template, ObjectValue data) =>
+ _renderer.ComputeLayoutAsync(template, data);
///
- /// Measures the size required to render the template.
+ /// Asynchronously measures the size required to render the template.
///
/// The parsed template.
/// The template data.
- /// The measured size in pixels.
+ /// A task that represents the asynchronous operation, containing the measured size in pixels.
///
/// This method is intended for debugging and testing only. For production rendering,
/// use
/// or one of the format-specific RenderTo* methods instead.
///
- public SKSize Measure(Template template, ObjectValue data) =>
- _renderer.Measure(template, data);
-
- ///
- /// Renders the template to an existing canvas.
- /// Intended for diagnostic and debugging tools.
- ///
- /// The canvas to render to.
- /// The parsed template.
- /// The template data.
- ///
- /// This method is intended for debugging and testing only. For production rendering,
- /// use
- /// or one of the format-specific RenderTo* methods instead.
- ///
- public void Render(SKCanvas canvas, Template template, ObjectValue data) =>
- _renderer.Render(canvas, template, data);
+ public Task Measure(Template template, ObjectValue data) =>
+ _renderer.MeasureAsync(template, data);
///
[Obsolete("Use RenderToBmp() with BmpOptions instead. This property will be removed in a future version.")]
diff --git a/src/FlexRender.Svg.Render/Rendering/SvgRenderingEngine.cs b/src/FlexRender.Svg.Render/Rendering/SvgRenderingEngine.cs
index 3db0775..a550d43 100644
--- a/src/FlexRender.Svg.Render/Rendering/SvgRenderingEngine.cs
+++ b/src/FlexRender.Svg.Render/Rendering/SvgRenderingEngine.cs
@@ -116,7 +116,7 @@ internal async Task RenderToSvgAsync(Template template, ObjectValue data
try
{
- return RenderToSvg(template, data);
+ return RenderToSvgFromProcessed(processedTemplate);
}
finally
{
@@ -128,20 +128,17 @@ internal async Task RenderToSvgAsync(Template template, ObjectValue data
}
///
- /// Renders a template with data to SVG markup.
+ /// Renders a pre-processed template to SVG markup.
+ /// The template must already be processed through the pipeline by the caller.
///
- /// The template to render.
- /// The data for variable substitution.
+ /// The already-processed template to render.
/// The SVG markup as a string.
- internal string RenderToSvg(Template template, ObjectValue data)
+ internal string RenderToSvgFromProcessed(Template processedTemplate)
{
- ArgumentNullException.ThrowIfNull(template);
- ArgumentNullException.ThrowIfNull(data);
-
- var processedTemplate = _pipeline.Process(template, data);
+ ArgumentNullException.ThrowIfNull(processedTemplate);
// Build font family map and font face declarations from the template.
- // Returned as local variables to ensure thread safety when RenderToSvg is called concurrently.
+ // Returned as local variables to ensure thread safety when RenderToSvgFromProcessed is called concurrently.
var (fontFamilyMap, fontFaces) = BuildFontMap(processedTemplate);
var rootNode = _layoutEngine.ComputeLayout(processedTemplate);
diff --git a/tests/FlexRender.ImageSharp.Tests/Rendering/ImageSharpRenderingEngineTests.cs b/tests/FlexRender.ImageSharp.Tests/Rendering/ImageSharpRenderingEngineTests.cs
index 045d96d..7dc450a 100644
--- a/tests/FlexRender.ImageSharp.Tests/Rendering/ImageSharpRenderingEngineTests.cs
+++ b/tests/FlexRender.ImageSharp.Tests/Rendering/ImageSharpRenderingEngineTests.cs
@@ -36,7 +36,7 @@ public void RenderToImage_SimpleBackground_ProducesColoredImage()
Canvas = new CanvasSettings { Width = 100, Height = 50, Fixed = FixedDimension.Both, Background = "#ff0000" }
};
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.Equal(100, image.Width);
Assert.Equal(50, image.Height);
@@ -57,7 +57,7 @@ public void RenderToImage_TextElement_DrawsText()
};
template.AddElement(new TextElement { Content = "Hello", Size = "16", Color = "#000000" });
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
// Verify some pixels are non-white (text was drawn)
var hasNonWhitePixel = false;
@@ -90,7 +90,7 @@ public void RenderToImage_SeparatorElement_DrawsLine()
};
template.AddElement(new SeparatorElement { Color = "#000000", Thickness = 2 });
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
// Verify some pixels are non-white (separator was drawn)
var hasNonWhitePixel = false;
@@ -125,7 +125,7 @@ public void RenderToImage_NestedFlex_ProducesImage()
flex.AddChild(new TextElement { Content = "Inside flex", Color = "#ffffff", Size = "14" });
template.AddElement(flex);
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.Equal(300, image.Width);
Assert.Equal(100, image.Height);
@@ -145,7 +145,7 @@ public void RenderToImage_DisplayNone_SkipsElement()
Display = Display.None
});
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
// All pixels should be white since the element is hidden
var allWhite = true;
@@ -193,7 +193,7 @@ public void RenderToImage_TextWithRotation_DrawsRotatedText()
});
template.AddElement(flex);
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected rotated text to be drawn");
}
@@ -221,7 +221,7 @@ public void RenderToImage_TextWithFlipRotation_DrawsText()
});
template.AddElement(flex);
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected flipped text to be drawn");
}
@@ -249,7 +249,7 @@ public void RenderToImage_TextWithNumericRotation_DrawsText()
});
template.AddElement(flex);
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected angled text to be drawn");
}
@@ -270,7 +270,7 @@ public void RenderToImage_TextWithNoneRotation_DrawsNormally()
Rotate = "none"
});
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected text to be drawn normally with 'none' rotation");
}
@@ -289,7 +289,7 @@ public void RenderToImage_SeparatorWithRotation_DrawsRotated()
Rotate = "45"
});
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected rotated separator to be drawn");
}
@@ -317,7 +317,7 @@ public void RenderToImage_TextWithNegativeRotation_DrawsText()
});
template.AddElement(flex);
- using var image = _engine.RenderToImage(template, new ObjectValue());
+ using var image = _engine.RenderToImage(template);
Assert.True(HasNonWhitePixel(image), "Expected text with negative rotation to be drawn");
}
diff --git a/tests/FlexRender.Tests/Configuration/ResourceLimitsTests.cs b/tests/FlexRender.Tests/Configuration/ResourceLimitsTests.cs
index d777dd9..3831a67 100644
--- a/tests/FlexRender.Tests/Configuration/ResourceLimitsTests.cs
+++ b/tests/FlexRender.Tests/Configuration/ResourceLimitsTests.cs
@@ -355,7 +355,7 @@ public async Task FullPipeline_WithCustomLimits_RespectsAllLimits()
// Verify SkiaRenderer uses limits
using var renderer = new SkiaRenderer(limits);
- var size = await renderer.Measure(template, new ObjectValue(), TestContext.Current.CancellationToken);
+ var size = await renderer.MeasureAsync(template, new ObjectValue(), TestContext.Current.CancellationToken);
Assert.True(size.Width > 0);
}
diff --git a/tests/FlexRender.Tests/Expressions/ExpressionInConditionTests.cs b/tests/FlexRender.Tests/Expressions/ExpressionInConditionTests.cs
index 7255f8a..661d60f 100644
--- a/tests/FlexRender.Tests/Expressions/ExpressionInConditionTests.cs
+++ b/tests/FlexRender.Tests/Expressions/ExpressionInConditionTests.cs
@@ -30,7 +30,7 @@ private static Template CreateTemplate(params TemplateElement[] elements)
}
[Fact]
- public void Expand_IfWithArithmeticCondition_EvaluatesExpression()
+ public async Task Expand_IfWithArithmeticCondition_EvaluatesExpression()
{
// condition: price * quantity, greaterThan: 100
var ifElem = new IfElement(
@@ -50,7 +50,7 @@ public void Expand_IfWithArithmeticCondition_EvaluatesExpression()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: 25 * 5 = 125 > 100 => true => "Free shipping!"
Assert.Single(result.Elements);
@@ -59,7 +59,7 @@ public void Expand_IfWithArithmeticCondition_EvaluatesExpression()
}
[Fact]
- public void Expand_IfWithArithmeticCondition_FalseCase()
+ public async Task Expand_IfWithArithmeticCondition_FalseCase()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Free shipping!" } },
@@ -78,7 +78,7 @@ public void Expand_IfWithArithmeticCondition_FalseCase()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: 5 * 2 = 10 > 100 => false => "Shipping: $5"
Assert.Single(result.Elements);
@@ -87,7 +87,7 @@ public void Expand_IfWithArithmeticCondition_FalseCase()
}
[Fact]
- public void Expand_IfWithSubtractionCondition_EvaluatesExpression()
+ public async Task Expand_IfWithSubtractionCondition_EvaluatesExpression()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Positive balance" } },
@@ -106,7 +106,7 @@ public void Expand_IfWithSubtractionCondition_EvaluatesExpression()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: 100 - 30 = 70 > 0 => true
Assert.Single(result.Elements);
@@ -115,7 +115,7 @@ public void Expand_IfWithSubtractionCondition_EvaluatesExpression()
}
[Fact]
- public void Expand_IfWithTruthyExpressionCondition_EvaluatesArithmeticResult()
+ public async Task Expand_IfWithTruthyExpressionCondition_EvaluatesArithmeticResult()
{
// Truthy check on arithmetic expression: price * quantity
// Non-zero number is truthy
@@ -134,7 +134,7 @@ public void Expand_IfWithTruthyExpressionCondition_EvaluatesArithmeticResult()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: 10 * 3 = 30, truthy => "Has value"
Assert.Single(result.Elements);
@@ -143,7 +143,7 @@ public void Expand_IfWithTruthyExpressionCondition_EvaluatesArithmeticResult()
}
[Fact]
- public void Expand_IfWithNullCoalesceCondition_EvaluatesExpression()
+ public async Task Expand_IfWithNullCoalesceCondition_EvaluatesExpression()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Has name" } },
@@ -160,7 +160,7 @@ public void Expand_IfWithNullCoalesceCondition_EvaluatesExpression()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: name is null, nickname is "JohnD" => truthy => "Has name"
Assert.Single(result.Elements);
diff --git a/tests/FlexRender.Tests/Integration/AllFeaturesIntegrationTest.cs b/tests/FlexRender.Tests/Integration/AllFeaturesIntegrationTest.cs
index 8dca9db..71e9d46 100644
--- a/tests/FlexRender.Tests/Integration/AllFeaturesIntegrationTest.cs
+++ b/tests/FlexRender.Tests/Integration/AllFeaturesIntegrationTest.cs
@@ -261,7 +261,7 @@ public async Task AllFeatures_MeasureLayout_ReturnsReasonableSize()
var template = _parser.Parse(AllFeaturesYaml);
var data = CreateTestData(hasDiscount: false, subtotal: 150);
- var size = await _renderer.Measure(template, data, TestContext.Current.CancellationToken);
+ var size = await _renderer.MeasureAsync(template, data, TestContext.Current.CancellationToken);
Assert.Equal(400f, size.Width);
Assert.True(size.Height > 100, "Layout height should be substantial for a multi-section template");
diff --git a/tests/FlexRender.Tests/Integration/CanvasRtlPipelineTests.cs b/tests/FlexRender.Tests/Integration/CanvasRtlPipelineTests.cs
index 0c54e82..dc579ff 100644
--- a/tests/FlexRender.Tests/Integration/CanvasRtlPipelineTests.cs
+++ b/tests/FlexRender.Tests/Integration/CanvasRtlPipelineTests.cs
@@ -31,7 +31,7 @@ public void Dispose()
/// Canvas.TextDirection to the processed canvas, causing RTL mirroring to be lost.
///
[Fact]
- public void Parse_CanvasRtl_RowChildrenPositionedRightToLeft()
+ public async Task Parse_CanvasRtl_RowChildrenPositionedRightToLeft()
{
const string yaml = """
canvas:
@@ -59,7 +59,7 @@ public void Parse_CanvasRtl_RowChildrenPositionedRightToLeft()
var data = new ObjectValue();
// Full pipeline: expand -> preprocess -> layout
- var root = _renderer.ComputeLayout(template, data);
+ var root = await _renderer.ComputeLayoutAsync(template, data);
// root has one child: the row flex container
Assert.Single(root.Children);
diff --git a/tests/FlexRender.Tests/Integration/WbReceiptIntegrationTests.cs b/tests/FlexRender.Tests/Integration/WbReceiptIntegrationTests.cs
index 8c92872..c2cfa3f 100644
--- a/tests/FlexRender.Tests/Integration/WbReceiptIntegrationTests.cs
+++ b/tests/FlexRender.Tests/Integration/WbReceiptIntegrationTests.cs
@@ -17,7 +17,7 @@ public void Dispose()
}
[Fact]
- public void WbReceipt_RendersWithBlackColumns()
+ public async Task WbReceipt_RendersWithBlackColumns()
{
// Layout: 630px total width with 8px gaps between 3 columns
// Left column: 130px black, Center: 450px white, Right: 26px black
@@ -59,7 +59,7 @@ public void WbReceipt_RendersWithBlackColumns()
var data = new ObjectValue();
using var bitmap = new SKBitmap(630, 200);
- var exception = Record.Exception(() => _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () => await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
@@ -83,7 +83,7 @@ public void WbReceipt_RendersWithBlackColumns()
}
[Fact]
- public void WbReceipt_TextWithBackgroundButton_Renders()
+ public async Task WbReceipt_TextWithBackgroundButton_Renders()
{
const string yaml = """
canvas:
@@ -103,7 +103,7 @@ public void WbReceipt_TextWithBackgroundButton_Renders()
var data = new ObjectValue();
using var bitmap = new SKBitmap(200, 50);
- var exception = Record.Exception(() => _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () => await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
diff --git a/tests/FlexRender.Tests/Layout/FlexLayoutBugTests.cs b/tests/FlexRender.Tests/Layout/FlexLayoutBugTests.cs
index 2bea316..32aa6fd 100644
--- a/tests/FlexRender.Tests/Layout/FlexLayoutBugTests.cs
+++ b/tests/FlexRender.Tests/Layout/FlexLayoutBugTests.cs
@@ -47,7 +47,7 @@ public void Dispose()
/// See: docs/known-issues/layout-bugs.md
///
[Fact]
- public void AlignItemsEnd_AutoHeightRow_ChildrenAlignedToBottom()
+ public async Task AlignItemsEnd_AutoHeightRow_ChildrenAlignedToBottom()
{
// Arrange: Row container with align: end, no explicit height, 3 children of different heights.
// Each child has a distinct background color so we can verify position via pixel checks.
@@ -107,7 +107,7 @@ public void AlignItemsEnd_AutoHeightRow_ChildrenAlignedToBottom()
// Act: Render to bitmap
using var bitmap = new SKBitmap(180, 80);
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: Check pixel colors at strategic positions.
@@ -224,7 +224,7 @@ public void AlignItemsEnd_AutoHeightRow_LayoutPositions()
/// See: docs/known-issues/layout-bugs.md
///
[Fact]
- public void AlignItemsCenter_AutoHeightRow_ChildrenVerticallyCentered()
+ public async Task AlignItemsCenter_AutoHeightRow_ChildrenVerticallyCentered()
{
// Arrange: Row container with align: center, no explicit height, 3 children of different heights.
//
@@ -283,7 +283,7 @@ public void AlignItemsCenter_AutoHeightRow_ChildrenVerticallyCentered()
// Act: Render to bitmap
using var bitmap = new SKBitmap(180, 80);
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: Check pixel colors at strategic positions.
@@ -408,7 +408,7 @@ public void AlignItemsCenter_AutoHeightRow_LayoutPositions()
/// See: docs/known-issues/layout-bugs.md
///
[Fact]
- public void VerticalSeparator_InRowWithExplicitHeight_StretchesToContainerHeight()
+ public async Task VerticalSeparator_InRowWithExplicitHeight_StretchesToContainerHeight()
{
// Arrange: Row container (100px height) with two colored boxes and a vertical separator between them.
//
@@ -470,7 +470,7 @@ public void VerticalSeparator_InRowWithExplicitHeight_StretchesToContainerHeight
// Act: Render to bitmap
using var bitmap = new SKBitmap(200, 100);
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: The separator should be a visible black line spanning most of the height.
// The separator is at approximately X=80..81 (2px wide).
@@ -503,7 +503,7 @@ public void VerticalSeparator_InRowWithExplicitHeight_StretchesToContainerHeight
/// See: docs/known-issues/layout-bugs.md
///
[Fact]
- public void VerticalSeparator_InAutoHeightRow_StretchesToContentHeight()
+ public async Task VerticalSeparator_InAutoHeightRow_StretchesToContentHeight()
{
// Arrange: Row container (auto height) with two colored boxes and a vertical separator.
//
@@ -560,7 +560,7 @@ public void VerticalSeparator_InAutoHeightRow_StretchesToContentHeight()
// Act: Render to bitmap
using var bitmap = new SKBitmap(200, 80);
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: The separator should span from Y=0 to Y=79 (80px total).
// Check at multiple Y positions.
diff --git a/tests/FlexRender.Tests/Rendering/RenderingIntegrationTests.cs b/tests/FlexRender.Tests/Rendering/RenderingIntegrationTests.cs
index 7841f50..5c710b8 100644
--- a/tests/FlexRender.Tests/Rendering/RenderingIntegrationTests.cs
+++ b/tests/FlexRender.Tests/Rendering/RenderingIntegrationTests.cs
@@ -127,7 +127,7 @@ public async Task FullPipeline_MultipleFormats_AllWork()
}
[Fact]
- public void FullPipeline_HeightFixed_CalculatesWidth()
+ public async Task FullPipeline_HeightFixed_CalculatesWidth()
{
const string yaml = """
canvas:
@@ -142,7 +142,7 @@ public void FullPipeline_HeightFixed_CalculatesWidth()
var template = _parser.Parse(yaml);
var data = new ObjectValue();
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
Assert.Equal(100f, size.Height);
Assert.True(size.Width > 0);
diff --git a/tests/FlexRender.Tests/Rendering/SkiaRendererRotationTests.cs b/tests/FlexRender.Tests/Rendering/SkiaRendererRotationTests.cs
index ffc7f23..8639bb7 100644
--- a/tests/FlexRender.Tests/Rendering/SkiaRendererRotationTests.cs
+++ b/tests/FlexRender.Tests/Rendering/SkiaRendererRotationTests.cs
@@ -18,7 +18,7 @@ public void Dispose()
}
[Fact]
- public void Render_WithRotateRight_SwapsDimensions()
+ public async Task Render_WithRotateRight_SwapsDimensions()
{
// Arrange: 300x100 canvas should become 100x300 after 90 degree rotation
var template = new Template
@@ -44,9 +44,9 @@ public void Render_WithRotateRight_SwapsDimensions()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: dimensions should be swapped
Assert.Equal(100, size.Width);
@@ -56,7 +56,7 @@ public void Render_WithRotateRight_SwapsDimensions()
}
[Fact]
- public void Render_WithRotateLeft_SwapsDimensions()
+ public async Task Render_WithRotateLeft_SwapsDimensions()
{
// Arrange: 300x100 canvas should become 100x300 after 270 degree rotation
var template = new Template
@@ -82,9 +82,9 @@ public void Render_WithRotateLeft_SwapsDimensions()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: dimensions should be swapped
Assert.Equal(100, size.Width);
@@ -94,7 +94,7 @@ public void Render_WithRotateLeft_SwapsDimensions()
}
[Fact]
- public void Render_WithRotateFlip_KeepsDimensions()
+ public async Task Render_WithRotateFlip_KeepsDimensions()
{
// Arrange: 300x100 canvas should remain 300x100 after 180 degree rotation
var template = new Template
@@ -120,9 +120,9 @@ public void Render_WithRotateFlip_KeepsDimensions()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: dimensions should NOT be swapped
Assert.Equal(300, size.Width);
@@ -132,7 +132,7 @@ public void Render_WithRotateFlip_KeepsDimensions()
}
[Fact]
- public void Render_WithRotateNone_DoesNothing()
+ public async Task Render_WithRotateNone_DoesNothing()
{
// Arrange: 300x100 canvas should remain 300x100 with no rotation
var template = new Template
@@ -158,9 +158,9 @@ public void Render_WithRotateNone_DoesNothing()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: dimensions should NOT be swapped
Assert.Equal(300, size.Width);
@@ -170,7 +170,7 @@ public void Render_WithRotateNone_DoesNothing()
}
[Fact]
- public void Measure_WithRotate90_ReturnsSwappedDimensions()
+ public async Task Measure_WithRotate90_ReturnsSwappedDimensions()
{
// Arrange: receipt template 630px wide, content height ~200px
var template = new Template
@@ -195,7 +195,7 @@ public void Measure_WithRotate90_ReturnsSwappedDimensions()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
// Assert: width and height should be swapped
Assert.Equal(200, size.Width);
@@ -203,7 +203,7 @@ public void Measure_WithRotate90_ReturnsSwappedDimensions()
}
[Fact]
- public void Render_WithRotateRight_RotatesContentCorrectly()
+ public async Task Render_WithRotateRight_RotatesContentCorrectly()
{
// Arrange: Place a red element at top-left, after 90 CW rotation it should be at top-right
var template = new Template
@@ -238,9 +238,9 @@ public void Render_WithRotateRight_RotatesContentCorrectly()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: After 90 CW rotation, original top-left becomes top-right
// The red element was at (0-20, 0-20) in original 100x40
@@ -252,7 +252,7 @@ public void Render_WithRotateRight_RotatesContentCorrectly()
}
[Fact]
- public void Render_WithRotateLeft_RotatesContentCorrectly()
+ public async Task Render_WithRotateLeft_RotatesContentCorrectly()
{
// Arrange: Similar setup for 270 degree (left) rotation
var template = new Template
@@ -285,9 +285,9 @@ public void Render_WithRotateLeft_RotatesContentCorrectly()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: After 270 CCW rotation
Assert.Equal(40, size.Width);
@@ -295,7 +295,7 @@ public void Render_WithRotateLeft_RotatesContentCorrectly()
}
[Fact]
- public void Render_ThermalPrinterScenario_RotatesReceiptCorrectly()
+ public async Task Render_ThermalPrinterScenario_RotatesReceiptCorrectly()
{
// Arrange: Thermal printer receipt scenario
// Original: 630px wide, ~300px tall receipt
@@ -341,9 +341,9 @@ public void Render_ThermalPrinterScenario_RotatesReceiptCorrectly()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, (int)size.Height);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Assert: Width and height should be swapped for thermal printer
Assert.Equal(300, size.Width);
@@ -353,7 +353,7 @@ public void Render_ThermalPrinterScenario_RotatesReceiptCorrectly()
}
[Fact]
- public void Render_WithDefaultRotation_DoesNotRotate()
+ public async Task Render_WithDefaultRotation_DoesNotRotate()
{
// Arrange: Default CanvasSettings has Rotate = "none"
var template = new Template
@@ -377,7 +377,7 @@ public void Render_WithDefaultRotation_DoesNotRotate()
var data = new ObjectValue();
// Act
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
// Assert: No dimension swap
Assert.Equal(200, size.Width);
@@ -392,7 +392,7 @@ public void Render_WithDefaultRotation_DoesNotRotate()
[InlineData("90")]
[InlineData("180")]
[InlineData("270")]
- public void Render_VariousRotationValues_DoesNotThrow(string rotateValue)
+ public async Task Render_VariousRotationValues_DoesNotThrow(string rotateValue)
{
// Arrange
var template = new Template
@@ -412,11 +412,11 @@ public void Render_VariousRotationValues_DoesNotThrow(string rotateValue)
var data = new ObjectValue();
// Act & Assert: Should not throw
- var exception = Record.Exception(() =>
+ var exception = await Record.ExceptionAsync(async () =>
{
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)Math.Max(size.Width, 1), (int)Math.Max(size.Height, 1));
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
});
Assert.Null(exception);
diff --git a/tests/FlexRender.Tests/Rendering/SkiaRendererTests.cs b/tests/FlexRender.Tests/Rendering/SkiaRendererTests.cs
index dfb3eaf..bedec6e 100644
--- a/tests/FlexRender.Tests/Rendering/SkiaRendererTests.cs
+++ b/tests/FlexRender.Tests/Rendering/SkiaRendererTests.cs
@@ -1,7 +1,9 @@
+using FlexRender.Abstractions;
using FlexRender.Configuration;
using FlexRender.Parsing;
using FlexRender.Parsing.Ast;
using FlexRender.Rendering;
+using FlexRender.TemplateEngine;
using SkiaSharp;
using Xunit;
@@ -27,7 +29,7 @@ public void Constructor_CreatesInstance()
}
[Fact]
- public void Measure_SimpleTemplate_ReturnsDimensions()
+ public async Task Measure_SimpleTemplate_ReturnsDimensions()
{
var template = new Template
{
@@ -39,14 +41,14 @@ public void Measure_SimpleTemplate_ReturnsDimensions()
};
var data = new ObjectValue();
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
Assert.Equal(300f, size.Width);
Assert.True(size.Height > 0);
}
[Fact]
- public void Measure_EmptyTemplate_ReturnsMinimalSize()
+ public async Task Measure_EmptyTemplate_ReturnsMinimalSize()
{
var template = new Template
{
@@ -55,14 +57,14 @@ public void Measure_EmptyTemplate_ReturnsMinimalSize()
};
var data = new ObjectValue();
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
Assert.Equal(300f, size.Width);
Assert.True(size.Height >= 0);
}
[Fact]
- public void Measure_WithHeightFixed_ReturnsCorrectDimensions()
+ public async Task Measure_WithHeightFixed_ReturnsCorrectDimensions()
{
var template = new Template
{
@@ -74,19 +76,15 @@ public void Measure_WithHeightFixed_ReturnsCorrectDimensions()
};
var data = new ObjectValue();
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
Assert.Equal(500f, size.Height);
Assert.True(size.Width > 0);
}
[Fact]
- public void Render_ToCanvas_DrawsWithoutError()
+ public async Task Render_ToCanvas_DrawsWithoutError()
{
- using var bitmap = new SKBitmap(300, 100);
- using var canvas = new SKCanvas(bitmap);
- canvas.Clear(SKColors.White);
-
var template = new Template
{
Canvas = new CanvasSettings { Width = 300 },
@@ -97,18 +95,18 @@ public void Render_ToCanvas_DrawsWithoutError()
};
var data = new ObjectValue();
- var exception = Record.Exception(() =>
- _renderer.Render(canvas, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ {
+ using var bitmap = await _renderer.Render(template, data);
+ });
Assert.Null(exception);
}
[Fact]
- public void Render_ToCanvas_WithOffset_AppliesOffset()
+ public async Task Render_ToCanvas_WithOffset_AppliesOffset()
{
using var bitmap = new SKBitmap(300, 100);
- using var canvas = new SKCanvas(bitmap);
- canvas.Clear(SKColors.White);
var template = new Template
{
@@ -121,14 +119,14 @@ public void Render_ToCanvas_WithOffset_AppliesOffset()
var data = new ObjectValue();
var offset = new SKPoint(50, 25);
- var exception = Record.Exception(() =>
- _renderer.Render(canvas, template, data, offset));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, offset, default));
Assert.Null(exception);
}
[Fact]
- public void Render_ToBitmap_DrawsWithoutError()
+ public async Task Render_ToBitmap_DrawsWithoutError()
{
using var bitmap = new SKBitmap(300, 100);
@@ -142,14 +140,14 @@ public void Render_ToBitmap_DrawsWithoutError()
};
var data = new ObjectValue();
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
[Fact]
- public void Render_WithBackground_FillsBackground()
+ public async Task Render_WithBackground_FillsBackground()
{
using var bitmap = new SKBitmap(100, 100);
@@ -164,7 +162,7 @@ public void Render_WithBackground_FillsBackground()
};
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Check a pixel in the upper area where background should be rendered
var topPixel = bitmap.GetPixel(50, 10);
@@ -174,7 +172,7 @@ public void Render_WithBackground_FillsBackground()
}
[Fact]
- public void Render_WithDataSubstitution_SubstitutesValues()
+ public async Task Render_WithDataSubstitution_SubstitutesValues()
{
using var bitmap = new SKBitmap(300, 50);
@@ -189,14 +187,14 @@ public void Render_WithDataSubstitution_SubstitutesValues()
var data = new ObjectValue { ["name"] = "World" };
// This should not throw - full text content verification would be in snapshot tests
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
[Fact]
- public void Render_ParsedYaml_RendersCorrectly()
+ public async Task Render_ParsedYaml_RendersCorrectly()
{
const string yaml = """
canvas:
@@ -212,14 +210,14 @@ public void Render_ParsedYaml_RendersCorrectly()
var data = new ObjectValue();
using var bitmap = new SKBitmap(200, 50);
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
[Fact]
- public void Render_MultipleTextElements_DrawsAll()
+ public async Task Render_MultipleTextElements_DrawsAll()
{
using var bitmap = new SKBitmap(300, 150);
@@ -235,14 +233,14 @@ public void Render_MultipleTextElements_DrawsAll()
};
var data = new ObjectValue();
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
[Fact]
- public void Render_WithITemplateData_WorksCorrectly()
+ public async Task Render_WithITemplateData_WorksCorrectly()
{
using var bitmap = new SKBitmap(300, 50);
@@ -256,8 +254,8 @@ public void Render_WithITemplateData_WorksCorrectly()
};
var data = new TestTemplateData { Price = 99.99m };
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data.ToTemplateValue(), default, default));
Assert.Null(exception);
}
@@ -273,7 +271,7 @@ public ObjectValue ToTemplateValue()
}
[Fact]
- public void Render_FlexWithBackground_DrawsBackground()
+ public async Task Render_FlexWithBackground_DrawsBackground()
{
using var bitmap = new SKBitmap(100, 100);
@@ -293,7 +291,7 @@ public void Render_FlexWithBackground_DrawsBackground()
};
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Check pixel in the flex area (should be red)
var pixel = bitmap.GetPixel(50, 25);
@@ -303,7 +301,7 @@ public void Render_FlexWithBackground_DrawsBackground()
}
[Fact]
- public void Render_TextWithBackground_DrawsBackground()
+ public async Task Render_TextWithBackground_DrawsBackground()
{
using var bitmap = new SKBitmap(200, 100);
@@ -324,7 +322,7 @@ public void Render_TextWithBackground_DrawsBackground()
};
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Check that green background was drawn in text area (bottom-left region below text line)
var pixel = bitmap.GetPixel(10, 40);
@@ -332,7 +330,7 @@ public void Render_TextWithBackground_DrawsBackground()
}
[Fact]
- public void Render_ElementWithoutBackground_NoBackgroundDrawn()
+ public async Task Render_ElementWithoutBackground_NoBackgroundDrawn()
{
using var bitmap = new SKBitmap(100, 100);
@@ -352,7 +350,7 @@ public void Render_ElementWithoutBackground_NoBackgroundDrawn()
};
var data = new ObjectValue();
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// Pixel should be white (canvas background), not anything else
var pixel = bitmap.GetPixel(25, 25);
@@ -365,7 +363,7 @@ public void Render_ElementWithoutBackground_NoBackgroundDrawn()
/// Verifies that a font named "default" is also registered as "main".
///
[Fact]
- public void Render_DefaultFont_RegisteredAsMain()
+ public async Task Render_DefaultFont_RegisteredAsMain()
{
const string yaml = """
fonts:
@@ -384,8 +382,8 @@ public void Render_DefaultFont_RegisteredAsMain()
// Should not throw - the "default" font is registered as "main"
// which is the default font reference for TextElement
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
@@ -394,7 +392,7 @@ public void Render_DefaultFont_RegisteredAsMain()
/// Verifies that text elements without explicit font use the default font.
///
[Fact]
- public void Render_TextWithoutExplicitFont_UsesDefaultFont()
+ public async Task Render_TextWithoutExplicitFont_UsesDefaultFont()
{
const string yaml = """
fonts:
@@ -416,8 +414,8 @@ public void Render_TextWithoutExplicitFont_UsesDefaultFont()
using var bitmap = new SKBitmap(300, 100);
// Both text elements should render without error
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
@@ -440,7 +438,7 @@ public void Constructor_WithResourceLimits_SetsMaxRenderDepth()
}
[Fact]
- public void Constructor_DefaultLimits_Uses100MaxRenderDepth()
+ public async Task Constructor_DefaultLimits_Uses100MaxRenderDepth()
{
// The parameterless constructor should use 100 as default
using var renderer = new SkiaRenderer();
@@ -456,12 +454,12 @@ public void Constructor_DefaultLimits_Uses100MaxRenderDepth()
};
var data = new ObjectValue();
- var size = renderer.Measure(template, data);
+ var size = await renderer.MeasureAsync(template, data);
Assert.True(size.Width > 0);
}
[Fact]
- public void Render_SeparatorWithTemplateColorExpression_ResolvesColor()
+ public async Task Render_SeparatorWithTemplateColorExpression_ResolvesColor()
{
var template = new Template
{
@@ -482,9 +480,9 @@ public void Render_SeparatorWithTemplateColorExpression_ResolvesColor()
};
// Measure first to get correct bitmap size
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
using var bitmap = new SKBitmap((int)size.Width, Math.Max((int)size.Height, 2));
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
// The separator line is drawn at y + height/2; with thickness=2, height=2, so lineY=1
var pixel = bitmap.GetPixel(100, 1);
@@ -498,7 +496,7 @@ public void Render_SeparatorWithTemplateColorExpression_ResolvesColor()
/// to a system default font.
///
[Fact]
- public void Render_TextWithNoFontsSection_UsesDefaultFallback()
+ public async Task Render_TextWithNoFontsSection_UsesDefaultFallback()
{
var template = new Template
{
@@ -518,8 +516,8 @@ public void Render_TextWithNoFontsSection_UsesDefaultFallback()
using var bitmap = new SKBitmap(200, 100);
// Should not throw NullReferenceException even though no fonts are configured
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
@@ -558,7 +556,7 @@ public async Task RenderToPng_NoFontsSection_DoesNotThrow()
/// Verifies that parsed YAML with text but no fonts section renders correctly.
///
[Fact]
- public void Render_ParsedYamlNoFonts_RendersCorrectly()
+ public async Task Render_ParsedYamlNoFonts_RendersCorrectly()
{
const string yaml = """
canvas:
@@ -577,8 +575,8 @@ public void Render_ParsedYamlNoFonts_RendersCorrectly()
using var bitmap = new SKBitmap(200, 100);
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
Assert.Null(exception);
}
@@ -588,7 +586,7 @@ public void Render_ParsedYamlNoFonts_RendersCorrectly()
/// NRE when no fonts section is present.
///
[Fact]
- public void Render_MultipleTextNoFonts_DoesNotThrow()
+ public async Task Render_MultipleTextNoFonts_DoesNotThrow()
{
var template = new Template
{
@@ -604,9 +602,82 @@ public void Render_MultipleTextNoFonts_DoesNotThrow()
using var bitmap = new SKBitmap(300, 200);
- var exception = Record.Exception(() =>
- _renderer.Render(bitmap, template, data));
+ var exception = await Record.ExceptionAsync(async () =>
+ await _renderer.Render(bitmap, template, data, default, default));
+
+ Assert.Null(exception);
+ }
+
+ ///
+ /// Regression test: ContentElement previously threw TemplateEngineException
+ /// "Content element expansion requires async processing" when render pipeline
+ /// used sync Expand() instead of ExpandAsync().
+ ///
+ [Fact]
+ public async Task ContentElement_RendersSuccessfully_ThroughAsyncPipeline()
+ {
+ // Arrange: register a simple content parser that expands to text elements
+ var registry = new ContentParserRegistry();
+ registry.Register(new InlineTestContentParser());
+
+ using var renderer = new SkiaRenderer(
+ new ResourceLimits(),
+ qrProvider: null,
+ barcodeProvider: null,
+ imageLoader: null,
+ contentParserRegistry: registry);
+
+ var template = new Template
+ {
+ Canvas = new CanvasSettings { Width = 300, Background = "#ffffff" },
+ Elements = new List
+ {
+ new FlexElement
+ {
+ Children = new List
+ {
+ new TextElement { Content = "Before content" },
+ new ContentElement { Source = "Hello|World", Format = "pipe-split" },
+ new TextElement { Content = "After content" }
+ }
+ }
+ }
+ };
+ var data = new ObjectValue();
+
+ // Act & Assert: should not throw TemplateEngineException about sync Expand()
+ var exception = await Record.ExceptionAsync(async () =>
+ {
+ using var bitmap = await renderer.Render(template, data);
+ Assert.NotNull(bitmap);
+ Assert.True(bitmap.Width > 0);
+ Assert.True(bitmap.Height > 0);
+ });
Assert.Null(exception);
}
+
+ ///
+ /// Simple content parser that splits text on pipe characters and creates a
+ /// for each segment. Used by regression tests.
+ ///
+ private sealed class InlineTestContentParser : IContentParser
+ {
+ ///
+ public string FormatName => "pipe-split";
+
+ ///
+ public IReadOnlyList Parse(
+ string text,
+ ContentParserContext context,
+ IReadOnlyDictionary? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(text);
+ if (string.IsNullOrWhiteSpace(text)) return [];
+
+ return text.Split('|', StringSplitOptions.RemoveEmptyEntries)
+ .Select(segment => (TemplateElement)new TextElement { Content = segment.Trim() })
+ .ToList();
+ }
+ }
}
diff --git a/tests/FlexRender.Tests/Rendering/SkiaTextShaperIntegrationTests.cs b/tests/FlexRender.Tests/Rendering/SkiaTextShaperIntegrationTests.cs
index b2c3887..ddb295f 100644
--- a/tests/FlexRender.Tests/Rendering/SkiaTextShaperIntegrationTests.cs
+++ b/tests/FlexRender.Tests/Rendering/SkiaTextShaperIntegrationTests.cs
@@ -18,7 +18,7 @@ public SkiaTextShaperIntegrationTests()
}
[Fact]
- public void ComputeLayout_PopulatesTextLinesFromSkiaTextShaper()
+ public async Task ComputeLayout_PopulatesTextLinesFromSkiaTextShaper()
{
var template = new Template
{
@@ -29,7 +29,7 @@ public void ComputeLayout_PopulatesTextLinesFromSkiaTextShaper()
}
};
- var root = _renderer.ComputeLayout(template, new ObjectValue());
+ var root = await _renderer.ComputeLayoutAsync(template, new ObjectValue());
var textNode = root.Children[0];
Assert.NotNull(textNode.TextLines);
@@ -38,7 +38,7 @@ public void ComputeLayout_PopulatesTextLinesFromSkiaTextShaper()
}
[Fact]
- public void ComputeLayout_WrappedText_ProducesMultipleLines()
+ public async Task ComputeLayout_WrappedText_ProducesMultipleLines()
{
var template = new Template
{
@@ -54,7 +54,7 @@ public void ComputeLayout_WrappedText_ProducesMultipleLines()
}
};
- var root = _renderer.ComputeLayout(template, new ObjectValue());
+ var root = await _renderer.ComputeLayoutAsync(template, new ObjectValue());
var textNode = root.Children[0];
Assert.NotNull(textNode.TextLines);
diff --git a/tests/FlexRender.Tests/Rendering/SvgQrRenderingTests.cs b/tests/FlexRender.Tests/Rendering/SvgQrRenderingTests.cs
index 1a268d7..fbe8970 100644
--- a/tests/FlexRender.Tests/Rendering/SvgQrRenderingTests.cs
+++ b/tests/FlexRender.Tests/Rendering/SvgQrRenderingTests.cs
@@ -21,13 +21,13 @@ public sealed class SvgQrRenderingTests
/// using the dedicated QrSvgProvider.
///
[Fact]
- public void RenderToSvg_QrElement_ProducesSvgPathNotBase64()
+ public async Task RenderToSvg_QrElement_ProducesSvgPathNotBase64()
{
var svgProvider = new QrSvgProvider();
var engine = CreateEngineWithSvgProvider(svgProvider);
var template = CreateTemplateWithQr("https://example.com");
- var svg = engine.RenderToSvg(template, new ObjectValue());
+ var svg = engine.Engine.RenderToSvgFromProcessed(await engine.Pipeline.ProcessAsync(template, new ObjectValue()));
// Should contain native SVG path, not base64 image
Assert.Contains("
[Fact]
- public void RenderToSvg_QrElementWithBitmapOnlyProvider_ProducesBase64Image()
+ public async Task RenderToSvg_QrElementWithBitmapOnlyProvider_ProducesBase64Image()
{
var bitmapOnlyProvider = new BitmapOnlyQrProvider();
var engine = CreateEngineWithRasterProvider(bitmapOnlyProvider);
var template = CreateTemplateWithQr("https://example.com");
- var svg = engine.RenderToSvg(template, new ObjectValue());
+ var svg = engine.Engine.RenderToSvgFromProcessed(await engine.Pipeline.ProcessAsync(template, new ObjectValue()));
// Should fall back to base64 image
Assert.Contains("data:image/png;base64", svg);
@@ -57,13 +57,13 @@ public void RenderToSvg_QrElementWithBitmapOnlyProvider_ProducesBase64Image()
/// Verifies the SVG output contains the correct foreground color.
///
[Fact]
- public void RenderToSvg_QrElementWithCustomColor_UsesForegroundColor()
+ public async Task RenderToSvg_QrElementWithCustomColor_UsesForegroundColor()
{
var svgProvider = new QrSvgProvider();
var engine = CreateEngineWithSvgProvider(svgProvider);
var template = CreateTemplateWithQr("Hello", foreground: "#ff0000");
- var svg = engine.RenderToSvg(template, new ObjectValue());
+ var svg = engine.Engine.RenderToSvgFromProcessed(await engine.Pipeline.ProcessAsync(template, new ObjectValue()));
Assert.Contains("fill=\"#ff0000\"", svg);
}
@@ -72,45 +72,49 @@ public void RenderToSvg_QrElementWithCustomColor_UsesForegroundColor()
/// Verifies the SVG output contains background rect when background is set.
///
[Fact]
- public void RenderToSvg_QrElementWithBackground_ContainsBackgroundRect()
+ public async Task RenderToSvg_QrElementWithBackground_ContainsBackgroundRect()
{
var svgProvider = new QrSvgProvider();
var engine = CreateEngineWithSvgProvider(svgProvider);
var template = CreateTemplateWithQr("Hello", background: "#ffffff");
- var svg = engine.RenderToSvg(template, new ObjectValue());
+ var svg = engine.Engine.RenderToSvgFromProcessed(await engine.Pipeline.ProcessAsync(template, new ObjectValue()));
Assert.Contains("fill=\"#ffffff\"", svg);
}
- private static SvgRenderingEngine CreateEngineWithSvgProvider(ISvgContentProvider svgProvider)
+ private static (SvgRenderingEngine Engine, TemplatePipeline Pipeline) CreateEngineWithSvgProvider(ISvgContentProvider svgProvider)
{
var limits = new ResourceLimits();
var expander = new TemplateExpander(limits);
var pipeline = new TemplatePipeline(expander, new TemplateProcessor(limits));
var layoutEngine = new LayoutEngine(limits);
- return new SvgRenderingEngine(
+ var engine = new SvgRenderingEngine(
limits,
pipeline,
layoutEngine,
baseFontSize: 16f,
qrSvgProvider: svgProvider);
+
+ return (engine, pipeline);
}
- private static SvgRenderingEngine CreateEngineWithRasterProvider(IContentProvider rasterProvider)
+ private static (SvgRenderingEngine Engine, TemplatePipeline Pipeline) CreateEngineWithRasterProvider(IContentProvider rasterProvider)
{
var limits = new ResourceLimits();
var expander = new TemplateExpander(limits);
var pipeline = new TemplatePipeline(expander, new TemplateProcessor(limits));
var layoutEngine = new LayoutEngine(limits);
- return new SvgRenderingEngine(
+ var engine = new SvgRenderingEngine(
limits,
pipeline,
layoutEngine,
baseFontSize: 16f,
qrProvider: rasterProvider);
+
+ return (engine, pipeline);
}
private static Template CreateTemplateWithQr(
diff --git a/tests/FlexRender.Tests/Snapshots/NdcSnapshotTests.cs b/tests/FlexRender.Tests/Snapshots/NdcSnapshotTests.cs
index 474cdfe..fcf2409 100644
--- a/tests/FlexRender.Tests/Snapshots/NdcSnapshotTests.cs
+++ b/tests/FlexRender.Tests/Snapshots/NdcSnapshotTests.cs
@@ -75,7 +75,7 @@ private static string GetFontsBasePath()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_SimpleRussianAscii()
+ public async Task NdcReceipt_SimpleRussianAscii()
{
var ndcData = ":02\x1b(1 \x1b(Intcnjdsq ~fyr f\x1b(1\r\n" +
" \x1b(Intk\x1b(1. 8 (800) 000-00-00\r\n" +
@@ -91,7 +91,7 @@ public void NdcReceipt_SimpleRussianAscii()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_simple", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_simple", template, new ObjectValue());
}
///
@@ -100,7 +100,7 @@ public void NdcReceipt_SimpleRussianAscii()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_WithFormFeed()
+ public async Task NdcReceipt_WithFormFeed()
{
var ndcData = "\x1b(1PAGE 1 CONTENT\r\n" +
"Line 2\x0c" +
@@ -120,7 +120,7 @@ public void NdcReceipt_WithFormFeed()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_formfeed", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_formfeed", template, new ObjectValue());
}
///
@@ -129,7 +129,7 @@ public void NdcReceipt_WithFormFeed()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_WithSpacing()
+ public async Task NdcReceipt_WithSpacing()
{
var ndcData = "\x1b(1Label\x0e5Value\r\n" +
"Left\x0e9Right";
@@ -147,7 +147,7 @@ public void NdcReceipt_WithSpacing()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_spacing", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_spacing", template, new ObjectValue());
}
///
@@ -155,7 +155,7 @@ public void NdcReceipt_WithSpacing()
/// Charset > renders bold text at 48pt (double of auto 24); charset 1 uses auto 24pt.
///
[Fact]
- public void NdcReceipt_DoubleSizeCharset()
+ public async Task NdcReceipt_DoubleSizeCharset()
{
var ndcData = "\x1b(>HEADER\r\n" +
"\x1b(1Normal text\r\n" +
@@ -188,7 +188,7 @@ public void NdcReceipt_DoubleSizeCharset()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_doublesize", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_doublesize", template, new ObjectValue());
}
///
@@ -196,7 +196,7 @@ public void NdcReceipt_DoubleSizeCharset()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankAMiniStatement()
+ public async Task NdcReceipt_BankAMiniStatement()
{
var text = LoadTestData("bank-a-mini-statement.bin");
@@ -208,7 +208,7 @@ public void NdcReceipt_BankAMiniStatement()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_a", template, new ObjectValue(), maxDifferencePercent: 7.0);
+ await AssertSnapshot("ndc_receipt_bank_a", template, new ObjectValue(), maxDifferencePercent: 7.0);
}
///
@@ -217,7 +217,7 @@ public void NdcReceipt_BankAMiniStatement()
/// Uses 44 columns with auto font size (~21pt from 576 / (44 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankEBalance()
+ public async Task NdcReceipt_BankEBalance()
{
var text = LoadTestData("bank-e-balance.bin");
@@ -229,7 +229,7 @@ public void NdcReceipt_BankEBalance()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_e", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_bank_e", template, new ObjectValue());
}
///
@@ -237,7 +237,7 @@ public void NdcReceipt_BankEBalance()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankCBalance()
+ public async Task NdcReceipt_BankCBalance()
{
var text = LoadTestData("bank-c-balance-receipt.bin");
@@ -249,7 +249,7 @@ public void NdcReceipt_BankCBalance()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_c_balance", template, new ObjectValue(), maxDifferencePercent: 7.0);
+ await AssertSnapshot("ndc_receipt_bank_c_balance", template, new ObjectValue(), maxDifferencePercent: 7.0);
}
///
@@ -257,7 +257,7 @@ public void NdcReceipt_BankCBalance()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankCStatement()
+ public async Task NdcReceipt_BankCStatement()
{
var text = LoadTestData("bank-c-statement-receipt.bin");
@@ -269,7 +269,7 @@ public void NdcReceipt_BankCStatement()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_c_statement", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_bank_c_statement", template, new ObjectValue());
}
///
@@ -277,7 +277,7 @@ public void NdcReceipt_BankCStatement()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankDBalance()
+ public async Task NdcReceipt_BankDBalance()
{
var text = LoadTestData("bank-d-balance-receipt.bin");
@@ -312,7 +312,7 @@ public void NdcReceipt_BankDBalance()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_d_balance", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_bank_d_balance", template, new ObjectValue());
}
///
@@ -320,7 +320,7 @@ public void NdcReceipt_BankDBalance()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankACashout()
+ public async Task NdcReceipt_BankACashout()
{
var text = LoadTestData("bank-a-cashout-receipt.bin");
@@ -332,7 +332,7 @@ public void NdcReceipt_BankACashout()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_a_cashout", template, new ObjectValue(), maxDifferencePercent: 7.0);
+ await AssertSnapshot("ndc_receipt_bank_a_cashout", template, new ObjectValue(), maxDifferencePercent: 7.0);
}
///
@@ -341,7 +341,7 @@ public void NdcReceipt_BankACashout()
/// Uses auto font size (24pt from 576 / (40 * 0.6)).
///
[Fact]
- public void NdcReceipt_BankBBalance()
+ public async Task NdcReceipt_BankBBalance()
{
var text = LoadTestData("bank-b-balance-receipt.bin", System.Text.Encoding.UTF8);
@@ -358,7 +358,7 @@ public void NdcReceipt_BankBBalance()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_bank_b_balance", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_bank_b_balance", template, new ObjectValue());
}
///
@@ -366,7 +366,7 @@ public void NdcReceipt_BankBBalance()
/// No explicit font_size on charsets -- auto = 576 / (40 * 0.6) = 24.
///
[Fact]
- public void NdcReceipt_AutoFontSize()
+ public async Task NdcReceipt_AutoFontSize()
{
var ndcData = ":02\x1b(1 \x1b(Intcnjdsq ~fyr f\x1b(1\r\n" +
" \x1b(Intk\x1b(1. 8 (800) 000-00-00\r\n" +
@@ -402,7 +402,7 @@ public void NdcReceipt_AutoFontSize()
foreach (var el in elements)
template.AddElement(el);
- AssertSnapshot("ndc_receipt_autofont", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_autofont", template, new ObjectValue());
}
///
@@ -412,7 +412,7 @@ public void NdcReceipt_AutoFontSize()
/// is embedded within a larger composed template with non-NDC elements and decorations.
///
[Fact]
- public void NdcReceipt_CompositeWithHeaderFooter()
+ public async Task NdcReceipt_CompositeWithHeaderFooter()
{
var text = LoadTestData("bank-a-cashout-receipt.bin");
@@ -484,7 +484,7 @@ public void NdcReceipt_CompositeWithHeaderFooter()
template.AddElement(card);
- AssertSnapshot("ndc_receipt_composite", template, new ObjectValue());
+ await AssertSnapshot("ndc_receipt_composite", template, new ObjectValue());
}
///
diff --git a/tests/FlexRender.Tests/Snapshots/SeparatorSnapshotTests.cs b/tests/FlexRender.Tests/Snapshots/SeparatorSnapshotTests.cs
index d544e91..9fd04d4 100644
--- a/tests/FlexRender.Tests/Snapshots/SeparatorSnapshotTests.cs
+++ b/tests/FlexRender.Tests/Snapshots/SeparatorSnapshotTests.cs
@@ -17,7 +17,7 @@ public sealed class SeparatorSnapshotTests : SnapshotTestBase
/// Tests horizontal separators with all three styles: dotted, dashed, and solid.
///
[Fact]
- public void SeparatorHorizontal_AllStyles()
+ public async Task SeparatorHorizontal_AllStyles()
{
var template = CreateTemplate(300);
@@ -57,14 +57,14 @@ public void SeparatorHorizontal_AllStyles()
template.AddElement(flex);
- AssertSnapshot("separator_horizontal_all_styles", template, new ObjectValue());
+ await AssertSnapshot("separator_horizontal_all_styles", template, new ObjectValue());
}
///
/// Tests vertical separators with all three styles: dotted, dashed, and solid.
///
[Fact]
- public void SeparatorVertical_AllStyles()
+ public async Task SeparatorVertical_AllStyles()
{
var template = CreateTemplate(300);
@@ -108,7 +108,7 @@ public void SeparatorVertical_AllStyles()
template.AddElement(flex);
- AssertSnapshot("separator_vertical_all_styles", template, new ObjectValue());
+ await AssertSnapshot("separator_vertical_all_styles", template, new ObjectValue());
}
///
diff --git a/tests/FlexRender.Tests/Snapshots/SnapshotTestBase.cs b/tests/FlexRender.Tests/Snapshots/SnapshotTestBase.cs
index 93e54a6..494d364 100644
--- a/tests/FlexRender.Tests/Snapshots/SnapshotTestBase.cs
+++ b/tests/FlexRender.Tests/Snapshots/SnapshotTestBase.cs
@@ -139,7 +139,7 @@ protected SnapshotTestBase()
///
/// Thrown when the rendered image does not match the golden image and update mode is disabled.
///
- protected void AssertSnapshot(
+ protected async Task AssertSnapshot(
string testName,
Template template,
ObjectValue data,
@@ -153,7 +153,7 @@ protected void AssertSnapshot(
ObjectDisposedException.ThrowIf(_disposed, this);
// Render the template to a bitmap
- using var actualBitmap = RenderTemplate(template, data);
+ using var actualBitmap = await RenderTemplate(template, data);
if (_updateSnapshots)
{
@@ -170,9 +170,9 @@ protected void AssertSnapshot(
/// The template to render.
/// The data for variable substitution.
/// A bitmap containing the rendered template.
- private SKBitmap RenderTemplate(Template template, ObjectValue data)
+ private async Task RenderTemplate(Template template, ObjectValue data)
{
- var size = _renderer.Measure(template, data);
+ var size = await _renderer.MeasureAsync(template, data);
var width = (int)Math.Ceiling(size.Width);
var height = (int)Math.Ceiling(size.Height);
@@ -181,7 +181,7 @@ private SKBitmap RenderTemplate(Template template, ObjectValue data)
height = Math.Max(height, 1);
var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- _renderer.Render(bitmap, template, data);
+ await _renderer.Render(bitmap, template, data, default, default);
return bitmap;
}
diff --git a/tests/FlexRender.Tests/Snapshots/SvgSnapshotTestBase.cs b/tests/FlexRender.Tests/Snapshots/SvgSnapshotTestBase.cs
index 9792219..7dad5c5 100644
--- a/tests/FlexRender.Tests/Snapshots/SvgSnapshotTestBase.cs
+++ b/tests/FlexRender.Tests/Snapshots/SvgSnapshotTestBase.cs
@@ -67,6 +67,7 @@ public abstract class SvgSnapshotTestBase
private const string OutputDirectoryName = "output";
private readonly SvgRenderingEngine _engine;
+ private readonly TemplatePipeline _pipeline;
private readonly string _snapshotsBasePath;
private readonly bool _updateSnapshots;
@@ -81,12 +82,12 @@ protected SvgSnapshotTestBase()
var limits = new ResourceLimits();
var expander = new TemplateExpander(limits);
- var pipeline = new TemplatePipeline(expander, new TemplateProcessor(limits));
+ _pipeline = new TemplatePipeline(expander, new TemplateProcessor(limits));
var layoutEngine = new LayoutEngine(limits);
_engine = new SvgRenderingEngine(
limits,
- pipeline,
+ _pipeline,
layoutEngine,
baseFontSize: 16f,
qrSvgProvider: new QrSvgProvider(),
@@ -134,14 +135,15 @@ protected static Template CreateTemplate(int width, int height)
///
/// Thrown when is empty or whitespace.
///
- protected void AssertSvgSnapshot(string testName, Template template, ObjectValue data)
+ protected async Task AssertSvgSnapshot(string testName, Template template, ObjectValue data)
{
ArgumentNullException.ThrowIfNull(testName);
ArgumentNullException.ThrowIfNull(template);
ArgumentNullException.ThrowIfNull(data);
ArgumentException.ThrowIfNullOrWhiteSpace(testName);
- var actualSvg = _engine.RenderToSvg(template, data);
+ var processedTemplate = await _pipeline.ProcessAsync(template, data);
+ var actualSvg = _engine.RenderToSvgFromProcessed(processedTemplate);
if (_updateSnapshots)
{
diff --git a/tests/FlexRender.Tests/Snapshots/SvgSnapshotTests.cs b/tests/FlexRender.Tests/Snapshots/SvgSnapshotTests.cs
index cca224f..f4e4c40 100644
--- a/tests/FlexRender.Tests/Snapshots/SvgSnapshotTests.cs
+++ b/tests/FlexRender.Tests/Snapshots/SvgSnapshotTests.cs
@@ -25,7 +25,7 @@ public sealed class SvgSnapshotTests : SvgSnapshotTestBase
/// with the expected font-size, fill color, and text-anchor attributes.
///
[Fact]
- public void SvgTextBasic()
+ public async Task SvgTextBasic()
{
var template = CreateTemplate(300, 100);
template.AddElement(new TextElement
@@ -37,7 +37,7 @@ public void SvgTextBasic()
Width = "300"
});
- AssertSvgSnapshot("svg_text_basic", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_text_basic", template, new ObjectValue());
}
///
@@ -46,7 +46,7 @@ public void SvgTextBasic()
/// with the correct stroke-dasharray attribute.
///
[Fact]
- public void SvgSeparatorHorizontal()
+ public async Task SvgSeparatorHorizontal()
{
var template = CreateTemplate(300, 50);
var flex = new FlexElement
@@ -66,7 +66,7 @@ public void SvgSeparatorHorizontal()
template.AddElement(flex);
- AssertSvgSnapshot("svg_separator_horizontal", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_separator_horizontal", template, new ObjectValue());
}
///
@@ -75,7 +75,7 @@ public void SvgSeparatorHorizontal()
/// for each child element.
///
[Fact]
- public void SvgFlexColumn()
+ public async Task SvgFlexColumn()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -91,7 +91,7 @@ public void SvgFlexColumn()
template.AddElement(flex);
- AssertSvgSnapshot("svg_flex_column", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_flex_column", template, new ObjectValue());
}
///
@@ -100,7 +100,7 @@ public void SvgFlexColumn()
/// with children placed side by side.
///
[Fact]
- public void SvgFlexRow()
+ public async Task SvgFlexRow()
{
var template = CreateTemplate(300, 100);
var flex = new FlexElement
@@ -115,7 +115,7 @@ public void SvgFlexRow()
template.AddElement(flex);
- AssertSvgSnapshot("svg_flex_row", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_flex_row", template, new ObjectValue());
}
///
@@ -124,7 +124,7 @@ public void SvgFlexRow()
/// for each nesting level and that positioning is accurate.
///
[Fact]
- public void SvgBackgroundColors()
+ public async Task SvgBackgroundColors()
{
var template = CreateTemplate(300, 200);
@@ -157,7 +157,7 @@ public void SvgBackgroundColors()
outer.AddChild(inner);
template.AddElement(outer);
- AssertSvgSnapshot("svg_background_colors", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_background_colors", template, new ObjectValue());
}
///
@@ -166,7 +166,7 @@ public void SvgBackgroundColors()
/// as an SVG rect with fill="none" and appropriate stroke attributes.
///
[Fact]
- public void SvgBorderBasic()
+ public async Task SvgBorderBasic()
{
var template = CreateTemplate(300, 150);
@@ -188,7 +188,7 @@ public void SvgBorderBasic()
template.AddElement(box);
- AssertSvgSnapshot("svg_border_basic", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_border_basic", template, new ObjectValue());
}
///
@@ -197,7 +197,7 @@ public void SvgBorderBasic()
/// rather than rasterized base64 images.
///
[Fact]
- public void SvgQrBasic()
+ public async Task SvgQrBasic()
{
var template = CreateTemplate(200, 200);
@@ -218,7 +218,7 @@ public void SvgQrBasic()
template.AddElement(flex);
- AssertSvgSnapshot("svg_qr_basic", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_qr_basic", template, new ObjectValue());
}
///
@@ -227,7 +227,7 @@ public void SvgQrBasic()
/// for Code 128 barcodes with correct foreground and background fills.
///
[Fact]
- public void SvgBarcodeBasic()
+ public async Task SvgBarcodeBasic()
{
var template = CreateTemplate(300, 120);
@@ -249,6 +249,6 @@ public void SvgBarcodeBasic()
template.AddElement(flex);
- AssertSvgSnapshot("svg_barcode_basic", template, new ObjectValue());
+ await AssertSvgSnapshot("svg_barcode_basic", template, new ObjectValue());
}
}
diff --git a/tests/FlexRender.Tests/Snapshots/VisualSnapshotTests.cs b/tests/FlexRender.Tests/Snapshots/VisualSnapshotTests.cs
index 4b1ae39..7705318 100644
--- a/tests/FlexRender.Tests/Snapshots/VisualSnapshotTests.cs
+++ b/tests/FlexRender.Tests/Snapshots/VisualSnapshotTests.cs
@@ -34,7 +34,7 @@ public sealed class VisualSnapshotTests : SnapshotTestBase
/// Tests basic text rendering with default settings.
///
[Fact]
- public void TextSimple()
+ public async Task TextSimple()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -46,14 +46,14 @@ public void TextSimple()
Color = "#000000"
});
- AssertSnapshot("text_simple", template, new ObjectValue());
+ await AssertSnapshot("text_simple", template, new ObjectValue());
}
///
/// Tests styled text with bold font, red color, and center alignment.
///
[Fact]
- public void TextStyled()
+ public async Task TextStyled()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -67,14 +67,14 @@ public void TextStyled()
Font = "bold"
});
- AssertSnapshot("text_styled", template, new ObjectValue());
+ await AssertSnapshot("text_styled", template, new ObjectValue());
}
///
/// Tests multiline text with maxLines constraint and ellipsis overflow.
///
[Fact]
- public void TextMultiline()
+ public async Task TextMultiline()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -90,14 +90,14 @@ public void TextMultiline()
Width = "280"
});
- AssertSnapshot("text_multiline", template, new ObjectValue());
+ await AssertSnapshot("text_multiline", template, new ObjectValue());
}
///
/// Tests template variable substitution in text content.
///
[Fact]
- public void TextVariables()
+ public async Task TextVariables()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -114,7 +114,7 @@ public void TextVariables()
["name"] = new StringValue("World")
};
- AssertSnapshot("text_variables", template, data);
+ await AssertSnapshot("text_variables", template, data);
}
#endregion
@@ -125,7 +125,7 @@ public void TextVariables()
/// Tests basic QR code generation.
///
[Fact]
- public void QrBasic()
+ public async Task QrBasic()
{
var template = CreateTemplate(300, 200);
template.AddElement(new QrElement
@@ -134,14 +134,14 @@ public void QrBasic()
Size = 100
});
- AssertSnapshot("qr_basic", template, new ObjectValue());
+ await AssertSnapshot("qr_basic", template, new ObjectValue());
}
///
/// Tests QR code with custom foreground and background colors.
///
[Fact]
- public void QrStyled()
+ public async Task QrStyled()
{
var template = CreateTemplate(300, 200);
template.AddElement(new QrElement
@@ -152,14 +152,14 @@ public void QrStyled()
Background = "#f0f0f0"
});
- AssertSnapshot("qr_styled", template, new ObjectValue());
+ await AssertSnapshot("qr_styled", template, new ObjectValue());
}
///
/// Tests Code128 barcode generation with visible text.
///
[Fact]
- public void BarcodeCode128()
+ public async Task BarcodeCode128()
{
var template = CreateTemplate(300, 200);
template.AddElement(new BarcodeElement
@@ -171,7 +171,7 @@ public void BarcodeCode128()
ShowText = true
});
- AssertSnapshot("barcode_code128", template, new ObjectValue());
+ await AssertSnapshot("barcode_code128", template, new ObjectValue());
}
#endregion
@@ -182,7 +182,7 @@ public void BarcodeCode128()
/// Tests image rendering with contain fit mode.
///
[Fact]
- public void ImageContain()
+ public async Task ImageContain()
{
var imageData = CreateTestImageBase64(100, 60, SKColors.Blue);
@@ -195,14 +195,14 @@ public void ImageContain()
Fit = ImageFit.Contain
});
- AssertSnapshot("image_contain", template, new ObjectValue());
+ await AssertSnapshot("image_contain", template, new ObjectValue());
}
///
/// Tests image rendering with cover fit mode.
///
[Fact]
- public void ImageCover()
+ public async Task ImageCover()
{
var imageData = CreateTestImageBase64(100, 60, SKColors.Green);
@@ -215,7 +215,7 @@ public void ImageCover()
Fit = ImageFit.Cover
});
- AssertSnapshot("image_cover", template, new ObjectValue());
+ await AssertSnapshot("image_cover", template, new ObjectValue());
}
#endregion
@@ -226,7 +226,7 @@ public void ImageCover()
/// Tests vertical flex column layout with gap.
///
[Fact]
- public void FlexColumn()
+ public async Task FlexColumn()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -240,14 +240,14 @@ public void FlexColumn()
template.AddElement(flex);
- AssertSnapshot("flex_column", template, new ObjectValue());
+ await AssertSnapshot("flex_column", template, new ObjectValue());
}
///
/// Tests horizontal flex row layout with space-between justification.
///
[Fact]
- public void FlexRow()
+ public async Task FlexRow()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -264,14 +264,14 @@ public void FlexRow()
template.AddElement(flex);
- AssertSnapshot("flex_row", template, new ObjectValue());
+ await AssertSnapshot("flex_row", template, new ObjectValue());
}
///
/// Tests two levels of nested flex containers (row inside column).
///
[Fact]
- public void FlexNested2Levels()
+ public async Task FlexNested2Levels()
{
var template = CreateTemplate(300, 200);
@@ -294,14 +294,14 @@ public void FlexNested2Levels()
template.AddElement(outerColumn);
- AssertSnapshot("flex_nested_2levels", template, new ObjectValue());
+ await AssertSnapshot("flex_nested_2levels", template, new ObjectValue());
}
///
/// Tests three levels of nested flex containers.
///
[Fact]
- public void FlexNested3Levels()
+ public async Task FlexNested3Levels()
{
var template = CreateTemplate(300, 200);
@@ -332,14 +332,14 @@ public void FlexNested3Levels()
template.AddElement(outer);
- AssertSnapshot("flex_nested_3levels", template, new ObjectValue());
+ await AssertSnapshot("flex_nested_3levels", template, new ObjectValue());
}
///
/// Tests flex grow distribution among child elements (1:2:1 ratio).
///
[Fact]
- public void FlexGrowDistribute()
+ public async Task FlexGrowDistribute()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -353,14 +353,14 @@ public void FlexGrowDistribute()
template.AddElement(flex);
- AssertSnapshot("flex_grow_distribute", template, new ObjectValue());
+ await AssertSnapshot("flex_grow_distribute", template, new ObjectValue());
}
///
/// Tests cross-axis alignment with center alignment.
///
[Fact]
- public void FlexAlignItems()
+ public async Task FlexAlignItems()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -376,14 +376,14 @@ public void FlexAlignItems()
template.AddElement(flex);
- AssertSnapshot("flex_align_items", template, new ObjectValue());
+ await AssertSnapshot("flex_align_items", template, new ObjectValue());
}
///
/// Tests space-around justification on main axis.
///
[Fact]
- public void FlexJustifyAll()
+ public async Task FlexJustifyAll()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -398,14 +398,14 @@ public void FlexJustifyAll()
template.AddElement(flex);
- AssertSnapshot("flex_justify_all", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_all", template, new ObjectValue());
}
///
/// Tests align: start with boxes of different heights aligned to the top of the container.
///
[Fact]
- public void FlexAlignStart()
+ public async Task FlexAlignStart()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -458,14 +458,14 @@ public void FlexAlignStart()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_align_start", template, new ObjectValue());
+ await AssertSnapshot("flex_align_start", template, new ObjectValue());
}
///
/// Tests align: center with boxes of different heights centered vertically in the container.
///
[Fact]
- public void FlexAlignCenter()
+ public async Task FlexAlignCenter()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -518,14 +518,14 @@ public void FlexAlignCenter()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_align_center", template, new ObjectValue());
+ await AssertSnapshot("flex_align_center", template, new ObjectValue());
}
///
/// Tests align: end with boxes of different heights aligned to the bottom of the container.
///
[Fact]
- public void FlexAlignEnd()
+ public async Task FlexAlignEnd()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -578,7 +578,7 @@ public void FlexAlignEnd()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_align_end", template, new ObjectValue());
+ await AssertSnapshot("flex_align_end", template, new ObjectValue());
}
///
@@ -586,7 +586,7 @@ public void FlexAlignEnd()
/// Child boxes have no explicit height so they stretch to the container's 140px height.
///
[Fact]
- public void FlexAlignStretch()
+ public async Task FlexAlignStretch()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -636,7 +636,7 @@ public void FlexAlignStretch()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_align_stretch", template, new ObjectValue());
+ await AssertSnapshot("flex_align_stretch", template, new ObjectValue());
}
///
@@ -644,7 +644,7 @@ public void FlexAlignStretch()
/// aligned along their text baselines.
///
[Fact]
- public void FlexAlignBaseline()
+ public async Task FlexAlignBaseline()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -694,14 +694,14 @@ public void FlexAlignBaseline()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_align_baseline", template, new ObjectValue());
+ await AssertSnapshot("flex_align_baseline", template, new ObjectValue());
}
///
/// Tests justify: start with boxes packed toward the start of the main axis.
///
[Fact]
- public void FlexJustifyStart()
+ public async Task FlexJustifyStart()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -754,14 +754,14 @@ public void FlexJustifyStart()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_justify_start", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_start", template, new ObjectValue());
}
///
/// Tests justify: center with boxes centered along the main axis.
///
[Fact]
- public void FlexJustifyCenter()
+ public async Task FlexJustifyCenter()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -814,14 +814,14 @@ public void FlexJustifyCenter()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_justify_center", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_center", template, new ObjectValue());
}
///
/// Tests justify: end with boxes packed toward the end of the main axis.
///
[Fact]
- public void FlexJustifyEnd()
+ public async Task FlexJustifyEnd()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -874,7 +874,7 @@ public void FlexJustifyEnd()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_justify_end", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_end", template, new ObjectValue());
}
///
@@ -882,7 +882,7 @@ public void FlexJustifyEnd()
/// First item at start, last item at end, remaining space distributed evenly.
///
[Fact]
- public void FlexJustifySpaceBetween()
+ public async Task FlexJustifySpaceBetween()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -934,7 +934,7 @@ public void FlexJustifySpaceBetween()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_justify_space_between", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_space_between", template, new ObjectValue());
}
///
@@ -942,7 +942,7 @@ public void FlexJustifySpaceBetween()
/// The space before the first item, between each item, and after the last item are all equal.
///
[Fact]
- public void FlexJustifySpaceEvenly()
+ public async Task FlexJustifySpaceEvenly()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -994,7 +994,7 @@ public void FlexJustifySpaceEvenly()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("flex_justify_space_evenly", template, new ObjectValue());
+ await AssertSnapshot("flex_justify_space_evenly", template, new ObjectValue());
}
///
@@ -1003,7 +1003,7 @@ public void FlexJustifySpaceEvenly()
/// space for justify-content to distribute.
///
[Fact]
- public void FlexRowJustifyWithFlexChildren()
+ public async Task FlexRowJustifyWithFlexChildren()
{
var template = CreateTemplate(400, 200);
var flex = new FlexElement
@@ -1049,7 +1049,7 @@ public void FlexRowJustifyWithFlexChildren()
flex.AddChild(child3);
template.AddElement(flex);
- AssertSnapshot("flex_row_justify_with_flex_children", template, new ObjectValue());
+ await AssertSnapshot("flex_row_justify_with_flex_children", template, new ObjectValue());
}
///
@@ -1057,7 +1057,7 @@ public void FlexRowJustifyWithFlexChildren()
/// produce negative child positions when content exceeds available space.
///
[Fact]
- public void FlexColumnAutoHeightJustifyCenter()
+ public async Task FlexColumnAutoHeightJustifyCenter()
{
var template = CreateTemplate(300, 200);
var row = new FlexElement
@@ -1087,14 +1087,14 @@ public void FlexColumnAutoHeightJustifyCenter()
row.AddChild(column);
template.AddElement(row);
- AssertSnapshot("flex_column_autoheight_justify_center", template, new ObjectValue());
+ await AssertSnapshot("flex_column_autoheight_justify_center", template, new ObjectValue());
}
///
/// Tests mixed content types (text, QR, barcode) in a single flex row.
///
[Fact]
- public void FlexMixedContent()
+ public async Task FlexMixedContent()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return;
@@ -1112,14 +1112,14 @@ public void FlexMixedContent()
template.AddElement(flex);
- AssertSnapshot("flex_mixed_content", template, new ObjectValue());
+ await AssertSnapshot("flex_mixed_content", template, new ObjectValue());
}
///
/// Tests percentage-based width distribution (30% and 70%).
///
[Fact]
- public void FlexPercentWidths()
+ public async Task FlexPercentWidths()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -1132,14 +1132,14 @@ public void FlexPercentWidths()
template.AddElement(flex);
- AssertSnapshot("flex_percent_widths", template, new ObjectValue());
+ await AssertSnapshot("flex_percent_widths", template, new ObjectValue());
}
///
/// Tests combination of padding and gap with multiple children.
///
[Fact]
- public void FlexPaddingGapCombo()
+ public async Task FlexPaddingGapCombo()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -1156,7 +1156,7 @@ public void FlexPaddingGapCombo()
template.AddElement(flex);
- AssertSnapshot("flex_padding_gap_combo", template, new ObjectValue());
+ await AssertSnapshot("flex_padding_gap_combo", template, new ObjectValue());
}
#endregion
@@ -1167,7 +1167,7 @@ public void FlexPaddingGapCombo()
/// Tests flex container with a solid red background color.
///
[Fact]
- public void FlexWithBackground()
+ public async Task FlexWithBackground()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -1181,14 +1181,14 @@ public void FlexWithBackground()
template.AddElement(flex);
- AssertSnapshot("flex_with_background", template, new ObjectValue());
+ await AssertSnapshot("flex_with_background", template, new ObjectValue());
}
///
/// Tests text element with a solid blue background color.
///
[Fact]
- public void TextWithBackground()
+ public async Task TextWithBackground()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -1201,7 +1201,7 @@ public void TextWithBackground()
Background = "#0000ff"
});
- AssertSnapshot("text_with_background", template, new ObjectValue());
+ await AssertSnapshot("text_with_background", template, new ObjectValue());
}
///
@@ -1209,7 +1209,7 @@ public void TextWithBackground()
/// Outer container has gray background, inner has light blue, and text has yellow.
///
[Fact]
- public void NestedBackgrounds()
+ public async Task NestedBackgrounds()
{
var template = CreateTemplate(300, 200);
@@ -1242,14 +1242,14 @@ public void NestedBackgrounds()
outer.AddChild(inner);
template.AddElement(outer);
- AssertSnapshot("nested_backgrounds", template, new ObjectValue());
+ await AssertSnapshot("nested_backgrounds", template, new ObjectValue());
}
///
/// Tests flex container with padding around child text element.
///
[Fact]
- public void FlexWithPadding()
+ public async Task FlexWithPadding()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -1263,14 +1263,14 @@ public void FlexWithPadding()
template.AddElement(flex);
- AssertSnapshot("flex_with_padding", template, new ObjectValue());
+ await AssertSnapshot("flex_with_padding", template, new ObjectValue());
}
///
/// Tests two flex containers with margin creating spacing between them.
///
[Fact]
- public void FlexWithMargin()
+ public async Task FlexWithMargin()
{
var template = CreateTemplate(300, 200);
@@ -1303,7 +1303,7 @@ public void FlexWithMargin()
container.AddChild(second);
template.AddElement(container);
- AssertSnapshot("flex_with_margin", template, new ObjectValue());
+ await AssertSnapshot("flex_with_margin", template, new ObjectValue());
}
///
@@ -1311,7 +1311,7 @@ public void FlexWithMargin()
/// that padding creates space inside the background area.
///
[Fact]
- public void BackgroundWithPadding()
+ public async Task BackgroundWithPadding()
{
var template = CreateTemplate(300, 200);
var flex = new FlexElement
@@ -1326,7 +1326,7 @@ public void BackgroundWithPadding()
template.AddElement(flex);
- AssertSnapshot("background_with_padding", template, new ObjectValue());
+ await AssertSnapshot("background_with_padding", template, new ObjectValue());
}
///
@@ -1334,7 +1334,7 @@ public void BackgroundWithPadding()
/// margin creates space outside the background area.
///
[Fact]
- public void ElementWithMarginAndBackground()
+ public async Task ElementWithMarginAndBackground()
{
var template = CreateTemplate(300, 200);
@@ -1360,7 +1360,7 @@ public void ElementWithMarginAndBackground()
container.AddChild(child);
template.AddElement(container);
- AssertSnapshot("element_with_margin_and_background", template, new ObjectValue());
+ await AssertSnapshot("element_with_margin_and_background", template, new ObjectValue());
}
#endregion
@@ -1372,7 +1372,7 @@ public void ElementWithMarginAndBackground()
/// Blue box appears on the right, red in the middle, green on the left.
///
[Fact]
- public void RtlRowLayout()
+ public async Task RtlRowLayout()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -1411,7 +1411,7 @@ public void RtlRowLayout()
flex.AddChild(box3);
template.AddElement(flex);
- AssertSnapshot("rtl_row_layout", template, new ObjectValue());
+ await AssertSnapshot("rtl_row_layout", template, new ObjectValue());
}
///
@@ -1419,7 +1419,7 @@ public void RtlRowLayout()
/// "First" appears on the right, "Second" in the middle, "Third" on the left.
///
[Fact]
- public void RtlRowWithText()
+ public async Task RtlRowWithText()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -1438,7 +1438,7 @@ public void RtlRowWithText()
template.AddElement(flex);
- AssertSnapshot("rtl_row_with_text", template, new ObjectValue());
+ await AssertSnapshot("rtl_row_with_text", template, new ObjectValue());
}
///
@@ -1446,7 +1446,7 @@ public void RtlRowWithText()
/// The text "Start Aligned" should appear right-aligned within the 300px canvas.
///
[Fact]
- public void RtlTextAlignStart()
+ public async Task RtlTextAlignStart()
{
if (!OperatingSystem.IsMacOS()) return;
@@ -1462,7 +1462,7 @@ public void RtlTextAlignStart()
Width = "300"
});
- AssertSnapshot("rtl_text_align_start", template, new ObjectValue());
+ await AssertSnapshot("rtl_text_align_start", template, new ObjectValue());
}
#endregion
diff --git a/tests/FlexRender.Tests/Snapshots/WbReceiptSnapshotTests.cs b/tests/FlexRender.Tests/Snapshots/WbReceiptSnapshotTests.cs
index d955a83..a69fb0c 100644
--- a/tests/FlexRender.Tests/Snapshots/WbReceiptSnapshotTests.cs
+++ b/tests/FlexRender.Tests/Snapshots/WbReceiptSnapshotTests.cs
@@ -34,7 +34,7 @@ public sealed class WbReceiptSnapshotTests : SnapshotTestBase
/// that the output matches the golden snapshot image.
///
[Fact]
- public void WbReceipt_RendersCorrectly()
+ public async Task WbReceipt_RendersCorrectly()
{
var repoRoot = FindRepositoryRoot();
var yamlPath = Path.Combine(repoRoot, "examples", "private", "wb-receipt.yaml");
@@ -56,7 +56,7 @@ public void WbReceipt_RendersCorrectly()
try
{
Directory.SetCurrentDirectory(Path.Combine(repoRoot, "examples"));
- AssertSnapshot("wb_receipt", template, data, colorThreshold: 10);
+ await AssertSnapshot("wb_receipt", template, data, colorThreshold: 10);
}
finally
{
diff --git a/tests/FlexRender.Tests/Table/TableExpansionTests.cs b/tests/FlexRender.Tests/Table/TableExpansionTests.cs
index 2ed174a..980aa4a 100644
--- a/tests/FlexRender.Tests/Table/TableExpansionTests.cs
+++ b/tests/FlexRender.Tests/Table/TableExpansionTests.cs
@@ -41,7 +41,7 @@ private static TableElement CreateDynamicTable(
// === Dynamic table expansion ===
[Fact]
- public void Expand_DynamicTable_CreatesFlexContainerWithRows()
+ public async Task Expand_DynamicTable_CreatesFlexContainerWithRows()
{
// Arrange
var columns = new List
@@ -62,7 +62,7 @@ public void Expand_DynamicTable_CreatesFlexContainerWithRows()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: should produce a column FlexElement wrapping rows
Assert.Single(result.Elements);
@@ -73,7 +73,7 @@ public void Expand_DynamicTable_CreatesFlexContainerWithRows()
}
[Fact]
- public void Expand_DynamicTableWithHeaders_CreatesHeaderRowAndDataRows()
+ public async Task Expand_DynamicTableWithHeaders_CreatesHeaderRowAndDataRows()
{
// Arrange
var columns = new List
@@ -93,7 +93,7 @@ public void Expand_DynamicTableWithHeaders_CreatesHeaderRowAndDataRows()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: should have header row + data rows
Assert.Single(result.Elements);
@@ -115,7 +115,7 @@ public void Expand_DynamicTableWithHeaders_CreatesHeaderRowAndDataRows()
}
[Fact]
- public void Expand_DynamicTable_DataRowsHaveCorrectContent()
+ public async Task Expand_DynamicTable_DataRowsHaveCorrectContent()
{
// Arrange
var columns = new List
@@ -135,7 +135,7 @@ public void Expand_DynamicTable_DataRowsHaveCorrectContent()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: the data row should contain TextElements with substituted values
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -149,7 +149,7 @@ public void Expand_DynamicTable_DataRowsHaveCorrectContent()
}
[Fact]
- public void Expand_DynamicTableWithFormat_UsesFormatString()
+ public async Task Expand_DynamicTableWithFormat_UsesFormatString()
{
// Arrange
var columns = new List
@@ -169,7 +169,7 @@ public void Expand_DynamicTableWithFormat_UsesFormatString()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: price column should use format string
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -179,7 +179,7 @@ public void Expand_DynamicTableWithFormat_UsesFormatString()
}
[Fact]
- public void Expand_DynamicTableWithAsVariable_SetsItemVariable()
+ public async Task Expand_DynamicTableWithAsVariable_SetsItemVariable()
{
// Arrange
var columns = new List
@@ -198,7 +198,7 @@ public void Expand_DynamicTableWithAsVariable_SetsItemVariable()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: should expand correctly with 'as' variable
Assert.Single(result.Elements);
@@ -209,7 +209,7 @@ public void Expand_DynamicTableWithAsVariable_SetsItemVariable()
// === Static table expansion ===
[Fact]
- public void Expand_StaticTableWithRows_CreatesFlexContainerWithRows()
+ public async Task Expand_StaticTableWithRows_CreatesFlexContainerWithRows()
{
// Arrange
var columns = new List
@@ -228,7 +228,7 @@ public void Expand_StaticTableWithRows_CreatesFlexContainerWithRows()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert
Assert.Single(result.Elements);
@@ -248,7 +248,7 @@ public void Expand_StaticTableWithRows_CreatesFlexContainerWithRows()
// === Column property propagation ===
[Fact]
- public void Expand_ColumnWidth_AppliedToEachCell()
+ public async Task Expand_ColumnWidth_AppliedToEachCell()
{
// Arrange
var columns = new List
@@ -266,7 +266,7 @@ public void Expand_ColumnWidth_AppliedToEachCell()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: the second column cell should have width "60"
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -276,7 +276,7 @@ public void Expand_ColumnWidth_AppliedToEachCell()
}
[Fact]
- public void Expand_ColumnGrow_AppliedToCell()
+ public async Task Expand_ColumnGrow_AppliedToCell()
{
// Arrange
var columns = new List
@@ -293,7 +293,7 @@ public void Expand_ColumnGrow_AppliedToCell()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: the cell should have grow=1
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -303,7 +303,7 @@ public void Expand_ColumnGrow_AppliedToCell()
}
[Fact]
- public void Expand_ColumnAlign_AppliedToTextElement()
+ public async Task Expand_ColumnAlign_AppliedToTextElement()
{
// Arrange
var columns = new List
@@ -320,7 +320,7 @@ public void Expand_ColumnAlign_AppliedToTextElement()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -330,7 +330,7 @@ public void Expand_ColumnAlign_AppliedToTextElement()
}
[Fact]
- public void Expand_ColumnFontAndColor_AppliedToCell()
+ public async Task Expand_ColumnFontAndColor_AppliedToCell()
{
// Arrange
var columns = new List
@@ -347,7 +347,7 @@ public void Expand_ColumnFontAndColor_AppliedToCell()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -361,7 +361,7 @@ public void Expand_ColumnFontAndColor_AppliedToCell()
// === Edge cases ===
[Fact]
- public void Expand_EmptyArray_HeaderRendersNoDataRows()
+ public async Task Expand_EmptyArray_HeaderRendersNoDataRows()
{
// Arrange
var columns = new List
@@ -377,7 +377,7 @@ public void Expand_EmptyArray_HeaderRendersNoDataRows()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: header should still render, but no data rows
Assert.Single(result.Elements);
@@ -394,7 +394,7 @@ public void Expand_EmptyArray_HeaderRendersNoDataRows()
}
[Fact]
- public void Expand_MissingKeyInData_ResolvesToEmptyString()
+ public async Task Expand_MissingKeyInData_ResolvesToEmptyString()
{
// Arrange
var columns = new List
@@ -414,7 +414,7 @@ public void Expand_MissingKeyInData_ResolvesToEmptyString()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: missing key should resolve to empty string
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -426,7 +426,7 @@ public void Expand_MissingKeyInData_ResolvesToEmptyString()
// === Spacing ===
[Fact]
- public void Expand_TableWithGap_SetsGapOnFlexContainers()
+ public async Task Expand_TableWithGap_SetsGapOnFlexContainers()
{
// Arrange
var columns = new List
@@ -448,7 +448,7 @@ public void Expand_TableWithGap_SetsGapOnFlexContainers()
var data = new ObjectValue();
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: the outer flex should have rowGap, inner row flex should have columnGap
var outerFlex = Assert.IsType(result.Elements[0]);
@@ -461,7 +461,7 @@ public void Expand_TableWithGap_SetsGapOnFlexContainers()
// === Header with border bottom (separator) ===
[Fact]
- public void Expand_HeaderBorderBottom_CreatesSeparatorAfterHeader()
+ public async Task Expand_HeaderBorderBottom_CreatesSeparatorAfterHeader()
{
// Arrange
var columns = new List
@@ -484,7 +484,7 @@ public void Expand_HeaderBorderBottom_CreatesSeparatorAfterHeader()
};
// Act
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
// Assert: should have header, separator, data row
var outerFlex = Assert.IsType(result.Elements[0]);
diff --git a/tests/FlexRender.Tests/TemplateEngine/ConditionOperatorTests.cs b/tests/FlexRender.Tests/TemplateEngine/ConditionOperatorTests.cs
index fc1a7b0..36fd4bc 100644
--- a/tests/FlexRender.Tests/TemplateEngine/ConditionOperatorTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/ConditionOperatorTests.cs
@@ -46,37 +46,37 @@ private static string GetResultContent(Template result)
// === In Operator Tests ===
[Fact]
- public void In_ValueInList_ReturnsTrue()
+ public async Task In_ValueInList_ReturnsTrue()
{
var ifElem = CreateIfElement("status", ConditionOperator.In, new List { "paid", "completed", "shipped" });
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("paid") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void In_ValueNotInList_ReturnsFalse()
+ public async Task In_ValueNotInList_ReturnsFalse()
{
var ifElem = CreateIfElement("status", ConditionOperator.In, new List { "paid", "completed", "shipped" });
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("pending") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void In_NumberValueInList_ReturnsTrue()
+ public async Task In_NumberValueInList_ReturnsTrue()
{
var ifElem = CreateIfElement("code", ConditionOperator.In, new List { "100", "200", "300" });
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["code"] = new NumberValue(200) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
@@ -84,25 +84,25 @@ public void In_NumberValueInList_ReturnsTrue()
// === NotIn Operator Tests ===
[Fact]
- public void NotIn_ValueNotInList_ReturnsTrue()
+ public async Task NotIn_ValueNotInList_ReturnsTrue()
{
var ifElem = CreateIfElement("status", ConditionOperator.NotIn, new List { "cancelled", "refunded" });
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("active") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void NotIn_ValueInList_ReturnsFalse()
+ public async Task NotIn_ValueInList_ReturnsFalse()
{
var ifElem = CreateIfElement("status", ConditionOperator.NotIn, new List { "cancelled", "refunded" });
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("cancelled") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -110,7 +110,7 @@ public void NotIn_ValueInList_ReturnsFalse()
// === Contains Operator Tests ===
[Fact]
- public void Contains_ArrayContainsValue_ReturnsTrue()
+ public async Task Contains_ArrayContainsValue_ReturnsTrue()
{
var ifElem = CreateIfElement("roles", ConditionOperator.Contains, "admin");
var template = CreateTemplate(ifElem);
@@ -124,13 +124,13 @@ public void Contains_ArrayContainsValue_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Contains_ArrayDoesNotContain_ReturnsFalse()
+ public async Task Contains_ArrayDoesNotContain_ReturnsFalse()
{
var ifElem = CreateIfElement("roles", ConditionOperator.Contains, "superadmin");
var template = CreateTemplate(ifElem);
@@ -143,13 +143,13 @@ public void Contains_ArrayDoesNotContain_ReturnsFalse()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void Contains_ArrayWithNumbers_ReturnsTrue()
+ public async Task Contains_ArrayWithNumbers_ReturnsTrue()
{
var ifElem = CreateIfElement("ids", ConditionOperator.Contains, "42");
var template = CreateTemplate(ifElem);
@@ -163,19 +163,19 @@ public void Contains_ArrayWithNumbers_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Contains_NonArrayValue_ReturnsFalse()
+ public async Task Contains_NonArrayValue_ReturnsFalse()
{
var ifElem = CreateIfElement("name", ConditionOperator.Contains, "test");
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["name"] = new StringValue("test string") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -183,37 +183,37 @@ public void Contains_NonArrayValue_ReturnsFalse()
// === GreaterThan Operator Tests ===
[Fact]
- public void GreaterThan_ValueGreater_ReturnsTrue()
+ public async Task GreaterThan_ValueGreater_ReturnsTrue()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThan, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(1500) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void GreaterThan_ValueEqual_ReturnsFalse()
+ public async Task GreaterThan_ValueEqual_ReturnsFalse()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThan, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(1000) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void GreaterThan_ValueLess_ReturnsFalse()
+ public async Task GreaterThan_ValueLess_ReturnsFalse()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThan, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(500) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -221,37 +221,37 @@ public void GreaterThan_ValueLess_ReturnsFalse()
// === GreaterThanOrEqual Operator Tests ===
[Fact]
- public void GreaterThanOrEqual_ValueGreater_ReturnsTrue()
+ public async Task GreaterThanOrEqual_ValueGreater_ReturnsTrue()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThanOrEqual, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(1500) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void GreaterThanOrEqual_ValueEqual_ReturnsTrue()
+ public async Task GreaterThanOrEqual_ValueEqual_ReturnsTrue()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThanOrEqual, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(1000) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void GreaterThanOrEqual_ValueLess_ReturnsFalse()
+ public async Task GreaterThanOrEqual_ValueLess_ReturnsFalse()
{
var ifElem = CreateIfElement("amount", ConditionOperator.GreaterThanOrEqual, 1000.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["amount"] = new NumberValue(999) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -259,37 +259,37 @@ public void GreaterThanOrEqual_ValueLess_ReturnsFalse()
// === LessThan Operator Tests ===
[Fact]
- public void LessThan_ValueLess_ReturnsTrue()
+ public async Task LessThan_ValueLess_ReturnsTrue()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThan, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(25) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void LessThan_ValueEqual_ReturnsFalse()
+ public async Task LessThan_ValueEqual_ReturnsFalse()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThan, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(50) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void LessThan_ValueGreater_ReturnsFalse()
+ public async Task LessThan_ValueGreater_ReturnsFalse()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThan, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(75) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -297,37 +297,37 @@ public void LessThan_ValueGreater_ReturnsFalse()
// === LessThanOrEqual Operator Tests ===
[Fact]
- public void LessThanOrEqual_ValueLess_ReturnsTrue()
+ public async Task LessThanOrEqual_ValueLess_ReturnsTrue()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThanOrEqual, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(25) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void LessThanOrEqual_ValueEqual_ReturnsTrue()
+ public async Task LessThanOrEqual_ValueEqual_ReturnsTrue()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThanOrEqual, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(50) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void LessThanOrEqual_ValueGreater_ReturnsFalse()
+ public async Task LessThanOrEqual_ValueGreater_ReturnsFalse()
{
var ifElem = CreateIfElement("discount", ConditionOperator.LessThanOrEqual, 50.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["discount"] = new NumberValue(51) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -335,7 +335,7 @@ public void LessThanOrEqual_ValueGreater_ReturnsFalse()
// === HasItems Operator Tests ===
[Fact]
- public void HasItems_NonEmptyArray_ReturnsTrue()
+ public async Task HasItems_NonEmptyArray_ReturnsTrue()
{
var ifElem = CreateIfElement("items", ConditionOperator.HasItems, true);
var template = CreateTemplate(ifElem);
@@ -348,13 +348,13 @@ public void HasItems_NonEmptyArray_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void HasItems_EmptyArray_ReturnsFalse()
+ public async Task HasItems_EmptyArray_ReturnsFalse()
{
var ifElem = CreateIfElement("items", ConditionOperator.HasItems, true);
var template = CreateTemplate(ifElem);
@@ -363,13 +363,13 @@ public void HasItems_EmptyArray_ReturnsFalse()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void HasItems_EmptyArrayExpectedFalse_ReturnsTrue()
+ public async Task HasItems_EmptyArrayExpectedFalse_ReturnsTrue()
{
var ifElem = CreateIfElement("items", ConditionOperator.HasItems, false);
var template = CreateTemplate(ifElem);
@@ -378,19 +378,19 @@ public void HasItems_EmptyArrayExpectedFalse_ReturnsTrue()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void HasItems_NonArrayValue_ReturnsFalse()
+ public async Task HasItems_NonArrayValue_ReturnsFalse()
{
var ifElem = CreateIfElement("name", ConditionOperator.HasItems, true);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["name"] = new StringValue("test") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -398,7 +398,7 @@ public void HasItems_NonArrayValue_ReturnsFalse()
// === CountEquals Operator Tests ===
[Fact]
- public void CountEquals_CorrectCount_ReturnsTrue()
+ public async Task CountEquals_CorrectCount_ReturnsTrue()
{
var ifElem = CreateIfElement("items", ConditionOperator.CountEquals, 3);
var template = CreateTemplate(ifElem);
@@ -412,13 +412,13 @@ public void CountEquals_CorrectCount_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void CountEquals_WrongCount_ReturnsFalse()
+ public async Task CountEquals_WrongCount_ReturnsFalse()
{
var ifElem = CreateIfElement("items", ConditionOperator.CountEquals, 5);
var template = CreateTemplate(ifElem);
@@ -431,13 +431,13 @@ public void CountEquals_WrongCount_ReturnsFalse()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void CountEquals_EmptyArrayZero_ReturnsTrue()
+ public async Task CountEquals_EmptyArrayZero_ReturnsTrue()
{
var ifElem = CreateIfElement("items", ConditionOperator.CountEquals, 0);
var template = CreateTemplate(ifElem);
@@ -446,7 +446,7 @@ public void CountEquals_EmptyArrayZero_ReturnsTrue()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
@@ -454,7 +454,7 @@ public void CountEquals_EmptyArrayZero_ReturnsTrue()
// === CountGreaterThan Operator Tests ===
[Fact]
- public void CountGreaterThan_CountGreater_ReturnsTrue()
+ public async Task CountGreaterThan_CountGreater_ReturnsTrue()
{
var ifElem = CreateIfElement("attachments", ConditionOperator.CountGreaterThan, 0);
var template = CreateTemplate(ifElem);
@@ -467,13 +467,13 @@ public void CountGreaterThan_CountGreater_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void CountGreaterThan_CountEqual_ReturnsFalse()
+ public async Task CountGreaterThan_CountEqual_ReturnsFalse()
{
var ifElem = CreateIfElement("attachments", ConditionOperator.CountGreaterThan, 2);
var template = CreateTemplate(ifElem);
@@ -486,13 +486,13 @@ public void CountGreaterThan_CountEqual_ReturnsFalse()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void CountGreaterThan_CountLess_ReturnsFalse()
+ public async Task CountGreaterThan_CountLess_ReturnsFalse()
{
var ifElem = CreateIfElement("attachments", ConditionOperator.CountGreaterThan, 5);
var template = CreateTemplate(ifElem);
@@ -504,7 +504,7 @@ public void CountGreaterThan_CountLess_ReturnsFalse()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -512,79 +512,79 @@ public void CountGreaterThan_CountLess_ReturnsFalse()
// === Equals Operator Tests (Extended) ===
[Fact]
- public void Equals_Numbers_ReturnsTrue()
+ public async Task Equals_Numbers_ReturnsTrue()
{
var ifElem = CreateIfElement("quantity", ConditionOperator.Equals, 42.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["quantity"] = new NumberValue(42) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Equals_NumbersNotEqual_ReturnsFalse()
+ public async Task Equals_NumbersNotEqual_ReturnsFalse()
{
var ifElem = CreateIfElement("quantity", ConditionOperator.Equals, 42.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["quantity"] = new NumberValue(43) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void Equals_Booleans_ReturnsTrue()
+ public async Task Equals_Booleans_ReturnsTrue()
{
var ifElem = CreateIfElement("active", ConditionOperator.Equals, true);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["active"] = new BoolValue(true) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Equals_Null_ReturnsTrue()
+ public async Task Equals_Null_ReturnsTrue()
{
var ifElem = CreateIfElement("missing", ConditionOperator.Equals, null);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["missing"] = NullValue.Instance };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Equals_NonNullWithNull_ReturnsFalse()
+ public async Task Equals_NonNullWithNull_ReturnsFalse()
{
var ifElem = CreateIfElement("value", ConditionOperator.Equals, null);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["value"] = new StringValue("something") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void Equals_MissingPathEqualsNull_ReturnsTrue()
+ public async Task Equals_MissingPathEqualsNull_ReturnsTrue()
{
var ifElem = CreateIfElement("nonexistent", ConditionOperator.Equals, null);
var template = CreateTemplate(ifElem);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void Equals_Arrays_ReturnsTrue()
+ public async Task Equals_Arrays_ReturnsTrue()
{
var compareArray = new ArrayValue(new List
{
@@ -602,7 +602,7 @@ public void Equals_Arrays_ReturnsTrue()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
@@ -610,25 +610,25 @@ public void Equals_Arrays_ReturnsTrue()
// === NotEquals Operator Tests (Extended) ===
[Fact]
- public void NotEquals_DifferentValues_ReturnsTrue()
+ public async Task NotEquals_DifferentValues_ReturnsTrue()
{
var ifElem = CreateIfElement("status", ConditionOperator.NotEquals, "cancelled");
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("active") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void NotEquals_SameValues_ReturnsFalse()
+ public async Task NotEquals_SameValues_ReturnsFalse()
{
var ifElem = CreateIfElement("status", ConditionOperator.NotEquals, "cancelled");
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("cancelled") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
@@ -636,31 +636,31 @@ public void NotEquals_SameValues_ReturnsFalse()
// === Edge Cases ===
[Fact]
- public void NumericComparison_NonNumericValue_ReturnsFalse()
+ public async Task NumericComparison_NonNumericValue_ReturnsFalse()
{
var ifElem = CreateIfElement("name", ConditionOperator.GreaterThan, 100.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["name"] = new StringValue("test") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void In_NullList_ReturnsFalse()
+ public async Task In_NullList_ReturnsFalse()
{
var ifElem = CreateIfElement("status", ConditionOperator.In, null);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("active") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void Contains_NullElement_ReturnsFalse()
+ public async Task Contains_NullElement_ReturnsFalse()
{
var ifElem = CreateIfElement("items", ConditionOperator.Contains, null);
var template = CreateTemplate(ifElem);
@@ -669,43 +669,43 @@ public void Contains_NullElement_ReturnsFalse()
["items"] = new ArrayValue(new List { new StringValue("test") })
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void CountEquals_NonArrayValue_ReturnsFalse()
+ public async Task CountEquals_NonArrayValue_ReturnsFalse()
{
var ifElem = CreateIfElement("name", ConditionOperator.CountEquals, 5);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["name"] = new StringValue("test") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("false", GetResultContent(result));
}
[Fact]
- public void DecimalPrecision_GreaterThan_WorksCorrectly()
+ public async Task DecimalPrecision_GreaterThan_WorksCorrectly()
{
var ifElem = CreateIfElement("price", ConditionOperator.GreaterThan, 99.99);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["price"] = new NumberValue(100.00m) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
[Fact]
- public void NegativeNumbers_LessThan_WorksCorrectly()
+ public async Task NegativeNumbers_LessThan_WorksCorrectly()
{
var ifElem = CreateIfElement("temperature", ConditionOperator.LessThan, 0.0);
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["temperature"] = new NumberValue(-10) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("true", GetResultContent(result));
}
diff --git a/tests/FlexRender.Tests/TemplateEngine/ExprValueIntegrationTests.cs b/tests/FlexRender.Tests/TemplateEngine/ExprValueIntegrationTests.cs
index b6467b7..5662579 100644
--- a/tests/FlexRender.Tests/TemplateEngine/ExprValueIntegrationTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/ExprValueIntegrationTests.cs
@@ -32,11 +32,11 @@ private static TemplatePipeline CreatePipeline()
/// The YAML template string.
/// The data context for expression evaluation.
/// The processed template with all expressions resolved.
- private Template ParseAndProcess(string yaml, ObjectValue data)
+ private async Task ParseAndProcess(string yaml, ObjectValue data)
{
var template = _parser.Parse(yaml);
var pipeline = CreatePipeline();
- return pipeline.Process(template, data);
+ return await pipeline.ProcessAsync(template, data);
}
// === 1. Expression in float property (Opacity) ===
@@ -46,7 +46,7 @@ private Template ParseAndProcess(string yaml, ObjectValue data)
/// is resolved correctly through the full pipeline.
///
[Fact]
- public void Pipeline_ExpressionInOpacity_ResolvesToFloat()
+ public async Task Pipeline_ExpressionInOpacity_ResolvesToFloat()
{
const string yaml = """
canvas:
@@ -65,7 +65,7 @@ public void Pipeline_ExpressionInOpacity_ResolvesToFloat()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(0.5f, text.Opacity.Value);
@@ -79,7 +79,7 @@ public void Pipeline_ExpressionInOpacity_ResolvesToFloat()
/// is resolved correctly through the full pipeline.
///
[Fact]
- public void Pipeline_ExpressionInMaxLines_ResolvesToInt()
+ public async Task Pipeline_ExpressionInMaxLines_ResolvesToInt()
{
const string yaml = """
canvas:
@@ -98,7 +98,7 @@ public void Pipeline_ExpressionInMaxLines_ResolvesToInt()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(3, text.MaxLines.Value);
@@ -112,7 +112,7 @@ public void Pipeline_ExpressionInMaxLines_ResolvesToInt()
/// is resolved correctly through the full pipeline.
///
[Fact]
- public void Pipeline_ExpressionInShowText_ResolvesToBool()
+ public async Task Pipeline_ExpressionInShowText_ResolvesToBool()
{
const string yaml = """
canvas:
@@ -131,7 +131,7 @@ public void Pipeline_ExpressionInShowText_ResolvesToBool()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var barcode = Assert.IsType(result.Elements[0]);
Assert.True(barcode.ShowText.Value);
@@ -142,7 +142,7 @@ public void Pipeline_ExpressionInShowText_ResolvesToBool()
/// Verifies that a false boolean expression is resolved correctly.
///
[Fact]
- public void Pipeline_ExpressionInShowText_FalseValue_ResolvesToFalse()
+ public async Task Pipeline_ExpressionInShowText_FalseValue_ResolvesToFalse()
{
const string yaml = """
canvas:
@@ -161,7 +161,7 @@ public void Pipeline_ExpressionInShowText_FalseValue_ResolvesToFalse()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var barcode = Assert.IsType(result.Elements[0]);
Assert.False(barcode.ShowText.Value);
@@ -175,7 +175,7 @@ public void Pipeline_ExpressionInShowText_FalseValue_ResolvesToFalse()
/// is resolved correctly through the full pipeline.
///
[Fact]
- public void Pipeline_ExpressionInAlign_ResolvesToEnum()
+ public async Task Pipeline_ExpressionInAlign_ResolvesToEnum()
{
const string yaml = """
canvas:
@@ -194,7 +194,7 @@ public void Pipeline_ExpressionInAlign_ResolvesToEnum()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(TextAlign.Center, text.Align.Value);
@@ -205,7 +205,7 @@ public void Pipeline_ExpressionInAlign_ResolvesToEnum()
/// Verifies that a "right" alignment expression resolves correctly.
///
[Fact]
- public void Pipeline_ExpressionInAlign_RightValue_ResolvesToRight()
+ public async Task Pipeline_ExpressionInAlign_RightValue_ResolvesToRight()
{
const string yaml = """
canvas:
@@ -224,7 +224,7 @@ public void Pipeline_ExpressionInAlign_RightValue_ResolvesToRight()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(TextAlign.Right, text.Align.Value);
@@ -239,7 +239,7 @@ public void Pipeline_ExpressionInAlign_RightValue_ResolvesToRight()
/// test confirming string expression substitution still works.
///
[Fact]
- public void Pipeline_ExpressionInContent_ResolvesToString()
+ public async Task Pipeline_ExpressionInContent_ResolvesToString()
{
const string yaml = """
canvas:
@@ -254,7 +254,7 @@ public void Pipeline_ExpressionInContent_ResolvesToString()
["name"] = new StringValue("World")
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("Hello World", text.Content.Value);
@@ -268,7 +268,7 @@ public void Pipeline_ExpressionInContent_ResolvesToString()
/// is resolved correctly through the full pipeline.
///
[Fact]
- public void Pipeline_ExpressionInColor_ResolvesToString()
+ public async Task Pipeline_ExpressionInColor_ResolvesToString()
{
const string yaml = """
canvas:
@@ -287,7 +287,7 @@ public void Pipeline_ExpressionInColor_ResolvesToString()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("#FF0000", text.Color.Value);
@@ -301,7 +301,7 @@ public void Pipeline_ExpressionInColor_ResolvesToString()
/// the materialized value is null.
///
[Fact]
- public void Pipeline_ExpressionInMaxLines_EmptyValue_ResolvesToNull()
+ public async Task Pipeline_ExpressionInMaxLines_EmptyValue_ResolvesToNull()
{
const string yaml = """
canvas:
@@ -320,7 +320,7 @@ public void Pipeline_ExpressionInMaxLines_EmptyValue_ResolvesToNull()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Null(text.MaxLines.Value);
@@ -332,7 +332,7 @@ public void Pipeline_ExpressionInMaxLines_EmptyValue_ResolvesToNull()
/// the nullable int? property resolves to null.
///
[Fact]
- public void Pipeline_ExpressionInMaxLines_MissingVariable_ResolvesToNull()
+ public async Task Pipeline_ExpressionInMaxLines_MissingVariable_ResolvesToNull()
{
const string yaml = """
canvas:
@@ -346,7 +346,7 @@ public void Pipeline_ExpressionInMaxLines_MissingVariable_ResolvesToNull()
// Data does not contain config.lines
var data = new ObjectValue();
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Null(text.MaxLines.Value);
@@ -360,7 +360,7 @@ public void Pipeline_ExpressionInMaxLines_MissingVariable_ResolvesToNull()
/// in a single pipeline pass.
///
[Fact]
- public void Pipeline_MultipleExpressions_AllResolvedCorrectly()
+ public async Task Pipeline_MultipleExpressions_AllResolvedCorrectly()
{
const string yaml = """
canvas:
@@ -391,7 +391,7 @@ public void Pipeline_MultipleExpressions_AllResolvedCorrectly()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("Hello World", text.Content.Value);
@@ -416,7 +416,7 @@ public void Pipeline_MultipleExpressions_AllResolvedCorrectly()
/// the pipeline unchanged and are correctly resolved.
///
[Fact]
- public void Pipeline_LiteralValues_PreservedUnchanged()
+ public async Task Pipeline_LiteralValues_PreservedUnchanged()
{
const string yaml = """
canvas:
@@ -433,7 +433,7 @@ public void Pipeline_LiteralValues_PreservedUnchanged()
var data = new ObjectValue();
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("Static text", text.Content.Value);
@@ -458,7 +458,7 @@ public void Pipeline_LiteralValues_PreservedUnchanged()
/// is resolved correctly.
///
[Fact]
- public void Pipeline_ExpressionInWrap_ResolvesToBool()
+ public async Task Pipeline_ExpressionInWrap_ResolvesToBool()
{
const string yaml = """
canvas:
@@ -477,7 +477,7 @@ public void Pipeline_ExpressionInWrap_ResolvesToBool()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.False(text.Wrap.Value);
@@ -489,7 +489,7 @@ public void Pipeline_ExpressionInWrap_ResolvesToBool()
/// is resolved correctly.
///
[Fact]
- public void Pipeline_ExpressionInGrow_ResolvesToFloat()
+ public async Task Pipeline_ExpressionInGrow_ResolvesToFloat()
{
const string yaml = """
canvas:
@@ -508,7 +508,7 @@ public void Pipeline_ExpressionInGrow_ResolvesToFloat()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(2.0f, text.Grow.Value);
@@ -520,7 +520,7 @@ public void Pipeline_ExpressionInGrow_ResolvesToFloat()
/// is resolved correctly.
///
[Fact]
- public void Pipeline_ExpressionInOrder_ResolvesToInt()
+ public async Task Pipeline_ExpressionInOrder_ResolvesToInt()
{
const string yaml = """
canvas:
@@ -539,7 +539,7 @@ public void Pipeline_ExpressionInOrder_ResolvesToInt()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(3, text.Order.Value);
@@ -551,7 +551,7 @@ public void Pipeline_ExpressionInOrder_ResolvesToInt()
/// Some properties are literal, others are expressions, and all are resolved.
///
[Fact]
- public void Pipeline_MixedLiteralAndExpression_AllResolvedCorrectly()
+ public async Task Pipeline_MixedLiteralAndExpression_AllResolvedCorrectly()
{
const string yaml = """
canvas:
@@ -578,7 +578,7 @@ public void Pipeline_MixedLiteralAndExpression_AllResolvedCorrectly()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
@@ -604,7 +604,7 @@ public void Pipeline_MixedLiteralAndExpression_AllResolvedCorrectly()
/// is correctly parsed as a float.
///
[Fact]
- public void Pipeline_ExpressionInOpacity_IntegerValue_ResolvesToFloat()
+ public async Task Pipeline_ExpressionInOpacity_IntegerValue_ResolvesToFloat()
{
const string yaml = """
canvas:
@@ -623,7 +623,7 @@ public void Pipeline_ExpressionInOpacity_IntegerValue_ResolvesToFloat()
}
};
- var result = ParseAndProcess(yaml, data);
+ var result = await ParseAndProcess(yaml, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal(1.0f, text.Opacity.Value);
diff --git a/tests/FlexRender.Tests/TemplateEngine/TableExpanderTests.cs b/tests/FlexRender.Tests/TemplateEngine/TableExpanderTests.cs
index 6e32a5a..a8f3cb6 100644
--- a/tests/FlexRender.Tests/TemplateEngine/TableExpanderTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/TableExpanderTests.cs
@@ -25,7 +25,7 @@ private static Template CreateTemplate(params TemplateElement[] elements)
// === Dynamic Table Tests ===
[Fact]
- public void Expand_DynamicTable_CreatesOuterColumnFlex()
+ public async Task Expand_DynamicTable_CreatesOuterColumnFlex()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "name", Label = "Name", Grow = 1 });
@@ -39,14 +39,14 @@ public void Expand_DynamicTable_CreatesOuterColumnFlex()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
Assert.Equal(FlexDirection.Column, outerFlex.Direction);
}
[Fact]
- public void Expand_DynamicTable_WithHeaders_CreatesHeaderRow()
+ public async Task Expand_DynamicTable_WithHeaders_CreatesHeaderRow()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "name", Label = "Name", Grow = 1 },
@@ -65,7 +65,7 @@ public void Expand_DynamicTable_WithHeaders_CreatesHeaderRow()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// First child should be the header row
@@ -84,7 +84,7 @@ public void Expand_DynamicTable_WithHeaders_CreatesHeaderRow()
}
[Fact]
- public void Expand_DynamicTable_WithData_CreatesDataRows()
+ public async Task Expand_DynamicTable_WithData_CreatesDataRows()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "name", Grow = 1 });
@@ -100,7 +100,7 @@ public void Expand_DynamicTable_WithData_CreatesDataRows()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// No headers (no labels), so all 3 children should be data row FlexElements
@@ -116,7 +116,7 @@ public void Expand_DynamicTable_WithData_CreatesDataRows()
}
[Fact]
- public void Expand_DynamicTable_WithEmptyArray_OnlyHeaders()
+ public async Task Expand_DynamicTable_WithEmptyArray_OnlyHeaders()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "name", Label = "Name", Grow = 1 });
@@ -127,7 +127,7 @@ public void Expand_DynamicTable_WithEmptyArray_OnlyHeaders()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// Just the header row, no data rows
@@ -136,7 +136,7 @@ public void Expand_DynamicTable_WithEmptyArray_OnlyHeaders()
}
[Fact]
- public void Expand_DynamicTable_WithoutItemVariable_UsesDirectAccess()
+ public async Task Expand_DynamicTable_WithoutItemVariable_UsesDirectAccess()
{
var columns = new List
{
@@ -158,7 +158,7 @@ public void Expand_DynamicTable_WithoutItemVariable_UsesDirectAccess()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
@@ -167,7 +167,7 @@ public void Expand_DynamicTable_WithoutItemVariable_UsesDirectAccess()
}
[Fact]
- public void Expand_DynamicTable_WithFormat_UsesFormatString()
+ public async Task Expand_DynamicTable_WithFormat_UsesFormatString()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "price", Grow = 1, Format = "{{item.price}} $" });
@@ -181,7 +181,7 @@ public void Expand_DynamicTable_WithFormat_UsesFormatString()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
@@ -192,7 +192,7 @@ public void Expand_DynamicTable_WithFormat_UsesFormatString()
// === Static Table Tests ===
[Fact]
- public void Expand_StaticTable_CreatesRows()
+ public async Task Expand_StaticTable_CreatesRows()
{
var columns = new List
{
@@ -211,7 +211,7 @@ public void Expand_StaticTable_CreatesRows()
var template = CreateTemplate(table);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// No headers (no labels), so 2 data rows
@@ -229,7 +229,7 @@ public void Expand_StaticTable_CreatesRows()
}
[Fact]
- public void Expand_StaticTable_WithHeaders_IncludesHeaderRow()
+ public async Task Expand_StaticTable_WithHeaders_IncludesHeaderRow()
{
var columns = new List
{
@@ -247,7 +247,7 @@ public void Expand_StaticTable_WithHeaders_IncludesHeaderRow()
var template = CreateTemplate(table);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// Header row + 1 data row
@@ -259,7 +259,7 @@ public void Expand_StaticTable_WithHeaders_IncludesHeaderRow()
}
[Fact]
- public void Expand_StaticTable_PerRowStyling_Applied()
+ public async Task Expand_StaticTable_PerRowStyling_Applied()
{
var columns = new List
{
@@ -281,7 +281,7 @@ public void Expand_StaticTable_PerRowStyling_Applied()
var template = CreateTemplate(table);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
@@ -296,7 +296,7 @@ public void Expand_StaticTable_PerRowStyling_Applied()
// === Header Border Bottom Tests ===
[Fact]
- public void Expand_WithHeaderBorderBottom_CreatesSeparator()
+ public async Task Expand_WithHeaderBorderBottom_CreatesSeparator()
{
var columns = new List
{
@@ -318,7 +318,7 @@ public void Expand_WithHeaderBorderBottom_CreatesSeparator()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
// header row + separator + data row
@@ -330,7 +330,7 @@ public void Expand_WithHeaderBorderBottom_CreatesSeparator()
}
[Fact]
- public void Expand_HeaderBorderBottomTrue_UsesDottedStyle()
+ public async Task Expand_HeaderBorderBottomTrue_UsesDottedStyle()
{
var columns = new List
{
@@ -349,7 +349,7 @@ public void Expand_HeaderBorderBottomTrue_UsesDottedStyle()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var sep = Assert.IsType(outerFlex.Children[1]);
@@ -359,7 +359,7 @@ public void Expand_HeaderBorderBottomTrue_UsesDottedStyle()
// === Spacing Tests ===
[Fact]
- public void Expand_RowGap_AppliedToOuterFlex()
+ public async Task Expand_RowGap_AppliedToOuterFlex()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "name", Grow = 1 });
@@ -374,14 +374,14 @@ public void Expand_RowGap_AppliedToOuterFlex()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
Assert.Equal("4", outerFlex.Gap);
}
[Fact]
- public void Expand_ColumnGap_AppliedToRowFlex()
+ public async Task Expand_ColumnGap_AppliedToRowFlex()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "a", Grow = 1 },
@@ -401,7 +401,7 @@ public void Expand_ColumnGap_AppliedToRowFlex()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
@@ -411,7 +411,7 @@ public void Expand_ColumnGap_AppliedToRowFlex()
// === Style Inheritance Tests ===
[Fact]
- public void Expand_HeaderStyling_OverridesTableDefaults()
+ public async Task Expand_HeaderStyling_OverridesTableDefaults()
{
var columns = new List
{
@@ -435,7 +435,7 @@ public void Expand_HeaderStyling_OverridesTableDefaults()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var headerRow = Assert.IsType(outerFlex.Children[0]);
@@ -447,7 +447,7 @@ public void Expand_HeaderStyling_OverridesTableDefaults()
}
[Fact]
- public void Expand_ColumnStyling_OverridesTableDefaults()
+ public async Task Expand_ColumnStyling_OverridesTableDefaults()
{
var columns = new List
{
@@ -471,7 +471,7 @@ public void Expand_ColumnStyling_OverridesTableDefaults()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
@@ -485,7 +485,7 @@ public void Expand_ColumnStyling_OverridesTableDefaults()
// === Base Properties Tests ===
[Fact]
- public void Expand_BaseProperties_CopiedToOuterFlex()
+ public async Task Expand_BaseProperties_CopiedToOuterFlex()
{
var columns = new List
{
@@ -506,7 +506,7 @@ public void Expand_BaseProperties_CopiedToOuterFlex()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
Assert.Equal("10", outerFlex.Padding);
@@ -515,7 +515,7 @@ public void Expand_BaseProperties_CopiedToOuterFlex()
}
[Fact]
- public void Expand_MissingKeyInData_ResolvesToEmptyString()
+ public async Task Expand_MissingKeyInData_ResolvesToEmptyString()
{
var table = CreateDynamicTable("items", "item",
new TableColumn { Key = "missing_key", Grow = 1 });
@@ -529,7 +529,7 @@ public void Expand_MissingKeyInData_ResolvesToEmptyString()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var outerFlex = Assert.IsType(result.Elements[0]);
var rowFlex = Assert.IsType(outerFlex.Children[0]);
diff --git a/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderBytesImageTests.cs b/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderBytesImageTests.cs
index c40bcf9..8fc05cc 100644
--- a/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderBytesImageTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderBytesImageTests.cs
@@ -25,7 +25,7 @@ private static Template CreateTemplate(params TemplateElement[] elements)
}
[Fact]
- public void Expand_ImageSrcBytesValue_ConvertsToDataUri()
+ public async Task Expand_ImageSrcBytesValue_ConvertsToDataUri()
{
var pngBytes = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
var template = CreateTemplate(
@@ -36,7 +36,7 @@ public void Expand_ImageSrcBytesValue_ConvertsToDataUri()
["logo"] = new BytesValue(pngBytes, "image/png")
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
var expectedBase64 = Convert.ToBase64String(pngBytes);
@@ -44,7 +44,7 @@ public void Expand_ImageSrcBytesValue_ConvertsToDataUri()
}
[Fact]
- public void Expand_ImageSrcBytesValue_RoundTripsBytes()
+ public async Task Expand_ImageSrcBytesValue_RoundTripsBytes()
{
var pngBytes = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x01, 0x02, 0xFF };
var template = CreateTemplate(
@@ -55,7 +55,7 @@ public void Expand_ImageSrcBytesValue_RoundTripsBytes()
["logo"] = new BytesValue(pngBytes, "image/png")
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
var srcValue = img.Src.Value;
@@ -68,7 +68,7 @@ public void Expand_ImageSrcBytesValue_RoundTripsBytes()
}
[Fact]
- public void Expand_ImageSrcBytesValueWithoutMimeType_DefaultsToOctetStream()
+ public async Task Expand_ImageSrcBytesValueWithoutMimeType_DefaultsToOctetStream()
{
var rawBytes = new byte[] { 0x01, 0x02, 0x03 };
var template = CreateTemplate(
@@ -79,7 +79,7 @@ public void Expand_ImageSrcBytesValueWithoutMimeType_DefaultsToOctetStream()
["data"] = new BytesValue(rawBytes)
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
var expectedBase64 = Convert.ToBase64String(rawBytes);
@@ -87,7 +87,7 @@ public void Expand_ImageSrcBytesValueWithoutMimeType_DefaultsToOctetStream()
}
[Fact]
- public void Expand_ImageSrcStringValue_ResolvesAsStringPath()
+ public async Task Expand_ImageSrcStringValue_ResolvesAsStringPath()
{
var template = CreateTemplate(
new ImageElement { Src = "{{imagePath}}" }
@@ -97,28 +97,28 @@ public void Expand_ImageSrcStringValue_ResolvesAsStringPath()
["imagePath"] = new StringValue("/images/logo.png")
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
Assert.Equal("/images/logo.png", img.Src.Value);
}
[Fact]
- public void Expand_ImageSrcLiteral_PreservesLiteral()
+ public async Task Expand_ImageSrcLiteral_PreservesLiteral()
{
var template = CreateTemplate(
new ImageElement { Src = "/static/logo.png" }
);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
Assert.Equal("/static/logo.png", img.Src.Value);
}
[Fact]
- public void Expand_ImageSrcMixedExpression_FallsBackToStringSubstitution()
+ public async Task Expand_ImageSrcMixedExpression_FallsBackToStringSubstitution()
{
// Mixed expression like "prefix_{{name}}.png" should not trigger bytes resolution
var template = CreateTemplate(
@@ -129,14 +129,14 @@ public void Expand_ImageSrcMixedExpression_FallsBackToStringSubstitution()
["name"] = new StringValue("logo")
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
Assert.Equal("prefix_logo.png", img.Src.Value);
}
[Fact]
- public void Expand_ImageSrcBytesValueInEach_ConvertsToDataUri()
+ public async Task Expand_ImageSrcBytesValueInEach_ConvertsToDataUri()
{
var bytes1 = new byte[] { 0x01, 0x02 };
var bytes2 = new byte[] { 0x03, 0x04 };
@@ -158,7 +158,7 @@ public void Expand_ImageSrcBytesValueInEach_ConvertsToDataUri()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var img1 = Assert.IsType(result.Elements[0]);
@@ -169,7 +169,7 @@ public void Expand_ImageSrcBytesValueInEach_ConvertsToDataUri()
}
[Fact]
- public void Expand_ImageSrcBytesValueNestedPath_ConvertsToDataUri()
+ public async Task Expand_ImageSrcBytesValueNestedPath_ConvertsToDataUri()
{
var pngBytes = new byte[] { 0x89, 0x50, 0x4E, 0x47 };
var template = CreateTemplate(
@@ -183,7 +183,7 @@ public void Expand_ImageSrcBytesValueNestedPath_ConvertsToDataUri()
}
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var img = Assert.IsType(result.Elements[0]);
var expectedBase64 = Convert.ToBase64String(pngBytes);
diff --git a/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderTests.cs b/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderTests.cs
index d8ae226..796911a 100644
--- a/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/TemplateExpanderTests.cs
@@ -25,7 +25,7 @@ private static Template CreateTemplate(params TemplateElement[] elements)
// === Each Expansion Tests ===
[Fact]
- public void Expand_NoControlFlow_ReturnsUnchanged()
+ public async Task Expand_NoControlFlow_ReturnsUnchanged()
{
var template = CreateTemplate(
new TextElement { Content = "Hello" },
@@ -33,7 +33,7 @@ public void Expand_NoControlFlow_ReturnsUnchanged()
);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
Assert.Equal("Hello", ((TextElement)result.Elements[0]).Content);
@@ -41,7 +41,7 @@ public void Expand_NoControlFlow_ReturnsUnchanged()
}
[Fact]
- public void Expand_EachWithArray_CreatesElementsPerItem()
+ public async Task Expand_EachWithArray_CreatesElementsPerItem()
{
var each = new EachElement(new List
{
@@ -62,14 +62,14 @@ public void Expand_EachWithArray_CreatesElementsPerItem()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(3, result.Elements.Count);
Assert.All(result.Elements, e => Assert.IsType(e));
}
[Fact]
- public void Expand_EachWithEmptyArray_ReturnsEmpty()
+ public async Task Expand_EachWithEmptyArray_ReturnsEmpty()
{
var each = new EachElement(new List
{
@@ -85,13 +85,13 @@ public void Expand_EachWithEmptyArray_ReturnsEmpty()
["items"] = new ArrayValue(new List())
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Empty(result.Elements);
}
[Fact]
- public void Expand_EachWithMissingArray_ReturnsEmpty()
+ public async Task Expand_EachWithMissingArray_ReturnsEmpty()
{
var each = new EachElement(new List
{
@@ -104,13 +104,13 @@ public void Expand_EachWithMissingArray_ReturnsEmpty()
var template = CreateTemplate(each);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Empty(result.Elements);
}
[Fact]
- public void Expand_EachWithItemVariable_SetsVariableInScope()
+ public async Task Expand_EachWithItemVariable_SetsVariableInScope()
{
var each = new EachElement(new List
{
@@ -131,7 +131,7 @@ public void Expand_EachWithItemVariable_SetsVariableInScope()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
// Verify variables are substituted during expansion
@@ -144,7 +144,7 @@ public void Expand_EachWithItemVariable_SetsVariableInScope()
// === If Expansion Tests ===
[Fact]
- public void Expand_IfTruthy_ReturnsThenBranch()
+ public async Task Expand_IfTruthy_ReturnsThenBranch()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Visible" } },
@@ -156,14 +156,14 @@ public void Expand_IfTruthy_ReturnsThenBranch()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["show"] = new BoolValue(true) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
Assert.Equal("Visible", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfFalsy_ReturnsElseBranch()
+ public async Task Expand_IfFalsy_ReturnsElseBranch()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Visible" } },
@@ -175,14 +175,14 @@ public void Expand_IfFalsy_ReturnsElseBranch()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["show"] = new BoolValue(false) };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
Assert.Equal("Hidden", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfEquals_MatchingValue_ReturnsThen()
+ public async Task Expand_IfEquals_MatchingValue_ReturnsThen()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Paid" } },
@@ -196,13 +196,13 @@ public void Expand_IfEquals_MatchingValue_ReturnsThen()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("paid") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("Paid", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfEquals_NonMatchingValue_ReturnsElse()
+ public async Task Expand_IfEquals_NonMatchingValue_ReturnsElse()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Paid" } },
@@ -216,13 +216,13 @@ public void Expand_IfEquals_NonMatchingValue_ReturnsElse()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("pending") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("Not paid", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfNotEquals_MatchingValue_ReturnsElse()
+ public async Task Expand_IfNotEquals_MatchingValue_ReturnsElse()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Active" } },
@@ -236,13 +236,13 @@ public void Expand_IfNotEquals_MatchingValue_ReturnsElse()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("cancelled") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("Cancelled", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfNotEquals_NonMatchingValue_ReturnsThen()
+ public async Task Expand_IfNotEquals_NonMatchingValue_ReturnsThen()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Active" } },
@@ -256,13 +256,13 @@ public void Expand_IfNotEquals_NonMatchingValue_ReturnsThen()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("active") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("Active", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfElseIf_EvaluatesChain()
+ public async Task Expand_IfElseIf_EvaluatesChain()
{
var elseIf = new IfElement(
new List { new TextElement { Content = "Pending" } })
@@ -285,14 +285,14 @@ public void Expand_IfElseIf_EvaluatesChain()
var template = CreateTemplate(ifElem);
var data = new ObjectValue { ["status"] = new StringValue("pending") };
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
Assert.Equal("Pending", ((TextElement)result.Elements[0]).Content);
}
[Fact]
- public void Expand_IfMissingPath_ReturnsFalsy()
+ public async Task Expand_IfMissingPath_ReturnsFalsy()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "Visible" } },
@@ -304,7 +304,7 @@ public void Expand_IfMissingPath_ReturnsFalsy()
var template = CreateTemplate(ifElem);
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
Assert.Equal("Hidden", ((TextElement)result.Elements[0]).Content);
@@ -313,7 +313,7 @@ public void Expand_IfMissingPath_ReturnsFalsy()
// === Nested Expansion Tests ===
[Fact]
- public void Expand_NestedEachInFlex_ExpandsCorrectly()
+ public async Task Expand_NestedEachInFlex_ExpandsCorrectly()
{
var each = new EachElement(new List
{
@@ -335,7 +335,7 @@ public void Expand_NestedEachInFlex_ExpandsCorrectly()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
var resultFlex = Assert.IsType(result.Elements[0]);
@@ -343,7 +343,7 @@ public void Expand_NestedEachInFlex_ExpandsCorrectly()
}
[Fact]
- public void Expand_NestedIfInEach_ExpandsCorrectly()
+ public async Task Expand_NestedIfInEach_ExpandsCorrectly()
{
var ifElem = new IfElement(
new List { new TextElement { Content = "active" } },
@@ -367,7 +367,7 @@ public void Expand_NestedIfInEach_ExpandsCorrectly()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var text1 = Assert.IsType(result.Elements[0]);
@@ -379,7 +379,7 @@ public void Expand_NestedIfInEach_ExpandsCorrectly()
// === Resource Limits Tests ===
[Fact]
- public void Expand_ExceedsMaxDepth_ThrowsException()
+ public async Task Expand_ExceedsMaxDepth_ThrowsException()
{
var limits = new ResourceLimits { MaxRenderDepth = 2 };
var expander = new TemplateExpander(limits);
@@ -409,11 +409,11 @@ public void Expand_ExceedsMaxDepth_ThrowsException()
["items"] = new ArrayValue(new List { new ObjectValue() })
};
- Assert.Throws(() => expander.Expand(template, data));
+ await Assert.ThrowsAsync(async () => await expander.ExpandAsync(template, data));
}
[Fact]
- public void Expand_DeeplyNestedEach_WithLowLimit_Throws()
+ public async Task Expand_DeeplyNestedEach_WithLowLimit_Throws()
{
// Create deeply nested Each elements (20 levels)
TemplateElement current = new TextElement { Content = "deep" };
@@ -436,13 +436,13 @@ public void Expand_DeeplyNestedEach_WithLowLimit_Throws()
var expander = new TemplateExpander(new ResourceLimits { MaxRenderDepth = 10 });
- Assert.Throws(() => expander.Expand(template, data));
+ await Assert.ThrowsAsync(async () => await expander.ExpandAsync(template, data));
}
// === Regular Element Pass-through Tests ===
[Fact]
- public void Expand_RegularElements_PassesThrough()
+ public async Task Expand_RegularElements_PassesThrough()
{
var template = CreateTemplate(
new TextElement { Content = "Hello" },
@@ -452,7 +452,7 @@ public void Expand_RegularElements_PassesThrough()
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(3, result.Elements.Count);
Assert.IsType(result.Elements[0]);
@@ -463,22 +463,22 @@ public void Expand_RegularElements_PassesThrough()
// === Null Argument Tests ===
[Fact]
- public void Expand_NullTemplate_ThrowsArgumentNullException()
+ public async Task Expand_NullTemplate_ThrowsArgumentNullException()
{
- Assert.Throws(() => _expander.Expand(null!, new ObjectValue()));
+ await Assert.ThrowsAsync(async () => await _expander.ExpandAsync(null!, new ObjectValue()));
}
[Fact]
- public void Expand_NullData_ThrowsArgumentNullException()
+ public async Task Expand_NullData_ThrowsArgumentNullException()
{
var template = CreateTemplate();
- Assert.Throws(() => _expander.Expand(template, null!));
+ await Assert.ThrowsAsync(async () => await _expander.ExpandAsync(template, null!));
}
// === Template Metadata Preservation Tests ===
[Fact]
- public void Expand_PreservesTemplateMetadata()
+ public async Task Expand_PreservesTemplateMetadata()
{
var template = new Template
{
@@ -491,7 +491,7 @@ public void Expand_PreservesTemplateMetadata()
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal("TestTemplate", result.Name);
Assert.Equal(2, result.Version);
@@ -526,7 +526,7 @@ public void Constructor_WithCustomLimits_Succeeds()
// === Variable Substitution Tests ===
[Fact]
- public void Expand_EachWithDirectAccess_SubstitutesVariables()
+ public async Task Expand_EachWithDirectAccess_SubstitutesVariables()
{
var each = new EachElement(new List
{
@@ -546,7 +546,7 @@ public void Expand_EachWithDirectAccess_SubstitutesVariables()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var text1 = Assert.IsType(result.Elements[0]);
@@ -556,7 +556,7 @@ public void Expand_EachWithDirectAccess_SubstitutesVariables()
}
[Fact]
- public void Expand_NestedEach_SubstitutesVariablesFromCurrentScope()
+ public async Task Expand_NestedEach_SubstitutesVariablesFromCurrentScope()
{
// Inner loop accesses variables from its current scope (the inner item)
var innerEach = new EachElement(new List
@@ -590,7 +590,7 @@ public void Expand_NestedEach_SubstitutesVariablesFromCurrentScope()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var text1 = Assert.IsType(result.Elements[0]);
@@ -600,7 +600,7 @@ public void Expand_NestedEach_SubstitutesVariablesFromCurrentScope()
}
[Fact]
- public void Expand_QrElement_SubstitutesDataVariable()
+ public async Task Expand_QrElement_SubstitutesDataVariable()
{
var each = new EachElement(new List
{
@@ -620,7 +620,7 @@ public void Expand_QrElement_SubstitutesDataVariable()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var qr1 = Assert.IsType(result.Elements[0]);
@@ -630,7 +630,7 @@ public void Expand_QrElement_SubstitutesDataVariable()
}
[Fact]
- public void Expand_ImageElement_SubstitutesSourceVariable()
+ public async Task Expand_ImageElement_SubstitutesSourceVariable()
{
var each = new EachElement(new List
{
@@ -650,7 +650,7 @@ public void Expand_ImageElement_SubstitutesSourceVariable()
})
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var img1 = Assert.IsType(result.Elements[0]);
@@ -660,7 +660,7 @@ public void Expand_ImageElement_SubstitutesSourceVariable()
}
[Fact]
- public void Expand_BarcodeElement_SubstitutesDataVariable()
+ public async Task Expand_BarcodeElement_SubstitutesDataVariable()
{
var template = CreateTemplate(new BarcodeElement { Data = "{{barcode}}" });
var data = new ObjectValue
@@ -668,7 +668,7 @@ public void Expand_BarcodeElement_SubstitutesDataVariable()
["barcode"] = new StringValue("1234567890")
};
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
var barcode = Assert.IsType(result.Elements[0]);
@@ -676,24 +676,24 @@ public void Expand_BarcodeElement_SubstitutesDataVariable()
}
[Fact]
- public void Expand_TextWithNoVariables_PreservesContent()
+ public async Task Expand_TextWithNoVariables_PreservesContent()
{
var template = CreateTemplate(new TextElement { Content = "Hello World" });
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("Hello World", text.Content);
}
[Fact]
- public void Expand_TextWithMissingVariable_ReplacesWithEmpty()
+ public async Task Expand_TextWithMissingVariable_ReplacesWithEmpty()
{
var template = CreateTemplate(new TextElement { Content = "Hello {{name}}" });
var data = new ObjectValue();
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
var text = Assert.IsType(result.Elements[0]);
Assert.Equal("Hello ", text.Content);
@@ -702,7 +702,7 @@ public void Expand_TextWithMissingVariable_ReplacesWithEmpty()
// === ObjectValue Iteration Tests ===
[Fact]
- public void Expand_EachWithObjectValue_IteratesKeyValuePairs()
+ public async Task Expand_EachWithObjectValue_IteratesKeyValuePairs()
{
var data = new ObjectValue
{
@@ -719,7 +719,7 @@ public void Expand_EachWithObjectValue_IteratesKeyValuePairs()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
var first = Assert.IsType(result.Elements[0]);
@@ -729,7 +729,7 @@ public void Expand_EachWithObjectValue_IteratesKeyValuePairs()
}
[Fact]
- public void Expand_EachWithObjectValue_WithAsVariable_BindsValue()
+ public async Task Expand_EachWithObjectValue_WithAsVariable_BindsValue()
{
var data = new ObjectValue
{
@@ -746,7 +746,7 @@ public void Expand_EachWithObjectValue_WithAsVariable_BindsValue()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
var text = Assert.IsType(result.Elements[0]);
@@ -754,7 +754,7 @@ public void Expand_EachWithObjectValue_WithAsVariable_BindsValue()
}
[Fact]
- public void Expand_EachWithObjectValue_IndexFirstLast()
+ public async Task Expand_EachWithObjectValue_IndexFirstLast()
{
var data = new ObjectValue
{
@@ -772,7 +772,7 @@ public void Expand_EachWithObjectValue_IndexFirstLast()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(3, result.Elements.Count);
Assert.Equal("0-true-false", ((TextElement)result.Elements[0]).Content.Value);
@@ -781,7 +781,7 @@ public void Expand_EachWithObjectValue_IndexFirstLast()
}
[Fact]
- public void Expand_EachWithObjectValue_NestedValues()
+ public async Task Expand_EachWithObjectValue_NestedValues()
{
var data = new ObjectValue
{
@@ -798,7 +798,7 @@ public void Expand_EachWithObjectValue_NestedValues()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Single(result.Elements);
var text = Assert.IsType(result.Elements[0]);
@@ -806,7 +806,7 @@ public void Expand_EachWithObjectValue_NestedValues()
}
[Fact]
- public void Expand_EachWithEmptyObjectValue_ReturnsEmpty()
+ public async Task Expand_EachWithEmptyObjectValue_ReturnsEmpty()
{
var data = new ObjectValue
{
@@ -819,13 +819,13 @@ public void Expand_EachWithEmptyObjectValue_ReturnsEmpty()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Empty(result.Elements);
}
[Fact]
- public void Expand_EachWithStringValue_ReturnsEmpty()
+ public async Task Expand_EachWithStringValue_ReturnsEmpty()
{
var data = new ObjectValue
{
@@ -838,13 +838,13 @@ public void Expand_EachWithStringValue_ReturnsEmpty()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Empty(result.Elements);
}
[Fact]
- public void Expand_EachOverObject_WithComputedKeyAccess()
+ public async Task Expand_EachOverObject_WithComputedKeyAccess()
{
// Scenario: iterate over labels dict, look up values from another dict by @key
var data = new ObjectValue
@@ -869,7 +869,7 @@ public void Expand_EachOverObject_WithComputedKeyAccess()
};
var template = CreateTemplate(each);
- var result = _expander.Expand(template, data);
+ var result = await _expander.ExpandAsync(template, data);
Assert.Equal(2, result.Elements.Count);
Assert.Equal("Name: Widget", ((TextElement)result.Elements[0]).Content.Value);
diff --git a/tests/FlexRender.Tests/TemplateEngine/TemplatePipelineTests.cs b/tests/FlexRender.Tests/TemplateEngine/TemplatePipelineTests.cs
index cd9f7a9..2159773 100644
--- a/tests/FlexRender.Tests/TemplateEngine/TemplatePipelineTests.cs
+++ b/tests/FlexRender.Tests/TemplateEngine/TemplatePipelineTests.cs
@@ -11,7 +11,7 @@ namespace FlexRender.Tests.TemplateEngine;
public sealed class TemplatePipelineTests
{
[Fact]
- public void Process_CanvasBackground_Expression_Resolves()
+ public async Task Process_CanvasBackground_Expression_Resolves()
{
// Arrange: create template with expression in canvas background
var template = new Template
@@ -30,7 +30,7 @@ public void Process_CanvasBackground_Expression_Resolves()
var pipeline = new TemplatePipeline(expander, processor);
// Act
- var result = pipeline.Process(template, data);
+ var result = await pipeline.ProcessAsync(template, data);
// Assert
Assert.Equal("#ff0000", result.Canvas.Background.Value);
@@ -38,7 +38,7 @@ public void Process_CanvasBackground_Expression_Resolves()
}
[Fact]
- public void Process_CanvasLiteral_PassesThrough()
+ public async Task Process_CanvasLiteral_PassesThrough()
{
var template = new Template
{
@@ -55,14 +55,14 @@ public void Process_CanvasLiteral_PassesThrough()
var processor = new TemplateProcessor(limits);
var pipeline = new TemplatePipeline(expander, processor);
- var result = pipeline.Process(template, data);
+ var result = await pipeline.ProcessAsync(template, data);
Assert.Equal("#ffffff", result.Canvas.Background.Value);
Assert.True(result.Canvas.Background.IsResolved);
}
[Fact]
- public void Process_CanvasRotate_Expression_Resolves()
+ public async Task Process_CanvasRotate_Expression_Resolves()
{
var template = new Template
{
@@ -79,25 +79,25 @@ public void Process_CanvasRotate_Expression_Resolves()
var processor = new TemplateProcessor(limits);
var pipeline = new TemplatePipeline(expander, processor);
- var result = pipeline.Process(template, data);
+ var result = await pipeline.ProcessAsync(template, data);
Assert.Equal("right", result.Canvas.Rotate.Value);
Assert.True(result.Canvas.Rotate.IsResolved);
}
[Fact]
- public void Process_NullTemplate_ThrowsArgumentNullException()
+ public async Task Process_NullTemplate_ThrowsArgumentNullException()
{
var limits = new ResourceLimits();
var expander = new TemplateExpander(limits);
var processor = new TemplateProcessor(limits);
var pipeline = new TemplatePipeline(expander, processor);
- Assert.Throws(() => pipeline.Process(null!, new ObjectValue()));
+ await Assert.ThrowsAsync(async () => await pipeline.ProcessAsync(null!, new ObjectValue()));
}
[Fact]
- public void Process_NullData_ThrowsArgumentNullException()
+ public async Task Process_NullData_ThrowsArgumentNullException()
{
var limits = new ResourceLimits();
var expander = new TemplateExpander(limits);
@@ -105,7 +105,7 @@ public void Process_NullData_ThrowsArgumentNullException()
var pipeline = new TemplatePipeline(expander, processor);
var template = new Template { Canvas = new CanvasSettings { Width = 300 } };
- Assert.Throws(() => pipeline.Process(template, null!));
+ await Assert.ThrowsAsync(async () => await pipeline.ProcessAsync(template, null!));
}
[Fact]
@@ -123,7 +123,7 @@ public void Constructor_NullProcessor_ThrowsArgumentNullException()
}
[Fact]
- public void Process_PreservesTemplateMetadata()
+ public async Task Process_PreservesTemplateMetadata()
{
var template = new Template
{
@@ -138,7 +138,7 @@ public void Process_PreservesTemplateMetadata()
var processor = new TemplateProcessor(limits);
var pipeline = new TemplatePipeline(expander, processor);
- var result = pipeline.Process(template, data);
+ var result = await pipeline.ProcessAsync(template, data);
Assert.Equal("test-pipeline", result.Name);
Assert.Equal(3, result.Version);
@@ -146,7 +146,7 @@ public void Process_PreservesTemplateMetadata()
}
[Fact]
- public void Process_ExpandsElements()
+ public async Task Process_ExpandsElements()
{
var template = new Template
{
@@ -161,7 +161,7 @@ public void Process_ExpandsElements()
var processor = new TemplateProcessor(limits);
var pipeline = new TemplatePipeline(expander, processor);
- var result = pipeline.Process(template, data);
+ var result = await pipeline.ProcessAsync(template, data);
Assert.Single(result.Elements);
}
diff --git a/tests/FlexRender.Tests/VisualEffects/BoxShadowParsingTests.cs b/tests/FlexRender.Tests/VisualEffects/BoxShadowParsingTests.cs
index 779f6a5..6ebdf00 100644
--- a/tests/FlexRender.Tests/VisualEffects/BoxShadowParsingTests.cs
+++ b/tests/FlexRender.Tests/VisualEffects/BoxShadowParsingTests.cs
@@ -100,7 +100,7 @@ public void Parse_TextWithBoxShadow_SetsProperty()
// === Expansion preserves box-shadow ===
[Fact]
- public void Expand_PreservesBoxShadow()
+ public async Task Expand_PreservesBoxShadow()
{
var expander = new FlexRender.TemplateEngine.TemplateExpander();
var template = new Template();
@@ -114,7 +114,7 @@ public void Expand_PreservesBoxShadow()
}
});
- var result = expander.Expand(template, new ObjectValue());
+ var result = await expander.ExpandAsync(template, new ObjectValue());
var flex = Assert.IsType(result.Elements[0]);
Assert.Equal("4 4 8 #000000", flex.BoxShadow);
diff --git a/tests/FlexRender.Tests/VisualEffects/OpacityTests.cs b/tests/FlexRender.Tests/VisualEffects/OpacityTests.cs
index 9854940..2a06d0e 100644
--- a/tests/FlexRender.Tests/VisualEffects/OpacityTests.cs
+++ b/tests/FlexRender.Tests/VisualEffects/OpacityTests.cs
@@ -152,7 +152,7 @@ public void Parse_OpacityOne_Parses()
// === Expansion preserves opacity ===
[Fact]
- public void Expand_PreservesOpacity()
+ public async Task Expand_PreservesOpacity()
{
var expander = new FlexRender.TemplateEngine.TemplateExpander();
var template = new Template();
@@ -165,7 +165,7 @@ public void Expand_PreservesOpacity()
}
});
- var result = expander.Expand(template, new ObjectValue());
+ var result = await expander.ExpandAsync(template, new ObjectValue());
var flex = Assert.IsType(result.Elements[0]);
Assert.Equal(0.5f, flex.Opacity);