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", "angular2-text-mask": "9.0.0",
"angulartics2": "^6.2.0", "angulartics2": "^6.2.0",
"body-parser": "1.18.2", "body-parser": "1.18.2",
"bootstrap": "4.1.1", "bootstrap": "4.1.3",
"cerialize": "0.1.18", "cerialize": "0.1.18",
"compression": "1.7.1", "compression": "1.7.1",
"cookie-parser": "1.4.3", "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": { "admin": {
"registries": { "registries": {
"metadata": { "metadata": {
@@ -202,18 +205,19 @@
}, },
"loading": { "loading": {
"default": "Loading...", "default": "Loading...",
"top-level-communities": "Loading top level communities...", "top-level-communities": "Loading top-level communities...",
"community": "Loading community...", "community": "Loading community...",
"collection": "Loading collection...", "collection": "Loading collection...",
"sub-collections": "Loading sub-collections...", "sub-collections": "Loading sub-collections...",
"recent-submissions": "Loading recent submissions...", "recent-submissions": "Loading recent submissions...",
"item": "Loading item...", "item": "Loading item...",
"objects": "Loading...", "objects": "Loading...",
"search-results": "Loading search results..." "search-results": "Loading search results...",
"browse-by": "Loading items..."
}, },
"error": { "error": {
"default": "Error", "default": "Error",
"top-level-communities": "Error fetching top level communities", "top-level-communities": "Error fetching top-level communities",
"community": "Error fetching community", "community": "Error fetching community",
"collection": "Error fetching collection", "collection": "Error fetching collection",
"sub-collections": "Error fetching sub-collections", "sub-collections": "Error fetching sub-collections",
@@ -221,6 +225,7 @@
"item": "Error fetching item", "item": "Error fetching item",
"objects": "Error fetching objects", "objects": "Error fetching objects",
"search-results": "Error fetching search results", "search-results": "Error fetching search results",
"browse-by": "Error fetching items",
"validation": { "validation": {
"pattern": "This input is restricted by the current pattern: {{ pattern }}.", "pattern": "This input is restricted by the current pattern: {{ pattern }}.",
"license": { "license": {
@@ -241,7 +246,7 @@
"group-collapse": "Collapse", "group-collapse": "Collapse",
"group-expand": "Expand", "group-expand": "Expand",
"group-collapse-help": "Click here to collapse", "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": { "login": {
"title": "Login", "title": "Login",
@@ -266,7 +271,7 @@
"expired": "Your session has expired. Please log in again." "expired": "Your session has expired. Please log in again."
}, },
"errors": { "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 { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowService } from '../../../shared/host-window.service';
describe('MetadataRegistryComponent', () => { describe('MetadataRegistryComponent', () => {
let comp: MetadataRegistryComponent; let comp: MetadataRegistryComponent;
let fixture: ComponentFixture<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; const mockName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(2) td:nth-child(3)')).nativeElement;
expect(mockName.textContent).toBe('mock'); 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) => { Object.keys(filterParams).forEach((key) => {
if (key.endsWith('.min') || key.endsWith('.max')) { if (key.endsWith('.min') || key.endsWith('.max')) {
const realKey = key.slice(0, -4); 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 min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*'; const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']'])); 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: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' }, { path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' }, { 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: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { 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 { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { HALEndpointService } from '../shared/hal-endpoint.service'; 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 { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface'; 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 { AuthStatus } from './models/auth-status.model';
import { AuthResponseParsingService } from './auth-response-parsing.service'; import { AuthResponseParsingService } from './auth-response-parsing.service';
import { AuthGetRequest, AuthPostRequest } from '../data/request.models'; 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', () => { describe('AuthResponseParsingService', () => {
let service: AuthResponseParsingService; let service: AuthResponseParsingService;
const EnvConfig = {cache: {msToLive: 1000}} as GlobalConfig; const EnvConfig = { cache: { msToLive: 1000 } } as GlobalConfig;
const store = {} as Store<CoreState>; const store = new MockStore<ObjectCacheState>({});
const objectCacheService = new ObjectCacheService(store); const objectCacheService = new ObjectCacheService(store as any);
beforeEach(() => { beforeEach(() => {
service = new AuthResponseParsingService(EnvConfig, objectCacheService); service = new AuthResponseParsingService(EnvConfig, objectCacheService);

View File

@@ -4,8 +4,7 @@ import { provideMockActions } from '@ngrx/effects/testing';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { cold, hot } from 'jasmine-marbles'; import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs'; import { Observable, of as observableOf, throwError as observableThrow } from 'rxjs';
import { of as observableOf } from 'rxjs';
import { AuthEffects } from './auth.effects'; import { AuthEffects } from './auth.effects';
import { import {
@@ -30,16 +29,21 @@ import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('AuthEffects', () => { describe('AuthEffects', () => {
let authEffects: AuthEffects; let authEffects: AuthEffects;
let actions: Observable<any>; let actions: Observable<any>;
const authServiceStub = new AuthServiceStub(); let authServiceStub;
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', { const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
dispatch: {}, dispatch: {},
/* tslint:enable:no-empty */ /* tslint:enable:no-empty */
select: observableOf(true) select: observableOf(true)
}); });
const token = authServiceStub.getToken(); let token;
function init() {
authServiceStub = new AuthServiceStub();
token = authServiceStub.getToken();
}
beforeEach(() => { beforeEach(() => {
init();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
AuthEffects, AuthEffects,
@@ -71,7 +75,7 @@ describe('AuthEffects', () => {
describe('when credentials are wrong', () => { describe('when credentials are wrong', () => {
it('should return a AUTHENTICATE_ERROR action in response to a AUTHENTICATE action', () => { 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-', { actions = hot('--a-', {
a: { a: {
@@ -112,7 +116,7 @@ describe('AuthEffects', () => {
describe('when token is not valid', () => { describe('when token is not valid', () => {
it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => { 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}}); actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
@@ -138,7 +142,7 @@ describe('AuthEffects', () => {
describe('when check token failed', () => { describe('when check token failed', () => {
it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => { 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}}); actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
@@ -164,7 +168,7 @@ describe('AuthEffects', () => {
describe('when refresh token failed', () => { describe('when refresh token failed', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => { 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}}); actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}});
@@ -190,7 +194,7 @@ describe('AuthEffects', () => {
describe('when refresh token failed', () => { describe('when refresh token failed', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => { 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}}); 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 { catchError, filter, map } from 'rxjs/operators';
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
@@ -11,10 +11,6 @@ import {
HttpResponse, HttpResponse,
HttpResponseBase HttpResponseBase
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs';
import { find } from 'lodash'; import { find } from 'lodash';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';

View File

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

View File

@@ -62,7 +62,8 @@ export class AuthService {
protected router: Router, protected router: Router,
protected storage: CookieService, protected storage: CookieService,
protected store: Store<AppState>, protected store: Store<AppState>,
protected rdbService: RemoteDataBuildService) { protected rdbService: RemoteDataBuildService
) {
this.store.pipe( this.store.pipe(
select(isAuthenticated), select(isAuthenticated),
startWith(false) startWith(false)
@@ -145,6 +146,7 @@ export class AuthService {
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe( return this.authRequestService.getRequest('status', options).pipe(
switchMap((status: AuthStatus) => { switchMap((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole... // 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 // Review when https://jira.duraspace.org/browse/DS-4006 is fixed
@@ -154,7 +156,7 @@ export class AuthService {
} else { } else {
throw(new Error('Not authenticated')); 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 { AuthTokenInfo } from './auth-token-info.model';
import { EPerson } from '../../eperson/models/eperson.model'; import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs';
export class AuthStatus { export class AuthStatus {

View File

@@ -1,3 +1,4 @@
import { first, map, switchMap } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -36,6 +37,7 @@ export class ServerAuthService extends AuthService {
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe( return this.authRequestService.getRequest('status', options).pipe(
switchMap((status: AuthStatus) => { switchMap((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole... // 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 { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.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 { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model'; import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseService } from './browse.service'; import { BrowseService } from './browse.service';
@@ -143,7 +143,9 @@ describe('BrowseService', () => {
}); });
describe('getBrowseEntriesFor', () => { describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
const mockAuthorName = 'Donald Smith';
beforeEach(() => { beforeEach(() => {
responseCache = initMockResponseCacheService(true); responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService(); requestService = getMockRequestService();
@@ -156,7 +158,7 @@ describe('BrowseService', () => {
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough(); 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', () => { it('should configure a new BrowseEntriesRequest', () => {
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries); 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', () => { it('should throw an Error', () => {
const definitionID = 'invalidID'; const definitionID = 'invalidID';
@@ -184,6 +205,16 @@ describe('BrowseService', () => {
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected); 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', () => { describe('getBrowseURLFor', () => {

View File

@@ -16,19 +16,27 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { RemoteData } from '../data/remote-data'; 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 { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model'; import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntry } from '../shared/browse-entry.model'; import { BrowseEntry } from '../shared/browse-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { import {
configureRequest, configureRequest,
filterSuccessfulResponses, filterSuccessfulResponses, getBrowseDefinitionLinks,
getRemoteDataPayload, getRemoteDataPayload,
getRequestFromSelflink, getRequestFromSelflink,
getResponseFromSelflink getResponseFromSelflink
} from '../shared/operators'; } from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner'; import { URLCombiner } from '../url-combiner/url-combiner';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
@Injectable() @Injectable()
export class BrowseService { export class BrowseService {
@@ -71,6 +79,8 @@ export class BrowseService {
map((entry: ResponseCacheEntry) => entry.response), map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload), map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload),
ensureArrayHasValue(), ensureArrayHasValue(),
map((definitions: BrowseDefinition[]) => definitions
.map((definition: BrowseDefinition) => Object.assign(new BrowseDefinition(), definition))),
distinctUntilChanged() distinctUntilChanged()
); );
@@ -82,17 +92,7 @@ export class BrowseService {
sort?: SortOptions; sort?: SortOptions;
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> { } = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
const request$ = this.getBrowseDefinitions().pipe( const request$ = this.getBrowseDefinitions().pipe(
getRemoteDataPayload(), getBrowseDefinitionLinks(definitionID),
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}'`);
}
}),
hasValueOperator(), hasValueOperator(),
map((_links: any) => _links.entries), map((_links: any) => _links.entries),
hasValueOperator(), hasValueOperator(),
@@ -124,6 +124,66 @@ export class BrowseService {
filterSuccessfulResponses(), filterSuccessfulResponses(),
map((entry: ResponseCacheEntry) => entry.response), map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseEntry[]>) => new PaginatedList(response.pageInfo, response.payload)), 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() distinctUntilChanged()
); );

View File

@@ -140,7 +140,7 @@ export class ErrorResponse extends RestResponse {
constructor(error: RequestError) { constructor(error: RequestError) {
super(false, error.statusText); super(false, error.statusText);
console.error(error); // console.error(error);
this.errorMessage = error.message; 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 { RegistryBitstreamformatsResponseParsingService } from './data/registry-bitstreamformats-response-parsing.service';
import { NotificationsService } from '../shared/notifications/notifications.service'; import { NotificationsService } from '../shared/notifications/notifications.service';
import { UploaderService } from '../shared/uploader/uploader.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'; import { DSpaceObjectDataService } from './data/dspace-object-data.service';
const IMPORTS = [ const IMPORTS = [
@@ -115,6 +116,7 @@ const PROVIDERS = [
ServerResponseService, ServerResponseService,
BrowseResponseParsingService, BrowseResponseParsingService,
BrowseEntriesResponseParsingService, BrowseEntriesResponseParsingService,
BrowseItemsResponseParsingService,
BrowseService, BrowseService,
ConfigResponseParsingService, ConfigResponseParsingService,
RouteService, 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(() => { beforeEach(() => {
service = new BrowseResponseParsingService(); service = new BrowseResponseParsingService();
}); });
let validRequest;
let validResponse;
let invalidResponse1;
let invalidResponse2;
let invalidResponse3;
let definitions;
describe('parse', () => { 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: { payload: {
_embedded: { _embedded: {
browses: [{ browses: [{
@@ -51,7 +58,7 @@ describe('BrowseResponseParsingService', () => {
}, statusCode: '200' }, statusCode: '200'
} as DSpaceRESTV2Response; } as DSpaceRESTV2Response;
const invalidResponse1 = { invalidResponse1 = {
payload: { payload: {
_embedded: { _embedded: {
browse: { browse: {
@@ -74,21 +81,21 @@ describe('BrowseResponseParsingService', () => {
}, statusCode: '200' }, statusCode: '200'
} as DSpaceRESTV2Response; } as DSpaceRESTV2Response;
const invalidResponse2 = { invalidResponse2 = {
payload: { payload: {
_links: { self: { href: 'https://rest.api/discover/browses' } }, _links: { self: { href: 'https://rest.api/discover/browses' } },
page: { size: 20, totalElements: 2, totalPages: 1, number: 0 } page: { size: 20, totalElements: 2, totalPages: 1, number: 0 }
}, statusCode: '200' }, statusCode: '200'
} as DSpaceRESTV2Response ; } as DSpaceRESTV2Response;
const invalidResponse3 = { invalidResponse3 = {
payload: { payload: {
_links: { self: { href: 'https://rest.api/discover/browses' } }, _links: { self: { href: 'https://rest.api/discover/browses' } },
page: { size: 20, totalElements: 2, totalPages: 1, number: 0 } page: { size: 20, totalElements: 2, totalPages: 1, number: 0 }
}, statusCode: '500' }, statusCode: '500'
} as DSpaceRESTV2Response; } as DSpaceRESTV2Response;
const definitions = [ definitions = [
Object.assign(new BrowseDefinition(), { Object.assign(new BrowseDefinition(), {
metadataBrowse: false, metadataBrowse: false,
sortOptions: [ sortOptions: [
@@ -110,7 +117,10 @@ describe('BrowseResponseParsingService', () => {
metadataKeys: [ metadataKeys: [
'dc.date.issued' 'dc.date.issued'
], ],
_links: { } _links: {
self: 'https://rest.api/discover/browses/dateissued',
items: 'https://rest.api/discover/browses/dateissued/items'
}
}), }),
Object.assign(new BrowseDefinition(), { Object.assign(new BrowseDefinition(), {
metadataBrowse: true, metadataBrowse: true,
@@ -134,10 +144,14 @@ describe('BrowseResponseParsingService', () => {
'dc.contributor.*', 'dc.contributor.*',
'dc.creator' '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', () => { it('should return a GenericSuccessResponse if data contains a valid browse endpoint response', () => {
const response = service.parse(validRequest, validResponse); const response = service.parse(validRequest, validResponse);
expect(response.constructor).toBe(GenericSuccessResponse); expect(response.constructor).toBe(GenericSuccessResponse);

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import { distinctUntilChanged, filter, take, first, map } from 'rxjs/operators';
import {of as observableOf, Observable } from 'rxjs'; import { of as observableOf, Observable } from 'rxjs';
import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators'; import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; 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 halService: HALEndpointService;
protected abstract objectCache: ObjectCacheService; 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>; let result: Observable<string>;
const args = []; const args = [];
if (hasValue(options.scopeID)) { result = this.getBrowseEndpoint(options).pipe(distinctUntilChanged());
result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged());
} else {
result = observableOf(endpoint);
}
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { 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 */ /* 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>>> { findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)), const hrefObs = this.getFindAllHref(options);
mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
hrefObs.pipe( hrefObs.pipe(
filter((href: string) => hasValue(href)), filter((href: string) => hasValue(href)),

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators'; import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { isNotEmpty } from '../../shared/empty.util';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service'; import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model'; 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 { DataService } from './data.service';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
@Injectable() @Injectable()
@@ -34,15 +33,21 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
super(); super();
} }
public getScopedEndpoint(scopeID: string): Observable<string> { /**
if (isEmpty(scopeID)) { * Get the endpoint for browsing items
return this.halService.getEndpoint(this.linkPath); * (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
} else { * @param {FindAllOptions} options
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe( * @returns {Observable<string>}
filter((href: string) => isNotEmpty(href)), */
map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()), public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
distinctUntilChanged(),); 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 { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service'; import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
import { RestRequestMethod } from './rest-request-method'; import { RestRequestMethod } from './rest-request-method';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
/* tslint:disable:max-classes-per-file */ /* 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 { export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) { constructor(uuid: string, href: string) {
super(uuid, href); 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, filter, first, map, take } from 'rxjs/operators';
import {distinctUntilKeyChanged, map, filter, first, take} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
ActivatedRoute,
Event,
NavigationEnd,
Params,
Router
} from '@angular/router';
import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject , Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { Bitstream } from '../shared/bitstream.model'; import { Bitstream } from '../shared/bitstream.model';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
import { Metadatum } from '../shared/metadatum.model';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { BitstreamFormat } from '../shared/bitstream-format.model'; import { BitstreamFormat } from '../shared/bitstream-format.model';

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { filter, first, flatMap, map, tap } from 'rxjs/operators'; 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 { DSOSuccessResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
@@ -8,6 +8,7 @@ import { RemoteData } from '../data/remote-data';
import { RestRequest } from '../data/request.models'; import { RestRequest } from '../data/request.models';
import { RequestEntry } from '../data/request.reducer'; import { RequestEntry } from '../data/request.reducer';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { SearchResult } from '../../+search-page/search-result.model'; import { SearchResult } from '../../+search-page/search-result.model';
@@ -62,3 +63,24 @@ export const toDSpaceObjectListRD = () =>
return Object.assign(rd, {payload: payload}); 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 deNavMenuItem: DebugElement;
let fixture: ComponentFixture<AuthNavMenuComponent>; let fixture: ComponentFixture<AuthNavMenuComponent>;
const notAuthState: AuthState = { let notAuthState: AuthState;
let authState: AuthState;
let routerState = {
url: '/home'
};
function init() {
notAuthState = {
authenticated: false, authenticated: false,
loaded: false, loaded: false,
loading: false loading: false
}; };
const authState: AuthState = { authState = {
authenticated: true, authenticated: true,
loaded: true, loaded: true,
loading: false, loading: false,
authToken: new AuthTokenInfo('test_token'), authToken: new AuthTokenInfo('test_token'),
user: EPersonMock user: EPersonMock
}; };
let routerState = { }
url: '/home'
};
describe('when is a not mobile view', () => { describe('when is a not mobile view', () => {
beforeEach(async(() => { beforeEach(async(() => {
const window = new HostWindowServiceStub(800); const window = new HostWindowServiceStub(800);
@@ -53,8 +58,13 @@ describe('AuthNavMenuComponent', () => {
AuthNavMenuComponent AuthNavMenuComponent
], ],
providers: [ providers: [
{provide: HostWindowService, useValue: window}, { provide: HostWindowService, useValue: window },
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}} {
provide: AuthService, useValue: {
setRedirectUrl: () => { /*empty*/
}
}
}
], ],
schemas: [ schemas: [
CUSTOM_ELEMENTS_SCHEMA CUSTOM_ELEMENTS_SCHEMA
@@ -64,11 +74,14 @@ describe('AuthNavMenuComponent', () => {
})); }));
beforeEach(() => {
init();
});
describe('when route is /login and user is not authenticated', () => { describe('when route is /login and user is not authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => {
routerState = { routerState = {
url: '/login' url: '/login'
}; };
beforeEach(inject([Store], (store: Store<AppState>) => {
store store
.subscribe((state) => { .subscribe((state) => {
(state as any).router = Object.create({}); (state as any).router = Object.create({});
@@ -91,7 +104,9 @@ describe('AuthNavMenuComponent', () => {
const navMenuItemSelector = 'li'; const navMenuItemSelector = 'li';
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
});
it('should not render', () => { it('should not render', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
expect(deNavMenu.nativeElement).toBeDefined(); expect(deNavMenu.nativeElement).toBeDefined();
@@ -101,10 +116,10 @@ describe('AuthNavMenuComponent', () => {
}); });
describe('when route is /logout and user is authenticated', () => { describe('when route is /logout and user is authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => {
routerState = { routerState = {
url: '/logout' url: '/logout'
}; };
beforeEach(inject([Store], (store: Store<AppState>) => {
store store
.subscribe((state) => { .subscribe((state) => {
(state as any).router = Object.create({}); (state as any).router = Object.create({});
@@ -128,6 +143,10 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
});
it('should not render', () => { it('should not render', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
expect(deNavMenu.nativeElement).toBeDefined(); expect(deNavMenu.nativeElement).toBeDefined();
@@ -166,6 +185,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render login dropdown menu', () => { it('should render login dropdown menu', () => {
const loginDropdownMenu = deNavMenuItem.query(By.css('div[id=loginDropdownMenu]')); const loginDropdownMenu = deNavMenuItem.query(By.css('div[id=loginDropdownMenu]'));
expect(loginDropdownMenu.nativeElement).toBeDefined(); expect(loginDropdownMenu.nativeElement).toBeDefined();
@@ -200,6 +224,10 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render logout dropdown menu', () => { it('should render logout dropdown menu', () => {
const logoutDropdownMenu = deNavMenuItem.query(By.css('div[id=logoutDropdownMenu]')); const logoutDropdownMenu = deNavMenuItem.query(By.css('div[id=logoutDropdownMenu]'));
expect(logoutDropdownMenu.nativeElement).toBeDefined(); expect(logoutDropdownMenu.nativeElement).toBeDefined();
@@ -223,8 +251,13 @@ describe('AuthNavMenuComponent', () => {
AuthNavMenuComponent AuthNavMenuComponent
], ],
providers: [ providers: [
{provide: HostWindowService, useValue: window}, { provide: HostWindowService, useValue: window },
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}} {
provide: AuthService, useValue: {
setRedirectUrl: () => { /*empty*/
}
}
}
], ],
schemas: [ schemas: [
CUSTOM_ELEMENTS_SCHEMA CUSTOM_ELEMENTS_SCHEMA
@@ -234,6 +267,9 @@ describe('AuthNavMenuComponent', () => {
})); }));
beforeEach(() => {
init();
});
describe('when user is not authenticated', () => { describe('when user is not authenticated', () => {
beforeEach(inject([Store], (store: Store<AppState>) => { beforeEach(inject([Store], (store: Store<AppState>) => {
@@ -260,6 +296,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render login link', () => { it('should render login link', () => {
const loginDropdownMenu = deNavMenuItem.query(By.css('a[id=loginLink]')); const loginDropdownMenu = deNavMenuItem.query(By.css('a[id=loginLink]'));
expect(loginDropdownMenu.nativeElement).toBeDefined(); expect(loginDropdownMenu.nativeElement).toBeDefined();
@@ -291,6 +332,11 @@ describe('AuthNavMenuComponent', () => {
deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector)); deNavMenuItem = deNavMenu.query(By.css(navMenuItemSelector));
})); }));
afterEach(() => {
fixture.destroy();
component = null;
});
it('should render logout link', inject([Store], (store: Store<AppState>) => { it('should render logout link', inject([Store], (store: Store<AppState>) => {
const logoutDropdownMenu = deNavMenuItem.query(By.css('a[id=logoutLink]')); const logoutDropdownMenu = deNavMenuItem.query(By.css('a[id=logoutLink]'));
expect(logoutDropdownMenu.nativeElement).toBeDefined(); 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 { map, filter } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core'; 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(); fixture.detectChanges();
console.log(fixture.componentInstance.componentViewContainerRef);
testElement = debugElement.query(By.css(`input[id='${testModel.id}']`)); 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() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicDsDatePickerModel; @Input() model: DynamicDsDatePickerModel;
// @Input() showErrorMessages = false;
// @Input() // @Input()
// minDate; // minDate;
// @Input() // @Input()

View File

@@ -6,16 +6,16 @@ import {
import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './tag/dynamic-tag.model'; import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './tag/dynamic-tag.model';
export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig { export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig {
notRepeteable: boolean; notRepeatable: boolean;
} }
export class DynamicRowArrayModel extends DynamicFormArrayModel { export class DynamicRowArrayModel extends DynamicFormArrayModel {
@serializable() notRepeteable = false; @serializable() notRepeatable = false;
isRowArray = true; isRowArray = true;
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) { constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout); 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 { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { DsDynamicGroupComponent } from './dynamic-group.components'; 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 { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { DsDynamicInputModel } from '../ds-dynamic-input.model'; import { DsDynamicInputModel } from '../ds-dynamic-input.model';
import { createTestComponent } from '../../../../../testing/utils'; 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 { 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, disabled: false,
errorMessages: {required: 'You must specify at least one author.'}, errorMessages: { required: 'You must specify at least one author.' },
formConfiguration: [{ formConfiguration: [{
fields: [{ fields: [{
hints: 'Enter the name of the author.', hints: 'Enter the name of the author.',
input: {type: 'onebox'}, input: { type: 'onebox' },
label: 'Author', label: 'Author',
languageCodes: [], languageCodes: [],
mandatory: 'true', mandatory: 'true',
@@ -49,7 +55,7 @@ export const FORM_GROUP_TEST_MODEL_CONFIG = {
} as FormRowModel, { } as FormRowModel, {
fields: [{ fields: [{
hints: 'Enter the affiliation of the author.', hints: 'Enter the affiliation of the author.',
input: {type: 'onebox'}, input: { type: 'onebox' },
label: 'Affiliation', label: 'Affiliation',
languageCodes: [], languageCodes: [],
mandatory: 'false', mandatory: 'false',
@@ -71,15 +77,14 @@ export const FORM_GROUP_TEST_MODEL_CONFIG = {
required: true, required: true,
scopeUUID: '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f', scopeUUID: '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f',
submissionScope: undefined, submissionScope: undefined,
validators: {required: null} validators: { required: null }
} as DynamicGroupModelConfig; } as DynamicGroupModelConfig;
export const FORM_GROUP_TEST_GROUP = new FormGroup({ FORM_GROUP_TEST_GROUP = new FormGroup({
dc_contributor_author: new FormControl(), dc_contributor_author: new FormControl(),
}); });
describe('DsDynamicGroupComponent test suite', () => { config = {
const config = {
form: { form: {
validatorMap: { validatorMap: {
required: 'required', required: 'required',
@@ -87,11 +92,15 @@ describe('DsDynamicGroupComponent test suite', () => {
} }
} }
} as any; } as any;
}
describe('DsDynamicGroupComponent test suite', () => {
let testComp: TestComponent; let testComp: TestComponent;
let groupComp: DsDynamicGroupComponent; let groupComp: DsDynamicGroupComponent;
let testFixture: ComponentFixture<TestComponent>; let testFixture: ComponentFixture<TestComponent>;
let groupFixture: ComponentFixture<DsDynamicGroupComponent>; let groupFixture: ComponentFixture<DsDynamicGroupComponent>;
// let modelValue: any; let modelValue: any;
let html; let html;
let control1: FormControl; let control1: FormControl;
let model1: DsDynamicInputModel; let model1: DsDynamicInputModel;
@@ -100,7 +109,9 @@ describe('DsDynamicGroupComponent test suite', () => {
// async beforeEach // async beforeEach
beforeEach(async(() => { 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({ TestBed.configureTestingModule({
imports: [ imports: [
BrowserAnimationsModule, BrowserAnimationsModule,
@@ -110,18 +121,20 @@ describe('DsDynamicGroupComponent test suite', () => {
TranslateModule.forRoot() TranslateModule.forRoot()
], ],
declarations: [ declarations: [
MockComponent(FormComponent), FormComponent,
DsDynamicGroupComponent, DsDynamicGroupComponent,
TestComponent, TestComponent,
], // declare the test component ], // declare the test component
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicGroupComponent, DsDynamicGroupComponent,
{provide: FormBuilderService, useValue: getMockFormBuilderService()}, DynamicFormValidationService,
{provide: FormService, useValue: getMockFormService()}, DynamicFormLayoutService,
{provide: GLOBAL_CONFIG, useValue: config}, FormBuilderService,
{provide: DynamicFormLayoutService, useValue: {}}, FormComponent,
{provide: DynamicFormValidationService, useValue: {}} FormService,
{ provide: GLOBAL_CONFIG, useValue: config },
{provide: Store, useValue: store},
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -143,6 +156,11 @@ describe('DsDynamicGroupComponent test suite', () => {
testComp = testFixture.componentInstance; testComp = testFixture.componentInstance;
}); });
afterEach(() => {
testFixture.destroy();
testComp = null;
});
it('should create DsDynamicGroupComponent', inject([DsDynamicGroupComponent], (app: DsDynamicGroupComponent) => { it('should create DsDynamicGroupComponent', inject([DsDynamicGroupComponent], (app: DsDynamicGroupComponent) => {
expect(app).toBeDefined(); expect(app).toBeDefined();
@@ -158,7 +176,6 @@ describe('DsDynamicGroupComponent test suite', () => {
groupComp.group = FORM_GROUP_TEST_GROUP; groupComp.group = FORM_GROUP_TEST_GROUP;
groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG); groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
groupFixture.detectChanges(); groupFixture.detectChanges();
control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl; 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; 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; 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; groupComp = null;
}); });
// it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => { it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
// const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel; const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
// const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly); const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
// const chips = new Chips([], 'value', 'dc.contributor.author'); const chips = new Chips([], 'value', 'dc.contributor.author');
// groupComp.formCollapsed.subscribe((value) => {
// expect(groupComp.formCollapsed).toEqual(observableOf(false)); expect(value).toEqual(false);
// expect(groupComp.formModel.length).toEqual(formModel.length); });
// expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); 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'); it('should save a new chips item', () => {
// (model1 as any).value = new FormFieldMetadataValueObject('test author'); control1.setValue('test author');
// control2.setValue('test affiliation'); (model1 as any).value = new FormFieldMetadataValueObject('test author');
// (model2 as any).value = new FormFieldMetadataValueObject('test affiliation'); control2.setValue('test affiliation');
// modelValue = [{ (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author'), modelValue = [{
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
// }]; 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// groupFixture.detectChanges(); }];
// groupFixture.detectChanges();
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[0]; const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// btnEl.click(); const btnEl = buttons[0];
// btnEl.click();
// expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
// expect(groupComp.formCollapsed).toEqual(observableOf(true)); 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'); it('should clear form inputs', () => {
// (model2 as any).value = new FormFieldMetadataValueObject('test affiliation'); control1.setValue('test author');
// (model1 as any).value = new FormFieldMetadataValueObject('test author');
// groupFixture.detectChanges(); control2.setValue('test affiliation');
// (model2 as any).value = new FormFieldMetadataValueObject('test affiliation');
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// const btnEl = buttons[2]; groupFixture.detectChanges();
// btnEl.click();
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// expect(control1.value).toBeNull(); const btnEl = buttons[2];
// expect(control2.value).toBeNull(); btnEl.click();
// expect(groupComp.formCollapsed).toEqual(observableOf(false));
// }); expect(control1.value).toBeNull();
// }); expect(control2.value).toBeNull();
// groupComp.formCollapsed.subscribe((value) => {
// describe('when init model value is not empty', () => { expect(value).toEqual(false);
// beforeEach(() => { });
// });
// groupFixture = TestBed.createComponent(DsDynamicGroupComponent); });
// groupComp = groupFixture.componentInstance; // FormComponent test instance
// groupComp.formId = 'testForm'; describe('when init model value is not empty', () => {
// groupComp.group = FORM_GROUP_TEST_GROUP; beforeEach(() => {
// groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
// modelValue = [{ groupFixture = TestBed.createComponent(DsDynamicGroupComponent);
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author'), groupComp = groupFixture.componentInstance; // FormComponent test instance
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') groupComp.formId = 'testForm';
// }]; groupComp.group = FORM_GROUP_TEST_GROUP;
// groupComp.model.value = modelValue; groupComp.model = new DynamicGroupModel(FORM_GROUP_TEST_MODEL_CONFIG);
// groupComp.showErrorMessages = false; modelValue = [{
// groupFixture.detectChanges(); 'dc.contributor.author': new FormFieldMetadataValueObject('test author'),
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// }); }];
// groupComp.model.value = modelValue;
// afterEach(() => { groupFixture.detectChanges();
// groupFixture.destroy();
// groupComp = null; });
// });
// afterEach(() => {
// it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => { groupFixture.destroy();
// const formConfig = {rows: groupComp.model.formConfiguration} as SubmissionFormsModel; groupComp = null;
// const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly); });
// const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
// it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
// expect(groupComp.formCollapsed).toEqual(observableOf(true)); const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
// expect(groupComp.formModel.length).toEqual(formModel.length); const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
// expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
// })); groupComp.formCollapsed.subscribe((value) => {
// expect(value).toEqual(true);
// it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => { })
// groupComp.onChipSelected(0); expect(groupComp.formModel.length).toEqual(formModel.length);
// groupFixture.detectChanges(); expect(groupComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
// }));
// 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; it('should modify existing chips item', inject([FormBuilderService], (service: FormBuilderService) => {
// groupComp.onChipSelected(0);
// control1.setValue('test author modify'); groupFixture.detectChanges();
// (model1 as any).value = new FormFieldMetadataValueObject('test author modify');
// control1 = service.getFormControlById('dc_contributor_author', (groupComp as any).formRef.formGroup, groupComp.formModel) as FormControl;
// modelValue = [{ model1 = service.findById('dc_contributor_author', groupComp.formModel) as DsDynamicInputModel;
// 'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'),
// 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation') control1.setValue('test author modify');
// }]; (model1 as any).value = new FormFieldMetadataValueObject('test author modify');
// groupFixture.detectChanges();
// modelValue = [{
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); 'dc.contributor.author': new FormFieldMetadataValueObject('test author modify'),
// const btnEl = buttons[0]; 'local.contributor.affiliation': new FormFieldMetadataValueObject('test affiliation')
// btnEl.click(); }];
// groupFixture.detectChanges();
// groupFixture.detectChanges();
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button');
// expect(groupComp.chips.getChipsItems()).toEqual(modelValue); const btnEl = buttons[0];
// expect(groupComp.formCollapsed).toEqual(observableOf(true)); btnEl.click();
// }));
// groupFixture.detectChanges();
// it('should delete existing chips item', () => {
// groupComp.onChipSelected(0); expect(groupComp.chips.getChipsItems()).toEqual(modelValue);
// groupFixture.detectChanges(); groupComp.formCollapsed.subscribe((value) => {
// expect(value).toEqual(true);
// const buttons = groupFixture.debugElement.nativeElement.querySelectorAll('button'); })
// const btnEl = buttons[1]; }));
// btnEl.click();
// it('should delete existing chips item', () => {
// expect(groupComp.chips.getChipsItems()).toEqual([]); groupComp.onChipSelected(0);
// expect(groupComp.formCollapsed).toEqual(observableOf(false)); 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() formId: string;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicGroupModel; @Input() model: DynamicGroupModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: 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() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicListCheckboxGroupModel | DynamicListRadioGroupModel; @Input() model: DynamicListCheckboxGroupModel | DynamicListRadioGroupModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: 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 { DynamicLookupModel } from './dynamic-lookup.model';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { TranslateModule } from '@ngx-translate/core'; 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 { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model'; import { AuthorityValueModel } from '../../../../../../core/integration/models/authority-value.model';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import { createTestComponent } from '../../../../../testing/utils'; import { createTestComponent } from '../../../../../testing/utils';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
export const LOOKUP_TEST_MODEL_CONFIG = { let LOOKUP_TEST_MODEL_CONFIG = {
authorityOptions: { authorityOptions: {
closed: false, closed: false,
metadata: 'lookup', metadata: 'lookup',
@@ -35,7 +32,7 @@ export const LOOKUP_TEST_MODEL_CONFIG = {
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23' scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions, } as AuthorityOptions,
disabled: false, disabled: false,
errorMessages: {required: 'Required field.'}, errorMessages: { required: 'Required field.' },
id: 'lookup', id: 'lookup',
label: 'Author', label: 'Author',
maxOptions: 10, maxOptions: 10,
@@ -45,11 +42,11 @@ export const LOOKUP_TEST_MODEL_CONFIG = {
required: true, required: true,
repeatable: true, repeatable: true,
separator: ',', separator: ',',
validators: {required: null}, validators: { required: null },
value: undefined value: undefined
}; };
export const LOOKUP_NAME_TEST_MODEL_CONFIG = { let LOOKUP_NAME_TEST_MODEL_CONFIG = {
authorityOptions: { authorityOptions: {
closed: false, closed: false,
metadata: 'lookup-name', metadata: 'lookup-name',
@@ -57,7 +54,7 @@ export const LOOKUP_NAME_TEST_MODEL_CONFIG = {
scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23' scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
} as AuthorityOptions, } as AuthorityOptions,
disabled: false, disabled: false,
errorMessages: {required: 'Required field.'}, errorMessages: { required: 'Required field.' },
id: 'lookupName', id: 'lookupName',
label: 'Author', label: 'Author',
maxOptions: 10, maxOptions: 10,
@@ -67,16 +64,67 @@ export const LOOKUP_NAME_TEST_MODEL_CONFIG = {
required: true, required: true,
repeatable: true, repeatable: true,
separator: ',', separator: ',',
validators: {required: null}, validators: { required: null },
value: undefined value: undefined
}; };
export const LOOKUP_TEST_GROUP = new FormGroup({ let LOOKUP_TEST_GROUP = new FormGroup({
lookup: new FormControl(), lookup: new FormControl(),
lookupName: new FormControl() lookupName: new FormControl()
}); });
describe('Dynamic Lookup component', () => { 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 testComp: TestComponent;
let lookupComp: DsDynamicLookupComponent; let lookupComp: DsDynamicLookupComponent;
@@ -84,11 +132,11 @@ describe('Dynamic Lookup component', () => {
let lookupFixture: ComponentFixture<DsDynamicLookupComponent>; let lookupFixture: ComponentFixture<DsDynamicLookupComponent>;
let html; let html;
const authorityServiceStub = new AuthorityServiceStub(); let authorityServiceStub;
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
const authorityService = new AuthorityServiceStub();
authorityServiceStub = authorityService;
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
DynamicFormsCoreModule, DynamicFormsCoreModule,
@@ -106,16 +154,19 @@ describe('Dynamic Lookup component', () => {
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicLookupComponent, DsDynamicLookupComponent,
{provide: AuthorityService, useValue: authorityServiceStub}, { provide: AuthorityService, useValue: authorityService },
{provide: DynamicFormLayoutService, useValue: {}}, { provide: DynamicFormLayoutService, useValue: {} },
{provide: DynamicFormValidationService, useValue: {}} { provide: DynamicFormValidationService, useValue: {} }
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
})); }));
describe('', () => { beforeEach(() => {
init();
});
describe('DynamicLookUpComponent', () => {
// synchronous beforeEach // synchronous beforeEach
beforeEach(() => { beforeEach(() => {
html = ` html = `
@@ -130,11 +181,13 @@ describe('Dynamic Lookup component', () => {
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>; testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance; testComp = testFixture.componentInstance;
}); });
afterEach(() => {
testFixture.destroy();
testComp = null;
});
it('should create DsDynamicLookupComponent', inject([DsDynamicLookupComponent], (app: DsDynamicLookupComponent) => { it('should create DsDynamicLookupComponent', inject([DsDynamicLookupComponent], (app: DsDynamicLookupComponent) => {
expect(app).toBeDefined(); expect(app).toBeDefined();
})); }));
});
describe('when model is DynamicLookupModel', () => { describe('when model is DynamicLookupModel', () => {
@@ -147,7 +200,10 @@ describe('Dynamic Lookup component', () => {
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG); lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
lookupFixture.detectChanges(); lookupFixture.detectChanges();
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should render only an input element', () => { it('should render only an input element', () => {
const de = lookupFixture.debugElement.queryAll(By.css('input.form-control')); const de = lookupFixture.debugElement.queryAll(By.css('input.form-control'));
expect(de.length).toBe(1); expect(de.length).toBe(1);
@@ -157,7 +213,6 @@ describe('Dynamic Lookup component', () => {
describe('and init model value is empty', () => { describe('and init model value is empty', () => {
beforeEach(() => { beforeEach(() => {
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent); lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
lookupComp = lookupFixture.componentInstance; // FormComponent test instance lookupComp = lookupFixture.componentInstance; // FormComponent test instance
lookupComp.group = LOOKUP_TEST_GROUP; lookupComp.group = LOOKUP_TEST_GROUP;
@@ -165,6 +220,11 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges(); lookupFixture.detectChanges();
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => { it('should init component properly', () => {
expect(lookupComp.firstInputValue).toBe(''); expect(lookupComp.firstInputValue).toBe('');
}); });
@@ -182,16 +242,19 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges(); lookupFixture.detectChanges();
results$.subscribe((results) => { results$.subscribe((results) => {
expect(lookupComp.optionsList).toEqual(results.payload); expect(lookupComp.optionsList).toEqual(results.payload);
}) });
})); }));
it('should select a results entry properly', fakeAsync(() => { it('should select a results entry properly', fakeAsync(() => {
let de = lookupFixture.debugElement.queryAll(By.css('button')); let de = lookupFixture.debugElement.queryAll(By.css('button'));
const btnEl = de[0].nativeElement; 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'); spyOn(lookupComp.change, 'emit');
lookupComp.firstInputValue = 'test'; lookupComp.firstInputValue = 'test';
lookupFixture.detectChanges(); lookupFixture.detectChanges();
btnEl.click(); btnEl.click();
@@ -200,7 +263,7 @@ describe('Dynamic Lookup component', () => {
de = lookupFixture.debugElement.queryAll(By.css('button.dropdown-item')); de = lookupFixture.debugElement.queryAll(By.css('button.dropdown-item'));
const entryEl = de[0].nativeElement; const entryEl = de[0].nativeElement;
entryEl.click(); entryEl.click();
lookupFixture.detectChanges();
expect(lookupComp.firstInputValue).toEqual('one'); expect(lookupComp.firstInputValue).toEqual('one');
expect(lookupComp.model.value).toEqual(selectedValue); expect(lookupComp.model.value).toEqual(selectedValue);
expect(lookupComp.change.emit).toHaveBeenCalled(); expect(lookupComp.change.emit).toHaveBeenCalled();
@@ -238,9 +301,12 @@ describe('Dynamic Lookup component', () => {
// spyOn(store, 'dispatch'); // spyOn(store, 'dispatch');
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => { 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'); // spyOn(store, 'dispatch');
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should render two input element', () => { it('should render two input element', () => {
const de = lookupFixture.debugElement.queryAll(By.css('input.form-control')); const de = lookupFixture.debugElement.queryAll(By.css('input.form-control'));
expect(de.length).toBe(2); expect(de.length).toBe(2);
@@ -277,15 +346,31 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges(); lookupFixture.detectChanges();
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should select a results entry properly', fakeAsync(() => { it('should select a results entry properly', fakeAsync(() => {
const payload = [ const payload = [
Object.assign(new AuthorityValueModel(), {id: 1, display: 'Name, Lastname', value: 1}), Object.assign(new AuthorityValueModel(), {
Object.assign(new AuthorityValueModel(), {id: 2, display: 'NameTwo, LastnameTwo', value: 2}), 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')); let de = lookupFixture.debugElement.queryAll(By.css('button'));
const btnEl = de[0].nativeElement; 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'); spyOn(lookupComp.change, 'emit');
authorityServiceStub.setNewPayload(payload); authorityServiceStub.setNewPayload(payload);
lookupComp.firstInputValue = 'test'; lookupComp.firstInputValue = 'test';
@@ -302,7 +387,6 @@ describe('Dynamic Lookup component', () => {
expect(lookupComp.model.value).toEqual(selectedValue); expect(lookupComp.model.value).toEqual(selectedValue);
expect(lookupComp.change.emit).toHaveBeenCalled(); expect(lookupComp.change.emit).toHaveBeenCalled();
})); }));
}); });
describe('and init model value is not empty', () => { describe('and init model value is not empty', () => {
@@ -316,13 +400,16 @@ describe('Dynamic Lookup component', () => {
lookupFixture.detectChanges(); lookupFixture.detectChanges();
}); });
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', () => { it('should init component properly', () => {
expect(lookupComp.firstInputValue).toBe('Name'); expect(lookupComp.firstInputValue).toBe('Name');
expect(lookupComp.secondInputValue).toBe('Lastname'); expect(lookupComp.secondInputValue).toBe('Lastname');
}); });
}); });
});
}); });
}); });
@@ -338,7 +425,4 @@ class TestComponent {
inputLookupModelConfig = LOOKUP_TEST_MODEL_CONFIG; inputLookupModelConfig = LOOKUP_TEST_MODEL_CONFIG;
model = new DynamicLookupModel(this.inputLookupModelConfig); model = new DynamicLookupModel(this.inputLookupModelConfig);
showErrorMessages = false;
} }

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import {of as observableOf, Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import {catchError, debounceTime, distinctUntilChanged, tap, switchMap, map, merge} from 'rxjs/operators'; 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() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTagModel; @Input() model: DynamicTagModel;
// @Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -86,6 +84,7 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
ngOnInit() { ngOnInit() {
this.hasAuthority = this.model.authorityOptions && hasValue(this.model.authorityOptions.name); this.hasAuthority = this.model.authorityOptions && hasValue(this.model.authorityOptions.name);
if (this.hasAuthority) { if (this.hasAuthority) {
this.searchOptions = new IntegrationSearchOptions( this.searchOptions = new IntegrationSearchOptions(
this.model.authorityOptions.scope, 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 { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { createTestComponent } from '../../../../../testing/utils'; import { createTestComponent } from '../../../../../testing/utils';
export const TYPEAHEAD_TEST_GROUP = new FormGroup({ export let TYPEAHEAD_TEST_GROUP;
typeahead: new FormControl(),
});
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: { authorityOptions: {
closed: false, closed: false,
metadata: 'typeahead', metadata: 'typeahead',
@@ -43,8 +48,8 @@ export const TYPEAHEAD_TEST_MODEL_CONFIG = {
required: false, required: false,
repeatable: false, repeatable: false,
value: undefined value: undefined
}; };
}
describe('DsDynamicTypeaheadComponent test suite', () => { describe('DsDynamicTypeaheadComponent test suite', () => {
let testComp: TestComponent; let testComp: TestComponent;
@@ -56,7 +61,7 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
const authorityServiceStub = new AuthorityServiceStub(); const authorityServiceStub = new AuthorityServiceStub();
init()
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
DynamicFormsCoreModule, DynamicFormsCoreModule,
@@ -72,9 +77,9 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
DsDynamicTypeaheadComponent, DsDynamicTypeaheadComponent,
{provide: AuthorityService, useValue: authorityServiceStub}, { provide: AuthorityService, useValue: authorityServiceStub },
{provide: DynamicFormLayoutService, useValue: {}}, { provide: DynamicFormLayoutService, useValue: {} },
{provide: DynamicFormValidationService, useValue: {}} { provide: DynamicFormValidationService, useValue: {} }
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -96,6 +101,9 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
testComp = testFixture.componentInstance; testComp = testFixture.componentInstance;
}); });
afterEach(() => {
testFixture.destroy();
});
it('should create DsDynamicTypeaheadComponent', inject([DsDynamicTypeaheadComponent], (app: DsDynamicTypeaheadComponent) => { it('should create DsDynamicTypeaheadComponent', inject([DsDynamicTypeaheadComponent], (app: DsDynamicTypeaheadComponent) => {
expect(app).toBeDefined(); expect(app).toBeDefined();
@@ -221,6 +229,4 @@ class TestComponent {
model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG); 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() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTypeaheadModel; @Input() model: DynamicTypeaheadModel;
@Input() showErrorMessages = false;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();

View File

@@ -13,13 +13,10 @@ import {
DynamicColorPickerModel, DynamicColorPickerModel,
DynamicDatePickerModel, DynamicDatePickerModel,
DynamicEditorModel, DynamicEditorModel,
DynamicFileUploadModel, DynamicFormArrayGroupModel, DynamicFileUploadModel,
DynamicFormArrayModel, DynamicFormArrayModel,
DynamicFormControlModel, DynamicFormControlModel,
// DynamicFormControlValue, DynamicFormGroupModel, DynamicFormValidationService,
DynamicFormGroupModel,
DynamicFormService,
DynamicFormValidationService,
DynamicFormValueControlModel, DynamicFormValueControlModel,
DynamicInputModel, DynamicInputModel,
DynamicRadioGroupModel, 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 { DynamicListRadioGroupModel } from './ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
import { AuthorityOptions } from '../../../core/integration/models/authority-options.model'; import { AuthorityOptions } from '../../../core/integration/models/authority-options.model';
import { FormFieldModel } from './models/form-field.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 { FormBuilderService } from './form-builder.service';
import { DynamicRowGroupModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-group-model'; 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'; 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 { 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 { 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 { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { getMockFormBuilderService } from '../../mocks/mock-form-builder-service';
describe('FormBuilderService test suite', () => { describe('FormBuilderService test suite', () => {
@@ -70,7 +69,8 @@ describe('FormBuilderService test suite', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ReactiveFormsModule], imports: [ReactiveFormsModule],
providers: [ providers: [
{provide: FormBuilderService, useValue: getMockFormBuilderService()}, {provide: FormBuilderService, useClass: FormBuilderService},
{provide: DynamicFormValidationService, useValue: {}},
{provide: NG_VALIDATORS, useValue: testValidator, multi: true}, {provide: NG_VALIDATORS, useValue: testValidator, multi: true},
{provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true} {provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true}
] ]
@@ -254,7 +254,7 @@ describe('FormBuilderService test suite', () => {
{ {
id: 'testFormRowArray', id: 'testFormRowArray',
initialCount: 5, initialCount: 5,
notRepeteable: false, notRepeatable: false,
groupFactory: () => { groupFactory: () => {
return [ return [
new DynamicInputModel({id: 'testFormRowArrayGroupInput'}) new DynamicInputModel({id: 'testFormRowArrayGroupInput'})

View File

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

View File

@@ -14,7 +14,7 @@
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context"> <ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
<!--Array with repeteable items--> <!--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"> 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"> <div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-secondary" <button type="button" class="btn btn-secondary"
@@ -31,7 +31,7 @@
</div> </div>
<!--Array with non repeteable items - Only delete button--> <!--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"> 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"> <div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-secondary" <button type="button" class="btn btn-secondary"

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,12 @@
import { FormService } from '../form/form.service'; import { FormService } from '../form/form.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { map } from 'rxjs/operators';
export function getMockFormService( export function getMockFormService(
id$: string = 'random_id', id$: string = 'random_id'
errors = new BehaviorSubject([])
): FormService { ): FormService {
return jasmine.createSpyObj('FormService', { return jasmine.createSpyObj('FormService', {
getUniqueId: id$, getUniqueId: id$,
resetForm: {}, resetForm: {},
validateAllFormFields: {}, 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
}); });
} }

View File

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

View File

@@ -8,7 +8,7 @@
<ds-object-grid [config]="config" <ds-object-grid [config]="config"
[sortConfig]="sortConfig" [sortConfig]="sortConfig"
[objects]="objects" [objects]="objects"
[hideGear]="true" [hideGear]="hideGear"
*ngIf="getViewMode()===viewModeEnum.Grid"> *ngIf="getViewMode()===viewModeEnum.Grid">
</ds-object-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"> <a [routerLink]="['/items/', object.id]" class="card-img-top">
<ds-grid-thumbnail [thumbnail]="object.getThumbnail()"> <ds-grid-thumbnail [thumbnail]="object.getThumbnail()">
</ds-grid-thumbnail> </ds-grid-thumbnail>
</a> </a>
<div class="card-body"> <div class="card-body">
<h4 class="card-title">{{object.findMetadata('dc.title')}}</h4> <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"> <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 *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<span *ngIf="!last">; </span> <span *ngIf="!last">; </span>
</span> </span>
<span *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-date">{{object.findMetadata("dc.date.issued")}}</span> <span *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-date">{{object.findMetadata("dc.date.issued")}}</span>
</p> </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> <a [routerLink]="['/items/', object.id]" class="lead btn btn-primary viewButton">View</a>
</div> </div>
</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")}} {{object.findMetadata("dc.title")}}
</a> </a>
<div> <div>
<ds-truncatable-part [id]="object.id" [minLines]="1">
<span class="text-muted"> <span class="text-muted">
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" <span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
class="item-list-authors"> 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 (<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>) *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
</span> </span>
</ds-truncatable-part>
<ds-truncatable-part [id]="object.id" [minLines]="3">
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract"> <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>
</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 { 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 { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component';
import { MockAdminGuard } from './mocks/mock-admin-guard.service'; 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 { DebounceDirective } from './utils/debounce.directive';
import { ClickOutsideDirective } from './utils/click-outside.directive'; import { ClickOutsideDirective } from './utils/click-outside.directive';
import { EmphasizePipe } from './utils/emphasize.pipe'; import { EmphasizePipe } from './utils/emphasize.pipe';
@@ -155,6 +158,7 @@ const COMPONENTS = [
ViewModeSwitchComponent, ViewModeSwitchComponent,
TruncatableComponent, TruncatableComponent,
TruncatablePartComponent, TruncatablePartComponent,
BrowseByComponent,
InputSuggestionsComponent InputSuggestionsComponent
]; ];
@@ -168,6 +172,7 @@ const ENTRY_COMPONENTS = [
CollectionGridElementComponent, CollectionGridElementComponent,
CommunityGridElementComponent, CommunityGridElementComponent,
SearchResultGridElementComponent, SearchResultGridElementComponent,
BrowseEntryListElementComponent
]; ];
const PROVIDERS = [ 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 { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';

View File

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

View File

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

View File

@@ -1,8 +1,5 @@
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects'; 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 { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { AppComponent } from '../../app/app.component'; import { AppComponent } from '../../app/app.component';
import { AppModule } from '../../app/app.module'; import { AppModule } from '../../app/app.module';

View File

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