From 73826591b648d6fb5265b6327d58afeb7cecb356 Mon Sep 17 00:00:00 2001 From: Andriy Sheredko Date: Tue, 23 Dec 2025 16:51:52 +0200 Subject: [PATCH 1/5] feat(user-page): User page should display a link to the merger profile page as a redirect --- .../features/profile/profile.component.html | 10 +++ .../profile/profile.component.spec.ts | 67 +++++++++++++++++++ src/app/features/profile/profile.component.ts | 6 +- src/app/shared/mappers/user/user.mapper.ts | 1 + .../shared/models/user/user-json-api.model.ts | 1 + src/app/shared/models/user/user.models.ts | 1 + src/assets/i18n/en.json | 5 ++ src/testing/mocks/data.mock.ts | 1 + 8 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html index 7ebc21851..7a009aa26 100644 --- a/src/app/features/profile/profile.component.html +++ b/src/app/features/profile/profile.component.html @@ -2,6 +2,16 @@ } @else { @if (user()) { + @if (user()?.mergedBy) { + + + + {{ 'profile.mergedAccount.message' | translate }} + {{ user()?.mergedBy }} + + + + } @if (defaultSearchFiltersInitialized()) { diff --git a/src/app/features/profile/profile.component.spec.ts b/src/app/features/profile/profile.component.spec.ts index 451e2c6e2..1853dc2eb 100644 --- a/src/app/features/profile/profile.component.spec.ts +++ b/src/app/features/profile/profile.component.spec.ts @@ -1,6 +1,7 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { PrerenderReadyService } from '@core/services/prerender-ready.service'; @@ -13,6 +14,7 @@ import { ProfileInformationComponent } from './components'; import { ProfileComponent } from './profile.component'; import { ProfileSelectors } from './store'; +import { MOCK_USER } from '@testing/mocks/data.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -78,4 +80,69 @@ describe('ProfileComponent', () => { expect(component.resourceTabOptions).toBeDefined(); expect(component.resourceTabOptions.every((option) => option.value !== ResourceType.Agent)).toBe(true); }); + + describe('merged user message', () => { + it('should display merged message when user has mergedBy property', async () => { + const mergedUser = { ...MOCK_USER, mergedBy: 'https://osf.io/user123/' }; + + await TestBed.configureTestingModule({ + imports: [ + ProfileComponent, + OSFTestingModule, + ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent), + ], + providers: [ + MockProvider(Router, routerMock), + MockProvider(ActivatedRoute, activatedRouteMock), + MockProvider(PrerenderReadyService), + provideMockStore({ + signals: [ + { selector: UserSelectors.getCurrentUser, value: mergedUser }, + { selector: ProfileSelectors.getUserProfile, value: null }, + { selector: ProfileSelectors.isUserProfileLoading, value: false }, + ], + }), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ProfileComponent); + fixture.detectChanges(); + + const messageElement = fixture.debugElement.query(By.css('p-message')); + expect(messageElement).toBeTruthy(); + + const linkElement = fixture.debugElement.query(By.css('p-message a')); + expect(linkElement.nativeElement.href).toContain('https://osf.io/user123/'); + }); + + it('should not display merged message when user does not have mergedBy property', async () => { + const normalUser = { ...MOCK_USER, mergedBy: undefined }; + + await TestBed.configureTestingModule({ + imports: [ + ProfileComponent, + OSFTestingModule, + ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent), + ], + providers: [ + MockProvider(Router, routerMock), + MockProvider(ActivatedRoute, activatedRouteMock), + MockProvider(PrerenderReadyService), + provideMockStore({ + signals: [ + { selector: UserSelectors.getCurrentUser, value: normalUser }, + { selector: ProfileSelectors.getUserProfile, value: null }, + { selector: ProfileSelectors.isUserProfileLoading, value: false }, + ], + }), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ProfileComponent); + fixture.detectChanges(); + + const messageElement = fixture.debugElement.query(By.css('p-message')); + expect(messageElement).toBeFalsy(); + }); + }); }); diff --git a/src/app/features/profile/profile.component.ts b/src/app/features/profile/profile.component.ts index 9aac4d808..fb7c186a8 100644 --- a/src/app/features/profile/profile.component.ts +++ b/src/app/features/profile/profile.component.ts @@ -1,5 +1,9 @@ import { createDispatchMap, select } from '@ngxs/store'; +import { TranslatePipe } from '@ngx-translate/core'; + +import { Message } from 'primeng/message'; + import { ChangeDetectionStrategy, Component, @@ -30,7 +34,7 @@ import { FetchUserProfile, ProfileSelectors, SetUserProfile } from './store'; templateUrl: './profile.component.html', styleUrl: './profile.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent], + imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent, Message, TranslatePipe], }) export class ProfileComponent implements OnInit, OnDestroy { private router = inject(Router); diff --git a/src/app/shared/mappers/user/user.mapper.ts b/src/app/shared/mappers/user/user.mapper.ts index 50b7b8b71..e55a1faac 100644 --- a/src/app/shared/mappers/user/user.mapper.ts +++ b/src/app/shared/mappers/user/user.mapper.ts @@ -36,6 +36,7 @@ export class UserMapper { canViewReviews: user.attributes.can_view_reviews === true, // [NS] Do not simplify it timezone: user.attributes.timezone, locale: user.attributes.locale, + mergedBy: user.links.merged_by, }; } diff --git a/src/app/shared/models/user/user-json-api.model.ts b/src/app/shared/models/user/user-json-api.model.ts index fd2fc083f..07ce651e0 100644 --- a/src/app/shared/models/user/user-json-api.model.ts +++ b/src/app/shared/models/user/user-json-api.model.ts @@ -70,6 +70,7 @@ interface UserLinksJsonApi { iri: string; profile_image: string; self: string; + merged_by?: string; } interface UserRelationshipsJsonApi { diff --git a/src/app/shared/models/user/user.models.ts b/src/app/shared/models/user/user.models.ts index 2eadb1721..22ee27f4e 100644 --- a/src/app/shared/models/user/user.models.ts +++ b/src/app/shared/models/user/user.models.ts @@ -27,4 +27,5 @@ export interface UserModel { defaultRegionId: string; link?: string; iri?: string; + mergedBy?: string; } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 4e1eafd6c..0386324d2 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -227,6 +227,11 @@ "institutions": "Institutions", "recentActivity": "Recent Activity" }, + "profile": { + "mergedAccount": { + "message": "This account has been merged with " + } + }, "toast": { "tos-consent": { "message": "Notice: We've updated our", diff --git a/src/testing/mocks/data.mock.ts b/src/testing/mocks/data.mock.ts index 7a1fd3c37..6acecbf35 100644 --- a/src/testing/mocks/data.mock.ts +++ b/src/testing/mocks/data.mock.ts @@ -57,6 +57,7 @@ export const MOCK_USER: UserModel = { defaultRegionId: 'us', allowIndexing: true, canViewReviews: true, + mergedBy: undefined, }; export const MOCK_USER_RELATED_COUNTS: UserRelatedCounts = { From 6984eaaf34519dac8ffe9637d7ed03fe16b4ec09 Mon Sep 17 00:00:00 2001 From: Andriy Sheredko Date: Tue, 6 Jan 2026 15:27:49 +0200 Subject: [PATCH 2/5] feat(user-page): Fix banner styling --- .../profile-information.component.html | 2 +- .../features/profile/profile.component.html | 24 ++++++++++--------- src/styles/overrides/message.scss | 4 ++++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/features/profile/components/profile-information/profile-information.component.html b/src/app/features/profile/components/profile-information/profile-information.component.html index 9bd69e20b..fb23fca21 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.html +++ b/src/app/features/profile/components/profile-information/profile-information.component.html @@ -1,4 +1,4 @@ -
+

{{ currentUser()?.fullName }}

diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html index 7a009aa26..ae4fc7d72 100644 --- a/src/app/features/profile/profile.component.html +++ b/src/app/features/profile/profile.component.html @@ -2,17 +2,19 @@ } @else { @if (user()) { - @if (user()?.mergedBy) { - - - - {{ 'profile.mergedAccount.message' | translate }} - {{ user()?.mergedBy }} - - - - } - +
+ @if (user()?.mergedBy) { + + + + {{ 'profile.mergedAccount.message' | translate }} + {{ user()?.mergedBy }} + + + + } + +
@if (defaultSearchFiltersInitialized()) {
diff --git a/src/styles/overrides/message.scss b/src/styles/overrides/message.scss index ddbe2b92d..d82b70a08 100644 --- a/src/styles/overrides/message.scss +++ b/src/styles/overrides/message.scss @@ -18,6 +18,10 @@ --p-message-success-color: var(--green-1); --p-message-success-background: var(--green-2); --p-message-success-border-color: var(--green-2); + + --p-message-info-color: var(--dark-blue-1); + --p-message-info-background: var(--bg-blue-2); + --p-message-info-border-color: var(--bg-blue-2); } .p-message-simple { From 2ec40c23bbb0a8fa5cb2b7d05c2c2924d347fd63 Mon Sep 17 00:00:00 2001 From: Andriy Sheredko Date: Tue, 6 Jan 2026 17:19:02 +0200 Subject: [PATCH 3/5] feat(user-page): Fix banner styling --- src/styles/overrides/message.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/styles/overrides/message.scss b/src/styles/overrides/message.scss index d82b70a08..714eea1a7 100644 --- a/src/styles/overrides/message.scss +++ b/src/styles/overrides/message.scss @@ -18,10 +18,6 @@ --p-message-success-color: var(--green-1); --p-message-success-background: var(--green-2); --p-message-success-border-color: var(--green-2); - - --p-message-info-color: var(--dark-blue-1); - --p-message-info-background: var(--bg-blue-2); - --p-message-info-border-color: var(--bg-blue-2); } .p-message-simple { @@ -32,4 +28,10 @@ .p-message { width: 100%; } + + &.p-message { + --p-message-info-color: var(--dark-blue-1); + --p-message-info-background: var(--bg-blue-2); + --p-message-info-border-color: var(--bg-blue-2); + } } From c5f8bfebd91ab39c6d5f54fc9d87b478212440f2 Mon Sep 17 00:00:00 2001 From: Andriy Sheredko Date: Tue, 6 Jan 2026 18:45:01 +0200 Subject: [PATCH 4/5] feat(user-page): Fix banner styling --- .../profile/profile.component.spec.ts | 67 ------------------- src/styles/overrides/message.scss | 6 -- 2 files changed, 73 deletions(-) diff --git a/src/app/features/profile/profile.component.spec.ts b/src/app/features/profile/profile.component.spec.ts index 1853dc2eb..451e2c6e2 100644 --- a/src/app/features/profile/profile.component.spec.ts +++ b/src/app/features/profile/profile.component.spec.ts @@ -1,7 +1,6 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { PrerenderReadyService } from '@core/services/prerender-ready.service'; @@ -14,7 +13,6 @@ import { ProfileInformationComponent } from './components'; import { ProfileComponent } from './profile.component'; import { ProfileSelectors } from './store'; -import { MOCK_USER } from '@testing/mocks/data.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -80,69 +78,4 @@ describe('ProfileComponent', () => { expect(component.resourceTabOptions).toBeDefined(); expect(component.resourceTabOptions.every((option) => option.value !== ResourceType.Agent)).toBe(true); }); - - describe('merged user message', () => { - it('should display merged message when user has mergedBy property', async () => { - const mergedUser = { ...MOCK_USER, mergedBy: 'https://osf.io/user123/' }; - - await TestBed.configureTestingModule({ - imports: [ - ProfileComponent, - OSFTestingModule, - ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent), - ], - providers: [ - MockProvider(Router, routerMock), - MockProvider(ActivatedRoute, activatedRouteMock), - MockProvider(PrerenderReadyService), - provideMockStore({ - signals: [ - { selector: UserSelectors.getCurrentUser, value: mergedUser }, - { selector: ProfileSelectors.getUserProfile, value: null }, - { selector: ProfileSelectors.isUserProfileLoading, value: false }, - ], - }), - ], - }).compileComponents(); - - fixture = TestBed.createComponent(ProfileComponent); - fixture.detectChanges(); - - const messageElement = fixture.debugElement.query(By.css('p-message')); - expect(messageElement).toBeTruthy(); - - const linkElement = fixture.debugElement.query(By.css('p-message a')); - expect(linkElement.nativeElement.href).toContain('https://osf.io/user123/'); - }); - - it('should not display merged message when user does not have mergedBy property', async () => { - const normalUser = { ...MOCK_USER, mergedBy: undefined }; - - await TestBed.configureTestingModule({ - imports: [ - ProfileComponent, - OSFTestingModule, - ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent), - ], - providers: [ - MockProvider(Router, routerMock), - MockProvider(ActivatedRoute, activatedRouteMock), - MockProvider(PrerenderReadyService), - provideMockStore({ - signals: [ - { selector: UserSelectors.getCurrentUser, value: normalUser }, - { selector: ProfileSelectors.getUserProfile, value: null }, - { selector: ProfileSelectors.isUserProfileLoading, value: false }, - ], - }), - ], - }).compileComponents(); - - fixture = TestBed.createComponent(ProfileComponent); - fixture.detectChanges(); - - const messageElement = fixture.debugElement.query(By.css('p-message')); - expect(messageElement).toBeFalsy(); - }); - }); }); diff --git a/src/styles/overrides/message.scss b/src/styles/overrides/message.scss index 714eea1a7..ddbe2b92d 100644 --- a/src/styles/overrides/message.scss +++ b/src/styles/overrides/message.scss @@ -28,10 +28,4 @@ .p-message { width: 100%; } - - &.p-message { - --p-message-info-color: var(--dark-blue-1); - --p-message-info-background: var(--bg-blue-2); - --p-message-info-border-color: var(--bg-blue-2); - } } From 4964725f141ee5a4d5c82e4ac8bfa0fb825ba17c Mon Sep 17 00:00:00 2001 From: Andriy Sheredko Date: Tue, 6 Jan 2026 19:03:56 +0200 Subject: [PATCH 5/5] feat(user-page): Fix banner styling --- src/app/features/profile/profile.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html index ae4fc7d72..4176e33fc 100644 --- a/src/app/features/profile/profile.component.html +++ b/src/app/features/profile/profile.component.html @@ -2,9 +2,9 @@ } @else { @if (user()) { -
+
@if (user()?.mergedBy) { - + {{ 'profile.mergedAccount.message' | translate }}