diff --git a/src/app/shared/components/contributors/contributors-table/contributors-table.component.spec.ts b/src/app/shared/components/contributors/contributors-table/contributors-table.component.spec.ts index f83741f0a..b3a4ccac0 100644 --- a/src/app/shared/components/contributors/contributors-table/contributors-table.component.spec.ts +++ b/src/app/shared/components/contributors/contributors-table/contributors-table.component.spec.ts @@ -1,4 +1,4 @@ -import { MockProvider } from 'ng-mocks'; +import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; @@ -13,36 +13,40 @@ import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog import { EducationHistoryDialogComponent } from '../../education-history-dialog/education-history-dialog.component'; import { EmploymentHistoryDialogComponent } from '../../employment-history-dialog/employment-history-dialog.component'; +import { IconComponent } from '../../icon/icon.component'; +import { InfoIconComponent } from '../../info-icon/info-icon.component'; +import { SelectComponent } from '../../select/select.component'; import { ContributorsTableComponent } from './contributors-table.component'; +const makeTableParams = (overrides: Partial = {}): TableParameters => ({ + rows: 10, + paginator: true, + scrollable: false, + rowsPerPageOptions: [10, 25, 50], + totalRecords: 4, + firstRowIndex: 10, + defaultSortOrder: null, + defaultSortColumn: null, + ...overrides, +}); + describe('ContributorsTableComponent', () => { let component: ContributorsTableComponent; let fixture: ComponentFixture; let mockCustomDialogService: ReturnType; - const tableParams: TableParameters = { - rows: 10, - paginator: true, - scrollable: false, - rowsPerPageOptions: [10, 25, 50], - totalRecords: 4, - firstRowIndex: 10, - defaultSortOrder: null, - defaultSortColumn: null, - }; - beforeEach(() => { mockCustomDialogService = CustomDialogServiceMockBuilder.create().build(); TestBed.configureTestingModule({ - imports: [ContributorsTableComponent], + imports: [ContributorsTableComponent, ...MockComponents(SelectComponent, IconComponent, InfoIconComponent)], providers: [provideOSFCore(), MockProvider(CustomDialogService, mockCustomDialogService)], }); fixture = TestBed.createComponent(ContributorsTableComponent); component = fixture.componentInstance; - fixture.componentRef.setInput('tableParams', tableParams); + fixture.componentRef.setInput('tableParams', makeTableParams()); fixture.detectChanges(); }); @@ -50,47 +54,54 @@ describe('ContributorsTableComponent', () => { expect(component).toBeTruthy(); }); - it('should compute isProject based on resourceType', () => { + it('should return true from isProject when resourceType is Project', () => { fixture.componentRef.setInput('resourceType', ResourceType.Project); fixture.detectChanges(); expect(component.isProject()).toBe(true); + }); + it('should return false from isProject when resourceType is Registration', () => { fixture.componentRef.setInput('resourceType', ResourceType.Registration); fixture.detectChanges(); expect(component.isProject()).toBe(false); }); - it('should compute deactivatedContributors when list contains deactivated contributor', () => { - const contributors: ContributorModel[] = [ + it('should return true from deactivatedContributors when at least one contributor is deactivated', () => { + fixture.componentRef.setInput('contributors', [ { ...MOCK_CONTRIBUTOR, id: '1', deactivated: false }, { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '2', deactivated: true }, - ]; - - component.contributors.set(contributors); - + ]); + fixture.detectChanges(); expect(component.deactivatedContributors()).toBe(true); }); - it('should compute showLoadMore when loaded contributors are below total records', () => { - component.contributors.set([{ ...MOCK_CONTRIBUTOR, id: '1' }]); - - expect(component.showLoadMore()).toBe(true); + it('should return false from deactivatedContributors when all contributors are active', () => { + fixture.componentRef.setInput('contributors', [ + { ...MOCK_CONTRIBUTOR, id: '1', deactivated: false }, + { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '2', deactivated: false }, + ]); + fixture.detectChanges(); + expect(component.deactivatedContributors()).toBe(false); }); - it('should compute showLoadMore as false when contributors length matches total records', () => { - const contributors: ContributorModel[] = [ - { ...MOCK_CONTRIBUTOR, id: '1' }, - { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '2' }, - { ...MOCK_CONTRIBUTOR, id: '3' }, - { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '4' }, - ]; - component.contributors.set(contributors); + it('should return false from deactivatedContributors when contributor list is empty', () => { + fixture.componentRef.setInput('contributors', []); + fixture.detectChanges(); + expect(component.deactivatedContributors()).toBe(false); + }); + it('should default showLoadMore to false', () => { expect(component.showLoadMore()).toBe(false); }); - it('should emit remove event when removeContributor is called', () => { - const contributor = { ...MOCK_CONTRIBUTOR, id: 'remove-id' }; + it('should reflect showLoadMore as true when set by parent', () => { + fixture.componentRef.setInput('showLoadMore', true); + fixture.detectChanges(); + expect(component.showLoadMore()).toBe(true); + }); + + it('should emit remove event with the given contributor when removeContributor is called', () => { + const contributor: ContributorModel = { ...MOCK_CONTRIBUTOR, id: 'remove-id' }; vi.spyOn(component.remove, 'emit'); component.removeContributor(contributor); @@ -106,7 +117,7 @@ describe('ContributorsTableComponent', () => { expect(component.loadMore.emit).toHaveBeenCalled(); }); - it('should open education history dialog with contributor education data', () => { + it('should open EducationHistoryDialogComponent with contributor education data', () => { const contributor: ContributorModel = { ...MOCK_CONTRIBUTOR, id: 'education-id', @@ -133,7 +144,19 @@ describe('ContributorsTableComponent', () => { }); }); - it('should open employment history dialog with contributor employment data', () => { + it('should open EducationHistoryDialogComponent with an empty education array', () => { + const contributor: ContributorModel = { ...MOCK_CONTRIBUTOR, id: 'no-education-id', education: [] }; + + component.openEducationHistory(contributor); + + expect(mockCustomDialogService.open).toHaveBeenCalledWith(EducationHistoryDialogComponent, { + header: 'project.contributors.table.headers.education', + width: '552px', + data: [], + }); + }); + + it('should open EmploymentHistoryDialogComponent with contributor employment data', () => { const contributor: ContributorModel = { ...MOCK_CONTRIBUTOR, id: 'employment-id', @@ -160,16 +183,42 @@ describe('ContributorsTableComponent', () => { }); }); - it('should reorder contributors indices using table firstRowIndex', () => { - const contributors: ContributorModel[] = [ + it('should open EmploymentHistoryDialogComponent with an empty employment array', () => { + const contributor: ContributorModel = { ...MOCK_CONTRIBUTOR, id: 'no-employment-id', employment: [] }; + + component.openEmploymentHistory(contributor); + + expect(mockCustomDialogService.open).toHaveBeenCalledWith(EmploymentHistoryDialogComponent, { + header: 'project.contributors.table.headers.employment', + width: '552px', + data: [], + }); + }); + + it('should reindex contributors starting from tableParams.firstRowIndex on row reorder', () => { + fixture.componentRef.setInput('tableParams', makeTableParams({ firstRowIndex: 10 })); + fixture.componentRef.setInput('contributors', [ { ...MOCK_CONTRIBUTOR, id: '1', index: 0 }, { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '2', index: 1 }, { ...MOCK_CONTRIBUTOR, id: '3', index: 2 }, - ]; - component.contributors.set(contributors); + ]); + fixture.detectChanges(); + + component.onRowReorder(); + + expect(component.contributors().map((c) => c.index)).toEqual([10, 11, 12]); + }); + + it('should reindex contributors from 0 when firstRowIndex is 0 on row reorder', () => { + fixture.componentRef.setInput('tableParams', makeTableParams({ firstRowIndex: 0 })); + fixture.componentRef.setInput('contributors', [ + { ...MOCK_CONTRIBUTOR, id: '1', index: 5 }, + { ...MOCK_CONTRIBUTOR_WITHOUT_HISTORY, id: '2', index: 6 }, + ]); + fixture.detectChanges(); component.onRowReorder(); - expect(component.contributors().map((item) => item.index)).toEqual([10, 11, 12]); + expect(component.contributors().map((c) => c.index)).toEqual([0, 1]); }); }); diff --git a/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts b/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts index d41244e28..800c35066 100644 --- a/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts +++ b/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts @@ -15,7 +15,6 @@ import { ContributorPermission } from '@osf/shared/enums/contributors/contributo import { ResourceType } from '@osf/shared/enums/resource-type.enum'; import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; import { ContributorModel } from '@shared/models/contributors/contributor.model'; -import { SelectOption } from '@shared/models/select-option.model'; import { TableParameters } from '@shared/models/table-parameters.model'; import { EducationHistoryDialogComponent } from '../../education-history-dialog/education-history-dialog.component'; @@ -61,10 +60,10 @@ export class ContributorsTableComponent { customDialogService = inject(CustomDialogService); - readonly permissionsOptions: SelectOption[] = PERMISSION_OPTIONS; + readonly permissionsOptions = PERMISSION_OPTIONS; readonly ContributorPermission = ContributorPermission; - skeletonData: ContributorModel[] = Array.from({ length: 3 }, () => ({}) as ContributorModel); + skeletonData = Array.from({ length: 3 }, () => ({}) as ContributorModel); isProject = computed(() => this.resourceType() === ResourceType.Project);