using System.Net; using Microsoft.AspNetCore.Components; using System.Globalization; using Bunit; using Microsoft.Extensions.DependencyInjection; using Prefab.Web.Client.Models.Catalog; using Prefab.Web.Client.Models.Shared; using Prefab.Web.Client.Pages.Browse; using Prefab.Web.Client.Services; using Shouldly; namespace Prefab.Tests.Web.Client.Pages; [Trait(TraitName.Category, TraitCategory.Unit)] public sealed class ProductPageTests : BunitContext { [Fact] public void ProductPage_Renders_Product_Details_From_Service() { var model = CreateSampleProduct(); Services.AddScoped(_ => new FakeProductDisplayService(Result.Success(model))); var navigation = Services.GetRequiredService(); navigation.NavigateTo(navigation.GetUriWithQueryParameter("slug", model.Slug)); var component = Render(); component.Markup.ShouldContain(model.Name); model.Description.ShouldNotBeNull(); component.Markup.ShouldContain(model.Description!); component.FindAll("input[type=radio]").Count.ShouldBe(2); component.Markup.ShouldContain("Quantity"); component.FindAll(".product__add-to-cart").Count.ShouldBe(1); component.Markup.ShouldContain("Documents"); component.Markup.ShouldContain("Spec Sheet"); component.Markup.ShouldContain("SW-001"); component.Markup.ShouldContain("Lighting"); var expectedPrice = model.BasePrice!.Value.ToString("C", CultureInfo.CurrentCulture); component.Markup.ShouldContain(expectedPrice); } [Fact] public void ProductPage_Shows_Problem_When_NotFound() { Services.AddScoped(_ => new FakeProductDisplayService(Result.Failure(new ResultProblem { Title = "Product not found.", Detail = "Product 'unknown' was not found.", StatusCode = (int)HttpStatusCode.NotFound }))); var navigation = Services.GetRequiredService(); navigation.NavigateTo(navigation.GetUriWithQueryParameter("slug", "unknown")); var component = Render(); component.Markup.ShouldContain("Product not found."); component.FindAll(".alert-warning").Count.ShouldBe(1); } private static ProductDisplayModel CreateSampleProduct() => new() { Id = Guid.NewGuid(), Slug = "sample-widget", Name = "Sample Widget", Description = "

Sample description.

", BasePrice = 42.50m, Sku = "SW-001", Categories = [ new ProductDisplayModel.CategoryRef(Guid.NewGuid(), "Lighting", "lighting") ], Options = [ new ProductDisplayModel.OptionDefinition( Id: Guid.NewGuid(), Code: "color", Name: "Color", DataType: 0, IsVariantAxis: true, Unit: null, Min: null, Max: null, Step: null, PricePerUnit: null, Tiers: Array.Empty(), Values: [ new ProductDisplayModel.OptionValue(Guid.NewGuid(), "red", "Red", 0m, 0), new ProductDisplayModel.OptionValue(Guid.NewGuid(), "blue", "Blue", 0m, 0) ]), new ProductDisplayModel.OptionDefinition( Id: Guid.NewGuid(), Code: "length", Name: "Length", DataType: 1, IsVariantAxis: false, Unit: "ft", Min: 1, Max: 10, Step: 1, PricePerUnit: null, Tiers: [ new ProductDisplayModel.Tier(1, 5, 1, null) ], Values: Array.Empty()) ], Specs = [ new ProductDisplayModel.Spec("Weight", "25 lb", null, null) ], GenericAttributes = new Dictionary { ["catalog.product.docs"] = """[{ "title": "Spec Sheet", "url": "https://example.com/spec.pdf" }]""" } }; private sealed class FakeProductDisplayService(Result result) : IProductDisplayService { private readonly Result _result = result; public Task> Get(string slug, CancellationToken cancellationToken = default) => Task.FromResult(_result); } }