Skip to content
Merged
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
11 changes: 8 additions & 3 deletions frontend/src/components/SearchArticles.simple.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ describe('SearchArticles Component', () => {

test('renders search form correctly', () => {
render(<SearchArticles />);

expect(screen.getByText('Search SE Evidence Articles')).toBeInTheDocument();
expect(screen.getByLabelText('Keywords (Title, Authors, Claim)')).toBeInTheDocument();

// Check for the main search input with the placeholder text
expect(screen.getByPlaceholderText('Enter keywords to search across titles, authors, and claims...')).toBeInTheDocument();

// Check for the search button
expect(screen.getByRole('button', { name: 'Search' })).toBeInTheDocument();

// Check for the Advanced Filters header
expect(screen.getByText('Advanced Filters')).toBeInTheDocument();
});
});
118 changes: 74 additions & 44 deletions frontend/src/components/SearchArticles.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,33 @@ describe('SearchArticles Component', () => {

test('renders search form correctly', () => {
render(<SearchArticles />);

expect(screen.getByText('Search SE Evidence Articles')).toBeInTheDocument();
expect(screen.getByLabelText('Keywords (Title, Authors, Claim)')).toBeInTheDocument();
expect(screen.getByLabelText('SE Practice (Evidence Type)')).toBeInTheDocument();
expect(screen.getByLabelText('Publication Year From')).toBeInTheDocument();
expect(screen.getByLabelText('Publication Year To')).toBeInTheDocument();
expect(screen.getByLabelText('Authors')).toBeInTheDocument();
expect(screen.getByLabelText('Status')).toBeInTheDocument();
expect(screen.getByLabelText('Source')).toBeInTheDocument();

// Check for the main search input with the placeholder text
expect(screen.getByPlaceholderText('Enter keywords to search across titles, authors, and claims...')).toBeInTheDocument();

// Check for the Advanced Filters header
expect(screen.getByText('Advanced Filters')).toBeInTheDocument();

// Click the advanced filters header to expand it
fireEvent.click(screen.getByText('Advanced Filters'));

// Check for evidence type select (now visible after expansion)
expect(screen.getByLabelText('Select SE Practice type')).toBeInTheDocument();

// Check for status select
expect(screen.getByLabelText('Select article status')).toBeInTheDocument();

// Check for year from and to inputs
expect(screen.getByLabelText('Publication year from')).toBeInTheDocument();
expect(screen.getByLabelText('Publication year to')).toBeInTheDocument();

// Check for authors input
expect(screen.getByLabelText('Filter by authors')).toBeInTheDocument();

// Check for source input
expect(screen.getByLabelText('Filter by source')).toBeInTheDocument();

// Check for the search button
expect(screen.getByRole('button', { name: 'Search' })).toBeInTheDocument();
});

Expand All @@ -64,13 +82,13 @@ describe('SearchArticles Component', () => {
});

render(<SearchArticles />);
const keywordInput = screen.getByLabelText('Keywords (Title, Authors, Claim)');

const keywordInput = screen.getByPlaceholderText('Enter keywords to search across titles, authors, and claims...');
fireEvent.change(keywordInput, { target: { value: 'test keyword' } });

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
expect(fetch).toHaveBeenCalledWith(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/articles/search/advanced?keywords=test+keyword&sortBy=createdAt&sortDirection=desc`
Expand All @@ -85,13 +103,16 @@ describe('SearchArticles Component', () => {
});

render(<SearchArticles />);

const evidenceTypeSelect = screen.getByLabelText('SE Practice (Evidence Type)');

// Click the advanced filters header to expand it
fireEvent.click(screen.getByText('Advanced Filters'));

const evidenceTypeSelect = screen.getByLabelText('Select SE Practice type');
fireEvent.change(evidenceTypeSelect, { target: { value: EvidenceType.MODERATELY_SUPPORTS } });

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
expect(fetch).toHaveBeenCalledWith(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/articles/search/advanced?evidenceType=Moderately+Supports&sortBy=createdAt&sortDirection=desc`
Expand All @@ -106,16 +127,19 @@ describe('SearchArticles Component', () => {
});

render(<SearchArticles />);

const yearFromInput = screen.getByLabelText('Publication Year From');

// Click the advanced filters header to expand it
fireEvent.click(screen.getByText('Advanced Filters'));

const yearFromInput = screen.getByLabelText('Publication year from');
fireEvent.change(yearFromInput, { target: { value: '2020' } });
const yearToInput = screen.getByLabelText('Publication Year To');

const yearToInput = screen.getByLabelText('Publication year to');
fireEvent.change(yearToInput, { target: { value: '2022' } });

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
expect(fetch).toHaveBeenCalledWith(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/articles/search/advanced?pubYearFrom=2020&pubYearTo=2022&sortBy=createdAt&sortDirection=desc`
Expand All @@ -130,20 +154,23 @@ describe('SearchArticles Component', () => {
});

render(<SearchArticles />);


// Click the advanced filters header to expand it
fireEvent.click(screen.getByText('Advanced Filters'));

// Apply multiple filters
const keywordInput = screen.getByLabelText('Keywords (Title, Authors, Claim)');
const keywordInput = screen.getByPlaceholderText('Enter keywords to search across titles, authors, and claims...');
fireEvent.change(keywordInput, { target: { value: 'test' } });
const evidenceTypeSelect = screen.getByLabelText('SE Practice (Evidence Type)');

const evidenceTypeSelect = screen.getByLabelText('Select SE Practice type');
fireEvent.change(evidenceTypeSelect, { target: { value: EvidenceType.WEAK_AGAINST } });
const authorsInput = screen.getByLabelText('Authors');

const authorsInput = screen.getByLabelText('Filter by authors');
fireEvent.change(authorsInput, { target: { value: 'Doe' } });

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
const fetchCallUrl = (global.fetch as jest.MockedFunction<typeof fetch>).mock.calls[0][0];
expect(fetchCallUrl).toContain('keywords=test');
Expand All @@ -161,14 +188,14 @@ describe('SearchArticles Component', () => {
});

render(<SearchArticles />);

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
expect(screen.getByText('Found 2 results')).toBeInTheDocument();
});

expect(screen.getByText('Test Article 1')).toBeInTheDocument();
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Journal of Testing')).toBeInTheDocument();
Expand All @@ -179,33 +206,36 @@ describe('SearchArticles Component', () => {
(global.fetch as jest.MockedFunction<typeof fetch>).mockRejectedValueOnce(new Error('Network error'));

render(<SearchArticles />);

const searchButton = screen.getByRole('button', { name: 'Search' });
fireEvent.click(searchButton);

await waitFor(() => {
expect(screen.getByText('Network error')).toBeInTheDocument();
});
});

test('clears all filters', async () => {
render(<SearchArticles />);


// Click the advanced filters header to expand it
fireEvent.click(screen.getByText('Advanced Filters'));

// Apply some filters
const keywordInput = screen.getByLabelText('Keywords (Title, Authors, Claim)');
const keywordInput = screen.getByPlaceholderText('Enter keywords to search across titles, authors, and claims...');
fireEvent.change(keywordInput, { target: { value: 'test' } });
const evidenceTypeSelect = screen.getByLabelText('SE Practice (Evidence Type)');

const evidenceTypeSelect = screen.getByLabelText('Select SE Practice type');
fireEvent.change(evidenceTypeSelect, { target: { value: EvidenceType.WEAK_AGAINST } });

// Verify filters are applied
expect(keywordInput).toHaveValue('test');
expect(evidenceTypeSelect).toHaveValue(EvidenceType.WEAK_AGAINST);

// Click clear filters
const clearButton = screen.getByRole('button', { name: 'Clear Filters' });
const clearButton = screen.getByRole('button', { name: 'Clear All' });
fireEvent.click(clearButton);

// Verify filters are cleared
expect(keywordInput).toHaveValue('');
expect(evidenceTypeSelect).toHaveValue('');
Expand Down
29 changes: 26 additions & 3 deletions frontend/src/components/SearchArticles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from "react";
import { Article, EvidenceType, ArticleStatus } from "../types/article";
import { exportToCSV } from "../utils/csv.utils";
import styles from "../styles/SearchPage.module.scss";

// Enhanced search functionality with real-time suggestions and search history
Expand Down Expand Up @@ -542,9 +543,31 @@ const SearchArticles: React.FC = () => {
<p className={styles.resultsCount}>
Found {results.length} results
</p>
<div className={styles.sortInfo}>
Sorting: <span className={styles.sortField}>{sortField}</span>{" "}
{sortDirection === "asc" ? "↑" : "↓"}
<div className={styles.resultsActions}>
<div className={styles.sortInfo}>
Sorting: <span className={styles.sortField}>{sortField}</span>{" "}
{sortDirection === "asc" ? "↑" : "↓"}
</div>
<button
onClick={() => exportToCSV(results, `search-results-${new Date().toISOString().slice(0, 10)}.csv`)}
className={styles.exportButton}
title="Export results to CSV"
>
<svg
className={styles.exportIcon}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
Export CSV
</button>
</div>
</div>

Expand Down
Loading