From 9629df32a0009f54616d0b709b0cb5baeece8757 Mon Sep 17 00:00:00 2001 From: Ahmed Hussein Date: Wed, 3 Jul 2024 23:10:56 +0300 Subject: [PATCH] address PR comments --- .../SettingsDrawer/WordByWordSection.tsx | 67 ++++++++----------- src/utils/array.ts | 25 +++++-- .../array/uniqueArrayByObjectProperty.test.ts | 39 +++++++++++ 3 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 src/utils/tests/array/uniqueArrayByObjectProperty.test.ts diff --git a/src/components/Navbar/SettingsDrawer/WordByWordSection.tsx b/src/components/Navbar/SettingsDrawer/WordByWordSection.tsx index e796f918ed..7621daf103 100644 --- a/src/components/Navbar/SettingsDrawer/WordByWordSection.tsx +++ b/src/components/Navbar/SettingsDrawer/WordByWordSection.tsx @@ -1,6 +1,5 @@ /* eslint-disable i18next/no-literal-string */ /* eslint-disable max-lines */ -import { useEffect, useState } from 'react'; import { Action } from '@reduxjs/toolkit'; import { useRouter } from 'next/router'; @@ -11,7 +10,7 @@ import { shallowEqual, useSelector } from 'react-redux'; import Section from './Section'; import styles from './WordByWordSection.module.scss'; -import { getAvailableWordByWordTranslations } from '@/api'; +import DataFetcher from '@/components/DataFetcher'; import Counter from '@/dls/Counter/Counter'; import Checkbox from '@/dls/Forms/Checkbox/Checkbox'; import Select, { SelectSize } from '@/dls/Forms/Select'; @@ -32,9 +31,10 @@ import { increaseWordByWordFontScale, selectWordByWordFontScale, } from '@/redux/slices/QuranReader/styles'; -import AvailableWordByWordTranslation from '@/types/AvailableWordByWordTranslation'; +import { WordByWordTranslationsResponse } from '@/types/ApiResponses'; import QueryParam from '@/types/QueryParam'; import { WordByWordDisplay, WordByWordType, WordClickFunctionality } from '@/types/QuranReader'; +import { makeWordByWordTranslationsUrl } from '@/utils/apiPaths'; import { removeItemFromArray, uniqueArrayByObjectProperty } from '@/utils/array'; import { logValueChange } from '@/utils/eventLogger'; import { getLocaleName } from '@/utils/locale'; @@ -58,33 +58,6 @@ const WordByWordSection = () => { const wordByWordFontScale = useSelector(selectWordByWordFontScale, shallowEqual); - const [hasError, setHasError] = useState(false); - const [translations, setTranslations] = useState([]); - - useEffect(() => { - getAvailableWordByWordTranslations(lang) - .then((res) => { - if (res.status === 500) { - setHasError(true); - } else { - const data = uniqueArrayByObjectProperty(res.wordByWordTranslations, 'isoCode'); - setTranslations(data); - } - }) - .catch(() => { - setHasError(true); - }); - }, [lang]); - - if (hasError) { - return null; - } - - const WORD_BY_WORD_LOCALES_OPTIONS = translations.map(({ isoCode }) => ({ - label: getLocaleName(isoCode), - value: isoCode, - })); - /** * Persist settings in the DB if the user is logged in before dispatching * Redux action, otherwise just dispatch it. @@ -240,15 +213,31 @@ const WordByWordSection = () => { - {t('trans-lang')} - + + ); + }} /> diff --git a/src/utils/array.ts b/src/utils/array.ts index ceaebb60a1..3d752d5155 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -72,14 +72,29 @@ export const mergeTwoArraysUniquely = (array1: T[], array2: T[]): T[] => { }; /** - * Return a unique array of objects with no dublicates based on an object property + * Return a unique array of objects with no duplicates based on an object property * * @param {any[]} array * @param {string} property - * @returns {T[]} + * @returns {any[]} */ export const uniqueArrayByObjectProperty = (array: any[], property: string) => { - return array.filter( - (obj, index) => array.findIndex((item) => item[property] === obj[property]) === index, - ); + const seenProperties = new Set(); + const seenValues = new Set(); + + return array.filter((item) => { + if (item && typeof item === 'object' && Object.prototype.hasOwnProperty.call(item, property)) { + if (seenProperties.has(item[property])) { + return false; + } + seenProperties.add(item[property]); + return true; + } + + if (seenValues.has(item)) { + return false; + } + seenValues.add(item); + return true; + }); }; diff --git a/src/utils/tests/array/uniqueArrayByObjectProperty.test.ts b/src/utils/tests/array/uniqueArrayByObjectProperty.test.ts new file mode 100644 index 0000000000..637a3be6e6 --- /dev/null +++ b/src/utils/tests/array/uniqueArrayByObjectProperty.test.ts @@ -0,0 +1,39 @@ +import { it, expect } from 'vitest'; + +import { uniqueArrayByObjectProperty } from '../../array'; + +it('should return an empty array if the input array is empty', () => { + const input = []; + const output = uniqueArrayByObjectProperty(input, 'id'); + expect(output).toEqual([]); +}); + +it('should return the same array if there are no duplicates', () => { + const input = [{ id: 1 }, { id: 2 }, { id: 3 }]; + const output = uniqueArrayByObjectProperty(input, 'id'); + expect(output).toEqual(input); +}); + +it('should return an array with duplicates removed based on the property', () => { + const input = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }]; + const output = uniqueArrayByObjectProperty(input, 'id'); + expect(output).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); +}); + +it('should handle different property names correctly', () => { + const input = [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Alice' }]; + const output = uniqueArrayByObjectProperty(input, 'name'); + expect(output).toEqual([{ name: 'Alice' }, { name: 'Bob' }]); +}); + +it('should handle objects that do not have the specified property correctly', () => { + const input = [{ id: 1 }, { name: 'Bob' }, { id: 1 }, { id: 2 }]; + const output = uniqueArrayByObjectProperty(input, 'id'); + expect(output).toEqual([{ id: 1 }, { name: 'Bob' }, { id: 2 }]); +}); + +it('should handle arrays with different types of data correctly', () => { + const input = [{ id: 1 }, 2, { id: 1 }, 'test', { id: 2 }, 'test']; + const output = uniqueArrayByObjectProperty(input, 'id'); + expect(output).toEqual([{ id: 1 }, 2, 'test', { id: 2 }]); +});