Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: setup Cypress test suite #62

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
},
extends: [
"next/core-web-vitals",
"plugin:cypress/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"eslint:recommended",
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/status_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ jobs:
with:
path: ./node_modules/
key: ${{ github.sha }}-install
- name: Cache Cypress
uses: actions/cache@v3
with:
path: ~/.cache/Cypress/
key: ${{ github.sha }}-cypress

code_style:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -75,3 +80,29 @@ jobs:
with:
path: ./.next/
key: ${{ github.sha }}-build

cypress:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Retrive install cache
uses: actions/cache@v3
with:
path: ./node_modules/
key: ${{ github.sha }}-install
- name: Retrive Cypress cache
uses: actions/cache@v3
with:
path: ~/.cache/Cypress/
key: ${{ github.sha }}-cypress
- name: Retrive build cache
uses: actions/cache@v3
with:
path: ./.next/
key: ${{ github.sha }}-build
- name: Run Cypress
uses: cypress-io/github-action@v5
with:
install: false
start: yarn start
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ yarn-error.log*
**/public/workbox-*.js.map
**/public/sw.js.map
**/public/worker-*.js.map

cypress/downloads
cypress/videos
cypress/screenshots
7 changes: 7 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
},
});
48 changes: 48 additions & 0 deletions cypress/e2e/date-info.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { DateTime } from "luxon";

import { TIME_ZONE } from "../../src/config/config";
import { calculateMondayOffset } from "../../src/lib/integration/common";

describe("Date Info", () => {
const today = DateTime.now().setZone(TIME_ZONE).startOf("day");
const mondayOffset = calculateMondayOffset();
const weekdays = ["Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"];

function validateDateInfo(weekOffset: number) {
cy.getByTestId("date-info").should("have.length", 7);
for (let i = 0; i < 6; i++) {
const currentDate = today.plus({ days: i - mondayOffset + weekOffset * 7 });
cy.getByTestId("date-info").eq(i).getByTestId("weekday").should("contain.text", weekdays[i]);
cy.getByTestId("date-info")
.eq(i)
.getByTestId("date")
.should("contain.text", currentDate.toFormat("yyyy-MM-dd"));
if (currentDate.equals(today)) {
cy.getByTestId("date-info").eq(i).getByTestId("today-chip").should("be.visible");
} else {
cy.getByTestId("date-info").eq(i).getByTestId("today-chip").should("not.exist");
}
}
}

beforeEach(() => {
cy.visit("/");
});

it("Displays correct dates for current week", () => {
cy.getByTestId("today-chip").should("have.length", 1);
validateDateInfo(0);
});

it("Displays correct dates for previous week", () => {
cy.goToWeek("prev");
cy.getByTestId("today-chip").should("not.exist");
validateDateInfo(-1);
});

it("Displays correct dates for next week", () => {
cy.goToWeek("next");
cy.getByTestId("today-chip").should("not.exist");
validateDateInfo(1);
});
});
34 changes: 34 additions & 0 deletions cypress/e2e/week-navigator.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DateTime } from "luxon";

describe("Week Navigator", () => {
const today = DateTime.now();

function assertWeekNumber(weekNumber: number) {
cy.getByTestId("week-number").should("contain.text", `UKE ${weekNumber}`);
}

beforeEach(() => {
cy.visit("/");
});

it("Displays correct current week number", () => {
cy.getByTestId("week-number").should("be.visible");
cy.getByTestId("today-week-btn").should("be.disabled");
assertWeekNumber(today.weekNumber);
});

it("Can navigate between weeks", () => {
for (let i = 1; i < 5; i++) {
cy.goToWeek("next");
assertWeekNumber(today.weekNumber + i);
}

cy.goToWeek("today");
assertWeekNumber(today.weekNumber);

for (let i = 1; i < 5; i++) {
cy.goToWeek("prev");
assertWeekNumber(today.weekNumber - i);
}
});
});
60 changes: 60 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Cypress.Commands.add("getByTestId", { prevSubject: "optional" }, (subject, id) => {
if (subject) {
return cy.wrap(subject).find(`[data-testid="${id}"]`);
}
return cy.get(`[data-testid="${id}"]`);
});

Cypress.Commands.add("waitIfNecessary", (url, func) => {
const alias = "interception#" + Math.random().toString(36).substring(2, 8);
cy.intercept(url, { times: 1 }, () => {}).as(alias);

func();

cy.get("@" + alias).then((interception) => {
if (!interception) {
fetch(url);
}
});

cy.wait("@" + alias);
});

Cypress.Commands.add("goToWeek", (action) => {
cy.waitIfNecessary("/api/schedule", () => {
cy.getByTestId(`${action}-week-btn`).click();
});
});

declare global {
namespace Cypress {
interface Chainable {
/**
* Custom command to select DOM element by data-testid attribute.
* @example cy.getTestId('greeting')
*/
getByTestId(value: string): Chainable<JQuery<HTMLElement>>;

/**
* Custom command to enable waiting for potential api requests caused by some function.
* If the api request occurs, it is awaited.
* Otherwise, the request called manually, to clear the interception.
* @param url the url pattern for the application api request
* @param func the function to be called, which might trigger an api request
* @example
* cy.waitIfNecessary("/api/schedule", () => {
* cy.getByTestId(`${action}-week-btn`).click();
* })
*/
waitIfNecessary(url: string, func: () => void): void;

/**
* Shorthand to enable interaction with the week navigation buttons.
* @param action the button to be clicked
*/
goToWeek(action: "next" | "prev" | "today"): void;
}
}
}

export {};
1 change: 1 addition & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./commands";
12 changes: 12 additions & 0 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// TS complation fails because Cypress does not currently work with moduleResolution: bundler
// This is an active issue https://github.com/cypress-io/cypress/issues/27731

{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"],
"baseUrl": "../src/*"
},
"include": ["**/*.ts"]
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"cypress": "^13.1.0",
"eslint": "^8.47.0",
"eslint-config-next": "^13.4.19",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-cypress": "^2.14.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-no-relative-import-paths": "^1.5.2",
"prettier": "^3.0.2",
Expand Down
6 changes: 4 additions & 2 deletions src/components/schedule/DaySchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ function DaySchedule({

return (
<Box key={daySchedule.date.toString()} width={180}>
<Box py={2} sx={{ opacity: isDayPassed(daySchedule.date) ? 1 : 0.5 }}>
<Typography variant="h6" component="div">
<Box py={2} sx={{ opacity: isDayPassed(daySchedule.date) ? 1 : 0.5 }} data-testid={"date-info"}>
<Typography variant="h6" component="div" data-testid={"weekday"}>
{getCapitalizedWeekday(daySchedule.date)}{" "}
{isToday(daySchedule.date) && (
<Chip
size={"small"}
sx={{ backgroundColor: theme.palette.primary.dark, color: "#fff" }}
label="I dag"
data-testid={"today-chip"}
/>
)}
</Typography>
Expand All @@ -61,6 +62,7 @@ function DaySchedule({
color: theme.palette.grey[600],
fontSize: 15,
}}
data-testid={"date"}
>
{daySchedule.date.toFormat("yyyy-MM-dd")}
</Typography>
Expand Down
10 changes: 9 additions & 1 deletion src/components/schedule/WeekNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default function WeekNavigator({
return (
<Stack direction={"row"} justifyContent={"center"} alignItems={"center"} mb={1} sx={{ position: "relative" }}>
<LoadingButton
data-testid={"prev-week-btn"}
loading={loadingPreviousWeek}
variant={"outlined"}
sx={{ minWidth: { xs: "2rem", md: "4rem" } }}
Expand All @@ -84,8 +85,14 @@ export default function WeekNavigator({
>
<ArrowBack />
</LoadingButton>
<Typography sx={{ opacity: 0.7 }} mx={2} variant={"subtitle2"}>{`UKE ${weekNumber}`}</Typography>
<Typography
data-testid={"week-number"}
sx={{ opacity: 0.7 }}
mx={2}
variant={"subtitle2"}
>{`UKE ${weekNumber}`}</Typography>
<LoadingButton
data-testid={"next-week-btn"}
loading={loadingNextWeek}
variant={"outlined"}
sx={{ minWidth: { xs: "2rem", md: "4rem" } }}
Expand All @@ -95,6 +102,7 @@ export default function WeekNavigator({
<ArrowForward />
</LoadingButton>
<Button
data-testid={"today-week-btn"}
sx={{
ml: 1,
position: { xs: "absolute", md: "inherit" },
Expand Down
Loading