Skip to content

Commit 46ab7fc

Browse files
committed
feat: update pagination logic on filtered cards as well
1 parent 229c2a9 commit 46ab7fc

5 files changed

Lines changed: 151 additions & 23 deletions

File tree

src/components/CardList.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const paginateOptions = [
5555
<!-- To-do -->
5656
<!--
5757
[x] Implement pagination feature with buttons and dropdowns (6, 12, 24)
58-
[ ] Implement unit test for pagination feature
58+
[x] Implement unit test for pagination feature
5959
[x] Migrate pagination feature to a new script (./scripts/pagination.ts)
6060
-->
6161

src/pages/index.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ const terms = await getCollection("terms")
4848
<script src="../scripts/cards.ts"></script>
4949
<script src="../scripts/modal.ts"></script>
5050
<script src="../scripts/hero-action.ts"></script>
51+
<script src="../scripts/paginate.ts"></script>

src/scripts/hero-action.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class Hero extends HTMLElement {
2-
2+
33
constructor() {
44
super()
55

@@ -29,18 +29,27 @@ class Hero extends HTMLElement {
2929
searchInput.value = ""
3030

3131
if (selectedCategory === "") {
32-
cards.forEach((card: HTMLElement) => (card.style.display = ""))
32+
cards.forEach((card: HTMLElement) => {
33+
card.style.display = ""
34+
card.removeAttribute('data-hidden-by-filter')
35+
})
36+
// Trigger pagination refresh
37+
window.dispatchEvent(new CustomEvent('cardsFiltered'))
3338
return
3439
}
3540
for (let i = 0; i < cards.length; i++) {
3641
const cardCategories =
3742
cards[i]!.getAttribute("data-categories")!.split(", ")
3843
if (cardCategories.includes(selectedCategory)) {
3944
;(cards[i] as HTMLElement).style.display = ""
45+
;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
4046
} else {
4147
;(cards[i] as HTMLElement).style.display = "none"
48+
;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
4249
}
4350
}
51+
// Trigger pagination refresh
52+
window.dispatchEvent(new CustomEvent('cardsFiltered'))
4453
})
4554

4655
let currentFocus = -1 // Track the currently focused item in the autocomplete list
@@ -168,10 +177,14 @@ class Hero extends HTMLElement {
168177
filterByCategory(selectedCategory, cardCategories)
169178
) {
170179
;(cards[i] as HTMLElement).style.display = ""
180+
;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
171181
} else {
172182
;(cards[i] as HTMLElement).style.display = "none"
183+
;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
173184
}
174185
}
186+
// Trigger pagination refresh
187+
window.dispatchEvent(new CustomEvent('cardsFiltered'))
175188
}
176189
}
177190
}

src/scripts/paginate.test.ts

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,44 @@ const setupDOM = (cardCount: number = 10) => {
2121
<button id="next-button">Next</button>
2222
</div>
2323
<div id="cardContainer">
24-
${Array.from({ length: cardCount }, (_, i) =>
24+
${Array.from({ length: cardCount }, (_, i) =>
2525
`<div class="card" data-title="Card ${i + 1}">Card ${i + 1}</div>`
2626
).join('')}
2727
</div>
2828
</body>
2929
</html>
30-
`)
30+
`, {
31+
url: "http://localhost", // Provide a URL to avoid localStorage issues
32+
})
3133

3234
global.document = dom.window.document
3335
global.window = dom.window as any
3436
global.Event = dom.window.Event
35-
}
36-
37-
// Simple PaginationManager class for testing (extracted from paginate.ts)
37+
global.CustomEvent = dom.window.CustomEvent
38+
39+
// Mock localStorage
40+
const localStorageMock = {
41+
getItem: vi.fn(),
42+
setItem: vi.fn(),
43+
removeItem: vi.fn(),
44+
clear: vi.fn(),
45+
length: 0,
46+
key: vi.fn()
47+
}
48+
Object.defineProperty(dom.window, 'localStorage', {
49+
value: localStorageMock,
50+
writable: true
51+
})
52+
global.localStorage = localStorageMock
53+
}// Simple PaginationManager class for testing (extracted from paginate.ts)
3854
class PaginationManager {
39-
private cards: HTMLElement[]
55+
private allCards: HTMLElement[]
4056
private currentPage: number = 1
4157
private itemsPerPage: number = 6
4258
private totalPages: number = 1
4359

4460
constructor() {
45-
this.cards = Array.from(document.querySelectorAll(".card"))
61+
this.allCards = Array.from(document.querySelectorAll(".card"))
4662
this.init()
4763
}
4864

@@ -58,6 +74,11 @@ class PaginationManager {
5874
const prevButton = document.getElementById("prev-button")
5975
const nextButton = document.getElementById("next-button")
6076

77+
// Listen for filter changes
78+
window.addEventListener('cardsFiltered', () => {
79+
setTimeout(() => this.refreshPagination(), 10)
80+
})
81+
6182
paginateSelect?.addEventListener("change", (e) => {
6283
const target = e.target as HTMLSelectElement
6384
this.itemsPerPage = parseInt(target.value)
@@ -84,19 +105,33 @@ class PaginationManager {
84105
})
85106
}
86107

108+
private refreshPagination() {
109+
this.currentPage = 1
110+
this.calculateTotalPages()
111+
this.showCurrentPage()
112+
this.updateButtonStates()
113+
}
114+
87115
private calculateTotalPages() {
88-
this.totalPages = Math.ceil(this.cards.length / this.itemsPerPage)
116+
const filteredCards = this.getFilteredCards()
117+
this.totalPages = Math.ceil(filteredCards.length / this.itemsPerPage)
118+
if (this.totalPages === 0) this.totalPages = 1
89119
}
90120

91121
private showCurrentPage() {
122+
const filteredCards = this.getFilteredCards()
92123
const startIndex = (this.currentPage - 1) * this.itemsPerPage
93124
const endIndex = startIndex + this.itemsPerPage
94125

95-
this.cards.forEach((card) => {
96-
card.style.display = "none"
126+
this.allCards.forEach((card) => {
127+
if (card.getAttribute('data-hidden-by-filter') === 'true') {
128+
card.style.display = "none"
129+
} else {
130+
card.style.display = "none"
131+
}
97132
})
98133

99-
this.cards.slice(startIndex, endIndex).forEach((card) => {
134+
filteredCards.slice(startIndex, endIndex).forEach((card) => {
100135
card.style.display = "block"
101136
})
102137
}
@@ -130,7 +165,13 @@ class PaginationManager {
130165
}
131166

132167
public getVisibleCards(): HTMLElement[] {
133-
return this.cards.filter(card => card.style.display !== "none")
168+
return this.allCards.filter(card => card.style.display !== "none")
169+
}
170+
171+
public getFilteredCards(): HTMLElement[] {
172+
return this.allCards.filter(card => {
173+
return card.getAttribute('data-hidden-by-filter') !== 'true'
174+
})
134175
}
135176
}
136177

@@ -268,4 +309,46 @@ describe("PaginationManager", () => {
268309
nextButton.click()
269310
expect(paginationManager.getCurrentPage()).toBe(1)
270311
})
312+
313+
test("works with filtered cards", () => {
314+
setupDOM(10)
315+
paginationManager = new PaginationManager()
316+
317+
// Simulate filtering by marking some cards as hidden by filter
318+
const cards = Array.from(document.querySelectorAll('.card'))
319+
if (cards.length >= 3) {
320+
cards[0]!.setAttribute('data-hidden-by-filter', 'true')
321+
cards[1]!.setAttribute('data-hidden-by-filter', 'true')
322+
cards[2]!.setAttribute('data-hidden-by-filter', 'true')
323+
}
324+
325+
// Trigger pagination refresh with custom event
326+
window.dispatchEvent(new CustomEvent('cardsFiltered'))
327+
328+
// Should now work with 7 visible cards instead of 10
329+
expect(paginationManager.getTotalPages()).toBe(2) // 7 cards / 6 per page = 2 pages
330+
331+
// First page should show 6 cards
332+
const visibleCards = paginationManager.getVisibleCards()
333+
expect(visibleCards).toHaveLength(6)
334+
})
335+
336+
test("resets to page 1 when filters change", async () => {
337+
setupDOM(15)
338+
paginationManager = new PaginationManager()
339+
340+
// Go to page 2
341+
const nextButton = document.getElementById("next-button") as HTMLButtonElement
342+
nextButton.click()
343+
expect(paginationManager.getCurrentPage()).toBe(2)
344+
345+
// Simulate filter change
346+
window.dispatchEvent(new CustomEvent('cardsFiltered'))
347+
348+
// Wait for the setTimeout in the event handler
349+
await new Promise(resolve => setTimeout(resolve, 20))
350+
351+
// Should reset to page 1
352+
expect(paginationManager.getCurrentPage()).toBe(1)
353+
})
271354
})

src/scripts/paginate.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
class PaginationManager {
2-
private cards: HTMLElement[]
2+
private allCards: HTMLElement[]
33
private currentPage: number = 1
44
private itemsPerPage: number = 6
55
private totalPages: number = 1
66

77
constructor() {
8-
this.cards = Array.from(document.querySelectorAll(".card"))
8+
this.allCards = Array.from(document.querySelectorAll(".card"))
99
this.init()
10+
this.observeFilterChanges()
1011
}
1112

1213
private init() {
@@ -16,6 +17,27 @@
1617
this.updateButtonStates()
1718
}
1819

20+
private observeFilterChanges() {
21+
// Listen for the custom event dispatched by hero-action.ts
22+
window.addEventListener('cardsFiltered', () => {
23+
setTimeout(() => this.refreshPagination(), 10)
24+
})
25+
}
26+
27+
private getFilteredCards(): HTMLElement[] {
28+
// Get cards that are not hidden by filtering
29+
return this.allCards.filter(card => {
30+
return card.getAttribute('data-hidden-by-filter') !== 'true'
31+
})
32+
}
33+
34+
private refreshPagination() {
35+
this.currentPage = 1 // Reset to first page when filters change
36+
this.calculateTotalPages()
37+
this.showCurrentPage()
38+
this.updateButtonStates()
39+
}
40+
1941
private setupEventListeners() {
2042
const paginateSelect = document.getElementById(
2143
"pagination-select"
@@ -53,20 +75,29 @@
5375
}
5476

5577
private calculateTotalPages() {
56-
this.totalPages = Math.ceil(this.cards.length / this.itemsPerPage)
78+
const filteredCards = this.getFilteredCards()
79+
this.totalPages = Math.ceil(filteredCards.length / this.itemsPerPage)
80+
if (this.totalPages === 0) this.totalPages = 1
5781
}
5882

5983
private showCurrentPage() {
84+
const filteredCards = this.getFilteredCards()
6085
const startIndex = (this.currentPage - 1) * this.itemsPerPage
6186
const endIndex = startIndex + this.itemsPerPage
6287

63-
// Hide all cards
64-
this.cards.forEach((card) => {
65-
card.style.display = "none"
88+
// First, hide all cards
89+
this.allCards.forEach((card) => {
90+
// If card is hidden by filter, keep it hidden
91+
if (card.getAttribute('data-hidden-by-filter') === 'true') {
92+
card.style.display = "none"
93+
} else {
94+
// For filtered cards, hide by default (pagination will show the correct ones)
95+
card.style.display = "none"
96+
}
6697
})
6798

68-
// Show cards for current page
69-
this.cards.slice(startIndex, endIndex).forEach((card) => {
99+
// Show only the cards for current page from filtered results
100+
filteredCards.slice(startIndex, endIndex).forEach((card) => {
70101
card.style.display = "block"
71102
})
72103
}

0 commit comments

Comments
 (0)