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

Refactor and convert components to TypeScript, fix linting errors, and improve typing #1207

Merged
merged 17 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { Route, MemoryRouter, Routes } from 'react-router-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, vi } from 'vitest';
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/FeedbackForm/FeedbackForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { useState, useRef } from "react";
import Question from "../Question/Question";
import Button from "../Button/Button";
import IQuestion from "@/types/Question";
import { OnResultType } from "@/hooks/useResultHandler";

interface FeedbackFormProps {
formActive: boolean;
form: IQuestion[];
buttonLabel: string;
skipLabel: string;
isSkippable: boolean;
onResult: (result: any) => void;
onResult: OnResultType
emphasizeTitle?: boolean;
}

Expand Down
82 changes: 82 additions & 0 deletions frontend/src/components/HTML/HTML.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import HTML from './HTML';

describe('HTML', () => {
it('renders the HTML content correctly', () => {
const htmlContent = '<p>Test content</p>';
const { container } = render(<HTML body={htmlContent} />);
expect(container.innerHTML).to.include(htmlContent);
});

it('applies the default inner className', () => {
const { container } = render(<HTML body="<div>Test</div>" />);
const innerDiv = container.querySelector('.html-content');

if (!innerDiv) {
throw new Error('Inner div not found');
}

expect(innerDiv.classList.contains('text-center')).toBe(true);
expect(innerDiv.classList.contains('pb-3')).toBe(true);
});

it('applies a custom inner className', () => {
const customClass = 'custom-class';
const { container } = render(<HTML body="<div>Test</div>" innerClassName={customClass} />);
const innerDiv = container.querySelector('.html-content');

if (!innerDiv) {
throw new Error('Inner div not found');
}

expect(innerDiv.classList.contains(customClass)).toBe(true);
expect(innerDiv.classList.contains('text-center')).toBe(false);
expect(innerDiv.classList.contains('pb-3')).toBe(false);
});

it('renders complex HTML content', () => {
const complexHTML = `
<div>
<h1>Title</h1>
<p>Paragraph <strong>with bold text</strong></p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
`;
const { container } = render(<HTML body={complexHTML} />);
expect(container.innerHTML).to.include(complexHTML);
});

it('renders the outer aha__HTML class', () => {
const { container } = render(<HTML body="<div>Test</div>" />);
const outerDiv = container.firstChild;

if (!outerDiv) {
throw new Error('Outer div not found');
}

expect(outerDiv.classList.contains('aha__HTML')).toBe(true);
});

it('handles empty body content', () => {
const { container } = render(<HTML body="" />);
const innerDiv = container.querySelector('.html-content');

if (!innerDiv) {
throw new Error('Inner div not found');
}

expect(innerDiv.innerHTML).to.equal('');
});

it('handles TrustedHTML type for body prop', () => {
const trustedHTML = {
toString: () => '<p>Trusted HTML</p>',
} as TrustedHTML;
const { container } = render(<HTML body={trustedHTML} />);
expect(container.innerHTML).to.include('<p>Trusted HTML</p>');
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from "react";
import classNames from "classnames";

interface HTMLProps {
body: string | TrustedHTML;
innerClassName?: string;
}

/** HTML is an block view, that shows custom HTML and a Form */
const HTML = ({ body, innerClassName = "text-center pb-3" }) => {
const HTML = ({ body, innerClassName = "text-center pb-3" }: HTMLProps) => {

return (
<div className={classNames("aha__HTML")}>
Expand Down
105 changes: 105 additions & 0 deletions frontend/src/components/Histogram/Histogram.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render } from '@testing-library/react';
import Histogram from './Histogram';

describe('Histogram', () => {
beforeEach(() => {
vi.useFakeTimers();
});

it('renders the correct number of bars', () => {
const { container } = render(<Histogram bars={5} />);
const bars = container.querySelectorAll('.aha__histogram > div');
expect(bars.length).to.equal(5);
});

it('applies the correct spacing between bars', () => {
const { container } = render(<Histogram bars={3} spacing={10} />);
const bars = container.querySelectorAll('.aha__histogram > div');
expect(getComputedStyle(bars[0]).marginRight).to.equal('10px');
expect(getComputedStyle(bars[1]).marginRight).to.equal('10px');
expect(getComputedStyle(bars[2]).marginRight).to.equal('0px');
});

it('applies the correct margin and background color', () => {
const { container } = render(
<Histogram marginLeft={20} marginTop={10} backgroundColor="red" />
);
const histogram = container.querySelector('.aha__histogram');

if (!histogram) {
throw new Error('Histogram not found');
}

expect(getComputedStyle(histogram).marginLeft).to.equal('20px');
expect(getComputedStyle(histogram).marginTop).to.equal('10px');
expect(getComputedStyle(histogram).backgroundColor).to.equal('red');
});

it('applies the correct border radius', () => {
const { container } = render(<Histogram borderRadius="5px" />);
const histogram = container.querySelector('.aha__histogram');

if (!histogram) {
throw new Error('Histogram not found');
}

expect(getComputedStyle(histogram).borderRadius).to.equal('5px');
});

it('has active class when running', () => {
const { container } = render(<Histogram running={true} />);
const histogram = container.querySelector('.aha__histogram');

if (!histogram) {
throw new Error('Histogram not found');
}

expect(histogram.classList.contains('active')).toBe(true);
});

it('does not have active class when not running', () => {
const { container } = render(<Histogram running={false} />);
const histogram = container.querySelector('.aha__histogram');

if (!histogram) {
throw new Error('Histogram not found');
}

expect(histogram.classList.contains('active')).toBe(false);
});

it('updates bar heights when running', async () => {
const { container, rerender } = render(<Histogram running={true} />);
const getHeights = () => Array.from(container.querySelectorAll('.aha__histogram > div')).map(
(bar) => bar.style.height
);

const initialHeights = getHeights();

// Advance timer and force re-render
vi.advanceTimersByTime(100);
rerender(<Histogram running={true} />);

const updatedHeights = getHeights();

expect(initialHeights).not.to.deep.equal(updatedHeights);
});

it('does not update bar heights when not running', () => {
const { container, rerender } = render(<Histogram running={false} />);
const getHeights = () => Array.from(container.querySelectorAll('.aha__histogram > div')).map(
(bar) => bar.style.height
);

const initialHeights = getHeights();

// Advance timer and force re-render
vi.advanceTimersByTime(100);
rerender(<Histogram running={false} />);

const updatedHeights = getHeights();

expect(initialHeights).to.deep.equal(updatedHeights);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import classNames from "classnames";

// Histogram with random bar movement for decoration
interface HistogramProps {
bars?: number;
spacing?: number;
interval?: number;
running?: boolean;
marginLeft?: number;
marginTop?: number;
backgroundColor?: string;
borderRadius?: string;
}

/** Histogram with random bar movement for decoration */
const Histogram = ({
bars = 7,
spacing = 6,
Expand All @@ -11,7 +22,7 @@ const Histogram = ({
marginTop = 0,
backgroundColor = undefined,
borderRadius = '0.15rem',
}) => {
}: HistogramProps) => {
const [pulse, setPulse] = useState(true);

useEffect(() => {
Expand Down Expand Up @@ -40,7 +51,7 @@ const Histogram = ({
return (
<div
className={classNames("aha__histogram", { active: running })}
style={{ height: '100%', marginLeft, marginTop, backgroundColor, width: '100%', borderRadius: borderRadius, border: backgroundColor? `10px solid ${backgroundColor}` : undefined}}
style={{ height: '100%', marginLeft, marginTop, backgroundColor, width: '100%', borderRadius: borderRadius, border: backgroundColor ? `10px solid ${backgroundColor}` : undefined }}
>
{_bars}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/MatchingPairs/MatchingPairs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState } from "react";
import { useRef, useState } from "react";
import classNames from "classnames";

import { scoreIntermediateResult } from "../../API";
Expand Down
26 changes: 13 additions & 13 deletions frontend/src/components/MatchingPairs/PlayCard.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,68 +31,68 @@ describe("PlayCard Component Tests", () => {

it("should display the back of the card by default", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={sectionProps} />);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".back"))).to.be.true;
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".back"))).toBe(true);
});

it("should display the front of the card when turned", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, turned: true }} />);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).to.be.true;
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).toBe(true);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".front"))).to.not.be.true;
});

it("should display image for visual matching pairs view", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, turned: true }} view="visual" />);
expect(document.body.contains(screen.getByAltText("Test"))).to.be.true;
expect(document.body.contains(screen.getByAltText("Test"))).toBe(true);
});

it("should display histogram for non-visual matching pairs view", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, turned: true }} view="MATCHINGPAIRS" />);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).to.be.true;
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).toBe(true);
});

it("should display a disabled card when inactive", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, inactive: true }} />);
expect(screen.getByTestId("play-card").classList.contains("disabled")).to.be.true;
expect(screen.getByTestId("play-card").classList.contains("disabled")).toBe(true);
});

it("should display a card with no events when noevents", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, noevents: true }} />);
expect(screen.getByTestId("play-card").classList.contains("noevents")).to.be.true;
expect(screen.getByTestId("play-card").classList.contains("noevents")).toBe(true);
});

it("should display a card with fbmemory when memory", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} showAnimation={true} section={{ ...sectionProps, matchClass: 'fbmemory' }} />);
expect(screen.getByRole("button").classList.contains("fbmemory")).to.be.true;
expect(screen.getByRole("button").classList.contains("fbmemory")).toBe(true);
});

it("should display a card with fblucky when lucky", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} showAnimation={true} section={{ ...sectionProps, matchClass: 'fblucky' }} />);
expect(screen.getByRole("button").classList.contains("fblucky")).to.be.true;
expect(screen.getByRole("button").classList.contains("fblucky")).toBe(true);
});

it("should display a card with fbnomatch when nomatch", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} showAnimation={true} section={{ ...sectionProps, matchClass: 'fbnomatch' }} />);
expect(screen.getByRole("button").classList.contains("fbnomatch")).to.be.true;
expect(screen.getByRole("button").classList.contains("fbnomatch")).toBe(true);
});

it("should not apply matchClass when showAnimations is false", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} showAnimation={false} section={{ ...sectionProps, matchClass: 'fbnomatch' }} />);
expect(screen.getByRole("button").classList.contains("fbnomatch")).not.to.be.true;
expect(screen.getByRole("button").classList.contains("fbnomatch")).not.toBe(true);
})

it("should display a card with seen when seen", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} section={{ ...sectionProps, seen: true }} />);
expect(screen.getByTestId("play-card").querySelector(".back").classList.contains("seen")).to.be.true;
expect(screen.getByTestId("play-card").querySelector(".back").classList.contains("seen")).toBe(true);
});

it("should display a card with a histogram when turned and playing", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} playing section={{ ...sectionProps, turned: true }} />);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).to.be.true;
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).toBe(true);
});

it("should display a card with a histogram when turned and not playing", () => {
render(<PlayCard onClick={mockOnClick} registerUserClicks={mockRegisterUserClicks} playing={false} section={{ ...sectionProps, turned: true }} />);
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).to.be.true;
expect(document.body.contains(screen.getByTestId("play-card").querySelector(".aha__histogram"))).toBe(true);
});

it("should display a card without a histogram when not turned and playing", () => {
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/Playback/Playback.test.jsx
drikusroor marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ describe('Playback', () => {
instruction: 'Listen, just listen!',
play_method: 'HTML',
preload_message: 'Get ready',
sections: [{id: 13, url: 'some/fancy/tune.mp3'}]
sections: [{ id: 13, url: 'some/fancy/tune.mp3' }]
};

it('renders itself', () => {
const { container } = render(
<Playback
{... basicProps} playbackArgs={playbackArgs}
/>);
expect(document.body.contains(container.querySelector('.aha__playback'))).to.be.true;
<Playback
{...basicProps} playbackArgs={playbackArgs}
/>);
expect(document.body.contains(container.querySelector('.aha__playback'))).toBe(true);
});

})
})
Loading
Loading