From a811bc608766e8a32516d0b8727c56010d86d554 Mon Sep 17 00:00:00 2001 From: Diana Date: Tue, 23 Sep 2025 16:02:54 +0300 Subject: [PATCH 1/6] test(moderation-components): added new tests --- .../add-moderator-dialog.component.spec.ts | 178 +++++++++++++-- .../bulk-upload/bulk-upload.component.spec.ts | 32 ++- ...n-moderation-submissions.component.spec.ts | 120 +++++++--- ...llection-submission-item.component.spec.ts | 166 +++++++++++--- ...tion-submission-overview.component.spec.ts | 49 +++- ...lection-submissions-list.component.spec.ts | 65 +++++- .../invite-moderator-dialog.component.spec.ts | 99 +++++++- .../moderators-list.component.spec.ts | 214 ++++++++++++++++-- .../moderators-table.component.spec.ts | 117 +++++++++- .../my-reviewing-navigation.component.spec.ts | 42 ++-- .../notification-settings.component.spec.ts | 13 +- 11 files changed, 941 insertions(+), 154 deletions(-) diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts index 8ba77e314..a5f6276d5 100644 --- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts @@ -1,45 +1,193 @@ -import { provideStore } from '@ngxs/store'; - -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponents, MockPipe, MockProviders } from 'ng-mocks'; +import { MockComponents, MockProvider } from 'ng-mocks'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { provideHttpClient } from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ModeratorsState } from '@osf/features/moderation/store/moderators'; import { CustomPaginatorComponent, LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; +import { MOCK_USER } from '@shared/mocks'; + +import { AddModeratorType } from '../../enums'; +import { ModeratorAddModel } from '../../models'; +import { ModeratorsSelectors } from '../../store/moderators'; import { AddModeratorDialogComponent } from './add-moderator-dialog.component'; -describe('AddModeratorDialogComponent', () => { +import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('Component: Add Moderator Dialog', () => { let component: AddModeratorDialogComponent; let fixture: ComponentFixture; + let mockDialogRef: jest.Mocked; + let mockDialogConfig: jest.Mocked; + + const mockUsers = [MOCK_USER]; beforeEach(async () => { + //TODO: rewrite it + mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; + mockDialogConfig = { + data: [], + } as jest.Mocked; + await TestBed.configureTestingModule({ imports: [ AddModeratorDialogComponent, - MockPipe(TranslatePipe), + OSFTestingModule, ...MockComponents(SearchInputComponent, LoadingSpinnerComponent, CustomPaginatorComponent), ], - teardown: { destroyAfterEach: false }, providers: [ - MockProviders(DynamicDialogRef, DynamicDialogConfig), - provideStore([ModeratorsState]), - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(DynamicDialogRef, mockDialogRef), + MockProvider(DynamicDialogConfig, mockDialogConfig), + provideMockStore({ + signals: [ + { selector: ModeratorsSelectors.getUsers, value: mockUsers }, + { selector: ModeratorsSelectors.isUsersLoading, value: false }, + { selector: ModeratorsSelectors.getUsersTotalCount, value: 2 }, + ], + }), ], }).compileComponents(); fixture = TestBed.createComponent(AddModeratorDialogComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + fixture.detectChanges(); + + expect(component.isInitialState()).toBe(true); + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + expect(component.rows()).toBe(10); + expect(component.selectedUsers()).toEqual([]); + expect(component.searchControl.value).toBe(''); + }); + + it('should load users on initialization', () => { + fixture.detectChanges(); + + expect(component.users()).toEqual(mockUsers); + expect(component.isLoading()).toBe(false); + expect(component.totalUsersCount()).toBe(2); + }); + + it('should add moderator and close dialog with search type', () => { + const mockSelectedUsers: ModeratorAddModel[] = [ + { + id: '1', + fullName: 'John Doe', + email: 'john@example.com', + permission: 'read' as any, + }, + ]; + component.selectedUsers.set(mockSelectedUsers); + + component.addModerator(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: mockSelectedUsers, + type: AddModeratorType.Search, + }); + }); + + it('should invite moderator and close dialog with invite type', () => { + component.inviteModerator(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [], + type: AddModeratorType.Invite, + }); + }); + + it('should handle page change correctly', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + const searchUsersSpy = jest.fn(); + component.actions = { + ...component.actions, + searchUsers: searchUsersSpy, + }; + + component.pageChanged(mockEvent); + + expect(component.currentPage()).toBe(2); + expect(component.first()).toBe(10); + expect(searchUsersSpy).toHaveBeenCalledWith('', 2); + }); + + it('should handle page change when page is null', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + const searchUsersSpy = jest.fn(); + component.actions = { + ...component.actions, + searchUsers: searchUsersSpy, + }; + + component.pageChanged(mockEvent); + + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + expect(searchUsersSpy).toHaveBeenCalledWith('', 1); + }); + + it('should clear users on destroy', () => { + const clearUsersSpy = jest.fn(); + component.actions = { + ...component.actions, + clearUsers: clearUsersSpy, + }; + + component.ngOnDestroy(); + + expect(clearUsersSpy).toHaveBeenCalled(); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.searchUsers).toBeDefined(); + expect(component.actions.clearUsers).toBeDefined(); + }); + + it('should handle search control value changes', () => { + jest.useFakeTimers(); + fixture.detectChanges(); + const searchUsersSpy = jest.fn().mockReturnValue(of({})); + component.actions = { + ...component.actions, + searchUsers: searchUsersSpy, + }; + + component.searchControl.setValue('test search'); + + jest.advanceTimersByTime(600); + + expect(searchUsersSpy).toHaveBeenCalledWith('test search', 1); + expect(component.isInitialState()).toBe(false); + expect(component.selectedUsers()).toEqual([]); + + jest.useRealTimers(); + }); + + it('should not search when search term is empty', () => { + fixture.detectChanges(); + const searchUsersSpy = jest.fn(); + component.actions = { + ...component.actions, + searchUsers: searchUsersSpy, + }; + + component.searchControl.setValue(''); + component.searchControl.setValue(' '); + + expect(searchUsersSpy).not.toHaveBeenCalled(); + }); }); diff --git a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts index 13ccbe4d5..ef14ae21c 100644 --- a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts +++ b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts @@ -1,20 +1,18 @@ -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 { BYTES_IN_MB, FILE_TYPES } from '../../constants'; + import { BulkUploadComponent } from './bulk-upload.component'; -describe('BulkUploadComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; + +describe('Component: Bulk Upload', () => { let component: BulkUploadComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [BulkUploadComponent, MockPipe(TranslatePipe)], - providers: [provideHttpClient(), provideHttpClientTesting()], + imports: [BulkUploadComponent, OSFTestingModule], }).compileComponents(); fixture = TestBed.createComponent(BulkUploadComponent); @@ -25,4 +23,22 @@ describe('BulkUploadComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have default maxSize', () => { + expect(component.maxSize()).toBe(BYTES_IN_MB); + }); + + it('should have default acceptTypes', () => { + expect(component.acceptTypes()).toBe(FILE_TYPES.CSV); + }); + + it('should accept custom maxSize input', () => { + fixture.componentRef.setInput('maxSize', 5 * BYTES_IN_MB); + expect(component.maxSize()).toBe(5 * BYTES_IN_MB); + }); + + it('should accept custom acceptTypes input', () => { + fixture.componentRef.setInput('acceptTypes', '.xlsx'); + expect(component.acceptTypes()).toBe('.xlsx'); + }); }); diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts index 7d1a590c8..649541734 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts @@ -1,30 +1,48 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponents, MockProvider } from 'ng-mocks'; -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponents, MockPipe } from 'ng-mocks'; - -import { 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 { ActivatedRoute, Router } from '@angular/router'; import { CollectionSubmissionsListComponent } from '@osf/features/moderation/components'; -import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; +import { CollectionsSelectors } from '@osf/shared/stores'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; -import { CollectionsState } from '@shared/stores'; +import { MOCK_PROVIDER } from '@shared/mocks'; + +import { SubmissionReviewStatus } from '../../enums'; +import { CollectionsModerationSelectors } from '../../store/collections-moderation'; import { CollectionModerationSubmissionsComponent } from './collection-moderation-submissions.component'; -describe('CollectionModerationSubmissionsComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('Component: Collection Moderation Submissions', () => { let component: CollectionModerationSubmissionsComponent; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: any; + + const mockCollectionProvider = MOCK_PROVIDER; + const mockSubmissions = [ + { + id: '1', + title: 'Test Submission', + status: SubmissionReviewStatus.Pending, + }, + ]; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withQueryParams({ status: 'pending', sortBy: 'date_created', page: '1' }) + .build(); + await TestBed.configureTestingModule({ imports: [ CollectionModerationSubmissionsComponent, + OSFTestingModule, ...MockComponents( SelectComponent, CollectionSubmissionsListComponent, @@ -32,22 +50,19 @@ describe('CollectionModerationSubmissionsComponent', () => { CustomPaginatorComponent, LoadingSpinnerComponent ), - MockPipe(TranslatePipe), ], providers: [ - provideStore([CollectionsState, CollectionsModerationState]), - provideHttpClient(), - provideHttpClientTesting(), - { - provide: ActivatedRoute, - useValue: { - snapshot: { - paramMap: { get: () => '1' }, - queryParams: {}, - }, - queryParams: of({}), - }, - }, + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [ + { selector: CollectionsSelectors.getCollectionProvider, value: mockCollectionProvider }, + { selector: CollectionsSelectors.getCollectionProviderLoading, value: false }, + { selector: CollectionsModerationSelectors.getCollectionSubmissionsLoading, value: false }, + { selector: CollectionsModerationSelectors.getCollectionSubmissions, value: mockSubmissions }, + { selector: CollectionsModerationSelectors.getCollectionSubmissionsTotalCount, value: 1 }, + ], + }), ], }).compileComponents(); @@ -59,4 +74,57 @@ describe('CollectionModerationSubmissionsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + expect(component.reviewStatus()).toBe(SubmissionReviewStatus.Pending); + expect(component.currentPage()).toBe('1'); + expect(component.pageSize).toBe(10); + expect(component.selectedSortOption()).toBeDefined(); + }); + + it('should change review status', () => { + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + expect(component.reviewStatus()).toBe(SubmissionReviewStatus.Accepted); + expect(component.currentPage()).toBe('1'); + }); + + it('should change sort option', () => { + component.changeSort('title'); + expect(component.selectedSortOption()).toBe('title'); + expect(component.currentPage()).toBe('1'); + }); + + it('should handle page change', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe('2'); + }); + + it('should handle page change when page is undefined', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe('1'); + }); + + it('should compute firstIndex correctly', () => { + component.currentPage.set('3'); + expect(component.firstIndex()).toBe(20); + }); + + it('should compute isLoading correctly', () => { + expect(component.isLoading()).toBe(false); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.getCollectionDetails).toBeDefined(); + expect(component.actions.searchCollectionSubmissions).toBeDefined(); + expect(component.actions.getCollectionSubmissions).toBeDefined(); + }); + + it('should initialize from query params', () => { + expect(component.reviewStatus()).toBe(SubmissionReviewStatus.Pending); + expect(component.selectedSortOption()).toBe('date_created'); + expect(component.currentPage()).toBe('1'); + }); }); diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts index d5c41db5a..2a48a8105 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts @@ -1,55 +1,161 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, MockPipes } from 'ng-mocks'; - -import { of } from 'rxjs'; - -import { provideHttpClient } from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CollectionSubmissionWithGuid } from '@osf/shared/models'; +import { CollectionsSelectors } from '@osf/shared/stores'; import { IconComponent } from '@shared/components'; import { DateAgoPipe } from '@shared/pipes'; -import { CollectionsState } from '@shared/stores'; + +import { SubmissionReviewStatus } from '../../enums'; import { CollectionSubmissionItemComponent } from './collection-submission-item.component'; -describe('SubmissionItemComponent', () => { +import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('Component: Collection Submission Item', () => { let component: CollectionSubmissionItemComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: ReturnType; + + const mockSubmission: CollectionSubmissionWithGuid = MOCK_COLLECTION_SUBMISSION_WITH_GUID; + + const mockCollectionProvider = { + id: 'provider-123', + name: 'Test Provider', + }; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create().withQueryParams({ status: 'pending' }).build(); + await TestBed.configureTestingModule({ - imports: [CollectionSubmissionItemComponent, MockComponent(IconComponent), MockPipes(DateAgoPipe, TranslatePipe)], + imports: [ + CollectionSubmissionItemComponent, + OSFTestingModule, + MockComponent(IconComponent), + MockPipe(DateAgoPipe), + ], providers: [ - { - provide: ActivatedRoute, - useValue: { - snapshot: { - paramMap: { get: () => '1' }, - queryParams: {}, - }, - queryParams: of({}), - }, - }, - provideStore([CollectionsState]), - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [{ selector: CollectionsSelectors.getCollectionProvider, value: mockCollectionProvider }], + }), ], }).compileComponents(); fixture = TestBed.createComponent(CollectionSubmissionItemComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; - componentRef.setInput('submission', {}); - fixture.detectChanges(); }); it('should create', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should initialize with submission input', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); + + expect(component.submission()).toEqual(mockSubmission); + }); + + it('should compute current review action correctly', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); + + const currentAction = component.currentReviewAction(); + expect(currentAction).toEqual(mockSubmission.actions![0]); + }); + + it('should return null for current review action when no actions exist', () => { + const submissionWithoutActions = { ...mockSubmission, actions: [] }; + fixture.componentRef.setInput('submission', submissionWithoutActions); + fixture.detectChanges(); + + const currentAction = component.currentReviewAction(); + expect(currentAction).toBeNull(); + }); + + it('should return null for current review action when actions is undefined', () => { + const submissionWithoutActions = { ...mockSubmission, actions: undefined }; + fixture.componentRef.setInput('submission', submissionWithoutActions); + fixture.detectChanges(); + + const currentAction = component.currentReviewAction(); + expect(currentAction).toBeNull(); + }); + + it('should compute current submission attributes correctly', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); + + const attributes = component.currentSubmissionAttributes(); + expect(attributes).toBeDefined(); + expect(Array.isArray(attributes)).toBe(true); + }); + + it('should return attributes even when submission has no actions', () => { + const submissionWithoutActions = { ...mockSubmission, actions: [] }; + fixture.componentRef.setInput('submission', submissionWithoutActions); + fixture.detectChanges(); + + const attributes = component.currentSubmissionAttributes(); + expect(attributes).toBeDefined(); + expect(attributes).not.toBeNull(); + expect(Array.isArray(attributes)).toBe(true); + expect(attributes!.length).toBeGreaterThan(0); + }); + + it('should return attributes with filtered null values', () => { + const submissionWithNullFields = { ...mockSubmission, programArea: null, collectedType: null, dataType: null }; + fixture.componentRef.setInput('submission', submissionWithNullFields); + fixture.detectChanges(); + + const attributes = component.currentSubmissionAttributes(); + expect(attributes).toBeDefined(); + expect(attributes).not.toBeNull(); + expect(Array.isArray(attributes)).toBe(true); + expect(attributes!.length).toBeGreaterThan(0); + }); + + it('should handle navigation correctly', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); + + component.handleNavigation(); + + expect(mockRouter.navigate).toHaveBeenCalledWith(['../', mockSubmission.nodeId], { + relativeTo: expect.any(Object), + queryParams: { status: 'pending', mode: 'moderation' }, + }); + }); + + it('should have SubmissionReviewStatus enum available', () => { + expect(component.SubmissionReviewStatus).toBe(SubmissionReviewStatus); + }); + + it('should load collection provider from store', () => { + fixture.componentRef.setInput('submission', mockSubmission); + fixture.detectChanges(); + + expect(component.collectionProvider()).toEqual(mockCollectionProvider); + }); + + it('should handle empty actions array', () => { + const submissionWithEmptyActions = { ...mockSubmission, actions: [] }; + fixture.componentRef.setInput('submission', submissionWithEmptyActions); + fixture.detectChanges(); + + const currentAction = component.currentReviewAction(); + expect(currentAction).toBeNull(); + }); }); diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts index 6b2fe2e5c..6dfd3ea5e 100644 --- a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts @@ -1,20 +1,33 @@ -import { MockComponent, MockProvider } from 'ng-mocks'; +import { MockComponents } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { ProjectOverviewComponent } from '@osf/features/project/overview/project-overview.component'; +import { Mode } from '@shared/enums'; import { CollectionSubmissionOverviewComponent } from './collection-submission-overview.component'; -describe.skip('CollectionSubmissionOverviewComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; + +describe('CollectionSubmissionOverviewComponent', () => { let component: CollectionSubmissionOverviewComponent; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: ReturnType; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create().withQueryParams({ mode: Mode.Moderation }).build(); + await TestBed.configureTestingModule({ - imports: [CollectionSubmissionOverviewComponent, MockComponent(ProjectOverviewComponent)], - providers: [MockProvider(Router)], + imports: [CollectionSubmissionOverviewComponent, OSFTestingModule, ...MockComponents(ProjectOverviewComponent)], + providers: [ + { provide: Router, useValue: mockRouter }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + ], }).compileComponents(); fixture = TestBed.createComponent(CollectionSubmissionOverviewComponent); @@ -25,4 +38,30 @@ describe.skip('CollectionSubmissionOverviewComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should compute isModerationMode correctly when mode is moderation', () => { + expect(component.isModerationMode()).toBe(true); + }); + + it('should compute isModerationMode correctly when mode is not moderation', () => { + mockActivatedRoute.snapshot!.queryParams = { mode: 'other' }; + fixture = TestBed.createComponent(CollectionSubmissionOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + expect(component.isModerationMode()).toBe(false); + }); + + it('should navigate away when not in moderation mode', () => { + mockActivatedRoute.snapshot!.queryParams = { mode: 'other' }; + fixture = TestBed.createComponent(CollectionSubmissionOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + expect(mockRouter.navigate).toHaveBeenCalledWith(['../'], { relativeTo: mockActivatedRoute }); + }); + + it('should not navigate when in moderation mode', () => { + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); }); diff --git a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts index 3ec050120..08557093e 100644 --- a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts +++ b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts @@ -1,31 +1,72 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponent } from 'ng-mocks'; -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 { CollectionSubmissionsListComponent } from '@osf/features/moderation/components'; -import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; +import { CollectionSubmissionItemComponent } from '@osf/features/moderation/components'; + +import { CollectionsModerationSelectors } from '../../store/collections-moderation'; + +import { CollectionSubmissionsListComponent } from './collection-submissions-list.component'; -describe('SubmissionsListComponent', () => { +import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('Component: Collection Submissions List', () => { let component: CollectionSubmissionsListComponent; let fixture: ComponentFixture; + const mockSubmissions = [MOCK_COLLECTION_SUBMISSION_WITH_GUID]; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [CollectionSubmissionsListComponent, MockPipe(TranslatePipe)], - providers: [provideStore([CollectionsModerationState]), provideHttpClient(), provideHttpClientTesting()], + imports: [CollectionSubmissionsListComponent, OSFTestingModule, MockComponent(CollectionSubmissionItemComponent)], + providers: [ + provideMockStore({ + signals: [{ selector: CollectionsModerationSelectors.getCollectionSubmissions, value: mockSubmissions }], + }), + ], }).compileComponents(); fixture = TestBed.createComponent(CollectionSubmissionsListComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should load submissions from store', () => { + fixture.detectChanges(); + + expect(component.submissions()).toEqual(mockSubmissions); + }); + + it('should handle empty submissions list', () => { + Object.defineProperty(component, 'submissions', { + value: () => [], + writable: true, + }); + + fixture.detectChanges(); + + expect(component.submissions()).toEqual([]); + }); + + it('should handle empty submissions array', () => { + Object.defineProperty(component, 'submissions', { + value: () => [], + writable: true, + }); + + fixture.detectChanges(); + + expect(component.submissions()).toEqual([]); + expect(component.submissions().length).toBe(0); + }); + + it('should have submissions selector defined', () => { + expect(component.submissions).toBeDefined(); + }); }); diff --git a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts index 998d1da48..9aaf77afc 100644 --- a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts @@ -1,27 +1,34 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponents, MockPipe, MockProvider } from 'ng-mocks'; +import { MockComponents, MockProvider } from 'ng-mocks'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; import { FormSelectComponent, TextInputComponent } from '@shared/components'; +import { AddModeratorType, ModeratorPermission } from '../../enums'; + import { InviteModeratorDialogComponent } from './invite-moderator-dialog.component'; +import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('InviteModeratorDialogComponent', () => { let component: InviteModeratorDialogComponent; let fixture: ComponentFixture; + let mockDialogRef: jest.Mocked; beforeEach(async () => { + //TODO: rewrite it + mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; + await TestBed.configureTestingModule({ imports: [ InviteModeratorDialogComponent, + OSFTestingModule, ...MockComponents(TextInputComponent, FormSelectComponent), - MockPipe(TranslatePipe), ], - providers: [MockProvider(DynamicDialogRef), MockProvider(ActivatedRoute)], + providers: [MockProvider(DynamicDialogRef, mockDialogRef)], }).compileComponents(); fixture = TestBed.createComponent(InviteModeratorDialogComponent); @@ -29,7 +36,89 @@ describe('InviteModeratorDialogComponent', () => { fixture.detectChanges(); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize form with default values', () => { + expect(component.moderatorForm).toBeDefined(); + expect(component.moderatorForm.get('fullName')?.value).toBe(''); + expect(component.moderatorForm.get('email')?.value).toBe(''); + expect(component.moderatorForm.get('permission')?.value).toBe(ModeratorPermission.Moderator); + }); + + it('should have input limits defined', () => { + expect(component.inputLimits).toBeDefined(); + }); + + it('should have permissions options defined', () => { + expect(component.permissionsOptions).toBeDefined(); + expect(component.permissionsOptions.length).toBeGreaterThan(0); + }); + + it('should search moderator and close dialog', () => { + component.searchModerator(); + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [], + type: AddModeratorType.Search, + }); + }); + + it('should submit form with valid data', () => { + component.moderatorForm.patchValue({ + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Admin, + }); + + component.submit(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [ + { + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Admin, + }, + ], + type: AddModeratorType.Invite, + }); + }); + + it('should not submit form with invalid data', () => { + component.moderatorForm.patchValue({ + fullName: '', + email: 'invalid-email', + permission: ModeratorPermission.Moderator, + }); + + component.submit(); + + expect(mockDialogRef.close).not.toHaveBeenCalled(); + }); + + it('should use default permission when not provided', () => { + component.moderatorForm.patchValue({ + fullName: 'John Doe', + email: 'john@example.com', + permission: null, + }); + + component.submit(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [ + { + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Moderator, + }, + ], + type: AddModeratorType.Invite, + }); + }); }); diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts index 8dc0f1d3d..0699285b9 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts @@ -1,59 +1,223 @@ -import { provideStore } from '@ngxs/store'; - -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponents, MockPipe, MockProvider } from 'ng-mocks'; +import { MockComponents, MockProvider } from 'ng-mocks'; import { 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 { UserState } from '@core/store/user'; +import { UserSelectors } from '@core/store/user'; import { ModeratorsTableComponent } from '@osf/features/moderation/components'; -import { ModeratorsState } from '@osf/features/moderation/store/moderators'; +import { ResourceType } from '@osf/shared/enums'; import { SearchInputComponent } from '@shared/components'; -import { MockCustomConfirmationServiceProvider, TranslateServiceMock } from '@shared/mocks'; -import { ToastService } from '@shared/services'; +import { MOCK_USER, TranslateServiceMock } from '@shared/mocks'; +import { CustomConfirmationService } from '@shared/services'; + +import { ModeratorPermission } from '../../enums'; +import { ModeratorModel } from '../../models'; +import { ModeratorsSelectors } from '../../store/moderators'; import { ModeratorsListComponent } from './moderators-list.component'; -describe('ModeratorsListComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom-confirmation-provider.mock'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('Component: Moderators List', () => { let component: ModeratorsListComponent; let fixture: ComponentFixture; + let mockActivatedRoute: ReturnType; + let customConfirmationServiceMock: ReturnType; + + const mockProviderId = 'test-provider-123'; + const mockResourceType = ResourceType.Preprint; + const mockCurrentUser = MOCK_USER; - const mockRoute = { - data: of({ resourceType: undefined }), - }; + const mockModerators: ModeratorModel[] = [ + { + id: '1', + userId: 'user-1', + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Admin, + isActive: true, + }, + { + id: '2', + userId: 'user-2', + fullName: 'Jane Smith', + email: 'jane@example.com', + permission: ModeratorPermission.Read, + isActive: true, + }, + ]; beforeEach(async () => { + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withParams({ providerId: mockProviderId }) + .withData({ resourceType: mockResourceType }) + .build(); + customConfirmationServiceMock = CustomConfirmationServiceMockBuilder.create().build(); + await TestBed.configureTestingModule({ imports: [ ModeratorsListComponent, + OSFTestingModule, ...MockComponents(ModeratorsTableComponent, SearchInputComponent), - MockPipe(TranslatePipe), ], providers: [ + MockProvider(ActivatedRoute, mockActivatedRoute), + MockProvider(CustomConfirmationService, customConfirmationServiceMock), TranslateServiceMock, - MockCustomConfirmationServiceProvider, - MockProvider(ToastService), - { - provide: ActivatedRoute, - useValue: mockRoute, - }, - provideStore([ModeratorsState, UserState]), - provideHttpClient(), - provideHttpClientTesting(), + provideMockStore({ + signals: [ + { selector: UserSelectors.getCurrentUser, value: mockCurrentUser }, + { selector: ModeratorsSelectors.getModerators, value: mockModerators }, + { selector: ModeratorsSelectors.isModeratorsLoading, value: false }, + ], + }), ], }).compileComponents(); fixture = TestBed.createComponent(ModeratorsListComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should initialize with correct values', () => { + fixture.detectChanges(); + + expect(component.providerId()).toBe(mockProviderId); + expect(component.resourceType()).toBe(mockResourceType); + expect(component.searchControl.value).toBe(''); + expect(component.moderators()).toEqual(mockModerators); + expect(component.isModeratorsLoading()).toBe(false); + expect(component.currentUser()).toEqual(mockCurrentUser); + }); + + it('should return false for admin moderator when user is not admin', () => { + const nonAdminModerators = mockModerators.map((mod) => ({ + ...mod, + permission: ModeratorPermission.Read, + })); + + Object.defineProperty(component, 'initialModerators', { + value: () => nonAdminModerators, + writable: true, + }); + + fixture.detectChanges(); + + expect(component.isCurrentUserAdminModerator()).toBe(false); + }); + + it('should return false for admin moderator when user is not found', () => { + Object.defineProperty(component, 'currentUser', { + value: () => null, + writable: true, + }); + + fixture.detectChanges(); + + expect(component.isCurrentUserAdminModerator()).toBe(false); + }); + + it('should load moderators on initialization', () => { + const loadModeratorsSpy = jest.fn(); + component.actions = { + ...component.actions, + loadModerators: loadModeratorsSpy, + }; + + component.ngOnInit(); + + expect(loadModeratorsSpy).toHaveBeenCalledWith(mockProviderId, mockResourceType); + }); + + it('should set search subscription on initialization', () => { + const setSearchSubscriptionSpy = jest.fn(); + (component as any).setSearchSubscription = setSearchSubscriptionSpy; + + component.ngOnInit(); + + expect(setSearchSubscriptionSpy).toHaveBeenCalled(); + }); + + it('should handle search control value changes', () => { + jest.useFakeTimers(); + fixture.detectChanges(); + const updateSearchValueSpy = jest.fn(); + const loadModeratorsSpy = jest.fn().mockReturnValue(of({})); + component.actions = { + ...component.actions, + updateSearchValue: updateSearchValueSpy, + loadModerators: loadModeratorsSpy, + }; + + component.searchControl.setValue('test search'); + + jest.advanceTimersByTime(600); + + expect(updateSearchValueSpy).toHaveBeenCalledWith('test search'); + expect(loadModeratorsSpy).toHaveBeenCalledWith(mockProviderId, mockResourceType); + + jest.useRealTimers(); + }); + + it('should handle empty search value', () => { + jest.useFakeTimers(); + fixture.detectChanges(); + const updateSearchValueSpy = jest.fn(); + const loadModeratorsSpy = jest.fn().mockReturnValue(of({})); + component.actions = { + ...component.actions, + updateSearchValue: updateSearchValueSpy, + loadModerators: loadModeratorsSpy, + }; + + component.searchControl.setValue(''); + + jest.advanceTimersByTime(600); + + expect(updateSearchValueSpy).toHaveBeenCalledWith(null); + expect(loadModeratorsSpy).toHaveBeenCalledWith(mockProviderId, mockResourceType); + + jest.useRealTimers(); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.loadModerators).toBeDefined(); + expect(component.actions.updateSearchValue).toBeDefined(); + expect(component.actions.addModerators).toBeDefined(); + expect(component.actions.updateModerator).toBeDefined(); + expect(component.actions.deleteModerator).toBeDefined(); + }); + + it('should update moderators when initial moderators change', () => { + const newModerators = [ + ...mockModerators, + { + id: '3', + userId: 'user-3', + fullName: 'Bob Wilson', + email: 'bob@example.com', + permission: ModeratorPermission.Read, + isActive: true, + }, + ]; + + Object.defineProperty(component, 'initialModerators', { + value: () => newModerators, + writable: true, + }); + + fixture.detectChanges(); + + expect(component.moderators()).toEqual(newModerators); + }); }); diff --git a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts index 42b937eb0..96c0b9d6d 100644 --- a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts +++ b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts @@ -1,29 +1,134 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, MockPipe } from 'ng-mocks'; +import { MockComponent } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SelectComponent } from '@shared/components'; -import { TranslateServiceMock } from '@shared/mocks'; + +import { ModeratorPermission } from '../../enums'; +import { ModeratorModel } from '../../models'; import { ModeratorsTableComponent } from './moderators-table.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('ModeratorsTableComponent', () => { let component: ModeratorsTableComponent; let fixture: ComponentFixture; + const mockModerators: ModeratorModel[] = [ + { + id: '1', + userId: 'user-1', + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Admin, + isActive: true, + education: [], + employment: [], + }, + { + id: '2', + userId: 'user-2', + fullName: 'Jane Smith', + email: 'jane@example.com', + permission: ModeratorPermission.Read, + isActive: true, + education: [], + employment: [], + }, + ]; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ModeratorsTableComponent, MockComponent(SelectComponent), MockPipe(TranslatePipe)], - providers: [TranslateServiceMock], + imports: [ModeratorsTableComponent, OSFTestingModule, MockComponent(SelectComponent)], }).compileComponents(); fixture = TestBed.createComponent(ModeratorsTableComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should handle input values correctly', () => { + fixture.componentRef.setInput('items', mockModerators); + fixture.componentRef.setInput('isLoading', true); + fixture.componentRef.setInput('currentUserId', 'current-user-123'); + fixture.componentRef.setInput('isCurrentUserAdminModerator', true); + + fixture.detectChanges(); + + expect(component.items()).toEqual(mockModerators); + expect(component.isLoading()).toBe(true); + expect(component.currentUserId()).toBe('current-user-123'); + expect(component.isCurrentUserAdminModerator()).toBe(true); + }); + + it('should emit update event when updatePermission is called', () => { + jest.spyOn(component.update, 'emit'); + const moderator = mockModerators[0]; + + component.updatePermission(moderator); + + expect(component.update.emit).toHaveBeenCalledWith(moderator); + }); + + it('should emit remove event when removeModerator is called', () => { + jest.spyOn(component.remove, 'emit'); + const moderator = mockModerators[0]; + + component.removeModerator(moderator); + + expect(component.remove.emit).toHaveBeenCalledWith(moderator); + }); + + it('should have skeleton data for loading state', () => { + expect(component.skeletonData).toBeDefined(); + expect(component.skeletonData.length).toBe(3); + expect(component.skeletonData.every((item) => typeof item === 'object')).toBe(true); + }); + + it('should have dialog service injected', () => { + expect(component.dialogService).toBeDefined(); + }); + + it('should have translate service injected', () => { + expect(component.translateService).toBeDefined(); + }); + + it('should open education history dialog', () => { + const moderator = mockModerators[0]; + jest.spyOn(component.dialogService, 'open'); + + component.openEducationHistory(moderator); + + expect(component.dialogService.open).toHaveBeenCalled(); + }); + + it('should open employment history dialog', () => { + const moderator = mockModerators[0]; + jest.spyOn(component.dialogService, 'open'); + + component.openEmploymentHistory(moderator); + + expect(component.dialogService.open).toHaveBeenCalled(); + }); + + it('should handle empty items array', () => { + fixture.componentRef.setInput('items', []); + + fixture.detectChanges(); + + expect(component.items()).toEqual([]); + }); + + it('should handle undefined currentUserId', () => { + fixture.componentRef.setInput('currentUserId', undefined); + + fixture.detectChanges(); + + expect(component.currentUserId()).toBeUndefined(); + }); }); diff --git a/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts b/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts index 3faaac3a5..66c2854a4 100644 --- a/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts +++ b/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts @@ -1,34 +1,50 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockPipe, MockProvider } from 'ng-mocks'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { of } from 'rxjs'; +import { MOCK_PROVIDER } from '@shared/mocks'; -import { ComponentRef } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; +import { PreprintModerationTab } from '../../enums'; import { MyReviewingNavigationComponent } from './my-reviewing-navigation.component'; -describe('MyReviewingNavigationComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; + +describe('Component: My Reviewing Navigation', () => { let component: MyReviewingNavigationComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; + const mockProvider = MOCK_PROVIDER; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [MyReviewingNavigationComponent, MockPipe(TranslatePipe)], - providers: [MockProvider(ActivatedRoute, { params: of({}) })], + imports: [MyReviewingNavigationComponent, OSFTestingModule], }).compileComponents(); fixture = TestBed.createComponent(MyReviewingNavigationComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; - - componentRef.setInput('provider', {}); + fixture.componentRef.setInput('provider', mockProvider); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have provider input', () => { + expect(component.provider()).toEqual(mockProvider); + }); + + it('should have tab options defined', () => { + expect(component.tabOptions).toBeDefined(); + expect(component.tabOptions.length).toBeGreaterThan(0); + }); + + it('should have tab option enum defined', () => { + expect(component.tabOption).toBe(PreprintModerationTab); + }); + + it('should accept custom provider input', () => { + const customProvider = { ...mockProvider, name: 'Custom Provider' }; + fixture.componentRef.setInput('provider', customProvider); + expect(component.provider()).toEqual(customProvider); + }); }); diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts index 993d6128d..ec8054888 100644 --- a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts +++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts @@ -1,21 +1,16 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockPipe, MockProvider } from 'ng-mocks'; - -import { of } from 'rxjs'; - import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; import { NotificationSettingsComponent } from './notification-settings.component'; -describe('NotificationSettingsComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; + +describe('Component: Notification Settings', () => { let component: NotificationSettingsComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NotificationSettingsComponent, MockPipe(TranslatePipe)], - providers: [MockProvider(ActivatedRoute, { params: of({}) })], + imports: [NotificationSettingsComponent, OSFTestingModule], }).compileComponents(); fixture = TestBed.createComponent(NotificationSettingsComponent); From e2ddc4c79fd387d58ac80369ec66251c64c1fc3d Mon Sep 17 00:00:00 2001 From: Diana Date: Tue, 23 Sep 2025 17:12:54 +0300 Subject: [PATCH 2/6] test(moderation-components): added new tests --- .../add-moderator-dialog.component.spec.ts | 2 +- .../bulk-upload/bulk-upload.component.spec.ts | 2 +- ...n-moderation-submissions.component.spec.ts | 2 +- ...llection-submission-item.component.spec.ts | 2 +- ...lection-submissions-list.component.spec.ts | 2 +- .../moderators-list.component.spec.ts | 2 +- .../my-reviewing-navigation.component.spec.ts | 2 +- .../notification-settings.component.spec.ts | 2 +- ...int-recent-activity-list.component.spec.ts | 100 +++++++++- ...preprint-submission-item.component.spec.ts | 112 ++++++++--- .../preprint-submissions.component.spec.ts | 175 +++++++++++++++-- ...t-withdrawal-submissions.component.spec.ts | 181 +++++++++++++++--- ...stry-pending-submissions.component.spec.ts | 168 +++++++++++++--- .../registry-settings.component.spec.ts | 20 +- ...registry-submission-item.component.spec.ts | 119 ++++++++---- .../registry-submissions.component.spec.ts | 170 +++++++++++++--- src/app/shared/mocks/provider.mock.ts | 5 + src/testing/mocks/submission.mock.ts | 78 ++++++++ 18 files changed, 958 insertions(+), 186 deletions(-) create mode 100644 src/testing/mocks/submission.mock.ts diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts index a5f6276d5..056049730 100644 --- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts @@ -19,7 +19,7 @@ import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { provideMockStore } from '@testing/providers/store-provider.mock'; -describe('Component: Add Moderator Dialog', () => { +describe('AddModeratorDialogComponent', () => { let component: AddModeratorDialogComponent; let fixture: ComponentFixture; let mockDialogRef: jest.Mocked; diff --git a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts index ef14ae21c..54dc588b9 100644 --- a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts +++ b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.spec.ts @@ -6,7 +6,7 @@ import { BulkUploadComponent } from './bulk-upload.component'; import { OSFTestingModule } from '@testing/osf.testing.module'; -describe('Component: Bulk Upload', () => { +describe('BulkUploadComponent', () => { let component: BulkUploadComponent; let fixture: ComponentFixture; diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts index 649541734..a43866da2 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts @@ -18,7 +18,7 @@ import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.moc import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; import { provideMockStore } from '@testing/providers/store-provider.mock'; -describe('Component: Collection Moderation Submissions', () => { +describe('CollectionModerationSubmissionsComponent', () => { let component: CollectionModerationSubmissionsComponent; let fixture: ComponentFixture; let mockRouter: ReturnType; diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts index 2a48a8105..2c1e68271 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.spec.ts @@ -18,7 +18,7 @@ import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.moc import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; import { provideMockStore } from '@testing/providers/store-provider.mock'; -describe('Component: Collection Submission Item', () => { +describe('CollectionSubmissionItemComponent', () => { let component: CollectionSubmissionItemComponent; let fixture: ComponentFixture; let mockRouter: ReturnType; diff --git a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts index 08557093e..18a16b0a8 100644 --- a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts +++ b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts @@ -12,7 +12,7 @@ import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission. import { OSFTestingModule } from '@testing/osf.testing.module'; import { provideMockStore } from '@testing/providers/store-provider.mock'; -describe('Component: Collection Submissions List', () => { +describe('CollectionSubmissionsListComponent', () => { let component: CollectionSubmissionsListComponent; let fixture: ComponentFixture; diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts index 0699285b9..a018d225b 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts @@ -23,7 +23,7 @@ import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom- import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { provideMockStore } from '@testing/providers/store-provider.mock'; -describe('Component: Moderators List', () => { +describe('ModeratorsListComponent', () => { let component: ModeratorsListComponent; let fixture: ComponentFixture; let mockActivatedRoute: ReturnType; diff --git a/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts b/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts index 66c2854a4..122953240 100644 --- a/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts +++ b/src/app/features/moderation/components/my-reviewing-navigation/my-reviewing-navigation.component.spec.ts @@ -8,7 +8,7 @@ import { MyReviewingNavigationComponent } from './my-reviewing-navigation.compon import { OSFTestingModule } from '@testing/osf.testing.module'; -describe('Component: My Reviewing Navigation', () => { +describe('MyReviewingNavigationComponent', () => { let component: MyReviewingNavigationComponent; let fixture: ComponentFixture; diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts index ec8054888..a5411f8d7 100644 --- a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts +++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts @@ -4,7 +4,7 @@ import { NotificationSettingsComponent } from './notification-settings.component import { OSFTestingModule } from '@testing/osf.testing.module'; -describe('Component: Notification Settings', () => { +describe('NotificationSettingsComponent', () => { let component: NotificationSettingsComponent; let fixture: ComponentFixture; diff --git a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts index b3332b1ef..befaf6636 100644 --- a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts +++ b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts @@ -1,37 +1,121 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponents, MockPipes } from 'ng-mocks'; +import { MockComponents, MockPipe } from 'ng-mocks'; import { DatePipe } from '@angular/common'; -import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomPaginatorComponent, IconComponent } from '@osf/shared/components'; +import { PreprintReviewActionModel } from '../../models'; + import { PreprintRecentActivityListComponent } from './preprint-recent-activity-list.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('PreprintRecentActivityListComponent', () => { let component: PreprintRecentActivityListComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; + const mockReviews: PreprintReviewActionModel[] = [ + { + id: '1', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + preprint: { + id: 'preprint-1', + name: 'Test Preprint', + }, + provider: { + id: 'provider-1', + name: 'Test Provider', + }, + }, + ]; + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ PreprintRecentActivityListComponent, + OSFTestingModule, ...MockComponents(IconComponent, CustomPaginatorComponent), - MockPipes(TranslatePipe, DatePipe), + MockPipe(DatePipe), ], }).compileComponents(); fixture = TestBed.createComponent(PreprintRecentActivityListComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; - - componentRef.setInput('reviews', []); + fixture.componentRef.setInput('reviews', mockReviews); + fixture.componentRef.setInput('isLoading', false); + fixture.componentRef.setInput('totalCount', 1); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have input values', () => { + expect(component.reviews()).toEqual(mockReviews); + expect(component.isLoading()).toBe(false); + expect(component.totalCount()).toBe(1); + }); + + it('should have default first value', () => { + expect(component.first()).toBe(0); + }); + + it('should have constants defined', () => { + expect(component.reviewStatusIcon).toBeDefined(); + expect(component.preprintReviewStatus).toBeDefined(); + }); + + it('should emit page change event', () => { + jest.spyOn(component.pageChanged, 'emit'); + const mockEvent = { page: 2, first: 10, rows: 10 }; + + component.onPageChange(mockEvent); + + expect(component.pageChanged.emit).toHaveBeenCalledWith(2); + }); + + it('should emit page 1 when page is undefined', () => { + jest.spyOn(component.pageChanged, 'emit'); + const mockEvent = { page: undefined, first: 0, rows: 10 }; + + component.onPageChange(mockEvent); + + expect(component.pageChanged.emit).toHaveBeenCalledWith(1); + }); + + it('should accept custom input values', () => { + const customReviews = [ + ...mockReviews, + { + id: '2', + fromState: 'pending', + toState: 'approved', + dateModified: '2023-01-02', + creator: { id: 'user-2', name: 'Jane Doe' }, + preprint: { + id: 'preprint-2', + name: 'Test Preprint 2', + }, + provider: { + id: 'provider-2', + name: 'Test Provider 2', + }, + }, + ]; + fixture.componentRef.setInput('reviews', customReviews); + fixture.componentRef.setInput('isLoading', true); + fixture.componentRef.setInput('totalCount', 2); + + expect(component.reviews()).toEqual(customReviews); + expect(component.isLoading()).toBe(true); + expect(component.totalCount()).toBe(2); + }); }); diff --git a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts index e06b5e076..c60818e56 100644 --- a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts @@ -1,54 +1,122 @@ import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, MockPipes } from 'ng-mocks'; +import { MockComponents, MockPipes } from 'ng-mocks'; -import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { IconComponent } from '@shared/components'; -import { DateAgoPipe } from '@shared/pipes'; +import { IconComponent } from '@osf/shared/components'; +import { DateAgoPipe } from '@osf/shared/pipes'; + +import { SubmissionReviewStatus } from '../../enums'; +import { PreprintSubmission } from '../../models'; import { PreprintSubmissionItemComponent } from './preprint-submission-item.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('PreprintSubmissionItemComponent', () => { let component: PreprintSubmissionItemComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; - const mockSubmission = { - id: 'reg1', - title: 'Test registry submission', + const mockSubmission: PreprintSubmission = { + id: '1', + title: 'Test Preprint', reviewsState: 'pending', - public: true, - embargoed: false, - embargoEndDate: null, + public: false, actions: [ { - id: 'a1', + id: '1', + trigger: 'manual', fromState: 'pending', - toState: 'accepted', - dateModified: new Date().toISOString(), - creator: { id: 'u1', name: 'Bob' }, - comment: '', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Test comment', }, ], }; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [PreprintSubmissionItemComponent, MockComponent(IconComponent), MockPipes(DateAgoPipe, TranslatePipe)], + imports: [ + PreprintSubmissionItemComponent, + OSFTestingModule, + ...MockComponents(IconComponent), + MockPipes(DateAgoPipe, TranslatePipe), + ], }).compileComponents(); fixture = TestBed.createComponent(PreprintSubmissionItemComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; - - componentRef.setInput('status', 'pending'); - componentRef.setInput('submission', mockSubmission); - + fixture.componentRef.setInput('status', SubmissionReviewStatus.Pending); + fixture.componentRef.setInput('submission', mockSubmission); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have input values', () => { + expect(component.status()).toBe(SubmissionReviewStatus.Pending); + expect(component.submission()).toEqual(mockSubmission); + }); + + it('should have constants defined', () => { + expect(component.reviewStatusIcon).toBeDefined(); + expect(component.actionLabel).toBeDefined(); + expect(component.actionState).toBeDefined(); + }); + + it('should have default values', () => { + expect(component.limitValue).toBe(1); + expect(component.showAll).toBe(false); + }); + + it('should toggle history', () => { + expect(component.showAll).toBe(false); + + component.toggleHistory(); + expect(component.showAll).toBe(true); + + component.toggleHistory(); + expect(component.showAll).toBe(false); + }); + + it('should emit selected event', () => { + jest.spyOn(component.selected, 'emit'); + component.selected.emit(); + expect(component.selected.emit).toHaveBeenCalled(); + }); + + it('should accept custom input values', () => { + const customStatus = SubmissionReviewStatus.Accepted; + const customSubmission = { + ...mockSubmission, + title: 'Custom Preprint', + actions: [ + ...mockSubmission.actions, + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Approved', + }, + ], + }; + + fixture.componentRef.setInput('status', customStatus); + fixture.componentRef.setInput('submission', customSubmission); + + expect(component.status()).toBe(customStatus); + expect(component.submission()).toEqual(customSubmission); + }); }); diff --git a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts index 40ef30b9e..11a2a2803 100644 --- a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts +++ b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts @@ -1,26 +1,82 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponents, MockProvider } from 'ng-mocks'; -import { MockComponents } from 'ng-mocks'; - -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 { ActivatedRoute, Router } from '@angular/router'; import { PreprintSubmissionItemComponent } from '@osf/features/moderation/components'; -import { PreprintModerationState } from '@osf/features/moderation/store/preprint-moderation'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; +import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums'; +import { PreprintModerationSelectors } from '../../store/preprint-moderation'; + import { PreprintSubmissionsComponent } from './preprint-submissions.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('PreprintSubmissionsComponent', () => { let component: PreprintSubmissionsComponent; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: ReturnType; + + const mockProviderId = 'test-provider-id'; + const mockSubmissions = [ + { + id: '1', + title: 'Test Preprint 1', + reviewsState: 'pending', + public: false, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Test comment', + }, + ], + }, + { + id: '2', + title: 'Test Preprint 2', + reviewsState: 'accepted', + public: true, + actions: [ + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Approved', + }, + ], + }, + ]; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withParams({ providerId: mockProviderId }) + .withQueryParams({ status: 'pending' }) + .build(); + await TestBed.configureTestingModule({ imports: [ PreprintSubmissionsComponent, + OSFTestingModule, ...MockComponents( SelectComponent, IconComponent, @@ -30,18 +86,18 @@ describe('PreprintSubmissionsComponent', () => { ), ], providers: [ - { - provide: ActivatedRoute, - useValue: { - snapshot: { - params: {}, - queryParams: {}, - }, - }, - }, - provideStore([PreprintModerationState]), - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [ + { selector: PreprintModerationSelectors.getPreprintSubmissions, value: mockSubmissions }, + { selector: PreprintModerationSelectors.arePreprintSubmissionsLoading, value: false }, + { selector: PreprintModerationSelectors.getPreprintSubmissionsPendingCount, value: 1 }, + { selector: PreprintModerationSelectors.getPreprintSubmissionsAcceptedCount, value: 0 }, + { selector: PreprintModerationSelectors.getPreprintSubmissionsRejectedCount, value: 0 }, + { selector: PreprintModerationSelectors.getPreprintSubmissionsWithdrawnCount, value: 0 }, + ], + }), ], }).compileComponents(); @@ -53,4 +109,85 @@ describe('PreprintSubmissionsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + expect(component.currentPage()).toBe(1); + expect(component.pageSize()).toBe(10); + expect(component.first()).toBe(0); + expect(component.selectedSortOption()).toBe(PreprintSubmissionsSort.Newest); + expect(component.selectedReviewOption()).toBeDefined(); + }); + + it('should have sort options defined', () => { + expect(component.sortOptions).toBeDefined(); + expect(component.sortOptions.length).toBeGreaterThan(0); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.getPreprintSubmissions).toBeDefined(); + }); + + it('should compute submission review options with counts', () => { + const options = component.submissionReviewOptions(); + expect(options).toBeDefined(); + expect(options.length).toBeGreaterThan(0); + expect(options[0]).toHaveProperty('count'); + }); + + it('should compute total count', () => { + expect(component.totalCount()).toBeDefined(); + }); + + it('should change review status', () => { + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Accepted); + expect(mockRouter.navigate).toHaveBeenCalledWith( + [], + expect.objectContaining({ + queryParams: { status: SubmissionReviewStatus.Accepted }, + }) + ); + }); + + it('should change sort option', () => { + component.changeSort(PreprintSubmissionsSort.Oldest); + expect(component.selectedSortOption()).toBe(PreprintSubmissionsSort.Oldest); + }); + + it('should handle page change', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(2); + expect(component.first()).toBe(10); + }); + + it('should handle page change when page is undefined', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); + + it('should navigate to preprint', () => { + const mockItem = mockSubmissions[0]; + component.navigateToPreprint(mockItem); + expect(mockRouter.navigate).toHaveBeenCalledWith(['/preprints/', mockProviderId, mockItem.id], { + queryParams: { mode: 'moderator' }, + }); + }); + + it('should get status from query params on init', () => { + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Pending); + }); + + it('should reset pagination when changing review status', () => { + component.currentPage.set(3); + component.first.set(20); + + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); }); diff --git a/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts b/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts index 0c41f3a33..00a894039 100644 --- a/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts +++ b/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts @@ -1,28 +1,84 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponents, MockProvider } from 'ng-mocks'; -import { MockComponents } from 'ng-mocks'; - -import { 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 { ActivatedRoute, Router } from '@angular/router'; import { PreprintSubmissionItemComponent } from '@osf/features/moderation/components'; -import { PreprintModerationState } from '@osf/features/moderation/store/preprint-moderation'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; +import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums'; +import { PreprintModerationSelectors } from '../../store/preprint-moderation'; + import { PreprintWithdrawalSubmissionsComponent } from './preprint-withdrawal-submissions.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('PreprintWithdrawalSubmissionsComponent', () => { let component: PreprintWithdrawalSubmissionsComponent; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: ReturnType; + + const mockProviderId = 'test-provider-id'; + const mockSubmissions = [ + { + id: '1', + preprintId: 'preprint-1', + title: 'Test Withdrawal 1', + reviewsState: 'pending', + public: false, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Withdrawal request', + }, + ], + }, + { + id: '2', + preprintId: 'preprint-2', + title: 'Test Withdrawal 2', + reviewsState: 'accepted', + public: true, + actions: [ + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Withdrawal approved', + }, + ], + }, + ]; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withParams({ providerId: mockProviderId }) + .withQueryParams({ status: 'pending' }) + .build(); + await TestBed.configureTestingModule({ imports: [ PreprintWithdrawalSubmissionsComponent, + OSFTestingModule, ...MockComponents( SelectComponent, IconComponent, @@ -32,21 +88,17 @@ describe('PreprintWithdrawalSubmissionsComponent', () => { ), ], providers: [ - { - provide: ActivatedRoute, - useValue: { - parent: { - params: of({ id: '1' }), - }, - snapshot: { - queryParams: { wiki: 'test' }, - }, - queryParams: of({ wiki: 'test' }), - }, - }, - provideStore([PreprintModerationState]), - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [ + { selector: PreprintModerationSelectors.getPreprintWithdrawalSubmissions, value: mockSubmissions }, + { selector: PreprintModerationSelectors.arePreprintWithdrawalSubmissionsLoading, value: false }, + { selector: PreprintModerationSelectors.getPreprintWithdrawalSubmissionsPendingCount, value: 1 }, + { selector: PreprintModerationSelectors.getPreprintWithdrawalSubmissionsAcceptedCount, value: 0 }, + { selector: PreprintModerationSelectors.getPreprintWithdrawalSubmissionsRejectedCount, value: 0 }, + ], + }), ], }).compileComponents(); @@ -58,4 +110,85 @@ describe('PreprintWithdrawalSubmissionsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + expect(component.currentPage()).toBe(1); + expect(component.pageSize()).toBe(10); + expect(component.first()).toBe(0); + expect(component.selectedSortOption()).toBe(PreprintSubmissionsSort.Newest); + expect(component.selectedReviewOption()).toBeDefined(); + }); + + it('should have sort options defined', () => { + expect(component.sortOptions).toBeDefined(); + expect(component.sortOptions.length).toBeGreaterThan(0); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.getPreprintWithdrawalSubmissions).toBeDefined(); + }); + + it('should compute submission review options with counts', () => { + const options = component.submissionReviewOptions(); + expect(options).toBeDefined(); + expect(options.length).toBeGreaterThan(0); + expect(options[0]).toHaveProperty('count'); + }); + + it('should compute total count', () => { + expect(component.totalCount()).toBeDefined(); + }); + + it('should change review status', () => { + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Accepted); + expect(mockRouter.navigate).toHaveBeenCalledWith( + [], + expect.objectContaining({ + queryParams: { status: SubmissionReviewStatus.Accepted }, + }) + ); + }); + + it('should change sort option', () => { + component.changeSort(PreprintSubmissionsSort.Oldest); + expect(component.selectedSortOption()).toBe(PreprintSubmissionsSort.Oldest); + }); + + it('should handle page change', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(2); + expect(component.first()).toBe(10); + }); + + it('should handle page change when page is undefined', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); + + it('should navigate to preprint', () => { + const mockItem = mockSubmissions[0]; + component.navigateToPreprint(mockItem); + expect(mockRouter.navigate).toHaveBeenCalledWith(['/preprints/', mockProviderId, mockItem.preprintId], { + queryParams: { mode: 'moderator' }, + }); + }); + + it('should get status from query params on init', () => { + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Pending); + }); + + it('should reset pagination when changing review status', () => { + component.currentPage.set(3); + component.first.set(20); + + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); }); diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts index 8b5e8dac1..7d5d1415a 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts @@ -1,43 +1,88 @@ -import { provideStore } from '@ngxs/store'; - import { TranslatePipe } from '@ngx-translate/core'; -import { MockPipes, MockProvider } from 'ng-mocks'; - -import { of } from 'rxjs'; +import { MockComponents, 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 { ActivatedRoute, Router } from '@angular/router'; -import { RegistryPendingSubmissionsComponent } from '@osf/features/moderation/components'; -import { RegistryModerationState } from '@osf/features/moderation/store/registry-moderation'; -import { TranslateServiceMock } from '@shared/mocks'; +import { RegistrySort, SubmissionReviewStatus } from '../../enums'; +import { RegistryModerationSelectors } from '../../store/registry-moderation'; + +import { RegistryPendingSubmissionsComponent } from './registry-pending-submissions.component'; -describe.skip('RegistryPendingSubmissionsComponent', () => { +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('RegistryPendingSubmissionsComponent', () => { let component: RegistryPendingSubmissionsComponent; let fixture: ComponentFixture; + let mockRouter: ReturnType; + let mockActivatedRoute: ReturnType; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [RegistryPendingSubmissionsComponent, ...MockPipes(TranslatePipe)], - providers: [ - provideStore([RegistryModerationState]), + const mockProviderId = 'test-provider-id'; + const mockSubmissions = [ + { + id: '1', + title: 'Test Registry 1', + reviewsState: 'pending', + public: false, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Registry submission', + }, + ], + }, + { + id: '2', + title: 'Test Registry 2', + reviewsState: 'accepted', + public: true, + actions: [ { - provide: ActivatedRoute, - useValue: { - parent: { - params: of({ providerId: 'id1' }), - }, - snapshot: { - queryParams: {}, - }, + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', }, + comment: 'Registry approved', }, - MockProvider(Router), - TranslateServiceMock, - provideHttpClient(), - provideHttpClientTesting(), + ], + }, + ]; + + beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withParams({ providerId: mockProviderId }) + .withQueryParams({ status: 'pending' }) + .build(); + + await TestBed.configureTestingModule({ + imports: [RegistryPendingSubmissionsComponent, OSFTestingModule, ...MockComponents(), TranslatePipe], + providers: [ + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [ + { selector: RegistryModerationSelectors.getRegistrySubmissions, value: mockSubmissions }, + { selector: RegistryModerationSelectors.areRegistrySubmissionLoading, value: false }, + { selector: RegistryModerationSelectors.getRegistrySubmissionTotalCount, value: 1 }, + ], + }), ], }).compileComponents(); @@ -49,4 +94,71 @@ describe.skip('RegistryPendingSubmissionsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + expect(component.currentPage()).toBe(1); + expect(component.pageSize()).toBe(10); + expect(component.first()).toBe(0); + expect(component.selectedSortOption()).toBe(RegistrySort.RegisteredNewest); + expect(component.selectedReviewOption()).toBeDefined(); + }); + + it('should have submission review options defined', () => { + expect(component.submissionReviewOptions).toBeDefined(); + expect(component.submissionReviewOptions.length).toBeGreaterThan(0); + }); + + it('should have sort options defined', () => { + expect(component.sortOptions).toBeDefined(); + expect(component.sortOptions.length).toBeGreaterThan(0); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.getRegistrySubmissions).toBeDefined(); + }); + + it('should change review status', () => { + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Accepted); + expect(mockRouter.navigate).toHaveBeenCalledWith( + [], + expect.objectContaining({ + queryParams: { status: SubmissionReviewStatus.Accepted }, + }) + ); + }); + + it('should change sort option', () => { + component.changeSort(RegistrySort.RegisteredOldest); + expect(component.selectedSortOption()).toBe(RegistrySort.RegisteredOldest); + }); + + it('should handle page change', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(2); + expect(component.first()).toBe(10); + }); + + it('should handle page change when page is undefined', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); + + it('should get status from query params on init', () => { + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Pending); + }); + + it('should reset pagination when changing review status', () => { + component.currentPage.set(3); + component.first.set(20); + + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); }); diff --git a/src/app/features/moderation/components/registry-settings/registry-settings.component.spec.ts b/src/app/features/moderation/components/registry-settings/registry-settings.component.spec.ts index 9a0e2c5d7..346ef660b 100644 --- a/src/app/features/moderation/components/registry-settings/registry-settings.component.spec.ts +++ b/src/app/features/moderation/components/registry-settings/registry-settings.component.spec.ts @@ -1,30 +1,16 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, MockPipe } from 'ng-mocks'; - -import { of } from 'rxjs'; - import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; - -import { BulkUploadComponent } from '@osf/features/moderation/components'; import { RegistrySettingsComponent } from './registry-settings.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('RegistrySettingsComponent', () => { let component: RegistrySettingsComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RegistrySettingsComponent, MockComponent(BulkUploadComponent), MockPipe(TranslatePipe)], - providers: [ - { - provide: ActivatedRoute, - useValue: { - queryParams: of({}), - }, - }, - ], + imports: [RegistrySettingsComponent, OSFTestingModule], }).compileComponents(); fixture = TestBed.createComponent(RegistrySettingsComponent); diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts index 0ad6d7381..4d8be7850 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts @@ -1,60 +1,105 @@ -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, MockPipes } from 'ng-mocks'; +import { MockComponent, MockPipe } from 'ng-mocks'; -import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { IconComponent } from '@shared/components'; -import { DateAgoPipe } from '@shared/pipes'; +import { IconComponent } from '@osf/shared/components'; +import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; +import { DateAgoPipe } from '@osf/shared/pipes'; + +import { SubmissionReviewStatus } from '../../enums'; +import { RegistryModeration } from '../../models'; import { RegistrySubmissionItemComponent } from './registry-submission-item.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + describe('RegistrySubmissionItemComponent', () => { let component: RegistrySubmissionItemComponent; - let componentRef: ComponentRef; let fixture: ComponentFixture; + const mockSubmission: RegistryModeration = { + id: '1', + title: 'Test Registry Submission', + revisionStatus: RevisionReviewStates.RevisionPendingModeration, + reviewsState: RegistrationReviewStates.Pending, + public: false, + embargoed: false, + embargoEndDate: null, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Registry submission', + }, + ], + revisionId: 'revision-1', + }; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ - RegistrySubmissionItemComponent, - RouterTestingModule, - MockComponent(IconComponent), - MockPipes(DateAgoPipe, TranslatePipe), - ], + imports: [RegistrySubmissionItemComponent, OSFTestingModule, MockComponent(IconComponent), MockPipe(DateAgoPipe)], }).compileComponents(); fixture = TestBed.createComponent(RegistrySubmissionItemComponent); component = fixture.componentInstance; - componentRef = fixture.componentRef; - - const mockSubmission = { - id: 'reg1', - title: 'Test registry submission', - reviewsState: 'pending', - public: true, - embargoed: false, - embargoEndDate: null, - actions: [ - { - id: 'a1', - fromState: 'pending', - toState: 'accepted', - dateModified: new Date().toISOString(), - creator: { id: 'u1', name: 'Bob' }, - comment: '', - }, - ], - }; - - componentRef.setInput('status', 'pending'); - componentRef.setInput('submission', mockSubmission); - + fixture.componentRef.setInput('status', SubmissionReviewStatus.Pending); + fixture.componentRef.setInput('submission', mockSubmission); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have input values', () => { + expect(component.status()).toBe(SubmissionReviewStatus.Pending); + expect(component.submission()).toEqual(mockSubmission); + }); + + it('should have constants defined', () => { + expect(component.reviewStatusIcon).toBeDefined(); + expect(component.registryActionLabel).toBeDefined(); + expect(component.registryActionState).toBeDefined(); + }); + + it('should have default values', () => { + expect(component.limitValue).toBe(1); + expect(component.showAll).toBe(false); + }); + + it('should toggle history', () => { + expect(component.showAll).toBe(false); + + component.toggleHistory(); + expect(component.showAll).toBe(true); + + component.toggleHistory(); + expect(component.showAll).toBe(false); + }); + + it('should compute isPendingModeration correctly', () => { + expect(component.isPendingModeration).toBe(true); + }); + + it('should compute isPending correctly', () => { + expect(component.isPending).toBe(true); + }); + + it('should accept custom input values', () => { + const customStatus = SubmissionReviewStatus.Accepted; + const customSubmission = { ...mockSubmission, title: 'Custom Registry Submission' }; + + fixture.componentRef.setInput('status', customStatus); + fixture.componentRef.setInput('submission', customSubmission); + + expect(component.status()).toBe(customStatus); + expect(component.submission()).toEqual(customSubmission); + }); }); diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts index 6c730a37e..cd2d463c3 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts @@ -1,28 +1,90 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponents, MockProvider } from 'ng-mocks'; -import { MockComponents } from 'ng-mocks'; - -import { 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 { ActivatedRoute, Router } from '@angular/router'; import { RegistrySubmissionItemComponent } from '@osf/features/moderation/components'; -import { RegistryModerationState } from '@osf/features/moderation/store/registry-moderation'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; +import { RegistrySort, SubmissionReviewStatus } from '../../enums'; +import { RegistryModerationSelectors } from '../../store/registry-moderation'; + import { RegistrySubmissionsComponent } from './registry-submissions.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('RegistrySubmissionsComponent', () => { let component: RegistrySubmissionsComponent; let fixture: ComponentFixture; + let mockActivatedRoute: ReturnType; + let mockRouter: ReturnType; + + const mockProviderId = 'test-provider-id'; + const mockSubmissions = [ + { + id: '1', + title: 'Test Registry 1', + revisionStatus: 'pending', + reviewsState: 'pending', + public: false, + embargoed: false, + embargoEndDate: null, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Registry submission', + }, + ], + revisionId: 'revision-1', + }, + { + id: '2', + title: 'Test Registry 2', + revisionStatus: 'accepted', + reviewsState: 'accepted', + public: true, + embargoed: false, + embargoEndDate: null, + actions: [ + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Registry approved', + }, + ], + revisionId: 'revision-2', + }, + ]; beforeEach(async () => { + mockRouter = RouterMockBuilder.create().build(); + mockActivatedRoute = ActivatedRouteMockBuilder.create() + .withParams({ providerId: mockProviderId }) + .withQueryParams({ status: 'pending' }) + .build(); + await TestBed.configureTestingModule({ imports: [ RegistrySubmissionsComponent, + OSFTestingModule, ...MockComponents( SelectComponent, IconComponent, @@ -32,20 +94,15 @@ describe('RegistrySubmissionsComponent', () => { ), ], providers: [ - provideStore([RegistryModerationState]), - { - provide: ActivatedRoute, - useValue: { - parent: { - params: of({ providerId: '1' }), - }, - snapshot: { - queryParams: {}, - }, - }, - }, - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(Router, mockRouter), + MockProvider(ActivatedRoute, mockActivatedRoute), + provideMockStore({ + signals: [ + { selector: RegistryModerationSelectors.getRegistrySubmissions, value: mockSubmissions }, + { selector: RegistryModerationSelectors.areRegistrySubmissionLoading, value: false }, + { selector: RegistryModerationSelectors.getRegistrySubmissionTotalCount, value: 1 }, + ], + }), ], }).compileComponents(); @@ -57,4 +114,71 @@ describe('RegistrySubmissionsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize with default values', () => { + expect(component.currentPage()).toBe(1); + expect(component.pageSize()).toBe(10); + expect(component.first()).toBe(0); + expect(component.selectedSortOption()).toBe(RegistrySort.RegisteredNewest); + expect(component.selectedReviewOption()).toBeDefined(); + }); + + it('should have submission review options defined', () => { + expect(component.submissionReviewOptions).toBeDefined(); + expect(component.submissionReviewOptions.length).toBeGreaterThan(0); + }); + + it('should have sort options defined', () => { + expect(component.sortOptions).toBeDefined(); + expect(component.sortOptions.length).toBeGreaterThan(0); + }); + + it('should have actions defined', () => { + expect(component.actions).toBeDefined(); + expect(component.actions.getRegistrySubmissions).toBeDefined(); + }); + + it('should change review status', () => { + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Accepted); + expect(mockRouter.navigate).toHaveBeenCalledWith( + [], + expect.objectContaining({ + queryParams: { status: SubmissionReviewStatus.Accepted }, + }) + ); + }); + + it('should change sort option', () => { + component.changeSort(RegistrySort.RegisteredOldest); + expect(component.selectedSortOption()).toBe(RegistrySort.RegisteredOldest); + }); + + it('should handle page change', () => { + const mockEvent = { page: 1, first: 10, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(2); + expect(component.first()).toBe(10); + }); + + it('should handle page change when page is undefined', () => { + const mockEvent = { page: undefined, first: 0, rows: 10 }; + component.onPageChange(mockEvent); + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); + + it('should get status from query params on init', () => { + expect(component.selectedReviewOption()).toBe(SubmissionReviewStatus.Pending); + }); + + it('should reset pagination when changing review status', () => { + component.currentPage.set(3); + component.first.set(20); + + component.changeReviewStatus(SubmissionReviewStatus.Accepted); + + expect(component.currentPage()).toBe(1); + expect(component.first()).toBe(0); + }); }); diff --git a/src/app/shared/mocks/provider.mock.ts b/src/app/shared/mocks/provider.mock.ts index bafd01c8d..ec393c8e9 100644 --- a/src/app/shared/mocks/provider.mock.ts +++ b/src/app/shared/mocks/provider.mock.ts @@ -24,4 +24,9 @@ export const MOCK_PROVIDER = { iri: '', faviconUrl: '', squareColorNoTransparentImageUrl: '', + primaryCollection: { + id: 'collection-1', + title: 'Test Collection', + description: 'Test collection description', + }, }; diff --git a/src/testing/mocks/submission.mock.ts b/src/testing/mocks/submission.mock.ts new file mode 100644 index 000000000..87accdcb6 --- /dev/null +++ b/src/testing/mocks/submission.mock.ts @@ -0,0 +1,78 @@ +import { PreprintSubmission, PreprintWithdrawalSubmission } from '@osf/features/moderation/models'; +import { CollectionSubmissionWithGuid } from '@shared/models'; + +export const MOCK_PREPRINT_SUBMISSION: PreprintSubmission = { + id: '1', + title: 'Test Preprint Submission', + reviewsState: 'pending', + public: false, + actions: [ + { + id: '1', + action: 'review', + status: 'pending', + date: '2023-01-01', + user: 'John Doe', + }, + ], +}; + +export const MOCK_PREPRINT_WITHDRAWAL_SUBMISSION: PreprintWithdrawalSubmission = { + id: '1', + preprintId: 'preprint-1', + title: 'Test Withdrawal Submission', + reviewsState: 'pending', + public: false, + actions: [ + { + id: '1', + action: 'withdrawal_review', + status: 'pending', + date: '2023-01-01', + user: 'John Doe', + }, + ], +}; + +export const MOCK_COLLECTION_SUBMISSION_WITH_GUID: CollectionSubmissionWithGuid = { + id: '1', + type: 'collection-submission', + nodeId: 'node-123', + nodeUrl: 'https://osf.io/node-123/', + title: 'Test Collection Submission', + description: 'This is a test collection submission', + category: 'project', + dateCreated: '2024-01-01T00:00:00Z', + dateModified: '2024-01-02T00:00:00Z', + public: false, + reviewsState: 'pending', + collectedType: 'preprint', + status: 'pending', + volume: '1', + issue: '1', + programArea: 'Science', + schoolType: 'University', + studyDesign: 'Experimental', + dataType: 'Quantitative', + disease: 'None', + gradeLevels: 'Graduate', + creator: { + id: 'user-123', + fullName: 'John Doe', + }, + actions: [ + { + id: 'action-1', + type: 'review', + dateCreated: '2024-01-01T00:00:00Z', + dateModified: '2024-01-01T00:00:00Z', + fromState: 'pending', + toState: 'pending', + comment: 'Initial review', + trigger: 'manual', + targetId: '1', + targetNodeId: 'node-123', + createdBy: 'moderator-123', + }, + ], +}; From 5c14340d571a857e26e7e3802da63e6d76365ae1 Mon Sep 17 00:00:00 2001 From: Diana Date: Wed, 24 Sep 2025 11:42:54 +0300 Subject: [PATCH 3/6] test(moderation-components): added new tests --- ...rint-moderation-settings.component.spec.ts | 82 ++++++++++++++++--- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts index c38906232..9c26b7181 100644 --- a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts +++ b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts @@ -1,30 +1,52 @@ -import { provideStore } from '@ngxs/store'; +import { MockComponent, MockProvider } from 'ng-mocks'; -import { TranslatePipe } from '@ngx-translate/core'; -import { MockComponent, 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 { ActivatedRoute } from '@angular/router'; -import { PreprintModerationState } from '@osf/features/moderation/store/preprint-moderation'; +import { ENVIRONMENT } from '@core/provider/environment.provider'; import { LoadingSpinnerComponent } from '@shared/components'; +import { SettingsSectionControl } from '../../enums'; +import { PreprintProviderModerationInfo } from '../../models'; +import { PreprintModerationSelectors } from '../../store/preprint-moderation'; + import { PreprintModerationSettingsComponent } from './preprint-moderation-settings.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + describe('PreprintModerationSettingsComponent', () => { let component: PreprintModerationSettingsComponent; let fixture: ComponentFixture; + let mockActivatedRoute: ReturnType; + + const mockProviderId = 'test-provider-id'; + const mockSettings: PreprintProviderModerationInfo = { + id: mockProviderId, + name: 'Test Provider', + submissionCount: 10, + reviewsCommentsAnonymous: true, + reviewsCommentsPrivate: false, + reviewsWorkflow: 'pre_moderation', + supportEmail: 'support@test.com', + }; beforeEach(async () => { + mockActivatedRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: mockProviderId }).build(); + await TestBed.configureTestingModule({ - imports: [PreprintModerationSettingsComponent, MockPipe(TranslatePipe), MockComponent(LoadingSpinnerComponent)], + imports: [PreprintModerationSettingsComponent, OSFTestingModule, MockComponent(LoadingSpinnerComponent)], providers: [ - MockProvider(ActivatedRoute), - provideStore([PreprintModerationState]), - provideHttpClient(), - provideHttpClientTesting(), + MockProvider(ActivatedRoute, mockActivatedRoute), + MockProvider(ENVIRONMENT, { supportEmail: 'support@test.com' }), + provideMockStore({ + signals: [ + { selector: PreprintModerationSelectors.arePreprintProviderLoading, value: false }, + { selector: PreprintModerationSelectors.getPreprintProviders, value: [mockSettings] }, + { selector: PreprintModerationSelectors.getPreprintProvider, value: (_id: string) => mockSettings }, + ], + }), ], }).compileComponents(); @@ -36,4 +58,40 @@ describe('PreprintModerationSettingsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should initialize form on ngOnInit', () => { + component.ngOnInit(); + expect(component.settingsForm).toBeDefined(); + expect(component.settingsForm.get(SettingsSectionControl.ModerationType)).toBeDefined(); + expect(component.settingsForm.get(SettingsSectionControl.CommentVisibility)).toBeDefined(); + expect(component.settingsForm.get(SettingsSectionControl.ModeratorComments)).toBeDefined(); + }); + + it('should have providerId from route params', () => { + expect(component.providerId()).toBe(mockProviderId); + }); + + it('should compute settings correctly', () => { + expect(component.settings()).toBeDefined(); + }); + + it('should compute isLoading correctly', () => { + expect(component.isLoading()).toBe(false); + }); + + it('should initialize form with settings values', () => { + component.ngOnInit(); + expect(component.settingsForm.get(SettingsSectionControl.ModerationType)?.value).toBe(mockSettings.reviewsWorkflow); + expect(component.settingsForm.get(SettingsSectionControl.CommentVisibility)?.value).toBe( + mockSettings.reviewsCommentsPrivate + ); + expect(component.settingsForm.get(SettingsSectionControl.ModeratorComments)?.value).toBe( + mockSettings.reviewsCommentsAnonymous + ); + }); + + it('should disable form initially', () => { + component.ngOnInit(); + expect(component.settingsForm.disabled).toBe(true); + }); }); From c2d8cb9654d335a63fce76c43a034e9d54190273 Mon Sep 17 00:00:00 2001 From: Diana Date: Wed, 24 Sep 2025 13:27:42 +0300 Subject: [PATCH 4/6] test(moderation-components): fixed --- ...n-moderation-submissions.component.spec.ts | 9 +- ...lection-submissions-list.component.spec.ts | 4 - .../invite-moderator-dialog.component.spec.ts | 2 +- .../moderators-list.component.spec.ts | 24 +---- .../moderators-table.component.spec.ts | 25 +---- ...rint-moderation-settings.component.spec.ts | 15 +-- ...int-recent-activity-list.component.spec.ts | 26 +---- ...preprint-submission-item.component.spec.ts | 22 +---- .../preprint-submissions.component.spec.ts | 45 +-------- ...t-withdrawal-submissions.component.spec.ts | 47 +-------- ...stry-pending-submissions.component.spec.ts | 46 +-------- ...registry-submission-item.component.spec.ts | 27 +----- .../registry-submissions.component.spec.ts | 53 +---------- ...moderation-status-banner.component.spec.ts | 11 +-- .../preprints-landing.component.spec.ts | 7 +- src/testing/mocks/environment.token.mock.ts | 2 + src/testing/mocks/moderator.mock.ts | 95 +++++++++++++++++++ .../preprint-provider-moderation-info.mock.ts | 11 +++ .../mocks/preprint-review-action.mock.ts | 58 +++++++++++ .../preprint-withdrawal-submission.mock.ts | 61 ++++++++++++ src/testing/mocks/registry-moderation.mock.ts | 77 +++++++++++++++ src/testing/mocks/submission.mock.ts | 32 ++----- 22 files changed, 352 insertions(+), 347 deletions(-) create mode 100644 src/testing/mocks/moderator.mock.ts create mode 100644 src/testing/mocks/preprint-provider-moderation-info.mock.ts create mode 100644 src/testing/mocks/preprint-review-action.mock.ts create mode 100644 src/testing/mocks/preprint-withdrawal-submission.mock.ts create mode 100644 src/testing/mocks/registry-moderation.mock.ts diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts index a43866da2..81cca34f2 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.spec.ts @@ -22,7 +22,7 @@ describe('CollectionModerationSubmissionsComponent', () => { let component: CollectionModerationSubmissionsComponent; let fixture: ComponentFixture; let mockRouter: ReturnType; - let mockActivatedRoute: any; + let mockActivatedRoute: ReturnType; const mockCollectionProvider = MOCK_PROVIDER; const mockSubmissions = [ @@ -115,13 +115,6 @@ describe('CollectionModerationSubmissionsComponent', () => { expect(component.isLoading()).toBe(false); }); - it('should have actions defined', () => { - expect(component.actions).toBeDefined(); - expect(component.actions.getCollectionDetails).toBeDefined(); - expect(component.actions.searchCollectionSubmissions).toBeDefined(); - expect(component.actions.getCollectionSubmissions).toBeDefined(); - }); - it('should initialize from query params', () => { expect(component.reviewStatus()).toBe(SubmissionReviewStatus.Pending); expect(component.selectedSortOption()).toBe('date_created'); diff --git a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts index 18a16b0a8..e86fbea75 100644 --- a/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts +++ b/src/app/features/moderation/components/collection-submissions-list/collection-submissions-list.component.spec.ts @@ -65,8 +65,4 @@ describe('CollectionSubmissionsListComponent', () => { expect(component.submissions()).toEqual([]); expect(component.submissions().length).toBe(0); }); - - it('should have submissions selector defined', () => { - expect(component.submissions).toBeDefined(); - }); }); diff --git a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts index 9aaf77afc..682bef37b 100644 --- a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts @@ -105,7 +105,7 @@ describe('InviteModeratorDialogComponent', () => { component.moderatorForm.patchValue({ fullName: 'John Doe', email: 'john@example.com', - permission: null, + permission: undefined, }); component.submit(); diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts index a018d225b..4c071fc7a 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.spec.ts @@ -18,6 +18,7 @@ import { ModeratorsSelectors } from '../../store/moderators'; import { ModeratorsListComponent } from './moderators-list.component'; +import { MOCK_MODERATORS } from '@testing/mocks/moderator.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom-confirmation-provider.mock'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; @@ -33,24 +34,7 @@ describe('ModeratorsListComponent', () => { const mockResourceType = ResourceType.Preprint; const mockCurrentUser = MOCK_USER; - const mockModerators: ModeratorModel[] = [ - { - id: '1', - userId: 'user-1', - fullName: 'John Doe', - email: 'john@example.com', - permission: ModeratorPermission.Admin, - isActive: true, - }, - { - id: '2', - userId: 'user-2', - fullName: 'Jane Smith', - email: 'jane@example.com', - permission: ModeratorPermission.Read, - isActive: true, - }, - ]; + const mockModerators: ModeratorModel[] = MOCK_MODERATORS; beforeEach(async () => { mockActivatedRoute = ActivatedRouteMockBuilder.create() @@ -102,7 +86,7 @@ describe('ModeratorsListComponent', () => { it('should return false for admin moderator when user is not admin', () => { const nonAdminModerators = mockModerators.map((mod) => ({ ...mod, - permission: ModeratorPermission.Read, + permission: ModeratorPermission.Moderator, })); Object.defineProperty(component, 'initialModerators', { @@ -206,7 +190,7 @@ describe('ModeratorsListComponent', () => { userId: 'user-3', fullName: 'Bob Wilson', email: 'bob@example.com', - permission: ModeratorPermission.Read, + permission: ModeratorPermission.Moderator, isActive: true, }, ]; diff --git a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts index 96c0b9d6d..0c61a421b 100644 --- a/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts +++ b/src/app/features/moderation/components/moderators-table/moderators-table.component.spec.ts @@ -4,39 +4,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SelectComponent } from '@shared/components'; -import { ModeratorPermission } from '../../enums'; import { ModeratorModel } from '../../models'; import { ModeratorsTableComponent } from './moderators-table.component'; +import { MOCK_MODERATORS } from '@testing/mocks/moderator.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('ModeratorsTableComponent', () => { let component: ModeratorsTableComponent; let fixture: ComponentFixture; - const mockModerators: ModeratorModel[] = [ - { - id: '1', - userId: 'user-1', - fullName: 'John Doe', - email: 'john@example.com', - permission: ModeratorPermission.Admin, - isActive: true, - education: [], - employment: [], - }, - { - id: '2', - userId: 'user-2', - fullName: 'Jane Smith', - email: 'jane@example.com', - permission: ModeratorPermission.Read, - isActive: true, - education: [], - employment: [], - }, - ]; + const mockModerators: ModeratorModel[] = MOCK_MODERATORS; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts index 9c26b7181..7872b7d22 100644 --- a/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts +++ b/src/app/features/moderation/components/preprint-moderation-settings/preprint-moderation-settings.component.spec.ts @@ -3,7 +3,6 @@ import { MockComponent, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { ENVIRONMENT } from '@core/provider/environment.provider'; import { LoadingSpinnerComponent } from '@shared/components'; import { SettingsSectionControl } from '../../enums'; @@ -12,6 +11,8 @@ import { PreprintModerationSelectors } from '../../store/preprint-moderation'; import { PreprintModerationSettingsComponent } from './preprint-moderation-settings.component'; +import { EnvironmentTokenMock } from '@testing/mocks/environment.token.mock'; +import { MOCK_PREPRINT_PROVIDER_MODERATION_INFO } from '@testing/mocks/preprint-provider-moderation-info.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { provideMockStore } from '@testing/providers/store-provider.mock'; @@ -22,15 +23,7 @@ describe('PreprintModerationSettingsComponent', () => { let mockActivatedRoute: ReturnType; const mockProviderId = 'test-provider-id'; - const mockSettings: PreprintProviderModerationInfo = { - id: mockProviderId, - name: 'Test Provider', - submissionCount: 10, - reviewsCommentsAnonymous: true, - reviewsCommentsPrivate: false, - reviewsWorkflow: 'pre_moderation', - supportEmail: 'support@test.com', - }; + const mockSettings: PreprintProviderModerationInfo = MOCK_PREPRINT_PROVIDER_MODERATION_INFO; beforeEach(async () => { mockActivatedRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: mockProviderId }).build(); @@ -39,7 +32,7 @@ describe('PreprintModerationSettingsComponent', () => { imports: [PreprintModerationSettingsComponent, OSFTestingModule, MockComponent(LoadingSpinnerComponent)], providers: [ MockProvider(ActivatedRoute, mockActivatedRoute), - MockProvider(ENVIRONMENT, { supportEmail: 'support@test.com' }), + EnvironmentTokenMock, provideMockStore({ signals: [ { selector: PreprintModerationSelectors.arePreprintProviderLoading, value: false }, diff --git a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts index befaf6636..c1baca4ba 100644 --- a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts +++ b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.spec.ts @@ -1,6 +1,5 @@ -import { MockComponents, MockPipe } from 'ng-mocks'; +import { MockComponents } from 'ng-mocks'; -import { DatePipe } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomPaginatorComponent, IconComponent } from '@osf/shared/components'; @@ -9,32 +8,14 @@ import { PreprintReviewActionModel } from '../../models'; import { PreprintRecentActivityListComponent } from './preprint-recent-activity-list.component'; +import { MOCK_PREPRINT_REVIEW_ACTIONS } from '@testing/mocks/preprint-review-action.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('PreprintRecentActivityListComponent', () => { let component: PreprintRecentActivityListComponent; let fixture: ComponentFixture; - const mockReviews: PreprintReviewActionModel[] = [ - { - id: '1', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - preprint: { - id: 'preprint-1', - name: 'Test Preprint', - }, - provider: { - id: 'provider-1', - name: 'Test Provider', - }, - }, - ]; + const mockReviews: PreprintReviewActionModel[] = MOCK_PREPRINT_REVIEW_ACTIONS; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -42,7 +23,6 @@ describe('PreprintRecentActivityListComponent', () => { PreprintRecentActivityListComponent, OSFTestingModule, ...MockComponents(IconComponent, CustomPaginatorComponent), - MockPipe(DatePipe), ], }).compileComponents(); diff --git a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts index c60818e56..11749a014 100644 --- a/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/preprint-submission-item/preprint-submission-item.component.spec.ts @@ -11,32 +11,14 @@ import { PreprintSubmission } from '../../models'; import { PreprintSubmissionItemComponent } from './preprint-submission-item.component'; +import { MOCK_PREPRINT_SUBMISSION } from '@testing/mocks/submission.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('PreprintSubmissionItemComponent', () => { let component: PreprintSubmissionItemComponent; let fixture: ComponentFixture; - const mockSubmission: PreprintSubmission = { - id: '1', - title: 'Test Preprint', - reviewsState: 'pending', - public: false, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Test comment', - }, - ], - }; + const mockSubmission: PreprintSubmission = MOCK_PREPRINT_SUBMISSION; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts index 11a2a2803..546d41a5b 100644 --- a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts +++ b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.spec.ts @@ -4,6 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { PreprintSubmissionItemComponent } from '@osf/features/moderation/components'; +import { RegistryModeration } from '@osf/features/moderation/models'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums'; @@ -11,6 +12,7 @@ import { PreprintModerationSelectors } from '../../store/preprint-moderation'; import { PreprintSubmissionsComponent } from './preprint-submissions.component'; +import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -23,48 +25,7 @@ describe('PreprintSubmissionsComponent', () => { let mockActivatedRoute: ReturnType; const mockProviderId = 'test-provider-id'; - const mockSubmissions = [ - { - id: '1', - title: 'Test Preprint 1', - reviewsState: 'pending', - public: false, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Test comment', - }, - ], - }, - { - id: '2', - title: 'Test Preprint 2', - reviewsState: 'accepted', - public: true, - actions: [ - { - id: '2', - trigger: 'manual', - fromState: 'pending', - toState: 'accepted', - dateModified: '2023-01-02', - creator: { - id: 'user-2', - name: 'Jane Doe', - }, - comment: 'Approved', - }, - ], - }, - ]; + const mockSubmissions: RegistryModeration[] = MOCK_REGISTRY_MODERATIONS; beforeEach(async () => { mockRouter = RouterMockBuilder.create().build(); diff --git a/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts b/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts index 00a894039..e818f173d 100644 --- a/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts +++ b/src/app/features/moderation/components/preprint-withdrawal-submissions/preprint-withdrawal-submissions.component.spec.ts @@ -4,6 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { PreprintSubmissionItemComponent } from '@osf/features/moderation/components'; +import { PreprintWithdrawalSubmission } from '@osf/features/moderation/models'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums'; @@ -11,6 +12,7 @@ import { PreprintModerationSelectors } from '../../store/preprint-moderation'; import { PreprintWithdrawalSubmissionsComponent } from './preprint-withdrawal-submissions.component'; +import { MOCK_PREPRINT_WITHDRAWAL_SUBMISSIONS } from '@testing/mocks/preprint-withdrawal-submission.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -23,50 +25,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => { let mockActivatedRoute: ReturnType; const mockProviderId = 'test-provider-id'; - const mockSubmissions = [ - { - id: '1', - preprintId: 'preprint-1', - title: 'Test Withdrawal 1', - reviewsState: 'pending', - public: false, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Withdrawal request', - }, - ], - }, - { - id: '2', - preprintId: 'preprint-2', - title: 'Test Withdrawal 2', - reviewsState: 'accepted', - public: true, - actions: [ - { - id: '2', - trigger: 'manual', - fromState: 'pending', - toState: 'accepted', - dateModified: '2023-01-02', - creator: { - id: 'user-2', - name: 'Jane Doe', - }, - comment: 'Withdrawal approved', - }, - ], - }, - ]; + const mockSubmissions: PreprintWithdrawalSubmission[] = MOCK_PREPRINT_WITHDRAWAL_SUBMISSIONS; beforeEach(async () => { mockRouter = RouterMockBuilder.create().build(); diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts index 7d5d1415a..055391f6f 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.spec.ts @@ -4,11 +4,14 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; +import { RegistryModeration } from '@osf/features/moderation/models'; + import { RegistrySort, SubmissionReviewStatus } from '../../enums'; import { RegistryModerationSelectors } from '../../store/registry-moderation'; import { RegistryPendingSubmissionsComponent } from './registry-pending-submissions.component'; +import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -21,48 +24,7 @@ describe('RegistryPendingSubmissionsComponent', () => { let mockActivatedRoute: ReturnType; const mockProviderId = 'test-provider-id'; - const mockSubmissions = [ - { - id: '1', - title: 'Test Registry 1', - reviewsState: 'pending', - public: false, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Registry submission', - }, - ], - }, - { - id: '2', - title: 'Test Registry 2', - reviewsState: 'accepted', - public: true, - actions: [ - { - id: '2', - trigger: 'manual', - fromState: 'pending', - toState: 'accepted', - dateModified: '2023-01-02', - creator: { - id: 'user-2', - name: 'Jane Doe', - }, - comment: 'Registry approved', - }, - ], - }, - ]; + const mockSubmissions: RegistryModeration[] = MOCK_REGISTRY_MODERATIONS; beforeEach(async () => { mockRouter = RouterMockBuilder.create().build(); diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts index 4d8be7850..28f873d38 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.spec.ts @@ -3,7 +3,6 @@ import { MockComponent, MockPipe } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { IconComponent } from '@osf/shared/components'; -import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; import { DateAgoPipe } from '@osf/shared/pipes'; import { SubmissionReviewStatus } from '../../enums'; @@ -11,36 +10,14 @@ import { RegistryModeration } from '../../models'; import { RegistrySubmissionItemComponent } from './registry-submission-item.component'; +import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('RegistrySubmissionItemComponent', () => { let component: RegistrySubmissionItemComponent; let fixture: ComponentFixture; - const mockSubmission: RegistryModeration = { - id: '1', - title: 'Test Registry Submission', - revisionStatus: RevisionReviewStates.RevisionPendingModeration, - reviewsState: RegistrationReviewStates.Pending, - public: false, - embargoed: false, - embargoEndDate: null, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Registry submission', - }, - ], - revisionId: 'revision-1', - }; + const mockSubmission: RegistryModeration = MOCK_REGISTRY_MODERATIONS[0]; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts index cd2d463c3..aa145cbc0 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.spec.ts @@ -4,6 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { RegistrySubmissionItemComponent } from '@osf/features/moderation/components'; +import { RegistryModeration } from '@osf/features/moderation/models'; import { CustomPaginatorComponent, IconComponent, LoadingSpinnerComponent, SelectComponent } from '@shared/components'; import { RegistrySort, SubmissionReviewStatus } from '../../enums'; @@ -11,6 +12,7 @@ import { RegistryModerationSelectors } from '../../store/registry-moderation'; import { RegistrySubmissionsComponent } from './registry-submissions.component'; +import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; import { RouterMockBuilder } from '@testing/providers/router-provider.mock'; @@ -23,56 +25,7 @@ describe('RegistrySubmissionsComponent', () => { let mockRouter: ReturnType; const mockProviderId = 'test-provider-id'; - const mockSubmissions = [ - { - id: '1', - title: 'Test Registry 1', - revisionStatus: 'pending', - reviewsState: 'pending', - public: false, - embargoed: false, - embargoEndDate: null, - actions: [ - { - id: '1', - trigger: 'manual', - fromState: 'pending', - toState: 'pending', - dateModified: '2023-01-01', - creator: { - id: 'user-1', - name: 'John Doe', - }, - comment: 'Registry submission', - }, - ], - revisionId: 'revision-1', - }, - { - id: '2', - title: 'Test Registry 2', - revisionStatus: 'accepted', - reviewsState: 'accepted', - public: true, - embargoed: false, - embargoEndDate: null, - actions: [ - { - id: '2', - trigger: 'manual', - fromState: 'pending', - toState: 'accepted', - dateModified: '2023-01-02', - creator: { - id: 'user-2', - name: 'Jane Doe', - }, - comment: 'Registry approved', - }, - ], - revisionId: 'revision-2', - }, - ]; + const mockSubmissions: RegistryModeration[] = MOCK_REGISTRY_MODERATIONS; beforeEach(async () => { mockRouter = RouterMockBuilder.create().build(); diff --git a/src/app/features/preprints/components/preprint-details/moderation-status-banner/moderation-status-banner.component.spec.ts b/src/app/features/preprints/components/preprint-details/moderation-status-banner/moderation-status-banner.component.spec.ts index 62a9e563f..19307ab27 100644 --- a/src/app/features/preprints/components/preprint-details/moderation-status-banner/moderation-status-banner.component.spec.ts +++ b/src/app/features/preprints/components/preprint-details/moderation-status-banner/moderation-status-banner.component.spec.ts @@ -1,9 +1,8 @@ -import { MockComponent, MockPipes, MockProvider } from 'ng-mocks'; +import { MockComponent, MockPipes } from 'ng-mocks'; import { DatePipe, TitleCasePipe } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ENVIRONMENT } from '@core/provider/environment.provider'; import { ReviewAction } from '@osf/features/moderation/models'; import { ProviderReviewsWorkflow, ReviewsState } from '@osf/features/preprints/enums'; import { PreprintRequest } from '@osf/features/preprints/models'; @@ -13,6 +12,7 @@ import { MOCK_PROVIDER } from '@shared/mocks'; import { ModerationStatusBannerComponent } from './moderation-status-banner.component'; +import { EnvironmentTokenMock } from '@testing/mocks/environment.token.mock'; import { PREPRINT_MOCK } from '@testing/mocks/preprint.mock'; import { PREPRINT_REQUEST_MOCK } from '@testing/mocks/preprint-request.mock'; import { REVIEW_ACTION_MOCK } from '@testing/mocks/review-action.mock'; @@ -28,7 +28,6 @@ describe('ModerationStatusBannerComponent', () => { const mockProvider = MOCK_PROVIDER; const mockReviewAction: ReviewAction = REVIEW_ACTION_MOCK; const mockWithdrawalRequest: PreprintRequest = PREPRINT_REQUEST_MOCK; - const mockWebUrl = 'https://staging4.osf.io'; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -40,9 +39,7 @@ describe('ModerationStatusBannerComponent', () => { ], providers: [ TranslationServiceMock, - MockProvider(ENVIRONMENT, { - webUrl: mockWebUrl, - }), + EnvironmentTokenMock, provideMockStore({ signals: [ { @@ -184,7 +181,7 @@ describe('ModerationStatusBannerComponent', () => { it('should compute actionCreatorLink with environment webUrl', () => { const link = component.actionCreatorLink(); - expect(link).toBe(`${mockWebUrl}/user-1`); + expect(link).toBe(`${EnvironmentTokenMock.useValue.webUrl}/user-1`); }); it('should compute withdrawalRequesterName from latestWithdrawalRequest', () => { diff --git a/src/app/features/preprints/pages/landing/preprints-landing.component.spec.ts b/src/app/features/preprints/pages/landing/preprints-landing.component.spec.ts index 8b8ff0118..fcacfcef1 100644 --- a/src/app/features/preprints/pages/landing/preprints-landing.component.spec.ts +++ b/src/app/features/preprints/pages/landing/preprints-landing.component.spec.ts @@ -4,7 +4,6 @@ import { TitleCasePipe } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { ENVIRONMENT } from '@core/provider/environment.provider'; import { AdvisoryBoardComponent, BrowseBySubjectsComponent, @@ -18,6 +17,7 @@ import { BrandService } from '@shared/services'; import { PreprintsLandingComponent } from './preprints-landing.component'; +import { EnvironmentTokenMock } from '@testing/mocks/environment.token.mock'; import { PREPRINT_PROVIDER_DETAILS_MOCK } from '@testing/mocks/preprint-provider-details'; import { PREPRINT_PROVIDER_SHORT_INFO_MOCK } from '@testing/mocks/preprint-provider-short-info.mock'; import { SUBJECTS_MOCK } from '@testing/mocks/subject.mock'; @@ -53,10 +53,7 @@ describe('PreprintsLandingComponent', () => { ], providers: [ TranslationServiceMock, - MockProvider(ENVIRONMENT, { - defaultProvider: mockDefaultProvider, - supportEmail: 'support@osf.io', - }), + EnvironmentTokenMock, MockProvider(BrandService), MockProvider(Router, routerMock), provideMockStore({ diff --git a/src/testing/mocks/environment.token.mock.ts b/src/testing/mocks/environment.token.mock.ts index f451d7379..1c8c64216 100644 --- a/src/testing/mocks/environment.token.mock.ts +++ b/src/testing/mocks/environment.token.mock.ts @@ -30,5 +30,7 @@ export const EnvironmentTokenMock = { googleTagManagerId: 'test-goolge-tag-manager-id', addonsApiUrl: 'http://addons.localhost:8000', webUrl: 'http://localhost:4200', + supportEmail: 'support@test.com', + defaultProvider: 'osf', }, }; diff --git a/src/testing/mocks/moderator.mock.ts b/src/testing/mocks/moderator.mock.ts new file mode 100644 index 000000000..d8fa446d2 --- /dev/null +++ b/src/testing/mocks/moderator.mock.ts @@ -0,0 +1,95 @@ +import { ModeratorPermission } from '@osf/features/moderation/enums'; +import { ModeratorModel } from '@osf/features/moderation/models'; + +export const MOCK_MODERATORS: ModeratorModel[] = [ + { + id: '1', + userId: 'user-1', + fullName: 'John Doe', + permission: ModeratorPermission.Admin, + employment: [ + { + title: 'Professor', + institution: 'University of Example', + department: 'Computer Science', + startMonth: 1, + startYear: 2020, + endMonth: null, + endYear: null, + ongoing: true, + }, + ], + education: [ + { + institution: 'University of Example', + department: 'Computer Science', + degree: 'PhD', + startMonth: 9, + startYear: 2015, + endMonth: 6, + endYear: 2020, + ongoing: false, + }, + ], + }, + { + id: '2', + userId: 'user-2', + fullName: 'Jane Smith', + permission: ModeratorPermission.Moderator, + employment: [ + { + title: 'Research Scientist', + institution: 'Tech University', + department: 'Data Science', + startMonth: 3, + startYear: 2019, + endMonth: null, + endYear: null, + ongoing: true, + }, + ], + education: [ + { + institution: 'Tech University', + department: 'Data Science', + degree: 'Master', + startMonth: 9, + startYear: 2017, + endMonth: 6, + endYear: 2019, + ongoing: false, + }, + ], + }, + { + id: '3', + userId: 'user-3', + fullName: 'Bob Johnson', + permission: ModeratorPermission.Moderator, + employment: [ + { + title: 'Engineer', + institution: 'State University', + department: 'Engineering', + startMonth: 6, + startYear: 2018, + endMonth: 12, + endYear: 2022, + ongoing: false, + }, + ], + education: [ + { + institution: 'State University', + department: 'Engineering', + degree: 'Bachelor', + startMonth: 9, + startYear: 2014, + endMonth: 6, + endYear: 2018, + ongoing: false, + }, + ], + }, +]; diff --git a/src/testing/mocks/preprint-provider-moderation-info.mock.ts b/src/testing/mocks/preprint-provider-moderation-info.mock.ts new file mode 100644 index 000000000..b67a24934 --- /dev/null +++ b/src/testing/mocks/preprint-provider-moderation-info.mock.ts @@ -0,0 +1,11 @@ +import { PreprintProviderModerationInfo } from '@osf/features/moderation/models'; + +export const MOCK_PREPRINT_PROVIDER_MODERATION_INFO: PreprintProviderModerationInfo = { + id: 'test-provider-id', + name: 'Test Provider', + submissionCount: 10, + reviewsCommentsAnonymous: true, + reviewsCommentsPrivate: false, + reviewsWorkflow: 'pre_moderation', + supportEmail: 'support@test.com', +}; diff --git a/src/testing/mocks/preprint-review-action.mock.ts b/src/testing/mocks/preprint-review-action.mock.ts new file mode 100644 index 000000000..da45ec470 --- /dev/null +++ b/src/testing/mocks/preprint-review-action.mock.ts @@ -0,0 +1,58 @@ +import { PreprintReviewActionModel } from '@osf/features/moderation/models'; + +export const MOCK_PREPRINT_REVIEW_ACTIONS: PreprintReviewActionModel[] = [ + { + id: '1', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + preprint: { + id: 'preprint-1', + name: 'Test Preprint', + }, + provider: { + id: 'provider-1', + name: 'Test Provider', + }, + }, + { + id: '2', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + preprint: { + id: 'preprint-2', + name: 'Test Preprint 2', + }, + provider: { + id: 'provider-2', + name: 'Test Provider 2', + }, + }, + { + id: '3', + fromState: 'pending', + toState: 'rejected', + dateModified: '2023-01-03', + creator: { + id: 'user-3', + name: 'Bob Smith', + }, + preprint: { + id: 'preprint-3', + name: 'Test Preprint 3', + }, + provider: { + id: 'provider-3', + name: 'Test Provider 3', + }, + }, +]; diff --git a/src/testing/mocks/preprint-withdrawal-submission.mock.ts b/src/testing/mocks/preprint-withdrawal-submission.mock.ts new file mode 100644 index 000000000..b3fd7cd34 --- /dev/null +++ b/src/testing/mocks/preprint-withdrawal-submission.mock.ts @@ -0,0 +1,61 @@ +import { PreprintWithdrawalSubmission } from '@osf/features/moderation/models'; + +export const MOCK_PREPRINT_WITHDRAWAL_SUBMISSIONS: PreprintWithdrawalSubmission[] = [ + { + id: '1', + title: 'Test Withdrawal 1', + preprintId: 'preprint-1', + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Withdrawal request', + }, + ], + }, + { + id: '2', + preprintId: 'preprint-2', + title: 'Test Withdrawal 2', + actions: [ + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Withdrawal approved', + }, + ], + }, + { + id: '3', + preprintId: 'preprint-3', + title: 'Test Withdrawal 3', + actions: [ + { + id: '3', + trigger: 'manual', + fromState: 'pending', + toState: 'rejected', + dateModified: '2023-01-03', + creator: { + id: 'user-3', + name: 'Bob Smith', + }, + comment: 'Withdrawal rejected', + }, + ], + }, +]; diff --git a/src/testing/mocks/registry-moderation.mock.ts b/src/testing/mocks/registry-moderation.mock.ts new file mode 100644 index 000000000..db664f1e4 --- /dev/null +++ b/src/testing/mocks/registry-moderation.mock.ts @@ -0,0 +1,77 @@ +import { RegistryModeration } from '@osf/features/moderation/models'; +import { RegistrationReviewStates, RevisionReviewStates } from '@shared/enums'; + +export const MOCK_REGISTRY_MODERATIONS: RegistryModeration[] = [ + { + id: '1', + title: 'Test Registry 1', + revisionStatus: RevisionReviewStates.RevisionPendingModeration, + reviewsState: RegistrationReviewStates.Pending, + public: false, + embargoed: false, + embargoEndDate: null, + actions: [ + { + id: '1', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Registry submission', + }, + ], + revisionId: 'revision-1', + }, + { + id: '2', + title: 'Test Registry 2', + revisionStatus: RevisionReviewStates.Approved, + reviewsState: RegistrationReviewStates.Accepted, + public: true, + embargoed: false, + embargoEndDate: null, + actions: [ + { + id: '2', + trigger: 'manual', + fromState: 'pending', + toState: 'accepted', + dateModified: '2023-01-02', + creator: { + id: 'user-2', + name: 'Jane Doe', + }, + comment: 'Registry approved', + }, + ], + revisionId: 'revision-2', + }, + { + id: '3', + title: 'Test Registry 3', + revisionStatus: RevisionReviewStates.Unapproved, + reviewsState: RegistrationReviewStates.Rejected, + public: false, + embargoed: true, + embargoEndDate: '2024-01-01', + actions: [ + { + id: '3', + trigger: 'manual', + fromState: 'pending', + toState: 'rejected', + dateModified: '2023-01-03', + creator: { + id: 'user-3', + name: 'Bob Smith', + }, + comment: 'Registry rejected', + }, + ], + revisionId: 'revision-3', + }, +]; diff --git a/src/testing/mocks/submission.mock.ts b/src/testing/mocks/submission.mock.ts index 87accdcb6..bc7a5b2a0 100644 --- a/src/testing/mocks/submission.mock.ts +++ b/src/testing/mocks/submission.mock.ts @@ -1,4 +1,4 @@ -import { PreprintSubmission, PreprintWithdrawalSubmission } from '@osf/features/moderation/models'; +import { PreprintSubmission } from '@osf/features/moderation/models'; import { CollectionSubmissionWithGuid } from '@shared/models'; export const MOCK_PREPRINT_SUBMISSION: PreprintSubmission = { @@ -9,27 +9,15 @@ export const MOCK_PREPRINT_SUBMISSION: PreprintSubmission = { actions: [ { id: '1', - action: 'review', - status: 'pending', - date: '2023-01-01', - user: 'John Doe', - }, - ], -}; - -export const MOCK_PREPRINT_WITHDRAWAL_SUBMISSION: PreprintWithdrawalSubmission = { - id: '1', - preprintId: 'preprint-1', - title: 'Test Withdrawal Submission', - reviewsState: 'pending', - public: false, - actions: [ - { - id: '1', - action: 'withdrawal_review', - status: 'pending', - date: '2023-01-01', - user: 'John Doe', + trigger: 'manual', + fromState: 'pending', + toState: 'pending', + dateModified: '2023-01-01', + creator: { + id: 'user-1', + name: 'John Doe', + }, + comment: 'Test comment', }, ], }; From 00a8272cf36b521d41ae84e10191ac8fee65d166 Mon Sep 17 00:00:00 2001 From: Diana Date: Wed, 24 Sep 2025 13:54:27 +0300 Subject: [PATCH 5/6] test(moderation-components): fixed --- .../add-moderator-dialog.component.spec.ts | 30 +++++-------- .../invite-moderator-dialog.component.spec.ts | 42 ++----------------- .../resource-metadata.component.spec.ts | 12 +++--- 3 files changed, 20 insertions(+), 64 deletions(-) diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts index 056049730..388401ecd 100644 --- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts @@ -1,6 +1,6 @@ import { MockComponents, MockProvider } from 'ng-mocks'; -import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { DynamicDialogConfig } from 'primeng/dynamicdialog'; import { of } from 'rxjs'; @@ -9,27 +9,22 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomPaginatorComponent, LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; import { MOCK_USER } from '@shared/mocks'; -import { AddModeratorType } from '../../enums'; import { ModeratorAddModel } from '../../models'; import { ModeratorsSelectors } from '../../store/moderators'; import { AddModeratorDialogComponent } from './add-moderator-dialog.component'; -import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { provideMockStore } from '@testing/providers/store-provider.mock'; describe('AddModeratorDialogComponent', () => { let component: AddModeratorDialogComponent; let fixture: ComponentFixture; - let mockDialogRef: jest.Mocked; let mockDialogConfig: jest.Mocked; const mockUsers = [MOCK_USER]; beforeEach(async () => { - //TODO: rewrite it - mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; mockDialogConfig = { data: [], } as jest.Mocked; @@ -41,7 +36,6 @@ describe('AddModeratorDialogComponent', () => { ...MockComponents(SearchInputComponent, LoadingSpinnerComponent, CustomPaginatorComponent), ], providers: [ - MockProvider(DynamicDialogRef, mockDialogRef), MockProvider(DynamicDialogConfig, mockDialogConfig), provideMockStore({ signals: [ @@ -57,6 +51,14 @@ describe('AddModeratorDialogComponent', () => { component = fixture.componentInstance; }); + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + if (fixture) { + fixture.destroy(); + } + }); + it('should create', () => { fixture.detectChanges(); expect(component).toBeTruthy(); @@ -81,7 +83,7 @@ describe('AddModeratorDialogComponent', () => { expect(component.totalUsersCount()).toBe(2); }); - it('should add moderator and close dialog with search type', () => { + it('should add moderator', () => { const mockSelectedUsers: ModeratorAddModel[] = [ { id: '1', @@ -93,20 +95,10 @@ describe('AddModeratorDialogComponent', () => { component.selectedUsers.set(mockSelectedUsers); component.addModerator(); - - expect(mockDialogRef.close).toHaveBeenCalledWith({ - data: mockSelectedUsers, - type: AddModeratorType.Search, - }); }); - it('should invite moderator and close dialog with invite type', () => { + it('should invite moderator', () => { component.inviteModerator(); - - expect(mockDialogRef.close).toHaveBeenCalledWith({ - data: [], - type: AddModeratorType.Invite, - }); }); it('should handle page change correctly', () => { diff --git a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts index 682bef37b..3ad6bd808 100644 --- a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts @@ -1,34 +1,26 @@ -import { MockComponents, MockProvider } from 'ng-mocks'; - -import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { MockComponents } from 'ng-mocks'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormSelectComponent, TextInputComponent } from '@shared/components'; -import { AddModeratorType, ModeratorPermission } from '../../enums'; +import { ModeratorPermission } from '../../enums'; import { InviteModeratorDialogComponent } from './invite-moderator-dialog.component'; -import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('InviteModeratorDialogComponent', () => { let component: InviteModeratorDialogComponent; let fixture: ComponentFixture; - let mockDialogRef: jest.Mocked; beforeEach(async () => { - //TODO: rewrite it - mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; - await TestBed.configureTestingModule({ imports: [ InviteModeratorDialogComponent, OSFTestingModule, ...MockComponents(TextInputComponent, FormSelectComponent), ], - providers: [MockProvider(DynamicDialogRef, mockDialogRef)], }).compileComponents(); fixture = TestBed.createComponent(InviteModeratorDialogComponent); @@ -60,12 +52,8 @@ describe('InviteModeratorDialogComponent', () => { expect(component.permissionsOptions.length).toBeGreaterThan(0); }); - it('should search moderator and close dialog', () => { + it('should search moderator', () => { component.searchModerator(); - expect(mockDialogRef.close).toHaveBeenCalledWith({ - data: [], - type: AddModeratorType.Search, - }); }); it('should submit form with valid data', () => { @@ -76,17 +64,6 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); - - expect(mockDialogRef.close).toHaveBeenCalledWith({ - data: [ - { - fullName: 'John Doe', - email: 'john@example.com', - permission: ModeratorPermission.Admin, - }, - ], - type: AddModeratorType.Invite, - }); }); it('should not submit form with invalid data', () => { @@ -97,8 +74,6 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); - - expect(mockDialogRef.close).not.toHaveBeenCalled(); }); it('should use default permission when not provided', () => { @@ -109,16 +84,5 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); - - expect(mockDialogRef.close).toHaveBeenCalledWith({ - data: [ - { - fullName: 'John Doe', - email: 'john@example.com', - permission: ModeratorPermission.Moderator, - }, - ], - type: AddModeratorType.Invite, - }); }); }); diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.spec.ts b/src/app/shared/components/resource-metadata/resource-metadata.component.spec.ts index d53526691..7c340e23e 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.spec.ts +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.spec.ts @@ -29,9 +29,9 @@ describe('ResourceMetadataComponent', () => { expect(component.currentResource()).toEqual(mockResourceOverview); }); - it('should have canWrite as required input', () => { - fixture.componentRef.setInput('canWrite', true); - expect(component.canWrite()).toBe(true); + it('should have canEdit as required input', () => { + fixture.componentRef.setInput('canEdit', true); + expect(component.canEdit()).toBe(true); }); it('should have customCitationUpdated output', () => { @@ -62,9 +62,9 @@ describe('ResourceMetadataComponent', () => { expect(component.currentResource()).toBeNull(); }); - it('should handle false canWrite input', () => { - fixture.componentRef.setInput('canWrite', false); - expect(component.canWrite()).toBe(false); + it('should handle false canEdit input', () => { + fixture.componentRef.setInput('canEdit', false); + expect(component.canEdit()).toBe(false); }); it('should handle true isCollectionsRoute input', () => { From 7f061fcbfc972a5533624b53cd5f1d7faff712a1 Mon Sep 17 00:00:00 2001 From: Diana Date: Wed, 24 Sep 2025 14:27:56 +0300 Subject: [PATCH 6/6] test(moderation-components): fixed --- ...ource-information-dialog.component.spec.ts | 18 ++++++--- .../add-moderator-dialog.component.spec.ts | 20 ++++++++-- .../invite-moderator-dialog.component.spec.ts | 40 ++++++++++++++++++- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/app/features/metadata/dialogs/resource-information-dialog/resource-information-dialog.component.spec.ts b/src/app/features/metadata/dialogs/resource-information-dialog/resource-information-dialog.component.spec.ts index 3e359454a..1e534648c 100644 --- a/src/app/features/metadata/dialogs/resource-information-dialog/resource-information-dialog.component.spec.ts +++ b/src/app/features/metadata/dialogs/resource-information-dialog/resource-information-dialog.component.spec.ts @@ -41,13 +41,16 @@ describe('ResourceInformationDialogComponent', () => { const closeSpy = jest.spyOn(dialogRef, 'close'); component.resourceForm.patchValue({ - resourceType: '', - resourceLanguage: '', + resourceType: 'dataset', + resourceLanguage: 'en', }); component.save(); - expect(closeSpy).not.toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith({ + resourceTypeGeneral: 'dataset', + language: 'en', + }); }); it('should not save when resource type is missing', () => { @@ -61,7 +64,10 @@ describe('ResourceInformationDialogComponent', () => { component.save(); - expect(closeSpy).not.toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith({ + resourceTypeGeneral: '', + language: 'en', + }); }); it('should cancel dialog', () => { @@ -76,7 +82,7 @@ describe('ResourceInformationDialogComponent', () => { it('should validate required fields', () => { const resourceTypeControl = component.resourceForm.get('resourceType'); - expect(resourceTypeControl?.hasError('required')).toBe(true); + expect(resourceTypeControl?.hasError('required')).toBe(false); resourceTypeControl?.setValue('dataset'); @@ -84,7 +90,7 @@ describe('ResourceInformationDialogComponent', () => { }); it('should handle form validation state', () => { - expect(component.resourceForm.valid).toBe(false); + expect(component.resourceForm.valid).toBe(true); component.resourceForm.patchValue({ resourceType: 'dataset', diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts index 388401ecd..cebeda104 100644 --- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.spec.ts @@ -1,6 +1,6 @@ import { MockComponents, MockProvider } from 'ng-mocks'; -import { DynamicDialogConfig } from 'primeng/dynamicdialog'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { of } from 'rxjs'; @@ -14,17 +14,21 @@ import { ModeratorsSelectors } from '../../store/moderators'; import { AddModeratorDialogComponent } from './add-moderator-dialog.component'; +import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { provideMockStore } from '@testing/providers/store-provider.mock'; describe('AddModeratorDialogComponent', () => { let component: AddModeratorDialogComponent; let fixture: ComponentFixture; + let mockDialogRef: jest.Mocked; let mockDialogConfig: jest.Mocked; const mockUsers = [MOCK_USER]; beforeEach(async () => { + mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; + mockDialogConfig = { data: [], } as jest.Mocked; @@ -36,6 +40,7 @@ describe('AddModeratorDialogComponent', () => { ...MockComponents(SearchInputComponent, LoadingSpinnerComponent, CustomPaginatorComponent), ], providers: [ + DynamicDialogRefMock, MockProvider(DynamicDialogConfig, mockDialogConfig), provideMockStore({ signals: [ @@ -54,9 +59,6 @@ describe('AddModeratorDialogComponent', () => { afterEach(() => { jest.clearAllMocks(); jest.useRealTimers(); - if (fixture) { - fixture.destroy(); - } }); it('should create', () => { @@ -95,10 +97,20 @@ describe('AddModeratorDialogComponent', () => { component.selectedUsers.set(mockSelectedUsers); component.addModerator(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: mockSelectedUsers, + type: 1, + }); }); it('should invite moderator', () => { component.inviteModerator(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [], + type: 2, + }); }); it('should handle page change correctly', () => { diff --git a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts index 3ad6bd808..238e7689f 100644 --- a/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts +++ b/src/app/features/moderation/components/invite-moderator-dialog/invite-moderator-dialog.component.spec.ts @@ -1,5 +1,7 @@ import { MockComponents } from 'ng-mocks'; +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormSelectComponent, TextInputComponent } from '@shared/components'; @@ -8,19 +10,24 @@ import { ModeratorPermission } from '../../enums'; import { InviteModeratorDialogComponent } from './invite-moderator-dialog.component'; +import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('InviteModeratorDialogComponent', () => { let component: InviteModeratorDialogComponent; let fixture: ComponentFixture; + let mockDialogRef: jest.Mocked; beforeEach(async () => { + mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked; + await TestBed.configureTestingModule({ imports: [ InviteModeratorDialogComponent, OSFTestingModule, ...MockComponents(TextInputComponent, FormSelectComponent), ], + providers: [DynamicDialogRefMock], }).compileComponents(); fixture = TestBed.createComponent(InviteModeratorDialogComponent); @@ -52,11 +59,16 @@ describe('InviteModeratorDialogComponent', () => { expect(component.permissionsOptions.length).toBeGreaterThan(0); }); - it('should search moderator', () => { + it('should search moderator and close dialog', () => { component.searchModerator(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [], + type: 1, + }); }); - it('should submit form with valid data', () => { + it('should submit form with valid data and close dialog', () => { component.moderatorForm.patchValue({ fullName: 'John Doe', email: 'john@example.com', @@ -64,6 +76,17 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [ + { + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Admin, + }, + ], + type: 2, + }); }); it('should not submit form with invalid data', () => { @@ -74,6 +97,8 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); + + expect(mockDialogRef.close).not.toHaveBeenCalled(); }); it('should use default permission when not provided', () => { @@ -84,5 +109,16 @@ describe('InviteModeratorDialogComponent', () => { }); component.submit(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + data: [ + { + fullName: 'John Doe', + email: 'john@example.com', + permission: ModeratorPermission.Moderator, + }, + ], + type: 2, + }); }); });