Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@
"preview": "astro preview",
"prepare": "husky",
"test": "vitest --run",

"test:coverage": "vitest run --coverage",

"lint": "prettier --write \"**/*.{js,jsx,ts,tsx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\""
},
"dependencies": {
"@astrojs/check": "^0.9.2",
"astro": "^4.16.18",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.13.1",
"typescript": "^5.5.4"
},
"devDependencies": {
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.11.1",
"@types/jsdom": "^21.1.7",
"@typescript-eslint/parser": "^8.8.0",
"@vitest/coverage-v8": "^2.1.9",
"eslint": "^9.11.1",
Expand Down
29 changes: 29 additions & 0 deletions src/components/CardList.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,37 @@
import type { CardProps } from "../types"

const { terms: cards } = Astro.props

const paginateOptions = [
{ label: "6", value: 6 },
{ label: "12", value: 12 },
{ label: "24", value: 24 },
{ label: "48", value: 48 }
]
---

<div class="pagination-bar">
<div class="pagination-filter">
<select class="select" id="pagination-select">
{
paginateOptions.map((option) => (
<option value={option.value} id={`paginate-${option.label}`}>
{option.label}
</option>
))
}
</select>
</div>

<button class="button navigation-button" id="prev-button">
<i class="bi bi-arrow-left-circle"></i>
</button>

<button class="button navigation-button" id="next-button">
<i class="bi bi-arrow-right-circle"></i>
</button>
</div>

<div class="card-container" id="cardContainer">
{
cards.map((card: CardProps) => (
Expand Down
121 changes: 120 additions & 1 deletion src/components/CardList.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { experimental_AstroContainer as AstroContainer } from "astro/container"
import { expect, test } from "vitest"
import { expect, test, describe, beforeEach, vi } from "vitest"
import CardList from "./CardList.astro"

Check failure on line 3 in src/components/CardList.test.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Cannot find module './CardList.astro' or its corresponding type declarations.

Check failure on line 3 in src/components/CardList.test.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Cannot find module './CardList.astro' or its corresponding type declarations.

describe("component UI display", () => {
let container: AstroContainer
Expand Down Expand Up @@ -78,3 +78,122 @@
})
})

describe("pagination functionality", () => {
let container: AstroContainer

beforeEach(async () => {
container = await AstroContainer.create()
})

test("renders pagination controls", async () => {
const mockCards = Array.from({ length: 10 }, (_, i) => ({
data: {
title: `Term ${i + 1}`,
subtext: `Subtext ${i + 1}`,
categories: ["Test"],
author: "Test Author",
description: {
title: `Term ${i + 1}`,
texts: [`Description for term ${i + 1}`],
image: "",
references: ["https://example.com"]
}
}
}))

const result = await container.renderToString(CardList, {
props: { terms: mockCards }
})

// Check pagination controls are rendered
expect(result).toContain('id="pagination-select"')
expect(result).toContain('id="prev-button"')
expect(result).toContain('id="next-button"')
expect(result).toContain('class="pagination-bar"')
})

test("renders pagination dropdown options", async () => {
const mockCards = [{
data: {
title: "Test Term",
subtext: "Test Subtext",
categories: ["Test"],
author: "Test Author",
description: {
title: "Test Term",
texts: ["Test description"],
image: "",
references: ["https://example.com"]
}
}
}]

const result = await container.renderToString(CardList, {
props: { terms: mockCards }
})

// Check all pagination options are present
expect(result).toContain('value="6"')
expect(result).toContain('value="12"')
expect(result).toContain('value="24"')
expect(result).toContain('value="48"')
expect(result).toContain('id="paginate-6"')
expect(result).toContain('id="paginate-12"')
expect(result).toContain('id="paginate-24"')
expect(result).toContain('id="paginate-48"')
})

test("renders all cards in container", async () => {
const mockCards = Array.from({ length: 8 }, (_, i) => ({
data: {
title: `Card ${i + 1}`,
subtext: `Subtext ${i + 1}`,
categories: ["Test"],
author: "Test Author",
description: {
title: `Card ${i + 1}`,
texts: [`Description ${i + 1}`],
image: "",
references: ["https://example.com"]
}
}
}))

const result = await container.renderToString(CardList, {
props: { terms: mockCards }
})

// Check that all cards are rendered (pagination logic happens in browser)
for (let i = 1; i <= 8; i++) {
expect(result).toContain(`Card ${i}`)
}
expect(result).toContain('id="cardContainer"')
})

test("renders navigation buttons with correct icons", async () => {
const mockCards = [{
data: {
title: "Test Term",
subtext: "Test Subtext",
categories: ["Test"],
author: "Test Author",
description: {
title: "Test Term",
texts: ["Test description"],
image: "",
references: ["https://example.com"]
}
}
}]

const result = await container.renderToString(CardList, {
props: { terms: mockCards }
})

// Check navigation buttons have correct Bootstrap icons
expect(result).toContain('class="bi bi-arrow-left-circle"')
expect(result).toContain('class="bi bi-arrow-right-circle"')
expect(result).toContain('class="button navigation-button"')
})
})

3 changes: 3 additions & 0 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
---
import "bootstrap-icons/font/bootstrap-icons.css"

import BaseLayout from "../layouts/BaseLayout.astro"
import Hero from "../components/Hero.astro"
import CardList from "../components/CardList.astro"
Expand Down Expand Up @@ -46,3 +48,4 @@ const terms = await getCollection("terms")
<script src="../scripts/cards.ts"></script>
<script src="../scripts/modal.ts"></script>
<script src="../scripts/hero-action.ts"></script>
<script src="../scripts/paginate.ts"></script>
17 changes: 15 additions & 2 deletions src/scripts/hero-action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Hero extends HTMLElement {

constructor() {
super()

Expand Down Expand Up @@ -29,18 +29,27 @@ class Hero extends HTMLElement {
searchInput.value = ""

if (selectedCategory === "") {
cards.forEach((card: HTMLElement) => (card.style.display = ""))
cards.forEach((card: HTMLElement) => {
card.style.display = ""
card.removeAttribute('data-hidden-by-filter')
})
// Trigger pagination refresh
window.dispatchEvent(new CustomEvent('cardsFiltered'))
return
}
for (let i = 0; i < cards.length; i++) {
const cardCategories =
cards[i]!.getAttribute("data-categories")!.split(", ")
if (cardCategories.includes(selectedCategory)) {
;(cards[i] as HTMLElement).style.display = ""
;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
} else {
;(cards[i] as HTMLElement).style.display = "none"
;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
}
}
// Trigger pagination refresh
window.dispatchEvent(new CustomEvent('cardsFiltered'))
})

let currentFocus = -1 // Track the currently focused item in the autocomplete list
Expand Down Expand Up @@ -168,10 +177,14 @@ class Hero extends HTMLElement {
filterByCategory(selectedCategory, cardCategories)
) {
;(cards[i] as HTMLElement).style.display = ""
;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
} else {
;(cards[i] as HTMLElement).style.display = "none"
;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
}
}
// Trigger pagination refresh
window.dispatchEvent(new CustomEvent('cardsFiltered'))
}
}
}
Expand Down
Loading
Loading