diff --git a/e2e/search-navbar/search-navbar.e2e-spec.ts b/e2e/search-navbar/search-navbar.e2e-spec.ts new file mode 100644 index 0000000000..b60f71919d --- /dev/null +++ b/e2e/search-navbar/search-navbar.e2e-spec.ts @@ -0,0 +1,46 @@ +import { ProtractorPage } from './search-navbar.po'; +import { browser } from 'protractor'; + +describe('protractor SearchNavbar', () => { + let page: ProtractorPage; + let queryString: string; + + beforeEach(() => { + page = new ProtractorPage(); + queryString = 'the test query'; + }); + + it('should go to search page with correct query if submitted (from home)', () => { + page.navigateToHome(); + return checkIfSearchWorks(); + }); + + it('should go to search page with correct query if submitted (from search)', () => { + page.navigateToSearch(); + return checkIfSearchWorks(); + }); + + it('check if can submit search box with pressing button', () => { + page.navigateToHome(); + page.expandAndFocusSearchBox(); + page.setCurrentQuery(queryString); + page.submitNavbarSearchForm(); + browser.wait(() => { + return browser.getCurrentUrl().then((url: string) => { + return url.indexOf('query=' + encodeURI(queryString)) !== -1; + }); + }); + }); + + function checkIfSearchWorks(): boolean { + page.setCurrentQuery(queryString); + page.submitByPressingEnter(); + browser.wait(() => { + return browser.getCurrentUrl().then((url: string) => { + return url.indexOf('query=' + encodeURI(queryString)) !== -1; + }); + }); + return false; + } + +}); diff --git a/e2e/search-navbar/search-navbar.po.ts b/e2e/search-navbar/search-navbar.po.ts new file mode 100644 index 0000000000..17112ab468 --- /dev/null +++ b/e2e/search-navbar/search-navbar.po.ts @@ -0,0 +1,40 @@ +import { browser, element, by, protractor } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +export class ProtractorPage { + HOME = '/home'; + SEARCH = '/search'; + + navigateToHome() { + return browser.get(this.HOME); + } + + navigateToSearch() { + return browser.get(this.SEARCH); + } + + getCurrentQuery(): promise.Promise { + return element(by.css('#search-navbar-container form input')).getAttribute('value'); + } + + expandAndFocusSearchBox() { + element(by.css('#search-navbar-container form a')).click(); + } + + setCurrentQuery(query: string) { + element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(query); + } + + submitNavbarSearchForm() { + element(by.css('#search-navbar-container form .submit-icon')).click(); + } + + submitByPressingEnter() { + element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER); + } + + submitByPressingEnter() { + element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER); + } + +} diff --git a/e2e/search-page/search-page.po.ts b/e2e/search-page/search-page.po.ts index fde3e68bf8..51bf86453b 100644 --- a/e2e/search-page/search-page.po.ts +++ b/e2e/search-page/search-page.po.ts @@ -1,4 +1,4 @@ -import { browser, element, by, protractor } from 'protractor'; +import { browser, by, element, protractor } from 'protractor'; import { promise } from 'selenium-webdriver'; export class ProtractorPage { @@ -27,15 +27,15 @@ export class ProtractorPage { } setCurrentScope(scope: string) { - element(by.css('option[value="' + scope + '"]')).click(); + element(by.css('#search-form option[value="' + scope + '"]')).click(); } setCurrentQuery(query: string) { - element(by.css('input[name="query"]')).sendKeys(query); + element(by.css('#search-form input[name="query"]')).sendKeys(query); } submitSearchForm() { - element(by.css('button.search-button')).click(); + element(by.css('#search-form button.search-button')).click(); } getRandomScopeOption(): promise.Promise { diff --git a/protractor.conf.js b/protractor.conf.js index 2949702a0a..6570c9f7c3 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -5,7 +5,7 @@ var SpecReporter = require('jasmine-spec-reporter').SpecReporter; exports.config = { - allScriptsTimeout: 11000, + allScriptsTimeout: 600000, // ----------------------------------------------------------------- // Uncomment to run tests using a remote Selenium server //seleniumAddress: 'http://selenium.address:4444/wd/hub', @@ -73,7 +73,7 @@ exports.config = { framework: 'jasmine', jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 30000, + defaultTimeoutInterval: 600000, print: function () {} }, useAllAngular2AppRoots: true, diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index 185d083764..72eb306bf1 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -17,6 +17,7 @@ import { CreateCollectionParentSelectorComponent } from '../../shared/dso-select import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; +import {CreateItemParentSelectorComponent} from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; /** * Component representing the admin sidebar @@ -137,18 +138,18 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { parentID: 'new', active: false, visible: true, - // model: { - // type: MenuItemType.ONCLICK, - // text: 'menu.section.new_item', - // function: () => { - // this.modalService.open(CreateItemParentSelectorComponent); - // } - // } as OnClickMenuItemModel, model: { - type: MenuItemType.LINK, + type: MenuItemType.ONCLICK, text: 'menu.section.new_item', - link: '/submit' - } as LinkMenuItemModel, + function: () => { + this.modalService.open(CreateItemParentSelectorComponent); + } + } as OnClickMenuItemModel, + // model: { + // type: MenuItemType.LINK, + // text: 'menu.section.new_item', + // link: '/submit' + // } as LinkMenuItemModel, }, { id: 'new_item_version', diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index 2cde216c05..befac7f331 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -62,7 +62,7 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements this.routeService.setParameter('configuration', this.configuration); } if (hasValue(this.fixedFilterQuery)) { - this.routeService.setParameter('fixedFilter', this.fixedFilterQuery); + this.routeService.setParameter('fixedFilterQuery', this.fixedFilterQuery); } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2bf1fb5404..4b803608f3 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -10,10 +10,14 @@ import { META_REDUCERS, MetaReducer, StoreModule } from '@ngrx/store'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { TranslateModule } from '@ngx-translate/core'; +import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; import { storeFreeze } from 'ngrx-store-freeze'; import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../config'; +import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; +import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component'; +import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -23,23 +27,20 @@ import { appMetaReducers, debugMetaReducers } from './app.metareducers'; import { appReducers, AppState } from './app.reducer'; import { CoreModule } from './core/core.module'; -import { FooterComponent } from './footer/footer.component'; -import { HeaderComponent } from './header/header.component'; -import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; - -import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer'; -import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component'; -import { NotificationComponent } from './shared/notifications/notification/notification.component'; -import { SharedModule } from './shared/shared.module'; -import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; -import { HeaderNavbarWrapperComponent } from './header-nav-wrapper/header-navbar-wrapper.component'; -import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component'; -import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; -import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; -import { NavbarModule } from './navbar/navbar.module'; import { ClientCookieService } from './core/services/client-cookie.service'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; +import { FooterComponent } from './footer/footer.component'; +import { HeaderNavbarWrapperComponent } from './header-nav-wrapper/header-navbar-wrapper.component'; +import { HeaderComponent } from './header/header.component'; +import { NavbarModule } from './navbar/navbar.module'; +import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; +import { SearchNavbarComponent } from './search-navbar/search-navbar.component'; + +import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer'; +import { NotificationComponent } from './shared/notifications/notification/notification.component'; +import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component'; +import { SharedModule } from './shared/shared.module'; export function getConfig() { return ENV_CONFIG; @@ -113,6 +114,7 @@ const DECLARATIONS = [ PageNotFoundComponent, NotificationComponent, NotificationsBoardComponent, + SearchNavbarComponent, ]; const EXPORTS = [ @@ -128,7 +130,7 @@ const EXPORTS = [ ...PROVIDERS ], declarations: [ - ...DECLARATIONS + ...DECLARATIONS, ], exports: [ ...EXPORTS diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index b4f8185767..32ba070002 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -20,6 +20,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../cache/response.models'; import { getResponseFromEntry } from '../shared/operators'; +import {URLCombiner} from '../url-combiner/url-combiner'; /** * The service handling all submission REST requests @@ -65,9 +66,15 @@ export class SubmissionRestService { * The base endpoint for the type of object * @param resourceID * The identifier for the object + * @param collectionId + * The owning collection for the object */ - protected getEndpointByIDHref(endpoint, resourceID): string { - return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`; + protected getEndpointByIDHref(endpoint, resourceID, collectionId?: string): string { + let url = isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`; + if (collectionId) { + url = new URLCombiner(url, `?owningCollection=${collectionId}`).toString(); + } + return url; } /** @@ -130,12 +137,14 @@ export class SubmissionRestService { * The [HttpOptions] object * @return Observable * server response + * @param collectionId + * The owning collection id */ - public postToEndpoint(linkName: string, body: any, scopeId?: string, options?: HttpOptions): Observable { + public postToEndpoint(linkName: string, body: any, scopeId?: string, options?: HttpOptions, collectionId?: string): Observable { const requestId = this.requestService.generateRequestId(); return this.halService.getEndpoint(linkName).pipe( filter((href: string) => isNotEmpty(href)), - map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)), + map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId, collectionId)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionPostRequest(requestId, endpointURL, body, options)), tap((request: PostRequest) => this.requestService.configure(request)), diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index a03fd01c53..58f7cb1ecf 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,20 +1,20 @@
-
- - - +
+ + + - -
+ +
diff --git a/src/app/search-navbar/search-navbar.component.html b/src/app/search-navbar/search-navbar.component.html new file mode 100644 index 0000000000..13d792c80f --- /dev/null +++ b/src/app/search-navbar/search-navbar.component.html @@ -0,0 +1,12 @@ +
+
+
+ + + + +
+
+
diff --git a/src/app/search-navbar/search-navbar.component.scss b/src/app/search-navbar/search-navbar.component.scss new file mode 100644 index 0000000000..3606c47afc --- /dev/null +++ b/src/app/search-navbar/search-navbar.component.scss @@ -0,0 +1,25 @@ +input[type="text"] { + margin-top: -0.5 * $font-size-base; + + &:focus { + background-color: rgba(255, 255, 255, 0.5) !important; + } + + &.collapsed { + opacity: 0; + } +} + +a.submit-icon { + cursor: pointer; +} + + + +@media screen and (max-width: map-get($grid-breakpoints, sm)) { + #query:focus { + max-width: 250px !important; + width: 40vw !important; + } +} + diff --git a/src/app/search-navbar/search-navbar.component.spec.ts b/src/app/search-navbar/search-navbar.component.spec.ts new file mode 100644 index 0000000000..2a03acd2a2 --- /dev/null +++ b/src/app/search-navbar/search-navbar.component.spec.ts @@ -0,0 +1,121 @@ +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { Router } from '@angular/router'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { SearchService } from '../core/shared/search/search.service'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; + +import { SearchNavbarComponent } from './search-navbar.component'; + +describe('SearchNavbarComponent', () => { + let component: SearchNavbarComponent; + let fixture: ComponentFixture; + let mockSearchService: any; + let router: Router; + let routerStub; + + beforeEach(async(() => { + mockSearchService = { + getSearchLink() { + return '/search'; + } + }; + + routerStub = { + navigate: (commands) => commands + }; + TestBed.configureTestingModule({ + imports: [ + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + })], + declarations: [SearchNavbarComponent], + providers: [ + { provide: SearchService, useValue: mockSearchService }, + { provide: Router, useValue: routerStub } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchNavbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + router = (component as any).router; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('when you click on search icon', () => { + beforeEach(fakeAsync(() => { + spyOn(component, 'expand').and.callThrough(); + spyOn(component, 'onSubmit').and.callThrough(); + spyOn(router, 'navigate').and.callThrough(); + const searchIcon = fixture.debugElement.query(By.css('#search-navbar-container form .submit-icon')); + searchIcon.triggerEventHandler('click', { + preventDefault: () => {/**/ + } + }); + tick(); + fixture.detectChanges(); + })); + + it('input expands', () => { + expect(component.expand).toHaveBeenCalled(); + }); + + describe('empty query', () => { + describe('press submit button', () => { + beforeEach(fakeAsync(() => { + const searchIcon = fixture.debugElement.query(By.css('#search-navbar-container form .submit-icon')); + searchIcon.triggerEventHandler('click', { + preventDefault: () => {/**/ + } + }); + tick(); + fixture.detectChanges(); + })); + it('to search page with empty query', () => { + expect(component.onSubmit).toHaveBeenCalledWith({ query: '' }); + expect(router.navigate).toHaveBeenCalled(); + }); + }); + }); + + describe('fill in some query', () => { + let searchInput; + beforeEach(async () => { + await fixture.whenStable(); + fixture.detectChanges(); + searchInput = fixture.debugElement.query(By.css('#search-navbar-container form input')); + searchInput.nativeElement.value = 'test'; + searchInput.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + }); + describe('press submit button', () => { + beforeEach(fakeAsync(() => { + const searchIcon = fixture.debugElement.query(By.css('#search-navbar-container form .submit-icon')); + searchIcon.triggerEventHandler('click', null); + tick(); + fixture.detectChanges(); + })); + it('to search page with query', async () => { + expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' }); + expect(router.navigate).toHaveBeenCalled(); + }); + }); + }) + + }); +}); diff --git a/src/app/search-navbar/search-navbar.component.ts b/src/app/search-navbar/search-navbar.component.ts new file mode 100644 index 0000000000..1bedfb73ef --- /dev/null +++ b/src/app/search-navbar/search-navbar.component.ts @@ -0,0 +1,71 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { Router } from '@angular/router'; +import { SearchService } from '../core/shared/search/search.service'; +import { expandSearchInput } from '../shared/animations/slide'; + +/** + * The search box in the header that expands on focus and collapses on focus out + */ +@Component({ + selector: 'ds-search-navbar', + templateUrl: './search-navbar.component.html', + styleUrls: ['./search-navbar.component.scss'], + animations: [expandSearchInput] +}) +export class SearchNavbarComponent { + + // The search form + searchForm; + // Whether or not the search bar is expanded, boolean for html ngIf, string fo AngularAnimation state change + searchExpanded = false; + isExpanded = 'collapsed'; + + // Search input field + @ViewChild('searchInput') searchField: ElementRef; + + constructor(private formBuilder: FormBuilder, private router: Router, private searchService: SearchService) { + this.searchForm = this.formBuilder.group(({ + query: '', + })); + } + + /** + * Expands search bar by angular animation, see expandSearchInput + */ + expand() { + this.searchExpanded = true; + this.isExpanded = 'expanded'; + this.editSearch(); + } + + /** + * Collapses & blurs search bar by angular animation, see expandSearchInput + */ + collapse() { + this.searchField.nativeElement.blur(); + this.searchExpanded = false; + this.isExpanded = 'collapsed'; + } + + /** + * Focuses on input search bar so search can be edited + */ + editSearch(): void { + this.searchField.nativeElement.focus(); + } + + /** + * Submits the search (on enter or on search icon click) + * @param data Data for the searchForm, containing the search query + */ + onSubmit(data: any) { + this.collapse(); + const linkToNavigateTo = this.searchService.getSearchLink().split('/'); + this.searchForm.reset(); + this.router.navigate(linkToNavigateTo, { + queryParams: Object.assign({}, { page: 1 }, data), + queryParamsHandling: 'merge' + }); + } +} diff --git a/src/app/shared/animations/slide.ts b/src/app/shared/animations/slide.ts index 38bfaaddca..7928a25659 100644 --- a/src/app/shared/animations/slide.ts +++ b/src/app/shared/animations/slide.ts @@ -1,13 +1,4 @@ -import { - animate, - animateChild, - group, - query, - state, - style, - transition, - trigger -} from '@angular/animations'; +import { animate, animateChild, group, query, state, style, transition, trigger } from '@angular/animations'; export const slide = trigger('slide', [ state('expanded', style({ height: '*' })), @@ -70,3 +61,30 @@ export const slideSidebarPadding = trigger('slideSidebarPadding', [ transition('hidden <=> expanded', [animate('200ms')]), transition('shown <=> expanded', [animate('200ms')]), ]); + +export const expandSearchInput = trigger('toggleAnimation', [ + state('collapsed', style({ + width: '30px', + opacity: '0' + })), + state('expanded', style({ + width: '250px', + opacity: '1' + })), + transition('* => collapsed', group([ + animate('300ms ease-in-out', style({ + width: '30px' + })), + animate('300ms ease-in', style({ + opacity: '0' + })) + ])), + transition('* => expanded', group([ + animate('300ms ease-out', style({ + opacity: '1' + })), + animate('300ms ease-in-out', style({ + width: '250px' + })) + ])) +]); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts index 2c9b2499ab..b3058ab879 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts @@ -59,9 +59,8 @@ describe('CreateItemParentSelectorComponent', () => { }); it('should call navigate on the router with the correct create path when navigate is called', () => { - /* TODO when there is a specific submission path */ - // component.navigate(item); - // expect(router.navigate).toHaveBeenCalledWith([createPath]); + component.navigate(collection); + expect(router.navigate).toHaveBeenCalledWith(['/submit'], { queryParams: { collection: collection.uuid } }); }); }); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts index 29af9f624e..02a0bd79cd 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -28,6 +28,11 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo * Navigate to the item create page */ navigate(dso: DSpaceObject) { - // There's no submit path per collection yet... + const navigationExtras: NavigationExtras = { + queryParams: { + ['collection']: dso.uuid, + } + }; + this.router.navigate(['/submit'], navigationExtras); } } diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index 3a95b0747b..1b346a56eb 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -401,6 +401,14 @@ describe('SubmissionService test suite', () => { service.createSubmission(); expect((service as any).restService.postToEndpoint).toHaveBeenCalled(); + expect((service as any).restService.postToEndpoint).toHaveBeenCalledWith('workspaceitems', {}, null, null, undefined); + }); + + it('should create a new submission with collection', () => { + service.createSubmission(collectionId); + + expect((service as any).restService.postToEndpoint).toHaveBeenCalled(); + expect((service as any).restService.postToEndpoint).toHaveBeenCalledWith('workspaceitems', {}, null, null, collectionId); }); }); diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 766aa518f3..ac0cd7bec8 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -103,11 +103,13 @@ export class SubmissionService { /** * Perform a REST call to create a new workspaceitem and return response * + * @param collectionId + * The owning collection id * @return Observable * observable of SubmissionObject */ - createSubmission(): Observable { - return this.restService.postToEndpoint(this.workspaceLinkPath, {}).pipe( + createSubmission(collectionId?: string): Observable { + return this.restService.postToEndpoint(this.workspaceLinkPath, {}, null, null, collectionId).pipe( map((workspaceitem: SubmissionObject[]) => workspaceitem[0] as SubmissionObject), catchError(() => observableOf({} as SubmissionObject))) } diff --git a/src/app/submission/submit/submission-submit.component.spec.ts b/src/app/submission/submit/submission-submit.component.spec.ts index 771171a2d1..ca3316669f 100644 --- a/src/app/submission/submit/submission-submit.component.spec.ts +++ b/src/app/submission/submit/submission-submit.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { NO_ERRORS_SCHEMA, ViewContainerRef } from '@angular/core'; import { of as observableOf } from 'rxjs'; @@ -14,6 +14,7 @@ import { getMockTranslateService } from '../../shared/mocks/mock-translate.servi import { RouterStub } from '../../shared/testing/router-stub'; import { mockSubmissionObject } from '../../shared/mocks/mock-submission'; import { SubmissionSubmitComponent } from './submission-submit.component'; +import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; describe('SubmissionSubmitComponent Component', () => { @@ -39,6 +40,7 @@ describe('SubmissionSubmitComponent Component', () => { { provide: SubmissionService, useClass: SubmissionServiceStub }, { provide: TranslateService, useValue: getMockTranslateService() }, { provide: Router, useValue: new RouterStub() }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, ViewContainerRef ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index 448ccf97e2..0aa0380a25 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; @@ -27,6 +27,12 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { */ public collectionId: string; + /** + * The collection id input to create a new submission + * @type {string} + */ + public collectionParam: string; + /** * The submission self url * @type {string} @@ -60,13 +66,18 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { * @param {Router} router * @param {TranslateService} translate * @param {ViewContainerRef} viewContainerRef + * @param {ActivatedRoute} route */ constructor(private changeDetectorRef: ChangeDetectorRef, private notificationsService: NotificationsService, private router: Router, private submissionService: SubmissionService, private translate: TranslateService, - private viewContainerRef: ViewContainerRef) { + private viewContainerRef: ViewContainerRef, + private route: ActivatedRoute) { + this.route + .queryParams + .subscribe((params) => { this.collectionParam = (params.collection); }); } /** @@ -75,7 +86,7 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { ngOnInit() { // NOTE execute the code on the browser side only, otherwise it is executed twice this.subs.push( - this.submissionService.createSubmission() + this.submissionService.createSubmission(this.collectionParam) .subscribe((submissionObject: SubmissionObject) => { // NOTE new submission is created on the browser side only if (isNotNull(submissionObject)) {