diff --git a/src/components/DataEntry/ExistingDataTable/ImmutableExistingData.tsx b/src/components/DataEntry/ExistingDataTable/ImmutableExistingData.tsx
index cfe67a86e5..259c8ff10e 100644
--- a/src/components/DataEntry/ExistingDataTable/ImmutableExistingData.tsx
+++ b/src/components/DataEntry/ExistingDataTable/ImmutableExistingData.tsx
@@ -1,17 +1,21 @@
import { Grid } from "@mui/material";
-import { ReactElement } from "react";
+import { CSSProperties, ReactElement } from "react";
import { Gloss } from "api/models";
import { TypographyWithFont } from "utilities/fontComponents";
+/** Style with a top dotted line if the index isn't 0. */
+function TopStyle(index: number, style?: "solid" | "dotted"): CSSProperties {
+ return index ? { borderTopStyle: style ?? "solid", borderTopWidth: 1 } : {};
+}
+
interface ImmutableExistingDataProps {
- gloss: Gloss;
+ glosses: Gloss[];
+ index: number;
vernacular: string;
}
-/**
- * Displays a word users cannot edit any more
- */
+/** Displays a word-sense that the user cannot edit. */
export default function ImmutableExistingData(
props: ImmutableExistingDataProps
): ReactElement {
@@ -19,13 +23,8 @@ export default function ImmutableExistingData(
{props.vernacular}
@@ -33,21 +32,20 @@ export default function ImmutableExistingData(
-
- {props.gloss.def}
-
+ {props.glosses.map((g, i) => (
+
+ {g.def}
+
+ ))}
);
diff --git a/src/components/DataEntry/ExistingDataTable/index.tsx b/src/components/DataEntry/ExistingDataTable/index.tsx
index 6404450829..0888c13f07 100644
--- a/src/components/DataEntry/ExistingDataTable/index.tsx
+++ b/src/components/DataEntry/ExistingDataTable/index.tsx
@@ -35,11 +35,12 @@ export default function ExistingDataTable(
const list = (): ReactElement => (
- {props.domainWords.map((domainWord) => (
+ {props.domainWords.map((w, i) => (
))}
diff --git a/src/components/DataEntry/ExistingDataTable/tests/ImmutableExistingData.test.tsx b/src/components/DataEntry/ExistingDataTable/tests/ImmutableExistingData.test.tsx
index 8e0703af83..a4e4b93f13 100644
--- a/src/components/DataEntry/ExistingDataTable/tests/ImmutableExistingData.test.tsx
+++ b/src/components/DataEntry/ExistingDataTable/tests/ImmutableExistingData.test.tsx
@@ -1,13 +1,17 @@
-import renderer from "react-test-renderer";
+import { act, create } from "react-test-renderer";
import ImmutableExistingData from "components/DataEntry/ExistingDataTable/ImmutableExistingData";
import { newGloss } from "types/word";
describe("ImmutableExistingData", () => {
- it("render without crashing", () => {
- renderer.act(() => {
- renderer.create(
-
+ it("renders", async () => {
+ await act(async () => {
+ create(
+
);
});
});
diff --git a/src/components/DataEntry/index.tsx b/src/components/DataEntry/index.tsx
index 8bc04848ec..6b37c8e8d0 100644
--- a/src/components/DataEntry/index.tsx
+++ b/src/components/DataEntry/index.tsx
@@ -13,10 +13,7 @@ import AppBar from "components/AppBar/AppBarComponent";
import DataEntryHeader from "components/DataEntry/DataEntryHeader";
import DataEntryTable from "components/DataEntry/DataEntryTable";
import ExistingDataTable from "components/DataEntry/ExistingDataTable";
-import {
- filterWordsByDomain,
- sortDomainWordsByVern,
-} from "components/DataEntry/utilities";
+import { filterWordsByDomain } from "components/DataEntry/utilities";
import TreeView from "components/TreeView";
import {
closeTreeAction,
@@ -46,6 +43,10 @@ const paperStyle = {
export default function DataEntry(): ReactElement {
const dispatch = useAppDispatch();
+ const analysisLang = useAppSelector(
+ (state: StoreState) =>
+ state.currentProjectState.project.analysisWritingSystems[0].bcp47
+ );
const { currentDomain, open } = useAppSelector(
(state: StoreState) => state.treeViewState
);
@@ -95,10 +96,11 @@ export default function DataEntry(): ReactElement {
}, [domain, questionsVisible, updateHeight, windowWidth]);
const returnControlToCaller = useCallback(async () => {
- const words = filterWordsByDomain(await getFrontierWords(), id);
- setDomainWords(sortDomainWordsByVern(words));
+ setDomainWords(
+ filterWordsByDomain(await getFrontierWords(), id, analysisLang)
+ );
dispatch(closeTreeAction());
- }, [dispatch, id]);
+ }, [analysisLang, dispatch, id]);
return (
diff --git a/src/components/DataEntry/tests/index.test.tsx b/src/components/DataEntry/tests/index.test.tsx
index a1f7f426ff..85a4ec1aff 100644
--- a/src/components/DataEntry/tests/index.test.tsx
+++ b/src/components/DataEntry/tests/index.test.tsx
@@ -8,6 +8,7 @@ import DataEntry, {
smallScreenThreshold,
treeViewDialogId,
} from "components/DataEntry";
+import { defaultState as currentProjectState } from "components/Project/ProjectReduxTypes";
import { openTreeAction } from "components/TreeView/Redux/TreeViewActions";
import {
TreeViewAction,
@@ -97,7 +98,7 @@ async function renderDataEntry(
spyOnUseWindowSize(windowWidth);
await renderer.act(async () => {
testHandle = renderer.create(
-
+
);
diff --git a/src/components/DataEntry/tests/utilities.test.ts b/src/components/DataEntry/tests/utilities.test.ts
index 5a72dcfe21..8b5c63f11f 100644
--- a/src/components/DataEntry/tests/utilities.test.ts
+++ b/src/components/DataEntry/tests/utilities.test.ts
@@ -1,46 +1,64 @@
-import { Status, Word } from "api/models";
+import { Sense, Status, Word } from "api/models";
import {
filterWordsByDomain,
filterWordsWithSenses,
- sortDomainWordsByVern,
} from "components/DataEntry/utilities";
import { newSemanticDomain } from "types/semanticDomain";
import { DomainWord, newSense, simpleWord } from "types/word";
const mockWord = simpleWord("vern", "gloss");
+const domainSense = (accessibility: Status, domainId?: string): Sense => {
+ const semanticDomains = [newSemanticDomain(domainId)];
+ return { ...newSense(), accessibility, semanticDomains };
+};
describe("DataEntryComponent", () => {
describe("filterWordsWithSenses", () => {
it("returns empty Word Array when given empty Word Array.", () => {
- const words: Word[] = [];
- const expectedWords: Word[] = [];
- expect(filterWordsWithSenses(words)).toEqual(expectedWords);
+ expect(filterWordsWithSenses([])).toEqual([]);
});
it("removes words with no Active/Protected sense.", () => {
const words: Word[] = [
- {
- ...mockWord,
- senses: [{ ...newSense(), accessibility: Status.Deleted }],
- },
- {
- ...mockWord,
- senses: [{ ...newSense(), accessibility: Status.Duplicate }],
- },
+ { ...mockWord, senses: [domainSense(Status.Deleted)] },
+ { ...mockWord, senses: [domainSense(Status.Duplicate)] },
];
expect(filterWordsWithSenses(words)).toHaveLength(0);
});
it("keeps words with an Active/Protected sense.", () => {
const words: Word[] = [
- mockWord,
- {
- ...mockWord,
- senses: [{ ...newSense(), accessibility: Status.Protected }],
- },
+ { ...mockWord, senses: [domainSense(Status.Active)] },
+ { ...mockWord, senses: [domainSense(Status.Protected)] },
];
expect(filterWordsWithSenses(words)).toHaveLength(2);
});
+
+ it("removes words with inactive sense even in specified domain.", () => {
+ const domId = "domain-id";
+ const words: Word[] = [
+ { ...mockWord, senses: [domainSense(Status.Deleted, domId)] },
+ { ...mockWord, senses: [domainSense(Status.Duplicate, domId)] },
+ ];
+ expect(filterWordsWithSenses(words, domId)).toHaveLength(0);
+ });
+
+ it("removes words with sense in wrong domain.", () => {
+ const words: Word[] = [
+ { ...mockWord, senses: [domainSense(Status.Active, "one wrong")] },
+ { ...mockWord, senses: [domainSense(Status.Protected, "other wrong")] },
+ ];
+ expect(filterWordsWithSenses(words, "right one")).toHaveLength(0);
+ });
+
+ it("keeps words with an Active/Protected sense in specified domain.", () => {
+ const domId = "domain-id";
+ const words: Word[] = [
+ { ...mockWord, senses: [domainSense(Status.Active, domId)] },
+ { ...mockWord, senses: [domainSense(Status.Protected, domId)] },
+ ];
+ expect(filterWordsWithSenses(words, domId)).toHaveLength(2);
+ });
});
describe("filterWordsByDomain", () => {
@@ -83,18 +101,4 @@ describe("DataEntryComponent", () => {
).toStrictEqual([new DomainWord(expectedWord, senseIndex)]);
});
});
-
- describe("sortDomainWordByVern", () => {
- it("sorts words alphabetically.", () => {
- const words = [mockWord, mockWord, mockWord].map(
- (w) => new DomainWord(w)
- );
- words[0].vernacular = "Always";
- words[1].vernacular = "Be";
- words[2].vernacular = "?character";
-
- const expectedList: DomainWord[] = [words[2], words[0], words[1]];
- expect(sortDomainWordsByVern([...words])).toStrictEqual(expectedList);
- });
- });
});
diff --git a/src/components/DataEntry/utilities.ts b/src/components/DataEntry/utilities.ts
index b9d438a0e2..1d641ae770 100644
--- a/src/components/DataEntry/utilities.ts
+++ b/src/components/DataEntry/utilities.ts
@@ -1,39 +1,51 @@
-import { Status, Word } from "api/models";
+import { Sense, Status, Word } from "api/models";
import { DomainWord } from "types/word";
-/** Filter out words that do not have at least 1 active sense */
-export function filterWordsWithSenses(words: Word[]): Word[] {
+/** Checks whether a sense is active
+ * (and in the specified domain if domainId is provided). */
+function isActiveInDomain(sense: Sense, domainId?: string): boolean {
+ return (
+ (!domainId || !!sense.semanticDomains.find((d) => d.id === domainId)) &&
+ // The undefined is for Statuses created before .accessibility was required.
+ [Status.Active, Status.Protected, undefined].includes(sense.accessibility)
+ );
+}
+
+/** Filter out words that do not have at least 1 active sense
+ * (and in the specified domain if domainId is provided). */
+export function filterWordsWithSenses(
+ words: Word[],
+ domainId?: string
+): Word[] {
return words.filter((w) =>
- w.senses.find((s) =>
- [Status.Active, Status.Protected].includes(s.accessibility)
- )
+ w.senses.find((s) => isActiveInDomain(s, domainId))
);
}
+/** Filter out sense's glosses with empty def
+ * (and if lang is specified, put glosses in that lang first). */
+function filterGlosses(sense: Sense, lang?: string): Sense {
+ const glosses = sense.glosses.filter((g) => g.def.trim());
+ if (lang) {
+ glosses.sort((a, b) => +(b.language === lang) - +(a.language === lang));
+ }
+ return { ...sense, glosses };
+}
+
export function filterWordsByDomain(
words: Word[],
- domainId: string
+ domainId: string,
+ lang?: string
): DomainWord[] {
const domainWords: DomainWord[] = [];
- for (const currentWord of words) {
- const senses = currentWord.senses.filter((s) =>
- // The undefined is for Statuses created before .accessibility was required in the frontend.
- [Status.Active, Status.Protected, undefined].includes(s.accessibility)
+ const wordsInDomain = filterWordsWithSenses(words, domainId);
+ wordsInDomain.sort((a, b) => a.vernacular.localeCompare(b.vernacular));
+ for (const w of wordsInDomain) {
+ domainWords.push(
+ ...w.senses
+ .filter((s) => isActiveInDomain(s, domainId))
+ .map((s) => new DomainWord({ ...w, senses: [filterGlosses(s, lang)] }))
);
- for (const sense of senses) {
- if (sense.semanticDomains.map((dom) => dom.id).includes(domainId)) {
- // Only the first gloss is shown, and no definitions.
- domainWords.push(new DomainWord({ ...currentWord, senses: [sense] }));
- }
- }
}
return domainWords;
}
-
-export function sortDomainWordsByVern(words: DomainWord[]): DomainWord[] {
- return words.sort(
- (a, b) =>
- a.vernacular.localeCompare(b.vernacular) ||
- a.gloss.def.localeCompare(b.gloss.def)
- );
-}
diff --git a/src/types/word.ts b/src/types/word.ts
index 68e0230423..c82dd30191 100644
--- a/src/types/word.ts
+++ b/src/types/word.ts
@@ -78,14 +78,14 @@ export class DomainWord {
wordGuid: string;
vernacular: string;
senseGuid: string;
- gloss: Gloss;
+ glosses: Gloss[];
- constructor(word: Word, senseIndex = 0, glossIndex = 0) {
+ constructor(word: Word, senseIndex = 0) {
const sense = word.senses[senseIndex] ?? newSense();
- this.gloss = sense.glosses[glossIndex] ?? newGloss();
this.wordGuid = word.guid;
this.vernacular = word.vernacular;
this.senseGuid = sense.guid;
+ this.glosses = sense.glosses;
}
}