diff --git a/src/app/project/list/list.component.spec.ts b/src/app/project/list/list.component.spec.ts index 87107b283f..581cc7488a 100644 --- a/src/app/project/list/list.component.spec.ts +++ b/src/app/project/list/list.component.spec.ts @@ -145,223 +145,480 @@ describe('ListComponent', () => { }).compileComponents(); })); - beforeEach(() => { - - // mock session service - const sessionSpy = TestBed.inject(SessionService); - - (sessionSpy as jasmine.SpyObj).getSession.and.callFake( - () => { - const session: Session = { - id: 12345, - user: { - name: 'username', - jwt: 'myToken', - lang: 'en', - sysAdmin: true, - projectAdmin: [] - } - }; + afterEach(async () => { + const dialogs = await rootLoader.getAllHarnesses(MatDialogHarness); + await Promise.all(dialogs.map(async d => await d.close())); - return session; - } - ); + // angular won't call this for us so we need to do it ourselves to avoid leaks. + overlayContainer.ngOnDestroy(); + }); - // mock cache service - const cacheSpy = TestBed.inject(CacheService); + describe('Displaying lists', () => { + beforeEach(() => { + + // mock session service + const sessionSpy = TestBed.inject(SessionService); + + (sessionSpy as jasmine.SpyObj).getSession.and.callFake( + () => { + const session: Session = { + id: 12345, + user: { + name: 'username', + jwt: 'myToken', + lang: 'en', + sysAdmin: true, + projectAdmin: [] + } + }; - (cacheSpy as jasmine.SpyObj).get.and.callFake( - () => { - const response: ProjectResponse = new ProjectResponse(); + return session; + } + ); - const mockProjects = MockProjects.mockProjects(); + // mock cache service + const cacheSpy = TestBed.inject(CacheService); - response.project = mockProjects.body.projects[0]; + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); - return of(response.project as ReadProject); - } - ); + const mockProjects = MockProjects.mockProjects(); - // mock router - const routerSpy = TestBed.inject(Router); + response.project = mockProjects.body.projects[0]; - (routerSpy as jasmine.SpyObj).navigate.and.stub(); - (routerSpy as jasmine.SpyObj).navigateByUrl.and.stub(); + return of(response.project as ReadProject); + } + ); - // mock lists endpoint - const dspConnSpy = TestBed.inject(DspApiConnectionToken); + // mock router + const routerSpy = TestBed.inject(Router); - (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( - () => { - const response = new ListsResponse(); + (routerSpy as jasmine.SpyObj).navigate.and.stub(); + (routerSpy as jasmine.SpyObj).navigateByUrl.and.stub(); - response.lists = new Array(); + // mock lists endpoint + const dspConnSpy = TestBed.inject(DspApiConnectionToken); - const mockList1 = new ListNodeInfo(); - mockList1.comments = []; - mockList1.id = 'http://rdfh.ch/lists/0001/mockList01'; - mockList1.isRootNode = true; - mockList1.labels = [{ language: 'en', value: 'Mock List 01' }]; - mockList1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( + () => { + const response = new ListsResponse(); - const mockList2 = new ListNodeInfo(); - mockList2.comments = []; - mockList2.id = 'http://rdfh.ch/lists/0001/mockList02'; - mockList2.isRootNode = true; - mockList2.labels = [{ language: 'en', value: 'Mock List 02' }]; - mockList2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + response.lists = new Array(); - response.lists.push(mockList1, mockList2); + const mockList1 = new ListNodeInfo(); + mockList1.comments = []; + mockList1.id = 'http://rdfh.ch/lists/0001/mockList01'; + mockList1.isRootNode = true; + mockList1.labels = [{ language: 'en', value: 'Mock List 01' }]; + mockList1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; - return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); - } - ); + const mockList2 = new ListNodeInfo(); + mockList2.comments = []; + mockList2.id = 'http://rdfh.ch/lists/0001/mockList02'; + mockList2.isRootNode = true; + mockList2.labels = [{ language: 'en', value: 'Mock List 02' }]; + mockList2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); + response.lists.push(mockList1, mockList2); - overlayContainer = TestBed.inject(OverlayContainer); - rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture); + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); - // mock projects endpoint - (dspConnSpy.admin.projectsEndpoint as jasmine.SpyObj).getProjectByShortcode.and.callFake( - () => { - const response = new ProjectResponse(); + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); - const mockProjects = MockProjects.mockProjects(); + overlayContainer = TestBed.inject(OverlayContainer); + rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture); - response.project = mockProjects.body.projects[0]; + // mock projects endpoint + (dspConnSpy.admin.projectsEndpoint as jasmine.SpyObj).getProjectByShortcode.and.callFake( + () => { + const response = new ProjectResponse(); - return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); - } - ); + const mockProjects = MockProjects.mockProjects(); - expect(testHostComponent.listComponent.session).toBeTruthy(); - expect(testHostComponent).toBeTruthy(); - }); + response.project = mockProjects.body.projects[0]; - afterEach(async () => { - const dialogs = await rootLoader.getAllHarnesses(MatDialogHarness); - await Promise.all(dialogs.map(async d => await d.close())); + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); - // angular won't call this for us so we need to do it ourselves to avoid leaks. - overlayContainer.ngOnDestroy(); - }); + expect(testHostComponent.listComponent.session).toBeTruthy(); + expect(testHostComponent).toBeTruthy(); + }); - it('should get the project', () => { - const mockProject = MockProjects.mockProjects().body.projects[0]; + it('should get the project', () => { + const mockProject = MockProjects.mockProjects().body.projects[0]; - expect(testHostComponent.listComponent.project).toEqual(mockProject); - }); + expect(testHostComponent.listComponent.project).toEqual(mockProject); + }); - it('should initialize the list of lists', () => { - const listOfLists = new Array(); + it('should initialize the list of lists', () => { + const listOfLists = new Array(); - const list1 = new ListNodeInfo(); - list1.comments = []; - list1.id = 'http://rdfh.ch/lists/0001/mockList01'; - list1.isRootNode = true; - list1.labels = [{ language: 'en', value: 'Mock List 01' }]; - list1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + const list1 = new ListNodeInfo(); + list1.comments = []; + list1.id = 'http://rdfh.ch/lists/0001/mockList01'; + list1.isRootNode = true; + list1.labels = [{ language: 'en', value: 'Mock List 01' }]; + list1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; - const list2 = new ListNodeInfo(); - list2.comments = []; - list2.id = 'http://rdfh.ch/lists/0001/mockList02'; - list2.isRootNode = true; - list2.labels = [{ language: 'en', value: 'Mock List 02' }]; - list2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + const list2 = new ListNodeInfo(); + list2.comments = []; + list2.id = 'http://rdfh.ch/lists/0001/mockList02'; + list2.isRootNode = true; + list2.labels = [{ language: 'en', value: 'Mock List 02' }]; + list2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + + listOfLists.push(list1, list2); + + expect(testHostComponent.listComponent.lists).toEqual(listOfLists); + }); - listOfLists.push(list1, list2); + it('should open a list', async () => { + const select = await rootLoader.getHarness(MatSelectHarness); - expect(testHostComponent.listComponent.lists).toEqual(listOfLists); + await select.open(); + + const options = await select.getOptions({ text: 'Mock List 01 (en)' }); + + expect(options.length).toEqual(1); + + await options[0].click(); + + expect(testHostComponent.listComponent.list.id).toEqual('http://rdfh.ch/lists/0001/mockList01'); + }); }); - it('should open a list', async () => { - const select = await rootLoader.getHarness(MatSelectHarness); + describe('Delete list with lists remaining after deletion', () => { + beforeEach(() => { + + // mock session service + const sessionSpy = TestBed.inject(SessionService); + + (sessionSpy as jasmine.SpyObj).getSession.and.callFake( + () => { + const session: Session = { + id: 12345, + user: { + name: 'username', + jwt: 'myToken', + lang: 'en', + sysAdmin: true, + projectAdmin: [] + } + }; + + return session; + } + ); + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + + // mock router + const routerSpy = TestBed.inject(Router); + + (routerSpy as jasmine.SpyObj).navigate.and.stub(); + (routerSpy as jasmine.SpyObj).navigateByUrl.and.stub(); + + // mock lists endpoint + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( + () => { + const response = new ListsResponse(); + + response.lists = new Array(); + + const mockList1 = new ListNodeInfo(); + mockList1.comments = []; + mockList1.id = 'http://rdfh.ch/lists/0001/mockList01'; + mockList1.isRootNode = true; + mockList1.labels = [{ language: 'en', value: 'Mock List 01' }]; + mockList1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + + const mockList2 = new ListNodeInfo(); + mockList2.comments = []; + mockList2.id = 'http://rdfh.ch/lists/0001/mockList02'; + mockList2.isRootNode = true; + mockList2.labels = [{ language: 'en', value: 'Mock List 02' }]; + mockList2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; - await select.open(); + response.lists.push(mockList1, mockList2); - const options = await select.getOptions({ text: 'Mock List 01 (en)' }); + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + overlayContainer = TestBed.inject(OverlayContainer); + rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture); + + // mock projects endpoint + (dspConnSpy.admin.projectsEndpoint as jasmine.SpyObj).getProjectByShortcode.and.callFake( + () => { + const response = new ProjectResponse(); - expect(options.length).toEqual(1); + const mockProjects = MockProjects.mockProjects(); - await options[0].click(); + response.project = mockProjects.body.projects[0]; - expect(testHostComponent.listComponent.list.id).toEqual('http://rdfh.ch/lists/0001/mockList01'); + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + expect(testHostComponent.listComponent.session).toBeTruthy(); + expect(testHostComponent).toBeTruthy(); + }); + + it('should delete a root node and the first of the remaining lists should be shown', async () => { + const deleteListResponse: DeleteListResponse = new DeleteListResponse(); + deleteListResponse.deleted = true; + deleteListResponse.iri = 'http://rdfh.ch/lists/0001/mockList01'; + + const listSpy = TestBed.inject(DspApiConnectionToken); + + // mock delete list root node response + (listSpy.admin.listsEndpoint as jasmine.SpyObj).deleteListNode.and.callFake( + () => { + const response = deleteListResponse; + + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + // mock the call to the API again because we call the API again after deletion to get the lists + // therefore the mock call in the BeforeEach method above will not work since it returns two lists + // after deletion, we should only have one list + (listSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( + () => { + const response = new ListsResponse(); + + response.lists = new Array(); + + const mockList2 = new ListNodeInfo(); + mockList2.comments = []; + mockList2.id = 'http://rdfh.ch/lists/0001/mockList02'; + mockList2.isRootNode = true; + mockList2.labels = [{ language: 'en', value: 'Mock List 02' }]; + mockList2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + + response.lists.push(mockList2); + + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + // open first list among lists + const select = await rootLoader.getHarness(MatSelectHarness); + + await select.open(); + + const options = await select.getOptions({ text: 'Mock List 01 (en)' }); + + expect(options.length).toEqual(1); + + await options[0].click(); + + // click delete button + const deleteButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.delete' })); + await deleteButton.click(); + + // get dialog harness + const dialogHarnesses = await rootLoader.getAllHarnesses(MatDialogHarness); + + expect(dialogHarnesses.length).toEqual(1); + + // click confirm button + const confirmButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.confirm-button' })); + + await confirmButton.click(); + + testHostFixture.whenStable().then(() => { + expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/mockList01'); + expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledTimes(1); + + expect(testHostComponent.listComponent.lists.length).toEqual(1); + expect(testHostComponent.listComponent.lists[0].id).toEqual('http://rdfh.ch/lists/0001/mockList02'); + + expect(testHostComponent.listComponent.listIri).toEqual(testHostComponent.listComponent.lists[0].id); + }); + }); }); - it('should delete a root node', async () => { - const deleteListResponse: DeleteListResponse = new DeleteListResponse(); - deleteListResponse.deleted = true; - deleteListResponse.iri = 'http://rdfh.ch/lists/0001/mockList01'; + describe('Delete list with no lists remaining after deletion', () => { + beforeEach(() => { + + // mock session service + const sessionSpy = TestBed.inject(SessionService); + + (sessionSpy as jasmine.SpyObj).getSession.and.callFake( + () => { + const session: Session = { + id: 12345, + user: { + name: 'username', + jwt: 'myToken', + lang: 'en', + sysAdmin: true, + projectAdmin: [] + } + }; - const listSpy = TestBed.inject(DspApiConnectionToken); + return session; + } + ); - // mock delete list root node response - (listSpy.admin.listsEndpoint as jasmine.SpyObj).deleteListNode.and.callFake( - () => { - const response = deleteListResponse; + // mock cache service + const cacheSpy = TestBed.inject(CacheService); - return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); - } - ); + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); - // mock the call to the API again because we call the API again after deletion to get the lists - // therefore the mock call in the BeforeEach method above will not work since it returns two lists - // after deletion, we should only have one list - (listSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( - () => { - const response = new ListsResponse(); + const mockProjects = MockProjects.mockProjects(); - response.lists = new Array(); + response.project = mockProjects.body.projects[0]; - const mockList2 = new ListNodeInfo(); - mockList2.comments = []; - mockList2.id = 'http://rdfh.ch/lists/0001/mockList02'; - mockList2.isRootNode = true; - mockList2.labels = [{ language: 'en', value: 'Mock List 02' }]; - mockList2.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + return of(response.project as ReadProject); + } + ); - response.lists.push(mockList2); + // mock router + const routerSpy = TestBed.inject(Router); - return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); - } - ); + (routerSpy as jasmine.SpyObj).navigate.and.stub(); + (routerSpy as jasmine.SpyObj).navigateByUrl.and.stub(); + + // mock lists endpoint + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( + () => { + const response = new ListsResponse(); + + response.lists = new Array(); + + const mockList1 = new ListNodeInfo(); + mockList1.comments = []; + mockList1.id = 'http://rdfh.ch/lists/0001/mockList01'; + mockList1.isRootNode = true; + mockList1.labels = [{ language: 'en', value: 'Mock List 01' }]; + mockList1.projectIri = 'http://rdfh.ch/projects/myProjectIri'; + + response.lists.push(mockList1); + + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + overlayContainer = TestBed.inject(OverlayContainer); + rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture); + + // mock projects endpoint + (dspConnSpy.admin.projectsEndpoint as jasmine.SpyObj).getProjectByShortcode.and.callFake( + () => { + const response = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; - // open first list among lists - const select = await rootLoader.getHarness(MatSelectHarness); + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + expect(testHostComponent.listComponent.session).toBeTruthy(); + expect(testHostComponent).toBeTruthy(); + }); + + it('should delete a root node and no list should be shown', async () => { + const deleteListResponse: DeleteListResponse = new DeleteListResponse(); + deleteListResponse.deleted = true; + deleteListResponse.iri = 'http://rdfh.ch/lists/0001/mockList01'; + + const listSpy = TestBed.inject(DspApiConnectionToken); + + // mock delete list root node response + (listSpy.admin.listsEndpoint as jasmine.SpyObj).deleteListNode.and.callFake( + () => { + const response = deleteListResponse; + + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); + + // mock the call to the API again because we call the API again after deletion to get the lists + // therefore the mock call in the BeforeEach method above will not work since it returns two lists + // after deletion, we should only have NO lists remaining + (listSpy.admin.listsEndpoint as jasmine.SpyObj).getListsInProject.and.callFake( + () => { + const response = new ListsResponse(); - await select.open(); + response.lists = new Array(); + + return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse)); + } + ); - const options = await select.getOptions({ text: 'Mock List 01 (en)' }); + // open first list among lists + const select = await rootLoader.getHarness(MatSelectHarness); - expect(options.length).toEqual(1); + await select.open(); - await options[0].click(); + const options = await select.getOptions({ text: 'Mock List 01 (en)' }); - // click delete button - const deleteButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.delete' })); - await deleteButton.click(); + expect(options.length).toEqual(1); - // get dialog harness - const dialogHarnesses = await rootLoader.getAllHarnesses(MatDialogHarness); + await options[0].click(); - expect(dialogHarnesses.length).toEqual(1); + // click delete button + const deleteButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.delete' })); + await deleteButton.click(); - // click confirm button - const confirmButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.confirm-button' })); + // get dialog harness + const dialogHarnesses = await rootLoader.getAllHarnesses(MatDialogHarness); - await confirmButton.click(); + expect(dialogHarnesses.length).toEqual(1); - testHostFixture.whenStable().then(() => { - expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/mockList01'); - expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledTimes(1); + // click confirm button + const confirmButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.confirm-button' })); - expect(testHostComponent.listComponent.lists.length).toEqual(1); - expect(testHostComponent.listComponent.lists[0].id).toEqual('http://rdfh.ch/lists/0001/mockList02'); + await confirmButton.click(); - expect(testHostComponent.listComponent.listIri).toEqual(testHostComponent.listComponent.lists[0].id); + testHostFixture.whenStable().then(() => { + expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/mockList01'); + expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledTimes(1); + + expect(testHostComponent.listComponent.lists.length).toEqual(0); + + expect(testHostComponent.listComponent.list).toBeNull(); + expect(testHostComponent.listComponent.listIri).toBeUndefined(); + }); }); }); + }); diff --git a/src/app/project/list/list.component.ts b/src/app/project/list/list.component.ts index 9dc7fa9fef..7d109045d8 100644 --- a/src/app/project/list/list.component.ts +++ b/src/app/project/list/list.component.ts @@ -235,10 +235,17 @@ export class ListComponent implements OnInit { this._dspApiConnection.admin.listsEndpoint.deleteListNode(this.listIri).subscribe( (res: ApiResponseData) => { this.lists = this.lists.filter(list => list.id !== res.body.iri); - this.listIri = this.lists[0].id; - this.listForm.controls.list.setValue(this.listIri); - this.openList(this.listIri); - this.initList(); + + // if there are still lists remaining after deleting a list, load the first list among lists + if (this.lists.length) { + this.listIri = this.lists[0].id; + this.listForm.controls.list.setValue(this.listIri); + this.openList(this.listIri); + this.initList(); + } else { // else set the list to null to remove it from the UI + this.list = null; + this.listIri = undefined; + } }, (error: ApiResponseError) => { // if DSP-API returns a 400, it is likely that the list node is in use so we inform the user of this