huridocs/uwazi

View on GitHub
app/react/Metadata/helpers/specs/formater.spec.js

Summary

Maintainability
A
0 mins
Test Coverage
/* eslint-disable max-statements */

import Immutable from 'immutable';
import moment from 'moment-timezone';

import { metadataSelectors } from '../../selectors';

import formater from '../formater';
import { doc, templates, thesauris, relationships } from './fixtures';

describe('metadata formater', () => {
  function assessBasicProperties(element, [label, name, translateContext, value]) {
    expect(element.label).toBe(label);
    expect(element.name).toBe(name);
    expect(element.translateContext).toBe(translateContext);

    if (value) {
      expect(element.value).toEqual(value);
    }
  }

  function assessMultiValues(element, values, secondaryDepth) {
    values.forEach((val, index) => {
      const value = secondaryDepth ? element.value[index].value : element.value[index];
      expect(value).toEqual(val);
    });
  }

  describe('prepareMetadata', () => {
    let data;
    let text;
    let date;
    let multiselect;
    let multidate;
    let daterange;
    let multidaterange;
    let markdown;
    let select;
    let selectWithCategory;
    let relationship1;
    let relationship2;
    let relationship3;
    let relationship4;
    let relationship5;
    let image;
    let preview;
    let media;
    let geolocation;
    let nested;
    let link;

    beforeAll(() => {
      data = formater.prepareMetadata(
        doc,
        templates,
        metadataSelectors.indexedThesaurus({ thesauris }),
        relationships,
        { sortedProperties: [] }
      );
      [
        text,
        date,
        multiselect,
        multidate,
        daterange,
        multidaterange,
        markdown,
        select,
        selectWithCategory,
        image,
        preview,
        media,
        relationship1,
        relationship2,
        relationship3,
        relationship4,
        relationship5,
        geolocation,
        nested,
        link,
      ] = data.metadata;
    });

    const formatValue = (value, originalValue) => ({
      icon: undefined,
      url: `/entity/${value.toLowerCase().replace(/ /g, '')}`,
      value,
      ...(originalValue ? { originalValue } : {}),
    });

    it('should maintain doc original data untouched', () => {
      expect(data.title).toBe(doc.title);
      expect(data.template).toBe(doc.template);
    });

    it('should process all metadata', () => {
      expect(data.metadata.length).toEqual(20);
    });

    it('should process text type', () => {
      assessBasicProperties(text, ['Text', 'text', 'templateID', 'text content']);
    });

    it('should process date type', () => {
      assessBasicProperties(date, ['Date', 'date', 'templateID']);
      expect(date.value).toContain('1970');
    });

    it('should process multiselect type', () => {
      assessBasicProperties(multiselect, ['Multiselect', 'multiselect', 'templateID']);
      expect(multiselect.value.length).toBe(4);
      assessMultiValues(multiselect, [
        expect.objectContaining({ value: 'Value 1' }),
        expect.objectContaining({ value: 'Value 2' }),
        {
          parent: 'Parent 1',
          value: [
            expect.objectContaining({ value: 'Value 5' }),
            expect.objectContaining({ value: 'Value 6' }),
          ],
        },
        {
          parent: 'Parent 2',
          value: [expect.objectContaining({ value: 'Value 7' })],
        },
      ]);
    });

    it('should process multidate type', () => {
      assessBasicProperties(multidate, ['Multi Date', 'multidate', 'templateID']);
      assessMultiValues(multidate, [
        { timestamp: 10, value: 'Jan 1, 1970' },
        { timestamp: 1000000, value: 'Jan 12, 1970' },
      ]);
      expect(multidate.label).toBe('Multi Date');
      expect(multidate.name).toBe('multidate');
      expect(multidate.value[0]).toEqual({ timestamp: 10, value: 'Jan 1, 1970' });
      expect(multidate.value[1]).toEqual({ timestamp: 1000000, value: 'Jan 12, 1970' });
    });

    it('should process daterange type', () => {
      assessBasicProperties(daterange, [
        'Date Range',
        'daterange',
        'templateID',
        'Jan 1, 1970 ~ Jan 12, 1970',
      ]);
    });

    it('should process multidaterange type', () => {
      assessBasicProperties(multidaterange, ['Multi Date Range', 'multidaterange', 'templateID']);
      assessMultiValues(
        multidaterange,
        ['Jan 1, 1970 ~ Jan 12, 1970', 'Jan 24, 1970 ~ Feb 4, 1970'],
        true
      );
    });

    it('should process markdown type', () => {
      assessBasicProperties(markdown, ['Mark Down', 'markdown', 'templateID', 'markdown content']);
      expect(markdown.type).toBe('markdown');
    });

    it('should process nested type', () => {
      assessBasicProperties(nested, ['Nested', 'nested', 'templateID']);
      expect(nested.type).toBe('markdown');
      expect(nested.value).toContain('nestedKey');
    });

    it('should process select type', () => {
      assessBasicProperties(select, ['Select', 'select', 'templateID', 'Value 5']);
    });

    it('should process select with category type', () => {
      expect(selectWithCategory).toEqual(
        expect.objectContaining({
          translateContext: 'templateID',
          name: 'selectWithCategory',
          content: 'thesauriId',
          type: 'select',
          label: 'selectWithCategory',
          value: 'Value 5',
          parent: 'Parent 1',
        })
      );
    });

    it('should process bound relationship types', () => {
      assessBasicProperties(relationship1, ['Relationship', 'relationship1', 'templateID']);
      expect(relationship1.value.length).toBe(2);
      assessMultiValues(relationship1, [
        formatValue('Value 1', 'value1'),
        formatValue('Value 2', 'value2'),
      ]);
    });

    it('should process free relationship types', () => {
      assessBasicProperties(relationship2, ['Relationship 2', 'relationship2', 'templateID']);
      expect(relationship2.value.length).toBe(3);
      assessMultiValues(relationship2, [
        formatValue('Value 1', 'value1'),
        formatValue('Value 2', 'value2'),
        formatValue('Value 4', 'value4'),
      ]);
    });

    describe('Inherit ', () => {
      it('should process inherit types', () => {
        assessBasicProperties(relationship3, ['Relationship 3', 'relationship3', 'templateID']);
        expect(relationship3.value.length).toBe(3);
        assessMultiValues(relationship3, [{ value: 'how' }, { value: 'are' }, { value: 'you?' }]);

        assessBasicProperties(relationship5, ['Relationship 5', 'relationship5', 'templateID']);
        expect(relationship5.value.length).toBe(5);
        assessMultiValues(relationship5, [
          expect.objectContaining({ value: 'Value 1' }),
          {
            parent: 'Parent 1',
            value: [expect.objectContaining({ value: 'Value 5' })],
          },
          expect.objectContaining({ value: 'Value 2' }),
          {
            parent: 'Parent 1',
            value: [expect.objectContaining({ value: 'Value 6' })],
          },
          {
            parent: 'Parent 2',
            value: [expect.objectContaining({ value: 'Value 7' })],
          },
        ]);
      });

      it('should not fail when inherited property is undefined', () => {
        const emptyDoc = {
          _id: 'languageSpecificId',
          template: 'templateID',
          title: 'Rude awakening',
          metadata: {},
        };

        expect(() =>
          formater.prepareMetadata(emptyDoc, templates, thesauris, relationships, {
            sortedProperties: [],
          })
        ).not.toThrow();
      });

      it('should not format as undefined value when there is no inherited value', () => {
        const docWithEmptyInherited = {
          _id: 'languageSpecificId',
          template: 'templateID',
          title: 'Rude awakening',
          metadata: {
            relationship3: [
              {
                value: 'value1',
                label: 'Value 1',
                inheritedValue: [],
              },
              {
                value: 'value2',
                label: 'Value 2',
                inheritedValue: [{ value: 'this one has a value' }],
              },
            ],
          },
        };

        const formatted = formater.prepareMetadata(
          docWithEmptyInherited,
          templates,
          thesauris,
          relationships,
          { sortedProperties: [] }
        );

        expect(
          formatted.metadata.find(m => m.name === 'relationship3').value.includes(undefined)
        ).toBe(false);
      });

      it('should not include empty inherited values', () => {
        const docWithInheritedEmptyValue = {
          _id: 'languageSpecificId',
          template: 'templateID',
          title: 'Rude awakening',
          metadata: {
            relationship3: [
              {
                value: 'value1',
                label: 'Value 1',
                inheritedValue: [{ value: '' }],
              },
              {
                value: 'value2',
                label: 'Value 2',
                inheritedValue: [{ value: 'this one has a value' }],
              },
            ],
          },
        };

        const formatted = formater.prepareMetadata(
          docWithInheritedEmptyValue,
          templates,
          thesauris,
          relationships,
          { sortedProperties: [] }
        );

        expect(formatted.metadata.find(m => m.name === 'relationship3').value.length).toBe(1);

        expect(formatted.metadata.find(m => m.name === 'relationship3').value[0]).toMatchObject({
          value: 'this one has a value',
        });
      });

      it('should append the translated entity title to certain values', () => {
        assessBasicProperties(relationship4, ['Relationship 4', 'relationship4', 'templateID']);
        expect(relationship4.value.length).toBe(3);
        assessMultiValues(relationship4, [
          { lat: 13, lon: 7, label: 'Entity 1 Title' },
          { lat: 5, lon: 10, label: 'Entity 2 Title (exisitng label)' },
          { lat: 23, lon: 8, label: 'Entity 2 Title (another label)' },
        ]);
      });
    });

    it('should process geolocation type', () => {
      assessBasicProperties(geolocation, ['Geolocation', 'geolocation', 'templateID']);
      expect(geolocation.value.length).toBe(2);
    });

    it('should process multimedia types', () => {
      expect(image.value).toBe('imageURL');
      expect(image.style).toBe('cover');
      expect(image.noLabel).toBe(true);
      expect(image.showInCard).toBe(true);

      expect(preview.type).toBe('image');
      expect(preview.value).toBe('/api/files/doc2.jpg');
      expect(preview.style).toBe('contain');
      expect(preview.noLabel).toBe(false);
      expect(preview.showInCard).toBe(true);

      expect(media.value).toBe('mediaURL');
      expect(media.noLabel).toBe(false);
      expect(media.showInCard).toBe(true);
    });

    it('should process link type', () => {
      expect(link).toEqual(
        expect.objectContaining({
          value: { label: 'link label', url: 'link url' },
          type: 'link',
        })
      );
    });

    it('should not fail when field do not exists on the document', () => {
      const clonedMetadata = Object.keys(doc.metadata).reduce(
        (memo, property) => ({ ...memo, [property]: doc.metadata[property] }),
        {}
      );

      const docCopy = { ...doc, metadata: clonedMetadata };

      docCopy.metadata.relationship1 = null;
      docCopy.metadata.multiselect = null;
      docCopy.metadata.select = null;
      docCopy.metadata.link = null;

      expect(() =>
        formater.prepareMetadata(docCopy, templates, thesauris, relationships, {
          sortedProperties: [],
        })
      ).not.toThrow();
    });

    it('should include the name of template', () => {
      expect(data.documentType).toBe('Mecanismo');
    });
  });

  describe('prepareMetadataForCard', () => {
    let data;

    let text;
    let markdown;
    let creationDate;
    let editDate;
    let image;
    let preview;
    let media;
    let geolocation;
    let link;

    beforeAll(() => {
      data = formater.prepareMetadataForCard(doc, templates, thesauris);
      [text, markdown, image, preview, media, geolocation, link] = data.metadata;
    });

    it('should process text type', () => {
      assessBasicProperties(text, ['Text', 'text', 'templateID', 'text content']);
    });

    it('should process markdown type', () => {
      assessBasicProperties(markdown, ['Mark Down', 'markdown', 'templateID', 'markdown content']);
    });

    it('should process multimedia type', () => {
      assessBasicProperties(image, ['Image', 'image', 'templateID', 'imageURL']);
      assessBasicProperties(preview, [
        'PDFPreview',
        'preview',
        'templateID',
        '/api/files/doc2.jpg',
      ]);
      assessBasicProperties(media, ['Media', 'media', 'templateID', 'mediaURL']);
    });

    it('should return empty value preview if no PDF associated to the entity', () => {
      const adaptedEntity = { ...doc, defaultDoc: undefined, documents: [] };
      const formatted = formater.prepareMetadata(
        adaptedEntity,
        templates,
        thesauris,
        relationships,
        { sortedProperties: [] }
      );

      const previewField = formatted.metadata.find(field => field.name === 'preview');

      expect(previewField.value).toBeNull();
    });

    it('should not include the preview field if excludePreview passed', () => {
      const formatted = formater.prepareMetadata(
        doc,
        templates,
        metadataSelectors.indexedThesaurus({ thesauris }),
        relationships,
        { excludePreview: true, sortedProperties: [] }
      );

      const previewField = formatted.metadata.find(field => field.name === 'preview');

      expect(previewField).toBeUndefined();
    });

    it('should process geolocation type', () => {
      assessBasicProperties(geolocation, [
        'Geolocation',
        'geolocation',
        'templateID',
        [
          { lat: 2, lon: 3 },
          { label: 'home', lat: 13, lon: 7 },
        ],
      ]);
      expect(geolocation.onlyForCards).toBe(true);
    });

    it('should process link type', () => {
      expect(link).toEqual(
        expect.objectContaining({
          value: { label: 'link label', url: 'link url' },
          type: 'link',
        })
      );
    });

    describe('when sort property passed', () => {
      let date;
      const prepareMetadata = dateString =>
        formater.prepareMetadataForCard(doc, templates, thesauris, dateString).metadata;
      it('should process also the sorted property even if its not a "showInCard"', () => {
        data = formater.prepareMetadataForCard(doc, templates, thesauris, 'metadata.date');
        [text, date, markdown] = data.metadata;
        assessBasicProperties(date, ['Date', 'date', 'templateID']);
        expect(data.metadata.length).toEqual(8);
        expect(date.value).toContain('1970');
      });

      it('should add sortedBy true to the property being sorted', () => {
        data = formater.prepareMetadataForCard(doc, templates, thesauris, 'metadata.date');
        [text, date, markdown] = data.metadata;
        expect(date.sortedBy).toBe(true);
        expect(text.sortedBy).toBe(false);
        expect(markdown.sortedBy).toBe(false);
      });

      describe('when sort property has no value', () => {
        it('should add No Value key and translateContext system to the property', () => {
          doc.metadata.date = '';
          data = formater.prepareMetadataForCard(doc, templates, thesauris, 'metadata.date');
          [text, date] = data.metadata;
          expect(date.value).toBe('No value');
          expect(date.translateContext).toBe('System');
        });
      });

      describe('creationDate', () => {
        it('should be formated using local time', async () => {
          moment.tz.setDefault('Europe/Madrid');
          let formated = prepareMetadata('creationDate');
          let formatedCreationDate = formated[formated.length - 1];
          expect(formatedCreationDate.value).toBe('Jan 1, 1970');

          moment.tz.setDefault('Pacific/Honolulu');
          formated = prepareMetadata('creationDate');
          formatedCreationDate = formated[formated.length - 1];
          expect(formatedCreationDate.value).toBe('Dec 31, 1969');
          moment.tz.setDefault();
        });
      });

      describe('editDate', () => {
        it('should be formated using local time', async () => {
          moment.tz.setDefault('Europe/Madrid');
          let formated = prepareMetadata('editDate');
          let formatedEditDate = formated[formated.length - 1];
          expect(formatedEditDate.value).toBe('Jan 1, 1970');

          moment.tz.setDefault('Pacific/Honolulu');
          formated = prepareMetadata('editDate');
          formatedEditDate = formated[formated.length - 1];
          expect(formatedEditDate.value).toBe('Dec 31, 1969');
          moment.tz.setDefault();
        });
      });

      describe('when sort property is creationDate', () => {
        it('should add it as a value to show', () => {
          [text, markdown, image, preview, media, geolocation, link, creationDate] =
            prepareMetadata('creationDate');
          expect(text.sortedBy).toBe(false);
          expect(markdown.sortedBy).toBe(false);
          expect(creationDate.sortedBy).toBe(true);
        });
      });

      describe('when sort property is editDate', () => {
        it('should add it as a value to show', () => {
          [text, markdown, image, preview, media, geolocation, link, editDate] =
            prepareMetadata('editDate');
          expect(text.sortedBy).toBe(false);
          expect(markdown.sortedBy).toBe(false);
          expect(editDate.sortedBy).toBe(true);
        });
      });

      describe('when sort property does not exists in the metadata', () => {
        it('should return a property with null as value', () => {
          const _templates = templates.push(
            Immutable.fromJS({
              _id: 'otherTemplate',
              properties: [{ name: 'nonexistent', type: 'date', label: 'NonExistentLabel' }],
            })
          );

          data = formater.prepareMetadataForCard(
            doc,
            _templates,
            thesauris,
            'metadata.nonexistent'
          );
          const nonexistent = data.metadata.find(p => p.name === 'nonexistent');
          assessBasicProperties(nonexistent, ['NonExistentLabel', 'nonexistent', 'otherTemplate']);
          expect(nonexistent.type).toBe(null);
        });

        it('should ignore non metadata properties', () => {
          const _templates = templates.push(
            Immutable.fromJS({
              properties: [{ name: 'nonexistent', type: 'date', label: 'NonExistentLabel' }],
            })
          );

          data = formater.prepareMetadataForCard(doc, _templates, thesauris, 'nonexistent');
          const nonexistent = data.metadata.find(p => p.name === 'nonexistent');
          expect(nonexistent).not.toBeDefined();
        });
      });
    });
  });

  describe('formatMetadata selector', () => {
    it('should use formater.prepareMetadata', () => {
      spyOn(formater, 'prepareMetadata').and.returnValue({ metadata: 'metadataFormated' });
      const state = {
        templates,
        thesauris,
        settings: Immutable.fromJS({ languages: [{ key: 'es', default: true }] }),
      };
      const metadata = metadataSelectors.formatMetadata(state, doc, null, relationships);
      expect(metadata).toBe('metadataFormated');
      expect(formater.prepareMetadata).toHaveBeenCalledWith(
        doc,
        templates,
        metadataSelectors.indexedThesaurus(state),
        relationships,
        undefined
      );
    });

    it('should exclude preview if option passed', () => {
      spyOn(formater, 'prepareMetadata').and.returnValue({ metadata: 'metadataFormated' });
      const state = {
        templates,
        thesauris,
        settings: Immutable.fromJS({ languages: [{ key: 'es', default: true }] }),
      };
      const metadata = metadataSelectors.formatMetadata(state, doc, null, relationships, {
        excludePreview: true,
      });
      expect(metadata).toBe('metadataFormated');
      expect(formater.prepareMetadata).toHaveBeenCalledWith(
        doc,
        templates,
        metadataSelectors.indexedThesaurus(state),
        relationships,
        { excludePreview: true }
      );
    });

    describe('when passing sortProperty', () => {
      it('should use formater.prepareMetadataForCard', () => {
        spyOn(formater, 'prepareMetadataForCard').and.returnValue({ metadata: 'metadataFormated' });
        const state = {
          templates,
          thesauris,
          settings: Immutable.fromJS({ languages: [{ key: 'es', default: true }] }),
        };
        const metadata = metadataSelectors.formatMetadata(
          state,
          doc,
          'sortProperty',
          relationships
        );
        expect(metadata).toBe('metadataFormated');
        expect(formater.prepareMetadataForCard).toHaveBeenCalledWith(
          doc,
          templates,
          metadataSelectors.indexedThesaurus(state),
          'sortProperty'
        );
      });
    });
  });
});