From a58b2b9052763e30cdc9f8f2d913e262cbf68d65 Mon Sep 17 00:00:00 2001 From: Kamrul H Shourov Date: Thu, 31 Oct 2024 00:31:41 +0600 Subject: [PATCH] Integrate search-result-view into app with dummy data --- views/interactivity/src/App.tsx | 57 ++++++++++++++-- .../components/api/dictionary.validator.ts | 6 +- .../src/components/general/Accordion.css | 55 ++++++++++++++++ .../src/components/general/Accordion.test.tsx | 17 +++++ .../src/components/general/Accordion.tsx | 22 +++++++ .../components/general/AudioPlayer.test.tsx | 20 ++++++ .../src/components/general/AudioPlayer.tsx | 25 +++++++ .../src/components/general/Icon.css | 4 ++ .../src/components/general/Icon.tsx | 15 +++++ .../src/components/general/icons.svg | 13 ++++ .../src/components/icons.svg.tsx | 17 ----- .../search-result-view/SearchResultView.css | 14 ++++ .../SearchResultView.test.tsx | 53 +++++++++++++++ .../search-result-view/SearchResultView.tsx | 30 +++++++++ .../header/SearchResultAccessSummary.css | 4 -- .../header/SearchResultAccessSummary.test.tsx | 2 +- .../header/SearchResultAccessSummary.tsx | 15 +---- .../header/SearchResultHeader.test.tsx | 2 +- .../header/SearchResultHeader.tsx | 4 +- .../search-result-view/ipa/IPAContainer.css | 6 ++ .../ipa/IPAContainer.test.tsx | 65 +++++++++++++++++++ .../search-result-view/ipa/IPAContainer.tsx | 27 ++++++++ .../search-result-view/ipa/IPAView.css | 18 +++++ .../search-result-view/ipa/IPAView.test.tsx | 17 +++++ .../search-result-view/ipa/IPAView.tsx | 18 +++++ .../meaning/CategoryMeaningView.test.tsx | 37 +++++++++++ .../meaning/CategoryMeaningView.tsx | 25 +++++++ .../meaning/MeaningView.css | 12 ++++ .../meaning/MeaningView.test.tsx | 16 +++++ .../meaning/MeaningView.tsx | 14 ++++ views/interactivity/src/components/types.d.ts | 7 +- views/interactivity/src/index.css | 48 +++++++++++--- views/interactivity/src/lib/utils.test.ts | 8 ++- views/interactivity/src/lib/utils.ts | 5 +- 34 files changed, 638 insertions(+), 60 deletions(-) create mode 100644 views/interactivity/src/components/general/Accordion.css create mode 100644 views/interactivity/src/components/general/Accordion.test.tsx create mode 100644 views/interactivity/src/components/general/Accordion.tsx create mode 100644 views/interactivity/src/components/general/AudioPlayer.test.tsx create mode 100644 views/interactivity/src/components/general/AudioPlayer.tsx create mode 100644 views/interactivity/src/components/general/Icon.css create mode 100644 views/interactivity/src/components/general/Icon.tsx create mode 100644 views/interactivity/src/components/general/icons.svg delete mode 100644 views/interactivity/src/components/icons.svg.tsx create mode 100644 views/interactivity/src/components/search-result-view/SearchResultView.css create mode 100644 views/interactivity/src/components/search-result-view/SearchResultView.test.tsx create mode 100644 views/interactivity/src/components/search-result-view/SearchResultView.tsx create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAContainer.css create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAContainer.test.tsx create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAContainer.tsx create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAView.css create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAView.test.tsx create mode 100644 views/interactivity/src/components/search-result-view/ipa/IPAView.tsx create mode 100644 views/interactivity/src/components/search-result-view/meaning/CategoryMeaningView.test.tsx create mode 100644 views/interactivity/src/components/search-result-view/meaning/CategoryMeaningView.tsx create mode 100644 views/interactivity/src/components/search-result-view/meaning/MeaningView.css create mode 100644 views/interactivity/src/components/search-result-view/meaning/MeaningView.test.tsx create mode 100644 views/interactivity/src/components/search-result-view/meaning/MeaningView.tsx diff --git a/views/interactivity/src/App.tsx b/views/interactivity/src/App.tsx index 5855c4b..bf08aca 100644 --- a/views/interactivity/src/App.tsx +++ b/views/interactivity/src/App.tsx @@ -2,12 +2,52 @@ import React, { useState, useEffect } from 'react'; import './App.css'; import { DictionaryApi } from './components/api/dictionary'; import SearchBox from './components/search-box/SearchBox'; +import { SearchResultView } from './components/search-result-view/SearchResultView'; import { DictionaryEntry } from './components/types'; function App() { - const [bearerToken, setBearerToken] = useState(null); + const [bearerToken, setBearerToken] = useState('bearer-token'); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [response, setResponse] = useState(null); + const [response, setResponse] = useState({ + dictionaryWord: { + name: 'hello', + entry: { + ipaListings: { + uk: [ + { + category: '', + ipa: '/heˈləʊ/', + audio: + 'https://dictionary.cambridge.org/media/english/uk_pron/u/ukh/ukhef/ukheft_029.mp3', + }, + ], + us: [ + { + category: '', + ipa: '/heˈloʊ/', + audio: + 'https://dictionary.cambridge.org/media/english/us_pron/h/hel/hello/hello.mp3', + }, + ], + }, + meanings: [ + { + categories: 'exclamation, noun', + entries: [ + { + meaning: 'used when meeting or greeting someone:', + examples: ["Hello, Paul. I haven't seen you for ages."], + }, + ], + }, + ], + }, + }, + accessSummary: { + totalAccess: 1, + lastAccessAt: new Date('2024-01-01T00:00:00.000Z'), + }, + }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [errorMessage, setErrorMessage] = useState(null); @@ -24,11 +64,14 @@ function App() { const dictionaryApi = new DictionaryApi({ token: bearerToken }); return ( - + <> + + {response && } + ); } diff --git a/views/interactivity/src/components/api/dictionary.validator.ts b/views/interactivity/src/components/api/dictionary.validator.ts index fa0ec51..3b7cb7e 100644 --- a/views/interactivity/src/components/api/dictionary.validator.ts +++ b/views/interactivity/src/components/api/dictionary.validator.ts @@ -11,18 +11,18 @@ export const responseSchema = object({ }).nullable(), }); -const ipaListingSchema = object({ +export const ipaListingSchema = object({ category: string().nonNullable().defined(), ipa: string().required(), audio: string().url().required(), }); -const meaningEntrySchema = object({ +export const meaningEntrySchema = object({ meaning: string().required(), examples: array().of(string()).required(), }); -const meaningSchema = object({ +export const meaningSchema = object({ categories: string().required(), entries: array().of(meaningEntrySchema).required(), }); diff --git a/views/interactivity/src/components/general/Accordion.css b/views/interactivity/src/components/general/Accordion.css new file mode 100644 index 0000000..955783a --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.css @@ -0,0 +1,55 @@ +.accordion > input[type='checkbox'] { + position: absolute; + left: -100vw; +} +.accordion .content { + overflow-y: hidden; + height: 0; + transition: height 0.3s ease; +} +.accordion > input[type='checkbox']:checked ~ .content { + height: auto; + overflow: visible; +} +.accordion label { + display: block; +} +.accordion { + margin-bottom: 1em; +} +.accordion > input[type='checkbox']:checked ~ .content { + padding: 1em; + border: 1px solid #e8e8e8; + border-top: 0; +} +.accordion .handle { + margin: 0; + font-size: 1.125em; + line-height: 1.2em; +} +.accordion label { + color: #333; + cursor: pointer; + font-weight: normal; + padding: 1em; + background: #e8e8e8; +} +.accordion label:hover, +.accordion label:focus { + background: #d8d8d8; +} +.accordion .handle label:before { + font-family: 'fontawesome'; + content: '\f054'; + display: inline-block; + margin-right: 0.625em; + font-size: 0.58em; + line-height: 1.556em; + vertical-align: middle; +} +.accordion > input[type='checkbox']:checked ~ .handle label:before { + content: '\f078'; +} +.accordion { + width: 100%; +} diff --git a/views/interactivity/src/components/general/Accordion.test.tsx b/views/interactivity/src/components/general/Accordion.test.tsx new file mode 100644 index 0000000..642429e --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.test.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Accordion } from './Accordion'; + +test('accordion should have a label and content block', async () => { + const id = 'accordion-id'; + const label = 'label'; + const Children = () =>
Hello
; + render( + + + , + ); + + expect(screen.queryByText(label)).toBeInTheDocument(); + expect(screen.queryByText('Hello')).toBeInTheDocument(); +}); diff --git a/views/interactivity/src/components/general/Accordion.tsx b/views/interactivity/src/components/general/Accordion.tsx new file mode 100644 index 0000000..0039b69 --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.tsx @@ -0,0 +1,22 @@ +import React, { ReactNode } from 'react'; +import './Accordion.css'; + +export function Accordion({ + id, + label, + children, +}: { + id: string; + label: string; + children: ReactNode; +}) { + return ( +
+ +

+ +

+
{children}
+
+ ); +} diff --git a/views/interactivity/src/components/general/AudioPlayer.test.tsx b/views/interactivity/src/components/general/AudioPlayer.test.tsx new file mode 100644 index 0000000..3fde939 --- /dev/null +++ b/views/interactivity/src/components/general/AudioPlayer.test.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { AudioPlayer } from './AudioPlayer'; + +test('audio player should play audio when button is clicked', async () => { + const id = 1; + const src = 'https://domain.com/media/1.mp3'; + + render(); + + const audioPlayer: HTMLAudioElement = screen.getByTestId( + `audio-player-${id}`, + ); + jest.spyOn(audioPlayer, 'play').mockImplementation(() => Promise.resolve()); + + const button = screen.getByTestId(`audio-button-${id}`); + fireEvent.click(button); + + expect(audioPlayer.play).toHaveBeenCalled(); +}); diff --git a/views/interactivity/src/components/general/AudioPlayer.tsx b/views/interactivity/src/components/general/AudioPlayer.tsx new file mode 100644 index 0000000..f2b3f3c --- /dev/null +++ b/views/interactivity/src/components/general/AudioPlayer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Icon } from './Icon'; + +export function AudioPlayer({ id, src }: { id: number; src: string }) { + const audioRef = React.useRef(null); + + const handlePlay = () => { + if (audioRef.current) { + audioRef.current.play(); + } + }; + + return ( + <> +
+ +
+