From fa646b2820fb2a0fa6b0e6428fbc52154345a52b Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 27 Aug 2025 15:05:36 +0300 Subject: [PATCH 01/21] Show Osf introduction video and Collections,Institutions, Registries, Preprints url banners if user have not created any project for home (/dashboard) tab --- .../pages/dashboard/dashboard.component.html | 181 +++++++++++++----- .../pages/dashboard/dashboard.component.scss | 17 ++ ...tions-c66e5d4408313e47ec3a7d0912d8b45d.png | Bin 0 -> 14433 bytes ...tions-3980d88f66513f667da557bfdc8401d5.png | Bin 0 -> 13268 bytes ...rints-d4831812809138161920fe6f3ba277eb.png | Bin 0 -> 11892 bytes ...tries-948f7d55ba7ca8585e25d020a26a7f59.png | Bin 0 -> 12953 bytes 6 files changed, 153 insertions(+), 45 deletions(-) create mode 100644 src/assets/images/dashboard/products/osf-collections-c66e5d4408313e47ec3a7d0912d8b45d.png create mode 100644 src/assets/images/dashboard/products/osf-institutions-3980d88f66513f667da557bfdc8401d5.png create mode 100644 src/assets/images/dashboard/products/osf-preprints-d4831812809138161920fe6f3ba277eb.png create mode 100644 src/assets/images/dashboard/products/osf-registries-948f7d55ba7ca8585e25d020a26a7f59.png diff --git a/src/app/features/home/pages/dashboard/dashboard.component.html b/src/app/features/home/pages/dashboard/dashboard.component.html index d732f630d..798ae2d9e 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.html +++ b/src/app/features/home/pages/dashboard/dashboard.component.html @@ -7,55 +7,146 @@ (buttonClick)="createProject()" /> -
-

- {{ 'home.loggedIn.dashboard.quickSearch.goTo' | translate }} - - {{ 'home.loggedIn.dashboard.quickSearch.myProjects' | translate }} - - {{ 'home.loggedIn.dashboard.quickSearch.toOrganize' | translate }} - - {{ 'home.loggedIn.dashboard.quickSearch.search' | translate }} - - {{ 'home.loggedIn.dashboard.quickSearch.osf' | translate }} -

- - -
- -
- -

{{ 'home.loggedIn.publicProjects.title' | translate }}

-
- -
+ + @if (filteredProjects().length) {
-

{{ 'home.loggedIn.latestResearch.title' | translate }}

-

{{ 'home.loggedIn.latestResearch.subtitle' | translate }}

+
+

+ {{ 'home.loggedIn.dashboard.quickSearch.goTo' | translate }} + + {{ 'home.loggedIn.dashboard.quickSearch.myProjects' | translate }} + + {{ 'home.loggedIn.dashboard.quickSearch.toOrganize' | translate }} + + {{ 'home.loggedIn.dashboard.quickSearch.search' | translate }} + + {{ 'home.loggedIn.dashboard.quickSearch.osf' | translate }} +

+ + +
+ +
+ +

{{ 'home.loggedIn.publicProjects.title' | translate }}

+
+ +
+
+

{{ 'home.loggedIn.latestResearch.title' | translate }}

+

{{ 'home.loggedIn.latestResearch.subtitle' | translate }}

+
+ + +
+ +
+
+

{{ 'home.loggedIn.hosting.title' | translate }}

+

{{ 'home.loggedIn.hosting.subtitle' | translate }}

+
+ + +
+ } @else { +
+
+

+ You haven’t created a project yet. Click the + "Create New Project" button above to get started. +

+ +

+ Watch the short video below or visit the OSF Get Started help guides to learn more. +

- -
+ +
+ +
-
-
-

{{ 'home.loggedIn.hosting.title' | translate }}

-

{{ 'home.loggedIn.hosting.subtitle' | translate }}

+ + + + +
- -
+ + } + + diff --git a/src/app/features/home/pages/dashboard/dashboard.component.scss b/src/app/features/home/pages/dashboard/dashboard.component.scss index db6bc077a..92abf60e0 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.scss +++ b/src/app/features/home/pages/dashboard/dashboard.component.scss @@ -8,6 +8,19 @@ max-width: 100%; } +.w-100 { + width: 100% !important; +} + +.w-20 { + width: 20% !important; +} + +.justify-center { + display: flex; + justify-content: center; +} + .quick-search-container { background-color: var(--white); @@ -27,3 +40,7 @@ .hosting-container { background-color: var(--bg-blue-2); } + +.col { + flex-direction: column; +} diff --git a/src/assets/images/dashboard/products/osf-collections-c66e5d4408313e47ec3a7d0912d8b45d.png b/src/assets/images/dashboard/products/osf-collections-c66e5d4408313e47ec3a7d0912d8b45d.png new file mode 100644 index 0000000000000000000000000000000000000000..c85939d6ef8834c37034a771712b5919abbeaabe GIT binary patch literal 14433 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/dashboard/products/osf-preprints-d4831812809138161920fe6f3ba277eb.png b/src/assets/images/dashboard/products/osf-preprints-d4831812809138161920fe6f3ba277eb.png new file mode 100644 index 0000000000000000000000000000000000000000..887a29d709c0b73a9ba2c0685916a412e8f100c6 GIT binary patch literal 11892 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/dashboard/products/osf-registries-948f7d55ba7ca8585e25d020a26a7f59.png b/src/assets/images/dashboard/products/osf-registries-948f7d55ba7ca8585e25d020a26a7f59.png new file mode 100644 index 0000000000000000000000000000000000000000..e3175dac265faaa6b79f6040b8c007dc2bf6afba GIT binary patch literal 12953 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 I3mXRiKWWx&egFUf literal 0 HcmV?d00001 From deb6fda87600b1635b814441eede2a5175e04e0e Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 27 Aug 2025 17:12:42 +0300 Subject: [PATCH 02/21] 1. add translations 2. add loading ( ) - otherwise interface show video and afterward search (shown in details in github PR 301 video) 3. maybe there is better solution for existsProjects() || cd angular-osfsearchControl?.value?.length to check if user has at least one project --- .../pages/dashboard/dashboard.component.html | 50 ++++++++++--------- .../pages/dashboard/dashboard.component.scss | 17 ------- .../pages/dashboard/dashboard.component.ts | 20 +++++++- src/assets/i18n/en.json | 4 ++ 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.html b/src/app/features/home/pages/dashboard/dashboard.component.html index 798ae2d9e..d69799b56 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.html +++ b/src/app/features/home/pages/dashboard/dashboard.component.html @@ -1,14 +1,14 @@
- } + @else { + @if (existsProjects() || !!searchControl?.value?.length) { + - - - @if (filteredProjects().length) { + />

@@ -63,19 +63,20 @@

{{ 'home.loggedIn.hosting.title' | translate }}

} @else { -
-
-

- You haven’t created a project yet. Click the - "Create New Project" button above to get started. -

+ +
+
+

{{ 'home.loggedIn.dashboard.noCreatedProject' | translate }}

-

- Watch the short video below or visit the OSF Get Started help guides to learn more. -

+

{{ 'home.loggedIn.dashboard.watchVideoBelow' | translate }}

- -
+
- - - +
+ +
-
diff --git a/src/app/features/home/pages/dashboard/dashboard.component.scss b/src/app/features/home/pages/dashboard/dashboard.component.scss index 92abf60e0..db6bc077a 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.scss +++ b/src/app/features/home/pages/dashboard/dashboard.component.scss @@ -8,19 +8,6 @@ max-width: 100%; } -.w-100 { - width: 100% !important; -} - -.w-20 { - width: 20% !important; -} - -.justify-center { - display: flex; - justify-content: center; -} - .quick-search-container { background-color: var(--white); @@ -40,7 +27,3 @@ .hosting-container { background-color: var(--bg-blue-2); } - -.col { - flex-direction: column; -} diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index 2c9fd708b..cfc1adde3 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -16,7 +16,12 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { CreateProjectDialogComponent } from '@osf/features/my-projects/components'; import { AccountSettingsService } from '@osf/features/settings/account-settings/services'; -import { IconComponent, MyProjectsTableComponent, SubHeaderComponent } from '@osf/shared/components'; +import { + IconComponent, + LoadingSpinnerComponent, + MyProjectsTableComponent, + SubHeaderComponent +} from '@osf/shared/components'; import { MY_PROJECTS_TABLE_PARAMS } from '@osf/shared/constants'; import { SortOrder } from '@osf/shared/enums'; import { IS_MEDIUM } from '@osf/shared/helpers'; @@ -27,7 +32,7 @@ import { ConfirmEmailComponent } from '../../components'; @Component({ selector: 'osf-dashboard', - imports: [RouterLink, Button, SubHeaderComponent, MyProjectsTableComponent, IconComponent, TranslatePipe], + imports: [RouterLink, Button, SubHeaderComponent, MyProjectsTableComponent, IconComponent, TranslatePipe, LoadingSpinnerComponent], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss', providers: [DialogService], @@ -56,12 +61,19 @@ export class DashboardComponent implements OnInit { protected readonly projects = select(MyResourcesSelectors.getProjects); protected readonly totalProjectsCount = select(MyResourcesSelectors.getTotalProjects); + protected readonly isProjectsLoading = select(MyResourcesSelectors.getProjectsLoading); + protected readonly isInitialLoading = false; + protected readonly filteredProjects = computed(() => { const search = this.searchControl.value?.toLowerCase() ?? ''; return this.projects().filter((project) => project.title.toLowerCase().includes(search)); }); + protected readonly existsProjects = computed(() => { + return this.projects().length; + }); + dialogRef: DynamicDialogRef | null = null; emailAddress = ''; @@ -241,4 +253,8 @@ export class DashboardComponent implements OnInit { this.isSubmitting.set(false); }); } + + protected openInfoLink(): void { + window.open('https://help.osf.io/', '_blank'); + } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index c217e0403..e9ae8372d 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -240,7 +240,11 @@ "loggedIn": { "dashboard": { "title": "Dashboard", + "welcome": "Welcome to OSF!", "createProject": "Create New Project", + "noCreatedProject": "You haven’t created a project yet. Click the \"Create New Project\" button above to get started.", + "watchVideoBelow": "Watch the short video below or visit the OSF Get Started help guides to learn more.", + "getStartedHelp": "Visit Get Started Help Guides", "quickSearch": { "goTo": "Go to", "myProjects": "My Projects", From 817fcde67bd5854955bc1861379d63f59504b05f Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 27 Aug 2025 18:23:45 +0300 Subject: [PATCH 03/21] align footer content left --- src/app/core/components/footer/footer.component.html | 12 +++++++++--- .../home/pages/dashboard/dashboard.component.html | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/core/components/footer/footer.component.html b/src/app/core/components/footer/footer.component.html index b11f9dd2b..7c29bd02a 100644 --- a/src/app/core/components/footer/footer.component.html +++ b/src/app/core/components/footer/footer.component.html @@ -1,8 +1,14 @@
-
+
Date: Wed, 27 Aug 2025 19:41:29 +0300 Subject: [PATCH 06/21] update footer formatting --- src/app/core/components/footer/footer.component.html | 6 ++++-- .../home/pages/dashboard/dashboard.component.html | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/core/components/footer/footer.component.html b/src/app/core/components/footer/footer.component.html index 7c29bd02a..91148acac 100644 --- a/src/app/core/components/footer/footer.component.html +++ b/src/app/core/components/footer/footer.component.html @@ -2,11 +2,13 @@ } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 07144e175..31cbed0dc 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -245,6 +245,12 @@ "noCreatedProject": "You haven’t created a project yet. Click the \"Create New Project\" button above to get started.", "watchVideoBelow": "Watch the short video below or visit the OSF Get Started help guides to learn more.", "getStartedHelp": "Visit Get Started Help Guides", + "images": { + "osfCollectionsImageAltText": "OSF Collections", + "osfInstitutionsImageAltText": "OSF Institutions", + "osfRegistriesImageAltTest": "OSF Registries", + "osfPreprintsImageAltTest": "OSF Preprints" + }, "quickSearch": { "goTo": "Go to", "myProjects": "My Projects", From 78b2b2369d7e34024e526a6909ffd1bb04445d89 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 29 Aug 2025 19:38:03 +0300 Subject: [PATCH 14/21] add test for openInfoLink() --- .../home/pages/dashboard/dashboard.component.spec.ts | 7 +++++++ .../features/home/pages/dashboard/dashboard.component.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 7ae3603c6..73fe349ff 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -75,4 +75,11 @@ describe('DashboardComponent', () => { const welcomeText = fixture.debugElement.nativeElement.textContent; expect(welcomeText).toContain('home.loggedIn.dashboard.noCreatedProject'); }); + + it('should open OSF help link in new tab when openInfoLink is called', () => { + const spy = jest.spyOn(window, 'open').mockImplementation(() => null); + component.openInfoLink(); + + expect(spy).toHaveBeenCalledWith('https://help.osf.io/', '_blank'); + }); }); diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index d5dfc2343..aebbc6d71 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -237,7 +237,7 @@ export class DashboardComponent implements OnInit { }); } - protected openInfoLink(): void { + openInfoLink(): void { window.open('https://help.osf.io/', '_blank'); } } From 90957d3ce86bd70b4529f513b2e36e6184636a1a Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 29 Aug 2025 20:18:49 +0300 Subject: [PATCH 15/21] update(dashboard): use dashboard.data for mocking --- .../dashboard/dashboard.component.spec.ts | 6 +- src/app/shared/mocks/dasboard.mock.ts | 19 ----- src/testing/data/dashboard/dasboard.data.ts | 73 +++++++++++++++++++ 3 files changed, 76 insertions(+), 22 deletions(-) delete mode 100644 src/app/shared/mocks/dasboard.mock.ts create mode 100644 src/testing/data/dashboard/dasboard.data.ts diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 73fe349ff..b780678db 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -5,10 +5,10 @@ import { Store } from '@ngxs/store'; import { of } from 'rxjs'; import { By } from '@angular/platform-browser'; import { OSFTestingModule, OSFTestingStoreModule } from '@testing/osf.testing.module'; -import {MOCK_PROJECTS} from '@shared/mocks/dasboard.mock'; import {MyResourcesSelectors} from '@shared/stores'; import {LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent} from '@shared/components'; import { MockComponents } from 'ng-mocks'; +import {getProjectsMockForComponent} from '@testing/data/addons/dasboard.data'; describe('DashboardComponent', () => { let component: DashboardComponent; @@ -22,8 +22,8 @@ describe('DashboardComponent', () => { beforeEach(async () => { (storeMock.selectSignal as jest.Mock).mockImplementation((selector) => { - if (selector === MyResourcesSelectors.getProjects) return () => MOCK_PROJECTS; - if (selector === MyResourcesSelectors.getTotalProjects) return () => MOCK_PROJECTS.length; + if (selector === MyResourcesSelectors.getProjects) return () => getProjectsMockForComponent(); + if (selector === MyResourcesSelectors.getTotalProjects) return () => getProjectsMockForComponent().length; if (selector === MyResourcesSelectors.getProjectsLoading) return () => false; return () => null; }); diff --git a/src/app/shared/mocks/dasboard.mock.ts b/src/app/shared/mocks/dasboard.mock.ts deleted file mode 100644 index 8dee4a599..000000000 --- a/src/app/shared/mocks/dasboard.mock.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MyResourcesItem } from '@shared/models'; - - -export const MOCK_PROJECTS: MyResourcesItem[] = [{ - id: "1", - type: "article", - title: "Deep Learning for Image Recognition", - dateCreated: "2025-01-15T10:30:00Z", - dateModified: "2025-02-01T14:45:00Z", - isPublic: true, - contributors: [ - { - familyName: "Smith", - fullName: "John Michael Smith", - givenName: "John", - middleName: "Michael", - } - ], - }]; diff --git a/src/testing/data/dashboard/dasboard.data.ts b/src/testing/data/dashboard/dasboard.data.ts new file mode 100644 index 000000000..bf1daf015 --- /dev/null +++ b/src/testing/data/dashboard/dasboard.data.ts @@ -0,0 +1,73 @@ +import structuredClone from 'structured-clone'; +import { MyResourcesItem } from '@shared/models'; + +const ProjectsMock = { + data: [ + { + type: 'my-resources-items', + id: '1', + attributes: { + title: 'Deep Learning for Image Recognition', + date_created: '2025-01-15T10:30:00Z', + date_modified: '2025-02-01T14:45:00Z', + is_public: true, + }, + relationships: { + contributors: { + data: [ + { + type: 'my-resources-contributors', + id: 'c1', + }, + ], + }, + }, + links: { + self: 'https://api.staging.osf.io/v1/my-resources-items/1', + }, + }, + ], + included: [ + { + type: 'my-resources-contributors', + id: 'c1', + attributes: { + family_name: 'Smith', + full_name: 'John Michael Smith', + given_name: 'John', + middle_name: 'Michael', + }, + links: { + self: 'https://api.staging.osf.io/v1/my-resources-contributors/c1', + }, + }, + ], +}; + +export function getProjectsMockData(index?: number, asArray?: boolean) { + if (index || index === 0) { + if (asArray) { + return Object({ + data: [structuredClone(ProjectsMock.data[index])], + }); + } else { + return structuredClone({ + data: ProjectsMock.data[index], + }); + } + } else { + return structuredClone(ProjectsMock); + } +} + +export function getProjectsMockForComponent(): MyResourcesItem[] { + return getProjectsMockData().data.map((item: any) => ({ + id: item.id, + type: item.type, + title: item.attributes.title, + dateCreated: item.attributes.date_created, + dateModified: item.attributes.date_modified, + isPublic: item.attributes.is_public, + contributors: [], + })); +} From d1914244dcc7035a1ea4a87d6d3c562aa8bc7c3c Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 29 Aug 2025 20:53:00 +0300 Subject: [PATCH 16/21] update(dashboard): add test to show that products footer images no exists on spinner run and are rendering after it finished running --- .../dashboard/dashboard.component.spec.ts | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index b780678db..5cb37f71e 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -8,7 +8,7 @@ import { OSFTestingModule, OSFTestingStoreModule } from '@testing/osf.testing.mo import {MyResourcesSelectors} from '@shared/stores'; import {LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent} from '@shared/components'; import { MockComponents } from 'ng-mocks'; -import {getProjectsMockForComponent} from '@testing/data/addons/dasboard.data'; +import {getProjectsMockForComponent} from '@testing/data/dashboard/dasboard.data'; describe('DashboardComponent', () => { let component: DashboardComponent; @@ -82,4 +82,43 @@ describe('DashboardComponent', () => { expect(spy).toHaveBeenCalledWith('https://help.osf.io/', '_blank'); }); + + it('should render product images after loading spinner disappears', () => { + jest.spyOn(component, 'areProjectsLoading').mockReturnValue(true); + fixture.detectChanges(); + + let productImages = fixture.debugElement + .queryAll(By.css('img')) + .filter(img => + img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/') + ); + + expect(productImages.length).toBe(0); + + const spinner = fixture.debugElement.query(By.css('osf-loading-spinner')); + expect(spinner).toBeTruthy(); + + jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); + fixture.detectChanges(); + + productImages = fixture.debugElement + .queryAll(By.css('img')) + .filter(img => + img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/') + ); + + expect(productImages.length).toBe(4); + + const sources = productImages.map(img => + img.nativeElement.getAttribute('src') + ); + + expect(sources).toEqual(expect.arrayContaining([ + 'assets/images/dashboard/products/osf-collections.png', + 'assets/images/dashboard/products/osf-institutions.png', + 'assets/images/dashboard/products/osf-registries.png', + 'assets/images/dashboard/products/osf-preprints.png', + ])); + }); + }); From dc756f264195cfd57dabacfd824314e36eb053e1 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 29 Aug 2025 21:01:47 +0300 Subject: [PATCH 17/21] fix(dashboard): npm run lint:fix && npm run format --- .../dashboard/dashboard.component.spec.ts | 65 ++++++++++--------- .../pages/dashboard/dashboard.component.ts | 23 +++++-- src/testing/data/dashboard/dasboard.data.ts | 3 +- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 5cb37f71e..6605b488d 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -1,19 +1,24 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule} from '@angular/forms'; -import { DashboardComponent } from './dashboard.component'; import { Store } from '@ngxs/store'; + +import { MockComponents } from 'ng-mocks'; + import { of } from 'rxjs'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; + +import { LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent } from '@shared/components'; +import { MyResourcesSelectors } from '@shared/stores'; + +import { DashboardComponent } from './dashboard.component'; + +import { getProjectsMockForComponent } from '@testing/data/dashboard/dasboard.data'; import { OSFTestingModule, OSFTestingStoreModule } from '@testing/osf.testing.module'; -import {MyResourcesSelectors} from '@shared/stores'; -import {LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent} from '@shared/components'; -import { MockComponents } from 'ng-mocks'; -import {getProjectsMockForComponent} from '@testing/data/dashboard/dasboard.data'; describe('DashboardComponent', () => { let component: DashboardComponent; let fixture: ComponentFixture; - console.log('hello hello') const storeMock = { dispatch: jest.fn().mockReturnValue(of({})), selectSnapshot: jest.fn(), @@ -28,8 +33,13 @@ describe('DashboardComponent', () => { return () => null; }); await TestBed.configureTestingModule({ - imports: [DashboardComponent, ReactiveFormsModule, OSFTestingModule, OSFTestingStoreModule, - ...MockComponents(SubHeaderComponent, MyProjectsTableComponent, LoadingSpinnerComponent)], + imports: [ + DashboardComponent, + ReactiveFormsModule, + OSFTestingModule, + OSFTestingStoreModule, + ...MockComponents(SubHeaderComponent, MyProjectsTableComponent, LoadingSpinnerComponent), + ], providers: [{ provide: Store, useValue: storeMock }], }).compileComponents(); @@ -38,7 +48,6 @@ describe('DashboardComponent', () => { fixture.detectChanges(); }); - it('should show loading spinner when projects are loading', () => { jest.spyOn(component, 'areProjectsLoading').mockReturnValue(true); fixture.detectChanges(); @@ -77,10 +86,9 @@ describe('DashboardComponent', () => { }); it('should open OSF help link in new tab when openInfoLink is called', () => { - const spy = jest.spyOn(window, 'open').mockImplementation(() => null); - component.openInfoLink(); - - expect(spy).toHaveBeenCalledWith('https://help.osf.io/', '_blank'); + const spy = jest.spyOn(window, 'open').mockImplementation(() => null); + component.openInfoLink(); + expect(spy).toHaveBeenCalledWith('https://help.osf.io/', '_blank'); }); it('should render product images after loading spinner disappears', () => { @@ -89,9 +97,7 @@ describe('DashboardComponent', () => { let productImages = fixture.debugElement .queryAll(By.css('img')) - .filter(img => - img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/') - ); + .filter((img) => img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/')); expect(productImages.length).toBe(0); @@ -103,22 +109,19 @@ describe('DashboardComponent', () => { productImages = fixture.debugElement .queryAll(By.css('img')) - .filter(img => - img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/') - ); + .filter((img) => img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/')); expect(productImages.length).toBe(4); - const sources = productImages.map(img => - img.nativeElement.getAttribute('src') - ); + const sources = productImages.map((img) => img.nativeElement.getAttribute('src')); - expect(sources).toEqual(expect.arrayContaining([ - 'assets/images/dashboard/products/osf-collections.png', - 'assets/images/dashboard/products/osf-institutions.png', - 'assets/images/dashboard/products/osf-registries.png', - 'assets/images/dashboard/products/osf-preprints.png', - ])); + expect(sources).toEqual( + expect.arrayContaining([ + 'assets/images/dashboard/products/osf-collections.png', + 'assets/images/dashboard/products/osf-institutions.png', + 'assets/images/dashboard/products/osf-registries.png', + 'assets/images/dashboard/products/osf-preprints.png', + ]) + ); }); - }); diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index aebbc6d71..066c935c4 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -16,7 +16,12 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { CreateProjectDialogComponent } from '@osf/features/my-projects/components'; import { AccountSettingsService } from '@osf/features/settings/account-settings/services'; -import { IconComponent, LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent } from '@osf/shared/components'; +import { + IconComponent, + LoadingSpinnerComponent, + MyProjectsTableComponent, + SubHeaderComponent, +} from '@osf/shared/components'; import { MY_PROJECTS_TABLE_PARAMS } from '@osf/shared/constants'; import { SortOrder } from '@osf/shared/enums'; import { IS_MEDIUM } from '@osf/shared/helpers'; @@ -27,7 +32,15 @@ import { ConfirmEmailComponent } from '../../components'; @Component({ selector: 'osf-dashboard', - imports: [RouterLink, Button, SubHeaderComponent, MyProjectsTableComponent, IconComponent, TranslatePipe, LoadingSpinnerComponent], + imports: [ + RouterLink, + Button, + SubHeaderComponent, + MyProjectsTableComponent, + IconComponent, + TranslatePipe, + LoadingSpinnerComponent, + ], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss', providers: [DialogService], @@ -61,7 +74,7 @@ export class DashboardComponent implements OnInit { }); protected readonly existsProjects = computed(() => { - return this.projects().length || !! this.searchControl.value?.length; + return this.projects().length || !!this.searchControl.value?.length; }); dialogRef: DynamicDialogRef | null = null; @@ -164,7 +177,9 @@ export class DashboardComponent implements OnInit { this.isLoading.set(true); const filters = this.createFilters(); const page = Math.floor(this.tableParams().firstRowIndex / this.tableParams().rows) + 1; - this.actions.getMyProjects(page, this.tableParams().rows, filters).pipe(takeUntilDestroyed(this.destroyRef)) + this.actions + .getMyProjects(page, this.tableParams().rows, filters) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ complete: () => { this.isLoading.set(false); diff --git a/src/testing/data/dashboard/dasboard.data.ts b/src/testing/data/dashboard/dasboard.data.ts index bf1daf015..38a8177ca 100644 --- a/src/testing/data/dashboard/dasboard.data.ts +++ b/src/testing/data/dashboard/dasboard.data.ts @@ -1,6 +1,7 @@ -import structuredClone from 'structured-clone'; import { MyResourcesItem } from '@shared/models'; +import structuredClone from 'structured-clone'; + const ProjectsMock = { data: [ { From 33235f55b3e18c71c577243e80b3f376d474fdbb Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 3 Sep 2025 18:02:07 +0300 Subject: [PATCH 18/21] add missing code for dashboard --- src/app/features/home/pages/dashboard/dashboard.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index c4eea24f8..f2549a391 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -63,7 +63,9 @@ export class DashboardComponent implements OnInit { readonly areProjectsLoading = select(MyResourcesSelectors.getProjectsLoading); readonly actions = createDispatchMap({ getMyProjects: GetMyProjects, clearMyResources: ClearMyResources }); - + protected readonly existsProjects = computed(() => { + return this.projects().length || !!this.searchControl.value?.length; + }); readonly filteredProjects = computed(() => { const search = this.searchControl.value?.toLowerCase() ?? ''; return this.projects().filter((project) => project.title.toLowerCase().includes(search)); From e5bdb8fc6782e707c700215b001425f70c7a6820 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 3 Sep 2025 19:14:32 +0300 Subject: [PATCH 19/21] remove redundant imports for dashboard test --- .../features/home/pages/dashboard/dashboard.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 6605b488d..5ed5c1fee 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -35,8 +35,6 @@ describe('DashboardComponent', () => { await TestBed.configureTestingModule({ imports: [ DashboardComponent, - ReactiveFormsModule, - OSFTestingModule, OSFTestingStoreModule, ...MockComponents(SubHeaderComponent, MyProjectsTableComponent, LoadingSpinnerComponent), ], From 928c19f8ca33f880a69873430b590ee619524d4a Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 3 Sep 2025 19:34:48 +0300 Subject: [PATCH 20/21] use providers -> useValue mock approach for dashboard --- .../dashboard/dashboard.component.spec.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index 5ed5c1fee..f9a563f12 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -1,11 +1,9 @@ import { Store } from '@ngxs/store'; +import { signal } from '@angular/core'; import { MockComponents } from 'ng-mocks'; -import { of } from 'rxjs'; - import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { LoadingSpinnerComponent, MyProjectsTableComponent, SubHeaderComponent } from '@shared/components'; @@ -14,31 +12,39 @@ import { MyResourcesSelectors } from '@shared/stores'; import { DashboardComponent } from './dashboard.component'; import { getProjectsMockForComponent } from '@testing/data/dashboard/dasboard.data'; -import { OSFTestingModule, OSFTestingStoreModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('DashboardComponent', () => { let component: DashboardComponent; let fixture: ComponentFixture; - const storeMock = { - dispatch: jest.fn().mockReturnValue(of({})), - selectSnapshot: jest.fn(), - selectSignal: jest.fn(), - }; beforeEach(async () => { - (storeMock.selectSignal as jest.Mock).mockImplementation((selector) => { - if (selector === MyResourcesSelectors.getProjects) return () => getProjectsMockForComponent(); - if (selector === MyResourcesSelectors.getTotalProjects) return () => getProjectsMockForComponent().length; - if (selector === MyResourcesSelectors.getProjectsLoading) return () => false; - return () => null; - }); await TestBed.configureTestingModule({ imports: [ DashboardComponent, OSFTestingStoreModule, ...MockComponents(SubHeaderComponent, MyProjectsTableComponent, LoadingSpinnerComponent), ], - providers: [{ provide: Store, useValue: storeMock }], + providers: [ + { + provide: Store, + useValue: { + selectSignal: (selector: any) => { + if (selector === MyResourcesSelectors.getProjects) { + return signal(getProjectsMockForComponent()); + } + if (selector === MyResourcesSelectors.getTotalProjects) { + return signal(getProjectsMockForComponent().length); + } + if (selector === MyResourcesSelectors.getProjectsLoading) { + return signal(false); + } + return signal(null); + }, + dispatch: jest.fn(), + }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(DashboardComponent); @@ -73,7 +79,6 @@ describe('DashboardComponent', () => { expect(iframe).toBeTruthy(); expect(iframe.nativeElement.src).toContain('youtube.com'); }); - it('should render welcome screen when no projects exist', () => { jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); jest.spyOn(component, 'existsProjects').mockReturnValue(false); From c8c4e9be5b142df34f6f3f5d27333c269c7b4b27 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Wed, 3 Sep 2025 22:32:05 +0300 Subject: [PATCH 21/21] resolve CR comments --- .../pages/dashboard/dashboard.component.html | 24 ++++++--- .../dashboard/dashboard.component.spec.ts | 49 ++++++++++--------- .../pages/dashboard/dashboard.component.ts | 23 +++------ 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/app/features/home/pages/dashboard/dashboard.component.html b/src/app/features/home/pages/dashboard/dashboard.component.html index 284e56964..d80a4d50b 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.html +++ b/src/app/features/home/pages/dashboard/dashboard.component.html @@ -2,7 +2,7 @@ @if (areProjectsLoading()) { } @else { - @if (existsProjects()){ + @if (existsProjects()) { {{ 'home.loggedIn.hosting.title' | translate }} rel="noopener noreferrer" data-test-products-collections > - + - + - + - +
} diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts index f9a563f12..a5a27171d 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts @@ -1,8 +1,8 @@ import { Store } from '@ngxs/store'; -import { signal } from '@angular/core'; import { MockComponents } from 'ng-mocks'; +import { signal, WritableSignal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; @@ -18,7 +18,15 @@ describe('DashboardComponent', () => { let component: DashboardComponent; let fixture: ComponentFixture; + let projectsSignal: WritableSignal; + let totalProjectsSignal: WritableSignal; + let areProjectsLoadingSignal: WritableSignal; + beforeEach(async () => { + projectsSignal = signal(getProjectsMockForComponent()); + totalProjectsSignal = signal(getProjectsMockForComponent().length); + areProjectsLoadingSignal = signal(false); + await TestBed.configureTestingModule({ imports: [ DashboardComponent, @@ -30,15 +38,9 @@ describe('DashboardComponent', () => { provide: Store, useValue: { selectSignal: (selector: any) => { - if (selector === MyResourcesSelectors.getProjects) { - return signal(getProjectsMockForComponent()); - } - if (selector === MyResourcesSelectors.getTotalProjects) { - return signal(getProjectsMockForComponent().length); - } - if (selector === MyResourcesSelectors.getProjectsLoading) { - return signal(false); - } + if (selector === MyResourcesSelectors.getProjects) return projectsSignal; + if (selector === MyResourcesSelectors.getTotalProjects) return totalProjectsSignal; + if (selector === MyResourcesSelectors.getProjectsLoading) return areProjectsLoadingSignal; return signal(null); }, dispatch: jest.fn(), @@ -49,11 +51,10 @@ describe('DashboardComponent', () => { fixture = TestBed.createComponent(DashboardComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); - it('should show loading spinner when projects are loading', () => { - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(true); + it('should show loading s pinner when projects are loading', () => { + areProjectsLoadingSignal.set(true); fixture.detectChanges(); const spinner = fixture.debugElement.query(By.directive(LoadingSpinnerComponent)); @@ -61,8 +62,9 @@ describe('DashboardComponent', () => { }); it('should render projects table when projects exist', () => { - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); - jest.spyOn(component, 'existsProjects').mockReturnValue(true); + projectsSignal.set(getProjectsMockForComponent()); + totalProjectsSignal.set(getProjectsMockForComponent().length); + areProjectsLoadingSignal.set(false); fixture.detectChanges(); const table = fixture.debugElement.query(By.directive(MyProjectsTableComponent)); @@ -70,18 +72,19 @@ describe('DashboardComponent', () => { }); it('should render welcome video when no projects exist', () => { - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); - jest.spyOn(component, 'existsProjects').mockReturnValue(false); - + projectsSignal.set([]); + totalProjectsSignal.set(0); + areProjectsLoadingSignal.set(false); fixture.detectChanges(); - const iframe = fixture.debugElement.query(By.css('iframe')); expect(iframe).toBeTruthy(); expect(iframe.nativeElement.src).toContain('youtube.com'); }); + it('should render welcome screen when no projects exist', () => { - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); - jest.spyOn(component, 'existsProjects').mockReturnValue(false); + projectsSignal.set([]); + totalProjectsSignal.set(0); + areProjectsLoadingSignal.set(false); fixture.detectChanges(); const welcomeText = fixture.debugElement.nativeElement.textContent; @@ -95,7 +98,7 @@ describe('DashboardComponent', () => { }); it('should render product images after loading spinner disappears', () => { - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(true); + areProjectsLoadingSignal.set(true); fixture.detectChanges(); let productImages = fixture.debugElement @@ -107,7 +110,7 @@ describe('DashboardComponent', () => { const spinner = fixture.debugElement.query(By.css('osf-loading-spinner')); expect(spinner).toBeTruthy(); - jest.spyOn(component, 'areProjectsLoading').mockReturnValue(false); + areProjectsLoadingSignal.set(false); fixture.detectChanges(); productImages = fixture.debugElement diff --git a/src/app/features/home/pages/dashboard/dashboard.component.ts b/src/app/features/home/pages/dashboard/dashboard.component.ts index f2549a391..7e1e41ed8 100644 --- a/src/app/features/home/pages/dashboard/dashboard.component.ts +++ b/src/app/features/home/pages/dashboard/dashboard.component.ts @@ -48,8 +48,6 @@ export class DashboardComponent implements OnInit { private readonly route = inject(ActivatedRoute); private readonly translateService = inject(TranslateService); private readonly dialogService = inject(DialogService); - - protected readonly isLoading = signal(false); readonly isMedium = toSignal(inject(IS_MEDIUM)); readonly searchControl = new FormControl(''); @@ -63,14 +61,16 @@ export class DashboardComponent implements OnInit { readonly areProjectsLoading = select(MyResourcesSelectors.getProjectsLoading); readonly actions = createDispatchMap({ getMyProjects: GetMyProjects, clearMyResources: ClearMyResources }); - protected readonly existsProjects = computed(() => { - return this.projects().length || !!this.searchControl.value?.length; - }); + readonly filteredProjects = computed(() => { const search = this.searchControl.value?.toLowerCase() ?? ''; return this.projects().filter((project) => project.title.toLowerCase().includes(search)); }); + protected readonly existsProjects = computed(() => { + return this.projects().length || !!this.searchControl.value?.length; + }); + dialogRef: DynamicDialogRef | null = null; emailAddress = ''; @@ -134,20 +134,9 @@ export class DashboardComponent implements OnInit { } fetchProjects(): void { - this.isLoading.set(true); const filters = this.createFilters(); const page = Math.floor(this.tableParams().firstRowIndex / this.tableParams().rows) + 1; - this.actions - .getMyProjects(page, this.tableParams().rows, filters) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe({ - complete: () => { - this.isLoading.set(false); - }, - error: () => { - this.isLoading.set(false); - }, - }); + this.actions.getMyProjects(page, this.tableParams().rows, filters); } createFilters(): MyResourcesSearchFilters {