Skip to content
Merged

PD-5538 #2840

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { TestBed } from '@angular/core/testing'
import { take } from 'rxjs/operators'
import { getUserRecord } from '../record/record.service.spec'
import { RecordHeaderStateService } from './record-header-state.service'

describe('RecordHeaderStateService', () => {
let service: RecordHeaderStateService

beforeEach(() => {
TestBed.configureTestingModule({})
service = TestBed.inject(RecordHeaderStateService)
service.reset()
})

it('should emit header loading separately from full record loading', (done) => {
service.setLoadingUserRecord(true)
service.setLoadingRecordHeader(false)

service.loadingRecordHeader$.pipe(take(1)).subscribe((loading) => {
expect(loading).toBeFalse()
done()
})
})

it('should share the latest user record with header consumers', (done) => {
const userRecord = getUserRecord()

service.setUserRecord(userRecord)

service.userRecord$.pipe(take(1)).subscribe((value) => {
expect(value).toBe(userRecord)
done()
})
})

it('should reset header-specific state', (done) => {
service.setLoadingRecordHeader(false)
service.setUserRecord(getUserRecord())

service.reset()

service.loadingRecordHeader$.pipe(take(1)).subscribe((loading) => {
expect(loading).toBeTrue()
service.userRecord$.pipe(take(1)).subscribe((userRecord) => {
expect(userRecord).toBeNull()
done()
})
})
})
})
13 changes: 13 additions & 0 deletions src/app/core/record-header-state/record-header-state.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'
import { UserRecord } from 'src/app/types/record.local'

/**
* Shares state required by the Record Header between My ORCID and the Header.
Expand All @@ -8,7 +9,9 @@ import { BehaviorSubject } from 'rxjs'
@Injectable({ providedIn: 'root' })
export class RecordHeaderStateService {
private readonly _loadingUserRecord = new BehaviorSubject<boolean>(true)
private readonly _loadingRecordHeader = new BehaviorSubject<boolean>(true)
private readonly _isPublicRecord = new BehaviorSubject<string | null>(null)
private readonly _userRecord = new BehaviorSubject<UserRecord | null>(null)
private readonly _affiliations = new BehaviorSubject<number>(0)
private readonly _displaySideBar = new BehaviorSubject<boolean>(false)
private readonly _displayBiography = new BehaviorSubject<boolean>(false)
Expand All @@ -17,7 +20,9 @@ export class RecordHeaderStateService {
private readonly _hasCreditOrOtherNames = new BehaviorSubject<boolean>(false)

readonly loadingUserRecord$ = this._loadingUserRecord.asObservable()
readonly loadingRecordHeader$ = this._loadingRecordHeader.asObservable()
readonly isPublicRecord$ = this._isPublicRecord.asObservable()
readonly userRecord$ = this._userRecord.asObservable()
readonly affiliations$ = this._affiliations.asObservable()
readonly displaySideBar$ = this._displaySideBar.asObservable()
readonly displayBiography$ = this._displayBiography.asObservable()
Expand All @@ -29,9 +34,15 @@ export class RecordHeaderStateService {
setLoadingUserRecord(val: boolean) {
this._loadingUserRecord.next(val)
}
setLoadingRecordHeader(val: boolean) {
this._loadingRecordHeader.next(val)
}
setIsPublicRecord(val: string | null) {
this._isPublicRecord.next(val)
}
setUserRecord(val: UserRecord | null) {
this._userRecord.next(val)
}
setAffiliations(val: number) {
this._affiliations.next(val)
}
Expand All @@ -53,7 +64,9 @@ export class RecordHeaderStateService {

reset() {
this._loadingUserRecord.next(true)
this._loadingRecordHeader.next(true)
this._isPublicRecord.next(null)
this._userRecord.next(null)
this._affiliations.next(0)
this._displaySideBar.next(false)
this._displayBiography.next(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
Affiliation,
AffiliationUIGroup,
} from 'src/app/types/record-affiliation.endpoint'

export function getFeaturedEmploymentCaption(
affiliations?: AffiliationUIGroup[]
): string {
if (!affiliations || affiliations.length === 0) {
return ''
}

const employmentGroup = affiliations.find((group) => group.type === 'EMPLOYMENT')

if (!employmentGroup || !employmentGroup.affiliationGroup) {
return ''
}

for (const group of employmentGroup.affiliationGroup) {
if (!group.affiliations) {
continue
}

const featuredAffiliation = group.affiliations.find(
(affiliation) =>
affiliation.featured === true &&
affiliation.affiliationType?.value === 'employment'
)

if (featuredAffiliation) {
return formatAffiliationCaption(featuredAffiliation)
}
}

return ''
}

function formatAffiliationCaption(affiliation: Affiliation): string {
const parts: string[] = []

const orgName = affiliation.affiliationName?.value
if (orgName) {
parts.push(orgName)
}

const locationParts: string[] = []
if (affiliation.city?.value) {
locationParts.push(affiliation.city.value)
}
if (affiliation.region?.value) {
locationParts.push(affiliation.region.value)
}
if (affiliation.countryForDisplay) {
locationParts.push(affiliation.countryForDisplay)
} else if (affiliation.country?.value) {
locationParts.push(affiliation.country.value)
}

if (orgName && locationParts.length > 0) {
parts[0] = `${orgName}: ${locationParts.join(', ')}`
} else if (locationParts.length > 0) {
parts.push(locationParts.join(', '))
}

const roleParts: string[] = []
if (affiliation.roleTitle?.value) {
roleParts.push(affiliation.roleTitle.value)
}
if (affiliation.departmentName?.value) {
roleParts.push(affiliation.departmentName.value)
}

if (roleParts.length > 0) {
if (parts.length > 0) {
parts.push(`- ${roleParts.join(', ')}`)
} else {
parts.push(roleParts.join(', '))
}
}

return parts.join(' ')
}
124 changes: 107 additions & 17 deletions src/app/record/components/record-header/record-header.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,80 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { RecordHeaderComponent } from './record-header.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { WINDOW_PROVIDERS } from 'src/app/cdk/window'
import { PlatformInfoService } from 'src/app/cdk/platform-info'
import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service'
import { SnackbarService } from 'src/app/cdk/snackbar/snackbar.service'
import { MatSnackBar } from '@angular/material/snack-bar'
import { MatDialog } from '@angular/material/dialog'
import { Overlay } from '@angular/cdk/overlay'
import { RouterTestingModule } from '@angular/router/testing'
import { RecordService } from 'src/app/core/record/record.service'

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { MatTooltipModule } from '@angular/material/tooltip'
import { of } from 'rxjs'
import { HeaderCompactService } from 'src/app/core/header-compact/header-compact.service'
import { RecordHeaderStateService } from 'src/app/core/record-header-state/record-header-state.service'
import { TogglzService } from 'src/app/core/togglz/togglz.service'
import { UserService } from 'src/app/core'
import { getUserRecord } from 'src/app/core/record/record.service.spec'
import { getUserSession } from 'src/app/core/user/user.service.spec'
import { NoopAnimationsModule } from '@angular/platform-browser/animations'
import { RumJourneyEventService } from 'src/app/rum/service/customEvent.service'
import { TogglzFlag } from 'src/app/types/config.endpoint'
import { AffiliationType } from 'src/app/types/record-affiliation.endpoint'

describe('RecordHeaderComponent', () => {
let component: RecordHeaderComponent
let fixture: ComponentFixture<RecordHeaderComponent>
let state: RecordHeaderStateService
let recordService: jasmine.SpyObj<RecordService>
let togglzService: { getStateOf: jasmine.Spy }

beforeEach(async () => {
recordService = jasmine.createSpyObj<RecordService>('RecordService', [
'getRecord',
])
togglzService = {
getStateOf: jasmine.createSpy('getStateOf').and.callFake((flag: string) =>
of(flag === TogglzFlag.FEATURED_AFFILIATIONS)
),
}

await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
RouterTestingModule,
NoopAnimationsModule,
MatTooltipModule,
RecordHeaderComponent,
],
providers: [
WINDOW_PROVIDERS,
RecordService,
PlatformInfoService,
ErrorHandlerService,
SnackbarService,
MatSnackBar,
MatDialog,
Overlay,
RecordHeaderStateService,
{ provide: RecordService, useValue: recordService },
{
provide: PlatformInfoService,
useValue: { get: () => of({ columns12: true }) },
},
{
provide: HeaderCompactService,
useValue: { compactActive$: of(false) },
},
{
provide: TogglzService,
useValue: togglzService,
},
{
provide: UserService,
useValue: { getUserSession: () => of(getUserSession()) },
},
{
provide: RumJourneyEventService,
useValue: {
recordSimpleEvent: jasmine.createSpy('recordSimpleEvent'),
},
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents()
})

beforeEach(() => {
state = TestBed.inject(RecordHeaderStateService)
state.reset()
fixture = TestBed.createComponent(RecordHeaderComponent)
component = fixture.componentInstance
fixture.detectChanges()
Expand All @@ -50,4 +83,61 @@ describe('RecordHeaderComponent', () => {
it('should create', () => {
expect(component).toBeTruthy()
})

it('should render header data from shared record state', () => {
const userRecord = getUserRecord()
const orcid = userRecord.userInfo.REAL_USER_ORCID

state.setIsPublicRecord(orcid)
state.setLoadingRecordHeader(false)
state.setUserRecord(userRecord)
fixture.detectChanges()

const text = fixture.nativeElement.textContent
expect(component.bannerTitle).toBe('Published Name')
expect(text).toContain('Published Name')
expect(text).toContain(`https:${runtimeEnvironment.BASE_URL}${orcid}`)
})

it('should not ask RecordService to load record data for the header', () => {
state.setIsPublicRecord(getUserRecord().userInfo.REAL_USER_ORCID)
state.setLoadingRecordHeader(false)
state.setUserRecord(getUserRecord())
fixture.detectChanges()

expect(recordService.getRecord).not.toHaveBeenCalled()
})

it('should keep featured employment caption non-blocking', () => {
const userRecord = getUserRecord()

state.setIsPublicRecord(userRecord.userInfo.REAL_USER_ORCID)
state.setLoadingRecordHeader(false)
state.setUserRecord({ ...userRecord, affiliations: undefined })
fixture.detectChanges()

expect(component.loadingUserRecord).toBeFalse()
expect(component.bannerTitle).toBe('Published Name')
expect(component.bannerCaption).toBe('')
})

it('should render the featured employment caption from shared state', () => {
const userRecord = getUserRecord()
const featuredAffiliation =
userRecord.affiliations[0].affiliationGroup[0].affiliations[0]

featuredAffiliation.featured = true
featuredAffiliation.affiliationType = { value: AffiliationType.employment }
featuredAffiliation.roleTitle = { value: 'Engineer' }
featuredAffiliation.departmentName = { value: 'Platform' }

state.setIsPublicRecord(userRecord.userInfo.REAL_USER_ORCID)
state.setLoadingRecordHeader(false)
state.setUserRecord(userRecord)
fixture.detectChanges()

expect(component.bannerCaption).toBe(
'ORCID: city, region, country - Engineer, Platform'
)
})
})
Loading
Loading