Skip to content

Commit

Permalink
Merge pull request #16 from stscoundrel/feature/backend-search
Browse files Browse the repository at this point in the history
Feature/backend search
  • Loading branch information
stscoundrel committed Nov 14, 2022
2 parents d388ab1 + 68429d3 commit 10844e6
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 50 deletions.
42 changes: 25 additions & 17 deletions src/components/SearchForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@ import { useRouter } from 'next/router'
import { useState, useEffect } from 'react'

// Services.
import { searchDictionary, SearchResult } from 'lib/services/search'
import { SearchResult } from 'lib/services/search'

// Components.
import LoadingSpinner from 'components/LoadingSpinner'
import SearchResults from 'components/SearchResults'

import { DictionaryEntry } from 'lib/models/dictionary'
import styles from './SearchForm.module.scss'

interface SearchFormProps{
words: DictionaryEntry[]
}

export default function SearchForm({ words }: SearchFormProps) {
export default function SearchForm() {
const router = useRouter()
const [search, setSearch] = useState('')
const [selectedCriteria, setSelectedCriteria] = useState('all')
Expand All @@ -24,10 +19,10 @@ export default function SearchForm({ words }: SearchFormProps) {

const getCriteria = (value) => {
if (!value || value === 'all') {
return ['headword', 'definitions']
return 'headword,definitions'
}

return [value]
return value
}

const changeCriteria = (e) => {
Expand All @@ -53,15 +48,28 @@ export default function SearchForm({ words }: SearchFormProps) {

useEffect(() => {
if (router.query.query) {
showSpinner()
setSearch(String(router.query.query))
setSelectedCriteria(String(router.query.criteria) ?? 'all')

const formattedCriteria = getCriteria(router.query.criteria)
setResults(searchDictionary(String(router.query.query), words, formattedCriteria))
hideSpinner()
const fetchSearchResults = async () => {
showSpinner()
setSearch(String(router.query.query))
setSelectedCriteria(String(router.query.criteria) ?? 'all')

const formattedCriteria = getCriteria(router.query.criteria)
const apiSearchResponse = await fetch(`/api/search?${new URLSearchParams({
search: String(router.query.query),
criteria: formattedCriteria,
})}`)
const apiSearchResult = await apiSearchResponse.json()

console.log(apiSearchResponse)
console.log(apiSearchResult)

setResults(apiSearchResult)
hideSpinner()
}

fetchSearchResults()
}
}, [router.query, words])
}, [router.query])

return (
<>
Expand Down
9 changes: 6 additions & 3 deletions src/lib/services/search.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { markWords } from 'markari'
import { DictionaryEntry } from 'lib/models/dictionary'

export interface SearchResult extends DictionaryEntry {
export interface SearchResult {
headword: string,
slug: string,
foundIn: string[],
}

type Criteria = 'headword' | 'definitions'
export type Criteria = 'headword' | 'definitions'

const formatResults = (
results: DictionaryEntry[],
Expand All @@ -29,7 +31,8 @@ const formatResults = (
}

return {
...result,
headword: result.headword,
slug: result.slug,
foundIn,
}
})
Expand Down
21 changes: 21 additions & 0 deletions src/pages/api/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getAllWords } from 'lib/services/dictionary'
import { Criteria, searchDictionary } from 'lib/services/search';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.query.search || !req.query.criteria) {
return res.status(422).json({ message: 'Missing search term or criteria' })
}

const { search, criteria } = req.query
const formattedCriteria = Array.isArray(criteria) ? criteria as Criteria[] : criteria.split(',') as Criteria[]

const dictionary = getAllWords();
const results = searchDictionary(String(search), dictionary, formattedCriteria)

if (results.length > 100) {
return res.status(200).json(results.slice(0, 100))
}

return res.status(200).json(results)
}
12 changes: 4 additions & 8 deletions src/pages/search.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Services.
import { AlphabetLetter, getAllWords, getAlphabet } from 'lib/services/dictionary'
import { AlphabetLetter, getAlphabet } from 'lib/services/dictionary'

// Components.
import Layout from 'components/Layout'
import SearchForm from 'components/SearchForm'
import { ContentType } from 'lib/models/content-types'
import { DictionaryEntry } from 'lib/models/dictionary'

interface SearchPageProps{
words: DictionaryEntry[],
interface SearchPageProps {
letters: AlphabetLetter[]
}

Expand All @@ -17,21 +15,19 @@ interface SearchPageStaticProps{
}

export async function getStaticProps(): Promise<SearchPageStaticProps> {
const words = getAllWords()
const letters = getAlphabet()

return {
props: {
words,
letters,
},
}
}

export default function Search({ words, letters }: SearchPageProps) {
export default function Search({ letters }: SearchPageProps) {
return (
<Layout type={ContentType.Page} letters={letters} letter={null} word={null} words={[]}>
<SearchForm words={words} />
<SearchForm />
</Layout>
)
}
18 changes: 0 additions & 18 deletions tests/unit/lib/services/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ describe('Search tests', () => {

const expected = {
headword: 'afköra',
partOfSpeech: 'vb',
grammaticalAspect: 'v.',
definitions: [
' , vräka. hafwi wald han afköra GS 43 (1416?). af ty ahren (för areno) han landboen afkörer ib.',
],
alternativeForms: ['-bortdrifva'],
slug: 'afkora',
foundIn: [' , vräka. hafwi wald han <mark>afköra</mark> GS 43 (1416?). af ty ahren (för areno) han landboen afkörer ib.'],
}
Expand All @@ -27,12 +21,6 @@ describe('Search tests', () => {

const expected = {
headword: 'afköra',
partOfSpeech: 'vb',
grammaticalAspect: 'v.',
definitions: [
' , vräka. hafwi wald han afköra GS 43 (1416?). af ty ahren (för areno) han landboen afkörer ib.',
],
alternativeForms: ['-bortdrifva'],
slug: 'afkora',
foundIn: ['In headword: <mark>afköra</mark>'],
}
Expand All @@ -46,12 +34,6 @@ describe('Search tests', () => {

const expected = {
headword: 'þiufstulin',
partOfSpeech: '',
grammaticalAspect: '',
definitions: [
'stulen. &quot; ther war i räthen en kätil grypa och i nysthe, ther för:ne sigeridh kändes widher, ath tz war henne thiwf stolith i fran &quot; JTb 36 ( 1463) . ib 61 ( 1462) . thenne ii vitnadha ok swora mz gäwa kwnsson vm en silffskedh . . . som honom war tiwffsvlin ib 75 (1481).',
],
alternativeForms: ['tiwff- . ', '-stolin )'],
slug: 'thiufstulin',
foundIn: ['In headword: þiufstulin'],
}
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/pages/api/__snapshots__/search.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Search API endpoint Should return search results 1`] = `
[
{
"foundIn": [
"In headword: afköra",
],
"headword": "afköra",
"slug": "afkora",
},
{
"foundIn": [
"In headword: utafköra",
],
"headword": "utafköra",
"slug": "utafkora",
},
]
`;
49 changes: 49 additions & 0 deletions tests/unit/pages/api/search.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createMocks } from 'node-mocks-http'
// eslint-disable-next-line import/no-unresolved
import searchHandler from 'pages/api/search'

describe('Search API endpoint', () => {
test('Should error if missing query params', async () => {
const { req, res } = createMocks()

// Ensure response is blank.
expect(res.finished).toBeFalsy()
expect(res._headers).toEqual({}) // eslint-disable-line

// Setup invalid query params
req.query = {
foo: 'bar',
}

await searchHandler(req, res)

// Should've received 422 bad request
expect(res.finished).toBeTruthy()
expect(res.statusCode).toBe(422)
})

test('Should return search results', async () => {
const { req, res } = createMocks()

// Ensure response is blank.
expect(res.finished).toBeFalsy()
expect(res._headers).toEqual({}) // eslint-disable-line

// Setup valid query params
req.query = {
search: 'afkora',
criteria: 'headword',
}

await searchHandler(req, res)

// eslint-disable-next-line no-underscore-dangle
const response = JSON.parse(res._getData())

// Should be valid response with results
expect(res.finished).toBeTruthy()
expect(res.statusCode).toBe(200)
expect(response.length).toEqual(2)
expect(response).toMatchSnapshot()
})
})
7 changes: 3 additions & 4 deletions tests/unit/pages/search.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ReactDOM from 'react-dom/client'
import Search, { getStaticProps } from 'pages/search'
import renderer from 'react-test-renderer'
import { getAllWords, getAlphabet } from 'lib/services/dictionary'
import { getAlphabet } from 'lib/services/dictionary'

/**
* Mock router
Expand All @@ -18,11 +18,11 @@ describe('Search page: render', () => {
test('Does not crash', () => {
const div = document.createElement('div')
const root = ReactDOM.createRoot(div)
root.render(<Search words={getAllWords().slice(0, 100)} letters={getAlphabet()} />)
root.render(<Search letters={getAlphabet()} />)
})

test('Matches snapshot', () => {
const tree = renderer.create(<Search words={getAllWords()} letters={getAlphabet()} />).toJSON()
const tree = renderer.create(<Search letters={getAlphabet()} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
Expand All @@ -31,7 +31,6 @@ describe('Search page: data fetching', () => {
test('getStaticProps works', async () => {
const expected = {
props: {
words: getAllWords(),
letters: getAlphabet(),
},
}
Expand Down

1 comment on commit 10844e6

@vercel
Copy link

@vercel vercel bot commented on 10844e6 Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.