Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion e2e/BrowseItemTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,33 @@ test('Browse Items', async ({ page }) => {

//Expect
await expect(page.getByRole('heading', { name: 'Adventurer GPS Watch' })).toBeVisible();
});
});

test('Catalog filters update results and reset pagination', async ({ page }) => {
await page.goto('/');

await expect(page.getByRole('heading', { name: 'Ready for a new adventure?' })).toBeVisible();

await page.getByRole('link', { name: '2', exact: true }).click();
await expect(page).toHaveURL(/page=2/);

await page.getByRole('link', { name: 'WildRunner' }).click();
await expect(page).toHaveURL(/brand=3/);
await expect(page).not.toHaveURL(/page=2/);
await expect(page.locator('.catalog-product')).toHaveCount(9);

await page.getByRole('link', { name: 'Ski/boarding' }).click();
await expect(page).toHaveURL(/brand=3/);
await expect(page).toHaveURL(/type=3/);
await expect(page.locator('.catalog-product')).toHaveCount(4);
await expect(page.getByRole('link', { name: 'Alpine Fusion Goggles' })).toBeVisible();
await expect(page.getByRole('link', { name: 'SummitRider Snowboard Boots' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Omni-Snow Dual Snowboard' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Maverick Pro Ski Goggles' })).toBeVisible();

const typeFilters = page.locator('.catalog-search-group').filter({ has: page.getByRole('heading', { name: 'Type' }) });
await typeFilters.getByRole('link', { name: 'All' }).click();
await expect(page).toHaveURL(/brand=3/);
await expect(page).not.toHaveURL(/type=3/);
await expect(page.locator('.catalog-product')).toHaveCount(9);
});
4 changes: 2 additions & 2 deletions src/WebApp/Components/Pages/Catalog/Catalog.razor
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
static IEnumerable<int> GetVisiblePageIndexes(CatalogResult result)
=> Enumerable.Range(1, (int)Math.Ceiling(1.0 * result.Count / PageSize));

protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
catalogResult = await CatalogService.GetCatalogItems(
Page.GetValueOrDefault(1) - 1,
Math.Max(Page.GetValueOrDefault(1), 1) - 1,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overlapping catalog load requests

Medium Severity

Moving catalog loading into OnParametersSetAsync without clearing catalogResult or canceling earlier calls means each query change starts another GetCatalogItems while the UI still shows the previous page and pagination. If requests finish out of order, the grid can disagree with the current page, brand, and type query string—the inconsistency this change targets.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 79b7693. Configure here.

PageSize,
BrandId,
ItemTypeId);
Expand Down
50 changes: 50 additions & 0 deletions tests/Catalog.FunctionalTests/CatalogApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,56 @@ public async Task GetCatalogItemWithTypeIdBrandId(double version)
Assert.Equal(3, result.Data.ToList().FirstOrDefault().CatalogBrandId);
}

[Theory]
[InlineData(1.0)]
[InlineData(2.0)]
public async Task GetCatalogItemsWithTypeId(double version)
{
var _httpClient = CreateHttpClient(new ApiVersion(version));

// Act
var response = version switch
{
1.0 => await _httpClient.GetAsync("api/catalog/items/type/3/brand?PageSize=5&PageIndex=0", TestContext.Current.CancellationToken),
2.0 => await _httpClient.GetAsync("api/catalog/items?type=3&PageSize=5&PageIndex=0", TestContext.Current.CancellationToken),
_ => throw new ArgumentOutOfRangeException(nameof(version), version, null)
};

// Arrange
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PaginatedItems<CatalogItem>>(body, _jsonSerializerOptions);

// Assert
Assert.NotNull(result.Data);
Assert.Equal(18, result.Count);
Assert.Equal(0, result.PageIndex);
Assert.Equal(5, result.PageSize);
Assert.All(result.Data, item => Assert.Equal(3, item.CatalogTypeId));
}

[Fact]
public async Task GetCatalogItemsWithBrandIdKeepsFilterOnLaterPages()
{
var _httpClient = CreateHttpClient(new ApiVersion(2.0));

// Act
var response = await _httpClient.GetAsync("api/catalog/items?brand=3&PageSize=5&PageIndex=1", TestContext.Current.CancellationToken);

// Arrange
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PaginatedItems<CatalogItem>>(body, _jsonSerializerOptions);

// Assert
Assert.NotNull(result.Data);
Assert.Equal(11, result.Count);
Assert.Equal(1, result.PageIndex);
Assert.Equal(5, result.PageSize);
Assert.Equal(5, result.Data.Count());
Assert.All(result.Data, item => Assert.Equal(3, item.CatalogBrandId));
}

[Theory]
[InlineData(1.0)]
[InlineData(2.0)]
Expand Down
Loading