using System.Globalization; using Bunit; using Prefab.Web.Client.Components.Catalog; using Prefab.Web.Client.Models.Shared; using Prefab.Web.Client.ViewModels.Catalog; using Shouldly; namespace Prefab.Tests.Web.Client.Components.Catalog; [Trait(TraitName.Category, TraitCategory.Unit)] public sealed class ProductCardShould : BunitContext { [Fact] public void RenderSaleBadgeOldPriceAndRating() { var model = CreateModel(m => { m.IsOnSale = true; m.OldPrice = 419m; m.Rating = 4; m.ReviewCount = 15; m.Badges.Add("New"); }); var cut = Render(parameters => { parameters.Add(p => p.Product, model); parameters.Add(p => p.ShowPrice, true); }); var root = cut.Find($".{TemplateCss.ProductCardRoot}"); root.ClassList.ShouldContain(TemplateCss.ProductCardGridModifier); cut.FindAll($".{TemplateCss.ProductCardBadgeSale}").Count.ShouldBe(1); cut.FindAll($".{TemplateCss.ProductCardBadge}").Count.ShouldBe(2); var newPrice = cut.Find($".{TemplateCss.ProductCardPriceNew}"); var expectedNewPrice = model.FromPrice?.Amount ?? throw new InvalidOperationException("Expected from price to be set."); newPrice.TextContent.Trim().ShouldBe(expectedNewPrice.ToString("C", CultureInfo.CurrentCulture)); var oldPrice = cut.Find($".{TemplateCss.ProductCardPriceOld}"); var expectedOldPrice = model.OldPrice ?? throw new InvalidOperationException("Expected old price to be set for sale items."); oldPrice.TextContent.Trim().ShouldBe(expectedOldPrice.ToString("C", CultureInfo.CurrentCulture)); var rating = cut.Find($".{TemplateCss.ProductCardRating}"); var ratingLabel = rating.QuerySelector($".{TemplateCss.Rating}")!; ratingLabel.GetAttribute("aria-label").ShouldBe("4 out of 5"); } [Fact] public void NotRenderSaleBadgeOrOldPriceWhenAbsent() { var model = CreateModel(m => { m.IsOnSale = false; m.OldPrice = null; m.Rating = 0; m.ReviewCount = 0; }); var cut = Render(parameters => { parameters.Add(p => p.Product, model); parameters.Add(p => p.ShowPrice, true); }); cut.FindAll($".{TemplateCss.ProductCardBadgeSale}").ShouldBeEmpty(); cut.Markup.ShouldNotContain(TemplateCss.ProductCardPriceOld); cut.Markup.ShouldNotContain(TemplateCss.ProductCardRating); } [Fact] public void NotRenderPricesByDefault() { var model = CreateModel(); var cut = Render(parameters => { parameters.Add(p => p.Product, model); // ShowPrice is false by default }); cut.Markup.ShouldNotContain(TemplateCss.ProductCardPrice); } [Fact] public void NotRenderActionButtons() { var model = CreateModel(); var cut = Render(parameters => { parameters.Add(p => p.Product, model); }); cut.Markup.ShouldNotContain("product-card__actions"); cut.Markup.ShouldNotContain("product-card__buttons"); } private static ProductCardModel CreateModel(Action? configure = null) { var model = new ProductCardModel { Id = Guid.NewGuid(), Title = "Aluminum Chandelier", Url = "/browse/product?slug=aluminum-chandelier", Slug = "aluminum-chandelier", CategoryName = "Chandeliers", CategoryUrl = "/catalog/chandeliers", PrimaryImageUrl = "/images/products/product1.jpg", FromPrice = new MoneyModel { Amount = 321.54m, Currency = "USD" }, IsPriced = true, Rating = 3, ReviewCount = 2, Sku = "SKU-1001", IsOnSale = true, OldPrice = 399.99m, LastModifiedOn = DateTimeOffset.UtcNow }; configure?.Invoke(model); return model; } }