diff --git a/README.md b/README.md index 6c8761ccd..af134e54b 100755 --- a/README.md +++ b/README.md @@ -252,6 +252,7 @@ The following are only in the resource subject (that is, the base subject). bfInstanceRefs: [uri of referenced Bibframe Instance resource, ...], bfItemRefs: [uri of referenced Bibframe Item resources, ...], sinopiaLocalAdminMetadataForRefs: [uri of referenced resources, ...] + defaultLang: } ``` @@ -287,7 +288,7 @@ The following are only in the resource subject (that is, the base subject). -> values: [{value},...] show: showNav: - rootResourceKey: + rootSubjectKey: rootPropertyKey: descUriOrLiteralValueKeys = [key of descendant uri or literal Value, ...] descWithErrorPropertyKeys = [key of descendant or self Property with an error, ...] diff --git a/__tests__/feature/editing/editingLanguage.test.js b/__tests__/feature/editing/editingLanguage.test.js index fe8a4dfdd..441b44b65 100644 --- a/__tests__/feature/editing/editingLanguage.test.js +++ b/__tests__/feature/editing/editingLanguage.test.js @@ -1,12 +1,12 @@ import { renderApp, createHistory } from "testUtils" -import { fireEvent, screen, within } from "@testing-library/react" +import { fireEvent, screen, within, waitFor } from "@testing-library/react" import { featureSetup } from "featureUtils" featureSetup() describe("editing a language", () => { - const history = createHistory(["/editor/resourceTemplate:testing:literal"]) it("allows selecting a language", async () => { + const history = createHistory(["/editor/resourceTemplate:testing:literal"]) renderApp(null, history) await screen.findByText("Literal", { @@ -84,4 +84,56 @@ describe("editing a language", () => { expect(scriptInput).toBeDisabled() expect(transliterationInput).toBeDisabled() }, 15000) + + it("allows selecting a default language", async () => { + const history = createHistory(["/editor/resourceTemplate:testing:inputs"]) + renderApp(null, history) + + await screen.findByText("Inputs", { + selector: "h3#resource-header", + }) + + // Add a value + const input = screen.getByPlaceholderText("Literal input") + fireEvent.change(input, { target: { value: "foo" } }) + fireEvent.keyDown(input, { key: "Enter", code: 13, charCode: 13 }) + + // There is language button. + const langBtn = screen.getByTestId("Change language for foo") + expect(langBtn).toHaveTextContent("en") + + fireEvent.click(langBtn) + // Using getByRole here and below because it limits to the visible modal. + screen.getByRole("heading", { name: "Select language tag for foo" }) + + expect( + screen.queryByText(/Make default for resource/) + ).not.toBeInTheDocument() + + const langInput = screen.getByTestId("langComponent-foo") + + // Select a language + fireEvent.click(langInput) + fireEvent.change(langInput, { target: { value: "Tai (taw)" } }) + fireEvent.click( + screen.getByText("Tai (taw)", { selector: ".rbt-highlight-text" }) + ) + + fireEvent.click(screen.getByText(/Make default for resource/)) + fireEvent.click(screen.getByTestId("Select language for foo")) + + await waitFor(() => { + expect( + screen.queryByRole("heading", { name: "Select language tag for foo" }) + ).not.toBeInTheDocument() + }) + + // Changed for this input and other inputs + expect(screen.getAllByText("taw", { selector: "button" })).toHaveLength(2) + + // Adding a new input also has default + fireEvent.click(screen.getByTestId("Add Literal input")) + await screen.findByText(/http:\/\/sinopia.io\/testing\/Inputs\/property4/) + expect(screen.getAllByText("taw", { selector: "button" })).toHaveLength(3) + }, 15000) }) diff --git a/__tests__/reducers/languages.test.js b/__tests__/reducers/languages.test.js index 31733b9f9..082c6208f 100644 --- a/__tests__/reducers/languages.test.js +++ b/__tests__/reducers/languages.test.js @@ -1,6 +1,10 @@ // Copyright 2020 Stanford University see LICENSE for license -import { languagesReceived, setLanguage } from "reducers/languages" +import { + languagesReceived, + setLanguage, + setDefaultLang, +} from "reducers/languages" import { createReducer } from "reducers/index" import { createState } from "stateUtils" @@ -8,6 +12,7 @@ import { createState } from "stateUtils" const reducers = { LANGUAGE_SELECTED: setLanguage, LANGUAGES_RECEIVED: languagesReceived, + SET_DEFAULT_LANG: setDefaultLang, } const reducer = createReducer(reducers) @@ -30,3 +35,23 @@ describe("setLanguage", () => { expect(newState.subjects[subjectKey].changed).toBeTruthy }) }) + +describe("setDefaultLang", () => { + it("sets default language on root subject and values", () => { + const oldState = createState({ hasResourceWithTwoNestedResources: true }) + oldState.entities.values["pRJ0lO_mU-"].literal = null + + const action = { + type: "SET_DEFAULT_LANG", + payload: { + resourceKey: "ljAblGiBW", + lang: "spa", + }, + } + + const newState = reducer(oldState.entities, action) + expect(newState.subjects.ljAblGiBW.defaultLang).toBe("spa") + expect(newState.values["pRJ0lO_mU-"].lang).toBe("spa") + expect(newState.values["pRJ0lO_mT-"].lang).toBe("en") + }) +}) diff --git a/__tests__/reducers/resources.test.js b/__tests__/reducers/resources.test.js index e33f8a431..c9066debd 100644 --- a/__tests__/reducers/resources.test.js +++ b/__tests__/reducers/resources.test.js @@ -343,6 +343,7 @@ describe("addSubject()", () => { labels: ["Barcode"], label: "Barcode", showNav: false, + defaultLang: "en", }, }) }) @@ -385,6 +386,7 @@ describe("addSubject()", () => { labels: ["Abbreviated Title"], label: "Abbreviated Title", showNav: false, + defaultLang: "en", }) }) }) @@ -438,6 +440,7 @@ describe("addSubject()", () => { labels: ["Abbreviated Title"], label: "Abbreviated Title", showNav: true, + defaultLang: "en", }) // Replaces values expect(newState.properties["KQEtq-vmq9"]).not.toBeUndefined() diff --git a/__tests__/testUtilities/stateResourceBuilderUtils.js b/__tests__/testUtilities/stateResourceBuilderUtils.js index 477c67d17..725b8d438 100644 --- a/__tests__/testUtilities/stateResourceBuilderUtils.js +++ b/__tests__/testUtilities/stateResourceBuilderUtils.js @@ -96,6 +96,7 @@ export default class StateResourceBuilder { localAdminMetadataForRefs = [], changed = false, label = null, + defaultLang = "en", ...props }) { return { @@ -111,6 +112,7 @@ export default class StateResourceBuilder { localAdminMetadataForRefs, changed, label: label || _.first(props?.labels), + defaultLang, } } diff --git a/src/actions/languages.js b/src/actions/languages.js index 1df5bd00e..50364dd11 100644 --- a/src/actions/languages.js +++ b/src/actions/languages.js @@ -21,3 +21,8 @@ export const languagesReceived = ( transliterationLookup, }, }) + +export const setDefaultLang = (resourceKey, lang) => ({ + type: "SET_DEFAULT_LANG", + payload: { resourceKey, lang }, +}) diff --git a/src/components/editor/inputs/InputLang.jsx b/src/components/editor/inputs/InputLang.jsx index 752dddd7c..a5cd32b82 100644 --- a/src/components/editor/inputs/InputLang.jsx +++ b/src/components/editor/inputs/InputLang.jsx @@ -4,13 +4,14 @@ import React, { useState, useEffect } from "react" import { Typeahead } from "react-bootstrap-typeahead" import { useSelector, useDispatch } from "react-redux" import { isCurrentModal, selectCurrentLangModalValue } from "selectors/modals" -import { languageSelected } from "actions/languages" +import { languageSelected, setDefaultLang } from "actions/languages" import { hideModal } from "actions/modals" import ModalWrapper from "components/ModalWrapper" import { selectLanguages, selectScripts, selectTransliterations, + selectDefaultLang, } from "selectors/languages" import { selectNormValue } from "selectors/resources" import { parseLangTag, stringifyLangTag } from "utilities/Language" @@ -26,11 +27,15 @@ const InputLang = () => { const transliterationOptions = useSelector((state) => selectTransliterations(state) ) + const resourceDefaultLang = useSelector((state) => + selectDefaultLang(state, value?.rootSubjectKey) + ) const textValue = value?.literal || value?.label || "" const [selectedLangOptions, setSelectedLanguageOptions] = useState([]) const [selectedScriptOptions, setSelectedScriptOptions] = useState([]) const [selectedTransliterationOptions, setSelectedTransliterationOptions] = useState([]) + const [isDefaultLang, setIsDefaultLang] = useState(false) const newTag = stringifyLangTag( _.first(selectedLangOptions)?.id, @@ -38,7 +43,10 @@ const InputLang = () => { _.first(selectedTransliterationOptions)?.id ) + const showDefaultLang = resourceDefaultLang !== newTag + useEffect(() => { + setIsDefaultLang(false) if (!value?.lang) return const [langSubtag, scriptSubtag, transliterationSubtag] = parseLangTag( value.lang @@ -98,6 +106,11 @@ const InputLang = () => { const handleLangSubmit = (event) => { close(event) dispatch(languageSelected(value.key, newTag)) + if (isDefaultLang) dispatch(setDefaultLang(value.rootSubjectKey, newTag)) + } + + const handleDefaultLangClick = () => { + setIsDefaultLang(!isDefaultLang) } const modal = ( @@ -112,10 +125,29 @@ const InputLang = () => {
Current tag:
{value?.lang || "None specified"}
-
+
New tag:
{newTag || "None specified"}
+
+
+
+ {showDefaultLang && ( +
+ + +
+ )} +
+