diff --git a/frontend/src/API.ts b/frontend/src/API.ts index 4ca97f79a..4dc094128 100644 --- a/frontend/src/API.ts +++ b/frontend/src/API.ts @@ -57,12 +57,14 @@ export const useParticipantScores = () => export const useParticipantLink = () => useGet(API_BASE_URL + URLS.participant.link); +type ConsentResponse = boolean | null; + export const useConsent = (slug: string) => - useGet(API_BASE_URL + URLS.result.get('consent_' + slug)); + useGet(API_BASE_URL + URLS.result.get('consent_' + slug)); interface CreateConsentParams { block: Block; - participant: Participant; + participant: Pick; } /** Create consent for given experiment */ diff --git a/frontend/src/components/Consent/Consent.test.jsx b/frontend/src/components/Consent/Consent.test.tsx similarity index 52% rename from frontend/src/components/Consent/Consent.test.jsx rename to frontend/src/components/Consent/Consent.test.tsx index c9686611e..153874fff 100644 --- a/frontend/src/components/Consent/Consent.test.jsx +++ b/frontend/src/components/Consent/Consent.test.tsx @@ -1,9 +1,8 @@ -import React from 'react'; import { fireEvent, render, waitFor } from '@testing-library/react'; -import Consent from './Consent'; +import Consent, { ConsentProps } from './Consent'; import { useConsent } from '../../API' import { saveAs } from 'file-saver'; -import { vi } from 'vitest'; +import { vi, Mock, expect, it, describe, } from 'vitest'; global.Blob = vi.fn().mockImplementation((content, options) => ({ content, @@ -26,51 +25,60 @@ const mockBlock = { loading_text: 'Loading...', }; +const getConsentProps: (overrides?: Partial) => ConsentProps = (overrides) => ({ + title: 'Consent', + text: '

Consent Text

', + block: mockBlock, + participant: { csrf_token: '42' }, + onNext: vi.fn(), + confirm: 'Agree', + deny: 'Disagree', + ...overrides, +}); + describe('Consent', () => { it('renders loading state correctly', () => { - useConsent.mockReturnValue([null, true]); // Mock loading state - const { getByText } = render(); - expect(document.body.contains(getByText('Loading...'))).to.be.true; + (useConsent as Mock).mockReturnValue([null, true]); // Mock loading state + const { getByText } = render(); + expect(document.body.contains(getByText('Loading...'))).toBe(true); }); it('renders consent text when not loading', () => { - useConsent.mockReturnValue([null, false]); - const { getByText } = render(); - expect(document.body.contains(getByText('Consent Text'))).to.be.true; + (useConsent as Mock).mockReturnValue([null, false]); + const { getByText } = render(Consent Text

', block: { slug: 'test-experiment', loading_text: 'Loading...' } })} />); + + expect(document.body.contains(getByText('Consent Text'))).toBe(true); }); it('calls onNext when Agree button is clicked', async () => { - useConsent.mockReturnValue([null, false]); + (useConsent as Mock).mockReturnValue([null, false]); const onNext = vi.fn(); - const { getByText } = render(); + const { getByText } = render(); fireEvent.click(getByText('Agree')); await waitFor(() => expect(onNext).toHaveBeenCalled()); }); it('triggers download when Download button is clicked', async () => { - useConsent.mockReturnValue([null, false]); - const { getByTestId } = render(); + (useConsent as Mock).mockReturnValue([null, false]); + const { getByTestId } = render(); fireEvent.click(getByTestId('download-button')); await waitFor(() => expect(saveAs).toHaveBeenCalled()); }); it('auto advances if consent is already given', () => { - useConsent.mockReturnValue([true, false]); + (useConsent as Mock).mockReturnValue([true, false]); const onNext = vi.fn(); - render(); + render(); expect(onNext).toHaveBeenCalled(); }); it('calculates style for consent text correctly', () => { - useConsent.mockReturnValue([null, false]); + (useConsent as Mock).mockReturnValue([null, false]); Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 800 }); - const { getByTestId } = render(); + const { getByTestId } = render(); const consentText = getByTestId('consent-text'); expect(consentText.style.height).toBe('500px'); }); - - - }); diff --git a/frontend/src/components/Consent/Consent.jsx b/frontend/src/components/Consent/Consent.tsx similarity index 88% rename from frontend/src/components/Consent/Consent.jsx rename to frontend/src/components/Consent/Consent.tsx index da7641749..8f0a77bb3 100644 --- a/frontend/src/components/Consent/Consent.jsx +++ b/frontend/src/components/Consent/Consent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { saveAs } from 'file-saver'; import { URLS } from "@/config"; @@ -6,9 +6,20 @@ import Button from "../Button/Button"; import Loading from "../Loading/Loading"; import { createConsent, useConsent } from "../../API"; import classNames from "classnames"; +import Participant from "@/types/Participant"; + +export interface ConsentProps { + title: string; + text: string; + block: any; + participant: Pick; + onNext: () => void; + confirm: string; + deny: string; +} /** Consent is an block view that shows the consent text, and handles agreement/stop actions */ -const Consent = ({ title, text, block, participant, onNext, confirm, deny }) => { +const Consent = ({ title, text, block, participant, onNext, confirm, deny }: ConsentProps) => { const [consent, loadingConsent] = useConsent(block.slug); const urlQueryString = window.location.search; @@ -30,7 +41,7 @@ const Consent = ({ title, text, block, participant, onNext, confirm, deny }) => const onDownload = async () => { const doc = new DOMParser().parseFromString(text, 'text/html'); - const txt = doc.body.textContent.split(' ').join(''); + const txt = doc.body.textContent ? doc.body.textContent.split(' ').join('') : ''; const blob = new Blob([txt], { type: "text/plain;charset=utf-8" }); saveAs(blob, 'consent.txt'); }