From d551c99357bb53ba00d6b4afb4d144f2720c1724 Mon Sep 17 00:00:00 2001
From: Jessie Keck <jessie.keck@gmail.com>
Date: Fri, 14 Jun 2019 10:13:02 -0700
Subject: [PATCH] Add the annotation resources label to the SearchHit
 component.

---
 __tests__/src/components/SearchHit.test.js | 14 ++++
 __tests__/src/selectors/searches.test.js   | 79 ++++++++++++++++++++++
 src/components/SearchHit.js                |  6 ++
 src/containers/SearchHit.js                | 15 ++--
 src/state/selectors/searches.js            | 26 +++++++
 5 files changed, 133 insertions(+), 7 deletions(-)

diff --git a/__tests__/src/components/SearchHit.test.js b/__tests__/src/components/SearchHit.test.js
index 3e4f62085..ba11e0b36 100644
--- a/__tests__/src/components/SearchHit.test.js
+++ b/__tests__/src/components/SearchHit.test.js
@@ -35,4 +35,18 @@ describe('SearchHit', () => {
     wrapper.find('WithStyles(ForwardRef(ListItem))').simulate('click');
     expect(selectContentSearchAnnotation).toHaveBeenCalledWith('window', ['foo']);
   });
+
+  describe('Annotation Labels', () => {
+    it('renders the annotationLabel if present', () => {
+      const wrapper = createWrapper({ annotationLabel: 'The Anno Label' });
+
+      expect(wrapper.find('WithStyles(ForwardRef(Typography))[variant="subtitle2"][children="The Anno Label"]').length).toEqual(1);
+    });
+
+    it('does not render the typography if no annotation label is present', () => {
+      const wrapper = createWrapper();
+
+      expect(wrapper.find('WithStyles(ForwardRef(Typography))[variant="subtitle2"]').length).toEqual(0);
+    });
+  });
 });
diff --git a/__tests__/src/selectors/searches.test.js b/__tests__/src/selectors/searches.test.js
index 33603b41e..e50ae15fd 100644
--- a/__tests__/src/selectors/searches.test.js
+++ b/__tests__/src/selectors/searches.test.js
@@ -5,6 +5,8 @@ import {
   getSelectedContentSearchAnnotationIds,
   getSelectedContentSearchAnnotations,
   getSearchAnnotationForCompanionWindow,
+  getResourceAnnotationForSearchHit,
+  getResourceAnnotationLabel,
 } from '../../../src/state/selectors';
 
 describe('getSearchResultsForWindow', () => {
@@ -186,3 +188,80 @@ describe('getSelectedContentSearchAnnotations', () => {
     ).toEqual([]);
   });
 });
+
+describe('getResourceAnnotationForSearchHit', () => {
+  const companionWindowId = 'cwid';
+  const annoId = 'annoId2';
+  it('returns the resource annotation connected to the hit by ID', () => {
+    const state = {
+      searches: {
+        a: {
+          [companionWindowId]: {
+            json: {
+              '@id': 'yolo',
+              resources: [{ '@id': annoId }],
+            },
+          },
+        },
+      },
+    };
+
+    expect(
+      getResourceAnnotationForSearchHit(
+        state, { annotationUri: annoId, companionWindowId, windowId: 'a' },
+      ).resource['@id'],
+    ).toEqual(annoId);
+  });
+});
+
+describe('getResourceAnnotationLabel', () => {
+  const companionWindowId = 'cwid';
+  const annoId = 'annoId2';
+  it('returns the label from a LanguageMap JSON object', () => {
+    const state = {
+      companionWindows: {
+        [companionWindowId]: { locale: 'en' },
+      },
+      searches: {
+        a: {
+          [companionWindowId]: {
+            json: {
+              '@id': 'yolo',
+              resources: [{
+                '@id': annoId,
+                label: { '@language': 'en', '@value': 'The Annotation Label' },
+              }],
+            },
+          },
+        },
+      },
+    };
+
+    expect(
+      getResourceAnnotationLabel(
+        state, { annotationUri: annoId, companionWindowId, windowId: 'a' },
+      ),
+    ).toEqual(['The Annotation Label']);
+  });
+
+  it('returns an empty array if the annotation resource does not have a label (to be consistent w/ the return of LanguageMap.parse)', () => {
+    const state = {
+      companionWindows: {
+        [companionWindowId]: { locale: 'en' },
+      },
+      searches: {
+        a: {
+          [companionWindowId]: {
+            json: { '@id': 'yolo', resources: [{ '@id': annoId }] },
+          },
+        },
+      },
+    };
+
+    expect(
+      getResourceAnnotationLabel(
+        state, { annotationUri: annoId, companionWindowId, windowId: 'a' },
+      ),
+    ).toEqual([]);
+  });
+});
diff --git a/src/components/SearchHit.js b/src/components/SearchHit.js
index 8e81154b3..6fed57af1 100644
--- a/src/components/SearchHit.js
+++ b/src/components/SearchHit.js
@@ -31,6 +31,7 @@ export class SearchHit extends Component {
   render() {
     const {
       adjacent,
+      annotationLabel,
       canvasLabel,
       classes,
       hit,
@@ -65,6 +66,9 @@ export class SearchHit extends Component {
             <Chip component="span" label={index + 1} className={classes.hitCounter} />
             {canvasLabel}
           </Typography>
+          {annotationLabel && (
+            <Typography variant="subtitle2">{annotationLabel}</Typography>
+          )}
           <SanitizedHtml ruleSet="iiif" htmlString={truncatedHit.before} />
           {' '}
           <strong>
@@ -86,6 +90,7 @@ export class SearchHit extends Component {
 
 SearchHit.propTypes = {
   adjacent: PropTypes.bool,
+  annotationLabel: PropTypes.string,
   canvasLabel: PropTypes.string,
   classes: PropTypes.objectOf(PropTypes.string),
   focused: PropTypes.bool,
@@ -105,6 +110,7 @@ SearchHit.propTypes = {
 
 SearchHit.defaultProps = {
   adjacent: false,
+  annotationLabel: undefined,
   canvasLabel: undefined,
   classes: {},
   focused: false,
diff --git a/src/containers/SearchHit.js b/src/containers/SearchHit.js
index 3cb7abf00..12401c9dc 100644
--- a/src/containers/SearchHit.js
+++ b/src/containers/SearchHit.js
@@ -7,8 +7,9 @@ import { SearchHit } from '../components/SearchHit';
 import * as actions from '../state/actions';
 import {
   getCanvasLabel,
-  getSearchAnnotationForCompanionWindow,
   getSelectedCanvases,
+  getResourceAnnotationForSearchHit,
+  getResourceAnnotationLabel,
   getSelectedContentSearchAnnotationIds,
 } from '../state/selectors';
 
@@ -18,17 +19,17 @@ import {
  * @private
  */
 const mapStateToProps = (state, { hit, companionWindowId, windowId }) => {
-  const annotation = getSearchAnnotationForCompanionWindow(
-    state, { companionWindowId, windowId },
+  const hitAnnotation = getResourceAnnotationForSearchHit(
+    state, { annotationUri: hit.annotations[0], companionWindowId, windowId },
+  );
+  const annotationLabel = getResourceAnnotationLabel(
+    state, { annotationUri: hit.annotations[0], companionWindowId, windowId },
   );
-
-  const resourceAnnotations = annotation.resources;
-  const hitAnnotation = resourceAnnotations.find(r => r.id === hit.annotations[0]);
-
   const selectedCanvasIds = getSelectedCanvases(state, { windowId }).map(canvas => canvas.id);
 
   return {
     adjacent: selectedCanvasIds.includes(hitAnnotation.targetId),
+    annotationLabel: annotationLabel[0],
     canvasLabel: hitAnnotation && getCanvasLabel(state, {
       canvasId: hitAnnotation.targetId,
       windowId,
diff --git a/src/state/selectors/searches.js b/src/state/selectors/searches.js
index 165c1c52d..f57602517 100644
--- a/src/state/selectors/searches.js
+++ b/src/state/selectors/searches.js
@@ -1,6 +1,8 @@
 import { createSelector } from 'reselect';
+import { LanguageMap } from 'manifesto.js';
 import Annotation from '../../lib/Annotation';
 import { getWindow } from './windows';
+import { getManifestLocale } from './manifests';
 
 export const getSearchResultsForWindow = createSelector(
   [
@@ -83,3 +85,27 @@ export const getSelectedContentSearchAnnotations = createSelector(
     ),
   })).filter(val => val.resources.length > 0),
 );
+
+export const getResourceAnnotationForSearchHit = createSelector(
+  [
+    getSearchAnnotationForCompanionWindow,
+    (state, { annotationUri }) => annotationUri,
+  ],
+  (searchAnnotations, annotationUri) => searchAnnotations.resources.find(
+    r => r.id === annotationUri,
+  ),
+);
+
+export const getResourceAnnotationLabel = createSelector(
+  [
+    getResourceAnnotationForSearchHit,
+    getManifestLocale,
+  ],
+  (resourceAnnotation, locale) => {
+    if (
+      !(resourceAnnotation && resourceAnnotation.resource && resourceAnnotation.resource.label)
+    ) return [];
+
+    return LanguageMap.parse(resourceAnnotation.resource.label, locale).map(label => label.value);
+  },
+);
-- 
GitLab