test: cover the meal wishlist (rules, validators, bUnit, integration); fix: bind wishlist search and sort as expressions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
019060ccea
commit
5ce407f401
7 files changed
+332
-2
No files matched your search
@@ -153,7 +153,7 @@ else
|
||||
{
|
||||
<MealWishList Wishes="_wishes" CanDecide="CanDecideWish" CanDelete="CanDeleteWish"
|
||||
OnAccept="AcceptWishAsync" OnReject="RejectWishAsync" OnDelete="DeleteWishAsync" />
|
||||
<DishWishlist Items="_wishlist" Search="_wishlistSearch" Sort="_wishlistSort" CanPlan="CanEdit" CanDelete="CanDeleteWishlistDish"
|
||||
<DishWishlist Items="_wishlist" Search="@_wishlistSearch" Sort="@_wishlistSort" CanPlan="CanEdit" CanDelete="CanDeleteWishlistDish"
|
||||
OnSearch="SearchWishlistAsync" OnSort="SortWishlistAsync" OnPlan="PlanWishlistDishAsync" OnDelete="DeleteWishlistDishAsync" />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,10 +888,16 @@ button.fp-meal-dish {
|
||||
}
|
||||
|
||||
/* Essenswünsche */
|
||||
.fp-meal-wishes {
|
||||
.fp-meal-wishes,
|
||||
.fp-wishlist {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.fp-wishlist-search {
|
||||
flex: 1 1 220px;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.fp-wish-items {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
||||
@@ -283,6 +283,125 @@ public sealed class MealTests(AppFixture fixture)
|
||||
(await admin.PostAsJsonAsync($"/api/meals/{Guid.NewGuid()}/to-shopping", new IngredientsToShoppingRequest(list.Id), Ct)).StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<List<WishlistDishDto>> WishlistAsync(HttpClient client, string? query = null, string? sort = null) =>
|
||||
(await client.GetFromJsonAsync<List<WishlistDishDto>>(
|
||||
$"/api/meals/wishlist?q={Uri.EscapeDataString(query ?? string.Empty)}&sort={sort}", Ct))!;
|
||||
|
||||
[Fact]
|
||||
public async Task Wishes_and_planned_dishes_land_in_a_searchable_deduplicated_wishlist()
|
||||
{
|
||||
using var family = (await fixture.CreateSignedInUserAsync(Roles.Family, Ct)).Client;
|
||||
var (childUser, child) = await fixture.CreateSignedInUserAsync(Roles.Child, Ct);
|
||||
using var _ = child;
|
||||
var day = NewWeek();
|
||||
var key = Guid.NewGuid().ToString("N")[..8];
|
||||
var dish = $"Wunsch{key}";
|
||||
|
||||
// Wunsch legt das Gericht an; erneuter Wunsch (andere Schreibweise) dedupliziert und zählt hoch
|
||||
Assert.Equal(HttpStatusCode.Created, (await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest(dish, "mit Soße"), Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.Created, (await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest($" {dish.ToUpperInvariant()} "), Ct)).StatusCode);
|
||||
|
||||
var listed = Assert.Single(await WishlistAsync(child, key));
|
||||
Assert.Equal((dish, "mit Soße", 2, 0, childUser.Id, childUser.DisplayName), (listed.Name, listed.Note, listed.WishCount, listed.UseCount, listed.WishedBy!.Value, listed.WishedByName));
|
||||
Assert.Null(listed.LastPlannedAt);
|
||||
Assert.Equal(listed.Id, Assert.Single(await WishlistAsync(family, key.ToUpperInvariant())).Id);
|
||||
|
||||
// Planen: Zähler und letztes Plandatum; Platz belegt → nur mit Ersetzen; ungültige Mahlzeit → 400
|
||||
var planned = await family.PostAsJsonAsync($"/api/meals/wishlist/{listed.Id}/plan", new PlanWishlistDishRequest(day, Lunch), Ct);
|
||||
Assert.Equal(HttpStatusCode.OK, planned.StatusCode);
|
||||
var entry = (await planned.Content.ReadFromJsonAsync<MealPlanEntryDto>(Ct))!;
|
||||
Assert.Equal((dish, "mit Soße", day, Lunch), (entry.DishName, entry.Note, entry.Date, entry.MealType));
|
||||
var after = Assert.Single(await WishlistAsync(family, key));
|
||||
Assert.Equal(1, after.UseCount);
|
||||
Assert.NotNull(after.LastPlannedAt);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict,
|
||||
(await family.PostAsJsonAsync($"/api/meals/wishlist/{listed.Id}/plan", new PlanWishlistDishRequest(day, Lunch), Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.BadRequest,
|
||||
(await family.PostAsJsonAsync($"/api/meals/wishlist/{listed.Id}/plan", new PlanWishlistDishRequest(day, "Mitternacht"), Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NotFound,
|
||||
(await family.PostAsJsonAsync($"/api/meals/wishlist/{Guid.NewGuid()}/plan", new PlanWishlistDishRequest(day, Dinner), Ct)).StatusCode);
|
||||
var replaced = await family.PostAsJsonAsync($"/api/meals/wishlist/{listed.Id}/plan", new PlanWishlistDishRequest(day, Lunch, Replace: true), Ct);
|
||||
Assert.Equal(HttpStatusCode.OK, replaced.StatusCode);
|
||||
Assert.Equal(2, Assert.Single(await WishlistAsync(family, key)).UseCount);
|
||||
|
||||
// Direkt geplante Gerichte erscheinen ebenfalls; Sortierung nach Häufigkeit und Name
|
||||
var other = $"Direkt{key}";
|
||||
await CreateAsync(family, day, Dinner, other);
|
||||
var byName = (await WishlistAsync(family, key, "name")).Select(d => d.Name).ToList();
|
||||
Assert.Equal([other, dish], byName);
|
||||
Assert.Equal(dish, (await WishlistAsync(family, key, "mostPlanned"))[0].Name);
|
||||
Assert.Equal(2, (await WishlistAsync(family, key, "lastPlanned")).Count);
|
||||
Assert.Equal(2, (await WishlistAsync(family, key, "nonsense")).Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Wishlist_rights_are_enforced_per_object()
|
||||
{
|
||||
using var family = (await fixture.CreateSignedInUserAsync(Roles.Family, Ct)).Client;
|
||||
var (_, child) = await fixture.CreateSignedInUserAsync(Roles.Child, Ct);
|
||||
var (_, sibling) = await fixture.CreateSignedInUserAsync(Roles.Child, Ct);
|
||||
var (_, guest) = await fixture.CreateSignedInUserAsync(Roles.Guest, Ct);
|
||||
using var _ = child;
|
||||
using var __ = sibling;
|
||||
using var ___ = guest;
|
||||
var day = NewWeek();
|
||||
var key = Guid.NewGuid().ToString("N")[..8];
|
||||
|
||||
await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest($"Eigen{key}"), Ct);
|
||||
await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest($"Geplant{key}"), Ct);
|
||||
var own = Assert.Single(await WishlistAsync(child, $"eigen{key}"));
|
||||
var planned = Assert.Single(await WishlistAsync(child, $"geplant{key}"));
|
||||
|
||||
// Gäste sehen nichts, Kinder planen nicht
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await guest.GetAsync("/api/meals/wishlist", Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await guest.DeleteAsync($"/api/meals/wishlist/{own.Id}", Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.Forbidden,
|
||||
(await child.PostAsJsonAsync($"/api/meals/wishlist/{own.Id}/plan", new PlanWishlistDishRequest(day, Lunch), Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.Forbidden,
|
||||
(await guest.PostAsJsonAsync($"/api/meals/wishlist/{own.Id}/plan", new PlanWishlistDishRequest(day, Lunch), Ct)).StatusCode);
|
||||
Assert.Equal(2, (await WishlistAsync(sibling, key)).Count);
|
||||
|
||||
// Löschen: Geschwister nicht fremde, Kind eigene nur ohne Planung, Familie alles
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await sibling.DeleteAsync($"/api/meals/wishlist/{own.Id}", Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.OK, (await family.PostAsJsonAsync($"/api/meals/wishlist/{planned.Id}/plan", new PlanWishlistDishRequest(day, Lunch), Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await child.DeleteAsync($"/api/meals/wishlist/{planned.Id}", Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, (await child.DeleteAsync($"/api/meals/wishlist/{own.Id}", Ct)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, (await child.DeleteAsync($"/api/meals/wishlist/{own.Id}", Ct)).StatusCode); // idempotent
|
||||
Assert.Equal(HttpStatusCode.NoContent, (await family.DeleteAsync($"/api/meals/wishlist/{planned.Id}", Ct)).StatusCode);
|
||||
Assert.Empty(await WishlistAsync(family, key));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Deleted_wishlist_dish_is_reactivated_by_a_new_wish_or_plan()
|
||||
{
|
||||
using var family = (await fixture.CreateSignedInUserAsync(Roles.Family, Ct)).Client;
|
||||
var (_, child) = await fixture.CreateSignedInUserAsync(Roles.Child, Ct);
|
||||
using var _ = child;
|
||||
var day = NewWeek();
|
||||
var key = Guid.NewGuid().ToString("N")[..8];
|
||||
var dish = $"Zurück{key}";
|
||||
|
||||
await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest(dish), Ct);
|
||||
var first = Assert.Single(await WishlistAsync(family, key));
|
||||
Assert.Equal(HttpStatusCode.NoContent, (await family.DeleteAsync($"/api/meals/wishlist/{first.Id}", Ct)).StatusCode);
|
||||
Assert.Empty(await WishlistAsync(family, key));
|
||||
Assert.Empty((await family.GetFromJsonAsync<List<DishSuggestionDto>>($"/api/meals/dishes?q={key}", Ct))!);
|
||||
|
||||
// Erneuter Wunsch: dasselbe Gericht (gleiche Id) ist wieder da – kein Duplikat, kein Index-Fehler
|
||||
await child.PostAsJsonAsync("/api/meals/wishes", new CreateMealWishRequest(dish.ToUpperInvariant()), Ct);
|
||||
var again = Assert.Single(await WishlistAsync(family, key));
|
||||
Assert.Equal(first.Id, again.Id);
|
||||
|
||||
// Löschen und über den Plan reaktivieren
|
||||
await family.DeleteAsync($"/api/meals/wishlist/{first.Id}", Ct);
|
||||
Assert.Empty(await WishlistAsync(family, key));
|
||||
await CreateAsync(family, day, Lunch, dish);
|
||||
var planned = Assert.Single(await WishlistAsync(family, key));
|
||||
Assert.Equal((first.Id, 1), (planned.Id, planned.UseCount));
|
||||
Assert.NotNull(planned.LastPlannedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Changes_are_broadcast_via_signalr()
|
||||
{
|
||||
|
||||
@@ -169,4 +169,49 @@ public sealed class MealPlanRulesTests
|
||||
Assert.True(MealPlanRules.CanDeleteWish(AccessLevel.Full, sibling, MealWishStatus.Rejected, child));
|
||||
Assert.False(MealPlanRules.CanDeleteWish(AccessLevel.None, child, MealWishStatus.Open, child));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Wishlist_is_visible_to_family_and_children_but_not_to_guests()
|
||||
{
|
||||
Assert.True(MealPlanRules.CanSeeWishlist(AccessLevel.Full));
|
||||
Assert.True(MealPlanRules.CanSeeWishlist(AccessLevel.Restricted));
|
||||
Assert.False(MealPlanRules.CanSeeWishlist(AccessLevel.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Child_deletes_only_own_wishlist_entries_that_were_never_planned()
|
||||
{
|
||||
var child = Guid.NewGuid();
|
||||
var sibling = Guid.NewGuid();
|
||||
|
||||
Assert.True(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Restricted, child, wasPlanned: false, child));
|
||||
Assert.False(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Restricted, child, wasPlanned: true, child));
|
||||
Assert.False(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Restricted, sibling, wasPlanned: false, child));
|
||||
Assert.False(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Restricted, null, wasPlanned: false, child));
|
||||
Assert.True(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Full, sibling, wasPlanned: true, child));
|
||||
Assert.True(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.Full, null, wasPlanned: false, child));
|
||||
Assert.False(MealPlanRules.CanDeleteWishlistEntry(AccessLevel.None, child, wasPlanned: false, child));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, WishlistSort.LastPlanned)]
|
||||
[InlineData("", WishlistSort.LastPlanned)]
|
||||
[InlineData("unbekannt", WishlistSort.LastPlanned)]
|
||||
[InlineData("mostPlanned", WishlistSort.MostPlanned)]
|
||||
[InlineData(" MOSTPLANNED ", WishlistSort.MostPlanned)]
|
||||
[InlineData("name", WishlistSort.Name)]
|
||||
public void Wishlist_sort_falls_back_to_last_planned(string? input, string expected) =>
|
||||
Assert.Equal(expected, MealPlanRules.NormalizeWishlistSort(input));
|
||||
|
||||
[Fact]
|
||||
public void Deleted_dish_is_restored_when_wished_or_planned_again()
|
||||
{
|
||||
var dish = new Dish { Name = "Pizza", IsDeleted = true, DeletedAt = DateTimeOffset.UtcNow, DeletedBy = Guid.NewGuid() };
|
||||
|
||||
dish.Restore();
|
||||
|
||||
Assert.False(dish.IsDeleted);
|
||||
Assert.Null(dish.DeletedAt);
|
||||
Assert.Null(dish.DeletedBy);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,18 @@ public sealed class MealValidatorTests
|
||||
Assert.False(new AcceptMealWishRequestValidator().Validate(new AcceptMealWishRequest(Day, new string('x', 41))).IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Planning_a_wishlist_dish_needs_day_and_meal()
|
||||
{
|
||||
var validator = new PlanWishlistDishRequestValidator();
|
||||
|
||||
Assert.True(validator.Validate(new PlanWishlistDishRequest(Day, "Mittagessen")).IsValid);
|
||||
Assert.True(validator.Validate(new PlanWishlistDishRequest(Day, "Mittagessen", Replace: true)).IsValid);
|
||||
Assert.False(validator.Validate(new PlanWishlistDishRequest(default, "Mittagessen")).IsValid);
|
||||
Assert.False(validator.Validate(new PlanWishlistDishRequest(Day, " ")).IsValid);
|
||||
Assert.False(validator.Validate(new PlanWishlistDishRequest(Day, new string('x', MealPlanEntry.MealTypeMaxLength + 1))).IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingredient_transfer_limits_the_number_of_ingredients()
|
||||
{
|
||||
|
||||
@@ -28,8 +28,19 @@ internal sealed class FakeMealsApi : IMealsApi
|
||||
new("Pfannkuchen", null, [], 3),
|
||||
];
|
||||
|
||||
public List<WishlistDishDto> Wishlist { get; } = [];
|
||||
|
||||
public List<(string? Query, string Sort)> WishlistQueries { get; } = [];
|
||||
|
||||
private int _version;
|
||||
|
||||
public WishlistDishDto SeedDish(string name, int used = 0, int wished = 0, DateTimeOffset? lastPlanned = null, Guid? wishedBy = null, string? wishedByName = null)
|
||||
{
|
||||
var dish = new WishlistDishDto(Guid.NewGuid(), name, null, null, [], wished, used, lastPlanned, wishedBy, wishedByName);
|
||||
Wishlist.Add(dish);
|
||||
return dish;
|
||||
}
|
||||
|
||||
public MealPlanEntryDto Seed(DateOnly date, string mealType, string dish, IReadOnlyList<string>? ingredients = null, string? note = null) =>
|
||||
Add(new MealPlanEntryDto(Guid.NewGuid(), date, mealType, dish, note, null, ingredients ?? [], null, null, DateTimeOffset.UtcNow, NextVersion()));
|
||||
|
||||
@@ -131,6 +142,36 @@ internal sealed class FakeMealsApi : IMealsApi
|
||||
|
||||
public Task<ApiResult<bool>> DeleteWishAsync(Guid id, CancellationToken ct = default) => Ok(Wishes.RemoveAll(w => w.Id == id) >= 0);
|
||||
|
||||
public Task<ApiResult<List<WishlistDishDto>>> GetWishlistAsync(string? query, string sort, CancellationToken ct = default)
|
||||
{
|
||||
WishlistQueries.Add((query, sort));
|
||||
var items = Wishlist.Where(d => string.IsNullOrWhiteSpace(query) || d.Name.Contains(query.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
var ordered = sort switch
|
||||
{
|
||||
WishlistSort.MostPlanned => items.OrderByDescending(d => d.UseCount),
|
||||
WishlistSort.Name => items.OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase),
|
||||
_ => items.OrderByDescending(d => d.LastPlannedAt),
|
||||
};
|
||||
return Ok(ordered.ToList());
|
||||
}
|
||||
|
||||
public Task<ApiResult<MealPlanEntryDto>> PlanWishlistDishAsync(Guid id, PlanWishlistDishRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var dish = Wishlist.Single(d => d.Id == id);
|
||||
if (Entries.Any(e => e.Date == request.Date && e.MealType == request.MealType) && !request.Replace)
|
||||
{
|
||||
return Task.FromResult(ApiResult<MealPlanEntryDto>.Fail(new ApiProblem { Status = 409, Detail = "Platz belegt." }));
|
||||
}
|
||||
|
||||
Entries.RemoveAll(e => e.Date == request.Date && e.MealType == request.MealType);
|
||||
var entry = Add(new MealPlanEntryDto(Guid.NewGuid(), request.Date, request.MealType, dish.Name, dish.Note, dish.RecipeUrl, dish.Ingredients,
|
||||
null, null, DateTimeOffset.UtcNow, NextVersion()));
|
||||
Wishlist[Wishlist.IndexOf(dish)] = dish with { UseCount = dish.UseCount + 1, LastPlannedAt = DateTimeOffset.UtcNow };
|
||||
return Ok(entry);
|
||||
}
|
||||
|
||||
public Task<ApiResult<bool>> DeleteWishlistDishAsync(Guid id, CancellationToken ct = default) => Ok(Wishlist.RemoveAll(d => d.Id == id) >= 0);
|
||||
|
||||
private MealPlanEntryDto Add(MealPlanEntryDto entry)
|
||||
{
|
||||
Entries.Add(entry);
|
||||
|
||||
@@ -300,6 +300,113 @@ public sealed class MealsPageTests : BunitContext
|
||||
cut.WaitForAssertion(() => Assert.Empty(cut.FindAll(".fp-meal-list .fp-meal-dish")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Wishlist_shows_dishes_with_planning_history()
|
||||
{
|
||||
_api.SeedDish("Pizza", used: 3, wished: 2, lastPlanned: new DateTimeOffset(2026, 10, 5, 12, 0, 0, TimeSpan.Zero), wishedByName: "Anna");
|
||||
_api.SeedDish("Gulasch");
|
||||
SignInAs(Roles.Family);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
Assert.Equal(2, cut.FindAll(".fp-wishlist-item").Count);
|
||||
var pizza = cut.FindAll(".fp-wishlist-item").Single(i => i.QuerySelector(".fp-wishlist-name")!.TextContent == "Pizza");
|
||||
Assert.Contains("zuletzt geplant am 05.10.2026", pizza.QuerySelector(".fp-wishlist-meta")!.TextContent);
|
||||
Assert.Contains("3-mal geplant", pizza.QuerySelector(".fp-wishlist-meta")!.TextContent);
|
||||
Assert.Contains("2-mal gewünscht (zuletzt Anna)", pizza.QuerySelector(".fp-wishlist-meta")!.TextContent);
|
||||
var gulasch = cut.FindAll(".fp-wishlist-item").Single(i => i.QuerySelector(".fp-wishlist-name")!.TextContent == "Gulasch");
|
||||
Assert.Contains("noch nie geplant", gulasch.QuerySelector(".fp-wishlist-meta")!.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Wishlist_is_searched_and_sorted_through_the_api()
|
||||
{
|
||||
_api.SeedDish("Pizza", used: 1);
|
||||
_api.SeedDish("Nudeln", used: 5);
|
||||
_api.SeedDish("Pizzabrötchen", used: 2);
|
||||
SignInAs(Roles.Family);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
cut.Find("#wishlist-search").Change("pizza");
|
||||
cut.WaitForAssertion(() => Assert.Equal(["Pizza", "Pizzabrötchen"], cut.FindAll(".fp-wishlist-name").Select(n => n.TextContent).Order()));
|
||||
Assert.Equal(("pizza", WishlistSort.LastPlanned), _api.WishlistQueries[^1]);
|
||||
|
||||
cut.Find(".fp-wishlist-sort-mostPlanned").Click();
|
||||
cut.WaitForAssertion(() => Assert.Equal(["Pizzabrötchen", "Pizza"], cut.FindAll(".fp-wishlist-name").Select(n => n.TextContent)));
|
||||
|
||||
cut.Find("#wishlist-search").Change("kuchen");
|
||||
cut.WaitForAssertion(() => Assert.Contains("Kein Gericht gefunden.", cut.Find(".fp-wishlist-empty").TextContent));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Family_plans_a_wishlist_dish_into_a_free_slot()
|
||||
{
|
||||
var dish = _api.SeedDish("Pizza");
|
||||
SignInAs(Roles.Family);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
cut.Find(".fp-wishlist-plan").Click();
|
||||
cut.WaitForElement(Slot(Today.AddDays(1), "Abendessen")).Click();
|
||||
|
||||
cut.WaitForAssertion(() => Assert.Equal("Pizza", cut.Find(".fp-meal-list .fp-meal-dish").TextContent));
|
||||
var entry = Assert.Single(_api.Entries);
|
||||
Assert.Equal((Today.AddDays(1), "Abendessen", "Pizza"), (entry.Date, entry.MealType, entry.DishName));
|
||||
cut.WaitForAssertion(() => Assert.Contains("1-mal geplant", cut.Find(".fp-wishlist-meta").TextContent));
|
||||
Assert.Equal(dish.Id, _api.Wishlist.Single().Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Planning_a_wishlist_dish_into_an_occupied_slot_asks_before_replacing()
|
||||
{
|
||||
_api.Seed(Today, "Mittagessen", "Linsen");
|
||||
_api.SeedDish("Pizza");
|
||||
SignInAs(Roles.Admin);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
cut.Find(".fp-wishlist-plan").Click();
|
||||
cut.WaitForElement(Slot(Today, "Mittagessen")).Click();
|
||||
cut.WaitForElement(".fp-accept-replace");
|
||||
Assert.Contains("Linsen", cut.Find(".fp-accept-replace").TextContent);
|
||||
Assert.Equal("Linsen", Assert.Single(_api.Entries).DishName);
|
||||
|
||||
cut.Find(".fp-accept-replace-confirm").Click();
|
||||
|
||||
cut.WaitForAssertion(() => Assert.Equal("Pizza", Assert.Single(_api.Entries).DishName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Child_sees_the_wishlist_but_cannot_plan_and_deletes_only_own_unplanned_dishes()
|
||||
{
|
||||
_api.SeedDish("Eigenes", wishedBy: UserId);
|
||||
_api.SeedDish("Geplantes", used: 2, wishedBy: UserId, lastPlanned: DateTimeOffset.UtcNow);
|
||||
_api.SeedDish("Fremdes", wishedBy: Guid.NewGuid());
|
||||
SignInAs(Roles.Child);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
Assert.Equal(3, cut.FindAll(".fp-wishlist-item").Count);
|
||||
Assert.Empty(cut.FindAll(".fp-wishlist-plan"));
|
||||
var deletable = cut.FindAll(".fp-wishlist-item").Where(i => i.QuerySelector(".fp-wishlist-delete") is not null).ToList();
|
||||
Assert.Equal("Eigenes", Assert.Single(deletable).QuerySelector(".fp-wishlist-name")!.TextContent);
|
||||
|
||||
cut.Find(".fp-wishlist-delete").Click();
|
||||
|
||||
cut.WaitForAssertion(() => Assert.DoesNotContain(_api.Wishlist, d => d.Name == "Eigenes"));
|
||||
Assert.Equal(2, cut.FindAll(".fp-wishlist-item").Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Family_deletes_any_wishlist_dish()
|
||||
{
|
||||
_api.SeedDish("Pizza", used: 4, lastPlanned: DateTimeOffset.UtcNow, wishedBy: Guid.NewGuid());
|
||||
SignInAs(Roles.Family);
|
||||
var cut = Render<MealsPageHost>();
|
||||
|
||||
cut.Find(".fp-wishlist-delete").Click();
|
||||
|
||||
cut.WaitForAssertion(() => Assert.Empty(cut.FindAll(".fp-wishlist-item")));
|
||||
Assert.Empty(_api.Wishlist);
|
||||
Assert.Contains("Die Wunschliste ist noch leer.", cut.Find(".fp-wishlist-empty").TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Navigation_moves_by_plan_length()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user