diff --git a/app/services-and-resources/tests/page.tsx b/app/services-and-resources/tests/page.tsx
index aea48b8..48867ce 100644
--- a/app/services-and-resources/tests/page.tsx
+++ b/app/services-and-resources/tests/page.tsx
@@ -2,14 +2,23 @@ import React from "react";
import HeroText from "../../../components/sections/HeroText";
import Divider from "../../../components/common/Divider";
import { KeyValueList } from "../../../components/common/KeyValueList";
+import { fetchTestsData } from "@/lib/tests";
-const testData = [
- { key: "ACT", values: ["240", "245", "247", "348", "349", "370"] },
- { key: "ANA", values: ["300", "301"] },
- { key: "APM", values: ["120", "130"] },
-];
+export async function generateStaticParams() {
+ try {
+ const tests = await fetchTestsData();
+ console.log(`Fetched ${tests.length} test courses at build time`);
+ return [{}];
+ } catch (error) {
+ console.error("Error in generateStaticParams:", error);
+ return [{}];
+ }
+}
+
+export default async function Tests() {
+ // Fetch tests data at build time
+ const testData = await fetchTestsData();
-const Tests = () => {
return (
@@ -26,7 +35,7 @@ const Tests = () => {
We are currently having a Test Drive in attempts to greatly expand our
test bank. We are having a draw for the students who donate tests we
- don’t currently have.{" "}
+ don't currently have.{" "}
Past tests can be submitted to the ASSU office.
@@ -51,6 +60,4 @@ const Tests = () => {
/>
);
-};
-
-export default Tests;
+}
diff --git a/lib/tests.ts b/lib/tests.ts
new file mode 100644
index 0000000..9a06a53
--- /dev/null
+++ b/lib/tests.ts
@@ -0,0 +1,173 @@
+// Import cheerio only on server side
+import * as cheerio from "cheerio";
+
+// Define the data shape for test courses
+export interface TestCourse {
+ key: string;
+ values: string[];
+}
+
+// Define the WordPress API response structure
+interface WordPressPage {
+ id: number;
+ date: string;
+ slug: string;
+ status: string;
+ title: {
+ rendered: string;
+ };
+ content: {
+ rendered: string;
+ };
+}
+
+// Function to clean and normalize text
+function cleanText(text: string): string {
+ return text
+ .trim()
+ .replace(/\s+/g, " ") // Replace multiple spaces with single space
+ .replace(/\n+/g, " ") // Replace newlines with spaces
+ .replace(/ /g, " ") // Replace HTML non-breaking spaces
+ .replace(/&/g, "&") // Replace HTML entities
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, '"')
+ .replace(/'/g, "'");
+}
+
+// Function to parse course numbers from text
+function parseCourseNumbers(text: string): string[] {
+ // Split by commas first
+ const parts = text.split(",");
+ const numbers: string[] = [];
+
+ for (const part of parts) {
+ const cleaned = cleanText(part);
+ if (!cleaned) continue;
+
+ // Handle slashes (e.g., "221/225" → ["221", "225"])
+ if (cleaned.includes("/")) {
+ const slashParts = cleaned.split("/");
+ for (const slashPart of slashParts) {
+ const num = cleanText(slashPart);
+ if (num) numbers.push(num);
+ }
+ } else {
+ numbers.push(cleaned);
+ }
+ }
+
+ return numbers;
+}
+
+// Main function to fetch and parse tests data
+export async function fetchTestsData(): Promise {
+ try {
+ // Only run on server side
+ if (typeof window !== "undefined") {
+ console.warn("fetchTestsData should only be called on server side");
+ return getFallbackTests();
+ }
+
+ console.log("Fetching tests data from WordPress API...");
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
+
+ const response = await fetch(
+ "https://assu.ca/wp/wp-json/wp/v2/pages?slug=past-test-library",
+ {
+ signal: controller.signal,
+ headers: {
+ "User-Agent": "ASSU-Website/1.0",
+ },
+ }
+ );
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data: WordPressPage[] = await response.json();
+ console.log("WordPress API response received:", data.length, "pages found");
+
+ if (!data || data.length === 0) {
+ console.warn("No past test library page found");
+ return getFallbackTests();
+ }
+
+ const page = data[0];
+ const content = page.content.rendered;
+
+ if (!content) {
+ console.warn("No content found in past test library page");
+ return getFallbackTests();
+ }
+
+ console.log("Parsing HTML content with Cheerio...");
+
+ // Parse HTML content with Cheerio
+ const $ = cheerio.load(content);
+ const tests: TestCourse[] = [];
+
+ // Find the paragraph containing course codes
+ // Look for paragraph with "ASSU TEST LIBRARY" or multiple tags
+ $("p").each((index: number, element) => {
+ const $element = $(element);
+ const strongTags = $element.find("strong");
+
+ // Process paragraphs that have multiple strong tags (likely the course list)
+ if (strongTags.length > 1) {
+ // Get the HTML content to preserve structure
+ const html = $element.html() || "";
+
+ // Use regex to find all patterns: CODE numbers
+ // Match: CODE followed by numbers, commas, slashes, spaces, and
+ const pattern = /([A-Z]{3,4})<\/strong>\s*([^<]+?)(?=|
|$)/g;
+ let match;
+
+ while ((match = pattern.exec(html)) !== null) {
+ const courseCode = cleanText(match[1]);
+ let courseNumbersText = match[2];
+
+ // Clean up the course numbers text (remove
tags and extra whitespace)
+ courseNumbersText = courseNumbersText
+ .replace(/
/gi, " ")
+ .replace(/\s+/g, " ")
+ .trim();
+
+ const courseNumbers = parseCourseNumbers(courseNumbersText);
+
+ if (courseNumbers.length > 0) {
+ tests.push({
+ key: courseCode,
+ values: courseNumbers,
+ });
+ }
+ }
+ }
+ });
+
+ console.log("Found", tests.length, "test courses");
+
+ // Sort by course code for consistent display
+ tests.sort((a, b) => a.key.localeCompare(b.key));
+
+ return tests;
+ } catch (error) {
+ console.error("Error fetching tests data:", error);
+ return getFallbackTests();
+ }
+}
+
+// Helper function to get fallback tests
+function getFallbackTests(): TestCourse[] {
+ return [
+ { key: "ACT", values: ["230", "240", "245", "247", "348", "349", "370"] },
+ { key: "ANA", values: ["301"] },
+ { key: "APM", values: ["346", "351"] },
+ ];
+}
+