<commit_before>import React from "react";
import { Link } from "react-scroll";
import { Icon } from "react-fa";
import { Label } from "react-bootstrap";
import GenericModalMenuSection from "../commons/GenericModalMenuSection";

const BaederModalMenuHelpSection = ({ uiState, uiStateActions }) => {
  return (
    <GenericModalMenuSection
      uiState={uiState}
      uiStateActions={uiStateActions}
      sectionKey="help"
      sectionTitle="Hilfe"
      sectionBsStyle="success"
      sectionContent={

            <Link to="Datengrundlage" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              Datengrundlage{" "}
            </Link>

            <Link to="POIauswahluabfragen" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              Kitas auswählen und abfragen{" "}
            </Link>
            <Link to="InKartePositionieren" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              In Karte positionieren{" "}
            </Link>
            <Link to="MeinStandort" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              Mein Standort{" "}
            </Link>
            <Link to="Einstellungen" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              Einstellungen{" "}
            </Link>
            <Link to="Personalisierung" containerId="myMenu" style={{ textDecoration: "none" }}>
              {" "}
              Personalisierung{" "}
            </Link>

            Datengrundlage{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Die Bäder Karte Wuppertal benutzt als Kartengrundlage das Stadtplanwerk
            2.0 des Regionalverbandes Ruhrgebiet. Dieses innovative Kartenwerk kombiniert das
            Straßennetz der OpenStreetMap mit den Gebäuden und Flächennutzungen aus dem
            Fachverfahren ALKIS des Liegenschaftskatasters. Das Stadtplanwerk 2.0 wird wöchentlich
            in einem automatischen Prozess aktualisiert. Zusätzlich nutzt die Bäder Karte den
            Datensatz{" "}

              POI Wuppertal
            {" "}
            des Stadtbetriebs R102 aus dem Open-Data-Angebot der Stadt Wuppertal.

            Bäder auswählen und abfragen{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Bewegen Sie den Mauszeiger im Kartenfenster auf ein konkretes Einzel-Bad, um sich die
            strukturierte, eindeutige Kurzbezeichnung anzeigen zu lassen. Diese Bezeichnung besteht
            - sofern vorhanden - aus dem individuellen Namen der Kita, der Straße und dem Trägertyp.
            Ein Klick auf das zugehörige Kita-Symbol setzt den Fokus auf diese Einrichtung. Sie wird
            dann blau hinterlegt und die zugehörigen Informationen (Name, Straße und Hausnummer,
            Info-Text mit Angabe der Kapazität und des Trägertyps, Mindestaufnahmealter und
            angebotener Betreuungsumfang) werden unten rechts in der Info-Box angezeigt. (Auf einem
            Tablet-PC wird der Fokus durch das erste Antippen des Angebots gesetzt, das zweite
            Antippen blendet die Bezeichnung ein.) Außerdem werden Ihnen in der Info-Box
            weiterführende (Kommunikations-) Links zur Kita angezeigt: <Icon name="link" /> Internet
            und <Icon name="phone" /> Telefon.

            Wenn Sie noch nicht aktiv eine bestimmte Kita im aktuellen Kartenausschnitt selektiert
            haben, wird der Fokus automatisch auf die nördlichste Einrichtung gesetzt. Mit den
            Funktionen  vorheriger Treffer und{" "}
             nächster Treffer können Sie
            ausgehend von der Kita, auf der gerade der Fokus liegt, in nördlicher bzw. südlicher
            Richtung alle aktuell im Kartenfenster angezeigten Kitas durchmustern.

            In Karte positionieren{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Um die Kitas in einem bestimmten Bereich des Stadtgebietes zu erkunden, geben Sie den
            Anfang eines Stadtteils (Stadtbezirk oder Quartier), einer Adresse, eines Straßennamens
            oder eines Kita-Namens im Eingabefeld links unten ein (mindestens 2 Zeichen). In der
            inkrementellen Auswahlliste werden Ihnen passende Treffer angeboten. (Wenn Sie weitere
            Zeichen eingeben, wird der Inhalt der Auswahlliste angepasst.) Durch das vorangestellte
            Symbol erkennen Sie, ob es sich dabei um einen <Icon name="circle" /> Stadtbezirk, ein{" "}
            <Icon name="pie-chart" /> Quartier, eine <Icon name="home" /> Adresse, eine{" "}
            <Icon name="road" /> Straße ohne zugeordnete Hausnummern, einen <Icon name="tag" /> POI
            oder eine <Icon name="child" /> Kita handelt.

            Nach der Auswahl eines Treffers aus der Liste wird die Karte auf die zugehörige Position
            zentriert. Bei Suchbegriffen mit Punktgeometrie (Adresse, Straße, Kita) wird außerdem
            ein großer Maßstab (Zoomstufe 14) eingestellt und ein Marker{" "}
             auf der Zielposition platziert. Bei
            Suchbegriffen mit Flächengeometrie (Stadtbezirk, Quartier) wird der Maßstab so
            eingestellt, dass die Fläche vollständig dargestellt werden kann. Zusätzlich wird der
            Bereich außerhalb dieser Fläche abgedunkelt (Spotlight-Effekt).

            Durch Anklicken des Werkzeugs <Icon name="times" /> links neben dem Eingabefeld können
            Sie die Suche zurücksetzen (Entfernung von Marker bzw. Abdunklung, Löschen des Textes im
            Eingabefeld).

            Mein Standort{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Mit der Funktion Mein Standort <Icon name="map-marker" /> können Sie ihren aktuellen
            Standort mit einem blauen Kreissymbol{" "}
             in der Karte anzeigen. Das
            Standortsymbol ist umgeben von einem zweiten Kreis mit transparenter, blauer Füllung,
            dessen Radius die Unsicherheit der Positionsbestimmung angibt{" "}
            . Die Richtigkeit der
            Positionsanzeige ist dabei nicht garantiert, ihre Genauigkeit hängt davon ab, mit
            welcher Methode Ihr Endgerät und der von Ihnen verwendete Browser die Position
            bestimmen. Smartphones und Tablet-PC's sind i. d. R. mit einer GPS-Antenne ausgestattet,
            so dass Sie bei diesen Geräten eine Positionsgenauigkeit in der Größenordnung von 10
            Metern erwarten können. Die Markierung Ihrer Position wird laufend automatisch
            aktualisiert. Ein weiterer Klick auf "Mein Standort" schaltet die Anzeige Ihrer Position
            wieder ab.

            Einstellungen{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Unter "Einstellungen" können Sie im Anwendungsmenü <Icon name="bars" />{" "}
            festlegen, wie die Kitas und die Hintergrundkarte angezeigt werden sollen. Zu den Kitas
            können Sie auswählen, ob Ihre unter "Filtern" festgelegten
            Filterbedingungen in einer Titelzeile ausgeprägt werden oder nicht. Weiter können Sie
            festlegen, ob räumlich nah beieinander liegende Kitas maßstabsabhängig zu einem
            Punktsymbol zusammengefasst werden oder nicht. "

              Zeichenvorschrift

            " erlaubt es Ihnen, zwischen der standardmäßig aktivierten Zeichenvorschrift "
            nach Trägertyp" und der Alternative "
            nach Profil (Inklusionsschwerpunkt j/n)" zu wechseln. Unter "

              Symbolgröße

            " können Sie in Abhängigkeit von Ihrer Bildschirmauflösung und Ihrem Sehvermögen
            auswählen, ob die Kitas mit kleinen (25 Pixel), mittleren (35 Pixel) oder großen (45
            Pixel) Symbolen angezeigt werden.

            Unter "

              Kartendarstellung

            " können Sie auswählen, ob Sie die standardmäßig aktivierte farbige Hintergrundkarte
            verwenden möchten ("
            Tag
            ") oder lieber eine invertierte Graustufenkarte ("
            Nacht
            "), zu der uns die von vielen PKW-Navis bei Dunkelheit eingesetzte Darstellungsweise
            inspiriert hat. Hinweis: Diese Auswahl wird Ihnen nur angeboten, wenn
            Ihr Browser CSS3-Filtereffekte unterstützt, also z. B. nicht beim Microsoft Internet
            Explorer. Die Nacht-Karte erzeugt einen deutlicheren Kontrast mit den farbigen
            Kita-Symbolen, die unterschiedlichen Flächennutzungen in der Hintergrundkarte lassen
            sich aber nicht mehr so gut unterscheiden wie in der Tag-Karte.

Im Vorschaubild sehen Sie direkt die prinzipielle Wirkung ihrer Einstellungen.

            Personalisierung{" "}
            <Link to="help" containerId="myMenu" style={{ color: "#00000044" }}>
              <Icon name="arrow-circle-up" />
            </Link>

            Ihre Filterbedingungen und Einstellungen bleiben auch nach einem Neustart der Anwendung
            erhalten. (Es sei denn, Sie löschen den Browser-Verlauf einschließlich der gehosteten
            App-Daten.) Damit können Sie mit wenigen Klicks aus dem Kita-Finder Wuppertal einen
            dauerhaft für Sie optimierten Kita-Finder machen.

      }
    />
  );
};
export default BaederModalMenuHelpSection;
<commit_msg>:text: wo #32: adding the right help text<commit_after>import React from 'react';
import { Link } from 'react-scroll';
import { Icon } from 'react-fa';
import { Label } from 'react-bootstrap';
import GenericModalMenuSection from '../commons/GenericModalMenuSection';

const BaederModalMenuHelpSection = ({ uiState, uiStateActions }) => {
	return (
		<GenericModalMenuSection
			uiState={uiState}
			uiStateActions={uiStateActions}
			sectionKey="help"
			sectionTitle="Hilfe"
			sectionBsStyle="success"
			sectionContent={

						<Link to="Datengrundlage" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							Datengrundlage{' '}
						</Link>

						<Link to="POIauswahluabfragen" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							Kitas auswählen und abfragen{' '}
						</Link>
						<Link to="InKartePositionieren" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							In Karte positionieren{' '}
						</Link>
						<Link to="MeinStandort" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							Mein Standort{' '}
						</Link>
						<Link to="Einstellungen" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							Einstellungen{' '}
						</Link>
						<Link to="Personalisierung" containerId="myMenu" style={{ textDecoration: 'none' }}>
							{' '}
							Personalisierung{' '}
						</Link>

						Datengrundlage{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Die Bäder-Karte Wuppertal benutzt als Kartengrundlage das Stadtplanwerk 2.0 des
						Regionalverbandes Ruhrgebiet. Dieses innovative Kartenwerk kombiniert das Straßennetz der
						OpenStreetMap mit den Gebäuden und Flächennutzungen aus dem Fachverfahren ALKIS des
						Liegenschaftskatasters. Das Stadtplanwerk 2.0 wird wöchentlich in einem automatischen Prozess
						aktualisiert. Zusätzlich nutzt die Bäder-Karte den Datensatz{' '}

							POI Wuppertal
						{' '}
						des{' '}

							Ressorts Vermessung, Katasteramt und Geodaten
						{' '}
						aus dem Open-Data-Angebot der Stadt Wuppertal, der auch die Wuppertaler Schwimmbäder umfasst.

						Bäder auswählen und abfragen{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Bewegen Sie den Mauszeiger im Kartenfenster auf eines der Schwimmbad-Symbole, um sich den Namen
						des Bades anzeigen zu lassen. Ein Klick auf das Symbol setzt den Fokus auf dieses Schwimmbad. Es
						wird dann blau hinterlegt und die zugehörigen Informationen (Name, Kurzinformation, Straße und
						Hausnummer) werden unten rechts in der Info-Box angezeigt. (Auf einem Tablet-PC wird der Fokus
						durch das erste Antippen des Schwimmbad-Symbols gesetzt, das zweite Antippen blendet den Namen
						ein.) Außerdem werden Ihnen in der Info-Box weiterführende (Kommunikations-) Links zu diesem
						Schwimmbad angezeigt: <Icon name="link" /> Internet, <Icon name="envelope-square" /> E-Mail und{' '}
						<Icon name="phone" />
						Telefon. Durch Anklicken des Kalender-Symbols <Icon name="calendar" /> gelangen Sie bei einigen
						städtischen Bädern zum Aqua-Aktiv-Kursprogramm des Sport- und Bäderamtes. Dort können Sie online
						Fitnesskurse in diesen Bädern buchen.

						Wenn Sie noch kein Schwimmbad im aktuellen Kartenausschnitt selektiert haben, wird der Fokus
						automatisch auf das nördlichste Bad gesetzt. Mit den Funktionen{' '}
						 vorheriger Treffer und{' '}
						 nächster Treffer können Sie ausgehend von
						dem Schwimmbad, auf dem gerade der Fokus liegt, in nördlicher bzw. südlicher Richtung alle
						aktuell im Kartenfenster angezeigten Bäder durchmustern.

						Zu einigen Schwimmbädern bieten wir Ihnen Fotos oder Fotoserien an. Sie finden dann ein
						Vorschaubild direkt über der Info-Box. Klicken Sie auf das Vorschaubild, um einen Bildbetrachter
						("Leuchtkasten") mit dem Foto / der Fotoserie zu öffnen. Wenn wir hier auf Bildmaterial
						zugreifen, das der Urheber auch selbst im Internet publiziert, finden Sie im Fußbereich des
						Bildbetrachters einen Link auf dieses Angebot.

						In Karte positionieren{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Um die Schwimmbäder in einem bestimmten Bereich des Stadtgebietes zu erkunden, geben Sie den
						Anfang eines Stadtteils (Stadtbezirk oder Quartier), einer Adresse, eines Straßennamens oder
						eines POI im Eingabefeld links unten ein (mindestens 2 Zeichen). In der inkrementellen
						Auswahlliste werden Ihnen passende Treffer angeboten. (Wenn Sie weitere Zeichen eingeben, wird
						der Inhalt der Auswahlliste angepasst.) Durch das vorangestellte Symbol erkennen Sie, ob es sich
						dabei um einen <Icon name="circle" /> Stadtbezirk, ein <Icon name="pie-chart" /> Quartier, eine{' '}
						<Icon name="home" />
						Adresse, eine <Icon name="road" /> Straße ohne zugeordnete Hausnummern, einen{' '}
						<Icon name="tag" /> POI oder die <Icon name="tags" /> alternative Bezeichnung eines POI handelt.

						Nach der Auswahl eines Treffers aus der Liste wird die Karte auf die zugehörige Position
						zentriert. Bei Suchbegriffen mit Punktgeometrie (Adresse, Straße, POI) wird außerdem ein großer
						Maßstab (Zoomstufe 14) eingestellt und ein Marker{' '}
						 auf der Zielposition platziert. Bei
						Suchbegriffen mit Flächengeometrie (Stadtbezirk, Quartier) wird der Maßstab so eingestellt, dass
						die Fläche vollständig dargestellt werden kann. Zusätzlich wird der Bereich außerhalb dieser
						Fläche abgedunkelt (Spotlight-Effekt).

						Durch Anklicken des Werkzeugs <Icon name="times" /> links neben dem Eingabefeld können Sie die
						Suche zurücksetzen (Entfernung von Marker bzw. Abdunklung, Löschen des Textes im Eingabefeld).

						Da die Wuppertaler Schwimmbäder vollständig im POI-Datensatz enthalten sind, können Sie sich
						über alle gängigen Bezeichnungen dieser Schwimmbäder in der Karte positionieren. Dabei sind auch
						die Namen der Vereine, die ein Schwimmbad betreiben, als alternative Bezeichnungen des
						jeweiligen Bades hinterlegt. (Geben Sie z. B. einmal "Wasserfreunde" ein.)

						Mein Standort{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Mit der Funktion Mein Standort <Icon name="map-marker" /> können Sie ihren aktuellen Standort
						mit einem blauen Kreissymbol  in der
						Karte anzeigen. Das Standortsymbol ist umgeben von einem zweiten Kreis mit transparenter, blauer
						Füllung, dessen Radius die Unsicherheit der Positionsbestimmung angibt{' '}
						. Die Richtigkeit der
						Positionsanzeige ist dabei nicht garantiert, ihre Genauigkeit hängt davon ab, mit welcher
						Methode Ihr Endgerät und der von Ihnen verwendete Browser die Position bestimmen. Smartphones
						und Tablet-PC's sind i. d. R. mit einer GPS-Antenne ausgestattet, so dass Sie bei diesen Geräten
						eine Positionsgenauigkeit in der Größenordnung von 10 Metern erwarten können. Die Markierung
						Ihrer Position wird laufend automatisch aktualisiert. Ein weiterer Klick auf "Mein Standort"
						schaltet die Anzeige Ihrer Position wieder ab.

						Einstellungen{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Unter "Einstellungen" können Sie im Anwendungsmenü <Icon name="bars" />{' '}
						festlegen, wie die Kitas und die Hintergrundkarte angezeigt werden sollen. Zu den Kitas können
						Sie auswählen, ob Ihre unter "Filtern" festgelegten Filterbedingungen in einer
						Titelzeile ausgeprägt werden oder nicht. Weiter können Sie festlegen, ob räumlich nah
						beieinander liegende Kitas maßstabsabhängig zu einem Punktsymbol zusammengefasst werden oder
						nicht. "

							Zeichenvorschrift

						" erlaubt es Ihnen, zwischen der standardmäßig aktivierten Zeichenvorschrift "
						nach Trägertyp" und der Alternative "
						nach Profil (Inklusionsschwerpunkt j/n)" zu wechseln. Unter "

							Symbolgröße

						" können Sie in Abhängigkeit von Ihrer Bildschirmauflösung und Ihrem Sehvermögen auswählen, ob
						die Kitas mit kleinen (25 Pixel), mittleren (35 Pixel) oder großen (45 Pixel) Symbolen angezeigt
						werden.

						Unter "

							Kartendarstellung

						" können Sie auswählen, ob Sie die standardmäßig aktivierte farbige Hintergrundkarte verwenden
						möchten ("
						Tag
						") oder lieber eine invertierte Graustufenkarte ("
						Nacht
						"), zu der uns die von vielen PKW-Navis bei Dunkelheit eingesetzte Darstellungsweise inspiriert
						hat. Hinweis: Diese Auswahl wird Ihnen nur angeboten, wenn Ihr Browser
						CSS3-Filtereffekte unterstützt, also z. B. nicht beim Microsoft Internet Explorer. Die
						Nacht-Karte erzeugt einen deutlicheren Kontrast mit den farbigen Kita-Symbolen, die
						unterschiedlichen Flächennutzungen in der Hintergrundkarte lassen sich aber nicht mehr so gut
						unterscheiden wie in der Tag-Karte.

Im Vorschaubild sehen Sie direkt die prinzipielle Wirkung ihrer Einstellungen.

						Personalisierung{' '}
						<Link to="help" containerId="myMenu" style={{ color: '#00000044' }}>
							<Icon name="arrow-circle-up" />
						</Link>

						Ihre Filterbedingungen und Einstellungen bleiben auch nach einem Neustart der Anwendung
						erhalten. (Es sei denn, Sie löschen den Browser-Verlauf einschließlich der gehosteten
						App-Daten.) Damit können Sie mit wenigen Klicks aus dem Kita-Finder Wuppertal einen dauerhaft
						für Sie optimierten Kita-Finder machen.

			}
		/>
	);
};
export default BaederModalMenuHelpSection;
<|endoftext|>


<commit_before>const titleProperties = {
  'general.person': [
    'general.lastname',
    'general.firstname'
  ],
  'general.location': [
    'general.address'
  ],
  'general.case': [
    'publicsafety.case'
  ],
  'sample.person2': [
    'general.lastname',
    'general.firstname'
  ],
  'sample.bookings2': [
    'publicsafety.datebooked2'
  ],
  'nc.PersonType': [
    'nc.PersonSurName',
    'nc.PersonGivenName'
  ],
  'publicsafety.GangType': [
    'publicsafety.GangName'
  ],
  'general.LocationType': [
    'nc.LocationCityName',
    'nc.LocationStateName'
  ],
  'j.WarrantType': [
    'j.WarrantLevelText'
  ],
  'j.ServiceCallType': [
    'publicsafety.CadEventNumber'
  ],
  'j.BookingType': [
    'publicsafety.CustodyID',
    'j.SubjectBooking'
  ],
  'j.ChargeType': [
    'j.ArrestCharge',
    'publicsafety.datereleased2'
  ],
  'nc.PersonType2': [
    'nc.PersonSurName',
    'nc.PersonGivenName'
  ],
  'jciowa.JailBookingType2': [
    'publicsafety.datebooked2'
  ],
  'jciowa.JailRecordType2': [
    'publicsafety.ArrestID'
  ],
  'j.EnforcementOfficialType2': [
    'j.EnforcementOfficialCategoryText',
    'nc.PersonSurName'
  ],
  'jciowa.ChargeType': [
    'j.ChargeSequenceID',
    'j.CourtEventCase'
  ],
  'jciowa.ChargesType': [
    'j.ChargeSequenceID',
    'j.CourtEventCase'
  ],
  'jciowa.OffenseType': [
    'publicsafety.OffenseViolatedStateStatute',
    'publicsafety.OffenseViolatedLocalStatute'
  ],
  'jciowa.OffensesType': [
    'publicsafety.OffenseViolatedStateStatute',
    'publicsafety.OffenseViolatedLocalStatute'
  ],
  'jciowa.SentenceType': [
    'publicsafety.SentenceTermDays',
    'publicsafety.SentenceTermHours'
  ],
  'jciowa.SentencesType': [
    'publicsafety.SentenceTermDays',
    'publicsafety.SentenceTermHours'
  ]
};

function getFqn(edmObj) {
  return `${edmObj.type.namespace}.${edmObj.type.name}`;
}

function getFormattedRow(row, propertyTypes) {
  if (Object.keys(row)[0].includes('.')) return row;
  const convertedRow = {};
  propertyTypes.forEach((propertyType) => {
    if (row[propertyType.id]) convertedRow[getFqn(propertyType)] = row[propertyType.id];
  });
  return convertedRow;
}

export default function getTitle(entityType, row, propertyTypes) {
  const formattedRow = getFormattedRow(row, propertyTypes);
  const titleProps = titleProperties[getFqn(entityType)];
  if (titleProps) {
    const titleValues = titleProps.map((propertyTypeFqn) => {
      return formattedRow[propertyTypeFqn];
    });
    if (titleValues.length) return titleValues.join(', ');
  }
  return `[${entityType.title}]`;
}
<commit_msg>fix entity type titles<commit_after>const titleProperties = {
  // PERSON
  'general.person': [
    'nc.PersonSurName',
    'nc.PersonGivenName'
  ],
  'sample.person2': [
    'general.lastname',
    'general.firstname'
  ],
  'nc.PersonType': [
    'nc.PersonSurName',
    'nc.PersonGivenName'
  ],
  'nc.PersonType2': [
    'nc.PersonSurName',
    'nc.PersonGivenName'
  ],

  // CASE
  'general.case': [
    'justice.courtcasenumber'
  ],
  'justice.case': [
    'justice.courtcasenumber'
  ],
  'danecountywi.dacase': [
    'justice.courtcasenumber'
  ],

  // LOCATION
  'general.location': [
    'general.address'
  ],
  'general.LocationType': [
    'nc.LocationCityName',
    'nc.LocationStateName'
  ],

  // BOOKINGS
  'j.BookingType': [
    'publicsafety.CustodyID',
    'j.SubjectBooking'
  ],
  'sample.bookings2': [
    'publicsafety.datebooked2'
  ],
  'jciowa.JailBookingType2': [
    'publicsafety.datebooked2'
  ],
  'jciowa.BookingType': [
    'publicsafety.datebooked2'
  ],

  // CHARGES
  'j.ChargeType': [
    'j.ArrestCharge',
    'publicsafety.datereleased2'
  ],
  'jciowa.ChargeType': [
    'j.ChargeSequenceID',
    'j.CourtEventCase'
  ],
  'jciowa.ChargesType': [
    'j.ChargeSequenceID',
    'j.CourtEventCase'
  ],
  'jciowa.ChargesType2': [
    'j.ChargeSequenceID',
    'j.CourtEventCase'
  ],

  // JAIL RECORDS
  'jciowa.JailRecordType': [
    'publicsafety.ArrestID'
  ],
  'jciowa.JailRecordType2': [
    'publicsafety.ArrestID'
  ],

  // ENFORCEMENT OFFICIALS
  'j.EnforcementOfficialType': [
    'j.EnforcementOfficialCategoryText',
    'nc.PersonSurName'
  ],
  'j.EnforcementOfficialType2': [
    'j.EnforcementOfficialCategoryText',
    'nc.PersonSurName'
  ],

  // OFFENSE TYPES
  'jciowa.OffenseType': [
    'publicsafety.OffenseViolatedStateStatute',
    'publicsafety.OffenseViolatedLocalStatute'
  ],
  'jciowa.OffensesType': [
    'publicsafety.OffenseViolatedStateStatute',
    'publicsafety.OffenseViolatedLocalStatute'
  ],
  'jciowa.OffensesType2': [
    'publicsafety.OffenseViolatedStateStatute',
    'publicsafety.OffenseViolatedLocalStatute'
  ],

  // SENTENCE TYPES
  'jciowa.SentenceType': [
    'publicsafety.SentenceTermDays',
    'publicsafety.SentenceTermHours'
  ],
  'jciowa.SentencesType': [
    'publicsafety.SentenceTermDays',
    'publicsafety.SentenceTermHours'
  ],
  'jciowa.SentencesType2': [
    'publicsafety.SentenceTermDays',
    'publicsafety.SentenceTermHours'
  ],

  // OTHER
  'publicsafety.GangType': [
    'publicsafety.GangName'
  ],
  'j.WarrantType': [
    'j.WarrantLevelText'
  ],
  'j.ServiceCallType': [
    'publicsafety.CadEventNumber'
  ]
};

function getFqn(edmObj) {
  return `${edmObj.type.namespace}.${edmObj.type.name}`;
}

function getFormattedRow(row, propertyTypes) {
  if (Object.keys(row)[0].includes('.')) return row;
  const convertedRow = {};
  propertyTypes.forEach((propertyType) => {
    if (row[propertyType.id]) convertedRow[getFqn(propertyType)] = row[propertyType.id];
  });
  return convertedRow;
}

export default function getTitle(entityType, row, propertyTypes) {
  const formattedRow = getFormattedRow(row, propertyTypes);
  const titleProps = titleProperties[getFqn(entityType)];
  if (titleProps) {
    const titleValues = titleProps.map((propertyTypeFqn) => {
      return formattedRow[propertyTypeFqn];
    });
    if (titleValues.length) return titleValues.join(', ');
  }
  return `[${entityType.title}]`;
}
<|endoftext|>


<commit_before>describe('Backbone.Validator', function() {
  'use strict';

  var Validator = Backbone.Validator,
    fn, model, view, spy;

  var create = function(base, classOpts, instanceOpts) {
    var Model = base.extend(classOpts || {});
    return new Model(instanceOpts || {});
  };

  describe('#validators', function() {
    var expectToFail = function(validatorName, value, expectation, errorMessage) {
      var validations = { attr: {} };
      validations.attr[validatorName] = expectation;

      it('fails with ' + jasmine.pp(value) + ' and returns ' + jasmine.pp(errorMessage), function() {
        var errors = Validator.validate({attr: value}, validations);
        expect(errors.attr[0]).toBeDefined();
      });
    };

    var expectToPass = function(validatorName, value, expectation) {
      it('passes with ' + jasmine.pp(value), function() {
        var validations = { attr: {} };
        validations.attr[validatorName] = expectation;
        expect(Validator.validate({attr: value}, validations)).toEqual(null);
      });
    };

    describe('add', function() {
      beforeEach(function() {
        spy = jasmine.createSpy('validator');
        Validator.add('custom', spy);
      });

      it('passes expected value and all attributes being validated', function() {
        var attributes = { field_1: 1, field_2: 2 };

        model = create(Backbone.Model, {
          validation: {
            field_1: {
              custom: 10
            }
          }
        });

        model.set(attributes, {validate: true});
        expect(spy).toHaveBeenCalledWith(1, 10, attributes);
      });
    });

    describe('required', function() {
      expectToPass('required', 'Hello', true);
      expectToPass('required', true, true);
      expectToPass('required', undefined, false);
      expectToPass('required', 1, true);
      expectToFail('required', false, true);
      expectToFail('required', '', true);
      expectToFail('required', null, true);
      expectToFail('required', undefined, true);
    });

    describe('blank', function() {
      expectToPass('blank', 'Hello', false);
      expectToPass('blank', '   Hello', false);
      expectToPass('blank', '   Hello   ', false);
      expectToPass('blank', [1], false);
      expectToPass('blank', {a: 1}, false);
      expectToFail('blank', '', false);
      expectToFail('blank', '   ', false);
      expectToFail('blank', [], false);
      expectToFail('blank', {}, false);
    });

    describe('minLength', function() {
      expectToPass('minLength', 'Sam', 3);
      expectToPass('minLength', undefined, 3);
      expectToPass('minLength', '', 3);
      expectToFail('minLength', 'S', 3);
    });

    describe('maxLength', function() {
      expectToPass('maxLength', 'Sam', 3);
      expectToPass('maxLength', undefined, 3);
      expectToPass('maxLength', '', 3);
      expectToFail('maxLength', 'Samuel', 3);
    });

    describe('fn', function() {
      expectToPass('fn', 'Sam', function(value) {
        return value.length === 3;
      });

      expectToFail('fn', 'Samuel', function(value) {
        return value.length === 3;
      });

      expectToFail('fn', 'Samuel', function(value) {
        return value.length === 3 ? null : 'custom message';
      }, 'custom message');
    });

    describe('format', function() {
      expectToPass('format', 'a_b_c', /^([abc_]*)$/);
      expectToFail('format', 'a_b_c_d', /^([abc_]*)$/);

      expectToPass('format', '', 'email');
      expectToPass('format', 'user@example.com', 'email');
      expectToFail('format', 'user_example.com', 'email');

      expectToPass('format', 'http://example.com', 'url');
      expectToFail('format', 'http_example_com', 'url');

      expectToPass('format', '1234', 'digits');
      expectToFail('format', '1234a', 'digits');

      expectToPass('format', '123.789', 'number');
      expectToFail('format', '123.789a', 'number');
    });

    describe('collection', function() {
      var User = Backbone.Model.extend({
        validation: {
          name: {
            required: true
          }
        }
      });

      var Users = Backbone.Collection.extend({ model: User });

      expectToPass('collection', new Users([
        { name: 'Sam' },
        { name: 'Tom' },
        { name: 'Dan' }
      ]));

      expectToPass('collection', new Users([
        { name: 'Sam' },
        { name: '' },
        { name: '' }
      ]), false);

      expectToFail('collection', new Users([
        { name: 'Sam' },
        { name: '' },
        { name: '' }
      ]), true, [
        [1, { name: ['Is required'] }],
        [2, { name: ['Is required'] }]
      ]);

      expectToPass('collection', [
        new User({ name: 'Sam' }),
        new User({ name: 'Tom' }),
        new User({ name: 'Dan' })
      ]);

      expectToFail('collection', [
        new User({ name: 'Sam' }),
        new User({ name: '' }),
        new User({ name: '' })
      ], true, [
        [1, { name: ['Is required'] }],
        [2, { name: ['Is required'] }]
      ]);
    });
  });

  describe('#validate', function() {
    var attrs, validation;

    beforeEach(function() {
      attrs = { name: 'a' };
      validation = {
        name: {
          minLength: 3,
          message: function(attr, value, expectation, validator) {
            return 'Inline: ' + _.toArray(arguments).join(', ');
          }
        }
      };
    });

    it('allows error to be a function', function() {
      var errors = Validator.validate(attrs, validation);
      expect(errors).toEqual({ name: ['Inline: name, a, 3, minLength'] });
    });

    it('uses global error generator if specified', function() {
      Validator.createMessage = function(object, attr, value, expectation, validator) {
        return 'Global: ' + _.initial(arguments).join(', ');
      };

      validation.name.message = null;
      var errors = Validator.validate(attrs, validation);
      expect(errors).toEqual({ name: ['Global: name, a, 3, minLength'] });
    });
  });

  describe('Model', function() {
    beforeEach(function() {
      model = create(Backbone.Model, {
        validation: {
          field_1: {
            required: true,
            message: '#1 required'
          },

          field_2: {
            required: true,
            format: 'email',
            message: '#2 format'
          },

          field_3: [
            {
              required: true,
              message: '#3 required'
            },
            {
              message: '#3 from fn',
              fn: function(value) {
                return !!value ? true : '#3 overridden';
              }
            },
            {
              message: '#3 from config',
              fn: function(value) {
                return !!value;
              }
            }
          ]
        }
      });

      spyOn(model, 'sync');
    });

    describe('via #save', function() {
      it('validates passed attributes', function() {
        model.save({field_1: null});
        expect(model.validationError).toEqual({
          field_1: ['#1 required']
        });
      });

      it('validates passed attributes even if no validation for it', function() {
        model.save({field_8: 1, field_9: 1});
        expect(model.validationError).toBeNull();
      });

      it('validates entire model', function() {
        model.save();
        expect(model.validationError).toEqual({
          field_1: ['#1 required'],
          field_2: ['#2 format'],
          field_3: ['#3 required', '#3 from fn', '#3 from config']
        });
      });

      it('passes validation with proper values', function() {
        model.save({field_1: 1, field_2: 'user@example.com', field_3: 1 });
        expect(model.validationError).toBeNull();
      });

      it('allows `validation` to be a function', function() {
        model.validation = function() {
          return {
            field_1: {
              required: true,
              message: '#1 required'
            }
          };
        };

        model.save();
        expect(model.validationError).toEqual({ field_1: ['#1 required'] });
      });
    });

    describe('via #isValid', function() {
      it('validates passed attributes', function() {
        expect(model.isValid('field_1')).toBeFalsy();
      });

      it('validates passed attributes even if no validation for it', function() {
        expect(model.isValid()).toBeFalsy();
      });

      it('validates entire model', function() {
        expect(model.isValid()).toBeFalsy();
      });

      it('passes validation with proper values', function() {
        model.set({field_1: 1, field_2: 'user@example.com', field_3: 1 });
        expect(model.isValid()).toBeTruthy();
      });
    });

    describe('via #set', function() {
      it('validates passed attributes', function() {
        model.set({field_1: null}, {validate: true});
        expect(model.validationError).toEqual({
          field_1: ['#1 required']
        });
      });

      it('validates passed attributes even if no validation for it', function() {
        model.set({field_8: 1, field_9: 1}, {validate: true});
        expect(model.validationError).toBeNull();
      });

      it('passes validation with proper values', function() {
        model.set({field_1: 1, field_2: 'user@example.com', field_3: 1 }, {validate: true});
        expect(model.validationError).toBeNull();
      });
    });

    describe('trigger validation', function() {
      var valid, invalid;

      beforeEach(function() {
        valid = jasmine.createSpy('valid');
        invalid = jasmine.createSpy('invalid');
        model.on('validated:valid', valid);
        model.on('validated:invalid', invalid);
      });

      it('filters empty error values', function() {
        model.triggerValidated(null, { email: [], name: ['Too short'] });
        expect(invalid.argsForCall[0][2]).toEqual({ name: ['Too short'] });
      });

      it('fires validated:valid error if all errors are empty', function() {
        model.triggerValidated(null, { email: [], name: [] });
        expect(valid).toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });
    });
  });

  describe('View', function() {
    var valid, invalid;

    beforeEach(function() {
      valid = jasmine.createSpy('valid');
      invalid = jasmine.createSpy('invalid');

      model = create(Backbone.Model, {
        validation: {
          email: {
            format: 'email',
            message: 'Invalid email'
          },

          name: {
            minLength: 4
          }
        }
      });

      view = create(Backbone.View, {
        model: model
      });

      jasmine.Clock.useMock();
    });

    afterEach(function() {
      view.remove();
    });

    it('raises error if model does not exist', function() {
      view.model = null;
      expect(function() {
        view.bindValidation();
      }).toThrow('Model is not provided');
    });

    describe('when validating model', function() {
      var expectBinding = function(setup) {
        setup();

        model.validate({ email: 'user_example_com', name: 'Valid name' });
        jasmine.Clock.tick(50);

        expect(valid).toHaveBeenCalledWith('name', 'Valid name', model);
        expect(invalid).toHaveBeenCalledWith('email', 'user_example_com', ['Invalid email'], model);
      };

      it('runs callbacks from options if specified', function() {
        expectBinding(function() {
          view.bindValidation(model, {
            onValidField: valid,
            onInvalidField: invalid
          });
        });
      });

      it('runs callbacks from view instance if specified', function() {
        expectBinding(function() {
          view.bindValidation(model);
          _.extend(view, {
            onValidField: valid,
            onInvalidField: invalid
          });
        });
      });

      it('runs callbacks fallback from Backbone.Validator.ViewCallbacks', function() {
        expectBinding(function() {
          view.bindValidation(model);
          Validator.ViewCallbacks = {
            onValidField: valid,
            onInvalidField: invalid
          };
        });
      });
    });

    describe('when view removed', function() {
      it('unbinds `validated` events', function() {
        view.bindValidation(model, {
          onValidField: valid,
          onInvalidField: invalid

        });

        view.remove();
        model.validate();
        jasmine.Clock.tick(50);

        expect(valid).not.toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });

      it('unbinds only `validated` event', function() {
        spy = jasmine.createSpy('spy');
        model.on('validated', spy);
        view.bindValidation(model, {
          onValidField: valid,
          onInvalidField: invalid
        });
        view.remove();

        model.validate({ email: 'user@example.com' });
        jasmine.Clock.tick(50);

        expect(spy).toHaveBeenCalled();
        expect(valid).not.toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });
    });
  });
});
<commit_msg>Ensure that spec does check error message if needed<commit_after>describe('Backbone.Validator', function() {
  'use strict';

  var Validator = Backbone.Validator,
    fn, model, view, spy;

  var create = function(base, classOpts, instanceOpts) {
    var Model = base.extend(classOpts || {});
    return new Model(instanceOpts || {});
  };

  describe('#validators', function() {
    var expectToFail = function(validatorName, value, expectation, errorMessage) {
      var validations = { attr: {} };
      validations.attr[validatorName] = expectation;

      it('fails with ' + jasmine.pp(value) + ' and returns ' + jasmine.pp(errorMessage), function() {
        var errors = Validator.validate({attr: value}, validations);

        errorMessage ?
          expect(errors.attr[0]).toEqual(errorMessage) :
          expect(errors.attr[0]).toBeDefined();
      });
    };

    var expectToPass = function(validatorName, value, expectation) {
      it('passes with ' + jasmine.pp(value), function() {
        var validations = { attr: {} };
        validations.attr[validatorName] = expectation;
        expect(Validator.validate({attr: value}, validations)).toEqual(null);
      });
    };

    describe('add', function() {
      beforeEach(function() {
        spy = jasmine.createSpy('validator');
        Validator.add('custom', spy);
      });

      it('passes expected value and all attributes being validated', function() {
        var attributes = { field_1: 1, field_2: 2 };

        model = create(Backbone.Model, {
          validation: {
            field_1: {
              custom: 10
            }
          }
        });

        model.set(attributes, {validate: true});
        expect(spy).toHaveBeenCalledWith(1, 10, attributes);
      });
    });

    describe('required', function() {
      expectToPass('required', 'Hello', true);
      expectToPass('required', true, true);
      expectToPass('required', undefined, false);
      expectToPass('required', 1, true);
      expectToFail('required', false, true);
      expectToFail('required', '', true);
      expectToFail('required', null, true);
      expectToFail('required', undefined, true);
    });

    describe('blank', function() {
      expectToPass('blank', 'Hello', false);
      expectToPass('blank', '   Hello', false);
      expectToPass('blank', '   Hello   ', false);
      expectToPass('blank', [1], false);
      expectToPass('blank', {a: 1}, false);
      expectToFail('blank', '', false);
      expectToFail('blank', '   ', false);
      expectToFail('blank', [], false);
      expectToFail('blank', {}, false);
    });

    describe('minLength', function() {
      expectToPass('minLength', 'Sam', 3);
      expectToPass('minLength', undefined, 3);
      expectToPass('minLength', '', 3);
      expectToFail('minLength', 'S', 3);
    });

    describe('maxLength', function() {
      expectToPass('maxLength', 'Sam', 3);
      expectToPass('maxLength', undefined, 3);
      expectToPass('maxLength', '', 3);
      expectToFail('maxLength', 'Samuel', 3);
    });

    describe('fn', function() {
      expectToPass('fn', 'Sam', function(value) {
        return value.length === 3;
      });

      expectToFail('fn', 'Samuel', function(value) {
        return value.length === 3;
      });

      expectToFail('fn', 'Samuel', function(value) {
        return value.length === 3 ? null : 'custom message';
      }, 'custom message');
    });

    describe('format', function() {
      expectToPass('format', 'a_b_c', /^([abc_]*)$/);
      expectToFail('format', 'a_b_c_d', /^([abc_]*)$/);

      expectToPass('format', '', 'email');
      expectToPass('format', 'user@example.com', 'email');
      expectToFail('format', 'user_example.com', 'email');

      expectToPass('format', 'http://example.com', 'url');
      expectToFail('format', 'http_example_com', 'url');

      expectToPass('format', '1234', 'digits');
      expectToFail('format', '1234a', 'digits');

      expectToPass('format', '123.789', 'number');
      expectToFail('format', '123.789a', 'number');
    });

    describe('collection', function() {
      var User = Backbone.Model.extend({
        validation: {
          name: {
            required: true
          }
        }
      });

      var Users = Backbone.Collection.extend({ model: User });

      expectToPass('collection', new Users([
        { name: 'Sam' },
        { name: 'Tom' },
        { name: 'Dan' }
      ]));

      expectToPass('collection', new Users([
        { name: 'Sam' },
        { name: '' },
        { name: '' }
      ]), false);

      expectToFail('collection', new Users([
        { name: 'Sam' },
        { name: '' },
        { name: '' }
      ]), true, [
        [1, { name: ['Is required'] }],
        [2, { name: ['Is required'] }]
      ]);

      expectToPass('collection', [
        new User({ name: 'Sam' }),
        new User({ name: 'Tom' }),
        new User({ name: 'Dan' })
      ]);

      expectToFail('collection', [
        new User({ name: 'Sam' }),
        new User({ name: '' }),
        new User({ name: '' })
      ], true, [
        [1, { name: ['Is required'] }],
        [2, { name: ['Is required'] }]
      ]);
    });
  });

  describe('#validate', function() {
    var attrs, validation;

    beforeEach(function() {
      attrs = { name: 'a' };
      validation = {
        name: {
          minLength: 3,
          message: function(attr, value, expectation, validator) {
            return 'Inline: ' + _.toArray(arguments).join(', ');
          }
        }
      };
    });

    it('allows error to be a function', function() {
      var errors = Validator.validate(attrs, validation);
      expect(errors).toEqual({ name: ['Inline: name, a, 3, minLength'] });
    });

    it('uses global error generator if specified', function() {
      Validator.createMessage = function(object, attr, value, expectation, validator) {
        return 'Global: ' + _.initial(arguments).join(', ');
      };

      validation.name.message = null;
      var errors = Validator.validate(attrs, validation);
      expect(errors).toEqual({ name: ['Global: name, a, 3, minLength'] });
    });
  });

  describe('Model', function() {
    beforeEach(function() {
      model = create(Backbone.Model, {
        validation: {
          field_1: {
            required: true,
            message: '#1 required'
          },

          field_2: {
            required: true,
            format: 'email',
            message: '#2 format'
          },

          field_3: [
            {
              required: true,
              message: '#3 required'
            },
            {
              message: '#3 from fn',
              fn: function(value) {
                return !!value ? true : '#3 overridden';
              }
            },
            {
              message: '#3 from config',
              fn: function(value) {
                return !!value;
              }
            }
          ]
        }
      });

      spyOn(model, 'sync');
    });

    describe('via #save', function() {
      it('validates passed attributes', function() {
        model.save({field_1: null});
        expect(model.validationError).toEqual({
          field_1: ['#1 required']
        });
      });

      it('validates passed attributes even if no validation for it', function() {
        model.save({field_8: 1, field_9: 1});
        expect(model.validationError).toBeNull();
      });

      it('validates entire model', function() {
        model.save();
        expect(model.validationError).toEqual({
          field_1: ['#1 required'],
          field_2: ['#2 format'],
          field_3: ['#3 required', '#3 from fn', '#3 from config']
        });
      });

      it('passes validation with proper values', function() {
        model.save({field_1: 1, field_2: 'user@example.com', field_3: 1 });
        expect(model.validationError).toBeNull();
      });

      it('allows `validation` to be a function', function() {
        model.validation = function() {
          return {
            field_1: {
              required: true,
              message: '#1 required'
            }
          };
        };

        model.save();
        expect(model.validationError).toEqual({ field_1: ['#1 required'] });
      });
    });

    describe('via #isValid', function() {
      it('validates passed attributes', function() {
        expect(model.isValid('field_1')).toBeFalsy();
      });

      it('validates passed attributes even if no validation for it', function() {
        expect(model.isValid()).toBeFalsy();
      });

      it('validates entire model', function() {
        expect(model.isValid()).toBeFalsy();
      });

      it('passes validation with proper values', function() {
        model.set({field_1: 1, field_2: 'user@example.com', field_3: 1 });
        expect(model.isValid()).toBeTruthy();
      });
    });

    describe('via #set', function() {
      it('validates passed attributes', function() {
        model.set({field_1: null}, {validate: true});
        expect(model.validationError).toEqual({
          field_1: ['#1 required']
        });
      });

      it('validates passed attributes even if no validation for it', function() {
        model.set({field_8: 1, field_9: 1}, {validate: true});
        expect(model.validationError).toBeNull();
      });

      it('passes validation with proper values', function() {
        model.set({field_1: 1, field_2: 'user@example.com', field_3: 1 }, {validate: true});
        expect(model.validationError).toBeNull();
      });
    });

    describe('trigger validation', function() {
      var valid, invalid;

      beforeEach(function() {
        valid = jasmine.createSpy('valid');
        invalid = jasmine.createSpy('invalid');
        model.on('validated:valid', valid);
        model.on('validated:invalid', invalid);
      });

      it('filters empty error values', function() {
        model.triggerValidated(null, { email: [], name: ['Too short'] });
        expect(invalid.argsForCall[0][2]).toEqual({ name: ['Too short'] });
      });

      it('fires validated:valid error if all errors are empty', function() {
        model.triggerValidated(null, { email: [], name: [] });
        expect(valid).toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });
    });
  });

  describe('View', function() {
    var valid, invalid;

    beforeEach(function() {
      valid = jasmine.createSpy('valid');
      invalid = jasmine.createSpy('invalid');

      model = create(Backbone.Model, {
        validation: {
          email: {
            format: 'email',
            message: 'Invalid email'
          },

          name: {
            minLength: 4
          }
        }
      });

      view = create(Backbone.View, {
        model: model
      });

      jasmine.Clock.useMock();
    });

    afterEach(function() {
      view.remove();
    });

    it('raises error if model does not exist', function() {
      view.model = null;
      expect(function() {
        view.bindValidation();
      }).toThrow('Model is not provided');
    });

    describe('when validating model', function() {
      var expectBinding = function(setup) {
        setup();

        model.validate({ email: 'user_example_com', name: 'Valid name' });
        jasmine.Clock.tick(50);

        expect(valid).toHaveBeenCalledWith('name', 'Valid name', model);
        expect(invalid).toHaveBeenCalledWith('email', 'user_example_com', ['Invalid email'], model);
      };

      it('runs callbacks from options if specified', function() {
        expectBinding(function() {
          view.bindValidation(model, {
            onValidField: valid,
            onInvalidField: invalid
          });
        });
      });

      it('runs callbacks from view instance if specified', function() {
        expectBinding(function() {
          view.bindValidation(model);
          _.extend(view, {
            onValidField: valid,
            onInvalidField: invalid
          });
        });
      });

      it('runs callbacks fallback from Backbone.Validator.ViewCallbacks', function() {
        expectBinding(function() {
          view.bindValidation(model);
          Validator.ViewCallbacks = {
            onValidField: valid,
            onInvalidField: invalid
          };
        });
      });
    });

    describe('when view removed', function() {
      it('unbinds `validated` events', function() {
        view.bindValidation(model, {
          onValidField: valid,
          onInvalidField: invalid

        });

        view.remove();
        model.validate();
        jasmine.Clock.tick(50);

        expect(valid).not.toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });

      it('unbinds only `validated` event', function() {
        spy = jasmine.createSpy('spy');
        model.on('validated', spy);
        view.bindValidation(model, {
          onValidField: valid,
          onInvalidField: invalid
        });
        view.remove();

        model.validate({ email: 'user@example.com' });
        jasmine.Clock.tick(50);

        expect(spy).toHaveBeenCalled();
        expect(valid).not.toHaveBeenCalled();
        expect(invalid).not.toHaveBeenCalled();
      });
    });
  });
});
<|endoftext|>


<commit_before>export default class ContentController {
  constructor(dimActivityTrackerService, dimState, ngDialog, $rootScope, loadingTracker, dimPlatformService, $interval, hotkeys, $timeout, dimStoreService, dimXurService, dimSettingsService, $window, $scope, $state, dimFeatureFlags, dimVendorService) {
    'ngInject';

    var vm = this;

    // Variables for templates that webpack does not automatically correct.
    vm.$DIM_VERSION = $DIM_VERSION;
    vm.$DIM_FLAVOR = $DIM_FLAVOR;
    vm.$DIM_CHANGELOG = $DIM_CHANGELOG;

    vm.loadingTracker = loadingTracker;
    vm.platforms = [];

    vm.platformChange = function platformChange(platform) {
      loadingTracker.addPromise(dimPlatformService.setActive(platform));
    };

    $scope.$on('dim-platforms-updated', function(e, args) {
      vm.platforms = args.platforms;
    });

    $scope.$on('dim-active-platform-updated', function(e, args) {
      dimState.active = vm.currentPlatform = args.platform;
    });

    loadingTracker.addPromise(dimPlatformService.getPlatforms());

    vm.settings = dimSettingsService;
    $scope.$watch(() => { return vm.settings.itemSize; }, function(size) {
      document.querySelector('html').style.setProperty("--item-size", size + 'px');
    });
    $scope.$watch(() => { return vm.settings.charCol; }, function(cols) {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });

    $scope.$watch(() => { return vm.settings.vaultMaxCol; }, function(cols) {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });

    vm.featureFlags = dimFeatureFlags;
    vm.vendorService = dimVendorService;

    hotkeys.add({
      combo: ['f'],
      description: 'Start a search',
      callback: function(event) {
        $rootScope.$broadcast('dim-focus-filter-input');

        event.preventDefault();
        event.stopPropagation();
      }
    });

    hotkeys.add({
      combo: ['esc'],
      allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
      callback: function() {
        $rootScope.$broadcast('dim-escape-filter-input');
      }
    });

    hotkeys.add({
      combo: ['r'],
      description: "Refresh inventory",
      callback: function() {
        vm.refresh();
      }
    });

    hotkeys.add({
      combo: ['i'],
      description: "Toggle showing full item details",
      callback: function() {
        $rootScope.$broadcast('dim-toggle-item-details');
      }
    });

    if (vm.featureFlags.tagsEnabled) {
      /* Add each hotkey manually until hotkeys can be translated.
          _.each(dimSettingsService.itemTags, (tag) => {
            if (tag.hotkey) {
              hotkeys.add({
                combo: [tag.hotkey],
                description: "Mark item as '" + tag.label + "'",
                callback: function() {
                  $rootScope.$broadcast('dim-item-tag', { tag: tag.type });
                }
              });
            }
          });
      */
      hotkeys.add({
        combo: ['!'],
        description: "Mark item as 'Favorite'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'favorite'
          });
        }
      });

      hotkeys.add({
        combo: ['@'],
        description: "Mark item as 'Keep'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'keep'
          });
        }
      });

      hotkeys.add({
        combo: ['#'],
        description: "Mark item as 'Junk'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'junk'
          });
        }
      });

      hotkeys.add({
        combo: ['$'],
        description: "Mark item as 'Infuse'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'infuse'
          });
        }
      });
    }

    hotkeys.add({
      combo: ['x'],
      description: "Clear new items",
      callback: function() {
        dimStoreService.clearNewItems();
      }
    });

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimFeatureFlags.debugMode = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    /**
     * Show a popup dialog containing the given template. Its class
     * will be based on the name.
     */
    function showPopupFunction(name) {
      var result;
      return function(e) {
        e.stopPropagation();

        if (result) {
          result.close();
        } else {
          ngDialog.closeAll();
          result = ngDialog.open({
            template: require('app/views/' + name + '.template.html'),
            className: name,
            appendClassName: 'modal-dialog'
          });

          result.closePromise.then(function() {
            result = null;
          });
        }
      };
    }

    vm.showSetting = showPopupFunction('settings');
    vm.showAbout = showPopupFunction('about');
    vm.showSupport = showPopupFunction('support');
    vm.showFilters = showPopupFunction('filters');
    vm.showXur = showPopupFunction('xur');
    vm.showMatsExchange = showPopupFunction('mats-exchange');

    function toggleState(name) {
      return function(e) {
        $state.go($state.is(name) ? 'inventory' : name);
      };
    }

    vm.toggleMinMax = toggleState('best');
    vm.toggleVendors = toggleState('vendors');
    vm.toggleRecordBooks = toggleState('record-books');

    vm.xur = dimXurService;

    vm.refresh = function refresh() {
      loadingTracker.addPromise(dimStoreService.reloadStores());
    };
  }
}
<commit_msg>Fix wildcard template inclusion from dynamic require<commit_after>import settingsTemplate from 'app/views/settings.template.html';
import aboutTemplate from 'app/views/about.template.html';
import supportTemplate from 'app/views/support.template.html';
import filtersTemplate from 'app/views/filters.template.html';
import xurTemplate from 'app/views/xur.template.html';
import matsExchangeTemplate from 'app/views/mats-exchange.template.html';

export default class ContentController {
  constructor(dimActivityTrackerService, dimState, ngDialog, $rootScope, loadingTracker, dimPlatformService, $interval, hotkeys, $timeout, dimStoreService, dimXurService, dimSettingsService, $window, $scope, $state, dimFeatureFlags, dimVendorService) {
    'ngInject';

    var vm = this;

    // Variables for templates that webpack does not automatically correct.
    vm.$DIM_VERSION = $DIM_VERSION;
    vm.$DIM_FLAVOR = $DIM_FLAVOR;
    vm.$DIM_CHANGELOG = $DIM_CHANGELOG;

    vm.loadingTracker = loadingTracker;
    vm.platforms = [];

    vm.platformChange = function platformChange(platform) {
      loadingTracker.addPromise(dimPlatformService.setActive(platform));
    };

    $scope.$on('dim-platforms-updated', function(e, args) {
      vm.platforms = args.platforms;
    });

    $scope.$on('dim-active-platform-updated', function(e, args) {
      dimState.active = vm.currentPlatform = args.platform;
    });

    loadingTracker.addPromise(dimPlatformService.getPlatforms());

    vm.settings = dimSettingsService;
    $scope.$watch(() => { return vm.settings.itemSize; }, function(size) {
      document.querySelector('html').style.setProperty("--item-size", size + 'px');
    });
    $scope.$watch(() => { return vm.settings.charCol; }, function(cols) {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });

    $scope.$watch(() => { return vm.settings.vaultMaxCol; }, function(cols) {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });

    vm.featureFlags = dimFeatureFlags;
    vm.vendorService = dimVendorService;

    hotkeys.add({
      combo: ['f'],
      description: 'Start a search',
      callback: function(event) {
        $rootScope.$broadcast('dim-focus-filter-input');

        event.preventDefault();
        event.stopPropagation();
      }
    });

    hotkeys.add({
      combo: ['esc'],
      allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
      callback: function() {
        $rootScope.$broadcast('dim-escape-filter-input');
      }
    });

    hotkeys.add({
      combo: ['r'],
      description: "Refresh inventory",
      callback: function() {
        vm.refresh();
      }
    });

    hotkeys.add({
      combo: ['i'],
      description: "Toggle showing full item details",
      callback: function() {
        $rootScope.$broadcast('dim-toggle-item-details');
      }
    });

    if (vm.featureFlags.tagsEnabled) {
      /* Add each hotkey manually until hotkeys can be translated.
          _.each(dimSettingsService.itemTags, (tag) => {
            if (tag.hotkey) {
              hotkeys.add({
                combo: [tag.hotkey],
                description: "Mark item as '" + tag.label + "'",
                callback: function() {
                  $rootScope.$broadcast('dim-item-tag', { tag: tag.type });
                }
              });
            }
          });
      */
      hotkeys.add({
        combo: ['!'],
        description: "Mark item as 'Favorite'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'favorite'
          });
        }
      });

      hotkeys.add({
        combo: ['@'],
        description: "Mark item as 'Keep'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'keep'
          });
        }
      });

      hotkeys.add({
        combo: ['#'],
        description: "Mark item as 'Junk'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'junk'
          });
        }
      });

      hotkeys.add({
        combo: ['$'],
        description: "Mark item as 'Infuse'",
        callback: function() {
          $rootScope.$broadcast('dim-item-tag', {
            tag: 'infuse'
          });
        }
      });
    }

    hotkeys.add({
      combo: ['x'],
      description: "Clear new items",
      callback: function() {
        dimStoreService.clearNewItems();
      }
    });

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimFeatureFlags.debugMode = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    /**
     * Show a popup dialog containing the given template. Its class
     * will be based on the name.
     */
    function showPopupFunction(name, template) {
      var result;
      return function(e) {
        e.stopPropagation();

        if (result) {
          result.close();
        } else {
          ngDialog.closeAll();
          result = ngDialog.open({
            template: template,
            className: name,
            appendClassName: 'modal-dialog'
          });

          result.closePromise.then(function() {
            result = null;
          });
        }
      };
    }

    vm.showSetting = showPopupFunction('settings', settingsTemplate);
    vm.showAbout = showPopupFunction('about', aboutTemplate);
    vm.showSupport = showPopupFunction('support', supportTemplate);
    vm.showFilters = showPopupFunction('filters', filtersTemplate);
    vm.showXur = showPopupFunction('xur', xurTemplate);
    vm.showMatsExchange = showPopupFunction('mats-exchange', matsExchangeTemplate);

    function toggleState(name) {
      return function(e) {
        $state.go($state.is(name) ? 'inventory' : name);
      };
    }

    vm.toggleMinMax = toggleState('best');
    vm.toggleVendors = toggleState('vendors');
    vm.toggleRecordBooks = toggleState('record-books');

    vm.xur = dimXurService;

    vm.refresh = function refresh() {
      loadingTracker.addPromise(dimStoreService.reloadStores());
    };
  }
}
<|endoftext|>


<commit_before>if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") {
    (function($) {
        $.fn.wordExport = function(fileName) {
            fileName = typeof fileName !== 'undefined' ? fileName : "jQuery-Word-Export";
            var static = {
                mhtml: {
                    top: "Mime-Version: 1.0\nContent-Base: " + location.href + "\nContent-Type: Multipart/related; boundary=\"NEXT.ITEM-BOUNDARY\";type=\"text/html\"\n\n--NEXT.ITEM-BOUNDARY\nContent-Type: text/html; charset=\"utf-8\"\nContent-Location: " + location.href + "\n\n<!DOCTYPE html>\n\n_html_",
                    head: "\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\n\n",
                    body: "_body_"
                }
            };
            var options = {
                maxWidth: 624
            };
            // Clone selected element before manipulating it
            var markup = $(this).clone();

            // Remove hidden elements from the output
            markup.each(function() {
                var self = $(this);
                if (self.is(':hidden'))
                    self.remove();
            });

            // Embed all images using Data URLs
            var images = Array();
            var img = markup.find('img');
            for (var i = 0; i < img.length; i++) {
                // Calculate dimensions of output image
                var w = Math.min(img[i].width, options.maxWidth);
                var h = img[i].height * (w / img[i].width);
                // Create canvas for converting image to data URL
                var canvas = document.createElement("CANVAS");
                canvas.width = w;
                canvas.height = h;
                // Draw image to canvas
                var context = canvas.getContext('2d');
                context.drawImage(img[i], 0, 0, w, h);
                // Get data URL encoding of image
                var uri = canvas.toDataURL("image/png");
                $(img[i]).attr("src", img[i].src);
                img[i].width = w;
                img[i].height = h;
                // Save encoded image to array
                images[i] = {
                    type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")),
                    encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")),
                    location: $(img[i]).attr("src"),
                    data: uri.substring(uri.indexOf(",") + 1)
                };
            }

            // Prepare bottom of mhtml file with image data
            var mhtmlBottom = "\n";
            for (var i = 0; i < images.length; i++) {
                mhtmlBottom += "--NEXT.ITEM-BOUNDARY\n";
                mhtmlBottom += "Content-Location: " + images[i].location + "\n";
                mhtmlBottom += "Content-Type: " + images[i].type + "\n";
                mhtmlBottom += "Content-Transfer-Encoding: " + images[i].encoding + "\n\n";
                mhtmlBottom += images[i].data + "\n\n";
            }
            mhtmlBottom += "--NEXT.ITEM-BOUNDARY--";

            //TODO: load css from included stylesheet
            var styles = "";

            // Aggregate parts of the file together
            var fileContent = static.mhtml.top.replace("_html_", static.mhtml.head.replace("_styles_", styles) + static.mhtml.body.replace("_body_", markup.html())) + mhtmlBottom;

            // Create a Blob with the file contents
            var blob = new Blob([fileContent], {
                type: "application/msword;charset=utf-8"
            });
            saveAs(blob, fileName + ".doc");
        };
    })(jQuery);
} else {
    if (typeof jQuery === "undefined") {
        console.error("jQuery Word Export: missing dependency (jQuery)");
    }
    if (typeof saveAs === "undefined") {
        console.error("jQuery Word Export: missing dependency (FileSaver.js)");
    }
}
<commit_msg>Mime-Version: 1.0 removed<commit_after>if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") {
    (function($) {
        $.fn.wordExport = function(fileName) {
            fileName = typeof fileName !== 'undefined' ? fileName : "jQuery-Word-Export";
            var static = {
                mhtml: {
                    top: "\nContent-Base: " + location.href + "\nContent-Type: Multipart/related; boundary=\"NEXT.ITEM-BOUNDARY\";type=\"text/html\"\n\n--NEXT.ITEM-BOUNDARY\nContent-Type: text/html; charset=\"utf-8\"\nContent-Location: " + location.href + "\n\n<!DOCTYPE html>\n\n_html_",
                    head: "\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\n\n",
                    body: "_body_"
                }
            };
            var options = {
                maxWidth: 624
            };
            // Clone selected element before manipulating it
            var markup = $(this).clone();

            // Remove hidden elements from the output
            markup.each(function() {
                var self = $(this);
                if (self.is(':hidden'))
                    self.remove();
            });

            // Embed all images using Data URLs
            var images = Array();
            var img = markup.find('img');
            for (var i = 0; i < img.length; i++) {
                // Calculate dimensions of output image
                var w = Math.min(img[i].width, options.maxWidth);
                var h = img[i].height * (w / img[i].width);
                // Create canvas for converting image to data URL
                var canvas = document.createElement("CANVAS");
                canvas.width = w;
                canvas.height = h;
                // Draw image to canvas
                var context = canvas.getContext('2d');
                context.drawImage(img[i], 0, 0, w, h);
                // Get data URL encoding of image
                var uri = canvas.toDataURL("image/png");
                $(img[i]).attr("src", img[i].src);
                img[i].width = w;
                img[i].height = h;
                // Save encoded image to array
                images[i] = {
                    type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")),
                    encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")),
                    location: $(img[i]).attr("src"),
                    data: uri.substring(uri.indexOf(",") + 1)
                };
            }

            // Prepare bottom of mhtml file with image data
            var mhtmlBottom = "\n";
            for (var i = 0; i < images.length; i++) {
                mhtmlBottom += "--NEXT.ITEM-BOUNDARY\n";
                mhtmlBottom += "Content-Location: " + images[i].location + "\n";
                mhtmlBottom += "Content-Type: " + images[i].type + "\n";
                mhtmlBottom += "Content-Transfer-Encoding: " + images[i].encoding + "\n\n";
                mhtmlBottom += images[i].data + "\n\n";
            }
            mhtmlBottom += "--NEXT.ITEM-BOUNDARY--";

            //TODO: load css from included stylesheet
            var styles = "";

            // Aggregate parts of the file together
            var fileContent = static.mhtml.top.replace("_html_", static.mhtml.head.replace("_styles_", styles) + static.mhtml.body.replace("_body_", markup.html())) + mhtmlBottom;

            // Create a Blob with the file contents
            var blob = new Blob([fileContent], {
                type: "application/msword;charset=utf-8"
            });
            saveAs(blob, fileName + ".doc");
        };
    })(jQuery);
} else {
    if (typeof jQuery === "undefined") {
        console.error("jQuery Word Export: missing dependency (jQuery)");
    }
    if (typeof saveAs === "undefined") {
        console.error("jQuery Word Export: missing dependency (FileSaver.js)");
    }
}
<|endoftext|>


<commit_before>const express = require('express');
const path = require('path');
const Film = require('../model/Film');
const filmData = require('../methods/getFilmData/FilmData');
const script = require('../methods/script');
const getBechdelResults = require('../methods/bechdel/getBechdelResults');
const multer = require('multer');

const router = express.Router();
const upload = multer({ dest: 'uploads/' });

const isNotCorrectFileFormat = file => {
	return path.extname(file.originalname) !== '.txt';
};

const fileWasNotUploadedCorrectly = file => {
	const exists = !file;
	return exists;
};

const errorReadingScript = title => {
	const titleExists = !title;
	return titleExists;
};

const handleError = (res, errMsg) => {
	console.error(errMsg);
	return res.status(500).send(errMsg);
};

const filmFound = film => {
	return film.length > 0;
};

const resetAll = scriptPath => {
	filmData.clear();
	script.clearTemp(scriptPath);
	return true;
};

const handleFilmFoundInDB = (res, film, scriptPath) => {
	script.clearTemp(scriptPath);
	const response = {
		...film,
		success: true,
		cacheHit: true,
	};
	return handleResponse(res, response);
};

const handleGetAllFilms = async (req, res) => {
	try {
		const films = await Film.listAll();
		if (!filmFound) {
			return handleError(res, 'No list of films returned from film.listAll()');
		}
		return handleResponse(res, films);
	} catch (error) {
		return handleError(res, error);
	}
};

const handleResponse = (res, data) => {
	return res.json(data);
};

const handlePostFilm = async (req, res) => {
	const { file } = req;
	const scriptPath = file.path;

	if (fileWasNotUploadedCorrectly(file)) {
		return handleError(res, 'No script submitted, please try again');
	}
	if (isNotCorrectFileFormat(file)) {
		return handleError(res, 'Please send a .txt script');
	}
	try {
		const title = await script.readMovieTitle(scriptPath);
		if (errorReadingScript(title)) {
			return handleError(res, 'Error reading script');
		}
		const film = await Film.findByTitle(title);
		if (filmFound(film)) {
			return handleFilmFoundInDB(res, film, scriptPath);
		}
		const bechdelResults = await getBechdelResults(title, scriptPath);
		const { actors, images, metadata } = filmData.getAllData();

		const filmMetaData = {
			title,
			bechdelResults,
			actors,
			images,
			data: metadata,
		};
		await Film.insertFilm(filmMetaData);
		const finalFilm = await Film.findByTitle(title);

		resetAll(scriptPath);

		const response = {
			...finalFilm,
			title,
			success: true,
			cacheHit: false,
		};
		return handleResponse(res, response);
	} catch (err) {
		resetAll(scriptPath);
		return handleError(res, 'Please try again');
	}
};

const handleDeleteFilm = async (req, res) => {
	try {
		const success = await Film.deleteFilm(req.params.id);

		if (success) {
			const response = { success: true };
			return handleResponse(res, response);
		}
		return handleError(res, 'No movie found by that ID');
	} catch (err) {
		return handleError(res, err);
	}
};

const handleGetFilm = async (req, res) => {
	try {
		const film = await Film.findByID(req.params.id);
		if (!film) {
			return handleError(res, 'No movie found by that ID');
		}
		return handleResponse(res, film);
	} catch (err) {
		return handleError(res, err);
	}
};

/*
  * FILM ROUTES
*/
router
	.route('/')
	.get(handleGetAllFilms)
	.post(upload.single('script'), handlePostFilm);

router
	.route('/:id')
	.get(handleGetFilm)
	.delete(handleDeleteFilm);

module.exports = router;
<commit_msg>break up post into smaller functions.<commit_after>const express = require('express');
const path = require('path');
const Film = require('../model/Film');
const filmData = require('../methods/getFilmData/FilmData');
const script = require('../methods/script');
const getBechdelResults = require('../methods/bechdel/getBechdelResults');
const multer = require('multer');

const router = express.Router();
const upload = multer({ dest: 'uploads/' });

const isNotCorrectFileFormat = file => {
	return path.extname(file.originalname) !== '.txt';
};

const fileWasNotUploadedCorrectly = file => {
	const exists = !file;
	return exists;
};

const errorReadingScript = title => {
	const titleExists = !title;
	return titleExists;
};

const handleError = (res, errMsg) => {
	console.error(errMsg);
	return res.status(500).send(errMsg);
};

const filmFound = film => {
	return film.length > 0;
};

const resetAll = scriptPath => {
	filmData.clear();
	script.clearTemp(scriptPath);
	return true;
};

const handleFilmFoundInDB = (res, film, scriptPath) => {
	script.clearTemp(scriptPath);
	const response = {
		...film,
		success: true,
		cacheHit: true,
	};
	return handleResponse(res, response);
};

const handleGetAllFilms = async (req, res) => {
	try {
		const films = await Film.listAll();
		if (!filmFound) {
			return handleError(res, 'No list of films returned from film.listAll()');
		}
		return handleResponse(res, films);
	} catch (error) {
		return handleError(res, error);
	}
};

const processScript = async (res, scriptPath) => {
	try {
		const title = await script.readMovieTitle(scriptPath);
		if (errorReadingScript(title)) {
			return handleError(res, 'Error reading script');
		}
		const film = await Film.findByTitle(title);
		if (filmFound(film)) {
			return handleFilmFoundInDB(res, film, scriptPath);
		}
		const bechdelResults = await getBechdelResults(title, scriptPath);
		const { actors, images, metadata } = filmData.getAllData();

		const filmMetaData = {
			title,
			bechdelResults,
			actors,
			images,
			data: metadata,
		};
		await Film.insertFilm(filmMetaData);
		const finalFilm = await Film.findByTitle(title);

		resetAll(scriptPath);

		const response = {
			...finalFilm,
			title,
			success: true,
			cacheHit: false,
		};
		return handleResponse(res, response);
	} catch (err) {
		resetAll(scriptPath);
		return handleError(res, 'Please try again');
	}
};

const handleResponse = (res, data) => {
	return res.json(data);
};

const handlePostFilm = async (req, res) => {
	const { file } = req;
	const scriptPath = file.path;

	if (fileWasNotUploadedCorrectly(file)) {
		return handleError(res, 'No script submitted, please try again');
	}
	if (isNotCorrectFileFormat(file)) {
		return handleError(res, 'Please send a .txt script');
	}
	processScript(res, scriptPath);
};

const handleDeleteFilm = async (req, res) => {
	try {
		const success = await Film.deleteFilm(req.params.id);

		if (success) {
			const response = { success: true };
			return handleResponse(res, response);
		}
		return handleError(res, 'No movie found by that ID');
	} catch (err) {
		return handleError(res, err);
	}
};

const handleGetFilm = async (req, res) => {
	try {
		const film = await Film.findByID(req.params.id);
		if (!film) {
			return handleError(res, 'No movie found by that ID');
		}
		return handleResponse(res, film);
	} catch (err) {
		return handleError(res, err);
	}
};

/*
  * FILM ROUTES
*/
router
	.route('/')
	.get(handleGetAllFilms)
	.post(upload.single('script'), handlePostFilm);

router
	.route('/:id')
	.get(handleGetFilm)
	.delete(handleDeleteFilm);

module.exports = router;
<|endoftext|>


<commit_before>import {isArray, isFunction} from 'lodash/lang';
import {capitalize} from 'lodash/string'
import {keys} from 'lodash/object';
import React, {Component} from 'react';

// - Provide the component
// - Provide the store configuration `[{store: yourStore, properties: ['property1', 'property2']}]`
// - Provide a function to read state from your store
export default function connectToStores(storesConfiguration, getState) {
    // Validate the stores object
    if (!isArray(storesConfiguration)) {
        throw new Error('connectToStores: you need to provide an array of store config.');
    }

    // Validate .
    if (!isFunction(getState)) {
        throw new Error('connectToStores: you need to provide function to read state from store.');
    }
    // Return a wrapper function around the component
    return function connectComponent(DecoratedComponent) {

        // Save the display name for later
        const displayName = DecoratedComponent.displayName || 'Component';

        // The goal of this class is to connect a component to a list of stores with properties.
        class StoreConnector extends Component {

            constructor(props) {
                super(props);
                //Build the initial state from props.
                this.state = getState(props);
                this._isMounted = false;
            }

            // When the component will mount, we listen to all stores changes.
            // When a change occurs the state is read again from the state.
            componentWillMount() {
                storesConfiguration.forEach(storeConf => {
                    const {properties, store} = storeConf;
                    properties.forEach((property) => {
                        if (!store || !store.definition || !store.definition[property]) {
                            console.warn(`
                                StoreConnector ${displayName}:
                                    You add a property : ${property} in your store configuration which is not in your definition : ${keys(store.definition)}
                            `);
                        }
                        const capitalizedProperty = capitalize(property);
                        storeConf.store[`add${capitalizedProperty}ChangeListener`](this.handleStoresChanged);
                        storeConf.store[`add${capitalizedProperty}ErrorListener`](this.handleStoresChanged);
                    });
                });
            }

            // When a component will receive a new props.
            componentWillReceiveProps(nextProps) {
                this.updateState(nextProps);
            }

            // Component unmount.
            componentWillUnmount() {
                this._isMounted = false;
                storesConfiguration.forEach(storeConf => {
                    const {properties, store} = storeConf;
                    properties.forEach((property) => {
                        const capitalizedProperty = capitalize(property);
                        store[`remove${capitalizedProperty}ChangeListener`](this.handleStoresChanged);
                        store[`remove${capitalizedProperty}ErrorListener`](this.handleStoresChanged);
                    });
                });
            }

            componentDidMount() {
                this._isMounted = true;
                this.updateState(this.props);
            }

            updateState(props) {
                if (this._isMounted) {
                    this.setState(getState(props));
                }
            }

            //Handle the store changes
            handleStoresChanged = () => {
                this.updateState(this.props);
            };

            // Render the component with only props, some from the real props some from the state
            render() {
                const {props, state} = this;
                return (
                    <DecoratedComponent
                        {...props}
                        {...state}
                    />
                );
            }
        }
        StoreConnector.displayName = `${displayName}Connected`;
        return StoreConnector;
    };
}

// Add a function to connect a store to a component .
// All the store properties values will be provided to the component as props.
// This could be use as an ES7 annotation or as a function.

// ### ES6 version
// ```jsx
// store
// const newStore = new CoreStore({definition: {name: 'name', email: 'email'}});
//Component
// const Component = props => <div>{JSON.stringify(props)}</div>;
// create a connector function
// const connector = storeConnectBehaviour(
//     [{store: newStore, properties: ['name', 'email']}],
//     (props) => {return newStore.getValue()}
// );
// Component connected to the store
// const ConnectedComponent = connector(Component);
// ```

// ### ES7 version
// ```jsx
//    Class version
// @connect( [{store: newStore, properties: ['name', 'email']}],(props) => newStore.getValue())
// class YourComponent extends Component{
//     render(){
//          return  <div>{JSON.stringify(props)}</div>;
//     }
// }
// ```
<commit_msg>Connect behaviour : remove state, use forceUpdate<commit_after>import { isArray, isFunction } from 'lodash/lang';
import { capitalize } from 'lodash/string'
import { keys } from 'lodash/object';
import React, { Component } from 'react';

// - Provide the component
// - Provide the store configuration `[{store: yourStore, properties: ['property1', 'property2']}]`
// - Provide a function to read state from your store
export default function connectToStores(storesConfiguration, getState) {
    // Validate the stores object
    if (!isArray(storesConfiguration)) {
        throw new Error('connectToStores: you need to provide an array of store config.');
    }

    // Validate .
    if (!isFunction(getState)) {
        throw new Error('connectToStores: you need to provide function to read state from store.');
    }
    // Return a wrapper function around the component
    return function connectComponent(DecoratedComponent) {

        // Save the display name for later
        const displayName = DecoratedComponent.displayName || 'Component';

        // The goal of this class is to connect a component to a list of stores with properties.
        class StoreConnector extends Component {

            constructor(props) {
                super(props);
                this.handleStoresChanged = this.handleStoresChanged.bind(this);
            }

            // When the component will mount, we listen to all stores changes.
            // When a change occurs the state is read again from the state.
            componentWillMount() {
                storesConfiguration.forEach(storeConf => {
                    const { properties, store } = storeConf;
                    properties.forEach((property) => {
                        if (!store || !store.definition || !store.definition[property]) {
                            console.warn(`
                                StoreConnector ${displayName}:
                                    You add a property : ${property} in your store configuration which is not in your definition : ${keys(store.definition)}
                            `);
                        }
                        const capitalizedProperty = capitalize(property);
                        storeConf.store[`add${capitalizedProperty}ChangeListener`](this.handleStoresChanged);
                        storeConf.store[`add${capitalizedProperty}ErrorListener`](this.handleStoresChanged);
                    });
                });
            }

            // Component unmount.
            componentWillUnmount() {
                this._isMounted = false;
                storesConfiguration.forEach(storeConf => {
                    const { properties, store } = storeConf;
                    properties.forEach((property) => {
                        const capitalizedProperty = capitalize(property);
                        store[`remove${capitalizedProperty}ChangeListener`](this.handleStoresChanged);
                        store[`remove${capitalizedProperty}ErrorListener`](this.handleStoresChanged);
                    });
                });
            }

            //Handle the store changes
            handleStoresChanged() {
                this.forceUpdate();
            };

            // Render the component with only props, some from the real props some from the state
            render() {
                return (
                    <DecoratedComponent
                        {...this.props}
                        {...getState(this.props) }
                    />
                );
            }
        }
        StoreConnector.displayName = `${displayName}Connected`;
        return StoreConnector;
    };
}

// Add a function to connect a store to a component .
// All the store properties values will be provided to the component as props.
// This could be use as an ES7 annotation or as a function.

// ### ES6 version
// ```jsx
// store
// const newStore = new CoreStore({definition: {name: 'name', email: 'email'}});
//Component
// const Component = props => <div>{JSON.stringify(props)}</div>;
// create a connector function
// const connector = storeConnectBehaviour(
//     [{store: newStore, properties: ['name', 'email']}],
//     (props) => {return newStore.getValue()}
// );
// Component connected to the store
// const ConnectedComponent = connector(Component);
// ```

// ### ES7 version
// ```jsx
//    Class version
// @connect( [{store: newStore, properties: ['name', 'email']}],(props) => newStore.getValue())
// class YourComponent extends Component{
//     render(){
//          return  <div>{JSON.stringify(props)}</div>;
//     }
// }
// ```
<|endoftext|>


<commit_before>var gulp = require('gulp');

var files = ['index.js', 'test/*.js', 'gulpfile.js'];

gulp.task('lint', function () {
    var eslint = require('gulp-eslint');
    return gulp.src(files)
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('test', function () {
    var mocha = require('gulp-mocha');
    return gulp.src('test/*.js', { read: false })
        .pipe(mocha());
});

gulp.task('default', ['lint', 'test']);

gulp.task('watch', function () {
    gulp.watch(files, ['lint', 'test']);
});
<commit_msg>Removes gulpfile<commit_after><|endoftext|>