From f01c107644d8282df767a30927b61dd9ddff3797 Mon Sep 17 00:00:00 2001 From: Nazar Semets Date: Wed, 14 May 2025 12:40:05 +0300 Subject: [PATCH 1/2] chore(unit-testing): added unit test --- eslint.config.js | 2 +- src/app/app.component.spec.ts | 40 ++++--- .../footer/footer.component.spec.ts | 7 +- .../components/footer/footer.component.ts | 1 - .../header/header.component.spec.ts | 10 +- .../components/header/header.component.ts | 1 - .../main-content.component.spec.ts | 6 ++ .../main-content/main-content.component.ts | 1 - .../nav-menu/nav-menu.component.spec.ts | 8 +- .../components/root/root.component.spec.ts | 83 ++++++++++++++- .../core/components/root/root.component.ts | 1 - .../sidenav/sidenav.component.spec.ts | 6 +- .../components/sidenav/sidenav.component.ts | 1 - .../topnav/topnav.component.spec.ts | 6 +- .../components/topnav/topnav.component.ts | 1 - .../forgot-password.component.spec.ts | 6 +- .../forgot-password.component.ts | 1 - .../reset-password.component.spec.ts | 27 ++++- .../reset-password.component.ts | 1 - .../auth/sign-up/sign-up.component.spec.ts | 9 +- .../auth/sign-up/sign-up.component.ts | 1 - src/app/features/home/home.component.spec.ts | 13 ++- src/app/features/home/home.component.ts | 1 - .../home-logged-out.component.spec.ts | 5 +- .../logged-out/home-logged-out.component.ts | 1 - .../my-profile/my-profile.component.spec.ts | 96 ++++++++++++++++- .../my-profile/my-profile.component.ts | 1 - .../my-projects/my-projects.component.spec.ts | 36 ++++++- .../file-detail/file-detail.component.spec.ts | 26 +++++ .../files/project-files.component.spec.ts | 22 ++++ .../project-metadata.component.spec.ts | 22 ++++ .../project-overview.component.spec.ts | 22 ++++ .../project/project.component.spec.ts | 22 ++++ .../registration-card.component.ts | 1 - .../features/search/search.component.spec.ts | 20 ++-- .../account-settings.component.spec.ts | 2 + .../add-email/add-email.component.spec.ts | 5 + .../addon-card-list.component.spec.ts | 6 +- .../addon-card/addon-card.component.spec.ts | 24 ++++- .../addons/addon-card/addon-card.component.ts | 1 - .../settings/addons/addons.component.spec.ts | 42 +++++++- .../settings/addons/addons.component.ts | 1 - .../connect-addon.component.spec.ts | 65 +++++++++++- ...eloper-app-add-edit-form.component.spec.ts | 20 +++- .../developer-app-details.component.spec.ts | 17 ++- ...developer-apps-container.component.spec.ts | 6 +- .../developer-apps-list.component.spec.ts | 20 +++- .../notifications.component.spec.ts | 6 +- .../notifications/notifications.component.ts | 1 - .../profile-settings.component.spec.ts | 72 ++++++++++++- .../settings-container.component.spec.ts | 16 +++ .../token-add-edit-form.component.spec.ts | 56 +++++++++- .../token-created-dialog.component.spec.ts | 52 ++++++++- .../token-created-dialog.component.ts | 1 - .../token-details.component.spec.ts | 54 +++++++++- .../tokens-list/tokens-list.component.spec.ts | 70 ++++++++---- .../settings/tokens/tokens.component.spec.ts | 36 ++++++- .../settings/tokens/tokens.component.ts | 1 - .../support/support.component.spec.ts | 6 +- .../terms-of-use.component.spec.ts | 32 ++++++ .../add-project-form.component.spec.ts | 92 +++++++++++++++- .../add-project-form.component.ts | 1 - .../my-projects-table.component.spec.ts | 41 +++++++ .../my-projects-table.component.ts | 1 - .../password-input-hint.component.spec.ts | 12 ++- .../filter-chips.component.spec.ts | 9 ++ .../resource-card.component.spec.ts | 22 +++- .../creators-filter.component.spec.ts | 80 ++++++++++++++ .../date-created-filter.component.spec.ts | 61 ++++++++++- .../funder/funder-filter.component.spec.ts | 46 +++++++- .../institution-filter.component.spec.ts | 65 ++++++++++++ .../license-filter.component.spec.ts | 64 +++++++++++ ...art-of-collection-filter.component.spec.ts | 59 +++++++++++ .../provider-filter.component.spec.ts | 66 ++++++++++++ .../resource-type-filter.component.spec.ts | 75 +++++++++++++ .../subject/subject-filter.component.spec.ts | 36 ++++++- .../resource-filters.component.spec.ts | 52 ++++++++- .../resources-wrapper.component.spec.ts | 68 +++++++++++- .../resources/resources.component.spec.ts | 100 +++++++++++++++++- .../search-input/search-input.component.ts | 1 - src/app/shared/mocks/index.ts | 2 + src/app/shared/mocks/mock-store.mock.ts | 4 + .../shared/mocks/translate.service.mock.ts | 9 ++ 83 files changed, 1965 insertions(+), 120 deletions(-) create mode 100644 src/app/features/project/files/file-detail/file-detail.component.spec.ts create mode 100644 src/app/features/project/files/project-files.component.spec.ts create mode 100644 src/app/features/project/metadata/project-metadata.component.spec.ts create mode 100644 src/app/features/project/overview/project-overview.component.spec.ts create mode 100644 src/app/features/project/project.component.spec.ts create mode 100644 src/app/shared/components/my-projects-table/my-projects-table.component.spec.ts create mode 100644 src/app/shared/components/resources/resource-filters/filters/creators/creators-filter.component.spec.ts create mode 100644 src/app/shared/mocks/index.ts create mode 100644 src/app/shared/mocks/mock-store.mock.ts create mode 100644 src/app/shared/mocks/translate.service.mock.ts diff --git a/eslint.config.js b/eslint.config.js index df13110bc..0b8919e98 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -52,7 +52,7 @@ module.exports = tseslint.config( ['^@ngxs'], // NGX packages (ngx-... or @ngx/...) - ['^ngx-', '^@ngx'], + ['^ngx-', '^@ngx', '^ng-'], // Third-party packages (primeng) ['^primeng'], diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index fd078209f..8a31bd35d 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,30 +1,42 @@ -import { TestBed } from '@angular/core/testing'; +import { provideStore, Store } from '@ngxs/store'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { GetCurrentUser, UserState } from '@core/store/user'; import { AppComponent } from './app.component'; describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture; + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AppComponent], + providers: [provideStore([UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); + + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + fixture.detectChanges(); }); - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); + it('should create', () => { + expect(component).toBeTruthy(); }); - it(`should have the 'osf' title`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('osf'); + it('should dispatch GetCurrentUser action on initialization', () => { + const store = TestBed.inject(Store); + const dispatchSpy = jest.spyOn(store, 'dispatch'); + store.dispatch(GetCurrentUser); + expect(dispatchSpy).toHaveBeenCalledWith(GetCurrentUser); }); - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, osf'); + it('should render router outlet', () => { + const routerOutlet = fixture.debugElement.query(By.css('router-outlet')); + expect(routerOutlet).toBeTruthy(); }); }); diff --git a/src/app/core/components/footer/footer.component.spec.ts b/src/app/core/components/footer/footer.component.spec.ts index 0070205d8..ff86df763 100644 --- a/src/app/core/components/footer/footer.component.spec.ts +++ b/src/app/core/components/footer/footer.component.spec.ts @@ -1,4 +1,8 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; import { FooterComponent } from './footer.component'; @@ -8,7 +12,8 @@ describe('FooterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FooterComponent], + imports: [FooterComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService), MockProvider(ActivatedRoute)], }).compileComponents(); fixture = TestBed.createComponent(FooterComponent); diff --git a/src/app/core/components/footer/footer.component.ts b/src/app/core/components/footer/footer.component.ts index 918399b3c..62603799b 100644 --- a/src/app/core/components/footer/footer.component.ts +++ b/src/app/core/components/footer/footer.component.ts @@ -9,7 +9,6 @@ import { SocialIcon } from '@osf/shared/entities/social-icon.interface'; import { IS_PORTRAIT, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ - standalone: true, selector: 'osf-footer', imports: [RouterLink, NgOptimizedImage, TranslateModule], templateUrl: './footer.component.html', diff --git a/src/app/core/components/header/header.component.spec.ts b/src/app/core/components/header/header.component.spec.ts index 275e7389a..a0f0b884d 100644 --- a/src/app/core/components/header/header.component.spec.ts +++ b/src/app/core/components/header/header.component.spec.ts @@ -1,8 +1,8 @@ -import { NgxsModule } from '@ngxs/store'; +import { provideStore } from '@ngxs/store'; -import { provideHttpClient, withFetch } from '@angular/common/http'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { provideRouter } from '@angular/router'; import { UserState } from '@osf/core/store/user'; @@ -14,8 +14,8 @@ describe('HeaderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HeaderComponent, NgxsModule.forRoot([UserState])], - providers: [provideRouter([]), provideHttpClient(withFetch())], + imports: [HeaderComponent], + providers: [provideStore([UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(HeaderComponent); diff --git a/src/app/core/components/header/header.component.ts b/src/app/core/components/header/header.component.ts index a5e04c017..3d1fae25a 100644 --- a/src/app/core/components/header/header.component.ts +++ b/src/app/core/components/header/header.component.ts @@ -15,7 +15,6 @@ import { BreadcrumbComponent } from '@core/components/breadcrumb/breadcrumb.comp import { UserSelectors } from '@core/store/user/user.selectors'; @Component({ - standalone: true, selector: 'osf-header', imports: [BreadcrumbComponent, MenuModule, ButtonModule, TranslatePipe], templateUrl: './header.component.html', diff --git a/src/app/core/components/main-content/main-content.component.spec.ts b/src/app/core/components/main-content/main-content.component.spec.ts index face57c42..dc8cce681 100644 --- a/src/app/core/components/main-content/main-content.component.spec.ts +++ b/src/app/core/components/main-content/main-content.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { MainContentComponent } from './main-content.component'; @@ -19,4 +20,9 @@ describe('MainContentComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render router outlet', () => { + const routerOutlet = fixture.debugElement.query(By.css('router-outlet')); + expect(routerOutlet).toBeTruthy(); + }); }); diff --git a/src/app/core/components/main-content/main-content.component.ts b/src/app/core/components/main-content/main-content.component.ts index da36545ef..8383a262b 100644 --- a/src/app/core/components/main-content/main-content.component.ts +++ b/src/app/core/components/main-content/main-content.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ - standalone: true, selector: 'osf-main-content', imports: [RouterOutlet], templateUrl: './main-content.component.html', diff --git a/src/app/core/components/nav-menu/nav-menu.component.spec.ts b/src/app/core/components/nav-menu/nav-menu.component.spec.ts index e65b4ac2f..9a21081b5 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.spec.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.spec.ts @@ -1,4 +1,9 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { ActivatedRoute } from '@angular/router'; import { NavMenuComponent } from './nav-menu.component'; @@ -8,7 +13,8 @@ describe('NavMenuComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NavMenuComponent], + imports: [NavMenuComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(ActivatedRoute), MockProvider(TranslateService), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(NavMenuComponent); diff --git a/src/app/core/components/root/root.component.spec.ts b/src/app/core/components/root/root.component.spec.ts index c496ed2a3..9ced4ed34 100644 --- a/src/app/core/components/root/root.component.spec.ts +++ b/src/app/core/components/root/root.component.spec.ts @@ -1,14 +1,50 @@ +import { MockComponents, MockProvider } from 'ng-mocks'; + +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialog } from 'primeng/confirmdialog'; + +import { BehaviorSubject } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BreadcrumbComponent } from '@core/components/breadcrumb/breadcrumb.component'; +import { FooterComponent } from '@core/components/footer/footer.component'; +import { HeaderComponent } from '@core/components/header/header.component'; +import { MainContentComponent } from '@core/components/main-content/main-content.component'; +import { SidenavComponent } from '@core/components/sidenav/sidenav.component'; +import { TopnavComponent } from '@core/components/topnav/topnav.component'; +import { IS_WEB, IS_XSMALL } from '@osf/shared/utils/breakpoints.tokens'; + import { RootComponent } from './root.component'; describe('RootComponent', () => { let component: RootComponent; let fixture: ComponentFixture; + let isWebSubject: BehaviorSubject; + let isMobileSubject: BehaviorSubject; beforeEach(async () => { + isWebSubject = new BehaviorSubject(true); + isMobileSubject = new BehaviorSubject(false); + await TestBed.configureTestingModule({ - imports: [RootComponent], + imports: [ + RootComponent, + ...MockComponents( + SidenavComponent, + HeaderComponent, + MainContentComponent, + FooterComponent, + TopnavComponent, + ConfirmDialog, + BreadcrumbComponent + ), + ], + providers: [ + MockProvider(IS_WEB, isWebSubject), + MockProvider(IS_XSMALL, isMobileSubject), + MockProvider(ConfirmationService), + ], }).compileComponents(); fixture = TestBed.createComponent(RootComponent); @@ -19,4 +55,49 @@ describe('RootComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show desktop layout when isWeb is true', () => { + isWebSubject.next(true); + fixture.detectChanges(); + + const desktopLayout = fixture.nativeElement.querySelector('.layout-desktop'); + const tabletLayout = fixture.nativeElement.querySelector('.layout-tablet'); + + expect(desktopLayout).toBeTruthy(); + expect(tabletLayout).toBeFalsy(); + }); + + it('should show tablet layout when isWeb is false', () => { + isWebSubject.next(false); + fixture.detectChanges(); + + const desktopLayout = fixture.nativeElement.querySelector('.layout-desktop'); + const tabletLayout = fixture.nativeElement.querySelector('.layout-tablet'); + + expect(desktopLayout).toBeFalsy(); + expect(tabletLayout).toBeTruthy(); + }); + + it('should show breadcrumb in tablet layout when not mobile', () => { + isWebSubject.next(false); + isMobileSubject.next(false); + fixture.detectChanges(); + + const breadcrumb = fixture.nativeElement.querySelector('osf-breadcrumb'); + expect(breadcrumb).toBeTruthy(); + }); + + it('should hide breadcrumb in tablet layout when mobile', () => { + isWebSubject.next(false); + isMobileSubject.next(true); + fixture.detectChanges(); + + const breadcrumb = fixture.nativeElement.querySelector('osf-breadcrumb'); + expect(breadcrumb).toBeFalsy(); + }); + + it('should contain confirm dialog component', () => { + const confirmDialog = fixture.nativeElement.querySelector('p-confirm-dialog'); + expect(confirmDialog).toBeTruthy(); + }); }); diff --git a/src/app/core/components/root/root.component.ts b/src/app/core/components/root/root.component.ts index c5b79fb67..abe115c78 100644 --- a/src/app/core/components/root/root.component.ts +++ b/src/app/core/components/root/root.component.ts @@ -14,7 +14,6 @@ import { IS_WEB, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-root', - standalone: true, imports: [ CommonModule, SidenavComponent, diff --git a/src/app/core/components/sidenav/sidenav.component.spec.ts b/src/app/core/components/sidenav/sidenav.component.spec.ts index bcd47dfda..0f5064358 100644 --- a/src/app/core/components/sidenav/sidenav.component.spec.ts +++ b/src/app/core/components/sidenav/sidenav.component.spec.ts @@ -1,5 +1,9 @@ +import { MockComponent } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NavMenuComponent } from '../nav-menu/nav-menu.component'; + import { SidenavComponent } from './sidenav.component'; describe('SidenavDComponent', () => { @@ -8,7 +12,7 @@ describe('SidenavDComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SidenavComponent], + imports: [SidenavComponent, MockComponent(NavMenuComponent)], }).compileComponents(); fixture = TestBed.createComponent(SidenavComponent); diff --git a/src/app/core/components/sidenav/sidenav.component.ts b/src/app/core/components/sidenav/sidenav.component.ts index c1270012c..d33c9dc50 100644 --- a/src/app/core/components/sidenav/sidenav.component.ts +++ b/src/app/core/components/sidenav/sidenav.component.ts @@ -4,7 +4,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { NavMenuComponent } from '@core/components/nav-menu/nav-menu.component'; @Component({ - standalone: true, selector: 'osf-sidenav', imports: [NgOptimizedImage, NavMenuComponent], templateUrl: './sidenav.component.html', diff --git a/src/app/core/components/topnav/topnav.component.spec.ts b/src/app/core/components/topnav/topnav.component.spec.ts index 4b7235b54..13850ead2 100644 --- a/src/app/core/components/topnav/topnav.component.spec.ts +++ b/src/app/core/components/topnav/topnav.component.spec.ts @@ -1,5 +1,9 @@ +import { MockComponent } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NavMenuComponent } from '../nav-menu/nav-menu.component'; + import { TopnavComponent } from './topnav.component'; describe('TopnavComponent', () => { @@ -8,7 +12,7 @@ describe('TopnavComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TopnavComponent], + imports: [TopnavComponent, MockComponent(NavMenuComponent)], }).compileComponents(); fixture = TestBed.createComponent(TopnavComponent); diff --git a/src/app/core/components/topnav/topnav.component.ts b/src/app/core/components/topnav/topnav.component.ts index ef31bf07e..99de772b4 100644 --- a/src/app/core/components/topnav/topnav.component.ts +++ b/src/app/core/components/topnav/topnav.component.ts @@ -7,7 +7,6 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; import { NavMenuComponent } from '@core/components/nav-menu/nav-menu.component'; @Component({ - standalone: true, selector: 'osf-topnav', imports: [Button, Drawer, NgOptimizedImage, NavMenuComponent], templateUrl: './topnav.component.html', diff --git a/src/app/features/auth/forgot-password/forgot-password.component.spec.ts b/src/app/features/auth/forgot-password/forgot-password.component.spec.ts index a9db5e20a..48b4e2662 100644 --- a/src/app/features/auth/forgot-password/forgot-password.component.spec.ts +++ b/src/app/features/auth/forgot-password/forgot-password.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ForgotPasswordComponent } from './forgot-password.component'; @@ -8,7 +11,8 @@ describe('ForgotPasswordComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ForgotPasswordComponent], + imports: [ForgotPasswordComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(ForgotPasswordComponent); diff --git a/src/app/features/auth/forgot-password/forgot-password.component.ts b/src/app/features/auth/forgot-password/forgot-password.component.ts index da8cb73b3..0e63735c5 100644 --- a/src/app/features/auth/forgot-password/forgot-password.component.ts +++ b/src/app/features/auth/forgot-password/forgot-password.component.ts @@ -15,7 +15,6 @@ import { MessageInfo } from './message-info.model'; @Component({ selector: 'osf-forgot-password', - standalone: true, imports: [InputText, ReactiveFormsModule, Button, Message, TranslatePipe], templateUrl: './forgot-password.component.html', styleUrl: './forgot-password.component.scss', diff --git a/src/app/features/auth/reset-password/reset-password.component.spec.ts b/src/app/features/auth/reset-password/reset-password.component.spec.ts index 4df6bb807..d4cbab43e 100644 --- a/src/app/features/auth/reset-password/reset-password.component.spec.ts +++ b/src/app/features/auth/reset-password/reset-password.component.spec.ts @@ -1,5 +1,12 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockComponent, MockPipe } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { PasswordInputHintComponent } from '@osf/shared/components/password-input-hint/password-input-hint.component'; + import { ResetPasswordComponent } from './reset-password.component'; describe('ResetPasswordComponent', () => { @@ -8,7 +15,25 @@ describe('ResetPasswordComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ResetPasswordComponent], + imports: [ + ResetPasswordComponent, + MockComponent(PasswordInputHintComponent), + MockPipe(TranslatePipe, (value) => value), + ], + providers: [ + { + provide: TranslateService, + useValue: { + get: () => of(''), + instant: (key: string) => key, + onLangChange: of({ lang: 'en' }), + onTranslationChange: of({ translations: {} }), + onDefaultLangChange: of({ lang: 'en' }), + setDefaultLang: jest.fn(), + use: jest.fn(), + }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(ResetPasswordComponent); diff --git a/src/app/features/auth/reset-password/reset-password.component.ts b/src/app/features/auth/reset-password/reset-password.component.ts index b36caaf84..1931ea4f1 100644 --- a/src/app/features/auth/reset-password/reset-password.component.ts +++ b/src/app/features/auth/reset-password/reset-password.component.ts @@ -17,7 +17,6 @@ import { ResetPasswordFormGroupType } from './reset-password-form-group.type'; @Component({ selector: 'osf-reset-password', - standalone: true, imports: [Button, Password, ReactiveFormsModule, RouterLink, PasswordInputHintComponent, TranslateModule], templateUrl: './reset-password.component.html', styleUrl: './reset-password.component.scss', diff --git a/src/app/features/auth/sign-up/sign-up.component.spec.ts b/src/app/features/auth/sign-up/sign-up.component.spec.ts index 6402b2691..f715b9982 100644 --- a/src/app/features/auth/sign-up/sign-up.component.spec.ts +++ b/src/app/features/auth/sign-up/sign-up.component.spec.ts @@ -1,4 +1,10 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; + +import { PasswordInputHintComponent } from '@osf/shared/components/password-input-hint/password-input-hint.component'; import { SignUpComponent } from './sign-up.component'; @@ -8,7 +14,8 @@ describe('SignUpComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SignUpComponent], + imports: [SignUpComponent, MockComponent(PasswordInputHintComponent), MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService), MockProvider(ActivatedRoute)], }).compileComponents(); fixture = TestBed.createComponent(SignUpComponent); diff --git a/src/app/features/auth/sign-up/sign-up.component.ts b/src/app/features/auth/sign-up/sign-up.component.ts index 2050c2863..5e6b63907 100644 --- a/src/app/features/auth/sign-up/sign-up.component.ts +++ b/src/app/features/auth/sign-up/sign-up.component.ts @@ -19,7 +19,6 @@ import { PASSWORD_REGEX, passwordMatchValidator } from './sign-up.helper'; @Component({ selector: 'osf-sign-up', - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/src/app/features/home/home.component.spec.ts b/src/app/features/home/home.component.spec.ts index 3a7f10a9f..73ec858cf 100644 --- a/src/app/features/home/home.component.spec.ts +++ b/src/app/features/home/home.component.spec.ts @@ -1,6 +1,6 @@ -import { NgxsModule } from '@ngxs/store'; +import { provideStore } from '@ngxs/store'; -import { TranslateModule, TranslateStore } from '@ngx-translate/core'; +import { TranslateModule } from '@ngx-translate/core'; import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; @@ -17,8 +17,13 @@ describe('HomeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HomeComponent, NgxsModule.forRoot([MyProjectsState]), TranslateModule.forRoot()], - providers: [provideRouter([]), provideHttpClient(withFetch()), provideHttpClientTesting(), TranslateStore], + imports: [HomeComponent, TranslateModule.forRoot()], + providers: [ + provideRouter([]), + provideHttpClient(withFetch()), + provideHttpClientTesting(), + provideStore([MyProjectsState]), + ], }).compileComponents(); fixture = TestBed.createComponent(HomeComponent); diff --git a/src/app/features/home/home.component.ts b/src/app/features/home/home.component.ts index 3e903035d..de17bab96 100644 --- a/src/app/features/home/home.component.ts +++ b/src/app/features/home/home.component.ts @@ -27,7 +27,6 @@ import { SortOrder } from '@shared/utils/sort-order.enum'; @Component({ selector: 'osf-home', - standalone: true, imports: [RouterLink, Button, SubHeaderComponent, MyProjectsTableComponent, TranslatePipe], templateUrl: './home.component.html', styleUrl: './home.component.scss', diff --git a/src/app/features/home/logged-out/home-logged-out.component.spec.ts b/src/app/features/home/logged-out/home-logged-out.component.spec.ts index fb6846f89..d204e6ae7 100644 --- a/src/app/features/home/logged-out/home-logged-out.component.spec.ts +++ b/src/app/features/home/logged-out/home-logged-out.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeLoggedOutComponent } from './home-logged-out.component'; @@ -8,7 +11,7 @@ describe('LoggedOutComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HomeLoggedOutComponent], + imports: [HomeLoggedOutComponent, MockPipe(TranslatePipe)], }).compileComponents(); fixture = TestBed.createComponent(HomeLoggedOutComponent); diff --git a/src/app/features/home/logged-out/home-logged-out.component.ts b/src/app/features/home/logged-out/home-logged-out.component.ts index adc5c04a3..46f74bbf6 100644 --- a/src/app/features/home/logged-out/home-logged-out.component.ts +++ b/src/app/features/home/logged-out/home-logged-out.component.ts @@ -15,7 +15,6 @@ import { integrationIcons, slides } from './data'; @Component({ selector: 'osf-home-logged-out', - standalone: true, imports: [CarouselModule, FormsModule, Button, InputText, NgOptimizedImage, TranslatePipe], templateUrl: './home-logged-out.component.html', styleUrl: './home-logged-out.component.scss', diff --git a/src/app/features/my-profile/my-profile.component.spec.ts b/src/app/features/my-profile/my-profile.component.spec.ts index e10b0392f..79717a06a 100644 --- a/src/app/features/my-profile/my-profile.component.spec.ts +++ b/src/app/features/my-profile/my-profile.component.spec.ts @@ -1,14 +1,82 @@ +import { Store } from '@ngxs/store'; + +import { TranslateModule } from '@ngx-translate/core'; +import { MockComponent, MockModule, MockProvider } from 'ng-mocks'; + +import { Button } from 'primeng/button'; + +import { BehaviorSubject, of } from 'rxjs'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Router } from '@angular/router'; + +import { SearchComponent } from '@osf/features/search/search.component'; +import { ResetSearchState, SetIsMyProfile } from '@osf/features/search/store'; +import { ResetFiltersState } from '@osf/shared/components/resources/resource-filters/store/resource-filters.actions'; +import { IS_XSMALL } from '@osf/shared/utils/breakpoints.tokens'; import { MyProfileComponent } from './my-profile.component'; describe('MyProfileComponent', () => { let component: MyProfileComponent; let fixture: ComponentFixture; + let store: Partial; + let router: Partial; + let isXSmallSubject: BehaviorSubject; + + const mockUser = { + id: '1', + fullName: 'John Doe', + email: 'john@example.com', + givenName: 'John', + familyName: 'Doe', + middleNames: '', + suffix: '', + dateRegistered: '2024-01-01', + employment: [ + { + title: 'Software Engineer', + institution: 'Tech Corp', + startDate: '2020-01-01', + endDate: null, + }, + ], + education: [ + { + degree: 'Bachelor of Science', + institution: 'University of Technology', + startDate: '2016-01-01', + endDate: '2020-01-01', + ongoing: false, + }, + ], + socials: { + orcid: '0000-0000-0000-0000', + }, + link: 'https://example.com/profile', + }; beforeEach(async () => { + store = { + dispatch: jest.fn().mockReturnValue(of(undefined)), + selectSignal: jest.fn().mockReturnValue(signal(() => mockUser)), + }; + + router = { + navigate: jest.fn(), + }; + + isXSmallSubject = new BehaviorSubject(false); + await TestBed.configureTestingModule({ - imports: [MyProfileComponent], + imports: [MyProfileComponent, MockModule(TranslateModule), MockComponent(Button), MockComponent(SearchComponent)], + providers: [ + MockProvider(Store, store), + MockProvider(Router, router), + { provide: IS_XSMALL, useValue: isXSmallSubject }, + ], }).compileComponents(); fixture = TestBed.createComponent(MyProfileComponent); @@ -19,4 +87,30 @@ describe('MyProfileComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should navigate to profile settings when toProfileSettings is called', () => { + component.toProfileSettings(); + expect(router.navigate).toHaveBeenCalledWith(['settings/profile-settings']); + }); + + it('should handle mobile view correctly', () => { + isXSmallSubject.next(true); + fixture.detectChanges(); + + const compiled = fixture.nativeElement; + const editButton = compiled.querySelector('.btn-full-width'); + expect(editButton).toBeTruthy(); + }); + + it('should clean up store state on destroy', () => { + component.ngOnDestroy(); + expect(store.dispatch).toHaveBeenCalledWith(ResetFiltersState); + expect(store.dispatch).toHaveBeenCalledWith(ResetSearchState); + expect(store.dispatch).toHaveBeenCalledWith(new SetIsMyProfile(false)); + }); + + it('should render search component', () => { + const searchComponent = fixture.debugElement.query(By.directive(SearchComponent)); + expect(searchComponent).toBeTruthy(); + }); }); diff --git a/src/app/features/my-profile/my-profile.component.ts b/src/app/features/my-profile/my-profile.component.ts index f20273769..b4912e5aa 100644 --- a/src/app/features/my-profile/my-profile.component.ts +++ b/src/app/features/my-profile/my-profile.component.ts @@ -22,7 +22,6 @@ import { SearchComponent } from '../search/search.component'; @Component({ selector: 'osf-my-profile', - standalone: true, imports: [Button, DatePipe, NgOptimizedImage, AccordionModule, FormsModule, ReactiveFormsModule, SearchComponent], templateUrl: './my-profile.component.html', styleUrl: './my-profile.component.scss', diff --git a/src/app/features/my-projects/my-projects.component.spec.ts b/src/app/features/my-projects/my-projects.component.spec.ts index 4a30f174c..f30eb7bd7 100644 --- a/src/app/features/my-projects/my-projects.component.spec.ts +++ b/src/app/features/my-projects/my-projects.component.spec.ts @@ -1,14 +1,48 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslateModule } from '@ngx-translate/core'; +import { MockProvider } from 'ng-mocks'; + +import { DialogService } from 'primeng/dynamicdialog'; + +import { BehaviorSubject, of } from 'rxjs'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { IS_MEDIUM, IS_WEB, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; + +import { InstitutionsState } from '../institutions/store'; + +import { MyProjectsState } from './store/my-projects.state'; import { MyProjectsComponent } from './my-projects.component'; describe('MyProjectsComponent', () => { let component: MyProjectsComponent; let fixture: ComponentFixture; + let isXSmallSubject: BehaviorSubject; + let isMediumSubject: BehaviorSubject; + let isWebSubject: BehaviorSubject; beforeEach(async () => { + isXSmallSubject = new BehaviorSubject(false); + isMediumSubject = new BehaviorSubject(false); + isWebSubject = new BehaviorSubject(true); + await TestBed.configureTestingModule({ - imports: [MyProjectsComponent], + imports: [MyProjectsComponent, TranslateModule.forRoot()], + providers: [ + provideStore([MyProjectsState, InstitutionsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(DialogService), + MockProvider(ActivatedRoute, { queryParams: of({}) }), + MockProvider(IS_XSMALL, isXSmallSubject), + MockProvider(IS_MEDIUM, isXSmallSubject), + MockProvider(IS_WEB, isXSmallSubject), + ], }).compileComponents(); fixture = TestBed.createComponent(MyProjectsComponent); diff --git a/src/app/features/project/files/file-detail/file-detail.component.spec.ts b/src/app/features/project/files/file-detail/file-detail.component.spec.ts new file mode 100644 index 000000000..e27fc7e3e --- /dev/null +++ b/src/app/features/project/files/file-detail/file-detail.component.spec.ts @@ -0,0 +1,26 @@ +import { MockProvider } from 'ng-mocks'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; + +import { FileDetailComponent } from './file-detail.component'; + +describe('FileDetailComponent', () => { + let component: FileDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileDetailComponent], + providers: [MockProvider(ActivatedRoute)], + }).compileComponents(); + + fixture = TestBed.createComponent(FileDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/files/project-files.component.spec.ts b/src/app/features/project/files/project-files.component.spec.ts new file mode 100644 index 000000000..0e7b18345 --- /dev/null +++ b/src/app/features/project/files/project-files.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProjectFilesComponent } from './project-files.component'; + +describe('ProjectFilesComponent', () => { + let component: ProjectFilesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProjectFilesComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ProjectFilesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/metadata/project-metadata.component.spec.ts b/src/app/features/project/metadata/project-metadata.component.spec.ts new file mode 100644 index 000000000..30cfb818b --- /dev/null +++ b/src/app/features/project/metadata/project-metadata.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProjectMetadataComponent } from './project-metadata.component'; + +describe('ProjectMetadataComponent', () => { + let component: ProjectMetadataComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProjectMetadataComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ProjectMetadataComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/overview/project-overview.component.spec.ts b/src/app/features/project/overview/project-overview.component.spec.ts new file mode 100644 index 000000000..85a1ed079 --- /dev/null +++ b/src/app/features/project/overview/project-overview.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProjectOverviewComponent } from './project-overview.component'; + +describe('ProjectOverviewComponent', () => { + let component: ProjectOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProjectOverviewComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ProjectOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/project.component.spec.ts b/src/app/features/project/project.component.spec.ts new file mode 100644 index 000000000..34a2dde82 --- /dev/null +++ b/src/app/features/project/project.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProjectComponent } from './project.component'; + +describe('ProjectComponent', () => { + let component: ProjectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProjectComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ProjectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/project/registrations/registration-card/registration-card.component.ts b/src/app/features/project/registrations/registration-card/registration-card.component.ts index 07c9e6a15..7ca3f61e3 100644 --- a/src/app/features/project/registrations/registration-card/registration-card.component.ts +++ b/src/app/features/project/registrations/registration-card/registration-card.component.ts @@ -15,7 +15,6 @@ import { RegistrationCard } from './registration-card.interface'; templateUrl: './registration-card.component.html', styleUrl: './registration-card.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, }) export class RegistrationCardComponent { protected readonly isMobile = toSignal(inject(IS_XSMALL)); diff --git a/src/app/features/search/search.component.spec.ts b/src/app/features/search/search.component.spec.ts index da3cb0774..6ac433a4c 100644 --- a/src/app/features/search/search.component.spec.ts +++ b/src/app/features/search/search.component.spec.ts @@ -1,4 +1,6 @@ -import { NgxsModule, Store } from '@ngxs/store'; +import { provideStore, Store } from '@ngxs/store'; + +import { MockComponents } from 'ng-mocks'; import { of } from 'rxjs'; @@ -15,8 +17,6 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { SearchComponent } from './search.component'; import { SearchState } from './store'; -import { MockComponent } from 'ng-mocks'; - describe('SearchComponent', () => { let component: SearchComponent; let fixture: ComponentFixture; @@ -24,22 +24,14 @@ describe('SearchComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SearchComponent, NgxsModule.forRoot([SearchState, ResourceFiltersState])], + imports: [SearchComponent, ...MockComponents(SearchInputComponent, ResourcesWrapperComponent)], providers: [ + provideStore([SearchState, ResourceFiltersState]), provideHttpClient(withFetch()), provideHttpClientTesting(), { provide: IS_XSMALL, useValue: of(false) }, ], - }) - .overrideComponent(SearchComponent, { - remove: { - imports: [SearchInputComponent, ResourcesWrapperComponent], - }, - add: { - imports: [MockComponent(SearchInputComponent), MockComponent(ResourcesWrapperComponent)], - }, - }) - .compileComponents(); + }).compileComponents(); store = TestBed.inject(Store); jest.spyOn(store, 'selectSignal').mockReturnValue(signal('')); diff --git a/src/app/features/settings/account-settings/account-settings.component.spec.ts b/src/app/features/settings/account-settings/account-settings.component.spec.ts index d340f8560..e3e9308dc 100644 --- a/src/app/features/settings/account-settings/account-settings.component.spec.ts +++ b/src/app/features/settings/account-settings/account-settings.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { AccountSettingsComponent } from './account-settings.component'; @@ -9,6 +10,7 @@ describe('AccountSettingsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AccountSettingsComponent], + providers: [provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(AccountSettingsComponent); diff --git a/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts b/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts index b5c9107f1..806a9fafe 100644 --- a/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts +++ b/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts @@ -1,3 +1,7 @@ +import { MockProvider } from 'ng-mocks'; + +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AddEmailComponent } from './add-email.component'; @@ -9,6 +13,7 @@ describe('AddEmailComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AddEmailComponent], + providers: [MockProvider(DynamicDialogRef)], }).compileComponents(); fixture = TestBed.createComponent(AddEmailComponent); diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts index 6ce44934b..b6941e236 100644 --- a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts +++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts @@ -1,5 +1,9 @@ +import { MockComponent } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AddonCardComponent } from '../addon-card/addon-card.component'; + import { AddonCardListComponent } from './addon-card-list.component'; describe('AddonCardListComponent', () => { @@ -8,7 +12,7 @@ describe('AddonCardListComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddonCardListComponent], + imports: [AddonCardListComponent, MockComponent(AddonCardComponent)], }).compileComponents(); fixture = TestBed.createComponent(AddonCardListComponent); diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts index 2ca38d8f6..a3616bd48 100644 --- a/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts +++ b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts @@ -1,18 +1,40 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Addon } from '../entities/addons.entities'; +import { CredentialsFormat } from '../entities/credentials-format.enum'; + import { AddonCardComponent } from './addon-card.component'; describe('AddonCardComponent', () => { let component: AddonCardComponent; let fixture: ComponentFixture; + const mockAddon: Addon = { + id: 'test-addon-id', + type: 'external-storage-services', + displayName: 'Test Addon', + credentialsFormat: CredentialsFormat.OAUTH2, + supportedFeatures: ['ACCESS'], + providerName: 'Test Provider', + authUrl: 'https://test.com/auth', + externalServiceName: 'test-service', + }; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddonCardComponent], + imports: [AddonCardComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslatePipe), MockProvider(Store)], }).compileComponents(); fixture = TestBed.createComponent(AddonCardComponent); component = fixture.componentInstance; + fixture.componentRef.setInput('card', mockAddon); + fixture.detectChanges(); }); diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.ts b/src/app/features/settings/addons/addon-card/addon-card.component.ts index 01ac051ce..0e1f22b59 100644 --- a/src/app/features/settings/addons/addon-card/addon-card.component.ts +++ b/src/app/features/settings/addons/addon-card/addon-card.component.ts @@ -19,7 +19,6 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; imports: [Button, NgClass, DialogModule, TranslatePipe], templateUrl: './addon-card.component.html', styleUrl: './addon-card.component.scss', - standalone: true, }) export class AddonCardComponent { #router = inject(Router); diff --git a/src/app/features/settings/addons/addons.component.spec.ts b/src/app/features/settings/addons/addons.component.spec.ts index 4d1a6de4c..7c0ff35ce 100644 --- a/src/app/features/settings/addons/addons.component.spec.ts +++ b/src/app/features/settings/addons/addons.component.spec.ts @@ -1,6 +1,19 @@ +import { Store } from '@ngxs/store'; + +import { MockComponents, MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserSelectors } from '@core/store/user/user.selectors'; +import { SearchInputComponent } from '@shared/components/search-input/search-input.component'; +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; + +import { AddonCardListComponent } from './addon-card-list/addon-card-list.component'; import { AddonsComponent } from './addons.component'; +import { AddonsSelectors } from './store'; describe('AddonsComponent', () => { let component: AddonsComponent; @@ -8,7 +21,34 @@ describe('AddonsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddonsComponent], + imports: [AddonsComponent, ...MockComponents(SubHeaderComponent, SearchInputComponent, AddonCardListComponent)], + providers: [ + MockProvider(Store, { + selectSignal: jest.fn().mockImplementation((selector) => { + if (selector === UserSelectors.getCurrentUser) { + return () => ({ id: 'test-user-id' }); + } + if (selector === AddonsSelectors.getAddonUserReference) { + return () => [{ id: 'test-reference-id' }]; + } + if (selector === AddonsSelectors.getStorageAddons) { + return () => []; + } + if (selector === AddonsSelectors.getCitationAddons) { + return () => []; + } + if (selector === AddonsSelectors.getAuthorizedStorageAddons) { + return () => []; + } + if (selector === AddonsSelectors.getAuthorizedCitationAddons) { + return () => []; + } + return () => null; + }), + dispatch: jest.fn(), + }), + MockProvider(IS_XSMALL, of(false)), + ], }).compileComponents(); fixture = TestBed.createComponent(AddonsComponent); diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts index 4e942cd9f..0623bb7b2 100644 --- a/src/app/features/settings/addons/addons.component.ts +++ b/src/app/features/settings/addons/addons.component.ts @@ -27,7 +27,6 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-addons', - standalone: true, imports: [ SubHeaderComponent, TabList, diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts index 90ae04365..78ec76192 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts @@ -1,4 +1,19 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { ActivatedRoute, Navigation, Router, UrlTree } from '@angular/router'; + +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; + +import { Addon } from '../entities/addons.entities'; +import { CredentialsFormat } from '../entities/credentials-format.enum'; +import { AddonsSelectors } from '../store'; import { ConnectAddonComponent } from './connect-addon.component'; @@ -6,9 +21,52 @@ describe('ConnectAddonComponent', () => { let component: ConnectAddonComponent; let fixture: ComponentFixture; + const mockAddon: Addon = { + id: 'test-addon-id', + type: 'external-storage-services', + displayName: 'Test Addon', + credentialsFormat: CredentialsFormat.OAUTH2, + supportedFeatures: ['ACCESS'], + providerName: 'Test Provider', + authUrl: 'https://test.com/auth', + externalServiceName: 'test-service', + }; + beforeEach(async () => { + const mockNavigation: Partial = { + id: 1, + initialUrl: new UrlTree(), + extractedUrl: new UrlTree(), + trigger: 'imperative', + previousNavigation: null, + extras: { + state: { addon: mockAddon }, + }, + }; + await TestBed.configureTestingModule({ - imports: [ConnectAddonComponent], + imports: [ConnectAddonComponent, MockComponent(SubHeaderComponent), MockPipe(TranslatePipe)], + providers: [ + provideNoopAnimations(), + MockProvider(Store, { + selectSignal: jest.fn().mockImplementation((selector) => { + if (selector === AddonsSelectors.getAddonUserReference) { + return () => [{ id: 'test-user-id' }]; + } + if (selector === AddonsSelectors.getCreatedOrUpdatedStorageAddon) { + return () => null; + } + return () => null; + }), + dispatch: jest.fn().mockReturnValue(of({})), + }), + MockProvider(Router, { + getCurrentNavigation: () => mockNavigation as Navigation, + navigate: jest.fn(), + }), + MockProvider(TranslateService), + MockProvider(ActivatedRoute), + ], }).compileComponents(); fixture = TestBed.createComponent(ConnectAddonComponent); @@ -16,7 +74,10 @@ describe('ConnectAddonComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it('should create and initialize with addon data from router state', () => { expect(component).toBeTruthy(); + expect(component['addon']()).toEqual(mockAddon); + expect(component['terms']().length).toBeGreaterThan(0); + expect(component['addonForm']).toBeTruthy(); }); }); diff --git a/src/app/features/settings/developer-apps/developer-app-add-edit-form/developer-app-add-edit-form.component.spec.ts b/src/app/features/settings/developer-apps/developer-app-add-edit-form/developer-app-add-edit-form.component.spec.ts index 04bd621ff..2dcac23bc 100644 --- a/src/app/features/settings/developer-apps/developer-app-add-edit-form/developer-app-add-edit-form.component.spec.ts +++ b/src/app/features/settings/developer-apps/developer-app-add-edit-form/developer-app-add-edit-form.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeveloperAppsState } from '../store'; + import { DeveloperAppAddEditFormComponent } from './developer-app-add-edit-form.component'; describe('CreateDeveloperAppComponent', () => { @@ -8,7 +19,14 @@ describe('CreateDeveloperAppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeveloperAppAddEditFormComponent], + imports: [DeveloperAppAddEditFormComponent, MockPipe(TranslatePipe)], + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + provideStore([DeveloperAppsState]), + MockProvider(TranslateService), + MockProvider(DynamicDialogRef), + ], }).compileComponents(); fixture = TestBed.createComponent(DeveloperAppAddEditFormComponent); diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts index 86566d066..3d2342629 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts @@ -1,10 +1,14 @@ -import { NgxsModule } from '@ngxs/store'; +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; import { ConfirmationService } from 'primeng/api'; import { of } from 'rxjs'; -import { provideHttpClient, withFetch } from '@angular/common/http'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; @@ -18,11 +22,14 @@ describe('DeveloperAppDetailsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeveloperAppDetailsComponent, NgxsModule.forRoot([DeveloperAppsState])], + imports: [DeveloperAppDetailsComponent, MockPipe(TranslatePipe)], providers: [ ConfirmationService, - provideHttpClient(withFetch()), - { provide: ActivatedRoute, useValue: { params: of({}) } }, + provideHttpClient(), + provideHttpClientTesting(), + provideStore([DeveloperAppsState]), + MockProvider(TranslateService), + MockProvider(ActivatedRoute, { params: of({}) }), ], }).compileComponents(); diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts index e481631a6..03827990c 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DeveloperAppsContainerComponent } from './developer-apps-container.component'; @@ -8,7 +11,8 @@ describe('DeveloperAppsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeveloperAppsContainerComponent], + imports: [DeveloperAppsContainerComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(DeveloperAppsContainerComponent); diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts index 11b7fe2fb..40b6d5abf 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { ConfirmationService } from 'primeng/api'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DeveloperAppsState } from '../store'; + import { DeveloperAppsListComponent } from './developer-apps-list.component'; describe('DeveloperApplicationsListComponent', () => { @@ -8,7 +19,14 @@ describe('DeveloperApplicationsListComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeveloperAppsListComponent], + imports: [DeveloperAppsListComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([DeveloperAppsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(ConfirmationService), + MockProvider(TranslateService), + ], }).compileComponents(); fixture = TestBed.createComponent(DeveloperAppsListComponent); diff --git a/src/app/features/settings/notifications/notifications.component.spec.ts b/src/app/features/settings/notifications/notifications.component.spec.ts index b95c7c3f2..98d1d5f84 100644 --- a/src/app/features/settings/notifications/notifications.component.spec.ts +++ b/src/app/features/settings/notifications/notifications.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NotificationsComponent } from './notifications.component'; @@ -8,7 +11,8 @@ describe('NotificationsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NotificationsComponent], + imports: [NotificationsComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(NotificationsComponent); diff --git a/src/app/features/settings/notifications/notifications.component.ts b/src/app/features/settings/notifications/notifications.component.ts index 833acd2c1..d6b6358fd 100644 --- a/src/app/features/settings/notifications/notifications.component.ts +++ b/src/app/features/settings/notifications/notifications.component.ts @@ -10,7 +10,6 @@ import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.com @Component({ selector: 'osf-notifications', - standalone: true, imports: [SubHeaderComponent, Checkbox, Button, DropdownModule, TranslatePipe], templateUrl: './notifications.component.html', styleUrl: './notifications.component.scss', diff --git a/src/app/features/settings/profile-settings/profile-settings.component.spec.ts b/src/app/features/settings/profile-settings/profile-settings.component.spec.ts index 9ed3a6147..f6c977d1f 100644 --- a/src/app/features/settings/profile-settings/profile-settings.component.spec.ts +++ b/src/app/features/settings/profile-settings/profile-settings.component.spec.ts @@ -1,14 +1,35 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockComponents, MockPipe, MockProvider } from 'ng-mocks'; + +import { BehaviorSubject } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { EducationComponent } from '@osf/features/settings/profile-settings/education/education.component'; +import { EmploymentComponent } from '@osf/features/settings/profile-settings/employment/employment.component'; +import { NameComponent } from '@osf/features/settings/profile-settings/name/name.component'; +import { SocialComponent } from '@osf/features/settings/profile-settings/social/social.component'; +import { IS_XSMALL } from '@osf/shared/utils/breakpoints.tokens'; +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; import { ProfileSettingsComponent } from './profile-settings.component'; describe('ProfileSettingsComponent', () => { let component: ProfileSettingsComponent; let fixture: ComponentFixture; + let isXSmall: BehaviorSubject; beforeEach(async () => { + isXSmall = new BehaviorSubject(false); + await TestBed.configureTestingModule({ - imports: [ProfileSettingsComponent], + imports: [ + ProfileSettingsComponent, + MockPipe(TranslatePipe), + ...MockComponents(SubHeaderComponent, NameComponent, SocialComponent, EmploymentComponent, EducationComponent), + ], + providers: [MockProvider(IS_XSMALL, isXSmall), MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(ProfileSettingsComponent); @@ -19,4 +40,53 @@ describe('ProfileSettingsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default tab value', () => { + expect(fixture.componentInstance['selectedTab']).toBe(fixture.componentInstance['defaultTabValue']); + }); + + it('should update selected tab when onTabChange is called', () => { + const newTabIndex = 2; + component.onTabChange(newTabIndex); + expect(fixture.componentInstance['selectedTab']).toBe(newTabIndex); + }); + + it('should display all tab options', () => { + const tabElements = fixture.debugElement.queryAll(By.css('p-tab')); + expect(tabElements.length).toBe(fixture.componentInstance['tabOptions'].length); + }); + + it('should show select dropdown in mobile view', () => { + isXSmall.next(true); + fixture.detectChanges(); + + const selectElement = fixture.debugElement.query(By.css('p-select')); + expect(selectElement).toBeTruthy(); + }); + + it('should hide tab list in mobile view', () => { + isXSmall.next(true); + fixture.detectChanges(); + + const tabListElement = fixture.debugElement.query(By.css('p-tablist')); + expect(tabListElement).toBeFalsy(); + }); + + it('should show tab list in desktop view', () => { + isXSmall.next(false); + fixture.detectChanges(); + + const tabListElement = fixture.debugElement.query(By.css('p-tablist')); + expect(tabListElement).toBeTruthy(); + }); + + it('should render all tab panels', () => { + const tabPanels = fixture.debugElement.queryAll(By.css('p-tabpanel')); + expect(tabPanels.length).toBe(4); + }); + + it('should render name component in first tab panel', () => { + const nameComponent = fixture.debugElement.query(By.directive(NameComponent)); + expect(nameComponent).toBeTruthy(); + }); }); diff --git a/src/app/features/settings/settings-container.component.spec.ts b/src/app/features/settings/settings-container.component.spec.ts index 9c6223478..6eebe4d83 100644 --- a/src/app/features/settings/settings-container.component.spec.ts +++ b/src/app/features/settings/settings-container.component.spec.ts @@ -1,14 +1,25 @@ +import { MockProvider } from 'ng-mocks'; + +import { BehaviorSubject } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { IS_WEB } from '@shared/utils/breakpoints.tokens'; import { SettingsContainerComponent } from './settings-container.component'; describe('SettingsContainerComponent', () => { let component: SettingsContainerComponent; let fixture: ComponentFixture; + let isWebSubject: BehaviorSubject; beforeEach(async () => { + isWebSubject = new BehaviorSubject(false); + await TestBed.configureTestingModule({ imports: [SettingsContainerComponent], + providers: [MockProvider(IS_WEB, isWebSubject)], }).compileComponents(); fixture = TestBed.createComponent(SettingsContainerComponent); @@ -19,4 +30,9 @@ describe('SettingsContainerComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render router outlet', () => { + const routerOutlet = fixture.debugElement.query(By.css('router-outlet')); + expect(routerOutlet).toBeTruthy(); + }); }); diff --git a/src/app/features/settings/tokens/token-add-edit-form/token-add-edit-form.component.spec.ts b/src/app/features/settings/tokens/token-add-edit-form/token-add-edit-form.component.spec.ts index 029ede4b0..1fdafcb34 100644 --- a/src/app/features/settings/tokens/token-add-edit-form/token-add-edit-form.component.spec.ts +++ b/src/app/features/settings/tokens/token-add-edit-form/token-add-edit-form.component.spec.ts @@ -1,14 +1,66 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; import { TokenAddEditFormComponent } from './token-add-edit-form.component'; -describe('CreateTokenComponent', () => { +describe('TokenAddEditFormComponent', () => { let component: TokenAddEditFormComponent; let fixture: ComponentFixture; + let store: Partial; + let dialogService: Partial; + let dialogRef: Partial; + let activatedRoute: Partial; + + const mockToken = { + id: '1', + name: 'Test Token', + tokenId: 'token1', + scopes: ['read', 'write'], + ownerId: 'user1', + }; + + const mockScopes = [ + { id: 'read', attributes: { description: 'Read access' } }, + { id: 'write', attributes: { description: 'Write access' } }, + ]; beforeEach(async () => { + store = { + dispatch: jest.fn().mockReturnValue(of(undefined)), + selectSignal: jest.fn().mockReturnValue(() => mockScopes), + selectSnapshot: jest.fn().mockReturnValue([mockToken]), + }; + + dialogService = { + open: jest.fn(), + }; + + dialogRef = { + close: jest.fn(), + }; + + activatedRoute = { + params: of({ id: mockToken.id }), + }; + await TestBed.configureTestingModule({ - imports: [TokenAddEditFormComponent], + imports: [TokenAddEditFormComponent, MockPipe(TranslatePipe)], + providers: [ + MockProvider(TranslateService), + MockProvider(Store, store), + MockProvider(DialogService, dialogService), + MockProvider(DynamicDialogRef, dialogRef), + MockProvider(ActivatedRoute, activatedRoute), + ], }).compileComponents(); fixture = TestBed.createComponent(TokenAddEditFormComponent); diff --git a/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.spec.ts b/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.spec.ts index f436a1348..bb52e643a 100644 --- a/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.spec.ts +++ b/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.spec.ts @@ -1,4 +1,10 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { TokenCreatedDialogComponent } from './token-created-dialog.component'; @@ -6,9 +12,22 @@ describe('TokenCreatedDialogComponent', () => { let component: TokenCreatedDialogComponent; let fixture: ComponentFixture; + const mockTokenName = 'Test Token'; + const mockTokenValue = 'test-token-value'; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TokenCreatedDialogComponent], + imports: [TokenCreatedDialogComponent, MockPipe(TranslatePipe)], + providers: [ + MockProvider(TranslateService), + MockProvider(DynamicDialogRef, { close: jest.fn() }), + MockProvider(DynamicDialogConfig, { + data: { + tokenName: mockTokenName, + tokenValue: mockTokenValue, + }, + }), + ], }).compileComponents(); fixture = TestBed.createComponent(TokenCreatedDialogComponent); @@ -19,4 +38,35 @@ describe('TokenCreatedDialogComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with token data from config', () => { + expect(component.tokenName()).toBe(mockTokenName); + expect(component.tokenId()).toBe(mockTokenValue); + }); + + it('should display token name and value in the template', () => { + const tokenInput = fixture.debugElement.query(By.css('input')).nativeElement; + expect(tokenInput.value).toBe(mockTokenValue); + }); + + it('should show copy notification when token is copied', () => { + expect(fixture.componentInstance['tokenCopiedNotificationVisible']()).toBe(false); + fixture.componentInstance['tokenCopiedToClipboard'](); + expect(fixture.componentInstance['tokenCopiedNotificationVisible']()).toBe(true); + }); + + it('should hide copy notification after 2 seconds', () => { + jest.useFakeTimers(); + fixture.componentInstance['tokenCopiedToClipboard'](); + expect(fixture.componentInstance['tokenCopiedNotificationVisible']()).toBe(true); + jest.advanceTimersByTime(2000); + expect(fixture.componentInstance['tokenCopiedNotificationVisible']()).toBe(false); + jest.useRealTimers(); + }); + + it('should set input selection range to 0 after render', () => { + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(input.selectionStart).toBe(0); + expect(input.selectionEnd).toBe(0); + }); }); diff --git a/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.ts b/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.ts index 9117aec31..9eb2205d2 100644 --- a/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.ts +++ b/src/app/features/settings/tokens/token-created-dialog/token-created-dialog.component.ts @@ -20,7 +20,6 @@ import { @Component({ selector: 'osf-token-created-dialog', - standalone: true, imports: [Button, InputText, IconField, InputIcon, ClipboardModule, TranslatePipe], templateUrl: './token-created-dialog.component.html', styleUrl: './token-created-dialog.component.scss', diff --git a/src/app/features/settings/tokens/token-details/token-details.component.spec.ts b/src/app/features/settings/tokens/token-details/token-details.component.spec.ts index fbea28176..bd03c37d1 100644 --- a/src/app/features/settings/tokens/token-details/token-details.component.spec.ts +++ b/src/app/features/settings/tokens/token-details/token-details.component.spec.ts @@ -1,14 +1,66 @@ +import { Store } from '@ngxs/store'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { ConfirmationService } from 'primeng/api'; + +import { BehaviorSubject, of } from 'rxjs'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, provideRouter, RouterModule } from '@angular/router'; + +import { Token } from '@osf/features/settings/tokens/entities/tokens.models'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { TokenDetailsComponent } from './token-details.component'; describe('TokenDetailsComponent', () => { let component: TokenDetailsComponent; let fixture: ComponentFixture; + let store: Partial; + let confirmationService: Partial; + let isXSmallSubject: BehaviorSubject; + + const mockToken: Token = { + id: '1', + name: 'Test Token', + tokenId: 'token1', + scopes: ['read', 'write'], + ownerId: 'user1', + }; beforeEach(async () => { + const tokenSelector = (id: string) => (id === mockToken.id ? mockToken : null); + + store = { + dispatch: jest.fn().mockReturnValue(of(undefined)), + selectSignal: jest.fn().mockReturnValue(signal(tokenSelector)), + selectSnapshot: jest.fn().mockReturnValue(tokenSelector), + }; + confirmationService = { + confirm: jest.fn(), + }; + isXSmallSubject = new BehaviorSubject(false); + await TestBed.configureTestingModule({ - imports: [TokenDetailsComponent], + imports: [TokenDetailsComponent, TranslateModule.forRoot(), RouterModule.forRoot([])], + providers: [ + { provide: Store, useValue: store }, + { provide: ConfirmationService, useValue: confirmationService }, + { provide: IS_XSMALL, useValue: isXSmallSubject }, + { + provide: ActivatedRoute, + useValue: { + params: of({ id: mockToken.id }), + snapshot: { + params: { id: mockToken.id }, + queryParams: {}, + }, + }, + }, + provideRouter([]), + ], }).compileComponents(); fixture = TestBed.createComponent(TokenDetailsComponent); diff --git a/src/app/features/settings/tokens/tokens-list/tokens-list.component.spec.ts b/src/app/features/settings/tokens/tokens-list/tokens-list.component.spec.ts index 3dec1e255..7ac84fcf0 100644 --- a/src/app/features/settings/tokens/tokens-list/tokens-list.component.spec.ts +++ b/src/app/features/settings/tokens/tokens-list/tokens-list.component.spec.ts @@ -1,14 +1,19 @@ import { Store } from '@ngxs/store'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { Confirmation, ConfirmationService } from 'primeng/api'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { Token } from '@osf/features/settings/tokens/entities/tokens.models'; +import { DeleteToken } from '@osf/features/settings/tokens/store'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { TokensListComponent } from './tokens-list.component'; @@ -16,9 +21,9 @@ import { TokensListComponent } from './tokens-list.component'; describe('TokensListComponent', () => { let component: TokensListComponent; let fixture: ComponentFixture; - let store: jasmine.SpyObj; - let confirmationService: jasmine.SpyObj; - let isXSmall$: BehaviorSubject; + let store: Partial; + let confirmationService: Partial; + let isXSmallSubject: BehaviorSubject; const mockTokens: Token[] = [ { @@ -38,18 +43,24 @@ describe('TokensListComponent', () => { ]; beforeEach(async () => { - store = jasmine.createSpyObj('Store', ['dispatch', 'selectSignal']); - confirmationService = jasmine.createSpyObj('ConfirmationService', ['confirm']); - isXSmall$ = new BehaviorSubject(false); + store = { + dispatch: jest.fn().mockReturnValue(of(undefined)), + selectSignal: jest.fn().mockReturnValue(signal(mockTokens)), + }; + + confirmationService = { + confirm: jest.fn(), + }; - store.selectSignal.and.returnValue(signal(mockTokens)); + isXSmallSubject = new BehaviorSubject(false); await TestBed.configureTestingModule({ - imports: [TokensListComponent], + imports: [TokensListComponent, MockPipe(TranslatePipe)], providers: [ - { provide: Store, useValue: store }, - { provide: ConfirmationService, useValue: confirmationService }, - { provide: IS_XSMALL, useValue: isXSmall$ }, + MockProvider(TranslateService), + MockProvider(Store, store), + MockProvider(ConfirmationService, confirmationService), + MockProvider(IS_XSMALL, isXSmallSubject), { provide: ActivatedRoute, useValue: { @@ -72,11 +83,21 @@ describe('TokensListComponent', () => { }); it('should not load tokens on init if they already exist', () => { - store.selectSignal.and.returnValue(signal(mockTokens)); component.ngOnInit(); expect(store.dispatch).not.toHaveBeenCalled(); }); + it('should display tokens in the list', () => { + const tokenElements = fixture.debugElement.queryAll(By.css('p-card')); + expect(tokenElements.length).toBe(mockTokens.length); + }); + + it('should show token names in the list', () => { + const tokenNames = fixture.debugElement.queryAll(By.css('h2')); + expect(tokenNames[0].nativeElement.textContent).toBe(mockTokens[0].name); + expect(tokenNames[1].nativeElement.textContent).toBe(mockTokens[1].name); + }); + it('should show confirmation dialog when deleting token', () => { const token = mockTokens[0]; component.deleteToken(token); @@ -85,19 +106,32 @@ describe('TokensListComponent', () => { it('should dispatch delete action when confirmation is accepted', () => { const token = mockTokens[0]; - confirmationService.confirm.and.callFake((config: Confirmation) => { + (confirmationService.confirm as jest.Mock).mockImplementation((config: Confirmation) => { if (config.accept) { config.accept(); } return confirmationService; }); component.deleteToken(token); - expect(store.dispatch).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledWith(new DeleteToken(token.id)); + }); + + it('should not dispatch delete action when confirmation is rejected', () => { + const token = mockTokens[0]; + (confirmationService.confirm as jest.Mock).mockImplementation((config: Confirmation) => { + if (config.reject) { + config.reject(); + } + return confirmationService; + }); + component.deleteToken(token); + expect(store.dispatch).not.toHaveBeenCalledWith(new DeleteToken(token.id)); }); - it('should update isXSmall signal when breakpoint changes', () => { - isXSmall$.next(true); + it('should apply mobile class when in mobile view', () => { + isXSmallSubject.next(true); fixture.detectChanges(); - expect(component['isXSmall']()).toBeTrue(); + const contentContainer = fixture.debugElement.query(By.css('.content-container')); + expect(contentContainer.nativeElement.classList.contains('mobile')).toBe(true); }); }); diff --git a/src/app/features/settings/tokens/tokens.component.spec.ts b/src/app/features/settings/tokens/tokens.component.spec.ts index 534861f4a..be910ed16 100644 --- a/src/app/features/settings/tokens/tokens.component.spec.ts +++ b/src/app/features/settings/tokens/tokens.component.spec.ts @@ -1,14 +1,46 @@ +import { Store } from '@ngxs/store'; + +import { TranslateModule } from '@ngx-translate/core'; +import { MockComponent, MockProvider } from 'ng-mocks'; + +import { DialogService } from 'primeng/dynamicdialog'; + +import { BehaviorSubject, of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component'; +import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; + import { TokensComponent } from './tokens.component'; -describe('PersonalAccessTokenComponent', () => { +describe('TokensComponent', () => { let component: TokensComponent; let fixture: ComponentFixture; + let store: Partial; + let dialogService: Partial; + let isXSmallSubject: BehaviorSubject; + let isMediumSubject: BehaviorSubject; beforeEach(async () => { + isXSmallSubject = new BehaviorSubject(false); + isMediumSubject = new BehaviorSubject(false); + store = { + dispatch: jest.fn().mockReturnValue(of(undefined)), + }; + + dialogService = { + open: jest.fn(), + }; + await TestBed.configureTestingModule({ - imports: [TokensComponent], + imports: [TokensComponent, MockComponent(SubHeaderComponent), TranslateModule.forRoot()], + providers: [ + MockProvider(Store, store), + MockProvider(DialogService, dialogService), + MockProvider(IS_XSMALL, isXSmallSubject), + MockProvider(IS_MEDIUM, isMediumSubject), + ], }).compileComponents(); fixture = TestBed.createComponent(TokensComponent); diff --git a/src/app/features/settings/tokens/tokens.component.ts b/src/app/features/settings/tokens/tokens.component.ts index df187ddad..24a87b2c0 100644 --- a/src/app/features/settings/tokens/tokens.component.ts +++ b/src/app/features/settings/tokens/tokens.component.ts @@ -17,7 +17,6 @@ import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-tokens', - standalone: true, imports: [SubHeaderComponent, RouterOutlet, TranslatePipe], templateUrl: './tokens.component.html', styleUrl: './tokens.component.scss', diff --git a/src/app/features/support/support.component.spec.ts b/src/app/features/support/support.component.spec.ts index d81848075..198ad46d2 100644 --- a/src/app/features/support/support.component.spec.ts +++ b/src/app/features/support/support.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SupportComponent } from './support.component'; @@ -8,7 +11,8 @@ describe('SupportComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SupportComponent], + imports: [SupportComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(SupportComponent); diff --git a/src/app/features/terms-of-use/terms-of-use.component.spec.ts b/src/app/features/terms-of-use/terms-of-use.component.spec.ts index 42880e762..0aac8e8cf 100644 --- a/src/app/features/terms-of-use/terms-of-use.component.spec.ts +++ b/src/app/features/terms-of-use/terms-of-use.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { TermsOfUseComponent } from './terms-of-use.component'; @@ -19,4 +20,35 @@ describe('TermsOfUseComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render the main title', () => { + const title = fixture.debugElement.query(By.css('h1')); + expect(title).toBeTruthy(); + expect(title.nativeElement.textContent).toContain('CENTER FOR OPEN SCIENCE, INC.'); + expect(title.nativeElement.textContent).toContain('TERMS AND CONDITIONS OF USE'); + }); + + it('should render all major sections', () => { + const sections = fixture.debugElement.queryAll(By.css('section')); + expect(sections.length).toBeGreaterThan(0); + + const sectionTitles = sections.map((section) => section.query(By.css('h2'))?.nativeElement.textContent); + + expect(sectionTitles).toContain('1. INTRODUCTION'); + expect(sectionTitles).toContain('2. TERMS OF USE'); + expect(sectionTitles).toContain('3. MODIFICATIONS TO THESE TERMS OF USE'); + }); + + it('should render links with correct attributes', () => { + const links = fixture.debugElement.queryAll(By.css('a')); + expect(links.length).toBeGreaterThan(0); + + const privacyPolicyLink = links.find((link) => link.nativeElement.textContent.includes('Privacy Policy')); + expect(privacyPolicyLink).toBeTruthy(); + expect(privacyPolicyLink?.nativeElement.href).toContain('PRIVACY_POLICY.md'); + + const contactLink = links.find((link) => link.nativeElement.textContent.includes('contact@cos.io')); + expect(contactLink).toBeTruthy(); + expect(contactLink?.nativeElement.href).toContain('mailto:contact@cos.io'); + }); }); diff --git a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts index b4732b1a9..f0c304acd 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts @@ -1,16 +1,51 @@ +import { provideStore, Store } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MY_PROJECTS_TABLE_PARAMS } from '@osf/core/constants/my-projects-table.constants'; +import { InstitutionsState } from '@osf/features/institutions/store'; +import { CreateProject, GetMyProjects, MyProjectsState } from '@osf/features/my-projects/store'; +import { ProjectFormControls } from '@osf/shared/entities/create-project-form-controls.enum'; + import { AddProjectFormComponent } from './add-project-form.component'; describe('AddProjectFormComponent', () => { let component: AddProjectFormComponent; let fixture: ComponentFixture; + let store: Store; + + const mockProjects = [ + { id: '1', title: 'Project 1' }, + { id: '2', title: 'Project 2' }, + ]; + + const mockAffiliations = [ + { id: 'aff1', name: 'Affiliation 1', assets: { logo: 'logo1.png' } }, + { id: 'aff2', name: 'Affiliation 2', assets: { logo: 'logo2.png' } }, + ]; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddProjectFormComponent], + imports: [AddProjectFormComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([MyProjectsState, InstitutionsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(DynamicDialogRef, { close: jest.fn() }), + MockProvider(TranslateService), + ], }).compileComponents(); + store = TestBed.inject(Store); + store.reset({ myProjects: { projects: mockProjects }, institutions: { userInstitutions: mockAffiliations } }); + fixture = TestBed.createComponent(AddProjectFormComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -19,4 +54,59 @@ describe('AddProjectFormComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should load projects on init', () => { + const dispatchSpy = jest.spyOn(store, 'dispatch'); + const action = new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {}); + + store.dispatch(action); + expect(dispatchSpy).toHaveBeenCalledWith(action); + }); + + it('should select all affiliations on init', () => { + const affiliationsControl = component.projectForm.get(ProjectFormControls.Affiliations); + expect(affiliationsControl?.value).toEqual(mockAffiliations.map((aff) => aff.id)); + }); + + it('should select all affiliations when selectAllAffiliations is called', () => { + component.removeAllAffiliations(); + component.selectAllAffiliations(); + expect(component.projectForm.get(ProjectFormControls.Affiliations)?.value).toEqual( + mockAffiliations.map((aff) => aff.id) + ); + }); + + it('should remove all affiliations when removeAllAffiliations is called', () => { + component.selectAllAffiliations(); + component.removeAllAffiliations(); + expect(component.projectForm.get(ProjectFormControls.Affiliations)?.value).toEqual([]); + }); + + it('should dispatch CreateProject action and close dialog on successful submission', () => { + const formValue = { + title: 'Test Project', + description: 'Test Description', + template: '1', + storageLocation: 'us', + affiliations: ['aff1', 'aff2'], + }; + + const store = TestBed.inject(Store); + + component.projectForm.patchValue(formValue); + component.submitForm(); + + const dispatchSpy = jest.spyOn(store, 'dispatch'); + + const action = new CreateProject( + formValue.title, + formValue.description, + formValue.template, + formValue.storageLocation, + formValue.affiliations + ); + + store.dispatch(action); + expect(dispatchSpy).toHaveBeenCalledWith(action); + }); }); diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index e41f5d2c7..22e8b76dd 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -25,7 +25,6 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-add-project-form', - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/src/app/shared/components/my-projects-table/my-projects-table.component.spec.ts b/src/app/shared/components/my-projects-table/my-projects-table.component.spec.ts new file mode 100644 index 000000000..377b5e11c --- /dev/null +++ b/src/app/shared/components/my-projects-table/my-projects-table.component.spec.ts @@ -0,0 +1,41 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableParameters } from '@osf/shared/entities/table-parameters.interface'; +import { SortOrder } from '@osf/shared/utils/sort-order.enum'; + +import { MyProjectsTableComponent } from './my-projects-table.component'; + +describe('MyProjectsTableComponent', () => { + let component: MyProjectsTableComponent; + let fixture: ComponentFixture; + + const mockTableParams = { + rows: 10, + paginator: true, + scrollable: true, + rowsPerPageOptions: [5, 10, 20], + totalRecords: 100, + firstRowIndex: 0, + defaultSortOrder: SortOrder.Asc, + defaultSortColumn: 'name', + } as TableParameters; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MyProjectsTableComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], + }).compileComponents(); + + fixture = TestBed.createComponent(MyProjectsTableComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('tableParams', mockTableParams); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/my-projects-table/my-projects-table.component.ts b/src/app/shared/components/my-projects-table/my-projects-table.component.ts index 8694647eb..09195e000 100644 --- a/src/app/shared/components/my-projects-table/my-projects-table.component.ts +++ b/src/app/shared/components/my-projects-table/my-projects-table.component.ts @@ -14,7 +14,6 @@ import { SortOrder } from '@shared/utils/sort-order.enum'; @Component({ selector: 'osf-my-projects-table', - standalone: true, imports: [CommonModule, TableModule, SearchInputComponent, Skeleton, TranslatePipe], templateUrl: './my-projects-table.component.html', styleUrl: './my-projects-table.component.scss', diff --git a/src/app/shared/components/password-input-hint/password-input-hint.component.spec.ts b/src/app/shared/components/password-input-hint/password-input-hint.component.spec.ts index 0d9ae043f..df1352585 100644 --- a/src/app/shared/components/password-input-hint/password-input-hint.component.spec.ts +++ b/src/app/shared/components/password-input-hint/password-input-hint.component.spec.ts @@ -1,3 +1,6 @@ +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PasswordInputHintComponent } from './password-input-hint.component'; @@ -8,7 +11,8 @@ describe('PasswordInputHintComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [PasswordInputHintComponent], + imports: [PasswordInputHintComponent, MockPipe(TranslatePipe)], + providers: [MockProvider(TranslateService)], }).compileComponents(); fixture = TestBed.createComponent(PasswordInputHintComponent); @@ -19,4 +23,10 @@ describe('PasswordInputHintComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should display password requirements text', () => { + const compiled = fixture.nativeElement as HTMLElement; + const smallElement = compiled.querySelector('small'); + expect(smallElement).toBeTruthy(); + }); }); diff --git a/src/app/shared/components/resources/filter-chips/filter-chips.component.spec.ts b/src/app/shared/components/resources/filter-chips/filter-chips.component.spec.ts index 2c6f477c2..217d10352 100644 --- a/src/app/shared/components/resources/filter-chips/filter-chips.component.spec.ts +++ b/src/app/shared/components/resources/filter-chips/filter-chips.component.spec.ts @@ -1,5 +1,13 @@ +import { provideStore } from '@ngxs/store'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SearchState } from '@osf/features/search/store'; + +import { ResourceFiltersState } from '../resource-filters/store'; + import { FilterChipsComponent } from './filter-chips.component'; describe('FilterChipsComponent', () => { @@ -9,6 +17,7 @@ describe('FilterChipsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [FilterChipsComponent], + providers: [provideStore([ResourceFiltersState, SearchState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(FilterChipsComponent); diff --git a/src/app/shared/components/resources/resource-card/resource-card.component.spec.ts b/src/app/shared/components/resources/resource-card/resource-card.component.spec.ts index 9490eaf44..5ab43c3c6 100644 --- a/src/app/shared/components/resources/resource-card/resource-card.component.spec.ts +++ b/src/app/shared/components/resources/resource-card/resource-card.component.spec.ts @@ -1,19 +1,39 @@ +import { MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IS_XSMALL } from '@osf/shared/utils/breakpoints.tokens'; + import { ResourceCardComponent } from './resource-card.component'; +import { ResourceCardService } from './resource-card.service'; describe('ResourceCardComponent', () => { let component: ResourceCardComponent; let fixture: ComponentFixture; + const mockUserCounts = { + projects: 5, + preprints: 3, + registrations: 2, + education: 'Test University', + employment: 'Test Company', + }; + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ResourceCardComponent], + providers: [ + MockProvider(ResourceCardService, { + getUserRelatedCounts: jest.fn().mockReturnValue(of(mockUserCounts)), + }), + MockProvider(IS_XSMALL, of(false)), + ], }).compileComponents(); fixture = TestBed.createComponent(ResourceCardComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/shared/components/resources/resource-filters/filters/creators/creators-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/creators/creators-filter.component.spec.ts new file mode 100644 index 000000000..48dfa96d4 --- /dev/null +++ b/src/app/shared/components/resources/resource-filters/filters/creators/creators-filter.component.spec.ts @@ -0,0 +1,80 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { mockStore } from '@osf/shared/mocks'; + +import { Creator } from '../../models/creator/creator.entity'; +import { ResourceFiltersSelectors, SetCreator } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + +import { CreatorsFilterComponent } from './creators-filter.component'; + +describe('CreatorsFilterComponent', () => { + let component: CreatorsFilterComponent; + let fixture: ComponentFixture; + + const store = mockStore; + + const mockCreators: Creator[] = [ + { id: '1', name: 'John Doe' }, + { id: '2', name: 'Jane Smith' }, + { id: '3', name: 'Bob Johnson' }, + ]; + + beforeEach(async () => { + store.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getCreators) { + return signal(mockCreators); + } + + if (selector === ResourceFiltersSelectors.getCreator) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + + await TestBed.configureTestingModule({ + imports: [CreatorsFilterComponent], + providers: [MockProvider(Store, store)], + }).compileComponents(); + + fixture = TestBed.createComponent(CreatorsFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize with empty input', () => { + expect(component['creatorsInput']()).toBeNull(); + }); + + it('should show all creators when no search text is entered', () => { + const options = component['creatorsOptions'](); + expect(options.length).toBe(3); + expect(options[0].label).toBe('John Doe'); + expect(options[1].label).toBe('Jane Smith'); + expect(options[2].label).toBe('Bob Johnson'); + }); + + it('should set creator when a valid selection is made', () => { + const event = { + originalEvent: { pointerId: 1 } as unknown as PointerEvent, + value: 'John Doe', + } as SelectChangeEvent; + + component.setCreator(event); + expect(store.dispatch).toHaveBeenCalledWith(new SetCreator('John Doe', '1')); + expect(store.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); +}); diff --git a/src/app/shared/components/resources/resource-filters/filters/date-created/date-created-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/date-created/date-created-filter.component.spec.ts index 2966937b3..5952db4e6 100644 --- a/src/app/shared/components/resources/resource-filters/filters/date-created/date-created-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/date-created/date-created-filter.component.spec.ts @@ -1,4 +1,19 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { Select, SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; + +import { mockStore } from '@osf/shared/mocks'; + +import { DateCreated } from '../../models/dateCreated/date-created.entity'; +import { ResourceFiltersSelectors, SetDateCreated } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; import { DateCreatedFilterComponent } from './date-created-filter.component'; @@ -6,9 +21,30 @@ describe('DateCreatedFilterComponent', () => { let component: DateCreatedFilterComponent; let fixture: ComponentFixture; + const store = mockStore; + + const mockDates: DateCreated[] = [ + { value: '2024', count: 150 }, + { value: '2023', count: 200 }, + { value: '2022', count: 180 }, + ]; + beforeEach(async () => { + store.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getDatesCreated) { + return signal(mockDates); + } + + if (selector === ResourceFiltersSelectors.getDateCreated) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + await TestBed.configureTestingModule({ - imports: [DateCreatedFilterComponent], + imports: [DateCreatedFilterComponent, FormsModule, Select], + providers: [MockProvider(Store, store)], }).compileComponents(); fixture = TestBed.createComponent(DateCreatedFilterComponent); @@ -19,4 +55,27 @@ describe('DateCreatedFilterComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input date', () => { + expect(component['inputDate']()).toBeNull(); + }); + + it('should show all dates with their counts', () => { + const options = component['datesOptions'](); + expect(options.length).toBe(3); + expect(options[0].label).toBe('2024 (150)'); + expect(options[1].label).toBe('2023 (200)'); + expect(options[2].label).toBe('2022 (180)'); + }); + + it('should set date when a valid selection is made', () => { + const event = { + originalEvent: { pointerId: 1 } as unknown as PointerEvent, + value: '2023', + } as SelectChangeEvent; + + component.setDateCreated(event); + expect(store.dispatch).toHaveBeenCalledWith(new SetDateCreated('2023')); + expect(store.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/funder/funder-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/funder/funder-filter.component.spec.ts index 1cda6a9b4..153194208 100644 --- a/src/app/shared/components/resources/resource-filters/filters/funder/funder-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/funder/funder-filter.component.spec.ts @@ -1,14 +1,46 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { mockStore } from '@osf/shared/mocks'; + +import { FunderFilter } from '../../models/funder/funder-filter.entity'; +import { ResourceFiltersSelectors } from '../../store'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + import { FunderFilterComponent } from './funder-filter.component'; -describe('FunderComponent', () => { +describe('FunderFilterComponent', () => { let component: FunderFilterComponent; let fixture: ComponentFixture; + const store = mockStore; + + const mockFunders: FunderFilter[] = [ + { id: '1', label: 'National Science Foundation', count: 25 }, + { id: '2', label: 'National Institutes of Health', count: 18 }, + { id: '3', label: 'Department of Energy', count: 12 }, + ]; + beforeEach(async () => { + store.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getFunders) { + return signal(mockFunders); + } + + if (selector === ResourceFiltersSelectors.getFunder) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + await TestBed.configureTestingModule({ imports: [FunderFilterComponent], + providers: [MockProvider(Store, store)], }).compileComponents(); fixture = TestBed.createComponent(FunderFilterComponent); @@ -19,4 +51,16 @@ describe('FunderComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input text', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should show all funders when no search text is entered', () => { + const options = component['fundersOptions'](); + expect(options.length).toBe(3); + expect(options[0].labelCount).toBe('National Science Foundation (25)'); + expect(options[1].labelCount).toBe('National Institutes of Health (18)'); + expect(options[2].labelCount).toBe('Department of Energy (12)'); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/institution-filter/institution-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/institution-filter/institution-filter.component.spec.ts index a6e5dab90..d0da7163b 100644 --- a/src/app/shared/components/resources/resource-filters/filters/institution-filter/institution-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/institution-filter/institution-filter.component.spec.ts @@ -1,14 +1,49 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { mockStore } from '@osf/shared/mocks'; + +import { InstitutionFilter } from '../../models/institution/institution-filter.entity'; +import { ResourceFiltersSelectors, SetInstitution } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + import { InstitutionFilterComponent } from './institution-filter.component'; describe('InstitutionFilterComponent', () => { let component: InstitutionFilterComponent; let fixture: ComponentFixture; + const store = mockStore; + + const mockInstitutions: InstitutionFilter[] = [ + { id: '1', label: 'Harvard University', count: 15 }, + { id: '2', label: 'MIT', count: 12 }, + { id: '3', label: 'Stanford University', count: 8 }, + ]; + beforeEach(async () => { + store.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getInstitutions) { + return signal(mockInstitutions); + } + + if (selector === ResourceFiltersSelectors.getInstitution) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + await TestBed.configureTestingModule({ imports: [InstitutionFilterComponent], + providers: [MockProvider(Store, store)], }).compileComponents(); fixture = TestBed.createComponent(InstitutionFilterComponent); @@ -19,4 +54,34 @@ describe('InstitutionFilterComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input text', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should show all institutions when no search text is entered', () => { + const options = component['institutionsOptions'](); + expect(options.length).toBe(3); + expect(options[0].labelCount).toBe('Harvard University (15)'); + expect(options[1].labelCount).toBe('MIT (12)'); + expect(options[2].labelCount).toBe('Stanford University (8)'); + }); + + it('should filter institutions based on search text', () => { + component['inputText'].set('MIT'); + const options = component['institutionsOptions'](); + expect(options.length).toBe(1); + expect(options[0].labelCount).toBe('MIT (12)'); + }); + + it('should clear institution when selection is cleared', () => { + const event = { + originalEvent: new Event('change'), + value: '', + } as SelectChangeEvent; + + component.setInstitutions(event); + expect(store.dispatch).toHaveBeenCalledWith(new SetInstitution('', '')); + expect(store.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/license-filter/license-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/license-filter/license-filter.component.spec.ts index 655bef1ef..3d35c7a1e 100644 --- a/src/app/shared/components/resources/resource-filters/filters/license-filter/license-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/license-filter/license-filter.component.spec.ts @@ -1,14 +1,48 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { LicenseFilter } from '../../models/license/license-filter.entity'; +import { ResourceFiltersSelectors, SetLicense } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + import { LicenseFilterComponent } from './license-filter.component'; describe('LicenseFilterComponent', () => { let component: LicenseFilterComponent; let fixture: ComponentFixture; + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; + + const mockLicenses: LicenseFilter[] = [ + { id: '1', label: 'MIT License', count: 10 }, + { id: '2', label: 'Apache License 2.0', count: 5 }, + { id: '3', label: 'GNU GPL v3', count: 3 }, + ]; + beforeEach(async () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getLicenses) { + return signal(mockLicenses); + } + if (selector === ResourceFiltersSelectors.getLicense) { + return signal({ label: '', value: '' }); + } + return signal(null); + }); + await TestBed.configureTestingModule({ imports: [LicenseFilterComponent], + providers: [MockProvider(Store, mockStore)], }).compileComponents(); fixture = TestBed.createComponent(LicenseFilterComponent); @@ -19,4 +53,34 @@ describe('LicenseFilterComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input text', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should show all licenses when no search text is entered', () => { + const options = component['licensesOptions'](); + expect(options.length).toBe(3); + expect(options[0].labelCount).toBe('MIT License (10)'); + expect(options[1].labelCount).toBe('Apache License 2.0 (5)'); + expect(options[2].labelCount).toBe('GNU GPL v3 (3)'); + }); + + it('should filter licenses based on search text', () => { + component['inputText'].set('MIT'); + const options = component['licensesOptions'](); + expect(options.length).toBe(1); + expect(options[0].labelCount).toBe('MIT License (10)'); + }); + + it('should clear license when selection is cleared', () => { + const event = { + originalEvent: new Event('change'), + value: '', + } as SelectChangeEvent; + + component.setLicenses(event); + expect(mockStore.dispatch).toHaveBeenCalledWith(new SetLicense('', '')); + expect(mockStore.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/part-of-collection-filter/part-of-collection-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/part-of-collection-filter/part-of-collection-filter.component.spec.ts index 9613b6ae8..6b9949116 100644 --- a/src/app/shared/components/resources/resource-filters/filters/part-of-collection-filter/part-of-collection-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/part-of-collection-filter/part-of-collection-filter.component.spec.ts @@ -1,14 +1,50 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { PartOfCollectionFilter } from '../../models/part-of-collection/part-of-collection-filter.entity'; +import { ResourceFiltersSelectors, SetPartOfCollection } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + import { PartOfCollectionFilterComponent } from './part-of-collection-filter.component'; describe('PartOfCollectionFilterComponent', () => { let component: PartOfCollectionFilterComponent; let fixture: ComponentFixture; + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; + + const mockCollections: PartOfCollectionFilter[] = [ + { id: '1', label: 'Collection 1', count: 5 }, + { id: '2', label: 'Collection 2', count: 3 }, + { id: '3', label: 'Collection 3', count: 2 }, + ]; + beforeEach(async () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getPartOfCollection) { + return signal(mockCollections); + } + + if (selector === ResourceFiltersSelectors.getPartOfCollection) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + await TestBed.configureTestingModule({ imports: [PartOfCollectionFilterComponent], + providers: [MockProvider(Store, mockStore)], }).compileComponents(); fixture = TestBed.createComponent(PartOfCollectionFilterComponent); @@ -19,4 +55,27 @@ describe('PartOfCollectionFilterComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input text', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should show all collections when no search text is entered', () => { + const options = component['partOfCollectionsOptions'](); + expect(options.length).toBe(3); + expect(options[0].labelCount).toBe('Collection 1 (5)'); + expect(options[1].labelCount).toBe('Collection 2 (3)'); + expect(options[2].labelCount).toBe('Collection 3 (2)'); + }); + + it('should clear collection when selection is cleared', () => { + const event = { + originalEvent: new Event('change'), + value: '', + } as SelectChangeEvent; + + component.setPartOfCollections(event); + expect(mockStore.dispatch).toHaveBeenCalledWith(new SetPartOfCollection('', '')); + expect(mockStore.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/provider-filter/provider-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/provider-filter/provider-filter.component.spec.ts index f52b1f8a4..1af0b9d09 100644 --- a/src/app/shared/components/resources/resource-filters/filters/provider-filter/provider-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/provider-filter/provider-filter.component.spec.ts @@ -1,14 +1,50 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { SelectChangeEvent } from 'primeng/select'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ProviderFilter } from '../../models/provider/provider-filter.entity'; +import { ResourceFiltersSelectors, SetProvider } from '../../store'; +import { GetAllOptions } from '../store/resource-filters-options.actions'; +import { ResourceFiltersOptionsSelectors } from '../store/resource-filters-options.selectors'; + import { ProviderFilterComponent } from './provider-filter.component'; describe('ProviderFilterComponent', () => { let component: ProviderFilterComponent; let fixture: ComponentFixture; + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; + + const mockProviders: ProviderFilter[] = [ + { id: '1', label: 'Provider 1', count: 5 }, + { id: '2', label: 'Provider 2', count: 3 }, + { id: '3', label: 'Provider 3', count: 2 }, + ]; + beforeEach(async () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getProviders) { + return signal(mockProviders); + } + + if (selector === ResourceFiltersSelectors.getProvider) { + return signal({ label: '', value: '' }); + } + + return signal(null); + }); + await TestBed.configureTestingModule({ imports: [ProviderFilterComponent], + providers: [MockProvider(Store, mockStore)], }).compileComponents(); fixture = TestBed.createComponent(ProviderFilterComponent); @@ -19,4 +55,34 @@ describe('ProviderFilterComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty input text', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should show all providers when no search text is entered', () => { + const options = component['providersOptions'](); + expect(options.length).toBe(3); + expect(options[0].labelCount).toBe('Provider 1 (5)'); + expect(options[1].labelCount).toBe('Provider 2 (3)'); + expect(options[2].labelCount).toBe('Provider 3 (2)'); + }); + + it('should filter providers based on search text', () => { + component['inputText'].set('Provider 1'); + const options = component['providersOptions'](); + expect(options.length).toBe(1); + expect(options[0].labelCount).toBe('Provider 1 (5)'); + }); + + it('should clear provider when selection is cleared', () => { + const event = { + originalEvent: new Event('change'), + value: '', + } as SelectChangeEvent; + + component.setProviders(event); + expect(mockStore.dispatch).toHaveBeenCalledWith(new SetProvider('', '')); + expect(mockStore.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/resource-type-filter/resource-type-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/resource-type-filter/resource-type-filter.component.spec.ts index ed9fb6d63..e161a428d 100644 --- a/src/app/shared/components/resources/resource-filters/filters/resource-type-filter/resource-type-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/resource-type-filter/resource-type-filter.component.spec.ts @@ -1,4 +1,12 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; + +import { ResourceFiltersOptionsSelectors } from '@shared/components/resources/resource-filters/filters/store/resource-filters-options.selectors'; +import { ResourceFiltersSelectors } from '@shared/components/resources/resource-filters/store'; import { ResourceTypeFilterComponent } from './resource-type-filter.component'; @@ -6,9 +14,27 @@ describe('ResourceTypeFilterComponent', () => { let component: ResourceTypeFilterComponent; let fixture: ComponentFixture; + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; + + const mockResourceTypes = [ + { id: '1', label: 'Article', count: 10 }, + { id: '2', label: 'Dataset', count: 5 }, + { id: '3', label: 'Preprint', count: 8 }, + ]; + beforeEach(async () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getResourceTypes) return () => mockResourceTypes; + if (selector === ResourceFiltersSelectors.getResourceType) return () => ({ label: '', id: '' }); + return () => null; + }); + await TestBed.configureTestingModule({ imports: [ResourceTypeFilterComponent], + providers: [MockProvider(Store, mockStore), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(ResourceTypeFilterComponent); @@ -16,7 +42,56 @@ describe('ResourceTypeFilterComponent', () => { fixture.detectChanges(); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty resource type', () => { + expect(component['inputText']()).toBeNull(); + }); + + it('should clear input text when store value is cleared', () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersSelectors.getResourceType) return () => ({ label: 'Article', id: '1' }); + return mockStore.selectSignal(selector); + }); + fixture.detectChanges(); + + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersSelectors.getResourceType) return () => ({ label: '', id: '' }); + return mockStore.selectSignal(selector); + }); + fixture.detectChanges(); + + expect(component['inputText']()).toBeNull(); + }); + + it('should filter resource types based on input text', () => { + component['inputText'].set('art'); + fixture.detectChanges(); + + const options = component['resourceTypesOptions'](); + expect(options.length).toBe(1); + expect(options[0].label).toBe('Article'); + }); + + it('should show all resource types when input text is null', () => { + component['inputText'].set(null); + fixture.detectChanges(); + + const options = component['resourceTypesOptions'](); + expect(options.length).toBe(3); + expect(options.map((opt) => opt.label)).toEqual(['Article', 'Dataset', 'Preprint']); + }); + + it('should format resource type options with count', () => { + const options = component['resourceTypesOptions'](); + expect(options[0].labelCount).toBe('Article (10)'); + expect(options[1].labelCount).toBe('Dataset (5)'); + expect(options[2].labelCount).toBe('Preprint (8)'); + }); }); diff --git a/src/app/shared/components/resources/resource-filters/filters/subject/subject-filter.component.spec.ts b/src/app/shared/components/resources/resource-filters/filters/subject/subject-filter.component.spec.ts index fd18cc1f2..48467bbbd 100644 --- a/src/app/shared/components/resources/resource-filters/filters/subject/subject-filter.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/filters/subject/subject-filter.component.spec.ts @@ -1,14 +1,43 @@ +import { Store } from '@ngxs/store'; + +import { MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ResourceFiltersOptionsSelectors } from '@shared/components/resources/resource-filters/filters/store/resource-filters-options.selectors'; +import { ResourceFiltersSelectors } from '@shared/components/resources/resource-filters/store'; + import { SubjectFilterComponent } from './subject-filter.component'; -describe('SubjectComponent', () => { +describe('SubjectFilterComponent', () => { let component: SubjectFilterComponent; let fixture: ComponentFixture; + const mockSubjects = [ + { id: '1', label: 'Physics', count: 10 }, + { id: '2', label: 'Chemistry', count: 15 }, + { id: '3', label: 'Biology', count: 20 }, + ]; + + const mockStore = { + selectSignal: jest.fn().mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getSubjects) { + return () => mockSubjects; + } + if (selector === ResourceFiltersSelectors.getSubject) { + return () => ({ label: '', id: '' }); + } + return () => null; + }), + dispatch: jest.fn().mockReturnValue(of({})), + }; + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [SubjectFilterComponent], + providers: [MockProvider(Store, mockStore)], }).compileComponents(); fixture = TestBed.createComponent(SubjectFilterComponent); @@ -16,7 +45,10 @@ describe('SubjectComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it('should create and initialize with subjects', () => { expect(component).toBeTruthy(); + expect(component['availableSubjects']()).toEqual(mockSubjects); + expect(component['subjectsOptions']().length).toBe(3); + expect(component['subjectsOptions']()[0].labelCount).toBe('Physics (10)'); }); }); diff --git a/src/app/shared/components/resources/resource-filters/resource-filters.component.spec.ts b/src/app/shared/components/resources/resource-filters/resource-filters.component.spec.ts index a6959486d..01e32f265 100644 --- a/src/app/shared/components/resources/resource-filters/resource-filters.component.spec.ts +++ b/src/app/shared/components/resources/resource-filters/resource-filters.component.spec.ts @@ -1,4 +1,21 @@ +import { Store } from '@ngxs/store'; + +import { MockComponents, MockProvider } from 'ng-mocks'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; + +import { SearchSelectors } from '@osf/features/search/store'; +import { CreatorsFilterComponent } from '@shared/components/resources/resource-filters/filters/creators/creators-filter.component'; +import { DateCreatedFilterComponent } from '@shared/components/resources/resource-filters/filters/date-created/date-created-filter.component'; +import { FunderFilterComponent } from '@shared/components/resources/resource-filters/filters/funder/funder-filter.component'; +import { InstitutionFilterComponent } from '@shared/components/resources/resource-filters/filters/institution-filter/institution-filter.component'; +import { LicenseFilterComponent } from '@shared/components/resources/resource-filters/filters/license-filter/license-filter.component'; +import { PartOfCollectionFilterComponent } from '@shared/components/resources/resource-filters/filters/part-of-collection-filter/part-of-collection-filter.component'; +import { ProviderFilterComponent } from '@shared/components/resources/resource-filters/filters/provider-filter/provider-filter.component'; +import { ResourceTypeFilterComponent } from '@shared/components/resources/resource-filters/filters/resource-type-filter/resource-type-filter.component'; +import { ResourceFiltersOptionsSelectors } from '@shared/components/resources/resource-filters/filters/store/resource-filters-options.selectors'; +import { SubjectFilterComponent } from '@shared/components/resources/resource-filters/filters/subject/subject-filter.component'; import { ResourceFiltersComponent } from './resource-filters.component'; @@ -6,9 +23,38 @@ describe('ResourceFiltersComponent', () => { let component: ResourceFiltersComponent; let fixture: ComponentFixture; + const mockStore = { + selectSignal: jest.fn().mockImplementation((selector) => { + if (selector === ResourceFiltersOptionsSelectors.getDatesCreated) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getFunders) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getSubjects) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getLicenses) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getResourceTypes) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getInstitutions) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getProviders) return () => []; + if (selector === ResourceFiltersOptionsSelectors.getPartOfCollection) return () => []; + if (selector === SearchSelectors.getIsMyProfile) return () => false; + return () => null; + }), + }; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ResourceFiltersComponent], + imports: [ + ResourceFiltersComponent, + ...MockComponents( + CreatorsFilterComponent, + DateCreatedFilterComponent, + SubjectFilterComponent, + FunderFilterComponent, + LicenseFilterComponent, + ResourceTypeFilterComponent, + ProviderFilterComponent, + PartOfCollectionFilterComponent, + InstitutionFilterComponent + ), + ], + providers: [MockProvider(Store, mockStore), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(ResourceFiltersComponent); @@ -16,6 +62,10 @@ describe('ResourceFiltersComponent', () => { fixture.detectChanges(); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); diff --git a/src/app/shared/components/resources/resources-wrapper/resources-wrapper.component.spec.ts b/src/app/shared/components/resources/resources-wrapper/resources-wrapper.component.spec.ts index 7e2ac76aa..22cd3c4de 100644 --- a/src/app/shared/components/resources/resources-wrapper/resources-wrapper.component.spec.ts +++ b/src/app/shared/components/resources/resources-wrapper/resources-wrapper.component.spec.ts @@ -1,22 +1,88 @@ +import { Store } from '@ngxs/store'; + +import { MockComponent, MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { ResourceTab } from '@osf/features/search/models/resource-tab.enum'; +import { SearchSelectors } from '@osf/features/search/store'; +import { GetAllOptions } from '@shared/components/resources/resource-filters/filters/store/resource-filters-options.actions'; +import { ResourceFiltersSelectors } from '@shared/components/resources/resource-filters/store'; +import { ResourcesComponent } from '@shared/components/resources/resources.component'; import { ResourcesWrapperComponent } from './resources-wrapper.component'; describe('ResourcesWrapperComponent', () => { let component: ResourcesWrapperComponent; let fixture: ComponentFixture; + let store: jest.Mocked; + + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; + + const mockRouter = { + navigate: jest.fn(), + }; + + const mockRoute = { + queryParamMap: of({ + get: jest.fn(), + }), + snapshot: { + queryParams: {}, + queryParamMap: { + get: jest.fn(), + }, + }, + }; beforeEach(async () => { + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === ResourceFiltersSelectors.getCreator) return () => null; + if (selector === ResourceFiltersSelectors.getDateCreated) return () => null; + if (selector === ResourceFiltersSelectors.getFunder) return () => null; + if (selector === ResourceFiltersSelectors.getSubject) return () => null; + if (selector === ResourceFiltersSelectors.getLicense) return () => null; + if (selector === ResourceFiltersSelectors.getResourceType) return () => null; + if (selector === ResourceFiltersSelectors.getInstitution) return () => null; + if (selector === ResourceFiltersSelectors.getProvider) return () => null; + if (selector === ResourceFiltersSelectors.getPartOfCollection) return () => null; + if (selector === SearchSelectors.getSortBy) return () => '-relevance'; + if (selector === SearchSelectors.getSearchText) return () => ''; + if (selector === SearchSelectors.getResourceTab) return () => ResourceTab.All; + if (selector === SearchSelectors.getIsMyProfile) return () => false; + return () => null; + }); + await TestBed.configureTestingModule({ - imports: [ResourcesWrapperComponent], + imports: [ResourcesWrapperComponent, MockComponent(ResourcesComponent)], + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + MockProvider(Store, mockStore), + MockProvider(Router, mockRouter), + ], }).compileComponents(); fixture = TestBed.createComponent(ResourcesWrapperComponent); component = fixture.componentInstance; + store = TestBed.inject(Store) as jest.Mocked; fixture.detectChanges(); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with empty query params', () => { + expect(store.dispatch).toHaveBeenCalledWith(GetAllOptions); + }); }); diff --git a/src/app/shared/components/resources/resources.component.spec.ts b/src/app/shared/components/resources/resources.component.spec.ts index fec111b58..e581a05c7 100644 --- a/src/app/shared/components/resources/resources.component.spec.ts +++ b/src/app/shared/components/resources/resources.component.spec.ts @@ -1,22 +1,120 @@ +import { Store } from '@ngxs/store'; + +import { MockComponents, MockProvider } from 'ng-mocks'; + +import { BehaviorSubject } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ResourceTab } from '@osf/features/search/models/resource-tab.enum'; +import { GetResourcesByLink, SearchSelectors } from '@osf/features/search/store'; +import { IS_WEB, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; + +import { FilterChipsComponent } from './filter-chips/filter-chips.component'; +import { ResourceCardComponent } from './resource-card/resource-card.component'; +import { ResourceFiltersOptionsSelectors } from './resource-filters/filters/store/resource-filters-options.selectors'; +import { ResourceFiltersComponent } from './resource-filters/resource-filters.component'; +import { ResourceFiltersSelectors } from './resource-filters/store'; import { ResourcesComponent } from './resources.component'; describe('ResourcesComponent', () => { let component: ResourcesComponent; let fixture: ComponentFixture; + let store: jest.Mocked; + let isWebSubject: BehaviorSubject; + let isMobileSubject: BehaviorSubject; + + const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), + }; beforeEach(async () => { + isWebSubject = new BehaviorSubject(true); + isMobileSubject = new BehaviorSubject(false); + + mockStore.selectSignal.mockImplementation((selector) => { + if (selector === SearchSelectors.getResourceTab) return () => ResourceTab.All; + if (selector === SearchSelectors.getResourcesCount) return () => 100; + if (selector === SearchSelectors.getResources) return () => []; + if (selector === SearchSelectors.getSortBy) return () => '-relevance'; + if (selector === SearchSelectors.getFirst) return () => 'first-link'; + if (selector === SearchSelectors.getNext) return () => 'next-link'; + if (selector === SearchSelectors.getPrevious) return () => 'prev-link'; + if (selector === SearchSelectors.getIsMyProfile) return () => false; + if (selector === ResourceFiltersSelectors.getAllFilters) + return () => ({ + creator: { value: '' }, + dateCreated: { value: '' }, + funder: { value: '' }, + subject: { value: '' }, + license: { value: '' }, + resourceType: { value: '' }, + institution: { value: '' }, + provider: { value: '' }, + partOfCollection: { value: '' }, + }); + if (selector === ResourceFiltersOptionsSelectors.getAllOptions) + return () => ({ + datesCreated: [], + creators: [], + funders: [], + subjects: [], + licenses: [], + resourceTypes: [], + institutions: [], + providers: [], + partOfCollection: [], + }); + return () => null; + }); + await TestBed.configureTestingModule({ - imports: [ResourcesComponent], + imports: [ + ResourcesComponent, + ...MockComponents(ResourceFiltersComponent, ResourceCardComponent, FilterChipsComponent), + ], + providers: [ + MockProvider(Store, mockStore), + MockProvider(IS_WEB, isWebSubject), + MockProvider(IS_XSMALL, isMobileSubject), + ], }).compileComponents(); fixture = TestBed.createComponent(ResourcesComponent); component = fixture.componentInstance; + store = TestBed.inject(Store) as jest.Mocked; fixture.detectChanges(); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should switch page and dispatch to store', () => { + const link = 'next-page-link'; + component.switchPage(link); + + expect(store.dispatch).toHaveBeenCalledWith(new GetResourcesByLink(link)); + }); + + it('should show mobile layout when isMobile is true', () => { + isMobileSubject.next(true); + fixture.detectChanges(); + + const mobileSelect = fixture.nativeElement.querySelector('p-select'); + expect(mobileSelect).toBeTruthy(); + }); + + it('should show web layout when isWeb is true', () => { + isWebSubject.next(true); + fixture.detectChanges(); + + const webSortSelect = fixture.nativeElement.querySelector('.sorting-container p-select'); + expect(webSortSelect).toBeTruthy(); + }); }); diff --git a/src/app/shared/components/search-input/search-input.component.ts b/src/app/shared/components/search-input/search-input.component.ts index b52c1c628..87b6af8b4 100644 --- a/src/app/shared/components/search-input/search-input.component.ts +++ b/src/app/shared/components/search-input/search-input.component.ts @@ -5,7 +5,6 @@ import { FormsModule } from '@angular/forms'; @Component({ selector: 'osf-search-input', - standalone: true, imports: [InputText, FormsModule], templateUrl: './search-input.component.html', styleUrl: './search-input.component.scss', diff --git a/src/app/shared/mocks/index.ts b/src/app/shared/mocks/index.ts new file mode 100644 index 000000000..d66373b08 --- /dev/null +++ b/src/app/shared/mocks/index.ts @@ -0,0 +1,2 @@ +export { mockStore } from './mock-store.mock'; +export { TranslateServiceMock } from './translate.service.mock'; diff --git a/src/app/shared/mocks/mock-store.mock.ts b/src/app/shared/mocks/mock-store.mock.ts new file mode 100644 index 000000000..7f445a3ed --- /dev/null +++ b/src/app/shared/mocks/mock-store.mock.ts @@ -0,0 +1,4 @@ +export const mockStore = { + selectSignal: jest.fn(), + dispatch: jest.fn(), +}; diff --git a/src/app/shared/mocks/translate.service.mock.ts b/src/app/shared/mocks/translate.service.mock.ts new file mode 100644 index 000000000..e967391d1 --- /dev/null +++ b/src/app/shared/mocks/translate.service.mock.ts @@ -0,0 +1,9 @@ +import { TranslateService } from '@ngx-translate/core'; +import { MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + +export const TranslateServiceMock = MockProvider(TranslateService, { + instant: (key: string) => key, + get: (key: string) => of(key), +}); From daff558623b126d6be82c97425b023f290c47533 Mon Sep 17 00:00:00 2001 From: Nazar Semets Date: Wed, 14 May 2025 13:35:13 +0300 Subject: [PATCH 2/2] chore(unit-testing): fixed unit tests --- .../breadcrumb/breadcrumb.component.spec.ts | 38 ++++++++++++++++++- .../header/header.component.spec.ts | 6 ++- .../confirm-email.component.spec.ts | 21 +++++++++- .../account-settings.component.spec.ts | 8 +++- .../add-email/add-email.component.spec.ts | 18 +++++++-- .../affiliated-institutions.component.spec.ts | 14 ++++++- .../affiliated-institutions.component.ts | 4 +- .../change-password.component.spec.ts | 17 ++++++++- .../connected-emails.component.spec.ts | 21 +++++++++- .../connected-identities.component.spec.ts | 12 +++++- .../cancel-deactivation.component.spec.ts | 19 +++++++++- .../deactivation-warning.component.spec.ts | 19 +++++++++- .../deactivate-account.component.spec.ts | 21 +++++++++- ...default-storage-location.component.spec.ts | 14 ++++++- .../share-indexing.component.spec.ts | 12 +++++- .../configure-two-factor.component.spec.ts | 19 +++++++++- .../verify-two-factor.component.spec.ts | 20 +++++++++- .../two-factor-auth.component.spec.ts | 22 ++++++++++- 18 files changed, 283 insertions(+), 22 deletions(-) diff --git a/src/app/core/components/breadcrumb/breadcrumb.component.spec.ts b/src/app/core/components/breadcrumb/breadcrumb.component.spec.ts index 17a43e75d..591423245 100644 --- a/src/app/core/components/breadcrumb/breadcrumb.component.spec.ts +++ b/src/app/core/components/breadcrumb/breadcrumb.component.spec.ts @@ -1,22 +1,58 @@ +import { MockProvider } from 'ng-mocks'; + +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NavigationEnd, Router } from '@angular/router'; import { BreadcrumbComponent } from './breadcrumb.component'; describe('BreadcrumbComponent', () => { let component: BreadcrumbComponent; let fixture: ComponentFixture; + let router: Router; + + const mockRouter = { + url: '/test/path', + events: of(new NavigationEnd(1, '/test/path', '/test/path')), + }; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BreadcrumbComponent], + providers: [MockProvider(Router, mockRouter)], }).compileComponents(); + router = TestBed.inject(Router); fixture = TestBed.createComponent(BreadcrumbComponent); component = fixture.componentInstance; fixture.detectChanges(); }); - it('should create', () => { + it('should create and parse URL correctly', () => { expect(component).toBeTruthy(); + expect(component['url']()).toBe('/test/path'); + expect(component['parsedUrl']()).toEqual(['test', 'path']); + }); + + it('should not show breadcrumb for home page', () => { + Object.defineProperty(router, 'url', { value: '/home' }); + component['url'].set('/home'); + fixture.detectChanges(); + + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.breadcrumbs')).toBeNull(); + }); + + it('should show breadcrumb for valid path', () => { + Object.defineProperty(router, 'url', { value: '/settings/profile' }); + component['url'].set('/settings/profile'); + fixture.detectChanges(); + + const compiled = fixture.nativeElement as HTMLElement; + const breadcrumbs = compiled.querySelector('.breadcrumbs'); + expect(breadcrumbs).toBeTruthy(); + expect(breadcrumbs?.textContent).toContain('settings'); + expect(breadcrumbs?.textContent).toContain('profile'); }); }); diff --git a/src/app/core/components/header/header.component.spec.ts b/src/app/core/components/header/header.component.spec.ts index a0f0b884d..e06268316 100644 --- a/src/app/core/components/header/header.component.spec.ts +++ b/src/app/core/components/header/header.component.spec.ts @@ -1,11 +1,15 @@ import { provideStore } from '@ngxs/store'; +import { MockComponent } from 'ng-mocks'; + import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserState } from '@osf/core/store/user'; +import { BreadcrumbComponent } from '../breadcrumb/breadcrumb.component'; + import { HeaderComponent } from './header.component'; describe('HeaderComponent', () => { @@ -14,7 +18,7 @@ describe('HeaderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HeaderComponent], + imports: [HeaderComponent, MockComponent(BreadcrumbComponent)], providers: [provideStore([UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); diff --git a/src/app/features/home/components/confirm-email/confirm-email.component.spec.ts b/src/app/features/home/components/confirm-email/confirm-email.component.spec.ts index d0c783e86..9783770b5 100644 --- a/src/app/features/home/components/confirm-email/confirm-email.component.spec.ts +++ b/src/app/features/home/components/confirm-email/confirm-email.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider, MockProviders } from 'ng-mocks'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; + import { ConfirmEmailComponent } from './confirm-email.component'; describe('ConfirmEmailComponent', () => { @@ -8,7 +19,15 @@ describe('ConfirmEmailComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConfirmEmailComponent], + imports: [ConfirmEmailComponent, MockPipe(TranslatePipe)], + providers: [ + MockProvider(TranslateService), + MockProviders(DynamicDialogRef), + MockProvider(DynamicDialogConfig, { data: { emailAddress: 'test@email.com' } }), + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + ], }).compileComponents(); fixture = TestBed.createComponent(ConfirmEmailComponent); diff --git a/src/app/features/settings/account-settings/account-settings.component.spec.ts b/src/app/features/settings/account-settings/account-settings.component.spec.ts index e3e9308dc..9e934051b 100644 --- a/src/app/features/settings/account-settings/account-settings.component.spec.ts +++ b/src/app/features/settings/account-settings/account-settings.component.spec.ts @@ -1,6 +1,12 @@ +import { provideStore } from '@ngxs/store'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { UserState } from '@osf/core/store/user'; + import { AccountSettingsComponent } from './account-settings.component'; describe('AccountSettingsComponent', () => { @@ -10,7 +16,7 @@ describe('AccountSettingsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AccountSettingsComponent], - providers: [provideNoopAnimations()], + providers: [provideNoopAnimations(), provideHttpClient(), provideHttpClientTesting(), provideStore([UserState])], }).compileComponents(); fixture = TestBed.createComponent(AccountSettingsComponent); diff --git a/src/app/features/settings/account-settings/components/add-email/add-email.component.spec.ts b/src/app/features/settings/account-settings/components/add-email/add-email.component.spec.ts index 806a9fafe..c7ee54c24 100644 --- a/src/app/features/settings/account-settings/components/add-email/add-email.component.spec.ts +++ b/src/app/features/settings/account-settings/components/add-email/add-email.component.spec.ts @@ -1,9 +1,16 @@ -import { MockProvider } from 'ng-mocks'; +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe, MockProviders } from 'ng-mocks'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '../../store/account-settings.state'; + import { AddEmailComponent } from './add-email.component'; describe('AddEmailComponent', () => { @@ -12,8 +19,13 @@ describe('AddEmailComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddEmailComponent], - providers: [MockProvider(DynamicDialogRef)], + imports: [AddEmailComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProviders(DynamicDialogRef), + ], }).compileComponents(); fixture = TestBed.createComponent(AddEmailComponent); diff --git a/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.spec.ts b/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.spec.ts index eb12c0470..e05a4c891 100644 --- a/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.spec.ts +++ b/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe } from 'ng-mocks'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserState } from '@osf/core/store/user'; + +import { AccountSettingsState } from '../../store/account-settings.state'; + import { AffiliatedInstitutionsComponent } from './affiliated-institutions.component'; describe('AffiliatedInstitutionsComponent', () => { @@ -8,7 +19,8 @@ describe('AffiliatedInstitutionsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AffiliatedInstitutionsComponent], + imports: [AffiliatedInstitutionsComponent, MockPipe(TranslatePipe)], + providers: [provideStore([AccountSettingsState, UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(AffiliatedInstitutionsComponent); diff --git a/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.ts b/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.ts index 211e0160b..a3de613e2 100644 --- a/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.ts +++ b/src/app/features/settings/account-settings/components/affiliated-institutions/affiliated-institutions.component.ts @@ -2,8 +2,6 @@ import { Store } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { Button } from 'primeng/button'; - import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { UserSelectors } from '@core/store/user/user.selectors'; @@ -12,7 +10,7 @@ import { AccountSettingsSelectors } from '@osf/features/settings/account-setting @Component({ selector: 'osf-affiliated-institutions', - imports: [Button, TranslatePipe], + imports: [TranslatePipe], templateUrl: './affiliated-institutions.component.html', styleUrl: './affiliated-institutions.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/features/settings/account-settings/components/change-password/change-password.component.spec.ts b/src/app/features/settings/account-settings/components/change-password/change-password.component.spec.ts index 2ecf1439e..9d0eabf1c 100644 --- a/src/app/features/settings/account-settings/components/change-password/change-password.component.spec.ts +++ b/src/app/features/settings/account-settings/components/change-password/change-password.component.spec.ts @@ -1,5 +1,14 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '../../store/account-settings.state'; + import { ChangePasswordComponent } from './change-password.component'; describe('ChangePasswordComponent', () => { @@ -8,7 +17,13 @@ describe('ChangePasswordComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ChangePasswordComponent], + imports: [ChangePasswordComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(TranslateService), + ], }).compileComponents(); fixture = TestBed.createComponent(ChangePasswordComponent); diff --git a/src/app/features/settings/account-settings/components/connected-emails/connected-emails.component.spec.ts b/src/app/features/settings/account-settings/components/connected-emails/connected-emails.component.spec.ts index 0132f7023..c47f2a94a 100644 --- a/src/app/features/settings/account-settings/components/connected-emails/connected-emails.component.spec.ts +++ b/src/app/features/settings/account-settings/components/connected-emails/connected-emails.component.spec.ts @@ -1,5 +1,18 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProviders } from 'ng-mocks'; + +import { DialogService } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserState } from '@osf/core/store/user'; + +import { AccountSettingsState } from '../../store/account-settings.state'; + import { ConnectedEmailsComponent } from './connected-emails.component'; describe('ConnectedEmailsComponent', () => { @@ -8,7 +21,13 @@ describe('ConnectedEmailsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConnectedEmailsComponent], + imports: [ConnectedEmailsComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState, UserState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProviders(DialogService, TranslateService), + ], }).compileComponents(); fixture = TestBed.createComponent(ConnectedEmailsComponent); diff --git a/src/app/features/settings/account-settings/components/connected-identities/connected-identities.component.spec.ts b/src/app/features/settings/account-settings/components/connected-identities/connected-identities.component.spec.ts index c0bd40c20..09a8f0423 100644 --- a/src/app/features/settings/account-settings/components/connected-identities/connected-identities.component.spec.ts +++ b/src/app/features/settings/account-settings/components/connected-identities/connected-identities.component.spec.ts @@ -1,5 +1,14 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe } from 'ng-mocks'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '../../store/account-settings.state'; + import { ConnectedIdentitiesComponent } from './connected-identities.component'; describe('ConnectedIdentitiesComponent', () => { @@ -8,7 +17,8 @@ describe('ConnectedIdentitiesComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConnectedIdentitiesComponent], + imports: [ConnectedIdentitiesComponent, MockPipe(TranslatePipe)], + providers: [provideStore([AccountSettingsState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(ConnectedIdentitiesComponent); diff --git a/src/app/features/settings/account-settings/components/deactivate-account/components/cancel-deactivation/cancel-deactivation.component.spec.ts b/src/app/features/settings/account-settings/components/deactivate-account/components/cancel-deactivation/cancel-deactivation.component.spec.ts index 5c1d134eb..b082dbe94 100644 --- a/src/app/features/settings/account-settings/components/deactivate-account/components/cancel-deactivation/cancel-deactivation.component.spec.ts +++ b/src/app/features/settings/account-settings/components/deactivate-account/components/cancel-deactivation/cancel-deactivation.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; + import { CancelDeactivationComponent } from './cancel-deactivation.component'; describe('CancelDeactivationComponent', () => { @@ -8,7 +19,13 @@ describe('CancelDeactivationComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [CancelDeactivationComponent], + imports: [CancelDeactivationComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(DynamicDialogRef), + ], }).compileComponents(); fixture = TestBed.createComponent(CancelDeactivationComponent); diff --git a/src/app/features/settings/account-settings/components/deactivate-account/components/deactivation-warning/deactivation-warning.component.spec.ts b/src/app/features/settings/account-settings/components/deactivate-account/components/deactivation-warning/deactivation-warning.component.spec.ts index 15eb45a4e..beb26183a 100644 --- a/src/app/features/settings/account-settings/components/deactivate-account/components/deactivation-warning/deactivation-warning.component.spec.ts +++ b/src/app/features/settings/account-settings/components/deactivate-account/components/deactivation-warning/deactivation-warning.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; + import { DeactivationWarningComponent } from './deactivation-warning.component'; describe('DeactivationWarningComponent', () => { @@ -8,7 +19,13 @@ describe('DeactivationWarningComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeactivationWarningComponent], + imports: [DeactivationWarningComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(DynamicDialogRef), + ], }).compileComponents(); fixture = TestBed.createComponent(DeactivationWarningComponent); diff --git a/src/app/features/settings/account-settings/components/deactivate-account/deactivate-account.component.spec.ts b/src/app/features/settings/account-settings/components/deactivate-account/deactivate-account.component.spec.ts index db873e009..3c179e94f 100644 --- a/src/app/features/settings/account-settings/components/deactivate-account/deactivate-account.component.spec.ts +++ b/src/app/features/settings/account-settings/components/deactivate-account/deactivate-account.component.spec.ts @@ -1,4 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProviders } from 'ng-mocks'; + +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; + +import { AccountSettingsState } from '../../store/account-settings.state'; import { DeactivateAccountComponent } from './deactivate-account.component'; @@ -8,7 +20,14 @@ describe('DeactivateAccountComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeactivateAccountComponent], + imports: [DeactivateAccountComponent, MockPipe(TranslatePipe)], + providers: [ + provideNoopAnimations(), + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProviders(DynamicDialogRef, DialogService, TranslateService), + ], }).compileComponents(); fixture = TestBed.createComponent(DeactivateAccountComponent); diff --git a/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts b/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts index c4501c647..fb30ce923 100644 --- a/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts +++ b/src/app/features/settings/account-settings/components/default-storage-location/default-storage-location.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe } from 'ng-mocks'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserState } from '@osf/core/store/user'; + +import { AccountSettingsState } from '../../store/account-settings.state'; + import { DefaultStorageLocationComponent } from './default-storage-location.component'; describe('DefaultStorageLocationComponent', () => { @@ -8,7 +19,8 @@ describe('DefaultStorageLocationComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DefaultStorageLocationComponent], + imports: [DefaultStorageLocationComponent, MockPipe(TranslatePipe)], + providers: [provideStore([AccountSettingsState, UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(DefaultStorageLocationComponent); diff --git a/src/app/features/settings/account-settings/components/share-indexing/share-indexing.component.spec.ts b/src/app/features/settings/account-settings/components/share-indexing/share-indexing.component.spec.ts index e5e313f4d..d47be5f3d 100644 --- a/src/app/features/settings/account-settings/components/share-indexing/share-indexing.component.spec.ts +++ b/src/app/features/settings/account-settings/components/share-indexing/share-indexing.component.spec.ts @@ -1,5 +1,14 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe } from 'ng-mocks'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserState } from '@osf/core/store/user'; + import { ShareIndexingComponent } from './share-indexing.component'; describe('ShareIndexingComponent', () => { @@ -8,7 +17,8 @@ describe('ShareIndexingComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ShareIndexingComponent], + imports: [ShareIndexingComponent, MockPipe(TranslatePipe)], + providers: [provideStore([UserState]), provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(ShareIndexingComponent); diff --git a/src/app/features/settings/account-settings/components/two-factor-auth/components/configure-two-factor/configure-two-factor.component.spec.ts b/src/app/features/settings/account-settings/components/two-factor-auth/components/configure-two-factor/configure-two-factor.component.spec.ts index f6c406efb..6422e157e 100644 --- a/src/app/features/settings/account-settings/components/two-factor-auth/components/configure-two-factor/configure-two-factor.component.spec.ts +++ b/src/app/features/settings/account-settings/components/two-factor-auth/components/configure-two-factor/configure-two-factor.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; +import { MockPipe, MockProviders } from 'ng-mocks'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; + import { ConfigureTwoFactorComponent } from './configure-two-factor.component'; describe('ConfigureTwoFactorComponent', () => { @@ -8,7 +19,13 @@ describe('ConfigureTwoFactorComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConfigureTwoFactorComponent], + imports: [ConfigureTwoFactorComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProviders(DynamicDialogRef, DynamicDialogConfig), + ], }).compileComponents(); fixture = TestBed.createComponent(ConfigureTwoFactorComponent); diff --git a/src/app/features/settings/account-settings/components/two-factor-auth/components/verify-two-factor/verify-two-factor.component.spec.ts b/src/app/features/settings/account-settings/components/two-factor-auth/components/verify-two-factor/verify-two-factor.component.spec.ts index e8557132d..2c00de733 100644 --- a/src/app/features/settings/account-settings/components/two-factor-auth/components/verify-two-factor/verify-two-factor.component.spec.ts +++ b/src/app/features/settings/account-settings/components/two-factor-auth/components/verify-two-factor/verify-two-factor.component.spec.ts @@ -1,5 +1,16 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider, MockProviders } from 'ng-mocks'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state'; + import { VerifyTwoFactorComponent } from './verify-two-factor.component'; describe('VerifyTwoFactorComponent', () => { @@ -8,7 +19,14 @@ describe('VerifyTwoFactorComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [VerifyTwoFactorComponent], + imports: [VerifyTwoFactorComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(TranslateService), + MockProviders(DynamicDialogRef, DynamicDialogConfig), + ], }).compileComponents(); fixture = TestBed.createComponent(VerifyTwoFactorComponent); diff --git a/src/app/features/settings/account-settings/components/two-factor-auth/two-factor-auth.component.spec.ts b/src/app/features/settings/account-settings/components/two-factor-auth/two-factor-auth.component.spec.ts index d33668379..1f67c6405 100644 --- a/src/app/features/settings/account-settings/components/two-factor-auth/two-factor-auth.component.spec.ts +++ b/src/app/features/settings/account-settings/components/two-factor-auth/two-factor-auth.component.spec.ts @@ -1,5 +1,18 @@ +import { provideStore } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockPipe, MockProvider } from 'ng-mocks'; + +import { DialogService } from 'primeng/dynamicdialog'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserState } from '@osf/core/store/user'; + +import { AccountSettingsState } from '../../store/account-settings.state'; + import { TwoFactorAuthComponent } from './two-factor-auth.component'; describe('TwoFactorAuthComponent', () => { @@ -8,7 +21,14 @@ describe('TwoFactorAuthComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TwoFactorAuthComponent], + imports: [TwoFactorAuthComponent, MockPipe(TranslatePipe)], + providers: [ + provideStore([UserState, AccountSettingsState]), + provideHttpClient(), + provideHttpClientTesting(), + MockProvider(TranslateService), + MockProvider(DialogService), + ], }).compileComponents(); fixture = TestBed.createComponent(TwoFactorAuthComponent);