Skip to content

Commit

Permalink
test: add tests for search filter (#25)
Browse files Browse the repository at this point in the history
* chore: set up jest

* fix: correct units to number

* chore: rename searchUtils to filterUtils

* test: add search filter tests

* ci: remove 14, 16 node versions
  • Loading branch information
KevinWu098 authored Dec 21, 2023
1 parent 8681e78 commit 4bb4228
Show file tree
Hide file tree
Showing 8 changed files with 8,942 additions and 4,505 deletions.
41 changes: 20 additions & 21 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,27 @@
name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:
build:
runs-on: ubuntu-latest

runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm run build --if-present
- run: npm test
245 changes: 245 additions & 0 deletions __tests__/search-filters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import { FilterValues } from "@/components/search/search";
import {
endsBefore,
filterData,
startsAfter,
} from "@/components/search/filterUtils";
import "@testing-library/jest-dom";

const data = {
institution: "University of California, Irvine",
geCategory: "II",
courses: [
{
sendingInstitution: "placeholder sending institution 1",
courseCode: "placeholder course code 1",
courseName: "placeholder course name 1",
cvcId: "123456",
niceToHaves: ["Zero Textbook Cost"],
units: 4,
term: "Mar 11 - May 25",
startMonth: 1,
startDay: 1,
endMonth: 6,
endDay: 1,
tuition: 138,
async: true,
hasOpenSeats: true,
hasPrereqs: false,
instantEnrollment: true,
assistPath: "placeholder path 1",
articulatesTo: ["placeholder course 1"],
fulfillsGEs: ["II"],
},
{
sendingInstitution: "placeholder sending institution 2",
courseCode: "placeholder course code 2",
courseName: "placeholder course name 2",
cvcId: "1234567",
niceToHaves: ["Zero Textbook Cost"],
units: 16,
term: "Mar 11 - May 25",
startMonth: 1,
startDay: 1,
endMonth: 6,
endDay: 1,
tuition: 100,
async: false,
hasOpenSeats: false,
hasPrereqs: false,
instantEnrollment: false,
assistPath: "placeholder path 2",
articulatesTo: ["placeholder course 2"],
fulfillsGEs: ["II"],
},
],
};

const defaultFilterValues: FilterValues = {
format: [true, true],
enrollment: [false],
available: [false],
start: "2023-12-20",
end: undefined,
institution: "Any Institution",
min: 0,
max: 20,
sort: "Default Sort",
};

describe("Search Filters", () => {
test("empty courses do not throw error", async () => {
expect(() => filterData([], defaultFilterValues)).not.toThrow(Error);
});

test("default filter values do not throw error", async () => {
expect(() => filterData(data.courses, defaultFilterValues)).not.toThrow(
Error,
);
});

test("default filter values return all", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
});
expect(result).toEqual(data.courses);
});

test("no formats filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
format: [false, false],
});
expect(result).toEqual([]);
});

test("both formats filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
format: [true, true],
});
expect(result).toEqual(data.courses);
});

test("only async format filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
format: [true, false],
});
expect(result.every((course) => course.async)).toBe(true);
});

test("only sync format filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
format: [false, true],
});

expect(result.every((course) => !course.async)).toBe(true);
});

test("only instant enrollment filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
enrollment: [true],
});

expect(result.every((course) => course.instantEnrollment)).toBe(true);
});

test("only available seats filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
available: [true],
});
expect(result.every((course) => course.hasOpenSeats)).toBe(true);
});

test("any institution filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
institution: "Any Institution",
});
expect(result).toEqual(data.courses);
});

test("specific institution filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
institution: "placeholder sending institution 1",
});
expect(result[0].sendingInstitution).toEqual(
"placeholder sending institution 1",
);
});

test("min units filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
min: 5,
});
expect(result.every((course) => course.units >= 5)).toBe(true);
});

test("max units filters correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
max: 5,
});
expect(result.every((course) => course.units <= 5)).toBe(true);
});
});

describe("Filter Utils' Time Utilities", () => {
test("startsAfter undefined", async () => {
const result = startsAfter(undefined, data.courses[0]);
expect(result).toBe(true);
});

test("startsAfter defined returns true", async () => {
const result = startsAfter("2023-12-25", data.courses[0]);
expect(result).toBe(true);
});

test("startsAfter defined returns false", async () => {
const result = startsAfter("2024-12-25", data.courses[0]);
expect(result).toBe(false);
});

test("endsBefore undefined", async () => {
const result = endsBefore(undefined, data.courses[0]);
expect(result).toBe(true);
});

test("endsBefore defined returns true", async () => {
const result = endsBefore("2024-06-14", data.courses[0]);
expect(result).toBe(true);
});

test("endsBefore defined returns false", async () => {
const result = endsBefore("2024-05-14", data.courses[0]);
expect(result).toBe(false);
});

test("tuition sorts correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
sort: "Tuition",
});
expect(result[0].sendingInstitution).toEqual(
"placeholder sending institution 2",
);
});
});

describe("Search Sorting", () => {
test("default sorts correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
sort: "Alphabetical",
});
expect(result[0].sendingInstitution).toEqual(
"placeholder sending institution 1",
);
});

test("alphabetical sorts correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
sort: "Alphabetical",
});
expect(result[0].sendingInstitution).toEqual(
"placeholder sending institution 1",
);
});

test("tuition sorts correctly", async () => {
const result = filterData(data.courses, {
...defaultFilterValues,
sort: "Tuition",
});
expect(result[0].sendingInstitution).toEqual(
"placeholder sending institution 2",
);
});
});
2 changes: 1 addition & 1 deletion components/search/filterComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const CustomFilterCheckbox = (props: FilterCheckboxProps) => {
interface CalendarFilterProps {
onStartChange: Dispatch<SetStateAction<string>>;
onEndChange: Dispatch<SetStateAction<string | undefined>>;
defaultStart: string;
defaultStart: string | undefined;
defaultEnd: string | undefined;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { CollegeObject, FilterValues } from "./search";

const startsAfter = (start: string, result: CollegeObject) => {
export const startsAfter = (
start: string | undefined,
result: CollegeObject,
) => {
if (start == undefined) return true;

return (
Expand All @@ -10,7 +13,7 @@ const startsAfter = (start: string, result: CollegeObject) => {
);
};

const endsBefore = (end: string | undefined, result: CollegeObject) => {
export const endsBefore = (end: string | undefined, result: CollegeObject) => {
if (end == undefined) return true;

return (
Expand All @@ -29,16 +32,16 @@ export function filterData(data: CollegeObject[], filterValues: FilterValues) {

const instantEnrollment = filterValues.enrollment[0]
? result.instantEnrollment
: false;
: true;
const hasOpenSeats = filterValues.available[0]
? result.hasOpenSeats
: false;
: true;
const teachingInstitution =
result.sendingInstitution == filterValues.institution ||
filterValues.institution == "Any Institution";
const withinUnits =
parseFloat(result.units) >= filterValues.min &&
parseFloat(result.units) <= filterValues.max;
result.units >= filterValues.min &&
result.units <= filterValues.max;
const withinTime =
startsAfter(filterValues.start, result) &&
endsBefore(filterValues.end, result);
Expand Down
6 changes: 3 additions & 3 deletions components/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SearchResults from "./searchResults";
import { FaFilter } from "react-icons/fa6";
import { SearchFilterPage, SearchFilters } from "./filters";
import SearchBlurb from "./blurb";
import { filterData } from "./searchUtils";
import { filterData } from "./filterUtils";
import { UNIVERSITY_GE } from "@/lib/constants";

export interface CollegeObject {
Expand All @@ -18,7 +18,7 @@ export interface CollegeObject {
courseName: string;
cvcId: string;
niceToHaves: string[];
units: string;
units: number;
term: string;
startMonth: number;
startDay: number;
Expand All @@ -38,7 +38,7 @@ export type FilterValues = {
format: boolean[];
enrollment: boolean[];
available: boolean[];
start: string;
start: string | undefined;
end: string | undefined;
institution: string;
min: number;
Expand Down
18 changes: 18 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Config } from "jest";
import nextJest from "next/jest.js";

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: "./",
});

// Add any custom config to be passed to Jest
const config: Config = {
coverageProvider: "v8",
testEnvironment: "jsdom",
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config);
Loading

0 comments on commit 4bb4228

Please sign in to comment.