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"] }, + ]; +} +