From e871a48c1817710d0351f1728b272072822bc0fe Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Tue, 23 Jul 2024 13:35:46 +0200 Subject: [PATCH] [Bug #484] Ensure localized vocabulary label is used as option label in ImportedVocabulariesListEdit. --- .../ImportedVocabulariesListEdit.tsx | 66 ++++++++-------- .../ImportedVocabulariesListEdit.test.tsx | 78 ++++++++----------- 2 files changed, 64 insertions(+), 80 deletions(-) diff --git a/src/component/vocabulary/ImportedVocabulariesListEdit.tsx b/src/component/vocabulary/ImportedVocabulariesListEdit.tsx index ca24996a0..38c296a31 100644 --- a/src/component/vocabulary/ImportedVocabulariesListEdit.tsx +++ b/src/component/vocabulary/ImportedVocabulariesListEdit.tsx @@ -1,44 +1,48 @@ import * as React from "react"; -import { injectIntl } from "react-intl"; // @ts-ignore import { IntelligentTreeSelect } from "intelligent-tree-select"; -import withI18n, { HasI18n } from "../hoc/withI18n"; import Vocabulary from "../../model/Vocabulary"; import { AssetData } from "../../model/Asset"; import { Col, FormGroup, Label, Row } from "reactstrap"; -import { connect } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import TermItState from "../../model/TermItState"; import Utils from "../../util/Utils"; import { createVocabularyValueRenderer } from "../misc/treeselect/Renderers"; import { ThunkDispatch } from "../../util/Types"; import { loadVocabularies } from "../../action/AsyncActions"; +import { useI18n } from "../hook/useI18n"; +import Term, { TermData } from "../../model/Term"; +import { getLocalized } from "../../model/MultilingualString"; +import { getShortLocale } from "../../util/IntlUtil"; -interface ImportedVocabulariesListEditProps extends HasI18n { +interface ImportedVocabulariesListEditProps { vocabulary: Vocabulary; - vocabularies: { [key: string]: Vocabulary }; importedVocabularies?: AssetData[]; onChange: (change: object) => void; - loadVocabularies: () => void; } -export class ImportedVocabulariesListEdit extends React.Component { - public componentDidMount() { - if (Object.getOwnPropertyNames(this.props.vocabularies).length === 0) { - this.props.loadVocabularies(); - } - } +const ImportedVocabulariesListEdit: React.FC = + ({ vocabulary, importedVocabularies, onChange }) => { + const { i18n, locale } = useI18n(); + const vocabularies = useSelector( + (state: TermItState) => state.vocabularies + ); + const dispatch: ThunkDispatch = useDispatch(); + React.useEffect(() => { + if (Object.getOwnPropertyNames(vocabularies).length === 0) { + dispatch(loadVocabularies()); + } + }, [dispatch, vocabularies]); - public onChange = (selected: Vocabulary[]) => { - const selectedVocabs = selected.map((v) => ({ iri: v.iri })); - this.props.onChange({ importedVocabularies: selectedVocabs }); - }; + const onSelect = (selected: Vocabulary[]) => { + const selectedVocabs = selected.map((v) => ({ iri: v.iri })); + onChange({ importedVocabularies: selectedVocabs }); + }; - public render() { - const i18n = this.props.i18n; - const options = Object.keys(this.props.vocabularies) - .map((v) => this.props.vocabularies[v]) - .filter((v) => v.iri !== this.props.vocabulary.iri); - const selected = Utils.sanitizeArray(this.props.importedVocabularies).map( + const options = Object.keys(vocabularies) + .map((v) => vocabularies[v]) + .filter((v) => v.iri !== vocabulary.iri); + const selected = Utils.sanitizeArray(importedVocabularies).map( (v) => v.iri! ); return ( @@ -50,11 +54,13 @@ export class ImportedVocabulariesListEdit extends React.Component + getLocalized(option.label, getShortLocale(locale)) + } childrenKey="children" placeholder={i18n("select.placeholder")} classNamePrefix="react-select" @@ -70,14 +76,6 @@ export class ImportedVocabulariesListEdit extends React.Component ); - } -} + }; -export default connect( - (state: TermItState) => ({ vocabularies: state.vocabularies }), - (dispatch: ThunkDispatch) => { - return { - loadVocabularies: () => dispatch(loadVocabularies()), - }; - } -)(injectIntl(withI18n(ImportedVocabulariesListEdit))); +export default ImportedVocabulariesListEdit; diff --git a/src/component/vocabulary/__tests__/ImportedVocabulariesListEdit.test.tsx b/src/component/vocabulary/__tests__/ImportedVocabulariesListEdit.test.tsx index c55fe9dec..7244add51 100644 --- a/src/component/vocabulary/__tests__/ImportedVocabulariesListEdit.test.tsx +++ b/src/component/vocabulary/__tests__/ImportedVocabulariesListEdit.test.tsx @@ -2,27 +2,40 @@ import Vocabulary from "../../../model/Vocabulary"; import Generator from "../../../__tests__/environment/Generator"; import VocabularyUtils from "../../../util/VocabularyUtils"; import { shallow } from "enzyme"; -import { ImportedVocabulariesListEdit } from "../ImportedVocabulariesListEdit"; -import { intlFunctions } from "../../../__tests__/environment/IntlUtil"; +import ImportedVocabulariesListEdit from "../ImportedVocabulariesListEdit"; +import { mockUseI18n } from "../../../__tests__/environment/IntlUtil"; // @ts-ignore import { IntelligentTreeSelect } from "intelligent-tree-select"; +import { langString } from "../../../model/MultilingualString"; +import * as Redux from "react-redux"; +import { ThunkDispatch } from "src/util/Types"; +import * as AsyncActions from "../../../action/AsyncActions"; +import { loadVocabularies } from "../../../action/AsyncActions"; +import { mountWithIntl } from "../../../__tests__/environment/Environment"; + +jest.mock("react-redux", () => ({ + ...jest.requireActual("react-redux"), + useSelector: jest.fn(), + useDispatch: jest.fn(), +})); describe("ImportedVocabulariesListEdit", () => { let vocabularies: { [key: string]: Vocabulary }; let vocabulary: Vocabulary; let onChange: (change: object) => void; - let loadVocabularies: () => void; + + let fakeDispatch: ThunkDispatch; beforeEach(() => { const vOne = new Vocabulary({ iri: Generator.generateUri(), - label: "Vocabulary One", + label: langString("Vocabulary One"), types: [VocabularyUtils.VOCABULARY], }); const vTwo = new Vocabulary({ iri: Generator.generateUri(), - label: "Vocabulary two", + label: langString("Vocabulary two"), types: [VocabularyUtils.VOCABULARY], }); vocabularies = {}; @@ -30,21 +43,22 @@ describe("ImportedVocabulariesListEdit", () => { vocabularies[vTwo.iri] = vTwo; vocabulary = new Vocabulary({ iri: Generator.generateUri(), - label: "Edited vocabulary", + label: langString("Edited vocabulary"), }); vocabularies[vocabulary.iri] = vocabulary; onChange = jest.fn(); - loadVocabularies = jest.fn(); + fakeDispatch = jest.fn(); + jest.spyOn(Redux, "useDispatch").mockReturnValue(fakeDispatch); + jest.spyOn(AsyncActions, "loadVocabularies"); + mockUseI18n(); }); it("loads vocabularies after mount when they are not loaded yet", () => { - shallow( + jest.spyOn(Redux, "useSelector").mockReturnValue({}); + mountWithIntl( ); expect(loadVocabularies).toHaveBeenCalled(); @@ -54,29 +68,26 @@ describe("ImportedVocabulariesListEdit", () => { const wrapper = shallow( ); expect(wrapper.find(IntelligentTreeSelect).prop("value")).toEqual([]); }); it("calls onChange with selected vocabularies IRIs on vocabulary selection", () => { + jest.spyOn(Redux, "useSelector").mockReturnValue(vocabularies); const vocabularyArray = Object.keys(vocabularies).map( (v) => vocabularies[v] ); - const wrapper = shallow( + const wrapper = shallow( ); - wrapper.instance().onChange(vocabularyArray); + (wrapper.find(IntelligentTreeSelect).prop("onChange") as any)( + vocabularyArray + ); expect(onChange).toHaveBeenCalledWith({ importedVocabularies: vocabularyArray.map((v) => ({ iri: v.iri })), }); @@ -84,47 +95,22 @@ describe("ImportedVocabulariesListEdit", () => { it("calls onChange with empty array when vocabulary selector is reset", () => { const selected = Object.keys(vocabularies).map((k) => ({ iri: k })); - const wrapper = shallow( + const wrapper = shallow( ); - wrapper.instance().onChange([]); + (wrapper.find(IntelligentTreeSelect).prop("onChange") as any)([]); expect(onChange).toHaveBeenCalledWith({ importedVocabularies: [] }); }); - it("updates vocabulary selection when props are updated", () => { - const wrapper = shallow( - - ); - expect(wrapper.find(IntelligentTreeSelect).prop("value")).toEqual([]); - const newSelected = [{ iri: Object.keys(vocabularies)[0] }]; - wrapper.setProps({ importedVocabularies: newSelected }); - wrapper.update(); - expect(wrapper.find(IntelligentTreeSelect).prop("value")).toEqual([ - newSelected[0].iri, - ]); - }); - it("does not offer the vocabulary itself for importing", () => { const wrapper = shallow( ); const options: Vocabulary[] = wrapper