Merge branch 'resolvers-branch-angular6' into patch-support

Conflicts:
	src/app/core/auth/auth-response-parsing.service.spec.ts
	src/app/core/auth/auth.service.ts
	src/app/core/auth/models/auth-status.model.ts
	src/app/core/auth/server-auth.service.ts
	src/app/core/data/data.service.spec.ts
	src/app/core/data/data.service.ts
	src/app/core/data/dspace-object-data.service.ts
	src/app/core/data/item-data.service.spec.ts
	src/app/core/data/item-data.service.ts
	src/app/core/data/request.models.ts
	src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
	src/app/shared/mocks/mock-form-service.ts
	src/app/shared/mocks/mock-remote-data-build.service.ts
	src/app/shared/mocks/mock-store.ts
This commit is contained in:
lotte
2018-10-11 12:51:10 +02:00
89 changed files with 3031 additions and 1272 deletions

View File

@@ -89,7 +89,7 @@
"angular2-text-mask": "9.0.0",
"angulartics2": "^6.2.0",
"body-parser": "1.18.2",
"bootstrap": "4.1.1",
"bootstrap": "4.1.3",
"cerialize": "0.1.18",
"compression": "1.7.1",
"cookie-parser": "1.4.3",

277
resources/i18n/cs.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "copyright © 2002-{{ year }}",
"link.dspace": "software DSpace",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Novinky",
"license": "Licence",
"browse": {
"recent": {
"head": "Poslední příspěvky"
}
}
}
},
"community": {
"page": {
"news": "Novinky",
"license": "Licence"
},
"sub-collection-list": {
"head": "Kolekce v této komunitě"
}
},
"item": {
"page": {
"author": "Autor",
"abstract": "Abstract",
"date": "Datum",
"uri": "URI",
"files": "Soubory",
"collections": "Kolekce",
"filesection": {
"download": "Stáhnout",
"name": "Název:",
"format": "Formát:",
"size": "Velikost:",
"description": "Popis:"
},
"link": {
"simple": "Minimální záznam",
"full": "Úplný záznam"
}
}
},
"nav": {
"home": "Domů",
"login": "Přihlásit se",
"logout": "Odhlásit se"
},
"pagination": {
"results-per-page": "Výsledků na stránku",
"sort-direction": "Seřazení",
"showing": {
"label": "Zobrazují se záznamy ",
"detail": "{{ range }} z {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevance"
},
"dc.title": {
"ASC": "Název vzestupně",
"DESC": "Název sestupně"
}
},
"title": "DSpace",
"404": {
"help": "Nepodařilo se najít stránku, kterou hledáte. Je možné, že stránka byla přesunuta nebo smazána. Pomocí tlačítka níže můžete přejít na domovskou stránku. ",
"page-not-found": "stránka nenalezena",
"link": {
"home-page": "Přejít na domovskou stránku"
}
},
"home": {
"title": "DSpace Angular :: Domů",
"description": "",
"top-level-communities": {
"head": "Komunity v DSpace",
"help": "Vybráním komunity můžete prohlížet její kolekce."
}
},
"search": {
"title": "DSpace Angular :: Hledat",
"description": "",
"form": {
"search": "Hledat",
"search_dspace": "Hledat v DSpace"
},
"results": {
"head": "Výsledky hledání",
"no-results": "Nebyli nalezeny žádné výsledky"
},
"sidebar": {
"close": "Zpět na výsledky",
"open": "Vyhledávací nástroje",
"results": "výsledky",
"filters": {
"title": "Filtry"
},
"settings": {
"title": "Nastavení",
"sort-by": "Řadit dle",
"rpp": "Výsledků na stránku"
}
},
"view-switch": {
"show-list": "Zobrazit seznam",
"show-grid": "Zobrazit mřížku"
},
"filters": {
"head": "Filtry",
"reset": "Obnovit filtry",
"applied": {
"f.author": "Autor",
"f.dateIssued.min": "Od data",
"f.dateIssued.max": "Do data",
"f.subject": "Předmět",
"f.has_content_in_original_bundle": "Má soubory"
},
"filter": {
"show-more": "Zobrazit více",
"show-less": "Sbalit",
"author": {
"placeholder": "Jméno autora",
"head": "Autor"
},
"scope": {
"placeholder": "Filtr rozsahu",
"head": "Rozsah"
},
"subject": {
"placeholder": "Předmět",
"head": "Předmět"
},
"dateIssued": {
"max": {
"placeholder": "Datum od"
},
"min": {
"placeholder": "Datum do"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Má soubory"
}
}
}
},
"browse": {
"title": "Prohlížíte {{ collection }} dle {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Registr metadat",
"head": "Registr metadat",
"description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu kvalifikový Dublin Core.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Jmenný prostor",
"name": "Název"
},
"no-items": "Žádná schémata metadat."
}
},
"schema": {
"title": "DSpace Angular :: Registr schémat metadat",
"head": "Metadata Schema",
"description": "Toto je schéma metadat pro „{{namespace}}“.",
"fields": {
"head": "Pole schématu metadat",
"table": {
"field": "Pole",
"scopenote": "Poznámka o rozsahu"
},
"no-items": "Žádná metadatová pole."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Registr formátů souborů",
"head": "Registr formátů souborů",
"description": "Tento seznam formátů souborů poskytuje informace o známých formátech a o úrovni jejich podpory.",
"formats": {
"table": {
"name": "Název",
"mimetype": "Typ MIME",
"supportLevel": {
"head": "Úroveň podpory",
"0": "Neznámá",
"1": "Známá",
"2": "Podpora"
},
"internal": "interní"
},
"no-items": "Žádné formáty souborů."
}
}
}
},
"loading": {
"default": "Načítá se...",
"top-level-communities": "Načítají se komunity nejvyšší úrovně...",
"community": "Načítá se komunita...",
"collection": "Načítá se kolekce...",
"sub-collections": "Načítají se subkolekce...",
"recent-submissions": "Načítají se poslední příspěvky...",
"item": "Načítá se záznam...",
"objects": "Načítá se...",
"search-results": "Načítají se výsledky hledání...",
"browse-by": "Načítají se záznamy..."
},
"error": {
"default": "Chyba",
"top-level-communities": "Chyba během stahování komunit nejvyšší úrovně",
"community": "Chyba během stahování komunity",
"collection": "Chyba během stahování kolekce",
"sub-collections": "Chyba během stahování subkolekcí",
"recent-submissions": "Chyba během stahování posledních příspěvků",
"item": "Chyba během stahování záznamu",
"objects": "Chyba během stahování objektů",
"search-results": "Chyba během stahování výsledků hledání",
"browse-by": "Chyba během stahování záznamů",
"validation": {
"pattern": "Tento vstup je omezen dle vzoru: {{ pattern }}.",
"license": {
"notgranted": "Pro dokončení zaslání Musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat."
}
}
},
"form": {
"submit": "Odeslat",
"cancel": "Zrušit",
"search": "Hledat",
"remove": "Smazat",
"first-name": "Křestní jméno",
"last-name": "Příjmení",
"loading": "Načítá se...",
"no-results": "Nebyli nalezeny žádné výsledky",
"no-value": "Nebyla zadána hodnota",
"group-collapse": "Sbalit",
"group-expand": "Rozbalit",
"group-collapse-help": "Kliknutím sem sbalíte",
"group-expand-help": "Kliknutím sem rozbalíte a přidáte další prvky"
},
"login": {
"title": "Přihlásit se",
"form": {
"header": "Prosím, přihlaste se do DSpace",
"email": "E-mailová adresa",
"forgot-password": "Zapomněli jste své heslo?",
"new-user": "Nový uživatel? Zaregistrujte se kliknutím sem.",
"password": "Heslo",
"submit": "Přihlásit se"
}
},
"logout": {
"title": "Odhlásit se",
"form": {
"header": "Odhlásit se z DSpace",
"submit": "Odhlásit se"
}
},
"auth": {
"messages": {
"expired": "Vaše relace vypršela. Prosím, znova se přihlaste."
},
"errors": {
"invalid-user": "Neplatná e-mailová adresa nebo heslo."
}
}
}

277
resources/i18n/de.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "Copyright © 2002-{{ year }}",
"link.dspace": "DSpace Software",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Neuigkeiten",
"license": "Lizenz",
"browse": {
"recent": {
"head": "Aktuellste Veröffentlichungen"
}
}
}
},
"community": {
"page": {
"news": "Neuigkeiten",
"license": "Lizenz"
},
"sub-collection-list": {
"head": "Sammlungen in diesem Bereich"
}
},
"item": {
"page": {
"author": "Autor",
"abstract": "Kurzfassung",
"date": "Datum",
"uri": "URI",
"files": "Dateien",
"collections": "Sammlungen",
"filesection": {
"download": "Herunterladen",
"name": "Name:",
"format": "Format:",
"size": "Größe:",
"description": "Beschreibung:"
},
"link": {
"simple": "Kurzanzeige",
"full": "Vollanzeige"
}
}
},
"nav": {
"home": "Zur Startseite",
"login": "Anmelden",
"logout": "Abmelden"
},
"pagination": {
"results-per-page": "Ergebnisse pro Seite",
"sort-direction": "Sortiermöglichkeiten",
"showing": {
"label": "Anzeige der Treffer ",
"detail": "{{ range }} bis {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevanz"
},
"dc.title": {
"ASC": "Titel aufsteigend",
"DESC": "Titel absteigend"
}
},
"title": "DSpace",
"404": {
"help": "Die Seite, die Sie aufrufen wollten, konnte nicht gefunden werden. Sie könnte verschoben oder gelöscht worden sein. Mit dem Link unten kommen Sie zurück zur Startseite. ",
"page-not-found": "Seite nicht gefunden",
"link": {
"home-page": "Zurück zur Startseite"
}
},
"home": {
"title": "DSpace Angular :: Startseite",
"description": "",
"top-level-communities": {
"head": "Bereiche in DSpace",
"help": "Wählen Sie einen Bereich, um seine Sammlungen einzusehen."
}
},
"search": {
"title": "DSpace Angular :: Suche",
"description": "",
"form": {
"search": "Suche",
"search_dspace": "DSpace durchsuchen"
},
"results": {
"head": "Suchergebnisse",
"no-results": "Zu dieser Suche gibt es keine Treffer."
},
"sidebar": {
"close": "Zurück zu den Ergebnissen",
"open": "Suchwerkzeuge",
"results": "Ergebnisse",
"filters": {
"title": "Filter"
},
"settings": {
"title": "Einstellungen",
"sort-by": "Sortiere nach",
"rpp": "Treffer pro Seite"
}
},
"view-switch": {
"show-list": "Zeige als Liste",
"show-grid": "Zeige als Raster"
},
"filters": {
"head": "Filter",
"reset": "Filter zurücksetzen",
"applied": {
"f.author": "Autor",
"f.dateIssued.min": "Anfangsdatum",
"f.dateIssued.max": "Enddatum",
"f.subject": "Thema",
"f.has_content_in_original_bundle": "Besitzt Dateien"
},
"filter": {
"show-more": "Zeige mehr",
"show-less": "Zeige weniger",
"author": {
"placeholder": "Autor",
"head": "Autor"
},
"scope": {
"placeholder": "Bereichsfilter",
"head": "Bereich"
},
"subject": {
"placeholder": "Schlagwort",
"head": "Schlagwort"
},
"dateIssued": {
"max": {
"placeholder": "Frühestes Datum"
},
"min": {
"placeholder": "Ältestes Datum"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Besitzt Dateien"
}
}
}
},
"browse": {
"title": "Anzeige {{ collection }} nach {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Metadatenreferenzliste",
"head": "Metadatenreferenzliste",
"description": "Die Metadatenreferenzliste beinhaltet alle Metadatenfelder, die zur Verfügung stehen. Die Felder können in unterschiedlichen Schemata enthalten sein. Nichtsdestotrotz benötigt DSpace mindestens qualifiziertes Dublin Core.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Namensraum",
"name": "Name"
},
"no-items": "Es gbit keine Metadatenschemata."
}
},
"schema": {
"title": "DSpace Angular :: Referenzliste der Metadatenschemata",
"head": "Metadatenschemata",
"description": "Dies ist das Metadatenschema für \"{{namespace}}\".",
"fields": {
"head": "Felder in diesem Schema",
"table": {
"field": "Feld",
"scopenote": "Gültigkeitsbereich"
},
"no-items": "Es gibt keine Felder in diesem Schema."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Referenzliste der Dateiformate",
"head": "Referenzliste der Dateiformate",
"description": "Diese Liste enhtält die in diesem Repositorium zulässigen Dateiformate und den jeweiligen Unterstützungsgrad.",
"formats": {
"table": {
"name": "Name",
"mimetype": "MIME Type",
"supportLevel": {
"head": "Unterstützungsgrad",
"0": "Unbekannt",
"1": "Bekannt",
"2": "Unterstützt"
},
"internal": "intern"
},
"no-items": "Es gibt keine Formate in dieser Referenzliste."
}
}
}
},
"loading": {
"default": "Am Laden ...",
"top-level-communities": "Die Hauptbereiche werden geladen ...",
"community": "Der Bereich wird geladen ...",
"collection": "Die Sammlung wird geladen ...",
"sub-collections": "Die untergeordneten Sammlungen werden geladen ...",
"recent-submissions": "Die aktuellsten Veröffentlichungen werden geladen ...",
"item": "Die Ressource wird geladen ...",
"objects": "Am Laden ...",
"search-results": "Die Suchergebnisse werden geladen ...",
"browse-by": "Die Ressourcen werden geladen ..."
},
"error": {
"default": "Fehler",
"top-level-communities": "Fehler beim Laden der Hauptbereiche.",
"community": "Fehler beim Laden des Bereiches.",
"collection": "Fehler beim Laden der Sammlung.",
"sub-collections": "Fehler beim Laden der untergeordneten Sammlungen.",
"recent-submissions": "Fehler beim Laden der aktuellsten Veröffentlichungen.",
"item": "Fehler beim Laden der Ressource.",
"objects": "Fehler beim Laden der Objekte.",
"search-results": "Fehler beim Laden der Suchergebnisse.",
"browse-by": "Fehler beim Laden der Ressourcen",
"validation": {
"pattern": "Die Eingabe kann nur folgendes Muster haben: {{ pattern }}.",
"license": {
"notgranted": "Sie müssen der Lizenz zustimmen, um die Ressource einzureichen. Wenn dies zur Zeit nicht geht, können Sie die Einreichung speichern und später wiederaufnehmen oder löschen."
}
}
},
"form": {
"submit": "Los",
"cancel": "Abbrechen",
"search": "Suchen",
"remove": "Löschen",
"first-name": "Vorname",
"last-name": "Nachname",
"loading": "Am Laden ...",
"no-results": "Keine Ergebnisse gefunden",
"no-value": "Kein Wert eingegeben",
"group-collapse": "Weniger",
"group-expand": "Mehr",
"group-collapse-help": "Hier klicken, um die Anzeige zu reduzieren",
"group-expand-help": "Hier klicken, um mehr Elemente anzuzeigen"
},
"login": {
"title": "Einloggen",
"form": {
"header": "Bitte Loggen Sie sich ein.",
"email": "E-Mail-Adresse",
"forgot-password": "Haben Sie Ihr Passwort vergessen?",
"new-user": "Sind Sie neu hier? Klicken Sie hier, um sich zu registrieren.",
"password": "Passwort",
"submit": "Einloggen"
}
},
"logout": {
"title": "Ausloggen",
"form": {
"header": "Ausloggen aus DSpace",
"submit": "Ausloggen"
}
},
"auth": {
"messages": {
"expired": "Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an."
},
"errors": {
"invalid-user": "Ungültige E-Mail-Adresse oder Passwort."
}
}
}

View File

@@ -151,6 +151,9 @@
}
}
},
"browse": {
"title": "Browsing {{ collection }} by {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
@@ -202,18 +205,19 @@
},
"loading": {
"default": "Loading...",
"top-level-communities": "Loading top level communities...",
"top-level-communities": "Loading top-level communities...",
"community": "Loading community...",
"collection": "Loading collection...",
"sub-collections": "Loading sub-collections...",
"recent-submissions": "Loading recent submissions...",
"item": "Loading item...",
"objects": "Loading...",
"search-results": "Loading search results..."
"search-results": "Loading search results...",
"browse-by": "Loading items..."
},
"error": {
"default": "Error",
"top-level-communities": "Error fetching top level communities",
"top-level-communities": "Error fetching top-level communities",
"community": "Error fetching community",
"collection": "Error fetching collection",
"sub-collections": "Error fetching sub-collections",
@@ -221,6 +225,7 @@
"item": "Error fetching item",
"objects": "Error fetching objects",
"search-results": "Error fetching search results",
"browse-by": "Error fetching items",
"validation": {
"pattern": "This input is restricted by the current pattern: {{ pattern }}.",
"license": {
@@ -241,7 +246,7 @@
"group-collapse": "Collapse",
"group-expand": "Expand",
"group-collapse-help": "Click here to collapse",
"group-expand-help": "Click here to expand and add more element"
"group-expand-help": "Click here to expand and add more elements"
},
"login": {
"title": "Login",
@@ -266,7 +271,7 @@
"expired": "Your session has expired. Please log in again."
},
"errors": {
"invalid-user": "Invalid email or password."
"invalid-user": "Invalid email address or password."
}
}
}

277
resources/i18n/nl.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "copyright © 2002-{{ year }}",
"link.dspace": "DSpace software",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Nieuws",
"license": "Licentie",
"browse": {
"recent": {
"head": "Recent toegevoegd"
}
}
}
},
"community": {
"page": {
"news": "Nieuws",
"license": "Licentie"
},
"sub-collection-list": {
"head": "Collecties in deze Community"
}
},
"item": {
"page": {
"author": "Auteur",
"abstract": "Abstract",
"date": "Datum",
"uri": "URI",
"files": "Bestanden",
"collections": "Collecties",
"filesection": {
"download": "Download",
"name": "Naam:",
"format": "Formaat:",
"size": "Grootte:",
"description": "Beschrijving:"
},
"link": {
"simple": "Eenvoudige item weergave",
"full": "Volledige item weergave"
}
}
},
"nav": {
"home": "Home",
"login": "Log In",
"logout": "Log Uit"
},
"pagination": {
"results-per-page": "Resultaten per pagina",
"sort-direction": "Sorteer mogelijkheden",
"showing": {
"label": "Getoonde items ",
"detail": "{{ range }} tot {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevantie"
},
"dc.title": {
"ASC": "Oplopend op titel",
"DESC": "Aflopend op titel"
}
},
"title": "DSpace",
"404": {
"help": "De pagina die u zoekt kan niet gevonden worden. De pagina werd mogelijk verplaatst of verwijderd. U kan onderstaande knop gebruiken om terug naar de homepagina te gaan. ",
"page-not-found": "Pagina niet gevonden",
"link": {
"home-page": "Terug naar de homepagina"
}
},
"home": {
"title": "DSpace Angular :: Home",
"description": "",
"top-level-communities": {
"head": "Communities in DSpace",
"help": "Selecteer een community om diens collecties te verkennen."
}
},
"search": {
"title": "DSpace Angular :: Zoek",
"description": "",
"form": {
"search": "Zoek",
"search_dspace": "Zoek in DSpace"
},
"results": {
"head": "Zoekresultaten",
"no-results": "Er waren geen resultaten voor deze zoekopdracht"
},
"sidebar": {
"close": "Terug naar de resultaten",
"open": "Zoek Tools",
"results": "resultaten",
"filters": {
"title": "Filters"
},
"settings": {
"title": "Instellingen",
"sort-by": "Sorteer volgens",
"rpp": "Resultaten per pagina"
}
},
"view-switch": {
"show-list": "Toon als lijst",
"show-grid": "Toon in raster"
},
"filters": {
"head": "Filters",
"reset": "Filters verwijderen",
"applied": {
"f.author": "Auteur",
"f.dateIssued.min": "Start datum",
"f.dateIssued.max": "Eind datum",
"f.subject": "Sleutelwoord",
"f.has_content_in_original_bundle": "Heeft bestanden"
},
"filter": {
"show-more": "Toon meer",
"show-less": "Inklappen",
"author": {
"placeholder": "Auteursnaam",
"head": "Auteur"
},
"scope": {
"placeholder": "Bereik filter",
"head": "Bereik"
},
"subject": {
"placeholder": "Onderwerp",
"head": "Onderwerp"
},
"dateIssued": {
"max": {
"placeholder": "Vroegste Datum"
},
"min": {
"placeholder": "Laatste Datum"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Heeft bestanden"
}
}
}
},
"browse": {
"title": "Verken {{ collection }} volgens {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Metadata Register",
"head": "Metadata Register",
"description": "Het metadata register omvat de lijst van alle metadata velden die beschikbaar zijn in het systeem. Deze velden kunnen verspreid zijn over verschillende metadata schema's. Het qualified Dublin Core schema (dc) is een verplicht schema en kan niet worden verwijderd.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Naamruimte",
"name": "Naam"
},
"no-items": "Er kunnen geen metadata schema's getoond worden."
}
},
"schema": {
"title": "DSpace Angular :: Metadata Schema Register",
"head": "Metadata Schema",
"description": "Dit is het metadata schema voor \"{{namespace}}\".",
"fields": {
"head": "Schema metadata velden",
"table": {
"field": "Veld",
"scopenote": "Opmerking over bereik"
},
"no-items": "Er kunnen geen metadata velden getoond worden."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Bitstream Formaat Register",
"head": "Bitstream Formaat Register",
"description": "Deze lijst van Bitstream formaten biedt informatie over de formaten die in deze repository zijn toegelaten en op welke manier ze ondersteund worden. De term Bitstream wordt in DSpace gebruikt om een bestand aan te duiden dat samen met metadata onderdeel uitmaakt van een item. De naam bitstream duidt op het feit dat het bestand achterliggend wordt opgeslaan zonder bestandsextensie.",
"formats": {
"table": {
"name": "Naam",
"mimetype": "MIME Type",
"supportLevel": {
"head": "Ondersteuning",
"0": "Onbekend",
"1": "Gekend",
"2": "Ondersteund"
},
"internal": "intern"
},
"no-items": "Er kunnen geen bitstream formaten getoond worden."
}
}
}
},
"loading": {
"default": "Laden...",
"top-level-communities": "Inladen van de Communities op het hoogste niveau...",
"community": "Community wordt ingeladen...",
"collection": "Collectie wordt ingeladen...",
"sub-collections": "De sub-collecties worden ingeladen...",
"recent-submissions": "Recent toegevoegde items worden ingeladen...",
"item": "Item wordt ingeladen...",
"objects": "Laden...",
"search-results": "Zoekresultaten worden ingeladen...",
"browse-by": "Items worden ingeladen..."
},
"error": {
"default": "Fout",
"top-level-communities": "Fout bij het inladen van communities op het hoogste niveau",
"community": "Fout bij het ophalen van een community",
"collection": "Fout bij het ophalen van een collectie",
"sub-collections": "Fout bij het ophalen van sub-collecties",
"recent-submissions": "Fout bij het ophalen van recent toegevoegde items",
"item": "Fout bij het ophalen van items",
"objects": "Fout bij het ophalen van objecten",
"search-results": "Fout bij het ophalen van zoekresultaten",
"browse-by": "Fout bij het ophalen van items",
"validation": {
"pattern": "Deze invoer is niet toegelaten volgens dit patroon: {{ pattern }}.",
"license": {
"notgranted": "U moet de invoerlicentie goedkeuren om de invoer af te werken. Indien u deze licentie momenteel niet kan of mag goedkeuren, kan u uw werk opslaan en de invoer later afwerken. U kan dit nieuwe item ook verwijderen indien u niet voldoet aan de vereisten van de invoer licentie."
}
}
},
"form": {
"submit": "Verstuur",
"cancel": "Annuleer",
"search": "Zoek",
"remove": "Verwijder",
"first-name": "Voornaam",
"last-name": "Achternaam",
"loading": "Inladen...",
"no-results": "Geen resultaten gevonden",
"no-value": "Geen waarde ingevoerd",
"group-collapse": "Inklappen",
"group-expand": "Uitklappen",
"group-collapse-help": "Klik hier op in te klappen",
"group-expand-help": "Klik hier om uit te klappen en om meer onderdelen toe te voegen"
},
"login": {
"title": "Aanmelden",
"form": {
"header": "Gelieve in te loggen in DSpace",
"email": "Email adres",
"forgot-password": "Bent u uw wachtwoord vergeten?",
"new-user": "Nieuwe gebruiker? Gelieve u hier te registreren",
"password": "Wachtwoord",
"submit": "Aanmelden"
}
},
"logout": {
"title": "Afmelden",
"form": {
"header": "Afmelden in DSpace",
"submit": "Afmelden"
}
},
"auth": {
"messages": {
"expired": "Uw sessie is vervallen. Gelieve opnieuw aan te melden."
},
"errors": {
"invalid-user": "Ongeldig email adres of wachtwoord."
}
}
}

View File

@@ -14,7 +14,6 @@ import { PaginationComponent } from '../../../shared/pagination/pagination.compo
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
import { HostWindowService } from '../../../shared/host-window.service';
describe('MetadataRegistryComponent', () => {
let comp: MetadataRegistryComponent;
let fixture: ComponentFixture<MetadataRegistryComponent>;
@@ -68,5 +67,4 @@ describe('MetadataRegistryComponent', () => {
const mockName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(2) td:nth-child(3)')).nativeElement;
expect(mockName.textContent).toBe('mock');
});
});

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="browse-by-author w-100 row">
<ds-browse-by class="col-xs-12 w-100"
title="{{'browse.title' | translate:{collection: '', field: 'Author', value: (value)? '&quot;' + value + '&quot;': ''} }}"
[objects$]="(items$ !== undefined)? items$ : authors$"
[currentUrl]="currentUrl"
[paginationConfig]="paginationConfig"
[sortConfig]="sortConfig">
</ds-browse-by>
</div>
</div>

View File

@@ -0,0 +1,107 @@
import {combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { ItemDataService } from '../../core/data/item-data.service';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { ActivatedRoute } from '@angular/router';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../../core/browse/browse.service';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
import { Item } from '../../core/shared/item.model';
@Component({
selector: 'ds-browse-by-author-page',
styleUrls: ['./browse-by-author-page.component.scss'],
templateUrl: './browse-by-author-page.component.html'
})
/**
* Component for browsing (items) by author (dc.contributor.author)
*/
export class BrowseByAuthorPageComponent implements OnInit {
authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
items$: Observable<RemoteData<PaginatedList<Item>>>;
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'browse-by-author-pagination',
currentPage: 1,
pageSize: 20
});
sortConfig: SortOptions = new SortOptions('dc.contributor.author', SortDirection.ASC);
subs: Subscription[] = [];
currentUrl: string;
value = '';
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute, private browseService: BrowseService) {
}
ngOnInit(): void {
this.currentUrl = this.route.snapshot.pathFromRoot
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
.join('/');
this.updatePage({
pagination: this.paginationConfig,
sort: this.sortConfig
});
this.subs.push(
observableCombineLatest(
this.route.params,
this.route.queryParams,
(params, queryParams, ) => {
return Object.assign({}, params, queryParams);
})
.subscribe((params) => {
const page = +params.page || this.paginationConfig.currentPage;
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = params.sortDirection || this.sortConfig.direction;
const sortField = params.sortField || this.sortConfig.field;
this.value = +params.value || params.value || '';
const pagination = Object.assign({},
this.paginationConfig,
{ currentPage: page, pageSize: pageSize }
);
const sort = Object.assign({},
this.sortConfig,
{ direction: sortDirection, field: sortField }
);
const searchOptions = {
pagination: pagination,
sort: sort
};
if (isNotEmpty(this.value)) {
this.updatePageWithItems(searchOptions, this.value);
} else {
this.updatePage(searchOptions);
}
}));
}
/**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
*/
updatePage(searchOptions) {
this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions);
this.items$ = undefined;
}
/**
* Updates the current page with searchOptions and display items linked to author
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
* @param author The author's name for displaying items
*/
updatePageWithItems(searchOptions, author: string) {
this.items$ = this.browseService.getBrowseItemsFor('author', author, searchOptions);
}
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="browse-by-title w-100 row">
<ds-browse-by class="col-xs-12 w-100"
title="{{'browse.title' | translate:{collection: '', field: 'Title', value: ''} }}"
[objects$]="items$"
[currentUrl]="currentUrl"
[paginationConfig]="paginationConfig"
[sortConfig]="sortConfig">
</ds-browse-by>
</div>
</div>

View File

@@ -0,0 +1,92 @@
import {combineLatest as observableCombineLatest, Observable , Subscription } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { PaginatedList } from '../../core/data/paginated-list';
import { ItemDataService } from '../../core/data/item-data.service';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { Item } from '../../core/shared/item.model';
import { ActivatedRoute, PRIMARY_OUTLET, UrlSegmentGroup } from '@angular/router';
import { hasValue } from '../../shared/empty.util';
import { Collection } from '../../core/shared/collection.model';
@Component({
selector: 'ds-browse-by-title-page',
styleUrls: ['./browse-by-title-page.component.scss'],
templateUrl: './browse-by-title-page.component.html'
})
/**
* Component for browsing items by title (dc.title)
*/
export class BrowseByTitlePageComponent implements OnInit {
items$: Observable<RemoteData<PaginatedList<Item>>>;
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'browse-by-title-pagination',
currentPage: 1,
pageSize: 20
});
sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
subs: Subscription[] = [];
currentUrl: string;
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute) {
}
ngOnInit(): void {
this.currentUrl = this.route.snapshot.pathFromRoot
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
.join('/');
this.updatePage({
pagination: this.paginationConfig,
sort: this.sortConfig
});
this.subs.push(
observableCombineLatest(
this.route.params,
this.route.queryParams,
(params, queryParams, ) => {
return Object.assign({}, params, queryParams);
})
.subscribe((params) => {
const page = +params.page || this.paginationConfig.currentPage;
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = params.sortDirection || this.sortConfig.direction;
const sortField = params.sortField || this.sortConfig.field;
const pagination = Object.assign({},
this.paginationConfig,
{ currentPage: page, pageSize: pageSize }
);
const sort = Object.assign({},
this.sortConfig,
{ direction: sortDirection, field: sortField }
);
this.updatePage({
pagination: pagination,
sort: sort
});
}));
}
/**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
*/
updatePage(searchOptions) {
this.items$ = this.itemDataService.findAll({
currentPage: searchOptions.pagination.currentPage,
elementsPerPage: searchOptions.pagination.pageSize,
sort: searchOptions.sort
});
}
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}

View File

@@ -0,0 +1,16 @@
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'title', component: BrowseByTitlePageComponent },
{ path: 'author', component: BrowseByAuthorPageComponent }
])
]
})
export class BrowseByRoutingModule {
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
import { ItemDataService } from '../core/data/item-data.service';
import { SharedModule } from '../shared/shared.module';
import { BrowseByRoutingModule } from './browse-by-routing.module';
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
import { BrowseService } from '../core/browse/browse.service';
@NgModule({
imports: [
BrowseByRoutingModule,
CommonModule,
SharedModule
],
declarations: [
BrowseByTitlePageComponent,
BrowseByAuthorPageComponent
],
providers: [
ItemDataService,
BrowseService
]
})
export class BrowseByModule {
}

View File

@@ -159,7 +159,7 @@ export class SearchConfigurationService implements OnDestroy {
Object.keys(filterParams).forEach((key) => {
if (key.endsWith('.min') || key.endsWith('.max')) {
const realKey = key.slice(0, -4);
if (hasNoValue(filters.find((filter) => filter.key === realKey))) {
if (hasNoValue(filters.find((f) => f.key === realKey))) {
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']']));

View File

@@ -12,6 +12,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },

View File

@@ -1,5 +1,4 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import { HALEndpointService } from '../shared/hal-endpoint.service';

View File

@@ -2,20 +2,18 @@ import { AuthStatusResponse } from '../cache/response-cache.models';
import { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface';
import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { AuthStatus } from './models/auth-status.model';
import { AuthResponseParsingService } from './auth-response-parsing.service';
import { AuthGetRequest, AuthPostRequest } from '../data/request.models';
// import { getMockStore } from '../../shared/mocks/mock-store';
import { MockStore } from '../../shared/testing/mock-store';
import { ObjectCacheState } from '../cache/object-cache.reducer';
describe('AuthResponseParsingService', () => {
let service: AuthResponseParsingService;
const EnvConfig = {cache: {msToLive: 1000}} as GlobalConfig;
const store = {} as Store<CoreState>;
const objectCacheService = new ObjectCacheService(store);
const EnvConfig = { cache: { msToLive: 1000 } } as GlobalConfig;
const store = new MockStore<ObjectCacheState>({});
const objectCacheService = new ObjectCacheService(store as any);
beforeEach(() => {
service = new AuthResponseParsingService(EnvConfig, objectCacheService);

View File

@@ -4,8 +4,7 @@ import { provideMockActions } from '@ngrx/effects/testing';
import { Store } from '@ngrx/store';
import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import { of as observableOf } from 'rxjs';
import { Observable, of as observableOf, throwError as observableThrow } from 'rxjs';
import { AuthEffects } from './auth.effects';
import {
@@ -30,16 +29,21 @@ import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('AuthEffects', () => {
let authEffects: AuthEffects;
let actions: Observable<any>;
const authServiceStub = new AuthServiceStub();
let authServiceStub;
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */
dispatch: {},
/* tslint:enable:no-empty */
select: observableOf(true)
});
const token = authServiceStub.getToken();
let token;
function init() {
authServiceStub = new AuthServiceStub();
token = authServiceStub.getToken();
}
beforeEach(() => {
init();
TestBed.configureTestingModule({
providers: [
AuthEffects,
@@ -71,7 +75,7 @@ describe('AuthEffects', () => {
describe('when credentials are wrong', () => {
it('should return a AUTHENTICATE_ERROR action in response to a AUTHENTICATE action', () => {
spyOn((authEffects as any).authService, 'authenticate').and.returnValue(Observable.throw(new Error('Message Error test')));
spyOn((authEffects as any).authService, 'authenticate').and.returnValue(observableThrow(new Error('Message Error test')));
actions = hot('--a-', {
a: {
@@ -112,7 +116,7 @@ describe('AuthEffects', () => {
describe('when token is not valid', () => {
it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => {
spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(Observable.throw(new Error('Message Error test')));
spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test')));
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
@@ -138,7 +142,7 @@ describe('AuthEffects', () => {
describe('when check token failed', () => {
it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => {
spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(Observable.throw(''));
spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(observableThrow(''));
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
@@ -164,7 +168,7 @@ describe('AuthEffects', () => {
describe('when refresh token failed', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => {
spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(Observable.throw(''));
spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(observableThrow(''));
actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}});
@@ -190,7 +194,7 @@ describe('AuthEffects', () => {
describe('when refresh token failed', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => {
spyOn((authEffects as any).authService, 'logout').and.returnValue(Observable.throw(new Error('Message Error test')));
spyOn((authEffects as any).authService, 'logout').and.returnValue(observableThrow(new Error('Message Error test')));
actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT, payload: token}});

View File

@@ -1,4 +1,4 @@
import { of as observableOf, throwError as observableThrowError } from 'rxjs';
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import { catchError, filter, map } from 'rxjs/operators';
import { Injectable, Injector } from '@angular/core';
@@ -11,10 +11,6 @@ import {
HttpResponse,
HttpResponseBase
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { find } from 'lodash';
import { AppState } from '../../app.reducer';

View File

@@ -31,38 +31,50 @@ describe('AuthService test', () => {
pipe: observableOf(true)
});
let authService: AuthService;
const authRequest = new AuthRequestServiceStub();
let authRequest;
const window = new NativeWindowRef();
const routerStub = new RouterStub();
const routeStub = new ActivatedRouteStub();
let routeStub;
let storage: CookieService;
const token: AuthTokenInfo = new AuthTokenInfo('test_token');
let token: AuthTokenInfo;
let authenticatedState;
const rdbService = getMockRemoteDataBuildService();
function init() {
token = new AuthTokenInfo('test_token');
token.expires = Date.now() + (1000 * 60 * 60);
let authenticatedState = {
authenticatedState = {
authenticated: true,
loaded: true,
loading: false,
authToken: token,
user: EPersonMock
};
const rdbService = getMockRemoteDataBuildService();
describe('', () => {
authRequest = new AuthRequestServiceStub();
routeStub = new ActivatedRouteStub();
}
beforeEach(() => {
init();
});
describe('', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
StoreModule.forRoot({authReducer}),
StoreModule.forRoot({ authReducer }),
],
declarations: [],
providers: [
{provide: AuthRequestService, useValue: authRequest},
{provide: NativeWindowService, useValue: window},
{provide: REQUEST, useValue: {}},
{provide: Router, useValue: routerStub},
{provide: ActivatedRoute, useValue: routeStub},
{ provide: AuthRequestService, useValue: authRequest },
{ provide: NativeWindowService, useValue: window },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
{ provide: ActivatedRoute, useValue: routeStub },
{provide: Store, useValue: mockStore},
{provide: RemoteDataBuildService, useValue: rdbService},
{ provide: RemoteDataBuildService, useValue: rdbService },
CookieService,
AuthService
],
@@ -115,15 +127,16 @@ describe('AuthService test', () => {
describe('', () => {
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({authReducer})
StoreModule.forRoot({ authReducer })
],
providers: [
{provide: AuthRequestService, useValue: authRequest},
{provide: REQUEST, useValue: {}},
{provide: Router, useValue: routerStub},
{provide: RemoteDataBuildService, useValue: rdbService},
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
{ provide: RemoteDataBuildService, useValue: rdbService },
CookieService
]
}).compileComponents();
@@ -167,12 +180,12 @@ describe('AuthService test', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({authReducer})
StoreModule.forRoot({ authReducer })
],
providers: [
{provide: AuthRequestService, useValue: authRequest},
{provide: REQUEST, useValue: {}},
{provide: Router, useValue: routerStub},
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
ClientCookieService,
CookieService
]

View File

@@ -62,7 +62,8 @@ export class AuthService {
protected router: Router,
protected storage: CookieService,
protected store: Store<AppState>,
protected rdbService: RemoteDataBuildService) {
protected rdbService: RemoteDataBuildService
) {
this.store.pipe(
select(isAuthenticated),
startWith(false)
@@ -145,6 +146,7 @@ export class AuthService {
options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe(
switchMap((status: AuthStatus) => {
if (status.authenticated) {
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
// Review when https://jira.duraspace.org/browse/DS-4006 is fixed
@@ -154,7 +156,7 @@ export class AuthService {
} else {
throw(new Error('Not authenticated'));
}
}));
}))
}
/**

View File

@@ -2,7 +2,7 @@ import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.model';
import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
export class AuthStatus {

View File

@@ -1,3 +1,4 @@
import { first, map, switchMap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@@ -36,6 +37,7 @@ export class ServerAuthService extends AuthService {
options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe(
switchMap((status: AuthStatus) => {
if (status.authenticated) {
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...

View File

@@ -6,7 +6,7 @@ import { getMockResponseCacheService } from '../../shared/mocks/mock-response-ca
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { BrowseEndpointRequest, BrowseEntriesRequest } from '../data/request.models';
import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseService } from './browse.service';
@@ -143,7 +143,9 @@ describe('BrowseService', () => {
});
describe('getBrowseEntriesFor', () => {
describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
const mockAuthorName = 'Donald Smith';
beforeEach(() => {
responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService();
@@ -156,7 +158,7 @@ describe('BrowseService', () => {
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
});
describe('when called with a valid browse definition id', () => {
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
it('should configure a new BrowseEntriesRequest', () => {
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
@@ -175,7 +177,26 @@ describe('BrowseService', () => {
});
describe('when called with an invalid browse definition id', () => {
describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
it('should configure a new BrowseItemsRequest', () => {
const expected = new BrowseItemsRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items + '?filterValue=' + mockAuthorName);
scheduler.schedule(() => service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName);
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
});
});
describe('when getBrowseEntriesFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => {
const definitionID = 'invalidID';
@@ -184,6 +205,16 @@ describe('BrowseService', () => {
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
});
});
describe('when getBrowseItemsFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => {
const definitionID = 'invalidID';
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`))
expect(service.getBrowseItemsFor(definitionID, mockAuthorName)).toBeObservable(expected);
});
});
});
describe('getBrowseURLFor', () => {

View File

@@ -16,19 +16,27 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
import { PaginatedList } from '../data/paginated-list';
import { RemoteData } from '../data/remote-data';
import { BrowseEndpointRequest, BrowseEntriesRequest, RestRequest } from '../data/request.models';
import {
BrowseEndpointRequest,
BrowseEntriesRequest,
BrowseItemsRequest,
GetRequest,
RestRequest
} from '../data/request.models';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntry } from '../shared/browse-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import {
configureRequest,
filterSuccessfulResponses,
filterSuccessfulResponses, getBrowseDefinitionLinks,
getRemoteDataPayload,
getRequestFromSelflink,
getResponseFromSelflink
} from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
@Injectable()
export class BrowseService {
@@ -71,6 +79,8 @@ export class BrowseService {
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload),
ensureArrayHasValue(),
map((definitions: BrowseDefinition[]) => definitions
.map((definition: BrowseDefinition) => Object.assign(new BrowseDefinition(), definition))),
distinctUntilChanged()
);
@@ -82,17 +92,7 @@ export class BrowseService {
sort?: SortOptions;
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
const request$ = this.getBrowseDefinitions().pipe(
getRemoteDataPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
.find((def: BrowseDefinition) => def.id === definitionID && def.metadataBrowse === true)
),
map((def: BrowseDefinition) => {
if (isNotEmpty(def)) {
return def._links;
} else {
throw new Error(`No metadata browse definition could be found for id '${definitionID}'`);
}
}),
getBrowseDefinitionLinks(definitionID),
hasValueOperator(),
map((_links: any) => _links.entries),
hasValueOperator(),
@@ -124,6 +124,66 @@ export class BrowseService {
filterSuccessfulResponses(),
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
map((list: PaginatedList<BrowseEntry>) => Object.assign(list, {
page: list.page ? list.page.map((entry: BrowseEntry) => Object.assign(new BrowseEntry(), entry)) : list.page
})),
distinctUntilChanged()
);
return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
}
/**
* Get all items linked to a certain metadata value
* @param {string} definitionID definition ID to define the metadata-field (e.g. author)
* @param {string} filterValue metadata value to filter by (e.g. author's name)
* @param options Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
*/
getBrowseItemsFor(definitionID: string, filterValue: string, options: {
pagination?: PaginationComponentOptions;
sort?: SortOptions;
} = {}): Observable<RemoteData<PaginatedList<Item>>> {
const request$ = this.getBrowseDefinitions().pipe(
getBrowseDefinitionLinks(definitionID),
hasValueOperator(),
map((_links: any) => _links.items),
hasValueOperator(),
map((href: string) => {
const args = [];
if (isNotEmpty(options.sort)) {
args.push(`sort=${options.sort.field},${options.sort.direction}`);
}
if (isNotEmpty(options.pagination)) {
args.push(`page=${options.pagination.currentPage - 1}`);
args.push(`size=${options.pagination.pageSize}`);
}
if (isNotEmpty(filterValue)) {
args.push(`filterValue=${filterValue}`);
}
if (isNotEmpty(args)) {
href = new URLCombiner(href, `?${args.join('&')}`).toString();
}
return href;
}),
map((endpointURL: string) => new BrowseItemsRequest(this.requestService.generateRequestId(), endpointURL)),
configureRequest(this.requestService)
);
const href$ = request$.pipe(map((request: RestRequest) => request.href));
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
const payload$ = responseCache$.pipe(
filterSuccessfulResponses(),
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<Item[]>) => new PaginatedList(response.pageInfo, response.payload)),
map((list: PaginatedList<Item>) => Object.assign(list, {
page: list.page ? list.page.map((item: DSpaceObject) => Object.assign(new Item(), item)) : list.page
})),
distinctUntilChanged()
);

View File

@@ -140,7 +140,7 @@ export class ErrorResponse extends RestResponse {
constructor(error: RequestError) {
super(false, error.statusText);
console.error(error);
// console.error(error);
this.errorMessage = error.message;
}
}

View File

@@ -62,6 +62,7 @@ import { RegistryMetadatafieldsResponseParsingService } from './data/registry-me
import { RegistryBitstreamformatsResponseParsingService } from './data/registry-bitstreamformats-response-parsing.service';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { UploaderService } from '../shared/uploader/uploader.service';
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
const IMPORTS = [
@@ -115,6 +116,7 @@ const PROVIDERS = [
ServerResponseService,
BrowseResponseParsingService,
BrowseEntriesResponseParsingService,
BrowseItemsResponseParsingService,
BrowseService,
ConfigResponseParsingService,
RouteService,

View File

@@ -0,0 +1,168 @@
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service';
import { BrowseEntriesRequest, BrowseItemsRequest } from './request.models';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
describe('BrowseItemsResponseParsingService', () => {
let service: BrowseItemsResponseParsingService;
beforeEach(() => {
service = new BrowseItemsResponseParsingService(undefined, getMockObjectCacheService());
});
describe('parse', () => {
const request = new BrowseItemsRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', 'https://rest.api/discover/browses/author/items');
const validResponse = {
payload: {
_embedded: {
items: [
{
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
{
id: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
uuid: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
name: 'Development of Local Supply Chain : The Missing Link for Concentrated Solar Power Projects in India',
handle: '10986/17475',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:33:42.526+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b'
}
}
}
]
},
_links: {
first: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=0&size=2'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items'
},
next: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=1&size=2'
},
last: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=7&size=2'
}
},
page: {
size: 2,
totalElements: 16,
totalPages: 8,
number: 0
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseNotAList = {
payload: {
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseStatusCode = {
payload: {}, statusCode: '500'
} as DSpaceRESTV2Response;
it('should return a GenericSuccessResponse if data contains a valid browse items response', () => {
const response = service.parse(request, validResponse);
expect(response.constructor).toBe(GenericSuccessResponse);
});
it('should return an ErrorResponse if data contains an invalid browse entries response', () => {
const response = service.parse(request, invalidResponseNotAList);
expect(response.constructor).toBe(ErrorResponse);
});
it('should return an ErrorResponse if data contains a statuscode other than 200', () => {
const response = service.parse(request, invalidResponseStatusCode);
expect(response.constructor).toBe(ErrorResponse);
});
});
});

View File

@@ -0,0 +1,58 @@
import { Inject, Injectable } from '@angular/core';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import {
ErrorResponse,
GenericSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BaseResponseParsingService } from './base-response-parsing.service';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
/**
* A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to Browse Items (DSpaceObject[])
*/
@Injectable()
export class BrowseItemsResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = {
getConstructor: () => DSpaceObject
};
protected toCache = false;
constructor(
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,
) { super();
}
/**
* Parses data from the browse endpoint to a list of DSpaceObjects
* @param {RestRequest} request
* @param {DSpaceRESTV2Response} data
* @returns {RestResponse}
*/
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded)
&& Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
const serializer = new DSpaceRESTv2Serializer(DSpaceObject);
const items = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
return new GenericSuccessResponse(items, data.statusCode, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from browse endpoint'),
{ statusText: data.statusCode }
)
);
}
}
}

View File

@@ -10,11 +10,18 @@ describe('BrowseResponseParsingService', () => {
beforeEach(() => {
service = new BrowseResponseParsingService();
});
let validRequest;
let validResponse;
let invalidResponse1;
let invalidResponse2;
let invalidResponse3;
let definitions;
describe('parse', () => {
const validRequest = new BrowseEndpointRequest('client/b186e8ce-e99c-4183-bc9a-42b4821bdb78', 'https://rest.api/discover/browses');
beforeEach(() => {
validRequest = new BrowseEndpointRequest('client/b186e8ce-e99c-4183-bc9a-42b4821bdb78', 'https://rest.api/discover/browses');
const validResponse = {
validResponse = {
payload: {
_embedded: {
browses: [{
@@ -51,7 +58,7 @@ describe('BrowseResponseParsingService', () => {
}, statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponse1 = {
invalidResponse1 = {
payload: {
_embedded: {
browse: {
@@ -74,21 +81,21 @@ describe('BrowseResponseParsingService', () => {
}, statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponse2 = {
invalidResponse2 = {
payload: {
_links: { self: { href: 'https://rest.api/discover/browses' } },
page: { size: 20, totalElements: 2, totalPages: 1, number: 0 }
}, statusCode: '200'
} as DSpaceRESTV2Response ;
} as DSpaceRESTV2Response;
const invalidResponse3 = {
invalidResponse3 = {
payload: {
_links: { self: { href: 'https://rest.api/discover/browses' } },
page: { size: 20, totalElements: 2, totalPages: 1, number: 0 }
}, statusCode: '500'
} as DSpaceRESTV2Response;
const definitions = [
definitions = [
Object.assign(new BrowseDefinition(), {
metadataBrowse: false,
sortOptions: [
@@ -110,7 +117,10 @@ describe('BrowseResponseParsingService', () => {
metadataKeys: [
'dc.date.issued'
],
_links: { }
_links: {
self: 'https://rest.api/discover/browses/dateissued',
items: 'https://rest.api/discover/browses/dateissued/items'
}
}),
Object.assign(new BrowseDefinition(), {
metadataBrowse: true,
@@ -134,10 +144,14 @@ describe('BrowseResponseParsingService', () => {
'dc.contributor.*',
'dc.creator'
],
_links: { }
_links: {
self: 'https://rest.api/discover/browses/author',
entries: 'https://rest.api/discover/browses/author/entries',
items: 'https://rest.api/discover/browses/author/items'
}
})
];
});
it('should return a GenericSuccessResponse if data contains a valid browse endpoint response', () => {
const response = service.parse(validRequest, validResponse);
expect(response.constructor).toBe(GenericSuccessResponse);

View File

@@ -9,7 +9,7 @@ import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { RequestService } from './request.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@@ -52,6 +52,10 @@ describe('ComColDataService', () => {
const EnvConfig = {} as GlobalConfig;
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID
});
const communitiesEndpoint = 'https://rest.api/core/communities';
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
@@ -98,7 +102,7 @@ describe('ComColDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
@@ -112,7 +116,7 @@ describe('ComColDataService', () => {
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
@@ -128,13 +132,13 @@ describe('ComColDataService', () => {
});
it('should fetch the scope Community from the cache', () => {
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID);
});
it('should return the endpoint to fetch resources within the given scope', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--e-', { e: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -151,7 +155,7 @@ describe('ComColDataService', () => {
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
expect(result).toBeObservable(expected);

View File

@@ -1,5 +1,5 @@
import { Observable, throwError as observableThrowError, merge as observableMerge } from 'rxjs';
import { distinctUntilChanged, filter, first, map, mergeMap, tap } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, mergeMap, take, tap } from 'rxjs/operators';
import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -7,10 +7,9 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DSOSuccessResponse } from '../cache/response-cache.models';
export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> {
protected abstract cds: CommunityDataService;
@@ -27,15 +26,16 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
* @return { Observable<string> }
* an Observable<string> containing the scoped URL
*/
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
if (isEmpty(options.scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)),
first((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID)),
filter((href: string) => isNotEmpty(href)),
take(1),
tap((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID);
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
this.requestService.configure(request);
}),);
@@ -61,16 +61,15 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
map((entry: ResponseCacheEntry) => entry.response));
const errorResponses = responses.pipe(
filter((response) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`)))
mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))
);
const successResponses = responses.pipe(
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getByUUID(scopeID)),
mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
filter((href) => isNotEmpty(href))
);
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
}
}

View File

@@ -41,7 +41,7 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity,
findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
const hrefObs = this.halService.getEndpoint(this.topLinkPath).pipe(filter((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
mergeMap((endpoint: string) => this.getFindAllHref(options)),);
hrefObs.pipe(
filter((href: string) => hasValue(href)),

View File

@@ -9,9 +9,9 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Observable } from 'rxjs';
import { FindAllOptions } from './request.models';
import { SortOptions, SortDirection } from '../cache/models/sort-options.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { of as observableOf } from 'rxjs';
const LINK_NAME = 'test'
const endpoint = 'https://rest.api/core';
// tslint:disable:max-classes-per-file
class NormalizedTestObject extends NormalizedObject {
@@ -30,10 +30,9 @@ class TestService extends DataService<NormalizedTestObject, any> {
super();
}
public getScopedEndpoint(scope: string): Observable<string> {
throw new Error('getScopedEndpoint is abstract in DataService');
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return observableOf(endpoint);
}
}
describe('DataService', () => {
@@ -45,7 +44,6 @@ describe('DataService', () => {
const rdbService = {} as RemoteDataBuildService;
const objectCache = {} as ObjectCacheService;
const store = {} as Store<CoreState>;
const endpoint = 'https://rest.api/core';
function initTestService(): TestService {
return new TestService(
@@ -53,9 +51,8 @@ describe('DataService', () => {
requestService,
rdbService,
store,
LINK_NAME,
halService,
objectCache
endpoint,
halService
);
}
@@ -66,27 +63,17 @@ describe('DataService', () => {
it('should return an observable with the endpoint', () => {
options = {};
(service as any).getFindAllHref(endpoint).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(endpoint);
}
);
});
// getScopedEndpoint is not implemented in abstract DataService
it('should throw error if scopeID provided in options', () => {
options = { scopeID: 'somevalue' };
expect(() => {
(service as any).getFindAllHref(endpoint, options)
})
.toThrowError('getScopedEndpoint is abstract in DataService');
});
it('should include page in href if currentPage provided in options', () => {
options = { currentPage: 2 };
const expected = `${endpoint}?page=${options.currentPage - 1}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -95,7 +82,7 @@ describe('DataService', () => {
options = { elementsPerPage: 5 };
const expected = `${endpoint}?size=${options.elementsPerPage}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -105,7 +92,7 @@ describe('DataService', () => {
options = { sort: sortOptions };
const expected = `${endpoint}?sort=${sortOptions.field},${sortOptions.direction}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -114,7 +101,7 @@ describe('DataService', () => {
options = { startsWith: 'ab' };
const expected = `${endpoint}?startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -130,7 +117,7 @@ describe('DataService', () => {
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
})

View File

@@ -1,6 +1,5 @@
import {of as observableOf, Observable } from 'rxjs';
import { distinctUntilChanged, filter, take, first, map } from 'rxjs/operators';
import { of as observableOf, Observable } from 'rxjs';
import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
@@ -27,17 +26,13 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
protected abstract halService: HALEndpointService;
protected abstract objectCache: ObjectCacheService;
public abstract getScopedEndpoint(scope: string): Observable<string>
public abstract getBrowseEndpoint(options: FindAllOptions): Observable<string>
protected getFindAllHref(endpoint, options: FindAllOptions = {}): Observable<string> {
protected getFindAllHref(options: FindAllOptions = {}): Observable<string> {
let result: Observable<string>;
const args = [];
if (hasValue(options.scopeID)) {
result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged());
} else {
result = observableOf(endpoint);
}
result = this.getBrowseEndpoint(options).pipe(distinctUntilChanged());
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
@@ -64,8 +59,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
}
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
const hrefObs = this.getFindAllHref(options);
hrefObs.pipe(
filter((href: string) => hasValue(href)),

View File

@@ -10,6 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DataService } from './data.service';
import { RemoteData } from './remote-data';
import { RequestService } from './request.service';
import { FindAllOptions } from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service';
/* tslint:disable:max-classes-per-file */
@@ -26,8 +27,8 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
super();
}
getScopedEndpoint(scope: string): Observable<string> {
return undefined;
getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
getFindByIDHref(endpoint, resourceID): string {

View File

@@ -9,6 +9,7 @@ import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { FindAllOptions } from './request.models';
describe('ItemDataService', () => {
let scheduler: TestScheduler;
@@ -22,6 +23,14 @@ describe('ItemDataService', () => {
const halEndpointService = {} as HALEndpointService;
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID,
sort: {
field: '',
direction: undefined
}
});
const browsesEndpoint = 'https://rest.api/discover/browses';
const itemBrowseEndpoint = `${browsesEndpoint}/author/items`;
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
@@ -49,16 +58,16 @@ describe('ItemDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
it('should return the endpoint to fetch Items within the given scope', () => {
it('should return the endpoint to fetch Items within the given scope and starting with the given string', () => {
bs = initMockBrowseService(true);
service = initTestService();
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--b-', { b: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -70,7 +79,7 @@ describe('ItemDataService', () => {
service = initTestService();
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, browseError);
expect(result).toBeObservable(expected);

View File

@@ -1,11 +1,9 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model';
@@ -17,6 +15,7 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service';
@Injectable()
@@ -34,15 +33,21 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
super();
}
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()),
distinctUntilChanged(),);
/**
* Get the endpoint for browsing items
* (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
let field = 'dc.date.issued';
if (options.sort && options.sort.field) {
field = options.sort.field;
}
return this.bs.getBrowseURLFor(field, this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString()),
distinctUntilChanged(),);
}
}

View File

@@ -10,6 +10,7 @@ import { AuthResponseParsingService } from '../auth/auth-response-parsing.servic
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
import { RestRequestMethod } from './rest-request-method';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
/* tslint:disable:max-classes-per-file */
@@ -164,6 +165,12 @@ export class BrowseEntriesRequest extends GetRequest {
}
}
export class BrowseItemsRequest extends GetRequest {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return BrowseItemsResponseParsingService;
}
}
export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);

View File

@@ -1,37 +0,0 @@
import { select } from '@ngrx/store';
import * as ngrx from '@ngrx/store';
import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs';
import { count, take } from 'rxjs/operators';
class TestClass {
selectSomething(input$: Observable<any>) {
return input$.pipe(
select('something'),
take(1)
)
}
}
describe('mockSelect', () => {
let testClass;
beforeEach(() => {
spyOnProperty(ngrx, 'select').and.callFake(() => {
return () => {
return () => cold('a', { a: 'bingo!' });
};
});
testClass = new TestClass();
});
it('should mock select', () => {
const input$ = hot('a', { a: '' });
const expected$ = hot('(b|)', { b: 'bingo!' });
const result$ = testClass.selectSomething(input$);
result$.pipe(count()).subscribe((t) => console.log('resykts', t));
expected$.pipe(count()).subscribe((t) => console.log('expected', t));
result$.subscribe((v) => console.log('result$', v));
expected$.subscribe((v) => console.log('expected$', v));
expect(result$).toBeObservable(expected$)
});
})

View File

@@ -1,29 +1,18 @@
import {distinctUntilKeyChanged, map, filter, first, take} from 'rxjs/operators';
import { distinctUntilKeyChanged, filter, first, map, take } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import {
ActivatedRoute,
Event,
NavigationEnd,
Params,
Router
} from '@angular/router';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject , Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { RemoteData } from '../data/remote-data';
import { Bitstream } from '../shared/bitstream.model';
import { CacheableObject } from '../cache/object-cache.reducer';
import { DSpaceObject } from '../shared/dspace-object.model';
import { Item } from '../shared/item.model';
import { Metadatum } from '../shared/metadatum.model';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { BitstreamFormat } from '../shared/bitstream-format.model';

View File

@@ -1,6 +1,7 @@
import { autoserialize, autoserializeAs } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
export class BrowseEntry {
export class BrowseEntry implements ListableObject {
@autoserialize
type: string;

View File

@@ -5,6 +5,7 @@ import { RemoteData } from '../data/remote-data';
import { ResourceType } from './resource-type';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { Observable } from 'rxjs';
import { autoserialize } from 'cerialize';
/**
* An abstract model class for a DSpaceObject.
@@ -16,11 +17,13 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/**
* The human-readable identifier of this DSpaceObject
*/
@autoserialize
id: string;
/**
* The universally unique identifier of this DSpaceObject
*/
@autoserialize
uuid: string;
/**
@@ -31,11 +34,13 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/**
* The name for this DSpaceObject
*/
@autoserialize
name: string;
/**
* An array containing all metadata of this DSpaceObject
*/
@autoserialize
metadata: Metadatum[];
/**

View File

@@ -1,6 +1,6 @@
import { Observable } from 'rxjs';
import { filter, first, flatMap, map, tap } from 'rxjs/operators';
import { hasValueOperator } from '../../shared/empty.util';
import { hasValueOperator, isNotEmpty } from '../../shared/empty.util';
import { DSOSuccessResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
@@ -8,6 +8,7 @@ import { RemoteData } from '../data/remote-data';
import { RestRequest } from '../data/request.models';
import { RequestEntry } from '../data/request.reducer';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model';
import { PaginatedList } from '../data/paginated-list';
import { SearchResult } from '../../+search-page/search-result.model';
@@ -62,3 +63,24 @@ export const toDSpaceObjectListRD = () =>
return Object.assign(rd, {payload: payload});
})
);
/**
* Get the browse links from a definition by ID given an array of all definitions
* @param {string} definitionID
* @returns {(source: Observable<RemoteData<BrowseDefinition[]>>) => Observable<any>}
*/
export const getBrowseDefinitionLinks = (definitionID: string) =>
(source: Observable<RemoteData<BrowseDefinition[]>>): Observable<any> =>
source.pipe(
getRemoteDataPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
.find((def: BrowseDefinition) => def.id === definitionID && def.metadataBrowse === true)
),
map((def: BrowseDefinition) => {
if (isNotEmpty(def)) {
return def._links;
} else {
throw new Error(`No metadata browse definition could be found for id '${definitionID}'`);
}
})
);

View File

@@ -22,23 +22,28 @@ describe('AuthNavMenuComponent', () => {
let deNavMenuItem: DebugElement;
let fixture: ComponentFixture<AuthNavMenuComponent>;
const notAuthState: AuthState = {
let notAuthState: AuthState;
let authState: AuthState;
let routerState = {
url: '/home'
};
function init() {
notAuthState = {
authenticated: false,
loaded: false,
loading: false
};
const authState: AuthState = {
authState = {
authenticated: true,
loaded: true,
loading: false,
authToken: new AuthTokenInfo('test_token'),
user: EPersonMock
};
let routerState = {
url: '/home'
};
}
describe('when is a not mobile view', () => {
beforeEach(async(() => {
const window = new HostWindowServiceStub(800);
@@ -53,8 +58,13 @@ describe('AuthNavMenuComponent', () => {
AuthNavMenuComponent
],
providers: [
{provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
{ provide: HostWindowService, useValue: window },
{
provide: AuthService, useValue: {
setRedirectUrl: () => { /*empty*/
}
}
}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
@@ -64,11 +74,14 @@ describe('AuthNavMenuComponent', () => {
}));
beforeEach(() => {
init();
});
describe('when route is /login and user is not authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => {
routerState = {
url: '/login'
};
beforeEach(inject([Store], (store: Store<AppState>) => {
store
.subscribe((state) => {
(state as any).router = Object.create({});
@@ -91,7 +104,9 @@ describe('AuthNavMenuComponent', () => {
const navMenuItemSelector = 'li';
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
});
it('should not render', () => {
expect(component).toBeTruthy();
expect(deNavMenu.nativeElement).toBeDefined();
@@ -101,10 +116,10 @@ describe('AuthNavMenuComponent', () => {
});
describe('when route is /logout and user is authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => {
routerState = {
url: '/logout'
};
beforeEach(inject([Store], (store: Store<AppState>) => {
store
.subscribe((state) => {
(state as any).router = Object.create({});
@@ -128,6 +143,10 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
});
it('should not render', () => {
expect(component).toBeTruthy();
expect(deNavMenu.nativeElement).toBeDefined();
@@ -166,6 +185,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render login dropdown menu', () => {
const loginDropdownMenu = deNavMenuItem.query(By.css('div[id=loginDropdownMenu]'));
expect(loginDropdownMenu.nativeElement).toBeDefined();
@@ -200,6 +224,10 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render logout dropdown menu', () => {
const logoutDropdownMenu = deNavMenuItem.query(By.css('div[id=logoutDropdownMenu]'));
expect(logoutDropdownMenu.nativeElement).toBeDefined();
@@ -223,8 +251,13 @@ describe('AuthNavMenuComponent', () => {
AuthNavMenuComponent
],
providers: [
{provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
{ provide: HostWindowService, useValue: window },
{
provide: AuthService, useValue: {
setRedirectUrl: () => { /*empty*/
}
}
}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
@@ -234,6 +267,9 @@ describe('AuthNavMenuComponent', () => {
}));
beforeEach(() => {
init();
});
describe('when user is not authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => {
@@ -260,6 +296,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render login link', () => {
const loginDropdownMenu = deNavMenuItem.query(By.css('a[id=loginLink]'));
expect(loginDropdownMenu.nativeElement).toBeDefined();
@@ -291,6 +332,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
}));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render logout link', inject([Store], (store: Store<AppState>) => {
const logoutDropdownMenu = deNavMenuItem.query(By.css('a[id=logoutLink]'));
expect(logoutDropdownMenu.nativeElement).toBeDefined();

View File

@@ -1,4 +1,4 @@
import { of as observableOf, Observable } from 'rxjs';
import { of as observableOf, Observable , Subscription } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';

View File

@@ -0,0 +1,12 @@
<ng-container *ngVar="(objects$ | async) as objects">
<h2 class="w-100">{{title}}</h2>
<div *ngIf="objects?.hasSucceeded && !objects?.isLoading && objects?.payload?.page.length > 0" @fadeIn>
<ds-viewable-collection
[config]="paginationConfig"
[sortConfig]="sortConfig"
[objects]="objects">
</ds-viewable-collection>
</div>
<ds-loading *ngIf="!objects || objects?.payload?.page.length <= 0" message="{{'loading.browse-by' | translate}}"></ds-loading>
<ds-error *ngIf="objects?.hasFailed" message="{{'error.browse-by' | translate}}"></ds-error>
</ng-container>

View File

@@ -0,0 +1,44 @@
import { BrowseByComponent } from './browse-by.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { SharedModule } from '../shared.module';
describe('BrowseByComponent', () => {
let comp: BrowseByComponent;
let fixture: ComponentFixture<BrowseByComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule],
declarations: [],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BrowseByComponent);
comp = fixture.componentInstance;
});
it('should display a loading message when objects is empty',() => {
(comp as any).objects = undefined;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-loading'))).toBeDefined();
});
it('should display results when objects is not empty', () => {
(comp as any).objects = observableOf({
payload: {
page: {
length: 1
}
}
});
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-viewable-collection'))).toBeDefined();
});
});

View File

@@ -0,0 +1,30 @@
import { Component, Input } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { SortOptions } from '../../core/cache/models/sort-options.model';
import { fadeIn, fadeInOut } from '../animations/fade';
import { Observable } from 'rxjs';
import { Item } from '../../core/shared/item.model';
import { ListableObject } from '../object-collection/shared/listable-object.model';
@Component({
selector: 'ds-browse-by',
styleUrls: ['./browse-by.component.scss'],
templateUrl: './browse-by.component.html',
animations: [
fadeIn,
fadeInOut
]
})
/**
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent {
@Input() title: string;
@Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>;
@Input() paginationConfig: PaginationComponentOptions;
@Input() sortConfig: SortOptions;
@Input() currentUrl: string;
query: string;
}

View File

@@ -172,7 +172,6 @@ describe('DsDynamicFormControlComponent test suite', () => {
});
fixture.detectChanges();
console.log(fixture.componentInstance.componentViewContainerRef);
testElement = debugElement.query(By.css(`input[id='${testModel.id}']`));
}));

View File

@@ -20,7 +20,6 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicDsDatePickerModel;
// @Input() showErrorMessages = false;
// @Input()
// minDate;
// @Input()

View File

@@ -6,16 +6,16 @@ import {
import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './tag/dynamic-tag.model';
export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig {
notRepeteable: boolean;
notRepeatable: boolean;
}
export class DynamicRowArrayModel extends DynamicFormArrayModel {
@serializable() notRepeteable = false;
@serializable() notRepeatable = false;
isRowArray = true;
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
this.notRepeteable = config.notRepeteable;
this.notRepeatable = config.notRepeatable;
}
}

View File

@@ -4,7 +4,6 @@ import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { DsDynamicGroupComponent } from './dynamic-group.components';
@@ -23,18 +22,25 @@ import { Chips } from '../../../../../chips/models/chips.model';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { DsDynamicInputModel } from '../ds-dynamic-input.model';
import { createTestComponent } from '../../../../../testing/utils';
import { getMockFormBuilderService } from '../../../../../mocks/mock-form-builder-service';
import { getMockFormService } from '../../../../../mocks/mock-form-service';
import { MockComponent } from 'ng-mocks';
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
import { MockStore } from '../../../../../testing/mock-store';
import { Store } from '@ngrx/store';
import { AppState } from '../../../../../../app.reducer';
export const FORM_GROUP_TEST_MODEL_CONFIG = {
export let FORM_GROUP_TEST_MODEL_CONFIG;
export let FORM_GROUP_TEST_GROUP;
let config;
function init() {
FORM_GROUP_TEST_MODEL_CONFIG = {
disabled: false,
errorMessages: {required: 'You must specify at least one author.'},
errorMessages: { required: 'You must specify at least one author.' },
formConfiguration: [{
fields: [{
hints: 'Enter the name of the author.',
input: {type: 'onebox'},
input: { type: 'onebox' },
label: 'Author',
languageCodes: [],
mandatory: 'true',
@@ -49,7 +55,7 @@ export const FORM_GROUP_TEST_MODEL_CONFIG = {
} as FormRowModel, {
fields: [{
hints: 'Enter the affiliation of the author.',
input: {type: 'onebox'},
input: { type: 'onebox' },
label: 'Affiliation',
languageCodes: [],
mandatory: 'false',
@@ -71,15 +77,14 @@ export const FORM_GROUP_TEST_MODEL_CONFIG = {
required: true,
scopeUUID: '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f',
submissionScope: undefined,
validators: {required: null}
} as DynamicGroupModelConfig;
validators: { required: null }
} as DynamicGroupModelConfig;
export const FORM_GROUP_TEST_GROUP = new FormGroup({
FORM_GROUP_TEST_GROUP = new FormGroup({
dc_contributor_author: new FormControl(),
});
});
describe('DsDynamicGroupComponent test suite', () => {
const config = {
config = {
form: {
validatorMap: {
required: 'required',
@@ -87,11 +92,15 @@ describe('DsDynamicGroupComponent test suite', () => {
}
}
} as any;
}
describe('DsDynamicGroupComponent test suite', () => {
let testComp: TestComponent;
let groupComp: DsDynamicGroupComponent;
let testFixture: ComponentFixture<TestComponent>;
let groupFixture: ComponentFixture<DsDynamicGroupComponent>;
// let modelValue: any;
let modelValue: any;
let html;
let control1: FormControl;
let model1: DsDynamicInputModel;
@@ -100,7 +109,9 @@ describe('DsDynamicGroupComponent test suite', () => {
// async beforeEach
beforeEach(async(() => {
init();
const store = new MockStore<AppState>(Object.create(null));
/* TODO make sure these files use mocks instead of real services/components https://github.com/DSpace/dspace-angular/issues/281 */
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
@@ -110,18 +121,20 @@ describe('DsDynamicGroupComponent test suite', () => {
TranslateModule.forRoot()
],
declarations: [
MockComponent(FormComponent),
FormComponent,
DsDynamicGroupComponent,
TestComponent,
], // declare the test component
providers: [
ChangeDetectorRef,
DsDynamicGroupComponent,
{provide: FormBuilderService, useValue: getMockFormBuilderService()},
{provide: FormService, useValue: getMockFormService()},
{provide: GLOBAL_CONFIG, useValue: config},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
DynamicFormValidationService,
DynamicFormLayoutService,
FormBuilderService,
FormComponent,
FormService,
{ provide: GLOBAL_CONFIG, useValue: config },
{provide: Store, useValue: store},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
@@ -143,6 +156,11 @@ describe('DsDynamicGroupComponent test suite', () => {
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
testComp = null;
});
it('should create DsDynamicGroupComponent', inject([DsDynamicGroupComponent], (app: DsDynamicGroupComponent) => {
expect(app).toBeDefined();
@@ -158,7 +176,6 @@ describe('DsDynamicGroupComponent test suite', () => {
groupComp.group = FORM_GROUP_TEST_GROUP;
groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
groupFixture.detectChanges();
control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel;
control2 = service.getFormControlById('local_contributor_affiliation', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
@@ -172,123 +189,132 @@ describe('DsDynamicGroupComponent test suite', () => {
groupComp = null;
});
// it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
// const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel;
// const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
// const chips = new Chips([], 'value', 'dc.contributor.author');
//
// expect(groupComp.formCollapsed).toEqual(observableOf(false));
// expect(groupComp.formModel.length).toEqual(formModel.length);
// expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
// }));
//
// it('should save a new chips item', () => {
// control1.setValue('test author');
// (model1 as any).value = new FormFieldMetadataValueObject('test author');
// control2.setValue('test affiliation');
// (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
// modelValue = [{
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// }];
// groupFixture.detectChanges();
//
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[0];
// btnEl.click();
//
// expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
// expect(groupComp.formCollapsed).toEqual(observableOf(true));
// });
//
// it('should clear form inputs', () => {
// control1.setValue('test author');
// (model1 as any).value = new FormFieldMetadataValueObject('test author');
// control2.setValue('test affiliation');
// (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
//
// groupFixture.detectChanges();
//
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[2];
// btnEl.click();
//
// expect(control1.value).toBeNull();
// expect(control2.value).toBeNull();
// expect(groupComp.formCollapsed).toEqual(observableOf(false));
// });
// });
//
// describe('when init model value is not empty', () => {
// beforeEach(() => {
//
// groupFixture = TestBed.createComponent(DsDynamicGroupComponent);
// groupComp = groupFixture.componentInstance; // FormComponent test instance
// groupComp.formId = 'testForm';
// groupComp.group = FORM_GROUP_TEST_GROUP;
// groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
// modelValue = [{
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// }];
// groupComp.model.value = modelValue;
// groupComp.showErrorMessages = false;
// groupFixture.detectChanges();
//
// });
//
// afterEach(() => {
// groupFixture.destroy();
// groupComp = null;
// });
//
// it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
// const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel;
// const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
// const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
//
// expect(groupComp.formCollapsed).toEqual(observableOf(true));
// expect(groupComp.formModel.length).toEqual(formModel.length);
// expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
// }));
//
// it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => {
// groupComp.onChipSelected(0);
// groupFixture.detectChanges();
//
// control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
// model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel;
//
// control1.setValue('test author modify');
// (model1 as any).value = new FormFieldMetadataValueObject('test author modify');
//
// modelValue = [{
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'),
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// }];
// groupFixture.detectChanges();
//
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[0];
// btnEl.click();
//
// groupFixture.detectChanges();
//
// expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
// expect(groupComp.formCollapsed).toEqual(observableOf(true));
// }));
//
// it('should delete existing chips item', () => {
// groupComp.onChipSelected(0);
// groupFixture.detectChanges();
//
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[1];
// btnEl.click();
//
// expect(groupComp.chips.getChipsItems()).toEqual([]);
// expect(groupComp.formCollapsed).toEqual(observableOf(false));
// });
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips([], 'value', 'dc.contributor.author');
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(false);
});
expect(groupComp.formModel.length).toEqual(formModel.length);
expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
}));
it('should save a new chips item', () => {
control1.setValue('test author');
(model1 as any).value = new FormFieldMetadataValueObject('test author');
control2.setValue('test affiliation');
(model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}];
groupFixture.detectChanges();
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[0];
btnEl.click();
expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(true);
})
});
it('should clear form inputs', () => {
control1.setValue('test author');
(model1 as any).value = new FormFieldMetadataValueObject('test author');
control2.setValue('test affiliation');
(model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
groupFixture.detectChanges();
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[2];
btnEl.click();
expect(control1.value).toBeNull();
expect(control2.value).toBeNull();
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(false);
});
});
});
describe('when init model value is not empty', () => {
beforeEach(() => {
groupFixture = TestBed.createComponent(DsDynamicGroupComponent);
groupComp = groupFixture.componentInstance; // FormComponent test instance
groupComp.formId = 'testForm';
groupComp.group = FORM_GROUP_TEST_GROUP;
groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}];
groupComp.model.value = modelValue;
groupFixture.detectChanges();
});
afterEach(() => {
groupFixture.destroy();
groupComp = null;
});
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(true);
})
expect(groupComp.formModel.length).toEqual(formModel.length);
expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
}));
it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => {
groupComp.onChipSelected(0);
groupFixture.detectChanges();
control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel;
control1.setValue('test author modify');
(model1 as any).value = new FormFieldMetadataValueObject('test author modify');
modelValue = [{
'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'),
'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
}];
groupFixture.detectChanges();
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[0];
btnEl.click();
groupFixture.detectChanges();
expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(true);
})
}));
it('should delete existing chips item', () => {
groupComp.onChipSelected(0);
groupFixture.detectChanges();
const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
const btnEl = buttons[1];
btnEl.click();
expect(groupComp.chips.getChipsItems()).toEqual([]);
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(false);
})
});
});
});

View File

@@ -45,7 +45,6 @@ export class DsDynamicGroupComponent extends DynamicFormControlComponent impleme
@Input() formId: string;
@Input() group: FormGroup;
@Input() model: DynamicGroupModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -33,7 +33,6 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicListCheckboxGroupModel | DynamicListRadioGroupModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -18,16 +18,13 @@ import { DsDynamicLookupComponent } from './dynamic-lookup.component';
import { DynamicLookupModel } from './dynamic-lookup.model';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { TranslateModule } from '@ngx-translate/core';
import { FormBuilderService } from '../../../form-builder.service';
import { FormService } from '../../../../form.service';
import { FormComponent } from '../../../../form.component';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { By } from '@angular/platform-browser';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import { createTestComponent } from '../../../../../testing/utils';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
export const LOOKUP_TEST_MODEL_CONFIG = {
let LOOKUP_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'lookup',
@@ -35,7 +32,7 @@ export const LOOKUP_TEST_MODEL_CONFIG = {
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions,
disabled: false,
errorMessages: {required: 'Required field.'},
errorMessages: { required: 'Required field.' },
id: 'lookup',
label: 'Author',
maxOptions: 10,
@@ -45,11 +42,11 @@ export const LOOKUP_TEST_MODEL_CONFIG = {
required: true,
repeatable: true,
separator: ',',
validators: {required: null},
validators: { required: null },
value: undefined
};
export const LOOKUP_NAME_TEST_MODEL_CONFIG = {
let LOOKUP_NAME_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'lookup-name',
@@ -57,7 +54,7 @@ export const LOOKUP_NAME_TEST_MODEL_CONFIG = {
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions,
disabled: false,
errorMessages: {required: 'Required field.'},
errorMessages: { required: 'Required field.' },
id: 'lookupName',
label: 'Author',
maxOptions: 10,
@@ -67,16 +64,67 @@ export const LOOKUP_NAME_TEST_MODEL_CONFIG = {
required: true,
repeatable: true,
separator: ',',
validators: {required: null},
validators: { required: null },
value: undefined
};
export const LOOKUP_TEST_GROUP = new FormGroup({
let LOOKUP_TEST_GROUP = new FormGroup({
lookup: new FormControl(),
lookupName: new FormControl()
});
describe('Dynamic Lookup component', () => {
function init() {
LOOKUP_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'lookup',
name: 'RPAuthority',
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions,
disabled: false,
errorMessages: { required: 'Required field.' },
id: 'lookup',
label: 'Author',
maxOptions: 10,
name: 'lookup',
placeholder: 'Author',
readOnly: false,
required: true,
repeatable: true,
separator: ',',
validators: { required: null },
value: undefined
};
LOOKUP_NAME_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'lookup-name',
name: 'RPAuthority',
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions,
disabled: false,
errorMessages: { required: 'Required field.' },
id: 'lookupName',
label: 'Author',
maxOptions: 10,
name: 'lookupName',
placeholder: 'Author',
readOnly: false,
required: true,
repeatable: true,
separator: ',',
validators: { required: null },
value: undefined
};
LOOKUP_TEST_GROUP = new FormGroup({
lookup: new FormControl(),
lookupName: new FormControl()
});
}
let testComp: TestComponent;
let lookupComp: DsDynamicLookupComponent;
@@ -84,11 +132,11 @@ describe('Dynamic Lookup component', () => {
let lookupFixture: ComponentFixture<DsDynamicLookupComponent>;
let html;
const authorityServiceStub = new AuthorityServiceStub();
let authorityServiceStub;
// async beforeEach
beforeEach(async(() => {
const authorityService = new AuthorityServiceStub();
authorityServiceStub = authorityService;
TestBed.configureTestingModule({
imports: [
DynamicFormsCoreModule,
@@ -106,16 +154,19 @@ describe('Dynamic Lookup component', () => {
providers: [
ChangeDetectorRef,
DsDynamicLookupComponent,
{provide: AuthorityService, useValue: authorityServiceStub},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
{ provide: AuthorityService, useValue: authorityService },
{ provide: DynamicFormLayoutService, useValue: {} },
{ provide: DynamicFormValidationService, useValue: {} }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
}));
describe('', () => {
beforeEach(() => {
init();
});
describe('DynamicLookUpComponent', () => {
// synchronous beforeEach
beforeEach(() => {
html = `
@@ -130,11 +181,13 @@ describe('Dynamic Lookup component', () => {
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
testComp = null;
});
it('should create DsDynamicLookupComponent', inject([DsDynamicLookupComponent], (app: DsDynamicLookupComponent) => {
expect(app).toBeDefined();
}));
});
describe('when model is DynamicLookupModel', () => {
@@ -147,7 +200,10 @@ describe('Dynamic Lookup component', () => {
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
lookupFixture.detectChanges();
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should render only an input element', () => {
const de = lookupFixture.debugElement.queryAll(By.css('input.form-control'));
expect(de.length).toBe(1);
@@ -157,7 +213,6 @@ describe('Dynamic Lookup component', () => {
describe('and init model value is empty', () => {
beforeEach(() => {
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
lookupComp.group = LOOKUP_TEST_GROUP;
@@ -165,6 +220,11 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges();
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => {
expect(lookupComp.firstInputValue).toBe('');
});
@@ -182,16 +242,19 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges();
results$.subscribe((results) => {
expect(lookupComp.optionsList).toEqual(results.payload);
})
});
}));
it('should select a results entry properly', fakeAsync(() => {
let de = lookupFixture.debugElement.queryAll(By.css('button'));
const btnEl = de[0].nativeElement;
const selectedValue = Object.assign(new AuthorityValueModel(), {id: 1, display: 'one', value: 1});
const selectedValue = Object.assign(new AuthorityValueModel(), {
id: 1,
display: 'one',
value: 1
});
spyOn(lookupComp.change, 'emit');
lookupComp.firstInputValue = 'test';
lookupFixture.detectChanges();
btnEl.click();
@@ -200,7 +263,7 @@ describe('Dynamic Lookup component', () => {
de = lookupFixture.debugElement.queryAll(By.css('button.dropdown-item'));
const entryEl = de[0].nativeElement;
entryEl.click();
lookupFixture.detectChanges();
expect(lookupComp.firstInputValue).toEqual('one');
expect(lookupComp.model.value).toEqual(selectedValue);
expect(lookupComp.change.emit).toHaveBeenCalled();
@@ -238,9 +301,12 @@ describe('Dynamic Lookup component', () => {
// spyOn(store, 'dispatch');
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => {
expect(lookupComp.firstInputValue).toBe('test')
expect(lookupComp.firstInputValue).toBe('test');
});
});
});
@@ -258,7 +324,10 @@ describe('Dynamic Lookup component', () => {
// spyOn(store, 'dispatch');
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should render two input element', () => {
const de = lookupFixture.debugElement.queryAll(By.css('input.form-control'));
expect(de.length).toBe(2);
@@ -277,15 +346,31 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges();
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should select a results entry properly', fakeAsync(() => {
const payload = [
Object.assign(new AuthorityValueModel(), {id: 1, display: 'Name, Lastname', value: 1}),
Object.assign(new AuthorityValueModel(), {id: 2, display: 'NameTwo, LastnameTwo', value: 2}),
Object.assign(new AuthorityValueModel(), {
id: 1,
display: 'Name, Lastname',
value: 1
}),
Object.assign(new AuthorityValueModel(), {
id: 2,
display: 'NameTwo, LastnameTwo',
value: 2
}),
];
let de = lookupFixture.debugElement.queryAll(By.css('button'));
const btnEl = de[0].nativeElement;
const selectedValue = Object.assign(new AuthorityValueModel(), {id: 1, display: 'Name, Lastname', value: 1});
const selectedValue = Object.assign(new AuthorityValueModel(), {
id: 1,
display: 'Name, Lastname',
value: 1
});
spyOn(lookupComp.change, 'emit');
authorityServiceStub.setNewPayload(payload);
lookupComp.firstInputValue = 'test';
@@ -302,7 +387,6 @@ describe('Dynamic Lookup component', () => {
expect(lookupComp.model.value).toEqual(selectedValue);
expect(lookupComp.change.emit).toHaveBeenCalled();
}));
});
describe('and init model value is not empty', () => {
@@ -316,13 +400,16 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges();
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => {
expect(lookupComp.firstInputValue).toBe('Name');
expect(lookupComp.secondInputValue).toBe('Lastname');
});
});
});
});
});
@@ -338,7 +425,4 @@ class TestComponent {
inputLookupModelConfig = LOOKUP_TEST_MODEL_CONFIG;
model = new DynamicLookupModel(this.inputLookupModelConfig);
showErrorMessages = false;
}

View File

@@ -28,7 +28,6 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicLookupModel | DynamicLookupNameModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -26,7 +26,6 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicScrollableDropdownModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -37,11 +37,15 @@ function createKeyUpEvent(key: number) {
return event;
}
export const TAG_TEST_GROUP = new FormGroup({
tag: new FormControl(),
});
let TAG_TEST_GROUP;
let TAG_TEST_MODEL_CONFIG;
export const TAG_TEST_MODEL_CONFIG = {
function init() {
TAG_TEST_GROUP = new FormGroup({
tag: new FormControl(),
});
TAG_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'tag',
@@ -57,7 +61,8 @@ export const TAG_TEST_MODEL_CONFIG = {
readOnly: false,
required: false,
repeatable: false
};
};
}
describe('DsDynamicTagComponent test suite', () => {
@@ -72,7 +77,7 @@ describe('DsDynamicTagComponent test suite', () => {
// async beforeEach
beforeEach(async(() => {
const authorityServiceStub = new AuthorityServiceStub();
init();
TestBed.configureTestingModule({
imports: [
DynamicFormsCoreModule,
@@ -88,10 +93,10 @@ describe('DsDynamicTagComponent test suite', () => {
providers: [
ChangeDetectorRef,
DsDynamicTagComponent,
{provide: AuthorityService, useValue: authorityServiceStub},
{provide: GLOBAL_CONFIG, useValue: {} as GlobalConfig},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
{ provide: AuthorityService, useValue: authorityServiceStub },
{ provide: GLOBAL_CONFIG, useValue: {} as GlobalConfig },
{ provide: DynamicFormLayoutService, useValue: {} },
{ provide: DynamicFormValidationService, useValue: {} }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
@@ -112,14 +117,16 @@ describe('DsDynamicTagComponent test suite', () => {
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
});
it('should create DsDynamicTagComponent', inject([DsDynamicTagComponent], (app: DsDynamicTagComponent) => {
expect(app).toBeDefined();
}));
});
describe('when authorityOptions are setted', () => {
describe('when authorityOptions are set', () => {
describe('and init model value is empty', () => {
beforeEach(() => {
@@ -138,6 +145,7 @@ describe('DsDynamicTagComponent test suite', () => {
it('should init component properly', () => {
chips = new Chips([], 'display');
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
expect(tagComp.searchOptions).toBeDefined();
});
@@ -151,10 +159,14 @@ describe('DsDynamicTagComponent test suite', () => {
it('should select a results entry properly', fakeAsync(() => {
modelValue = [
Object.assign(new AuthorityValueModel(), {id: 1, display: 'Name, Lastname', value: 1})
Object.assign(new AuthorityValueModel(), { id: 1, display: 'Name, Lastname', value: 1 })
];
const event: NgbTypeaheadSelectItemEvent = {
item: Object.assign(new AuthorityValueModel(), {id: 1, display: 'Name, Lastname', value: 1}),
item: Object.assign(new AuthorityValueModel(), {
id: 1,
display: 'Name, Lastname',
value: 1
}),
preventDefault: () => {
return;
}
@@ -229,7 +241,7 @@ describe('DsDynamicTagComponent test suite', () => {
});
describe('when authorityOptions are not setted', () => {
describe('when authorityOptions are not set', () => {
describe('and init model value is empty', () => {
beforeEach(() => {

View File

@@ -1,4 +1,3 @@
import {of as observableOf, Observable } from 'rxjs';
import {catchError, debounceTime, distinctUntilChanged, tap, switchMap, map, merge} from 'rxjs/operators';
@@ -29,7 +28,6 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicTagModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -86,6 +84,7 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
ngOnInit() {
this.hasAuthority = this.model.authorityOptions && hasValue(this.model.authorityOptions.name);
if (this.hasAuthority) {
this.searchOptions = new IntegrationSearchOptions(
this.model.authorityOptions.scope,

View File

@@ -22,11 +22,16 @@ import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { createTestComponent } from '../../../../../testing/utils';
export const TYPEAHEAD_TEST_GROUP = new FormGroup({
typeahead: new FormControl(),
});
export let TYPEAHEAD_TEST_GROUP;
export const TYPEAHEAD_TEST_MODEL_CONFIG = {
export let TYPEAHEAD_TEST_MODEL_CONFIG;
function init() {
TYPEAHEAD_TEST_GROUP = new FormGroup({
typeahead: new FormControl(),
});
TYPEAHEAD_TEST_MODEL_CONFIG = {
authorityOptions: {
closed: false,
metadata: 'typeahead',
@@ -43,8 +48,8 @@ export const TYPEAHEAD_TEST_MODEL_CONFIG = {
required: false,
repeatable: false,
value: undefined
};
};
}
describe('DsDynamicTypeaheadComponent test suite', () => {
let testComp: TestComponent;
@@ -56,7 +61,7 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
// async beforeEach
beforeEach(async(() => {
const authorityServiceStub = new AuthorityServiceStub();
init()
TestBed.configureTestingModule({
imports: [
DynamicFormsCoreModule,
@@ -72,9 +77,9 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
providers: [
ChangeDetectorRef,
DsDynamicTypeaheadComponent,
{provide: AuthorityService, useValue: authorityServiceStub},
{provide: DynamicFormLayoutService, useValue: {}},
{provide: DynamicFormValidationService, useValue: {}}
{ provide: AuthorityService, useValue: authorityServiceStub },
{ provide: DynamicFormLayoutService, useValue: {} },
{ provide: DynamicFormValidationService, useValue: {} }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
@@ -96,6 +101,9 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
});
it('should create DsDynamicTypeaheadComponent', inject([DsDynamicTypeaheadComponent], (app: DsDynamicTypeaheadComponent) => {
expect(app).toBeDefined();
@@ -221,6 +229,4 @@ class TestComponent {
model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
showErrorMessages = false;
}

View File

@@ -26,7 +26,6 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicTypeaheadModel;
@Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -13,13 +13,10 @@ import {
DynamicColorPickerModel,
DynamicDatePickerModel,
DynamicEditorModel,
DynamicFileUploadModel, DynamicFormArrayGroupModel,
DynamicFileUploadModel,
DynamicFormArrayModel,
DynamicFormControlModel,
// DynamicFormControlValue,
DynamicFormGroupModel,
DynamicFormService,
DynamicFormValidationService,
DynamicFormGroupModel, DynamicFormValidationService,
DynamicFormValueControlModel,
DynamicInputModel,
DynamicRadioGroupModel,
@@ -41,7 +38,10 @@ import { DynamicTypeaheadModel } from './ds-dynamic-form-ui/models/typeahead/dyn
import { DynamicListRadioGroupModel } from './ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
import { AuthorityOptions } from '../../../core/integration/models/authority-options.model';
import { FormFieldModel } from './models/form-field.model';
import { FormRowModel, SubmissionFormsModel } from '../../../core/shared/config/config-submission-forms.model';
import {
FormRowModel,
SubmissionFormsModel
} from '../../../core/shared/config/config-submission-forms.model';
import { FormBuilderService } from './form-builder.service';
import { DynamicRowGroupModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-input.model';
@@ -49,7 +49,6 @@ import { FormFieldMetadataValueObject } from './models/form-field-metadata-value
import { DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
import { DynamicLookupNameModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup-name.model';
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { getMockFormBuilderService } from '../../mocks/mock-form-builder-service';
describe('FormBuilderService test suite', () => {
@@ -70,7 +69,8 @@ describe('FormBuilderService test suite', () => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
providers: [
{provide: FormBuilderService, useValue: getMockFormBuilderService()},
{provide: FormBuilderService, useClass: FormBuilderService},
{provide: DynamicFormValidationService, useValue: {}},
{provide: NG_VALIDATORS, useValue: testValidator, multi: true},
{provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true}
]
@@ -254,7 +254,7 @@ describe('FormBuilderService test suite', () => {
{
id: 'testFormRowArray',
initialCount: 5,
notRepeteable: false,
notRepeatable: false,
groupFactory: () => {
return [
new DynamicInputModel({id: 'testFormRowArrayGroupInput'})

View File

@@ -35,7 +35,7 @@ export abstract class FieldParser {
id: uniqueId() + '_array',
label: this.configData.label,
initialCount: this.getInitArrayIndex(),
notRepeteable: !this.configData.repeatable,
notRepeatable: !this.configData.repeatable,
groupFactory: () => {
let model;
if ((arrayCounter === 0)) {

View File

@@ -14,7 +14,7 @@
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
<!--Array with repeteable items-->
<div *ngIf="!context.notRepeteable"
<div *ngIf="!context.notRepeatable"
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
<div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-secondary"
@@ -31,7 +31,7 @@
</div>
<!--Array with non repeteable items - Only delete button-->
<div *ngIf="context.notRepeteable && group.context.groups.length > 1"
<div *ngIf="context.notRepeatable && group.context.groups.length > 1"
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
<div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-secondary"

View File

@@ -1,28 +1,41 @@
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed, } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { BrowserModule, By } from '@angular/platform-browser';
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
DynamicFormArrayModel,
DynamicFormControlEvent,
DynamicFormControlModel,
DynamicFormValidationService,
DynamicInputModel
} from '@ng-dynamic-forms/core';
import { Store } from '@ngrx/store';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { FormComponent } from './form.component';
import { FormService } from './form.service';
import { FormBuilderService } from './builder/form-builder.service';
import { FormAddError, FormChangeAction } from './form.actions';
import { FormState } from './form.reducer';
import { FormChangeAction, FormStatusChangeAction } from './form.actions';
import { MockStore } from '../testing/mock-store';
import { FormFieldMetadataValueObject } from './builder/models/form-field-metadata-value.model';
import { GLOBAL_CONFIG } from '../../../config';
import { createTestComponent } from '../testing/utils';
import { getMockFormService } from '../mocks/mock-form-service';
import { getMockFormBuilderService } from '../mocks/mock-form-builder-service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
export const TEST_FORM_MODEL = [
let TEST_FORM_MODEL;
let TEST_FORM_MODEL_WITH_ARRAY;
let config;
let formState: FormState;
let html;
let store: MockStore<FormState>;
function init() {
TEST_FORM_MODEL = [
new DynamicInputModel(
{
id: 'dc_title',
@@ -68,9 +81,9 @@ export const TEST_FORM_MODEL = [
placeholder: 'Identifiers',
}
),
];
];
export const TEST_FORM_MODEL_WITH_ARRAY = [
TEST_FORM_MODEL_WITH_ARRAY = [
new DynamicFormArrayModel({
id: 'bootstrapFormArray',
@@ -87,16 +100,8 @@ export const TEST_FORM_MODEL_WITH_ARRAY = [
];
}
})
];
describe('FormComponent test suite', () => {
let testComp: TestComponent;
let formComp: FormComponent;
let testFixture: ComponentFixture<TestComponent>;
let formFixture: ComponentFixture<FormComponent>;
let dynamicForm;
const config = {
];
config = {
form: {
validatorMap: {
required: 'required',
@@ -105,12 +110,33 @@ describe('FormComponent test suite', () => {
}
} as any;
let html;
let formService;
let formBuilderService;
formState = {
testForm: {
data: {
dc_title: null,
dc_title_alternative: null,
dc_publisher: null,
dc_identifier_citation: null,
dc_identifier_issn: null
},
valid: false,
errors: []
}
};
store = new MockStore<FormState>(formState);
}
describe('FormComponent test suite', () => {
let testComp: TestComponent;
let formComp: FormComponent;
let testFixture: ComponentFixture<TestComponent>;
let formFixture: ComponentFixture<FormComponent>;
// async beforeEach
beforeEach(async(() => {
init();
/* TODO make sure these files use mocks instead of real services/components https://github.com/DSpace/dspace-angular/issues/281 */
TestBed.configureTestingModule({
imports: [
BrowserModule,
@@ -126,19 +152,22 @@ describe('FormComponent test suite', () => {
], // declare the test component
providers: [
ChangeDetectorRef,
DynamicFormValidationService,
FormBuilderService,
FormComponent,
{ provide: FormBuilderService, useValue: getMockFormBuilderService() },
{ provide: FormService, useValue: getMockFormService() },
{ provide: GLOBAL_CONFIG, useValue: config }
FormService,
{ provide: GLOBAL_CONFIG, useValue: config },
{
provide: Store, useValue: store
}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
formService = TestBed.get(FormService);
formBuilderService = TestBed.get(FormBuilderService);
}));
describe('', () => {
// // synchronous beforeEach
// synchronous beforeEach
beforeEach(() => {
html = `
<ds-form *ngIf="formModel" #formRef="formComponent"
@@ -149,13 +178,19 @@ describe('FormComponent test suite', () => {
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
testComp = null;
html = undefined;
});
it('should create FormComponent', inject([FormComponent], (app: FormComponent) => {
expect(app).toBeDefined();
}));
});
describe('', () => {
let form;
let valid;
beforeEach(() => {
formFixture = TestBed.createComponent(FormComponent);
@@ -163,247 +198,238 @@ describe('FormComponent test suite', () => {
formComp.formId = 'testForm';
formComp.formModel = TEST_FORM_MODEL;
formComp.displaySubmit = false;
form = new BehaviorSubject(formState);
valid = new BehaviorSubject(false);
spyOn((formComp as any).formService, 'getForm').and.returnValue(form);
spyOn((formComp as any).formService, 'isValid').and.returnValue(valid);
formFixture.detectChanges();
dynamicForm = formFixture.debugElement.query(By.css('ds-dynamic-form'));
formBuilderService.findById.and.returnValue(TEST_FORM_MODEL);
spyOn(store, 'dispatch');
});
afterEach(() => {
formFixture.destroy();
formComp = null;
});
//
// it('should dispatch a FormStatusChangeAction when Form group status changes', () => {
// // spyOn(formComp, 'onChange');
// const control = new FormControl('', Validators.required);
// const event = {
// $event: new FormFieldMetadataValueObject('Test Title'),
// context: null,
// control: control,
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'change'
// } as DynamicFormControlEvent;
// dynamicForm.componentInstance.change.emit(event);
//
// // expect(formComp.onChange).toHaveBeenCalledWith('testForm', formComp.formGroup.valid);
// formComp.onChange(event);
// // const control = new FormControl('', Validators.required);
// formComp.formGroup.addControl('dc_title', control);
// control.setValue('Test Title');
//
// expect(formService.changeForm).toHaveBeenCalledWith('testForm', formComp.formGroup.valid);
// });
//
// it('should display form errors when errors are added to the state', () => {
//
// const error = {formComp.formId, 'dc_title', 0, 'error.validation.required';
// formService.getFormErrors().next([error]);
// formFixture.detectChanges();
//
// expect((formComp as any).formErrors).toEqual([error]);
//
// });
//
// fit('should remove form errors when errors are empty in the state', () => {
// (formComp as any).formErrors = [{
// fieldId: 'dc_title',
// message: 'error.validation.required'
// }];
// const errors = [];
//
// formService.getFormErrors().next([]);
// formFixture.detectChanges();
//
// expect((formComp as any).formErrors).toEqual(errors);
//
// });
//
// it('should dispatch FormChangeAction on form change', inject([FormBuilderService], (service: FormBuilderService) => {
// const event = {
// $event: new FormFieldMetadataValueObject('Test Title'),
// context: null,
// control: formComp.formGroup.get('dc_title'),
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'change'
// } as DynamicFormControlEvent;
//
// spyOn(formComp.change, 'emit');
//
// formComp.onChange(event);
//
// expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testForm', service.getValueFromModel(formComp.formModel)));
// expect(formComp.change.emit).toHaveBeenCalled();
// }));
//
// it('should emit change on form change', inject([FormBuilderService], (service: FormBuilderService) => {
// const event = {
// $event: new FormFieldMetadataValueObject('Test Title'),
// context: null,
// control: formComp.formGroup.get('dc_title'),
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'change'
// } as DynamicFormControlEvent;
//
// spyOn(formComp.change, 'emit');
//
// formComp.onChange(event);
//
// expect(formComp.change.emit).toHaveBeenCalled();
// }));
//
// it('should not emit change Event on form change when emitChange is false', inject([FormBuilderService], (service: FormBuilderService) => {
// const event = {
// $event: new FormFieldMetadataValueObject('Test Title'),
// context: null,
// control: formComp.formGroup.get('dc_title'),
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'change'
// } as DynamicFormControlEvent;
//
// formComp.emitChange = false;
// spyOn(formComp.change, 'emit');
//
// formComp.onChange(event);
//
// expect(formComp.change.emit).not.toHaveBeenCalled();
// }));
//
// it('should emit blur Event on blur', () => {
// const event = {
// $event: new FocusEvent('blur'),
// context: null,
// control: formComp.formGroup.get('dc_title'),
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'blur'
// } as DynamicFormControlEvent;
//
// spyOn(formComp.blur, 'emit');
//
// formComp.onBlur(event);
//
// expect(formComp.blur.emit).toHaveBeenCalled();
// });
//
// it('should emit focus Event on focus', () => {
// const event = {
// $event: new FocusEvent('focus'),
// context: null,
// control: formComp.formGroup.get('dc_title'),
// group: formComp.formGroup,
// model: formComp.formModel[0],
// type: 'focus'
// } as DynamicFormControlEvent;
//
// spyOn(formComp.focus, 'emit');
//
// formComp.onFocus(event);
//
// expect(formComp.focus.emit).toHaveBeenCalled();
// });
//
// it('should return Observable of form status', () => {
//
// const control = formComp.formGroup.get(['dc_title']);
// control.setValue('Test Title');
// formState.testForm.valid = true;
// store.nextState(formState);
// formFixture.detectChanges();
//
// formComp.isValid().subscribe((valid) => {
// expect(valid).toBe(true);
// });
// });
//
// it('should emit submit Event on form submit whether the form is valid', () => {
//
// const control = formComp.formGroup.get(['dc_title']);
// control.setValue('Test Title');
// formState.testForm.valid = true;
// spyOn(formComp.submit, 'emit');
//
// store.nextState(formState);
// formFixture.detectChanges();
//
// formComp.onSubmit();
// expect(formComp.submit.emit).toHaveBeenCalled();
// });
//
// it('should not emit submit Event on form submit whether the form is not valid', () => {
//
// spyOn((formComp as any).formService, 'validateAllFormFields');
//
// store.nextState(formState);
// formFixture.detectChanges();
//
// formComp.onSubmit();
// expect((formComp as any).formService.validateAllFormFields).toHaveBeenCalled();
// });
//
// it('should reset form group', () => {
//
// spyOn(formComp.formGroup, 'reset');
//
// formComp.reset();
//
// expect(formComp.formGroup.reset).toHaveBeenCalled();
// });
// });
//
// describe('', () => {
// beforeEach(() => {
//
// formFixture = TestBed.createComponent(FormComponent);
// formComp = formFixture.componentInstance; // FormComponent test instance
// formComp.formId = 'testFormArray';
// formComp.formModel = TEST_FORM_MODEL_WITH_ARRAY;
// formComp.displaySubmit = false;
// formFixture.detectChanges();
// spyOn(store, 'dispatch');
// });
//
// afterEach(() => {
// formFixture.destroy();
// formComp = null;
// });
//
// it('should return ReadOnly property from array item', inject([FormBuilderService], (service: FormBuilderService) => {
// const readOnly = formComp.isItemReadOnly(formComp.formModel[0] as DynamicFormArrayModel, 0);
//
// expect(readOnly).toBe(false);
// }));
//
// it('should dispatch FormChangeAction when an item has been added to an array', inject([FormBuilderService], (service: FormBuilderService) => {
// formComp.insertItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
//
// expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
// }));
//
// it('should emit addArrayItem Event when an item has been added to an array', inject([FormBuilderService], (service: FormBuilderService) => {
// spyOn(formComp.addArrayItem, 'emit');
//
// formComp.insertItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
//
// expect(formComp.addArrayItem.emit).toHaveBeenCalled();
// }));
//
// it('should dispatch FormChangeAction when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
// formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
//
// expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
// }));
//
// it('should emit removeArrayItem Event when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
// spyOn(formComp.removeArrayItem, 'emit');
//
// formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
//
// expect(formComp.removeArrayItem.emit).toHaveBeenCalled();
// }));
it('should dispatch a FormStatusChangeAction when Form group status changes', () => {
const control = formComp.formGroup.get(['dc_title']);
control.setValue('Test Title');
expect(store.dispatch).toHaveBeenCalledWith(new FormStatusChangeAction('testForm', formComp.formGroup.valid));
});
it('should display form errors when errors are added to the state', () => {
const errors = [{
fieldId: 'dc_title',
fieldIndex: 0,
message: 'error.validation.required'
}];
formState.testForm.errors = errors;
form.next(formState.testForm);
formFixture.detectChanges();
expect((formComp as any).formErrors).toEqual(errors);
});
it('should remove form errors when errors are empty in the state', () => {
(formComp as any).formErrors = [{
fieldId: 'dc_title',
message: 'error.validation.required'
}];
const errors = [];
formState.testForm.errors = errors;
form.next(formState.testForm);
formFixture.detectChanges();
expect((formComp as any).formErrors).toEqual(errors);
});
it('should dispatch FormChangeAction on form change', inject([FormBuilderService], (service: FormBuilderService) => {
const event = {
$event: new FormFieldMetadataValueObject('Test Title'),
context: null,
control: formComp.formGroup.get('dc_title'),
group: formComp.formGroup,
model: formComp.formModel[0],
type: 'change'
} as DynamicFormControlEvent;
spyOn(formComp.change, 'emit');
formComp.onChange(event);
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testForm', service.getValueFromModel(formComp.formModel)));
expect(formComp.change.emit).toHaveBeenCalled();
}));
it('should emit change on form change', inject([FormBuilderService], (service: FormBuilderService) => {
const event = {
$event: new FormFieldMetadataValueObject('Test Title'),
context: null,
control: formComp.formGroup.get('dc_title'),
group: formComp.formGroup,
model: formComp.formModel[0],
type: 'change'
} as DynamicFormControlEvent;
spyOn(formComp.change, 'emit');
formComp.onChange(event);
expect(formComp.change.emit).toHaveBeenCalled();
}));
it('should not emit change Event on form change when emitChange is false', inject([FormBuilderService], (service: FormBuilderService) => {
const event = {
$event: new FormFieldMetadataValueObject('Test Title'),
context: null,
control: formComp.formGroup.get('dc_title'),
group: formComp.formGroup,
model: formComp.formModel[0],
type: 'change'
} as DynamicFormControlEvent;
formComp.emitChange = false;
spyOn(formComp.change, 'emit');
formComp.onChange(event);
expect(formComp.change.emit).not.toHaveBeenCalled();
}));
it('should emit blur Event on blur', () => {
const event = {
$event: new FocusEvent('blur'),
context: null,
control: formComp.formGroup.get('dc_title'),
group: formComp.formGroup,
model: formComp.formModel[0],
type: 'blur'
} as DynamicFormControlEvent;
spyOn(formComp.blur, 'emit');
formComp.onBlur(event);
expect(formComp.blur.emit).toHaveBeenCalled();
});
it('should emit focus Event on focus', () => {
const event = {
$event: new FocusEvent('focus'),
context: null,
control: formComp.formGroup.get('dc_title'),
group: formComp.formGroup,
model: formComp.formModel[0],
type: 'focus'
} as DynamicFormControlEvent;
spyOn(formComp.focus, 'emit');
formComp.onFocus(event);
expect(formComp.focus.emit).toHaveBeenCalled();
});
it('should return Observable of form status', () => {
const control = formComp.formGroup.get(['dc_title']);
control.setValue('Test Title');
valid.next(true);
formFixture.detectChanges();
formComp.isValid().subscribe((v) => {
expect(v).toBe(true);
});
});
it('should emit submit Event on form submit whether the form is valid', () => {
const control = formComp.formGroup.get(['dc_title']);
control.setValue('Test Title');
formState.testForm.valid = true;
spyOn(formComp.submit, 'emit');
form.next(formState.testForm);
formFixture.detectChanges();
formComp.onSubmit();
expect(formComp.submit.emit).toHaveBeenCalled();
});
it('should not emit submit Event on form submit whether the form is not valid', () => {
spyOn((formComp as any).formService, 'validateAllFormFields');
form.next(formState.testForm)
formFixture.detectChanges();
formComp.onSubmit();
expect((formComp as any).formService.validateAllFormFields).toHaveBeenCalled();
});
it('should reset form group', () => {
spyOn(formComp.formGroup, 'reset');
formComp.reset();
expect(formComp.formGroup.reset).toHaveBeenCalled();
});
});
describe('', () => {
beforeEach(() => {
formFixture = TestBed.createComponent(FormComponent);
formComp = formFixture.componentInstance; // FormComponent test instance
formComp.formId = 'testFormArray';
formComp.formModel = TEST_FORM_MODEL_WITH_ARRAY;
formComp.displaySubmit = false;
formFixture.detectChanges();
spyOn(store, 'dispatch');
});
afterEach(() => {
formFixture.destroy();
formComp = null;
});
it('should return ReadOnly property from array item', inject([FormBuilderService], (service: FormBuilderService) => {
const readOnly = formComp.isItemReadOnly(formComp.formModel[0] as DynamicFormArrayModel, 0);
expect(readOnly).toBe(false);
}));
it('should dispatch FormChangeAction when an item has been added to an array', inject([FormBuilderService], (service: FormBuilderService) => {
formComp.insertItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
}));
it('should emit addArrayItem Event when an item has been added to an array', inject([FormBuilderService], (service: FormBuilderService) => {
spyOn(formComp.addArrayItem, 'emit');
formComp.insertItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
expect(formComp.addArrayItem.emit).toHaveBeenCalled();
}));
it('should dispatch FormChangeAction when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
}));
it('should emit removeArrayItem Event when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
spyOn(formComp.removeArrayItem, 'emit');
formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
expect(formComp.removeArrayItem.emit).toHaveBeenCalled();
}));
})
});

View File

@@ -156,7 +156,6 @@ export class FormComponent implements OnDestroy, OnInit {
// .delay(100) // this terrible delay is here to prevent the detection change error
.subscribe((errors: FormError[]) => {
const { formGroup, formModel } = this;
errors
.filter((error: FormError) => findIndex(this.formErrors, {
fieldId: error.fieldId,
@@ -171,25 +170,15 @@ export class FormComponent implements OnDestroy, OnInit {
} else {
field = this.formBuilderService.getFormControlById(fieldId, formGroup, formModel, fieldIndex);
}
console.log('1', error);
if (field) {
console.log('2',error);
const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel);
console.log('4',error);
this.formService.addErrorToField(field, model, error.message);
// this.formService.validateAllFormFields(formGroup);
console.log('5',error);
this.changeDetectorRef.detectChanges();
}
console.log('4',error);
});
console.log(errors);
this.formErrors
.filter((error: FormError) => findIndex(errors, {
@@ -211,7 +200,6 @@ export class FormComponent implements OnDestroy, OnInit {
this.formService.removeErrorFromField(field, model, error.message);
}
});
console.log(this.formErrors);
this.formErrors = errors;
this.changeDetectorRef.detectChanges();
})
@@ -256,7 +244,6 @@ export class FormComponent implements OnDestroy, OnInit {
}
onChange(event: DynamicFormControlEvent): void {
this.formService.changeForm(this.formId, this.formModel);
this.formGroup.markAsPristine();

View File

@@ -67,7 +67,6 @@ export function formReducer(state = initialState, action: FormAction): FormState
}
function addFormErrors(state: FormState, action: FormAddError) {
console.log(state);
const formId = action.payload.formId;
if (hasValue(state[formId])) {
const error: FormError = {

View File

@@ -1,14 +1,8 @@
import { Store, StoreModule } from '@ngrx/store';
import { async, inject, TestBed } from '@angular/core/testing';
import { FormGroup } from '@angular/forms';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import {
DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormService,
DynamicFormValidationService,
DynamicInputModel
} from '@ng-dynamic-forms/core';
import { DynamicFormControlModel, DynamicInputModel } from '@ng-dynamic-forms/core';
import { FormService } from './form.service';
import { FormBuilderService } from './builder/form-builder.service';
@@ -31,7 +25,7 @@ describe('FormService test suite', () => {
let formGroup: FormGroup;
const formModel: DynamicFormControlModel[] = [
new DynamicInputModel({id: 'author', value: 'test'}),
new DynamicInputModel({ id: 'author', value: 'test' }),
new DynamicInputModel({
id: 'title',
validators: {
@@ -41,34 +35,36 @@ describe('FormService test suite', () => {
required: 'Title is required'
}
}),
new DynamicInputModel({id: 'date'}),
new DynamicInputModel({id: 'description'}),
new DynamicFormGroupModel({
id: 'addressLocation',
group: [
new DynamicInputModel({
id: 'zipCode',
label: 'Zip Code',
placeholder: 'ZIP'
}),
new DynamicInputModel({
id: 'state',
label: 'State',
placeholder: 'State'
}),
new DynamicInputModel({
id: 'city',
label: 'City',
placeholder: 'City'
})
]
}),
new DynamicInputModel({ id: 'date' }),
new DynamicInputModel({ id: 'description' }),
// new DynamicFormGroupModel({
//
// id: 'addressLocation',
// group: [
// new DynamicInputModel({
//
// id: 'zipCode',
// label: 'Zip Code',
// placeholder: 'ZIP'
// }),
// new DynamicInputModel({
//
// id: 'state',
// label: 'State',
// placeholder: 'State'
// }),
// new DynamicInputModel({
//
// id: 'city',
// label: 'City',
// placeholder: 'City'
// })
// ]
// }),
];
let controls;
const formData = {
author: ['test'],
title: null,
@@ -91,23 +87,27 @@ describe('FormService test suite', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({formReducer})
],
providers: [
{provide: FormBuilderService, useValue: getMockFormBuilderService()},
StoreModule.forRoot({ formReducer })
]
}).compileComponents();
}));
beforeEach(inject([Store, FormBuilderService], (store: Store<AppState>, formBuilderService: FormBuilderService) => {
beforeEach(inject([Store], (store: Store<AppState>) => {
builderService = getMockFormBuilderService();
store
.subscribe((state) => {
state.forms = formState;
});
builderService = formBuilderService;
formGroup = builderService.createFormGroup(formModel);
service = new FormService(config, formBuilderService, store);
}));
const author: AbstractControl = new FormControl('test');
const title: AbstractControl = new FormControl(undefined, Validators.required);
const date: AbstractControl = new FormControl(undefined);
const description: AbstractControl = new FormControl(undefined);
formGroup = new FormGroup({ author, title, date, description });
controls = { author, title, date, description };
service = new FormService(config, builderService, store);
})
)
;
it('should check whether form state is init', () => {
service.isFormInitialized(formId).subscribe((init) => {
@@ -136,7 +136,6 @@ describe('FormService test suite', () => {
it('should validate all form fields', () => {
service.validateAllFormFields(formGroup);
expect(formGroup.controls.author.touched).toBe(true);
expect(formGroup.controls.author.status).toBe('VALID');
@@ -149,8 +148,8 @@ describe('FormService test suite', () => {
});
it('should add error to field', () => {
let control = builderService.getFormControlById('description', formGroup, formModel);
let model = builderService.findById('description', formModel);
let control = controls.description;
let model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'description');
let errorKeys: string[];
service.addErrorToField(control, model, 'Test error message');
@@ -162,8 +161,8 @@ describe('FormService test suite', () => {
expect(formGroup.controls.description.touched).toBe(true);
control = builderService.getFormControlById('title', formGroup, formModel);
model = builderService.findById('title', formModel);
control = controls.title;
model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'title');
service.addErrorToField(control, model, 'error.required');
errorKeys = Object.keys(control.errors);
@@ -175,8 +174,8 @@ describe('FormService test suite', () => {
});
it('should remove error from field', () => {
let control = builderService.getFormControlById('description', formGroup, formModel);
let model = builderService.findById('description', formModel);
let control = controls.description;
let model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'description');
let errorKeys: string[];
service.addErrorToField(control, model, 'Test error message');
@@ -190,8 +189,8 @@ describe('FormService test suite', () => {
expect(formGroup.controls.description.touched).toBe(false);
control = builderService.getFormControlById('title', formGroup, formModel);
model = builderService.findById('title', formModel);
control = controls.title;
model = formModel.find((mdl: DynamicFormControlModel) => mdl.id === 'title');
service.addErrorToField(control, model, 'error.required');
@@ -205,10 +204,11 @@ describe('FormService test suite', () => {
});
it('should reset form group', () => {
const control = builderService.getFormControlById('author', formGroup, formModel);
const control = controls.author;
service.resetForm(formGroup, formModel, formId);
expect(control.value).toBeNull();
});
});
})
;

View File

@@ -1,11 +1,9 @@
import { filter, takeWhile, map } from 'rxjs/operators';
import { filter, map, takeWhile } from 'rxjs/operators';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import {
AuthenticateAction,
ResetAuthenticationMessagesAction
@@ -135,7 +133,7 @@ export class LogInComponent implements OnDestroy, OnInit {
this.store.pipe(
select(isAuthenticated),
takeWhile(() => this.alive),
filter((authenticated) => authenticated),)
filter((authenticated) => authenticated))
.subscribe(() => {
this.authService.redirectToPreviousUrl();
}

View File

@@ -2,7 +2,7 @@ import { FormBuilderService } from '../form/builder/form-builder.service';
import { FormControl, FormGroup } from '@angular/forms';
export function getMockFormBuilderService(): FormBuilderService {
return jasmine.createSpyObj('FormService', {
return jasmine.createSpyObj('FormBuilderService', {
modelFromConfiguration: [],
createFormGroup: new FormGroup({}),
getValueFromModel: {},

View File

@@ -1,28 +1,12 @@
import { FormService } from '../form/form.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { map } from 'rxjs/operators';
export function getMockFormService(
id$: string = 'random_id',
errors = new BehaviorSubject([])
id$: string = 'random_id'
): FormService {
return jasmine.createSpyObj('FormService', {
getUniqueId: id$,
resetForm: {},
validateAllFormFields: {},
getForm: errors.pipe(
map((err) => {
return { data: {}, valid: true, errors: err }
}
)
),
removeForm: undefined,
removeError: undefined,
changeForm: undefined,
setStatusChanged: undefined,
initForm: undefined,
getFormErrors: errors,
addErrorToField: undefined
validateAllFormFields: {}
});
}

View File

@@ -1,5 +1,5 @@
import { Observable, of as observableOf } from 'rxjs';
import { map } from 'rxjs/operators';
import {of as observableOf, Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
import { RemoteData } from '../../core/data/remote-data';

View File

@@ -8,7 +8,7 @@
<ds-object-grid [config]="config"
[sortConfig]="sortConfig"
[objects]="objects"
[hideGear]="true"
[hideGear]="hideGear"
*ngIf="getViewMode()===viewModeEnum.Grid">
</ds-object-grid>

View File

@@ -1,22 +1,28 @@
<div class="card">
<ds-truncatable [id]="object.id">
<div class="card">
<a [routerLink]="['/items/', object.id]" class="card-img-top">
<ds-grid-thumbnail [thumbnail]="object.getThumbnail()">
</ds-grid-thumbnail>
</a>
<div class="card-body">
<h4 class="card-title">{{object.findMetadata('dc.title')}}</h4>
<ds-truncatable-part [id]="object.id" [minLines]="2">
<p *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" class="item-authors card-text text-muted">
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<span *ngIf="!last">; </span>
</span>
<span *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-date">{{object.findMetadata("dc.date.issued")}}</span>
</p>
</ds-truncatable-part>
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</p>
<ds-truncatable-part [id]="object.id" [minLines]="5">
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") }}</p>
</ds-truncatable-part>
<div class="text-center">
<div class="text-center pt-2">
<a [routerLink]="['/items/', object.id]" class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,7 @@
<div class="d-flex flex-row">
<a [routerLink]="" [queryParams]="{value: object.value}" class="lead">
{{object.value}}
</a>
<span class="pr-2">&nbsp;</span>
<span class="badge badge-pill badge-secondary align-self-center">{{object.count}}</span>
</div>

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables';

View File

@@ -0,0 +1,47 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TruncatePipe } from '../../utils/truncate.pipe';
import { Metadatum } from '../../../core/shared/metadatum.model';
import { BrowseEntryListElementComponent } from './browse-entry-list-element.component';
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
let browseEntryListElementComponent: BrowseEntryListElementComponent;
let fixture: ComponentFixture<BrowseEntryListElementComponent>;
const mockValue: BrowseEntry = Object.assign(new BrowseEntry(), {
type: 'browseEntry',
value: 'De Langhe Kristof'
});
describe('MetadataListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BrowseEntryListElementComponent , TruncatePipe],
providers: [
{ provide: 'objectElementProvider', useValue: {mockValue}}
],
schemas: [ NO_ERRORS_SCHEMA ]
}).overrideComponent(BrowseEntryListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(BrowseEntryListElementComponent);
browseEntryListElementComponent = fixture.componentInstance;
}));
describe('When the metadatum is loaded', () => {
beforeEach(() => {
browseEntryListElementComponent.object = mockValue;
fixture.detectChanges();
});
it('should show the value as a link', () => {
const browseEntryLink = fixture.debugElement.query(By.css('a.lead'));
expect(browseEntryLink.nativeElement.textContent.trim()).toBe(mockValue.value);
});
});
});

View File

@@ -0,0 +1,18 @@
import { Component, Input, Inject } from '@angular/core';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
import { ViewMode } from '../../../core/shared/view-mode.model';
@Component({
selector: 'ds-browse-entry-list-element',
styleUrls: ['./browse-entry-list-element.component.scss'],
templateUrl: './browse-entry-list-element.component.html'
})
/**
* This component is automatically used to create a list view for BrowseEntry objects when used in ObjectCollectionComponent
*/
@renderElementsFor(BrowseEntry, ViewMode.List)
export class BrowseEntryListElementComponent extends AbstractListableElementComponent<BrowseEntry> {}

View File

@@ -1,7 +1,9 @@
<a [routerLink]="['/items/' + object.id]" class="lead">
<ds-truncatable [id]="object.id">
<a [routerLink]="['/items/' + object.id]" class="lead">
{{object.findMetadata("dc.title")}}
</a>
<div>
</a>
<div>
<ds-truncatable-part [id]="object.id" [minLines]="1">
<span class="text-muted">
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
class="item-list-authors">
@@ -12,7 +14,11 @@
(<span *ngIf="hasValue(object.findMetadata('dc.publisher'))" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span
*ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
</span>
</ds-truncatable-part>
<ds-truncatable-part [id]="object.id" [minLines]="3">
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">
{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}
{{object.findMetadata("dc.description.abstract")}}
</div>
</div>
</ds-truncatable-part>
</div>
</ds-truncatable>

View File

@@ -76,6 +76,9 @@ import { NumberPickerComponent } from './number-picker/number-picker.component';
import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component';
import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component';
import { MockAdminGuard } from './mocks/mock-admin-guard.service';
import { BrowseByModule } from '../+browse-by/browse-by.module';
import { BrowseByComponent } from './browse-by/browse-by.component';
import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component';
import { DebounceDirective } from './utils/debounce.directive';
import { ClickOutsideDirective } from './utils/click-outside.directive';
import { EmphasizePipe } from './utils/emphasize.pipe';
@@ -155,6 +158,7 @@ const COMPONENTS = [
ViewModeSwitchComponent,
TruncatableComponent,
TruncatablePartComponent,
BrowseByComponent,
InputSuggestionsComponent
];
@@ -168,6 +172,7 @@ const ENTRY_COMPONENTS = [
CollectionGridElementComponent,
CommunityGridElementComponent,
SearchResultGridElementComponent,
BrowseEntryListElementComponent
];
const PROVIDERS = [

View File

@@ -1,4 +1,4 @@
import {of as observableOf, Observable } from 'rxjs';
import { Observable, of as observableOf } from 'rxjs';
import { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';

View File

@@ -1,5 +1,4 @@
import {of as observableOf, Observable } from 'rxjs';
import { Observable, of as observableOf } from 'rxjs';
import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { EPersonMock } from './eperson-mock';

View File

@@ -1,10 +1,7 @@
import {distinctUntilChanged, debounceTime, takeUntil} from 'rxjs/operators';
import { Directive, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms';
import { Subject } from 'rxjs';
@Directive({

View File

@@ -1,8 +1,5 @@
import { of as observableOf } from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects';

View File

@@ -6,8 +6,6 @@ import { RouterModule } from '@angular/router';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module';

View File

@@ -1216,9 +1216,9 @@ boom@5.x.x:
dependencies:
hoek "4.x.x"
bootstrap@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.1.tgz#3aec85000fa619085da8d2e4983dfd67cf2114cb"
bootstrap@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
boxen@^1.2.1:
version "1.3.0"