Skip to content

Commit

Permalink
Merge pull request #3375 from LD4P/t3372-default_lang
Browse files Browse the repository at this point in the history
  • Loading branch information
mjgiarlo authored Nov 29, 2021
2 parents 025f255 + 3055cd1 commit 0f7e277
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <default language tag>
}
```

Expand Down Expand Up @@ -287,7 +288,7 @@ The following are only in the resource subject (that is, the base subject).
-> values: [{value},...]
show: <true | false>
showNav: <true | false>
rootResourceKey: <key of root resource that this property is descendant of>
rootSubjectKey: <key of root resource that this property is descendant of>
rootPropertyKey: <key of root property that this subject is part of; for root property is own key>
descUriOrLiteralValueKeys = [key of descendant uri or literal Value, ...]
descWithErrorPropertyKeys = [key of descendant or self Property with an error, ...]
Expand Down
56 changes: 54 additions & 2 deletions __tests__/feature/editing/editingLanguage.test.js
Original file line number Diff line number Diff line change
@@ -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", {
Expand Down Expand Up @@ -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)
})
27 changes: 26 additions & 1 deletion __tests__/reducers/languages.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// 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"

const reducers = {
LANGUAGE_SELECTED: setLanguage,
LANGUAGES_RECEIVED: languagesReceived,
SET_DEFAULT_LANG: setDefaultLang,
}
const reducer = createReducer(reducers)

Expand All @@ -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")
})
})
3 changes: 3 additions & 0 deletions __tests__/reducers/resources.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ describe("addSubject()", () => {
labels: ["Barcode"],
label: "Barcode",
showNav: false,
defaultLang: "en",
},
})
})
Expand Down Expand Up @@ -385,6 +386,7 @@ describe("addSubject()", () => {
labels: ["Abbreviated Title"],
label: "Abbreviated Title",
showNav: false,
defaultLang: "en",
})
})
})
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions __tests__/testUtilities/stateResourceBuilderUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default class StateResourceBuilder {
localAdminMetadataForRefs = [],
changed = false,
label = null,
defaultLang = "en",
...props
}) {
return {
Expand All @@ -111,6 +112,7 @@ export default class StateResourceBuilder {
localAdminMetadataForRefs,
changed,
label: label || _.first(props?.labels),
defaultLang,
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/actions/languages.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ export const languagesReceived = (
transliterationLookup,
},
})

export const setDefaultLang = (resourceKey, lang) => ({
type: "SET_DEFAULT_LANG",
payload: { resourceKey, lang },
})
36 changes: 34 additions & 2 deletions src/components/editor/inputs/InputLang.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,19 +27,26 @@ 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,
_.first(selectedScriptOptions)?.id,
_.first(selectedTransliterationOptions)?.id
)

const showDefaultLang = resourceDefaultLang !== newTag

useEffect(() => {
setIsDefaultLang(false)
if (!value?.lang) return
const [langSubtag, scriptSubtag, transliterationSubtag] = parseLangTag(
value.lang
Expand Down Expand Up @@ -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 = (
Expand All @@ -112,10 +125,29 @@ const InputLang = () => {
<div className="col-sm-3">Current tag:</div>
<div className="col-sm-9">{value?.lang || "None specified"}</div>
</div>
<div className="row mb-4">
<div className="row">
<div className="col-sm-3">New tag:</div>
<div className="col-sm-9">{newTag || "None specified"}</div>
</div>
<div className="row mb-4">
<div className="col-sm-3"></div>
<div className="col-sm-9">
{showDefaultLang && (
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
checked={isDefaultLang}
id="defaultLang"
onChange={handleDefaultLangClick}
/>
<label className="form-check-label" htmlFor="defaultLang">
Make default for resource.
</label>
</div>
)}
</div>
</div>
<div className="row mb-1">
<label
className="col-sm-3 col-form-label"
Expand Down
6 changes: 6 additions & 0 deletions src/components/editor/inputs/InputLiteralOrURI.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
newBlankListValue,
} from "utilities/valueFactory"
import { selectProperty } from "selectors/resources"
import { selectDefaultLang } from "selectors/languages"

const InputLiteralOrURI = ({
property,
Expand All @@ -23,6 +24,9 @@ const InputLiteralOrURI = ({
const fullProperty = useSelector((state) =>
selectProperty(state, property.key)
)
const defaultLang = useSelector((state) =>
selectDefaultLang(state, property.rootSubjectKey)
)

const inputValues = fullProperty.values.map((value) => {
const props = {
Expand Down Expand Up @@ -52,6 +56,7 @@ const InputLiteralOrURI = ({
newValue = newBlankUriValue(
property,
propertyTemplate.languageSuppressed,
defaultLang,
propertyTemplate.defaultUri
)
break
Expand All @@ -65,6 +70,7 @@ const InputLiteralOrURI = ({
newValue = newBlankLiteralValue(
property,
propertyTemplate.languageSuppressed,
defaultLang,
propertyTemplate.defaultUri
)
}
Expand Down
12 changes: 8 additions & 4 deletions src/components/editor/inputs/InputLookupValue.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useRef, useEffect, useState } from "react"
import { useDispatch } from "react-redux"
import { useDispatch, useSelector } from "react-redux"
import PropTypes from "prop-types"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faSearch } from "@fortawesome/free-solid-svg-icons"
Expand All @@ -12,10 +12,11 @@ import DiacriticsSelection from "components/editor/diacritics/DiacriticsSelectio
import useDiacritics from "hooks/useDiacritics"
import DiacriticsButton from "./DiacriticsButton"
import Lookup from "./Lookup"
import Config from "Config"
import ResourceList from "../property/ResourceList"
import RemoveButton from "./RemoveButton"
import ValuePropertyURI from "../property/ValuePropertyURI"
import { selectDefaultLang } from "selectors/languages"
import { chooseLang } from "utilities/Language"
import _ from "lodash"

const InputLookupValue = ({
Expand All @@ -26,6 +27,9 @@ const InputLookupValue = ({
}) => {
const dispatch = useDispatch()
const inputRef = useRef(null)
const defaultLang = useSelector((state) =>
selectDefaultLang(state, value.rootSubjectKey)
)
const [focusHasBeenSet, setFocusHasBeenSet] = useState(false)
const [showLookup, setShowLookup] = useState(false)
// currentContent is what appears in the input. Query is sent to Lookup.
Expand Down Expand Up @@ -84,7 +88,7 @@ const InputLookupValue = ({
value.key,
null,
null,
propertyTemplate.languageSuppressed ? null : Config.defaultLanguageId,
chooseLang(propertyTemplate.languageSuppressed, defaultLang),
"InputURIValue"
)
)
Expand All @@ -111,7 +115,7 @@ const InputLookupValue = ({
updateLiteralValue(
value.key,
literal,
propertyTemplate.languageSuppressed ? null : Config.defaultLanguageId,
propertyTemplate.languageSuppressed ? null : defaultLang,
"InputLiteralValue"
)
)
Expand Down
3 changes: 2 additions & 1 deletion src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { combineReducers } from "redux"
import { setUser, removeUser } from "./authenticate"
import { setLanguage, languagesReceived } from "./languages"
import { setLanguage, languagesReceived, setDefaultLang } from "./languages"
import { groupsReceived } from "./groups"
import {
setBaseURL,
Expand Down Expand Up @@ -132,6 +132,7 @@ const entityHandlers = {
SAVE_RESOURCE_FINISHED: saveResourceFinished,
SET_BASE_URL: setBaseURL,
SET_CLASSES: setClasses,
SET_DEFAULT_LANG: setDefaultLang,
SET_VALUE_PROPERTY_URI: setValuePropertyURI,
SET_PROPERTY_PROPERTY_URI: setPropertyPropertyURI,
SET_RELATIONSHIPS: setRelationships,
Expand Down
23 changes: 23 additions & 0 deletions src/reducers/languages.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Copyright 2020 Stanford University see LICENSE for license
import { setSubjectChanged } from "reducers/resources"
import {
mergeSubjectPropsToNewState,
recursiveDescFromSubject,
} from "./resourceHelpers"
import _ from "lodash"

export const setLanguage = (state, action) => {
const valueKey = action.payload.valueKey
Expand All @@ -22,3 +27,21 @@ export const languagesReceived = (state, action) => ({
...state,
...action.payload,
})

export const setDefaultLang = (state, action) => {
const { resourceKey, lang } = action.payload
const newState = mergeSubjectPropsToNewState(state, resourceKey, {
defaultLang: lang,
})

const updateLang = (value) => {
if (value.component === "InputLiteralValue" && _.isEmpty(value.literal)) {
value.lang = lang
}
if (value.component === "InputURIValue" && _.isEmpty(value.label)) {
value.lang = lang
}
}

return recursiveDescFromSubject(newState, resourceKey, updateLang)
}
Loading

0 comments on commit 0f7e277

Please sign in to comment.