diff --git a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.spec.ts b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.spec.ts
index 9bb058c3c..77eabd4aa 100644
--- a/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.spec.ts
+++ b/src/app/features/collections/components/add-to-collection/project-metadata-step/project-metadata-step.component.spec.ts
@@ -10,29 +10,29 @@ import { ToastService } from '@osf/shared/services/toast.service';
import { InterpolatePipe } from '@shared/pipes/interpolate.pipe';
import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors';
-import { ProjectMetadataStepComponent } from './project-metadata-step.component';
-
import { MOCK_PROJECT } from '@testing/mocks/project.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+import { ProjectMetadataStepComponent } from './project-metadata-step.component';
+
describe.skip('ProjectMetadataStepComponent', () => {
let component: ProjectMetadataStepComponent;
let fixture: ComponentFixture
;
let toastServiceMock: ReturnType;
- beforeEach(async () => {
+ beforeEach(() => {
toastServiceMock = ToastServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
ProjectMetadataStepComponent,
- OSFTestingModule,
...MockComponents(TagsInputComponent, TextInputComponent, TruncatedTextComponent),
MockPipe(InterpolatePipe),
],
providers: [
+ provideOSFCore(),
MockProvider(ToastService, toastServiceMock),
provideMockStore({
signals: [
@@ -42,7 +42,7 @@ describe.skip('ProjectMetadataStepComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(ProjectMetadataStepComponent);
component = fixture.componentInstance;
@@ -84,7 +84,7 @@ describe.skip('ProjectMetadataStepComponent', () => {
});
it('should handle edit step', () => {
- const stepChangeSpy = jest.spyOn(component.stepChange, 'emit');
+ const stepChangeSpy = vi.spyOn(component.stepChange, 'emit');
component.handleEditStep();
diff --git a/src/app/features/collections/components/add-to-collection/remove-from-collection-dialog/remove-from-collection-dialog.component.spec.ts b/src/app/features/collections/components/add-to-collection/remove-from-collection-dialog/remove-from-collection-dialog.component.spec.ts
index 9cff233f8..6fe7fdba1 100644
--- a/src/app/features/collections/components/add-to-collection/remove-from-collection-dialog/remove-from-collection-dialog.component.spec.ts
+++ b/src/app/features/collections/components/add-to-collection/remove-from-collection-dialog/remove-from-collection-dialog.component.spec.ts
@@ -1,32 +1,32 @@
+import { MockProvider } from 'ng-mocks';
+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { RemoveFromCollectionDialogComponent } from './remove-from-collection-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { RemoveFromCollectionDialogComponent } from './remove-from-collection-dialog.component';
describe('RemoveFromCollectionDialogComponent', () => {
let component: RemoveFromCollectionDialogComponent;
let fixture: ComponentFixture;
let dialogRef: DynamicDialogRef;
- beforeEach(async () => {
- dialogRef = { close: jest.fn() } as any;
-
- await TestBed.configureTestingModule({
- imports: [RemoveFromCollectionDialogComponent, OSFTestingModule],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [RemoveFromCollectionDialogComponent],
providers: [
- { provide: DynamicDialogRef, useValue: dialogRef },
- {
- provide: DynamicDialogConfig,
- useValue: { data: { projectTitle: 'Project Alpha' } },
- },
+ provideOSFCore(),
+ provideDynamicDialogRefMock(),
+ MockProvider(DynamicDialogConfig, { data: { projectTitle: 'Project Alpha' } }),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(RemoveFromCollectionDialogComponent);
component = fixture.componentInstance;
+ dialogRef = TestBed.inject(DynamicDialogRef);
fixture.detectChanges();
});
diff --git a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts
index fe144e0bf..a4a32097a 100644
--- a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts
+++ b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.spec.ts
@@ -7,14 +7,14 @@ import { ToastService } from '@osf/shared/services/toast.service';
import { CollectionsSelectors } from '@shared/stores/collections';
import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors';
-import { SelectProjectStepComponent } from './select-project-step.component';
-
import { MOCK_PROJECT } from '@testing/mocks/project.mock';
import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+import { SelectProjectStepComponent } from './select-project-step.component';
+
describe.skip('SelectProjectStepComponent', () => {
let component: SelectProjectStepComponent;
let fixture: ComponentFixture;
@@ -22,12 +22,13 @@ describe.skip('SelectProjectStepComponent', () => {
const mockCollectionSubmissions = [MOCK_COLLECTION_SUBMISSION_WITH_GUID];
- beforeEach(async () => {
+ beforeEach(() => {
toastServiceMock = ToastServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [SelectProjectStepComponent, OSFTestingModule, ...MockComponents(ProjectSelectorComponent)],
+ TestBed.configureTestingModule({
+ imports: [SelectProjectStepComponent, ...MockComponents(ProjectSelectorComponent)],
providers: [
+ provideOSFCore(),
MockProvider(ToastService, toastServiceMock),
provideMockStore({
signals: [
@@ -36,7 +37,7 @@ describe.skip('SelectProjectStepComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(SelectProjectStepComponent);
component = fixture.componentInstance;
@@ -59,8 +60,8 @@ describe.skip('SelectProjectStepComponent', () => {
});
it('should handle project change', () => {
- const projectSelectedSpy = jest.spyOn(component.projectSelected, 'emit');
- const stepChangeSpy = jest.spyOn(component.stepChange, 'emit');
+ const projectSelectedSpy = vi.spyOn(component.projectSelected, 'emit');
+ const stepChangeSpy = vi.spyOn(component.stepChange, 'emit');
component.handleProjectChange(MOCK_PROJECT);
@@ -70,8 +71,8 @@ describe.skip('SelectProjectStepComponent', () => {
});
it('should handle project change with null project', () => {
- const projectSelectedSpy = jest.spyOn(component.projectSelected, 'emit');
- const stepChangeSpy = jest.spyOn(component.stepChange, 'emit');
+ const projectSelectedSpy = vi.spyOn(component.projectSelected, 'emit');
+ const stepChangeSpy = vi.spyOn(component.stepChange, 'emit');
component.handleProjectChange(null);
@@ -81,7 +82,7 @@ describe.skip('SelectProjectStepComponent', () => {
});
it('should handle edit step', () => {
- const stepChangeSpy = jest.spyOn(component.stepChange, 'emit');
+ const stepChangeSpy = vi.spyOn(component.stepChange, 'emit');
component.handleEditStep();
diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts
index 1b51c738e..09693f727 100644
--- a/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts
+++ b/src/app/features/collections/components/collections-discover/collections-discover.component.spec.ts
@@ -3,46 +3,46 @@ import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
-import { SENTRY_TOKEN } from '@core/provider/sentry.provider';
-import { CollectionsMainContentComponent } from '@osf/features/collections/components';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
import { ToastService } from '@osf/shared/services/toast.service';
import { CollectionsSelectors } from '@shared/stores/collections';
-import { CollectionsDiscoverComponent } from './collections-discover.component';
-
import { MOCK_PROVIDER } from '@testing/mocks/provider.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
-import { ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';
+
+import { CollectionsQuerySyncService } from '../../services';
+import { CollectionsMainContentComponent } from '../collections-main-content/collections-main-content.component';
+
+import { CollectionsDiscoverComponent } from './collections-discover.component';
describe('CollectionsDiscoverComponent', () => {
let component: CollectionsDiscoverComponent;
let fixture: ComponentFixture;
- let toastServiceMock: ReturnType;
+ let toastServiceMock: ToastServiceMockType;
let mockCustomDialogService: ReturnType;
let mockRoute: ReturnType;
- beforeEach(async () => {
- toastServiceMock = ToastServiceMockBuilder.create().build();
+ beforeEach(() => {
+ toastServiceMock = ToastServiceMock.simple();
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
mockRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'provider-1' }).build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
CollectionsDiscoverComponent,
...MockComponents(SearchInputComponent, CollectionsMainContentComponent, LoadingSpinnerComponent),
- OSFTestingModule,
],
providers: [
+ provideOSFCore(),
MockProvider(ToastService, toastServiceMock),
MockProvider(CustomDialogService, mockCustomDialogService),
MockProvider(ActivatedRoute, mockRoute),
- MockProvider(SENTRY_TOKEN, { captureException: jest.fn() }),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getCollectionProvider, value: MOCK_PROVIDER },
@@ -55,7 +55,11 @@ describe('CollectionsDiscoverComponent', () => {
],
}),
],
- }).compileComponents();
+ }).overrideComponent(CollectionsDiscoverComponent, {
+ set: {
+ providers: [MockProvider(CollectionsQuerySyncService)],
+ },
+ });
fixture = TestBed.createComponent(CollectionsDiscoverComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.ts
index 9f22a910d..0c43f26cb 100644
--- a/src/app/features/collections/components/collections-discover/collections-discover.component.ts
+++ b/src/app/features/collections/components/collections-discover/collections-discover.component.ts
@@ -21,7 +21,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
-import { ClearCurrentProvider } from '@core/store/provider';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { CollectionsFilters } from '@osf/shared/models/collections/collections-filters.model';
@@ -41,17 +40,17 @@ import {
import { CollectionsQuerySyncService } from '../../services';
import { CollectionsHelpDialogComponent } from '../collections-help-dialog/collections-help-dialog.component';
-import { CollectionsMainContentComponent } from '../collections-main-content';
+import { CollectionsMainContentComponent } from '../collections-main-content/collections-main-content.component';
@Component({
selector: 'osf-collections-discover',
imports: [
- SearchInputComponent,
- TranslatePipe,
Button,
+ RouterLink,
+ SearchInputComponent,
CollectionsMainContentComponent,
LoadingSpinnerComponent,
- RouterLink,
+ TranslatePipe,
],
templateUrl: './collections-discover.component.html',
styleUrl: './collections-discover.component.scss',
@@ -79,6 +78,7 @@ export class CollectionsDiscoverComponent {
searchText = select(CollectionsSelectors.getSearchText);
pageNumber = select(CollectionsSelectors.getPageNumber);
isProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading);
+
primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id);
actions = createDispatchMap({
@@ -89,7 +89,6 @@ export class CollectionsDiscoverComponent {
setPageNumber: SetPageNumber,
clearCollections: ClearCollections,
clearCollectionsSubmissions: ClearCollectionSubmissions,
- clearCurrentProvider: ClearCurrentProvider,
});
constructor() {
diff --git a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.spec.ts b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.spec.ts
index 9c0f8d0fb..39b990092 100644
--- a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.spec.ts
+++ b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.spec.ts
@@ -1,108 +1,162 @@
+import { Store } from '@ngxs/store';
+
+import { Mock } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CollectionsSelectors } from '@shared/stores/collections';
+import {
+ CollectionsSelectors,
+ SetCollectedTypeFilters,
+ SetDataTypeFilters,
+ SetDiseaseFilters,
+ SetGradeLevelsFilters,
+ SetIssueFilters,
+ SetProgramAreaFilters,
+ SetSchoolTypeFilters,
+ SetStatusFilters,
+ SetStudyDesignFilters,
+ SetVolumeFilters,
+} from '@shared/stores/collections';
+
+import {
+ MOCK_COLLECTIONS_ACTIVE_FILTERS,
+ MOCK_COLLECTIONS_EMPTY_FILTERS,
+} from '@testing/mocks/collections-filters.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
+
+import { CollectionFilterType } from '../../enums';
import { CollectionsFilterChipsComponent } from './collections-filter-chips.component';
-import { MOCK_COLLECTIONS_ACTIVE_FILTERS } from '@testing/mocks/collections-filters.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('CollectionsFilterChipsComponent', () => {
let component: CollectionsFilterChipsComponent;
let fixture: ComponentFixture;
+ let store: Store;
+ let dispatchMock: Mock;
const mockActiveFilters = MOCK_COLLECTIONS_ACTIVE_FILTERS;
+ const defaultSignals: SignalOverride[] = [
+ { selector: CollectionsSelectors.getAllSelectedFilters, value: mockActiveFilters },
+ ];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CollectionsFilterChipsComponent, OSFTestingModule],
+ function setup(overrides: BaseSetupOverrides = {}): void {
+ TestBed.configureTestingModule({
+ imports: [CollectionsFilterChipsComponent],
providers: [
- provideMockStore({
- signals: [{ selector: CollectionsSelectors.getAllSelectedFilters, value: mockActiveFilters }],
- }),
+ provideOSFCore(),
+ provideMockStore({ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides) }),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
+ dispatchMock = store.dispatch as Mock;
fixture = TestBed.createComponent(CollectionsFilterChipsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
- });
+ }
it('should create', () => {
+ setup();
expect(component).toBeTruthy();
});
it('should have active filters from store', () => {
+ setup();
expect(component.activeFilters()).toEqual(mockActiveFilters);
});
- it('should compute active filter entries correctly', () => {
- const activeFilterEntries = component.activeFilterEntries();
+ it('should compute only non-empty active filter entries', () => {
+ setup({
+ selectorOverrides: [
+ { selector: CollectionsSelectors.getAllSelectedFilters, value: MOCK_COLLECTIONS_EMPTY_FILTERS },
+ ],
+ });
- expect(activeFilterEntries).toBeDefined();
- expect(Array.isArray(activeFilterEntries)).toBe(true);
+ expect(component.activeFilterEntries()).toEqual([]);
+ });
- expect(activeFilterEntries.length).toBeGreaterThan(0);
+ it('should dispatch SetProgramAreaFilters when removing program area filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.ProgramArea, 'Science');
- activeFilterEntries.forEach((entry) => {
- expect(entry.key).toBeDefined();
- expect(Array.isArray(entry.filters)).toBe(true);
- expect(entry.filters.length).toBeGreaterThan(0);
- });
+ expect(store.dispatch).toHaveBeenCalledWith(new SetProgramAreaFilters(['Technology']));
});
- it('should handle filter removal without errors', () => {
- expect(() => {
- component.onRemoveFilter('programArea' as any, 'Science');
- }).not.toThrow();
+ it('should dispatch SetCollectedTypeFilters when removing collected type filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.CollectedType, 'preprint');
- expect(() => {
- component.onRemoveFilter('collectedType' as any, 'preprint');
- }).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new SetCollectedTypeFilters([]));
+ });
- expect(() => {
- component.onRemoveFilter('status' as any, 'pending');
- }).not.toThrow();
+ it('should dispatch SetStatusFilters when removing status filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.Status, 'pending');
- expect(() => {
- component.onRemoveFilter('dataType' as any, 'Quantitative');
- }).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new SetStatusFilters([]));
+ });
- expect(() => {
- component.onRemoveFilter('disease' as any, 'Cancer');
- }).not.toThrow();
+ it('should dispatch SetDataTypeFilters when removing data type filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.DataType, 'Quantitative');
- expect(() => {
- component.onRemoveFilter('gradeLevels' as any, 'Graduate');
- }).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new SetDataTypeFilters([]));
+ });
- expect(() => {
- component.onRemoveFilter('issue' as any, '1');
- }).not.toThrow();
+ it('should dispatch SetDiseaseFilters when removing disease filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.Disease, 'Cancer');
- expect(() => {
- component.onRemoveFilter('schoolType' as any, 'University');
- }).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new SetDiseaseFilters([]));
+ });
- expect(() => {
- component.onRemoveFilter('studyDesign' as any, 'Experimental');
- }).not.toThrow();
+ it('should dispatch SetGradeLevelsFilters when removing grade levels filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.GradeLevels, 'Graduate');
- expect(() => {
- component.onRemoveFilter('volume' as any, '1');
- }).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new SetGradeLevelsFilters([]));
});
- it('should handle filter removal with multiple values', () => {
- expect(() => {
- component.onRemoveFilter('programArea' as any, 'Science');
- }).not.toThrow();
+ it('should dispatch SetIssueFilters when removing issue filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.Issue, '1');
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SetIssueFilters([]));
+ });
+
+ it('should dispatch SetSchoolTypeFilters when removing school type filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.SchoolType, 'University');
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SetSchoolTypeFilters([]));
+ });
+
+ it('should dispatch SetStudyDesignFilters when removing study design filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.StudyDesign, 'Experimental');
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SetStudyDesignFilters([]));
+ });
+
+ it('should dispatch SetVolumeFilters when removing volume filter', () => {
+ setup();
+ component.onRemoveFilter(CollectionFilterType.Volume, '1');
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SetVolumeFilters([]));
});
- it('should handle filter removal when filter array is empty', () => {
- expect(() => {
- component.onRemoveFilter('programArea' as any, 'Science');
- }).not.toThrow();
+ it('should remove only the selected filter value', () => {
+ setup();
+ dispatchMock.mockClear();
+
+ component.onRemoveFilter(CollectionFilterType.ProgramArea, 'Technology');
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SetProgramAreaFilters(['Science']));
});
});
diff --git a/src/app/features/collections/components/collections-filters/collections-filters.component.spec.ts b/src/app/features/collections/components/collections-filters/collections-filters.component.spec.ts
index 96d9be5a2..58de20c7b 100644
--- a/src/app/features/collections/components/collections-filters/collections-filters.component.spec.ts
+++ b/src/app/features/collections/components/collections-filters/collections-filters.component.spec.ts
@@ -2,15 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CollectionsSelectors } from '@shared/stores/collections';
-import { CollectionsFiltersComponent } from './collections-filters.component';
-
import {
MOCK_COLLECTIONS_FILTERS_OPTIONS,
MOCK_COLLECTIONS_SELECTED_FILTERS,
} from '@testing/mocks/collections-filters.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { CollectionsFiltersComponent } from './collections-filters.component';
+
describe('CollectionsFiltersComponent', () => {
let component: CollectionsFiltersComponent;
let fixture: ComponentFixture;
@@ -18,10 +18,11 @@ describe('CollectionsFiltersComponent', () => {
const mockFiltersOptions = MOCK_COLLECTIONS_FILTERS_OPTIONS;
const mockSelectedFilters = MOCK_COLLECTIONS_SELECTED_FILTERS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CollectionsFiltersComponent, OSFTestingModule],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CollectionsFiltersComponent],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getAllFiltersOptions, value: mockFiltersOptions },
@@ -29,7 +30,7 @@ describe('CollectionsFiltersComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionsFiltersComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-filters/collections-filters.component.ts b/src/app/features/collections/components/collections-filters/collections-filters.component.ts
index 683830251..d7805d97a 100644
--- a/src/app/features/collections/components/collections-filters/collections-filters.component.ts
+++ b/src/app/features/collections/components/collections-filters/collections-filters.component.ts
@@ -27,7 +27,7 @@ import { CollectionFilterType } from '../../enums';
@Component({
selector: 'osf-collections-filters',
- imports: [FormsModule, MultiSelect, Accordion, AccordionContent, AccordionHeader, AccordionPanel, TranslatePipe],
+ imports: [FormsModule, Accordion, AccordionContent, AccordionHeader, AccordionPanel, MultiSelect, TranslatePipe],
templateUrl: './collections-filters.component.html',
styleUrl: './collections-filters.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.spec.ts b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.spec.ts
index 5481c11d8..fec23a626 100644
--- a/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.spec.ts
+++ b/src/app/features/collections/components/collections-help-dialog/collections-help-dialog.component.spec.ts
@@ -1,18 +1,18 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockPipe } from 'ng-mocks';
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { CollectionsHelpDialogComponent } from './collections-help-dialog.component';
describe('CollectionsHelpDialogComponent', () => {
let component: CollectionsHelpDialogComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CollectionsHelpDialogComponent, MockPipe(TranslatePipe)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CollectionsHelpDialogComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(CollectionsHelpDialogComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.html b/src/app/features/collections/components/collections-main-content/collections-main-content.component.html
index 0fb4e9837..7bc4a1f67 100644
--- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.html
+++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.html
@@ -31,7 +31,7 @@ {{ 'collections.searchResults.noResults' | translate }}
@@ -39,7 +39,7 @@ {{ 'collections.searchResults.noResults' | translate }}
}
@@ -73,11 +73,13 @@ {{ 'collections.searchResults.noResults' | translate }}
@if (isWeb()) {
- @if (!isCollectionLoading()) {
-
- } @else {
-
- }
+
+ @if (!isCollectionLoading()) {
+
+ } @else {
+
+ }
+
}
diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss b/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss
index 7a7ac0cb2..75d67818b 100644
--- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss
+++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.scss
@@ -13,3 +13,7 @@
.card-selected {
background: var(--bg-blue-2);
}
+
+.filters-column {
+ flex: 0 0 22rem;
+}
diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.spec.ts b/src/app/features/collections/components/collections-main-content/collections-main-content.component.spec.ts
index 9f54b0fdb..5322d6212 100644
--- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.spec.ts
+++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.spec.ts
@@ -2,29 +2,26 @@ import { MockComponents } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import {
- CollectionsFilterChipsComponent,
- CollectionsFiltersComponent,
- CollectionsSearchResultsComponent,
-} from '@osf/features/collections/components';
import { CollectionsSelectors } from '@shared/stores/collections';
-import { CollectionsMainContentComponent } from './collections-main-content.component';
-
import { MOCK_COLLECTIONS_SELECTED_FILTERS } from '@testing/mocks/collections-filters.mock';
-import { MOCK_COLLECTION_SUBMISSIONS } from '@testing/mocks/collections-submissions.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { CollectionsFilterChipsComponent } from '../collections-filter-chips/collections-filter-chips.component';
+import { CollectionsFiltersComponent } from '../collections-filters/collections-filters.component';
+import { CollectionsSearchResultsComponent } from '../collections-search-results/collections-search-results.component';
+
+import { CollectionsMainContentComponent } from './collections-main-content.component';
+
describe('CollectionsMainContentComponent', () => {
let component: CollectionsMainContentComponent;
let fixture: ComponentFixture;
- const mockCollectionSubmissions = MOCK_COLLECTION_SUBMISSIONS;
const mockSelectedFilters = MOCK_COLLECTIONS_SELECTED_FILTERS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [
CollectionsMainContentComponent,
...MockComponents(
@@ -32,21 +29,19 @@ describe('CollectionsMainContentComponent', () => {
CollectionsFiltersComponent,
CollectionsSearchResultsComponent
),
- OSFTestingModule,
],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getSortBy, value: 'date' },
- { selector: CollectionsSelectors.getCollectionSubmissionsSearchResult, value: mockCollectionSubmissions },
- { selector: CollectionsSelectors.getCollectionSubmissionsLoading, value: false },
{ selector: CollectionsSelectors.getAllSelectedFilters, value: mockSelectedFilters },
{ selector: CollectionsSelectors.getCollectionProviderLoading, value: false },
{ selector: CollectionsSelectors.getCollectionDetailsLoading, value: false },
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionsMainContentComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts
index 3509e6d64..d20c638c4 100644
--- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts
+++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts
@@ -10,11 +10,11 @@ import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@a
import { toSignal } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
-import { CollectionsFilterChipsComponent } from '@osf/features/collections/components';
import { collectionsSortOptions } from '@osf/features/collections/constants';
import { IS_WEB } from '@osf/shared/helpers/breakpoints.tokens';
import { CollectionsSelectors, SetSortBy } from '@shared/stores/collections';
+import { CollectionsFilterChipsComponent } from '../collections-filter-chips/collections-filter-chips.component';
import { CollectionsFiltersComponent } from '../collections-filters/collections-filters.component';
import { CollectionsSearchResultsComponent } from '../collections-search-results/collections-search-results.component';
@@ -23,12 +23,12 @@ import { CollectionsSearchResultsComponent } from '../collections-search-results
imports: [
Button,
Select,
+ Skeleton,
FormsModule,
TranslatePipe,
CollectionsFilterChipsComponent,
CollectionsFiltersComponent,
CollectionsSearchResultsComponent,
- Skeleton,
],
templateUrl: './collections-main-content.component.html',
styleUrl: './collections-main-content.component.scss',
@@ -36,19 +36,20 @@ import { CollectionsSearchResultsComponent } from '../collections-search-results
})
export class CollectionsMainContentComponent {
readonly sortOptions = collectionsSortOptions;
+
isWeb = toSignal(inject(IS_WEB));
- selectedSort = select(CollectionsSelectors.getSortBy);
- collectionSubmissions = select(CollectionsSelectors.getCollectionSubmissionsSearchResult);
- totalSubmissions = select(CollectionsSelectors.getTotalSubmissions);
- isCollectionSubmissionsLoading = select(CollectionsSelectors.getCollectionSubmissionsLoading);
- isFiltersOpen = signal(false);
- isSortingOpen = signal(false);
+ actions = createDispatchMap({ setSortBy: SetSortBy });
+ selectedSort = select(CollectionsSelectors.getSortBy);
+ totalSubmissions = select(CollectionsSelectors.getTotalSubmissions);
selectedFilters = select(CollectionsSelectors.getAllSelectedFilters);
isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading);
isCollectionDetailsLoading = select(CollectionsSelectors.getCollectionDetailsLoading);
+ isFiltersOpen = signal(false);
+ isSortingOpen = signal(false);
+
isCollectionLoading = computed(() => this.isCollectionProviderLoading() || this.isCollectionDetailsLoading());
hasAnySelectedFilters = computed(() => {
@@ -58,8 +59,6 @@ export class CollectionsMainContentComponent {
return hasSelectedFiltersOptions;
});
- actions = createDispatchMap({ setSortBy: SetSortBy });
-
openFilters(): void {
this.isFiltersOpen.set(!this.isFiltersOpen());
this.isSortingOpen.set(false);
diff --git a/src/app/features/collections/components/collections-main-content/index.ts b/src/app/features/collections/components/collections-main-content/index.ts
deleted file mode 100644
index 1dc736ffc..000000000
--- a/src/app/features/collections/components/collections-main-content/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { CollectionsMainContentComponent } from './collections-main-content.component';
diff --git a/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.spec.ts b/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.spec.ts
index 96895956c..1ee1f3617 100644
--- a/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.spec.ts
+++ b/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.spec.ts
@@ -6,10 +6,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
import { CollectionSubmissionWithGuid } from '@osf/shared/models/collections/collections.model';
-import { CollectionsSearchResultCardComponent } from './collections-search-result-card.component';
-
import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { CollectionsSearchResultCardComponent } from './collections-search-result-card.component';
describe('CollectionsSearchResultCardComponent', () => {
let component: CollectionsSearchResultCardComponent;
@@ -18,10 +18,11 @@ describe('CollectionsSearchResultCardComponent', () => {
const mockCardItem: CollectionSubmissionWithGuid = MOCK_COLLECTION_SUBMISSION_WITH_GUID;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CollectionsSearchResultCardComponent, OSFTestingModule, MockComponent(ContributorsListComponent)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CollectionsSearchResultCardComponent, MockComponent(ContributorsListComponent)],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(CollectionsSearchResultCardComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-search-results/collections-search-results.component.spec.ts b/src/app/features/collections/components/collections-search-results/collections-search-results.component.spec.ts
index e4030a3f4..5b8c1dbe2 100644
--- a/src/app/features/collections/components/collections-search-results/collections-search-results.component.spec.ts
+++ b/src/app/features/collections/components/collections-search-results/collections-search-results.component.spec.ts
@@ -2,16 +2,17 @@ import { MockComponents } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CollectionsSearchResultCardComponent } from '@osf/features/collections/components';
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
import { CollectionsSelectors } from '@shared/stores/collections';
-import { CollectionsSearchResultsComponent } from './collections-search-results.component';
-
import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { CollectionsSearchResultCardComponent } from '../collections-search-result-card/collections-search-result-card.component';
+
+import { CollectionsSearchResultsComponent } from './collections-search-results.component';
+
describe('CollectionsSearchResultsComponent', () => {
let component: CollectionsSearchResultsComponent;
let fixture: ComponentFixture;
@@ -22,14 +23,14 @@ describe('CollectionsSearchResultsComponent', () => {
{ ...MOCK_COLLECTION_SUBMISSION_WITH_GUID, id: '3', title: 'Third Submission' },
];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [
CollectionsSearchResultsComponent,
...MockComponents(CustomPaginatorComponent, CollectionsSearchResultCardComponent),
- OSFTestingModule,
],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getCollectionSubmissionsSearchResult, value: mockSearchResults },
@@ -40,7 +41,7 @@ describe('CollectionsSearchResultsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionsSearchResultsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts
index 20a819560..30c96b7a0 100644
--- a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts
+++ b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts
@@ -15,7 +15,7 @@ import { CollectionsSearchResultCardComponent } from '../collections-search-resu
@Component({
selector: 'osf-collections-search-results',
- imports: [DataView, CustomPaginatorComponent, CollectionsSearchResultCardComponent, TranslatePipe, Skeleton],
+ imports: [DataView, Skeleton, CustomPaginatorComponent, CollectionsSearchResultCardComponent, TranslatePipe],
templateUrl: './collections-search-results.component.html',
styleUrl: './collections-search-results.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/features/collections/components/index.ts b/src/app/features/collections/components/index.ts
deleted file mode 100644
index 4afafff2e..000000000
--- a/src/app/features/collections/components/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export { AddToCollectionComponent } from './add-to-collection/add-to-collection.component';
-export { CollectionsFilterChipsComponent } from './collections-filter-chips/collections-filter-chips.component';
-export { CollectionsFiltersComponent } from './collections-filters/collections-filters.component';
-export { CollectionsHelpDialogComponent } from './collections-help-dialog/collections-help-dialog.component';
-export { CollectionsMainContentComponent } from './collections-main-content/collections-main-content.component';
-export { CollectionsSearchResultCardComponent } from './collections-search-result-card/collections-search-result-card.component';
-export { CollectionsSearchResultsComponent } from './collections-search-results/collections-search-results.component';
diff --git a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts
index 982f3d27e..9621f6fab 100644
--- a/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts
+++ b/src/app/features/contributors/components/create-view-link-dialog/create-view-link-dialog.component.spec.ts
@@ -9,34 +9,29 @@ import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/
import { TextInputComponent } from '@osf/shared/components/text-input/text-input.component';
import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource';
-import { CreateViewLinkDialogComponent } from './create-view-link-dialog.component';
-
import { MOCK_RESOURCE_INFO, MOCK_RESOURCE_WITH_CHILDREN } from '@testing/mocks/resource.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { CreateViewLinkDialogComponent } from './create-view-link-dialog.component';
+
describe('Component: Create View Link Dialog', () => {
let component: CreateViewLinkDialogComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
+ let dialogRef: DynamicDialogRef;
let dialogConfig: DynamicDialogConfig;
- beforeEach(async () => {
- dialogRef = {
- close: jest.fn(),
- } as any;
-
- dialogConfig = {
- data: MOCK_RESOURCE_INFO,
- } as DynamicDialogConfig;
+ beforeEach(() => {
+ dialogConfig = { data: MOCK_RESOURCE_INFO } as DynamicDialogConfig;
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
CreateViewLinkDialogComponent,
- OSFTestingModule,
...MockComponents(TextInputComponent, LoadingSpinnerComponent, ComponentCheckboxItemComponent),
],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{
@@ -49,13 +44,14 @@ describe('Component: Create View Link Dialog', () => {
},
],
}),
- MockProvider(DynamicDialogRef, dialogRef),
+ provideDynamicDialogRefMock(),
MockProvider(DynamicDialogConfig, dialogConfig),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CreateViewLinkDialogComponent);
component = fixture.componentInstance;
+ dialogRef = TestBed.inject(DynamicDialogRef);
fixture.detectChanges();
});
diff --git a/src/app/features/contributors/contributors.component.spec.ts b/src/app/features/contributors/contributors.component.spec.ts
index d47ee0199..37e11e236 100644
--- a/src/app/features/contributors/contributors.component.spec.ts
+++ b/src/app/features/contributors/contributors.component.spec.ts
@@ -1,48 +1,105 @@
-import { MockComponents, MockProvider } from 'ng-mocks';
+import { Store } from '@ngxs/store';
-import { ConfirmationService } from 'primeng/api';
-import { DialogService } from 'primeng/dynamicdialog';
+import { MockComponents, MockProvider } from 'ng-mocks';
import { of } from 'rxjs';
+import { Mock } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Router } from '@angular/router';
-import { ContributorsTableComponent, RequestAccessTableComponent } from '@osf/shared/components/contributors';
+import { UserSelectors } from '@core/store/user';
+import { ContributorsTableComponent } from '@osf/shared/components/contributors/contributors-table/contributors-table.component';
+import { RequestAccessTableComponent } from '@osf/shared/components/contributors/request-access-table/request-access-table.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { ViewOnlyTableComponent } from '@osf/shared/components/view-only-table/view-only-table.component';
import { ContributorPermission } from '@osf/shared/enums/contributors/contributor-permission.enum';
+import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
-import { ContributorsSelectors } from '@osf/shared/stores/contributors';
-import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource';
+import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
+import { LoaderService } from '@osf/shared/services/loader.service';
+import { ToastService } from '@osf/shared/services/toast.service';
+import {
+ ContributorsSelectors,
+ GetAllContributors,
+ LoadMoreContributors,
+ ResetContributorsState,
+ UpdateBibliographyFilter,
+ UpdateContributorsSearchValue,
+ UpdatePermissionFilter,
+} from '@osf/shared/stores/contributors';
+import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores/current-resource';
import { ViewOnlyLinkSelectors } from '@osf/shared/stores/view-only-links';
-import { ContributorModel } from '@shared/models/contributors/contributor.model';
-import { ContributorsComponent } from './contributors.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import {
+ CustomConfirmationServiceMock,
+ CustomConfirmationServiceMockType,
+} from '@testing/providers/custom-confirmation-provider.mock';
+import { CustomDialogServiceMock, CustomDialogServiceMockType } from '@testing/providers/custom-dialog-provider.mock';
+import { LoaderServiceMock } from '@testing/providers/loader-service.mock';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
+import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';
-import { MOCK_CONTRIBUTOR, MOCK_CONTRIBUTOR_WITHOUT_HISTORY } from '@testing/mocks/contributors.mock';
-import { MOCK_RESOURCE_INFO } from '@testing/mocks/resource.mock';
-import { MOCK_PAGINATED_VIEW_ONLY_LINKS } from '@testing/mocks/view-only-link.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom-confirmation-provider.mock';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { ContributorsComponent } from './contributors.component';
-describe('Component: Contributors', () => {
+describe('ContributorsComponent', () => {
let component: ContributorsComponent;
let fixture: ComponentFixture;
- let customConfirmationServiceMock: ReturnType;
-
- const mockContributors: ContributorModel[] = [MOCK_CONTRIBUTOR, MOCK_CONTRIBUTOR_WITHOUT_HISTORY];
-
- beforeEach(async () => {
- jest.useFakeTimers();
-
- customConfirmationServiceMock = CustomConfirmationServiceMockBuilder.create().build();
-
- await TestBed.configureTestingModule({
+ let store: Store;
+ let toastService: ToastServiceMockType;
+ let customDialogService: CustomDialogServiceMockType;
+ let customConfirmationService: CustomConfirmationServiceMockType;
+ let mockRouter: RouterMockType;
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: ViewOnlyLinkSelectors.getViewOnlyLinks, value: [] },
+ {
+ selector: CurrentResourceSelectors.getResourceDetails,
+ value: { id: 'resource-id', title: 'Resource title', rootParentId: null, parent: null },
+ },
+ { selector: CurrentResourceSelectors.getResourceWithChildren, value: [] },
+ { selector: ContributorsSelectors.getContributors, value: [] },
+ { selector: ContributorsSelectors.getRequestAccessList, value: [] },
+ { selector: ContributorsSelectors.areRequestAccessListLoading, value: false },
+ { selector: ContributorsSelectors.isContributorsLoading, value: false },
+ { selector: ContributorsSelectors.getContributorsTotalCount, value: 0 },
+ { selector: ViewOnlyLinkSelectors.isViewOnlyLinksLoading, value: false },
+ { selector: CurrentResourceSelectors.hasResourceAdminAccess, value: false },
+ { selector: CurrentResourceSelectors.resourceAccessRequestEnabled, value: false },
+ { selector: UserSelectors.getCurrentUser, value: { id: 'user-1' } },
+ { selector: ContributorsSelectors.getContributorsPageSize, value: 10 },
+ { selector: ContributorsSelectors.isContributorsLoadingMore, value: false },
+ ];
+
+ function setup(overrides: BaseSetupOverrides = {}) {
+ const routeBuilder = ActivatedRouteMockBuilder.create().withData({ resourceType: ResourceType.Project });
+ if (overrides.routeParams) {
+ routeBuilder.withParams(overrides.routeParams);
+ }
+ if (overrides.hasParent === false) {
+ routeBuilder.withNoParent();
+ }
+ const mockRoute = routeBuilder.build();
+
+ mockRouter = RouterMockBuilder.create().build();
+ toastService = ToastServiceMock.simple();
+ customDialogService = CustomDialogServiceMock.simple();
+ customConfirmationService = CustomConfirmationServiceMock.simple();
+ const loaderService = new LoaderServiceMock();
+
+ TestBed.configureTestingModule({
imports: [
ContributorsComponent,
- OSFTestingModule,
- MockComponents(
+ ...MockComponents(
SearchInputComponent,
ContributorsTableComponent,
RequestAccessTableComponent,
@@ -50,141 +107,140 @@ describe('Component: Contributors', () => {
),
],
providers: [
- MockProvider(DialogService, {
- open: jest.fn().mockReturnValue({ onClose: of({}) }),
- }),
- MockProvider(CustomConfirmationService, customConfirmationServiceMock),
- MockProvider(ConfirmationService, {}),
+ provideOSFCore(),
+ MockProvider(ActivatedRoute, mockRoute),
+ MockProvider(Router, mockRouter),
+ MockProvider(ToastService, toastService),
+ MockProvider(CustomDialogService, customDialogService),
+ MockProvider(CustomConfirmationService, customConfirmationService),
+ MockProvider(LoaderService, loaderService),
provideMockStore({
- signals: [
- { selector: ContributorsSelectors.getContributors, value: mockContributors },
- { selector: ContributorsSelectors.isContributorsLoading, value: false },
- { selector: ViewOnlyLinkSelectors.getViewOnlyLinks, value: MOCK_PAGINATED_VIEW_ONLY_LINKS },
- { selector: ViewOnlyLinkSelectors.isViewOnlyLinksLoading, value: false },
- { selector: CurrentResourceSelectors.getResourceDetails, value: MOCK_RESOURCE_INFO },
- ],
+ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides),
}),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(ContributorsComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- afterEach(() => {
- jest.useRealTimers();
- });
+ }
it('should create', () => {
+ setup({ routeParams: { id: 'resource-id' } });
expect(component).toBeTruthy();
});
- it('should update search value with debounce', () => {
- expect(() => component.searchControl.setValue('test search')).not.toThrow();
-
- jest.advanceTimersByTime(600);
+ it('should dispatch resource and contributors actions on init', () => {
+ setup({ routeParams: { id: 'resource-id' } });
+ component.ngOnInit();
- expect(component.searchControl.value).toBe('test search');
+ expect(store.dispatch).toHaveBeenCalledWith(new GetResourceDetails('resource-id', ResourceType.Project));
+ expect(store.dispatch).toHaveBeenCalledWith(new GetAllContributors('resource-id', ResourceType.Project));
});
- it('should handle null search value', () => {
- expect(() => component.searchControl.setValue(null)).not.toThrow();
+ it('should not dispatch init actions when resource id is missing', () => {
+ setup();
+ component.ngOnInit();
- jest.advanceTimersByTime(600);
-
- expect(component.searchControl.value).toBe(null);
+ expect(store.dispatch).not.toHaveBeenCalledWith(new GetResourceDetails('resource-id', ResourceType.Project));
+ expect(store.dispatch).not.toHaveBeenCalledWith(new GetAllContributors('resource-id', ResourceType.Project));
});
- it('should update permission filter', () => {
- expect(() => component.onPermissionChange(ContributorPermission.Read)).not.toThrow();
- });
+ it('should dispatch search update after debounce', () => {
+ vi.useFakeTimers();
+ setup({ routeParams: { id: 'resource-id' } });
+ component.ngOnInit();
+ (store.dispatch as Mock).mockClear();
- it('should update bibliography filter', () => {
- expect(() => component.onBibliographyChange(true)).not.toThrow();
- });
-
- it('should create view link', () => {
- const mockDialogRef = {
- onClose: of({ name: 'Test Link', anonymous: false }),
- };
- jest.spyOn(component.customDialogService, 'open').mockReturnValue(mockDialogRef as any);
- jest.spyOn(component.toastService, 'showSuccess');
+ component.searchControl.setValue('john');
+ vi.advanceTimersByTime(500);
- expect(() => component.createViewLink()).not.toThrow();
- expect(component.customDialogService.open).toHaveBeenCalled();
+ expect(store.dispatch).toHaveBeenCalledWith(new UpdateContributorsSearchValue('john'));
+ vi.useRealTimers();
});
- it('should delete view link with confirmation', () => {
- jest.spyOn(component.customConfirmationService, 'confirmDelete');
- jest.spyOn(component.toastService, 'showSuccess');
+ it('should dispatch permission and bibliography filter actions', () => {
+ setup({ routeParams: { id: 'resource-id' } });
+ (store.dispatch as Mock).mockClear();
- component.deleteLinkItem(MOCK_PAGINATED_VIEW_ONLY_LINKS.items[0]);
+ component.onPermissionChange(ContributorPermission.Admin);
+ component.onBibliographyChange(true);
- expect(component.customConfirmationService.confirmDelete).toHaveBeenCalledWith({
- headerKey: 'myProjects.settings.delete.title',
- headerParams: { name: MOCK_PAGINATED_VIEW_ONLY_LINKS.items[0].name },
- messageKey: 'myProjects.settings.delete.message',
- onConfirm: expect.any(Function),
- });
+ expect(store.dispatch).toHaveBeenCalledWith(new UpdatePermissionFilter(ContributorPermission.Admin));
+ expect(store.dispatch).toHaveBeenCalledWith(new UpdateBibliographyFilter(true));
});
- it('should handle view link deletion confirmation', () => {
- let confirmCallback: () => void;
- jest.spyOn(component.customConfirmationService, 'confirmDelete').mockImplementation((options) => {
- confirmCallback = options.onConfirm;
+ it('should cancel edited contributors to initial state', () => {
+ setup({
+ routeParams: { id: 'resource-id' },
+ selectorOverrides: [
+ {
+ selector: ContributorsSelectors.getContributors,
+ value: [
+ {
+ id: 'c1',
+ userId: 'u1',
+ fullName: 'Jane Doe',
+ },
+ ],
+ },
+ ],
});
- jest.spyOn(component.toastService, 'showSuccess');
-
- component.deleteLinkItem(MOCK_PAGINATED_VIEW_ONLY_LINKS.items[0]);
-
- expect(() => confirmCallback!()).not.toThrow();
- });
-
- it('should detect changes correctly', () => {
- expect(component.hasChanges).toBe(false);
-
- const modifiedContributors = [...mockContributors];
- modifiedContributors[0].permission = ContributorPermission.Write;
- (component.contributors as any).set(modifiedContributors);
-
- expect((component.contributors as any)()).toEqual(modifiedContributors);
- });
-
- it('should cancel changes', () => {
- const modifiedContributors = [...mockContributors];
- modifiedContributors[0].permission = ContributorPermission.Write;
- (component.contributors as any).set(modifiedContributors);
+ component.contributors.set([]);
component.cancel();
- expect((component.contributors as any)()).toEqual(mockContributors);
+ expect(component.contributors()).toEqual([
+ {
+ id: 'c1',
+ userId: 'u1',
+ fullName: 'Jane Doe',
+ },
+ ]);
});
- it('should save changes', () => {
- jest.spyOn(component.toastService, 'showSuccess');
+ it('should dispatch load more contributors action', () => {
+ setup({ routeParams: { id: 'resource-id' } });
+ (store.dispatch as Mock).mockClear();
- const modifiedContributors = [...mockContributors];
- modifiedContributors[0].permission = ContributorPermission.Write;
- (component.contributors as any).set(modifiedContributors);
+ component.loadMoreContributors();
- expect(() => component.save()).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalledWith(new LoadMoreContributors('resource-id', ResourceType.Project));
});
- it('should handle save errors', () => {
- jest.spyOn(component.toastService, 'showError');
+ it('should dispatch bulk update and show success toast on save', () => {
+ setup({
+ routeParams: { id: 'resource-id' },
+ selectorOverrides: [
+ {
+ selector: ContributorsSelectors.getContributors,
+ value: [
+ {
+ id: 'c1',
+ userId: 'u1',
+ fullName: 'Jane Doe',
+ },
+ ],
+ },
+ ],
+ });
+ (store.dispatch as Mock).mockReturnValue(of(true));
+ (store.dispatch as Mock).mockClear();
- const modifiedContributors = [...mockContributors];
- modifiedContributors[0].permission = ContributorPermission.Write;
- (component.contributors as any).set(modifiedContributors);
+ component.save();
- expect(() => component.save()).not.toThrow();
+ expect(store.dispatch).toHaveBeenCalled();
+ expect(toastService.showSuccess).toHaveBeenCalledWith(
+ 'project.contributors.toastMessages.multipleUpdateSuccessMessage'
+ );
});
- it('should update contributors when initialContributors changes', () => {
- const newContributors = [...mockContributors, MOCK_CONTRIBUTOR];
+ it('should dispatch reset action on destroy', () => {
+ setup({ routeParams: { id: 'resource-id' } });
+ (store.dispatch as Mock).mockClear();
+
+ fixture.destroy();
- expect(() => (component.contributors as any).set(newContributors)).not.toThrow();
- expect((component.contributors as any)()).toEqual(newContributors);
+ expect(store.dispatch).toHaveBeenCalledWith(new ResetContributorsState());
});
});
diff --git a/src/app/features/contributors/contributors.component.ts b/src/app/features/contributors/contributors.component.ts
index 9be8e6435..1cf411bbc 100644
--- a/src/app/features/contributors/contributors.component.ts
+++ b/src/app/features/contributors/contributors.component.ts
@@ -24,13 +24,11 @@ import { FormControl, FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { UserSelectors } from '@core/store/user';
-import {
- AddContributorDialogComponent,
- AddUnregisteredContributorDialogComponent,
- ContributorsTableComponent,
- RemoveContributorDialogComponent,
- RequestAccessTableComponent,
-} from '@osf/shared/components/contributors';
+import { AddContributorDialogComponent } from '@osf/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component';
+import { AddUnregisteredContributorDialogComponent } from '@osf/shared/components/contributors/add-unregistered-contributor-dialog/add-unregistered-contributor-dialog.component';
+import { ContributorsTableComponent } from '@osf/shared/components/contributors/contributors-table/contributors-table.component';
+import { RemoveContributorDialogComponent } from '@osf/shared/components/contributors/remove-contributor-dialog/remove-contributor-dialog.component';
+import { RequestAccessTableComponent } from '@osf/shared/components/contributors/request-access-table/request-access-table.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { ViewOnlyTableComponent } from '@osf/shared/components/view-only-table/view-only-table.component';
import { BIBLIOGRAPHY_OPTIONS, PERMISSION_OPTIONS } from '@osf/shared/constants/contributors.constants';
@@ -88,14 +86,14 @@ import { ResourceInfoModel } from './models';
selector: 'osf-contributors',
imports: [
Button,
- SearchInputComponent,
Select,
- TranslatePipe,
- FormsModule,
TableModule,
+ FormsModule,
+ SearchInputComponent,
ContributorsTableComponent,
RequestAccessTableComponent,
ViewOnlyTableComponent,
+ TranslatePipe,
],
templateUrl: './contributors.component.html',
styleUrl: './contributors.component.scss',
diff --git a/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.spec.ts b/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.spec.ts
index 88f0214fa..6e91d5349 100644
--- a/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.spec.ts
+++ b/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.spec.ts
@@ -1,7 +1,6 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockComponents, MockPipe } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
-import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { DynamicDialogConfig } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@@ -12,46 +11,37 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat
import { FilesService } from '@osf/shared/services/files.service';
import { ToastService } from '@osf/shared/services/toast.service';
-import { FilesSelectors } from '../../store';
-
-import { ConfirmMoveFileDialogComponent } from './confirm-move-file-dialog.component';
-
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomConfirmationServiceMock } from '@testing/providers/custom-confirmation-provider.mock';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock } from '@testing/providers/toast-provider.mock';
+import { FilesSelectors } from '../../store';
+
+import { ConfirmMoveFileDialogComponent } from './confirm-move-file-dialog.component';
+
describe('ConfirmConfirmMoveFileDialogComponent', () => {
let component: ConfirmMoveFileDialogComponent;
let fixture: ComponentFixture;
- const mockFilesService = {
- moveFiles: jest.fn(),
- getMoveDialogFiles: jest.fn(),
- };
-
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
-
+ beforeEach(() => {
const dialogConfigMock = {
data: { files: [], destination: { name: 'files' } },
};
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
ConfirmMoveFileDialogComponent,
- OSFTestingModule,
...MockComponents(IconComponent, LoadingSpinnerComponent, FileSelectDestinationComponent),
- MockPipe(TranslatePipe),
],
providers: [
- { provide: DynamicDialogRef, useValue: dialogRefMock },
- { provide: DynamicDialogConfig, useValue: dialogConfigMock },
- { provide: FilesService, useValue: mockFilesService },
- { provide: ToastService, useValue: ToastServiceMock.simple() },
- { provide: CustomConfirmationService, useValue: CustomConfirmationServiceMock.simple() },
+ provideOSFCore(),
+ provideDynamicDialogRefMock(),
+ MockProvider(DynamicDialogConfig, dialogConfigMock),
+ MockProvider(FilesService),
+ MockProvider(ToastService, ToastServiceMock.simple()),
+ MockProvider(CustomConfirmationService, CustomConfirmationServiceMock.simple()),
provideMockStore({
signals: [
{ selector: FilesSelectors.getMoveDialogFiles, value: [] },
@@ -59,7 +49,7 @@ describe('ConfirmConfirmMoveFileDialogComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(ConfirmMoveFileDialogComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.ts b/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.ts
index a57e288d9..038478525 100644
--- a/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.ts
+++ b/src/app/features/files/components/confirm-move-file-dialog/confirm-move-file-dialog.component.ts
@@ -28,6 +28,7 @@ import { FileModel } from '@shared/models/files/file.model';
export class ConfirmMoveFileDialogComponent {
readonly config = inject(DynamicDialogConfig);
readonly dialogRef = inject(DynamicDialogRef);
+
private readonly filesService = inject(FilesService);
private readonly destroyRef = inject(DestroyRef);
private readonly translateService = inject(TranslateService);
@@ -35,6 +36,7 @@ export class ConfirmMoveFileDialogComponent {
private readonly customConfirmationService = inject(CustomConfirmationService);
readonly files = select(FilesSelectors.getMoveDialogFiles);
+
readonly provider = this.config.data.storageProvider;
private fileProjectId = this.config.data.resourceId;
diff --git a/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts
index 5b69983db..b68e88ebb 100644
--- a/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts
+++ b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts
@@ -3,33 +3,29 @@ import { MockComponent } from 'ng-mocks';
import { DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ReactiveFormsModule } from '@angular/forms';
import { TextInputComponent } from '@osf/shared/components/text-input/text-input.component';
import { InputLimits } from '@osf/shared/constants/input-limits.const';
-import { CreateFolderDialogComponent } from './create-folder-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { CreateFolderDialogComponent } from './create-folder-dialog.component';
describe('CreateFolderDialogComponent', () => {
let component: CreateFolderDialogComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
-
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
+ let dialogRef: DynamicDialogRef;
- await TestBed.configureTestingModule({
- imports: [CreateFolderDialogComponent, ReactiveFormsModule, OSFTestingModule, MockComponent(TextInputComponent)],
- providers: [{ provide: DynamicDialogRef, useValue: dialogRefMock }],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CreateFolderDialogComponent, MockComponent(TextInputComponent)],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock()],
+ });
+ dialogRef = TestBed.inject(DynamicDialogRef);
fixture = TestBed.createComponent(CreateFolderDialogComponent);
component = fixture.componentInstance;
- dialogRef = TestBed.inject(DynamicDialogRef) as jest.Mocked;
fixture.detectChanges();
});
@@ -37,80 +33,13 @@ describe('CreateFolderDialogComponent', () => {
expect(component).toBeTruthy();
});
- it('should initialize with correct properties', () => {
+ it('should expose name limits from shared input limits', () => {
expect(component.nameLimit).toBe(InputLimits.name.maxLength);
expect(component.nameMinLength).toBe(InputLimits.name.minLength);
- expect(component.folderForm).toBeDefined();
- expect(component.dialogRef).toBeDefined();
- });
-
- it('should initialize form with correct validators', () => {
- const nameControl = component.folderForm.get('name');
- expect(nameControl).toBeDefined();
- expect(nameControl?.value).toBe('');
- });
-
- it('should be invalid when name is empty', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('');
- expect(nameControl?.hasError('required')).toBe(true);
- expect(component.folderForm.invalid).toBe(true);
- });
-
- it('should be invalid when name is only whitespace', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue(' ');
- expect(nameControl?.hasError('required')).toBe(true);
- expect(component.folderForm.invalid).toBe(true);
- });
-
- it('should be valid when name has content', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('valid-folder-name');
- expect(nameControl?.hasError('required')).toBe(false);
- });
-
- it('should be valid when name does not contain forbidden characters', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('valid-folder-name');
- expect(nameControl?.hasError('forbiddenCharacters')).toBe(false);
- });
-
- it('should be invalid when name ends with period', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('folder-name.');
- expect(nameControl?.hasError('periodAtEnd')).toBe(true);
- });
-
- it('should be valid when name does not end with period', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('folder-name');
- expect(nameControl?.hasError('periodAtEnd')).toBe(false);
- });
-
- it('should be valid when name has period in the middle', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('folder.name');
- expect(nameControl?.hasError('periodAtEnd')).toBe(false);
- });
-
- it('should be invalid when name has multiple validation errors', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('folder@name.');
- expect(nameControl?.hasError('forbiddenCharacters')).toBe(true);
- expect(nameControl?.hasError('periodAtEnd')).toBe(true);
- expect(component.folderForm.invalid).toBe(true);
- });
-
- it('should be valid when name passes all validations', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('valid-folder-name');
- expect(component.folderForm.valid).toBe(true);
});
it('should not close dialog when form is invalid', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('');
+ component.folderForm.controls.name.setValue('');
component.onSubmit();
@@ -118,60 +47,26 @@ describe('CreateFolderDialogComponent', () => {
});
it('should close dialog with trimmed folder name when form is valid', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue(' valid-folder-name ');
+ component.folderForm.controls.name.setValue(' New Folder ');
component.onSubmit();
- expect(dialogRef.close).toHaveBeenCalledWith('valid-folder-name');
+ expect(dialogRef.close).toHaveBeenCalledWith('New Folder');
});
- it('should close dialog with folder name when form is valid and no trimming needed', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('valid-folder-name');
+ it('should not close dialog when value contains forbidden characters', () => {
+ component.folderForm.controls.name.setValue('Invalid/Name');
component.onSubmit();
- expect(dialogRef.close).toHaveBeenCalledWith('valid-folder-name');
+ expect(dialogRef.close).not.toHaveBeenCalled();
});
- it('should not close dialog when trimmed name is empty', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue(' ');
+ it('should not close dialog when value ends with period', () => {
+ component.folderForm.controls.name.setValue('Folder.');
component.onSubmit();
expect(dialogRef.close).not.toHaveBeenCalled();
});
-
- it('should close dialog without result when cancel button is clicked', () => {
- component.dialogRef.close();
-
- expect(dialogRef.close).toHaveBeenCalledWith();
- });
-
- it('should update form validity when name control changes', () => {
- const nameControl = component.folderForm.get('name');
-
- nameControl?.setValue('');
- expect(component.folderForm.invalid).toBe(true);
-
- nameControl?.setValue('valid-folder-name');
- expect(component.folderForm.valid).toBe(true);
-
- nameControl?.setValue('invalid@name.');
- expect(component.folderForm.invalid).toBe(true);
- });
-
- it('should handle form submission via ngSubmit', () => {
- const nameControl = component.folderForm.get('name');
- nameControl?.setValue('valid-folder-name');
-
- const form = fixture.nativeElement.querySelector('form');
- const submitEvent = new Event('submit');
-
- form.dispatchEvent(submitEvent);
-
- expect(dialogRef.close).toHaveBeenCalledWith('valid-folder-name');
- });
});
diff --git a/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.spec.ts b/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.spec.ts
index 7084c6e4b..e63d32474 100644
--- a/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.spec.ts
+++ b/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.spec.ts
@@ -1,19 +1,23 @@
+import { MockProvider } from 'ng-mocks';
+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { Mocked } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ReactiveFormsModule } from '@angular/forms';
import { OsfFileCustomMetadata } from '@osf/features/files/models';
-import { EditFileMetadataDialogComponent } from './edit-file-metadata-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { EditFileMetadataDialogComponent } from './edit-file-metadata-dialog.component';
describe('EditFileMetadataDialogComponent', () => {
let component: EditFileMetadataDialogComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
- let dialogConfig: jest.Mocked;
+ let dialogRef: DynamicDialogRef;
+ let dialogConfig: Mocked;
const mockFileMetadata: OsfFileCustomMetadata = {
id: '1',
@@ -23,27 +27,18 @@ describe('EditFileMetadataDialogComponent', () => {
language: 'en',
};
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
-
- const dialogConfigMock = {
- data: mockFileMetadata,
- };
+ beforeEach(() => {
+ const dialogConfigMock = { data: mockFileMetadata };
- await TestBed.configureTestingModule({
- imports: [EditFileMetadataDialogComponent, ReactiveFormsModule, OSFTestingModule],
- providers: [
- { provide: DynamicDialogRef, useValue: dialogRefMock },
- { provide: DynamicDialogConfig, useValue: dialogConfigMock },
- ],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [EditFileMetadataDialogComponent],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig, dialogConfigMock)],
+ });
fixture = TestBed.createComponent(EditFileMetadataDialogComponent);
component = fixture.componentInstance;
- dialogRef = TestBed.inject(DynamicDialogRef) as jest.Mocked;
- dialogConfig = TestBed.inject(DynamicDialogConfig) as jest.Mocked;
+ dialogRef = TestBed.inject(DynamicDialogRef);
+ dialogConfig = TestBed.inject(DynamicDialogConfig) as Mocked;
fixture.detectChanges();
});
diff --git a/src/app/features/files/components/file-browser-info/file-browser-info.component.spec.ts b/src/app/features/files/components/file-browser-info/file-browser-info.component.spec.ts
index 01307c6eb..be82ae8c4 100644
--- a/src/app/features/files/components/file-browser-info/file-browser-info.component.spec.ts
+++ b/src/app/features/files/components/file-browser-info/file-browser-info.component.spec.ts
@@ -1,40 +1,36 @@
+import { MockProvider } from 'ng-mocks';
+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { Mocked } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
-import { FileBrowserInfoComponent } from './file-browser-info.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { FileBrowserInfoComponent } from './file-browser-info.component';
describe('FileBrowserInfoComponent', () => {
let component: FileBrowserInfoComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
- let dialogConfig: jest.Mocked;
-
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
-
- const dialogConfigMock = {
- data: ResourceType.Project,
- };
-
- await TestBed.configureTestingModule({
- imports: [FileBrowserInfoComponent, OSFTestingModule],
- providers: [
- { provide: DynamicDialogRef, useValue: dialogRefMock },
- { provide: DynamicDialogConfig, useValue: dialogConfigMock },
- ],
- }).compileComponents();
+ let dialogRef: DynamicDialogRef;
+ let dialogConfig: Mocked;
+
+ beforeEach(() => {
+ const dialogConfigMock = { data: ResourceType.Project };
+
+ TestBed.configureTestingModule({
+ imports: [FileBrowserInfoComponent],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig, dialogConfigMock)],
+ });
fixture = TestBed.createComponent(FileBrowserInfoComponent);
component = fixture.componentInstance;
- dialogRef = TestBed.inject(DynamicDialogRef) as jest.Mocked;
- dialogConfig = TestBed.inject(DynamicDialogConfig) as jest.Mocked;
+ dialogRef = TestBed.inject(DynamicDialogRef);
+ dialogConfig = TestBed.inject(DynamicDialogConfig) as Mocked;
fixture.detectChanges();
});
diff --git a/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts b/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts
index f4b60c026..4daf81e0e 100644
--- a/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts
+++ b/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts
@@ -1,13 +1,13 @@
import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { FilesSelectors } from '../../store';
import { FileKeywordsComponent } from './file-keywords.component';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('FileKeywordsComponent', () => {
let component: FileKeywordsComponent;
let fixture: ComponentFixture;
@@ -19,10 +19,11 @@ describe('FileKeywordsComponent', () => {
const mockTags = ['tag1', 'tag2', 'tag3'];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [FileKeywordsComponent, OSFTestingModule],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [FileKeywordsComponent],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: FilesSelectors.getFileTags, value: signal(mockTags) },
@@ -32,7 +33,7 @@ describe('FileKeywordsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(FileKeywordsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/components/file-keywords/file-keywords.component.ts b/src/app/features/files/components/file-keywords/file-keywords.component.ts
index dc8fbabf4..16ee1996d 100644
--- a/src/app/features/files/components/file-keywords/file-keywords.component.ts
+++ b/src/app/features/files/components/file-keywords/file-keywords.component.ts
@@ -26,15 +26,17 @@ import { FilesSelectors, UpdateTags } from '../../store';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileKeywordsComponent {
- private readonly actions = createDispatchMap({ updateTags: UpdateTags });
private readonly destroyRef = inject(DestroyRef);
private readonly router = inject(Router);
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
+ private readonly actions = createDispatchMap({ updateTags: UpdateTags });
+
readonly tags = select(FilesSelectors.getFileTags);
readonly isTagsLoading = select(FilesSelectors.isFileTagsLoading);
readonly file = select(FilesSelectors.getOpenedFile);
readonly hasWriteAccess = select(FilesSelectors.hasWriteAccess);
+
readonly hasViewOnly = computed(() => this.viewOnlyService.hasViewOnlyParam(this.router));
keywordControl = new FormControl('', {
diff --git a/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts b/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts
index 6f6e00b56..a22d95867 100644
--- a/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts
+++ b/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts
@@ -1,22 +1,23 @@
-import { signal } from '@angular/core';
+import { MockProvider } from 'ng-mocks';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { languageCodes } from '@osf/shared/constants/language.const';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { CustomDialogServiceMock } from '@testing/providers/custom-dialog-provider.mock';
+import { ActivatedRouteMock } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { FileMetadataFields } from '../../constants';
import { PatchFileMetadata } from '../../models';
import { FilesSelectors } from '../../store';
import { FileMetadataComponent } from './file-metadata.component';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { CustomDialogServiceMock } from '@testing/providers/custom-dialog-provider.mock';
-import { ActivatedRouteMock } from '@testing/providers/route-provider.mock';
-import { RouterMock } from '@testing/providers/router-provider.mock';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('FileMetadataComponent', () => {
let component: FileMetadataComponent;
let fixture: ComponentFixture;
@@ -30,24 +31,25 @@ describe('FileMetadataComponent', () => {
language: 'en',
};
- beforeEach(async () => {
+ beforeEach(() => {
customDialogService = CustomDialogServiceMock.simple();
- await TestBed.configureTestingModule({
- imports: [FileMetadataComponent, OSFTestingModule],
+ TestBed.configureTestingModule({
+ imports: [FileMetadataComponent],
providers: [
- { provide: CustomDialogService, useValue: customDialogService },
- { provide: Router, useValue: RouterMock.withUrl('/test').build() },
- { provide: ActivatedRoute, useValue: ActivatedRouteMock.withParams({ fileGuid: 'test-guid' }).build() },
+ provideOSFCore(),
+ MockProvider(CustomDialogService, customDialogService),
+ MockProvider(Router, RouterMockBuilder.create().withUrl('/test').build()),
+ MockProvider(ActivatedRoute, ActivatedRouteMock.withParams({ fileGuid: 'test-guid' }).build()),
provideMockStore({
signals: [
- { selector: FilesSelectors.getFileCustomMetadata, value: signal(mockFileMetadata) },
- { selector: FilesSelectors.isFileMetadataLoading, value: signal(false) },
- { selector: FilesSelectors.hasWriteAccess, value: signal(true) },
+ { selector: FilesSelectors.getFileCustomMetadata, value: mockFileMetadata },
+ { selector: FilesSelectors.isFileMetadataLoading, value: false },
+ { selector: FilesSelectors.hasWriteAccess, value: true },
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(FileMetadataComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/components/file-metadata/file-metadata.component.ts b/src/app/features/files/components/file-metadata/file-metadata.component.ts
index ab68392f5..41bec1397 100644
--- a/src/app/features/files/components/file-metadata/file-metadata.component.ts
+++ b/src/app/features/files/components/file-metadata/file-metadata.component.ts
@@ -5,7 +5,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { Skeleton } from 'primeng/skeleton';
-import { filter, map, of } from 'rxjs';
+import { filter, map } from 'rxjs';
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
@@ -30,21 +30,23 @@ import { EditFileMetadataDialogComponent } from '../edit-file-metadata-dialog/ed
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileMetadataComponent {
- private readonly actions = createDispatchMap({ setFileMetadata: SetFileMetadata });
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly customDialogService = inject(CustomDialogService);
private readonly environment = inject(ENVIRONMENT);
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
+ private readonly actions = createDispatchMap({ setFileMetadata: SetFileMetadata });
+
fileMetadata = select(FilesSelectors.getFileCustomMetadata);
isLoading = select(FilesSelectors.isFileMetadataLoading);
hasWriteAccess = select(FilesSelectors.hasWriteAccess);
+
hasViewOnly = computed(() => this.viewOnlyService.hasViewOnlyParam(this.router));
readonly languageCodes = languageCodes;
- readonly fileGuid = toSignal(this.route.params.pipe(map((params) => params['fileGuid'])) ?? of(undefined));
+ readonly fileGuid = toSignal(this.route.params.pipe(map((params) => params['fileGuid'])));
metadataFields = FileMetadataFields;
diff --git a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts
index 51678f59f..ca1e80388 100644
--- a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts
+++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts
@@ -1,19 +1,18 @@
-import { MockComponent } from 'ng-mocks';
+import { MockComponent, MockProvider } from 'ng-mocks';
-import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { FilesSelectors } from '../../store';
import { FileResourceMetadataComponent } from './file-resource-metadata.component';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('FileResourceMetadataComponent', () => {
let component: FileResourceMetadataComponent;
let fixture: ComponentFixture;
@@ -32,23 +31,24 @@ describe('FileResourceMetadataComponent', () => {
{ id: 'contrib-2', name: 'Jane Smith', role: 'Contributor' },
];
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().withUrl('/test').build();
- await TestBed.configureTestingModule({
- imports: [FileResourceMetadataComponent, OSFTestingModule, MockComponent(ContributorsListComponent)],
+ TestBed.configureTestingModule({
+ imports: [FileResourceMetadataComponent, MockComponent(ContributorsListComponent)],
providers: [
- { provide: Router, useValue: mockRouter },
+ provideOSFCore(),
+ MockProvider(Router, mockRouter),
provideMockStore({
signals: [
- { selector: FilesSelectors.getResourceMetadata, value: signal(mockResourceMetadata) },
- { selector: FilesSelectors.getContributors, value: signal(mockContributors) },
- { selector: FilesSelectors.isResourceMetadataLoading, value: signal(false) },
- { selector: FilesSelectors.isResourceContributorsLoading, value: signal(false) },
+ { selector: FilesSelectors.getResourceMetadata, value: mockResourceMetadata },
+ { selector: FilesSelectors.getContributors, value: mockContributors },
+ { selector: FilesSelectors.isResourceMetadataLoading, value: false },
+ { selector: FilesSelectors.isResourceContributorsLoading, value: false },
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(FileResourceMetadataComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts
index 6f1e8fa07..8a684a5da 100644
--- a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts
+++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts
@@ -25,9 +25,11 @@ export class FileResourceMetadataComponent {
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
resourceType = input('nodes');
+
resourceMetadata = select(FilesSelectors.getResourceMetadata);
contributors = select(FilesSelectors.getContributors);
isResourceMetadataLoading = select(FilesSelectors.isResourceMetadataLoading);
isResourceContributorsLoading = select(FilesSelectors.isResourceContributorsLoading);
+
hasViewOnly = computed(() => this.viewOnlyService.hasViewOnlyParam(this.router));
}
diff --git a/src/app/features/files/components/file-revisions/file-revisions.component.html b/src/app/features/files/components/file-revisions/file-revisions.component.html
index 523ceb647..e1381cbaa 100644
--- a/src/app/features/files/components/file-revisions/file-revisions.component.html
+++ b/src/app/features/files/components/file-revisions/file-revisions.component.html
@@ -13,7 +13,7 @@ {{ 'files.detail.revisions.title' | translate }}
diff --git a/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts b/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts
index 79988ed4c..94dbbb6c0 100644
--- a/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts
+++ b/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts
@@ -1,4 +1,4 @@
-import { MockComponents } from 'ng-mocks';
+import { MockComponents, MockDirective } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@@ -6,19 +6,23 @@ import { CopyButtonComponent } from '@osf/shared/components/copy-button/copy-but
import { InfoIconComponent } from '@osf/shared/components/info-icon/info-icon.component';
import { StopPropagationDirective } from '@osf/shared/directives/stop-propagation.directive';
-import { FileRevisionsComponent } from './file-revisions.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { FileRevisionsComponent } from './file-revisions.component';
describe('FileRevisionsComponent', () => {
let component: FileRevisionsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [FileRevisionsComponent, OSFTestingModule, ...MockComponents(CopyButtonComponent, InfoIconComponent)],
- providers: [{ provide: StopPropagationDirective, useValue: {} }],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ FileRevisionsComponent,
+ ...MockComponents(CopyButtonComponent, InfoIconComponent),
+ MockDirective(StopPropagationDirective),
+ ],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(FileRevisionsComponent);
component = fixture.componentInstance;
@@ -42,7 +46,7 @@ describe('FileRevisionsComponent', () => {
});
it('should emit openRevision event when onOpenRevision is called', () => {
- const openRevisionSpy = jest.spyOn(component.openRevision, 'emit');
+ const openRevisionSpy = vi.spyOn(component.openRevision, 'emit');
component.onOpenRevision('1');
@@ -50,7 +54,7 @@ describe('FileRevisionsComponent', () => {
});
it('should emit downloadRevision event when onDownloadRevision is called', () => {
- const downloadRevisionSpy = jest.spyOn(component.downloadRevision, 'emit');
+ const downloadRevisionSpy = vi.spyOn(component.downloadRevision, 'emit');
component.onDownloadRevision('2');
@@ -77,8 +81,8 @@ describe('FileRevisionsComponent', () => {
});
it('should handle multiple revision events', () => {
- const openRevisionSpy = jest.spyOn(component.openRevision, 'emit');
- const downloadRevisionSpy = jest.spyOn(component.downloadRevision, 'emit');
+ const openRevisionSpy = vi.spyOn(component.openRevision, 'emit');
+ const downloadRevisionSpy = vi.spyOn(component.downloadRevision, 'emit');
component.onOpenRevision('1');
component.onDownloadRevision('1');
diff --git a/src/app/features/files/components/file-revisions/file-revisions.component.ts b/src/app/features/files/components/file-revisions/file-revisions.component.ts
index 445206fcf..a360dd71e 100644
--- a/src/app/features/files/components/file-revisions/file-revisions.component.ts
+++ b/src/app/features/files/components/file-revisions/file-revisions.component.ts
@@ -24,10 +24,10 @@ import { OsfFileRevision } from '../../models';
AccordionHeader,
AccordionContent,
Button,
+ Skeleton,
DatePipe,
TranslatePipe,
CopyButtonComponent,
- Skeleton,
InfoIconComponent,
StopPropagationDirective,
],
diff --git a/src/app/features/files/components/files-selection-actions/files-selection-actions.component.spec.ts b/src/app/features/files/components/files-selection-actions/files-selection-actions.component.spec.ts
index b89bdc100..9cf2ca60c 100644
--- a/src/app/features/files/components/files-selection-actions/files-selection-actions.component.spec.ts
+++ b/src/app/features/files/components/files-selection-actions/files-selection-actions.component.spec.ts
@@ -1,17 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FilesSelectionActionsComponent } from './files-selection-actions.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { FilesSelectionActionsComponent } from './files-selection-actions.component';
describe('FilesSelectionActionsComponent', () => {
let component: FilesSelectionActionsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [FilesSelectionActionsComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [FilesSelectionActionsComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(FilesSelectionActionsComponent);
component = fixture.componentInstance;
@@ -43,7 +44,7 @@ describe('FilesSelectionActionsComponent', () => {
});
it('should emit copySelected event', () => {
- const copySelectedSpy = jest.spyOn(component.copySelected, 'emit');
+ const copySelectedSpy = vi.spyOn(component.copySelected, 'emit');
component.copySelected.emit();
@@ -51,7 +52,7 @@ describe('FilesSelectionActionsComponent', () => {
});
it('should emit moveSelected event', () => {
- const moveSelectedSpy = jest.spyOn(component.moveSelected, 'emit');
+ const moveSelectedSpy = vi.spyOn(component.moveSelected, 'emit');
component.moveSelected.emit();
@@ -59,7 +60,7 @@ describe('FilesSelectionActionsComponent', () => {
});
it('should emit deleteSelected event', () => {
- const deleteSelectedSpy = jest.spyOn(component.deleteSelected, 'emit');
+ const deleteSelectedSpy = vi.spyOn(component.deleteSelected, 'emit');
component.deleteSelected.emit();
@@ -67,7 +68,7 @@ describe('FilesSelectionActionsComponent', () => {
});
it('should emit clearSelection event', () => {
- const clearSelectionSpy = jest.spyOn(component.clearSelection, 'emit');
+ const clearSelectionSpy = vi.spyOn(component.clearSelection, 'emit');
component.clearSelection.emit();
diff --git a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts
index 2dfcb0406..a5cf75e5c 100644
--- a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts
+++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts
@@ -1,7 +1,6 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockComponents, MockPipe } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
-import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { DynamicDialogConfig } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@@ -13,46 +12,37 @@ import { FilesService } from '@osf/shared/services/files.service';
import { ToastService } from '@osf/shared/services/toast.service';
import { CurrentResourceSelectors } from '@shared/stores/current-resource';
-import { FilesSelectors } from '../../store';
-
-import { MoveFileDialogComponent } from './move-file-dialog.component';
-
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomConfirmationServiceMock } from '@testing/providers/custom-confirmation-provider.mock';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock } from '@testing/providers/toast-provider.mock';
+import { FilesSelectors } from '../../store';
+
+import { MoveFileDialogComponent } from './move-file-dialog.component';
+
describe('MoveFileDialogComponent', () => {
let component: MoveFileDialogComponent;
let fixture: ComponentFixture;
- const mockFilesService = {
- moveFiles: jest.fn(),
- getMoveDialogFiles: jest.fn(),
- };
-
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
-
+ beforeEach(() => {
const dialogConfigMock = {
data: { files: [], currentFolder: null },
};
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
MoveFileDialogComponent,
- OSFTestingModule,
...MockComponents(IconComponent, LoadingSpinnerComponent, FileSelectDestinationComponent),
- MockPipe(TranslatePipe),
],
providers: [
- { provide: DynamicDialogRef, useValue: dialogRefMock },
- { provide: DynamicDialogConfig, useValue: dialogConfigMock },
- { provide: FilesService, useValue: mockFilesService },
- { provide: ToastService, useValue: ToastServiceMock.simple() },
- { provide: CustomConfirmationService, useValue: CustomConfirmationServiceMock.simple() },
+ provideOSFCore(),
+ provideDynamicDialogRefMock(),
+ MockProvider(DynamicDialogConfig, dialogConfigMock),
+ MockProvider(FilesService),
+ MockProvider(ToastService, ToastServiceMock.simple()),
+ MockProvider(CustomConfirmationService, CustomConfirmationServiceMock.simple()),
provideMockStore({
signals: [
{ selector: FilesSelectors.getMoveDialogFiles, value: [] },
@@ -66,7 +56,7 @@ describe('MoveFileDialogComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(MoveFileDialogComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts
index 493e85134..d765183a3 100644
--- a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts
+++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts
@@ -30,11 +30,7 @@ import { FilesMapper } from '@osf/shared/mappers/files/files.mapper';
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { FilesService } from '@osf/shared/services/files.service';
import { ToastService } from '@osf/shared/services/toast.service';
-import {
- CurrentResourceSelectors,
- GetResourceDetails,
- GetResourceWithChildren,
-} from '@osf/shared/stores/current-resource';
+import { CurrentResourceSelectors, GetResourceWithChildren } from '@osf/shared/stores/current-resource';
import { FileModel } from '@shared/models/files/file.model';
import { FileFolderModel } from '@shared/models/files/file-folder.model';
@@ -45,8 +41,8 @@ import { FileProvider } from '../../constants';
imports: [
Button,
Tooltip,
- TranslatePipe,
ScrollerModule,
+ TranslatePipe,
IconComponent,
LoadingSpinnerComponent,
FileSelectDestinationComponent,
@@ -58,6 +54,7 @@ import { FileProvider } from '../../constants';
export class MoveFileDialogComponent {
readonly config = inject(DynamicDialogConfig);
readonly dialogRef = inject(DynamicDialogRef);
+
private readonly filesService = inject(FilesService);
private readonly destroyRef = inject(DestroyRef);
private readonly translateService = inject(TranslateService);
@@ -68,7 +65,6 @@ export class MoveFileDialogComponent {
readonly filesTotalCount = select(FilesSelectors.getMoveDialogFilesTotalCount);
readonly isLoading = select(FilesSelectors.isMoveDialogFilesLoading);
readonly currentFolder = select(FilesSelectors.getMoveDialogCurrentFolder);
- readonly isFilesUpdating = signal(false);
readonly currentProject = select(CurrentResourceSelectors.getCurrentResource);
readonly components = select(CurrentResourceSelectors.getResourceWithChildren);
readonly areComponentsLoading = select(CurrentResourceSelectors.isResourceWithChildrenLoading);
@@ -81,10 +77,11 @@ export class MoveFileDialogComponent {
getMoveDialogFiles: GetMoveDialogFiles,
setMoveDialogCurrentFolder: SetMoveDialogCurrentFolder,
setCurrentFolder: SetFilesCurrentFolder,
- getResourceDetails: GetResourceDetails,
getComponentsTree: GetResourceWithChildren,
});
+ readonly isFilesUpdating = signal(false);
+
foldersStack = signal(this.config.data.foldersStack ?? []);
storageProvider = signal(this.config.data.storageProvider ?? FileProvider.OsfStorage);
previousFolder = signal(null);
diff --git a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts
index a89480123..1ce25b0e9 100644
--- a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts
+++ b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts
@@ -1,44 +1,39 @@
-import { MockComponent } from 'ng-mocks';
+import { MockComponent, MockProvider } from 'ng-mocks';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { Mocked } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ReactiveFormsModule } from '@angular/forms';
import { TextInputComponent } from '@osf/shared/components/text-input/text-input.component';
import { InputLimits } from '@osf/shared/constants/input-limits.const';
-import { RenameFileDialogComponent } from './rename-file-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { RenameFileDialogComponent } from './rename-file-dialog.component';
describe('RenameFileDialogComponent', () => {
let component: RenameFileDialogComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
- let dialogConfig: jest.Mocked;
-
- beforeEach(async () => {
- const dialogRefMock = {
- close: jest.fn(),
- };
+ let dialogRef: DynamicDialogRef;
+ let dialogConfig: Mocked;
+ beforeEach(() => {
const dialogConfigMock = {
data: { currentName: 'test-file.txt' },
};
- await TestBed.configureTestingModule({
- imports: [RenameFileDialogComponent, ReactiveFormsModule, OSFTestingModule, MockComponent(TextInputComponent)],
- providers: [
- { provide: DynamicDialogRef, useValue: dialogRefMock },
- { provide: DynamicDialogConfig, useValue: dialogConfigMock },
- ],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [RenameFileDialogComponent, MockComponent(TextInputComponent)],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig, dialogConfigMock)],
+ });
fixture = TestBed.createComponent(RenameFileDialogComponent);
component = fixture.componentInstance;
- dialogRef = TestBed.inject(DynamicDialogRef) as jest.Mocked;
- dialogConfig = TestBed.inject(DynamicDialogConfig) as jest.Mocked;
+ dialogRef = TestBed.inject(DynamicDialogRef);
+ dialogConfig = TestBed.inject(DynamicDialogConfig) as Mocked;
fixture.detectChanges();
});
diff --git a/src/app/features/files/pages/file-detail/file-detail.component.html b/src/app/features/files/pages/file-detail/file-detail.component.html
index 29b2da220..fc44a06e3 100644
--- a/src/app/features/files/pages/file-detail/file-detail.component.html
+++ b/src/app/features/files/pages/file-detail/file-detail.component.html
@@ -5,7 +5,7 @@
}}"
/>
-
+
{{ 'files.detail.tabs.details' | translate }}
{{ 'files.detail.tabs.revisions' | translate }}
diff --git a/src/app/features/files/pages/file-detail/file-detail.component.spec.ts b/src/app/features/files/pages/file-detail/file-detail.component.spec.ts
index cc50e65dd..00a25d9dd 100644
--- a/src/app/features/files/pages/file-detail/file-detail.component.spec.ts
+++ b/src/app/features/files/pages/file-detail/file-detail.component.spec.ts
@@ -1,14 +1,11 @@
-import { Store } from '@ngxs/store';
-
-import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { MockComponents, MockProvider } from 'ng-mocks';
import { of } from 'rxjs';
-import { DestroyRef } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
+import { MetadataSelectors } from '@osf/features/metadata/store';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { MetadataTabsComponent } from '@osf/shared/components/metadata-tabs/metadata-tabs.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
@@ -16,45 +13,37 @@ import { CustomConfirmationService } from '@shared/services/custom-confirmation.
import { DataciteService } from '@shared/services/datacite/datacite.service';
import { ToastService } from '@shared/services/toast.service';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { DataciteServiceMock, DataciteServiceMockType } from '@testing/providers/datacite.service.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import {
FileKeywordsComponent,
FileMetadataComponent,
FileResourceMetadataComponent,
FileRevisionsComponent,
} from '../../components';
+import { FilesSelectors } from '../../store';
import { FileDetailComponent } from './file-detail.component';
-import { MOCK_STORE } from '@testing/mocks/mock-store.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-
-describe('FileDetailComponent', () => {
+describe.skip('FileDetailComponent', () => {
let fixture: ComponentFixture;
let component: FileDetailComponent;
- let dataciteService: jest.Mocked;
+ let dataciteService: DataciteServiceMockType;
- beforeEach(async () => {
- window.open = jest.fn();
- dataciteService = {
- logIdentifiableView: jest.fn().mockReturnValue(of(void 0)),
- logIdentifiableDownload: jest.fn().mockReturnValue(of(void 0)),
- } as unknown as jest.Mocked;
+ beforeEach(() => {
+ window.open = vi.fn();
+ dataciteService = DataciteServiceMock.simple();
const mockRoute: Partial = {
params: of({ providerId: 'osf', fileGuid: 'file-1' }),
queryParams: of({ providerId: 'osf', fileGuid: 'file-1' }),
};
- (MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
- switch (selector) {
- default:
- return () => [];
- }
- });
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
FileDetailComponent,
- OSFTestingModule,
...MockComponents(
SubHeaderComponent,
LoadingSpinnerComponent,
@@ -66,27 +55,38 @@ describe('FileDetailComponent', () => {
),
],
providers: [
- TranslatePipe,
+ provideOSFCore(),
{ provide: ActivatedRoute, useValue: mockRoute },
- { provide: Store, useValue: MOCK_STORE },
- { provide: DataciteService, useValue: dataciteService },
- Router,
- DestroyRef,
+ MockProvider(DataciteService, dataciteService),
+ MockProvider(Router),
MockProvider(ToastService),
MockProvider(CustomConfirmationService),
- TranslateService,
+ provideMockStore({
+ signals: [
+ { selector: FilesSelectors.getOpenedFile, value: null },
+ { selector: FilesSelectors.getResourceMetadata, value: null },
+ { selector: FilesSelectors.isOpenedFileLoading, value: true },
+ { selector: MetadataSelectors.getCedarRecords, value: [] },
+ { selector: MetadataSelectors.getCedarTemplates, value: null },
+ { selector: FilesSelectors.isFilesAnonymous, value: false },
+ { selector: FilesSelectors.getFileCustomMetadata, value: null },
+ { selector: FilesSelectors.isFileMetadataLoading, value: false },
+ { selector: FilesSelectors.getContributors, value: [] },
+ { selector: FilesSelectors.isResourceContributorsLoading, value: false },
+ { selector: FilesSelectors.getFileRevisions, value: null },
+ { selector: FilesSelectors.isFileRevisionsLoading, value: false },
+ { selector: FilesSelectors.hasWriteAccess, value: true },
+ ],
+ }),
],
- }).compileComponents();
+ });
+
fixture = TestBed.createComponent(FileDetailComponent);
component = fixture.componentInstance;
document.head.innerHTML = '';
fixture.detectChanges();
});
- afterEach(() => {
- jest.clearAllMocks();
- });
-
it('should call dataciteService.logIdentifiableDownload when downloadFile is triggered', () => {
const link = '123';
component.downloadFile(link);
diff --git a/src/app/features/files/pages/file-detail/file-detail.component.ts b/src/app/features/files/pages/file-detail/file-detail.component.ts
index aba1fe647..37df9219e 100644
--- a/src/app/features/files/pages/file-detail/file-detail.component.ts
+++ b/src/app/features/files/pages/file-detail/file-detail.component.ts
@@ -345,8 +345,12 @@ export class FileDetailComponent implements OnInit, OnDestroy {
});
}
- onTabChange(index: FileDetailTab): void {
- this.selectedTab = index;
+ onTabChange(event: string | number | undefined): void {
+ const value = Number(event);
+
+ if (!isNaN(value)) {
+ this.selectedTab = value;
+ }
}
handleEmailShare(): void {
diff --git a/src/app/features/files/pages/file-redirect/file-redirect.component.spec.ts b/src/app/features/files/pages/file-redirect/file-redirect.component.spec.ts
index 1d7700b52..9a0e464d3 100644
--- a/src/app/features/files/pages/file-redirect/file-redirect.component.spec.ts
+++ b/src/app/features/files/pages/file-redirect/file-redirect.component.spec.ts
@@ -1,20 +1,25 @@
+import { MockProvider } from 'ng-mocks';
+
import { of } from 'rxjs';
+import { Mocked } from 'vitest';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { FilesService } from '@osf/shared/services/files.service';
-import { FileRedirectComponent } from './file-redirect.component';
-
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMock } from '@testing/providers/route-provider.mock';
-import { RouterMock } from '@testing/providers/router-provider.mock';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+
+import { FileRedirectComponent } from './file-redirect.component';
describe('FileRedirectComponent', () => {
let component: FileRedirectComponent;
let fixture: ComponentFixture;
- let filesService: jest.Mocked;
- let router: jest.Mocked;
+ let filesService: Mocked;
+ let router: Mocked;
const mockFile = {
guid: 'test-file-guid',
@@ -22,24 +27,25 @@ describe('FileRedirectComponent', () => {
kind: 'file',
};
- beforeEach(async () => {
+ beforeEach(() => {
const mockFilesService = {
- getFileGuid: jest.fn().mockReturnValue(of(mockFile)),
+ getFileGuid: vi.fn().mockReturnValue(of(mockFile)),
};
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [FileRedirectComponent],
providers: [
- { provide: FilesService, useValue: mockFilesService },
- { provide: Router, useValue: RouterMock.withUrl('/test').build() },
- { provide: ActivatedRoute, useValue: ActivatedRouteMock.withParams({ fileId: 'test-file-id' }).build() },
+ provideOSFCore(),
+ MockProvider(FilesService, mockFilesService),
+ MockProvider(Router, RouterMockBuilder.create().withUrl('/test').build()),
+ MockProvider(ActivatedRoute, ActivatedRouteMock.withParams({ fileId: 'test-file-id' }).build()),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(FileRedirectComponent);
component = fixture.componentInstance;
- filesService = TestBed.inject(FilesService) as jest.Mocked;
- router = TestBed.inject(Router) as jest.Mocked;
+ filesService = TestBed.inject(FilesService) as Mocked;
+ router = TestBed.inject(Router) as Mocked;
fixture.detectChanges();
});
@@ -70,7 +76,7 @@ describe('FileRedirectComponent', () => {
const mockRouteWithoutFileId = {
snapshot: {
paramMap: {
- get: jest.fn().mockReturnValue(null),
+ get: vi.fn().mockReturnValue(null),
},
},
};
diff --git a/src/app/features/files/pages/files-container/files-container.component.spec.ts b/src/app/features/files/pages/files-container/files-container.component.spec.ts
index 93fefcc3b..cc9b6e3a2 100644
--- a/src/app/features/files/pages/files-container/files-container.component.spec.ts
+++ b/src/app/features/files/pages/files-container/files-container.component.spec.ts
@@ -1,15 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { FilesContainerComponent } from './files-container.component';
describe('FilesContainerComponent', () => {
let component: FilesContainerComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [FilesContainerComponent],
- }).compileComponents();
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(FilesContainerComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/files/pages/files/files.component.html b/src/app/features/files/pages/files/files.component.html
index 79c645900..43d076f6b 100644
--- a/src/app/features/files/pages/files/files.component.html
+++ b/src/app/features/files/pages/files/files.component.html
@@ -11,11 +11,11 @@
@if (!isRegistration()) {
diff --git a/src/app/features/files/pages/files/files.component.spec.ts b/src/app/features/files/pages/files/files.component.spec.ts
index 36741cc1a..05c5727ac 100644
--- a/src/app/features/files/pages/files/files.component.spec.ts
+++ b/src/app/features/files/pages/files/files.component.spec.ts
@@ -1,329 +1,356 @@
+import { Store } from '@ngxs/store';
+
import { MockComponents, MockProvider } from 'ng-mocks';
-import { DialogService } from 'primeng/dynamicdialog';
+import { of } from 'rxjs';
+
+import { Mock } from 'vitest';
-import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ActivatedRoute, Router } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
-import { SENTRY_TOKEN } from '@core/provider/sentry.provider';
+import { ENVIRONMENT } from '@core/provider/environment.provider';
+import { FileProvider } from '@osf/features/files/constants';
+import { FilesSelectors, GetFiles } from '@osf/features/files/store';
import { FileUploadDialogComponent } from '@osf/shared/components/file-upload-dialog/file-upload-dialog.component';
import { FilesTreeComponent } from '@osf/shared/components/files-tree/files-tree.component';
import { FormSelectComponent } from '@osf/shared/components/form-select/form-select.component';
+import { GoogleFilePickerComponent } from '@osf/shared/components/google-file-picker/google-file-picker.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
import { ViewOnlyLinkMessageComponent } from '@osf/shared/components/view-only-link-message/view-only-link-message.component';
+import { SupportedFeature } from '@osf/shared/enums/addon-supported-features.enum';
+import { FileKind } from '@osf/shared/enums/file-kind.enum';
+import { FileMenuType } from '@osf/shared/enums/file-menu-type.enum';
+import { ResourceType } from '@osf/shared/enums/resource-type.enum';
+import { UserPermissions } from '@osf/shared/enums/user-permissions.enum';
+import { ConfiguredAddonModel } from '@osf/shared/models/addons/configured-addon.model';
+import { CurrentResource } from '@osf/shared/models/current-resource.model';
+import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
+import { FileLabelModel } from '@osf/shared/models/files/file-label.model';
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { FilesService } from '@osf/shared/services/files.service';
+import { ToastService } from '@osf/shared/services/toast.service';
+import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource';
-import { GoogleFilePickerComponent } from '@shared/components/google-file-picker/google-file-picker.component';
-import { FileLabelModel } from '@shared/models/files/file-label.model';
+import { CustomDialogService } from '@shared/services/custom-dialog.service';
+
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import {
+ CustomConfirmationServiceMock,
+ CustomConfirmationServiceMockType,
+} from '@testing/providers/custom-confirmation-provider.mock';
+import { CustomDialogServiceMock, CustomDialogServiceMockType } from '@testing/providers/custom-dialog-provider.mock';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { provideRouterMock, RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
+import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';
+import { ViewOnlyLinkHelperMock, ViewOnlyLinkHelperMockType } from '@testing/providers/view-only-link-helper.mock';
import { FilesSelectionActionsComponent } from '../../components';
-import { FileProvider } from '../../constants';
-import { FilesSelectors } from '../../store';
import { FilesComponent } from './files.component';
-import { getConfiguredAddonsMappedData } from '@testing/data/addons/addons.configured.data';
-import { getNodeFilesMappedData } from '@testing/data/files/node.data';
-import { testNode } from '@testing/mocks/base-node.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { MockComponentWithSignal } from '@testing/providers/component-provider.mock';
-import { ActivatedRouteMock } from '@testing/providers/route-provider.mock';
-import { provideRouterMock, RouterMockType } from '@testing/providers/router-provider.mock';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
+interface SetupOverrides extends BaseSetupOverrides {
+ fileProvider?: string;
+ hasViewOnlyParam?: boolean;
+}
-describe('Component: Files', () => {
+describe('FilesComponent', () => {
let component: FilesComponent;
let fixture: ComponentFixture;
- const currentFolderSignal = signal(getNodeFilesMappedData(0));
+ let store: Store;
+ let routerMock: RouterMockType & { serializeUrl: Mock };
+ let customDialogServiceMock: CustomDialogServiceMockType;
+ let customConfirmationServiceMock: CustomConfirmationServiceMockType;
+ let toastService: ToastServiceMockType;
+ let viewOnlyLinkHelperMock: ViewOnlyLinkHelperMockType;
+
+ const currentFolder: FileFolderModel = {
+ id: 'folder-1',
+ kind: FileKind.Folder,
+ name: 'Root folder',
+ node: 'node-1',
+ path: '/',
+ provider: FileProvider.OsfStorage,
+ links: {
+ newFolder: '/new-folder',
+ storageAddons: '/storage-addons',
+ upload: '/upload',
+ filesLink: '/files-link',
+ download: '/download-link',
+ },
+ };
- beforeEach(async () => {
- jest.clearAllMocks();
- await TestBed.configureTestingModule({
+ const rootFolders: FileFolderModel[] = [currentFolder];
+
+ const configuredAddons: ConfiguredAddonModel[] = [
+ {
+ id: 'addon-osfstorage',
+ type: 'addons',
+ externalServiceName: FileProvider.OsfStorage,
+ displayName: 'OSF Storage',
+ connectedCapabilities: [],
+ connectedOperationNames: [],
+ currentUserIsOwner: true,
+ selectedStorageItemId: '',
+ baseAccountId: '',
+ baseAccountType: '',
+ iconUrl: '',
+ authUrl: '',
+ credentialsAvailable: true,
+ },
+ {
+ id: 'addon-gdrive',
+ type: 'addons',
+ externalServiceName: FileProvider.GoogleDrive,
+ displayName: 'Google Drive',
+ connectedCapabilities: [],
+ connectedOperationNames: [],
+ currentUserIsOwner: true,
+ selectedStorageItemId: 'google-item',
+ baseAccountId: 'base-google',
+ baseAccountType: 'users',
+ iconUrl: '',
+ authUrl: '',
+ credentialsAvailable: true,
+ },
+ ];
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: FilesSelectors.getFiles, value: [] },
+ { selector: FilesSelectors.getFilesTotalCount, value: 0 },
+ { selector: FilesSelectors.isFilesLoading, value: false },
+ { selector: FilesSelectors.getCurrentFolder, value: currentFolder },
+ { selector: FilesSelectors.getProvider, value: FileProvider.OsfStorage },
+ {
+ selector: CurrentResourceSelectors.getResourceDetails,
+ value: {
+ id: 'node-1',
+ type: 'nodes',
+ title: 'Node',
+ description: '',
+ category: 'project',
+ dateCreated: '',
+ dateModified: '',
+ isRegistration: false,
+ isPreprint: false,
+ isFork: false,
+ isCollection: false,
+ isPublic: true,
+ tags: [],
+ accessRequestsEnabled: false,
+ nodeLicense: { copyrightHolders: null, year: null },
+ currentUserPermissions: [UserPermissions.Admin],
+ currentUserIsContributor: true,
+ wikiEnabled: true,
+ },
+ },
+ {
+ selector: CurrentResourceSelectors.getCurrentResource,
+ value: { id: 'node-1', type: 'nodes', permissions: [UserPermissions.Admin] } as CurrentResource,
+ },
+ { selector: FilesSelectors.getRootFolders, value: rootFolders },
+ { selector: FilesSelectors.isRootFoldersLoading, value: false },
+ { selector: FilesSelectors.getConfiguredStorageAddons, value: configuredAddons },
+ { selector: FilesSelectors.isConfiguredStorageAddonsLoading, value: false },
+ {
+ selector: FilesSelectors.getStorageSupportedFeatures,
+ value: {
+ [FileProvider.OsfStorage]: [
+ SupportedFeature.DownloadAsZip,
+ SupportedFeature.AddUpdateFiles,
+ SupportedFeature.DeleteFiles,
+ SupportedFeature.CopyInto,
+ ],
+ },
+ },
+ ];
+
+ function setup(overrides: SetupOverrides = {}) {
+ const routerBuilder = RouterMockBuilder.create().withUrl('/abc');
+ routerMock = {
+ ...routerBuilder.build(),
+ serializeUrl: vi.fn().mockReturnValue('/guid-url'),
+ };
+ (routerMock.createUrlTree as Mock).mockReturnValue('/guid-url');
+ customDialogServiceMock = CustomDialogServiceMock.simple();
+ customConfirmationServiceMock = CustomConfirmationServiceMock.simple();
+ toastService = ToastServiceMock.simple();
+ viewOnlyLinkHelperMock = ViewOnlyLinkHelperMock.simple(overrides.hasViewOnlyParam ?? false);
+ viewOnlyLinkHelperMock.getViewOnlyParamFromUrl.mockReturnValue('view-only-token');
+
+ const resourceRouteMock = ActivatedRouteMockBuilder.create().withParams({ id: 'node-1' }).build();
+ const dataRouteMock = ActivatedRouteMockBuilder.create()
+ .withData({ resourceType: ResourceType.Project })
+ .withParentRoute(resourceRouteMock)
+ .build();
+ const activatedRouteMock = ActivatedRouteMockBuilder.create()
+ .withParams({ fileProvider: overrides.fileProvider ?? FileProvider.OsfStorage })
+ .withParentRoute(dataRouteMock)
+ .build();
+
+ TestBed.configureTestingModule({
imports: [
FilesComponent,
- OSFTestingModule,
...MockComponents(
- FileUploadDialogComponent,
+ FilesTreeComponent,
FormSelectComponent,
GoogleFilePickerComponent,
LoadingSpinnerComponent,
SearchInputComponent,
SubHeaderComponent,
+ FileUploadDialogComponent,
ViewOnlyLinkMessageComponent,
GoogleFilePickerComponent,
FilesSelectionActionsComponent
),
],
providers: [
- FilesService,
- MockProvider(ActivatedRoute),
- MockProvider(CustomConfirmationService),
- DialogService,
- {
- provide: SENTRY_TOKEN,
- useValue: {
- captureException: jest.fn(),
- captureMessage: jest.fn(),
- setUser: jest.fn(),
- },
- },
- provideMockStore({
- signals: [
- {
- selector: CurrentResourceSelectors.getResourceDetails,
- value: testNode,
- },
- {
- selector: FilesSelectors.getRootFolders,
- value: getNodeFilesMappedData(),
- },
- {
- selector: FilesSelectors.getCurrentFolder,
- value: currentFolderSignal(),
- },
- {
- selector: FilesSelectors.getConfiguredStorageAddons,
- value: getConfiguredAddonsMappedData(),
- },
- {
- selector: FilesSelectors.getProvider,
- value: 'osfstorage',
- },
- {
- selector: FilesSelectors.getStorageSupportedFeatures,
- value: {
- osfstorage: ['AddUpdateFiles', 'DownloadAsZip', 'DeleteFiles', 'CopyInto'],
- googledrive: ['AddUpdateFiles', 'DownloadAsZip', 'DeleteFiles', 'CopyInto'],
- },
- },
- ],
+ provideOSFCore(),
+ MockProvider(ActivatedRoute, activatedRouteMock),
+ provideRouterMock(routerMock),
+ MockProvider(FilesService, {
+ uploadFile: vi.fn().mockReturnValue(of({})),
+ getFolderDownloadLink: vi.fn().mockReturnValue('https://download.link'),
}),
+ MockProvider(CustomDialogService, customDialogServiceMock),
+ MockProvider(CustomConfirmationService, customConfirmationServiceMock),
+ MockProvider(ToastService, toastService),
+ MockProvider(ViewOnlyLinkHelperService, viewOnlyLinkHelperMock),
+ MockProvider(ENVIRONMENT, { webUrl: 'http://localhost:4200', apiDomainUrl: 'http://localhost:8000' }),
+ provideMockStore({ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides) }),
],
- })
- .overrideComponent(FilesComponent, {
- remove: {
- imports: [FilesTreeComponent],
- },
- add: {
- imports: [
- MockComponentWithSignal('osf-files-tree', [
- 'files',
- 'currentFolder',
- 'isLoading',
- 'viewOnly',
- 'resourceId',
- 'provider',
- 'storage',
- 'totalCount',
- 'allowedMenuActions',
- 'supportUpload',
- 'selectedFiles',
- 'scrollHeight',
- ]),
- ],
- },
- })
- .compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(FilesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
+ }
+
+ it('should create', () => {
+ setup();
+
+ expect(component).toBeTruthy();
});
- describe('CurrentRootFolder effect', () => {
- it('should handle the initial effects', () => {
- expect(component.currentRootFolder()?.folder.name).toBe('osfstorage');
- expect(component.isGoogleDrive()).toBeFalsy();
- expect(component.accountId()).toBeFalsy();
- expect(component.selectedRootFolder()).toEqual(Object({}));
- });
+ it('should compute canEdit based on current user permissions', () => {
+ setup();
+ expect(component.canEdit()).toBe(true);
+ });
- it('should handle changing the folder to googledrive', () => {
- component.currentRootFolder.set(
- Object({
- label: 'label',
- folder: Object({
- name: 'Google Drive',
- provider: 'googledrive',
- }),
- })
- );
-
- fixture.detectChanges();
-
- expect(component.currentRootFolder()?.folder.name).toBe('Google Drive');
- expect(component.isGoogleDrive()).toBeTruthy();
- expect(component.accountId()).toBe('62ed6dd7-f7b7-4003-b7b4-855789c1f991');
- expect(component.selectedRootFolder()).toEqual(
- Object({
- itemId: '0AIl0aR4C9JAFUk9PVA',
- })
- );
+ it('should return false for canEdit without admin/write permissions', () => {
+ setup({
+ selectorOverrides: [
+ {
+ selector: CurrentResourceSelectors.getResourceDetails,
+ value: {
+ id: 'node-1',
+ type: 'nodes',
+ title: 'Node',
+ description: '',
+ category: 'project',
+ dateCreated: '',
+ dateModified: '',
+ isRegistration: false,
+ isPreprint: false,
+ isFork: false,
+ isCollection: false,
+ isPublic: true,
+ tags: [],
+ accessRequestsEnabled: false,
+ nodeLicense: { copyrightHolders: null, year: null },
+ currentUserPermissions: [UserPermissions.Read],
+ currentUserIsContributor: true,
+ wikiEnabled: true,
+ },
+ },
+ ],
});
+ expect(component.canEdit()).toBe(false);
});
- describe('updateFilesList', () => {
- it('should call updateFilesList without errors when filesLink exists', () => {
- expect(() => component.updateFilesList()).not.toThrow();
- });
+ it('should expose read-only menu actions when view-only mode is enabled', () => {
+ setup({ hasViewOnlyParam: true });
- it('should not throw when filesLink is null', () => {
- const mockFolder: any = {
- id: 'folder-123',
- kind: 'folder',
- name: 'Test Folder',
- node: 'node-456',
- path: '/test',
- provider: 'osfstorage',
- links: {
- newFolder: '/test/new',
- storageAddons: '/addons',
- upload: '/upload',
- filesLink: '',
- download: '/download',
- },
- };
- currentFolderSignal.set(mockFolder);
+ const actions = component.allowedMenuActions();
- expect(() => component.updateFilesList()).not.toThrow();
- });
+ expect(actions[FileMenuType.Download]).toBe(true);
+ expect(actions[FileMenuType.Embed]).toBe(true);
+ expect(actions[FileMenuType.Share]).toBe(true);
+ expect(actions[FileMenuType.Rename]).toBe(false);
+ expect(actions[FileMenuType.Delete]).toBe(false);
+ expect(actions[FileMenuType.Move]).toBe(false);
+ expect(actions[FileMenuType.Copy]).toBe(false);
});
- describe('handleRootFolderChange', () => {
- it('should preserve view_only query param when switching storage providers', () => {
- const router = TestBed.inject(Router);
- const navigateSpy = jest.spyOn(router, 'navigate').mockResolvedValue(true);
+ it('should map root folder options from folders and configured addons', () => {
+ setup();
- const selectedFolder: FileLabelModel = {
- label: 'Dropbox',
- folder: { provider: FileProvider.Dropbox } as any,
- };
+ const options = component.rootFoldersOptions();
- component.handleRootFolderChange(selectedFolder);
+ expect(options.length).toBe(1);
+ expect(options[0].folder.id).toBe('folder-1');
+ });
- expect(navigateSpy).toHaveBeenCalledWith([`/${component.resourceId()}/files`, FileProvider.Dropbox], {
- queryParamsHandling: 'preserve',
- });
- });
+ it('should return addon display name for non-osf provider in getAddonName', () => {
+ setup();
+
+ const name = component.getAddonName(configuredAddons, FileProvider.GoogleDrive);
+
+ expect(name).toBe('Google Drive');
});
- describe('invalid provider fallback effect', () => {
- let innerComponent: FilesComponent;
- let innerFixture: ComponentFixture;
- let routerMock: RouterMockType;
-
- beforeEach(async () => {
- jest.clearAllMocks();
- routerMock = {
- ...TestBed.inject(Router),
- navigate: jest.fn().mockResolvedValue(true),
- url: '/abc123/files/unknownprovider?view_only=testtoken',
- } as RouterMockType;
-
- await TestBed.configureTestingModule({
- imports: [
- FilesComponent,
- OSFTestingModule,
- ...MockComponents(
- FileUploadDialogComponent,
- FormSelectComponent,
- GoogleFilePickerComponent,
- LoadingSpinnerComponent,
- SearchInputComponent,
- SubHeaderComponent,
- ViewOnlyLinkMessageComponent,
- GoogleFilePickerComponent,
- FilesSelectionActionsComponent
- ),
- ],
- providers: [
- FilesService,
- MockProvider(CustomConfirmationService),
- DialogService,
- {
- provide: SENTRY_TOKEN,
- useValue: {
- captureException: jest.fn(),
- captureMessage: jest.fn(),
- setUser: jest.fn(),
- },
- },
- {
- provide: ActivatedRoute,
- useValue: ActivatedRouteMock.withParams({ fileProvider: 'unknownprovider' }).build(),
- },
- provideRouterMock(routerMock),
- provideMockStore({
- signals: [
- {
- selector: CurrentResourceSelectors.getResourceDetails,
- value: testNode,
- },
- {
- selector: FilesSelectors.getRootFolders,
- value: getNodeFilesMappedData(),
- },
- {
- selector: FilesSelectors.getCurrentFolder,
- value: getNodeFilesMappedData(0),
- },
- {
- selector: FilesSelectors.getConfiguredStorageAddons,
- value: getConfiguredAddonsMappedData(),
- },
- {
- selector: FilesSelectors.getProvider,
- value: 'osfstorage',
- },
- {
- selector: FilesSelectors.getStorageSupportedFeatures,
- value: {
- osfstorage: ['AddUpdateFiles', 'DownloadAsZip', 'DeleteFiles', 'CopyInto'],
- },
- },
- ],
- }),
- ],
- })
- .overrideComponent(FilesComponent, {
- remove: {
- imports: [FilesTreeComponent],
- },
- add: {
- imports: [
- MockComponentWithSignal('osf-files-tree', [
- 'files',
- 'currentFolder',
- 'isLoading',
- 'viewOnly',
- 'resourceId',
- 'provider',
- 'storage',
- 'totalCount',
- 'allowedMenuActions',
- 'supportUpload',
- 'selectedFiles',
- 'scrollHeight',
- ]),
- ],
- },
- })
- .compileComponents();
+ it('should show warning and skip upload when selected file exceeds size limit', () => {
+ setup();
+ const uploadSpy = vi.spyOn(component, 'uploadFiles');
+ const oversizedFile = new File([new ArrayBuffer(1)], 'large.txt');
+ Object.defineProperty(oversizedFile, 'size', { value: 5 * 1024 * 1024 * 1024 });
+ const input = document.createElement('input');
+ Object.defineProperty(input, 'files', { value: [oversizedFile] });
- innerFixture = TestBed.createComponent(FilesComponent);
- innerComponent = innerFixture.componentInstance;
- innerFixture.detectChanges();
- });
+ component.onFileSelected({ target: input } as unknown as Event);
+
+ expect(toastService.showWarn).toHaveBeenCalledWith('shared.files.limitText');
+ expect(uploadSpy).not.toHaveBeenCalled();
+ });
+
+ it('should pass selected files to uploadFiles when files are valid', () => {
+ setup();
+ const uploadSpy = vi.spyOn(component, 'uploadFiles').mockImplementation(() => {});
+ const validFile = new File(['body'], 'small.txt');
+ const input = document.createElement('input');
+ Object.defineProperty(input, 'files', { value: [validFile] });
+
+ component.onFileSelected({ target: input } as unknown as Event);
+
+ expect(uploadSpy).toHaveBeenCalledWith([validFile]);
+ });
+
+ it('should dispatch GetFiles from updateFilesList when current folder has files link', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
+
+ component.updateFilesList();
+
+ expect(store.dispatch).toHaveBeenCalledWith(new GetFiles('/files-link', 1));
+ });
+
+ it('should navigate with provider on root folder change', () => {
+ setup();
+ const selectedFolder: FileLabelModel = { label: 'OSF Storage', folder: currentFolder };
+
+ component.handleRootFolderChange(selectedFolder);
- it('should preserve view_only query param when redirecting to osfstorage for invalid provider', () => {
- expect(routerMock.navigate).toHaveBeenCalledWith(
- [`/${innerComponent.resourceId()}/files`, FileProvider.OsfStorage],
- { queryParamsHandling: 'preserve' }
- );
+ expect(routerMock.navigate).toHaveBeenCalledWith(['/node-1/files', FileProvider.OsfStorage], {
+ queryParamsHandling: 'preserve',
});
});
});
diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts
index 75b852eae..bd3f57497 100644
--- a/src/app/features/files/pages/files/files.component.ts
+++ b/src/app/features/files/pages/files/files.component.ts
@@ -95,21 +95,21 @@ import { FilesSelectors } from '../../store';
selector: 'osf-files',
imports: [
Button,
- FileUploadDialogComponent,
+ TableModule,
+ Select,
+ FormsModule,
+ ReactiveFormsModule,
FilesTreeComponent,
FormSelectComponent,
- FormsModule,
GoogleFilePickerComponent,
LoadingSpinnerComponent,
- ReactiveFormsModule,
SearchInputComponent,
- Select,
SubHeaderComponent,
- TableModule,
- TranslatePipe,
+ FileUploadDialogComponent,
ViewOnlyLinkMessageComponent,
GoogleFilePickerComponent,
FilesSelectionActionsComponent,
+ TranslatePipe,
],
templateUrl: './files.component.html',
styleUrl: './files.component.scss',
diff --git a/src/app/features/home/home.component.spec.ts b/src/app/features/home/home.component.spec.ts
index 665c42054..0b0c3ee7b 100644
--- a/src/app/features/home/home.component.spec.ts
+++ b/src/app/features/home/home.component.spec.ts
@@ -6,26 +6,26 @@ import { ActivatedRoute, Router } from '@angular/router';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
-import { HomeComponent } from './home.component';
-
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { HomeComponent } from './home.component';
+
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture;
let routerMock: ReturnType;
let activatedRouteMock: ReturnType;
- beforeEach(async () => {
+ beforeEach(() => {
routerMock = RouterMockBuilder.create().build();
activatedRouteMock = ActivatedRouteMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [HomeComponent, OSFTestingModule, ...MockComponents(SearchInputComponent, IconComponent)],
- providers: [MockProvider(Router, routerMock), MockProvider(ActivatedRoute, activatedRouteMock)],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [HomeComponent, ...MockComponents(SearchInputComponent, IconComponent)],
+ providers: [provideOSFCore(), MockProvider(Router, routerMock), MockProvider(ActivatedRoute, activatedRouteMock)],
+ });
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts
index 5e78d2e44..ba3b7cf3c 100644
--- a/src/app/features/home/pages/dashboard/dashboard.component.spec.ts
+++ b/src/app/features/home/pages/dashboard/dashboard.component.spec.ts
@@ -1,44 +1,72 @@
import { Store } from '@ngxs/store';
-import { MockComponents, MockProviders } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
-import { signal, WritableSignal } from '@angular/core';
+import { DynamicDialogRef } from 'primeng/dynamicdialog';
+
+import { Subject } from 'rxjs';
+
+import { Mock } from 'vitest';
+
+import { PLATFORM_ID } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { By } from '@angular/platform-browser';
+import { ActivatedRoute, Router } from '@angular/router';
import { ScheduledBannerComponent } from '@core/components/osf-banners/scheduled-banner/scheduled-banner.component';
+import { CreateProjectDialogComponent } from '@osf/features/my-projects/components';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { MyProjectsTableComponent } from '@osf/shared/components/my-projects-table/my-projects-table.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
-import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
+import { SortOrder } from '@osf/shared/enums/sort-order.enum';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
import { ProjectRedirectDialogService } from '@osf/shared/services/project-redirect-dialog.service';
-import { MyResourcesSelectors } from '@shared/stores/my-resources';
+import { ClearMyResources, GetMyProjects, MyResourcesSelectors } from '@osf/shared/stores/my-resources';
+
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { CustomDialogServiceMock, CustomDialogServiceMockType } from '@testing/providers/custom-dialog-provider.mock';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
import { DashboardComponent } from './dashboard.component';
-import { getProjectsMockForComponent } from '@testing/data/dashboard/dasboard.data';
-import { OSFTestingStoreModule } from '@testing/osf.testing.module';
-
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture;
-
- let projectsSignal: WritableSignal;
- let totalProjectsSignal: WritableSignal;
- let areProjectsLoadingSignal: WritableSignal;
-
- beforeEach(async () => {
- projectsSignal = signal(getProjectsMockForComponent());
- totalProjectsSignal = signal(getProjectsMockForComponent().length);
- areProjectsLoadingSignal = signal(false);
-
- await TestBed.configureTestingModule({
+ let store: Store;
+ let routerMock: RouterMockType;
+ let customDialogService: CustomDialogServiceMockType;
+ let projectRedirectDialogService: { showProjectRedirectDialog: Mock };
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: MyResourcesSelectors.getProjects, value: [] },
+ { selector: MyResourcesSelectors.getTotalProjects, value: 0 },
+ { selector: MyResourcesSelectors.getProjectsLoading, value: false },
+ ];
+
+ interface SetupOverrides extends BaseSetupOverrides {
+ platformId?: 'browser' | 'server';
+ routeQueryParams?: Record;
+ }
+
+ function setup(options: SetupOverrides = {}) {
+ routerMock = RouterMockBuilder.create().build();
+ customDialogService = CustomDialogServiceMock.simple();
+ projectRedirectDialogService = { showProjectRedirectDialog: vi.fn() };
+ const routeMock = ActivatedRouteMockBuilder.create()
+ .withQueryParams(options.routeQueryParams ?? {})
+ .build();
+
+ TestBed.configureTestingModule({
imports: [
DashboardComponent,
- OSFTestingStoreModule,
...MockComponents(
SubHeaderComponent,
MyProjectsTableComponent,
@@ -49,101 +77,148 @@ describe('DashboardComponent', () => {
),
],
providers: [
- {
- provide: Store,
- useValue: {
- selectSignal: (selector: any) => {
- if (selector === MyResourcesSelectors.getProjects) return projectsSignal;
- if (selector === MyResourcesSelectors.getTotalProjects) return totalProjectsSignal;
- if (selector === MyResourcesSelectors.getProjectsLoading) return areProjectsLoadingSignal;
- return signal(null);
- },
- dispatch: jest.fn(),
- },
- },
- MockProviders(CustomDialogService, CustomConfirmationService, ProjectRedirectDialogService),
+ provideOSFCore(),
+ MockProvider(ActivatedRoute, routeMock),
+ MockProvider(Router, routerMock),
+ MockProvider(CustomDialogService, customDialogService),
+ MockProvider(ProjectRedirectDialogService, projectRedirectDialogService),
+ MockProvider(PLATFORM_ID, options?.platformId ?? 'browser'),
+ provideMockStore({ signals: mergeSignalOverrides(defaultSignals, options.selectorOverrides) }),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
- });
-
- it('should show loading spinner when projects are loading', () => {
- areProjectsLoadingSignal.set(true);
fixture.detectChanges();
+ }
- const spinner = fixture.debugElement.query(By.directive(LoadingSpinnerComponent));
- expect(spinner).toBeTruthy();
+ it('should create', () => {
+ setup();
+ expect(component).toBeTruthy();
});
- it('should render projects table when projects exist', () => {
- projectsSignal.set(getProjectsMockForComponent());
- totalProjectsSignal.set(getProjectsMockForComponent().length);
- areProjectsLoadingSignal.set(false);
- fixture.detectChanges();
-
- const table = fixture.debugElement.query(By.directive(MyProjectsTableComponent));
- expect(table).toBeTruthy();
+ it('should read query params and fetch projects on init', () => {
+ setup({
+ routeQueryParams: {
+ page: '2',
+ rows: '25',
+ sortField: 'title',
+ sortOrder: '1',
+ search: 'abc',
+ },
+ });
+
+ expect(component.tableParams().firstRowIndex).toBe(25);
+ expect(component.tableParams().rows).toBe(25);
+ expect(component.sortColumn()).toBe('title');
+ expect(component.sortOrder()).toBe(SortOrder.Asc);
+ expect(component.searchControl.value).toBe('abc');
+ expect(store.dispatch).toHaveBeenCalledWith(
+ new GetMyProjects(2, 25, {
+ searchValue: 'abc',
+ searchFields: ['title'],
+ sortColumn: 'title',
+ sortOrder: SortOrder.Asc,
+ })
+ );
});
- it('should render welcome video when no projects exist', () => {
- projectsSignal.set([]);
- totalProjectsSignal.set(0);
- areProjectsLoadingSignal.set(false);
- fixture.detectChanges();
- const iframe = fixture.debugElement.query(By.css('iframe'));
- expect(iframe).toBeTruthy();
- expect(iframe.nativeElement.src).toContain('youtube.com');
+ it('should update query params on page change', () => {
+ setup();
+ (routerMock.navigate as Mock).mockClear();
+
+ component.onPageChange({ first: 20, rows: 10 } as never);
+
+ expect(routerMock.navigate).toHaveBeenCalledWith([], {
+ relativeTo: TestBed.inject(ActivatedRoute),
+ queryParams: {
+ page: 3,
+ rows: 10,
+ search: undefined,
+ sortField: undefined,
+ sortOrder: 1,
+ },
+ queryParamsHandling: 'merge',
+ });
});
- it('should render welcome screen when no projects exist', () => {
- projectsSignal.set([]);
- totalProjectsSignal.set(0);
- areProjectsLoadingSignal.set(false);
- fixture.detectChanges();
-
- const welcomeText = fixture.debugElement.nativeElement.textContent;
- expect(welcomeText).toContain('home.loggedIn.dashboard.noCreatedProject');
+ it('should update sort and reset page in query params on sort', () => {
+ setup();
+ (routerMock.navigate as Mock).mockClear();
+
+ component.onSort({ field: 'dateModified', order: -1 } as never);
+
+ expect(component.sortColumn()).toBe('dateModified');
+ expect(component.sortOrder()).toBe(-1);
+ expect(routerMock.navigate).toHaveBeenCalledWith([], {
+ relativeTo: TestBed.inject(ActivatedRoute),
+ queryParams: {
+ page: 1,
+ rows: 10,
+ search: undefined,
+ sortField: 'dateModified',
+ sortOrder: -1,
+ },
+ queryParamsHandling: 'merge',
+ });
});
- it('should open OSF help link in new tab when openInfoLink is called', () => {
- const spy = jest.spyOn(window, 'open').mockImplementation(() => null);
- component.openInfoLink();
- expect(spy).toHaveBeenCalledWith('https://help.osf.io/', '_blank');
+ it('should create filters from current search and sort state', () => {
+ setup({
+ selectorOverrides: [
+ {
+ selector: MyResourcesSelectors.getProjects,
+ value: [
+ { id: '1', title: 'Alpha project' },
+ { id: '2', title: 'Beta project' },
+ ],
+ },
+ ],
+ });
+
+ component.searchControl.setValue('alp');
+ component.sortColumn.set('title');
+ component.sortOrder.set(-1);
+
+ expect(component.createFilters()).toEqual({
+ searchValue: 'alp',
+ searchFields: ['title'],
+ sortColumn: 'title',
+ sortOrder: -1,
+ });
});
- it('should render product images after loading spinner disappears', () => {
- areProjectsLoadingSignal.set(true);
- fixture.detectChanges();
+ it('should open create project dialog and redirect on close result', () => {
+ setup();
+ const onClose$ = new Subject<{ project: { id: string } }>();
+ customDialogService.open.mockReturnValue({ onClose: onClose$.asObservable() } as unknown as DynamicDialogRef);
- let productImages = fixture.debugElement
- .queryAll(By.css('img'))
- .filter((img) => img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/'));
+ component.createProject();
+ onClose$.next({ project: { id: 'p1' } });
- expect(productImages.length).toBe(0);
+ expect(customDialogService.open).toHaveBeenCalledWith(CreateProjectDialogComponent, {
+ header: 'myProjects.header.createProject',
+ width: '850px',
+ });
+ expect(projectRedirectDialogService.showProjectRedirectDialog).toHaveBeenCalledWith('p1');
+ });
- const spinner = fixture.debugElement.query(By.css('osf-loading-spinner'));
- expect(spinner).toBeTruthy();
+ it('should open help link in new tab', () => {
+ setup();
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
- areProjectsLoadingSignal.set(false);
- fixture.detectChanges();
+ component.openInfoLink();
- productImages = fixture.debugElement
- .queryAll(By.css('img'))
- .filter((img) => img.nativeElement.getAttribute('src')?.includes('assets/images/dashboard/products/'));
+ expect(openSpy).toHaveBeenCalledWith('https://help.osf.io/', '_blank');
+ });
- expect(productImages.length).toBe(4);
+ it('should clear my resources on destroy in browser', () => {
+ setup({ platformId: 'browser' });
+ (store.dispatch as Mock).mockClear();
- const sources = productImages.map((img) => img.nativeElement.getAttribute('src'));
+ fixture.destroy();
- expect(sources).toEqual(
- expect.arrayContaining([
- 'assets/images/dashboard/products/osf-collections.png',
- 'assets/images/dashboard/products/osf-institutions.png',
- 'assets/images/dashboard/products/osf-registries.png',
- 'assets/images/dashboard/products/osf-preprints.png',
- ])
- );
+ expect(store.dispatch).toHaveBeenCalledWith(new ClearMyResources());
});
});
diff --git a/src/app/features/institutions/institutions.component.spec.ts b/src/app/features/institutions/institutions.component.spec.ts
index 80bbe42de..86b1b4310 100644
--- a/src/app/features/institutions/institutions.component.spec.ts
+++ b/src/app/features/institutions/institutions.component.spec.ts
@@ -1,16 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { InstitutionsComponent } from './institutions.component';
describe('InstitutionsComponent', () => {
let component: InstitutionsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [InstitutionsComponent],
- }).compileComponents();
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(InstitutionsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/institutions/pages/institutions-list/institutions-list.component.spec.ts b/src/app/features/institutions/pages/institutions-list/institutions-list.component.spec.ts
index a83e876a8..6c9df80bb 100644
--- a/src/app/features/institutions/pages/institutions-list/institutions-list.component.spec.ts
+++ b/src/app/features/institutions/pages/institutions-list/institutions-list.component.spec.ts
@@ -2,8 +2,11 @@ import { Store } from '@ngxs/store';
import { MockComponents } from 'ng-mocks';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { Mock } from 'vitest';
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl } from '@angular/forms';
+import { provideRouter } from '@angular/router';
import { ScheduledBannerComponent } from '@core/components/osf-banners/scheduled-banner/scheduled-banner.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
@@ -11,12 +14,12 @@ import { SearchInputComponent } from '@osf/shared/components/search-input/search
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
import { FetchInstitutions, InstitutionsSelectors } from '@osf/shared/stores/institutions';
-import { InstitutionsListComponent } from './institutions-list.component';
-
import { MOCK_INSTITUTION } from '@testing/mocks/institution.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { InstitutionsListComponent } from './institutions-list.component';
+
describe('InstitutionsListComponent', () => {
let component: InstitutionsListComponent;
let fixture: ComponentFixture;
@@ -24,14 +27,15 @@ describe('InstitutionsListComponent', () => {
const mockInstitutions = [MOCK_INSTITUTION];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [
InstitutionsListComponent,
- OSFTestingModule,
...MockComponents(SubHeaderComponent, SearchInputComponent, LoadingSpinnerComponent, ScheduledBannerComponent),
],
providers: [
+ provideOSFCore(),
+ provideRouter([]),
provideMockStore({
signals: [
{ selector: InstitutionsSelectors.getInstitutions, value: mockInstitutions },
@@ -39,7 +43,7 @@ describe('InstitutionsListComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(InstitutionsListComponent);
component = fixture.componentInstance;
@@ -47,29 +51,52 @@ describe('InstitutionsListComponent', () => {
fixture.detectChanges();
});
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
it('should create', () => {
expect(component).toBeTruthy();
});
it('should dispatch FetchInstitutions on init', () => {
expect(store.dispatch).toHaveBeenCalledWith(expect.any(FetchInstitutions));
- const action = (store.dispatch as jest.Mock).mock.calls[0][0] as FetchInstitutions;
+ const action = (store.dispatch as Mock).mock.calls[0][0] as FetchInstitutions;
expect(action.searchValue).toBeUndefined();
});
- it('should dispatch FetchInstitutions with search value after debounce', fakeAsync(() => {
- (store.dispatch as jest.Mock).mockClear();
+ it('should dispatch FetchInstitutions with search value after debounce', () => {
+ vi.useFakeTimers();
+ (store.dispatch as Mock).mockClear();
+
component.searchControl.setValue('test search');
- tick(300);
+ vi.advanceTimersByTime(300);
+
expect(store.dispatch).toHaveBeenCalledWith(new FetchInstitutions('test search'));
- }));
+ });
+
+ it('should dispatch FetchInstitutions with empty string when search is null', () => {
+ vi.useFakeTimers();
+ (store.dispatch as Mock).mockClear();
- it('should dispatch FetchInstitutions with empty string when search is null', fakeAsync(() => {
- (store.dispatch as jest.Mock).mockClear();
component.searchControl.setValue(null);
- tick(300);
+ vi.advanceTimersByTime(300);
+
expect(store.dispatch).toHaveBeenCalledWith(new FetchInstitutions(''));
- }));
+ });
+
+ it('should not dispatch another search action for unchanged value', () => {
+ vi.useFakeTimers();
+ (store.dispatch as Mock).mockClear();
+
+ component.searchControl.setValue('same value');
+ vi.advanceTimersByTime(300);
+ component.searchControl.setValue('same value');
+ vi.advanceTimersByTime(300);
+
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+ expect(store.dispatch).toHaveBeenCalledWith(new FetchInstitutions('same value'));
+ });
it('should initialize with correct default values', () => {
expect(component.classes).toBe('flex-1 flex flex-column w-full');
@@ -78,31 +105,10 @@ describe('InstitutionsListComponent', () => {
});
it('should return institutions from store', () => {
- const institutions = component.institutions();
- expect(institutions).toBe(mockInstitutions);
+ expect(component.institutions()).toBe(mockInstitutions);
});
it('should return loading state from store', () => {
- const loading = component.institutionsLoading();
- expect(loading).toBe(false);
- });
-
- it('should handle search control value changes', () => {
- const searchValue = 'test search';
- component.searchControl.setValue(searchValue);
-
- expect(component.searchControl.value).toBe(searchValue);
- });
-
- it('should handle empty search', () => {
- component.searchControl.setValue('');
-
- expect(component.searchControl.value).toBe('');
- });
-
- it('should handle null search value', () => {
- component.searchControl.setValue(null);
-
- expect(component.searchControl.value).toBe(null);
+ expect(component.institutionsLoading()).toBe(false);
});
});
diff --git a/src/app/features/institutions/pages/institutions-search/institutions-search.component.spec.ts b/src/app/features/institutions/pages/institutions-search/institutions-search.component.spec.ts
index 64389ce0b..397e20ea9 100644
--- a/src/app/features/institutions/pages/institutions-search/institutions-search.component.spec.ts
+++ b/src/app/features/institutions/pages/institutions-search/institutions-search.component.spec.ts
@@ -2,73 +2,86 @@ import { Store } from '@ngxs/store';
import { MockComponents, MockProvider } from 'ng-mocks';
-import { of } from 'rxjs';
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { GlobalSearchComponent } from '@osf/shared/components/global-search/global-search.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
-import { InstitutionsSearchSelectors } from '@osf/shared/stores/institutions-search';
-
-import { InstitutionsSearchComponent } from './institutions-search.component';
+import { SetDefaultFilterValue } from '@osf/shared/stores/global-search';
+import { FetchInstitutionById, InstitutionsSearchSelectors } from '@osf/shared/stores/institutions-search';
import { MOCK_INSTITUTION } from '@testing/mocks/institution.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
-describe('Component: Institutions Search', () => {
+import { InstitutionsSearchComponent } from './institutions-search.component';
+
+describe('InstitutionsSearchComponent', () => {
let component: InstitutionsSearchComponent;
let fixture: ComponentFixture;
- let activatedRouteMock: ReturnType;
- let store: jest.Mocked;
-
- beforeEach(async () => {
- activatedRouteMock = ActivatedRouteMockBuilder.create().build();
-
- await TestBed.configureTestingModule({
- imports: [
- InstitutionsSearchComponent,
- ...MockComponents(LoadingSpinnerComponent, GlobalSearchComponent),
- OSFTestingModule,
- ],
+ let store: Store;
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: InstitutionsSearchSelectors.getInstitution, value: MOCK_INSTITUTION },
+ { selector: InstitutionsSearchSelectors.getInstitutionLoading, value: false },
+ ];
+
+ function setup(overrides: BaseSetupOverrides = {}) {
+ const routeBuilder = ActivatedRouteMockBuilder.create();
+ if (overrides.routeParams) {
+ routeBuilder.withParams(overrides.routeParams);
+ }
+ if (overrides.hasParent === false) {
+ routeBuilder.withNoParent();
+ }
+ const mockRoute = routeBuilder.build();
+
+ TestBed.configureTestingModule({
+ imports: [InstitutionsSearchComponent, ...MockComponents(GlobalSearchComponent, LoadingSpinnerComponent)],
providers: [
- MockProvider(ActivatedRoute, activatedRouteMock),
+ provideOSFCore(),
+ MockProvider(ActivatedRoute, mockRoute),
provideMockStore({
- signals: [
- { selector: InstitutionsSearchSelectors.getInstitution, value: MOCK_INSTITUTION },
- { selector: InstitutionsSearchSelectors.getInstitutionLoading, value: false },
- ],
+ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides),
}),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(InstitutionsSearchComponent);
component = fixture.componentInstance;
-
- store = TestBed.inject(Store) as jest.Mocked;
- store.dispatch = jest.fn().mockReturnValue(of(undefined));
- });
+ }
it('should create', () => {
- fixture.detectChanges();
+ setup({ routeParams: { institutionId: 'inst-1' } });
expect(component).toBeTruthy();
});
- it('should fetch institution and set default filter value on ngOnInit when institutionId is provided', () => {
- activatedRouteMock.snapshot!.params = { institutionId: MOCK_INSTITUTION.id };
-
+ it('should dispatch fetch and initialize default filter on init', () => {
+ setup({ routeParams: { institutionId: 'inst-1' } });
fixture.detectChanges();
- expect(store.dispatch).toHaveBeenCalled();
+ expect(store.dispatch).toHaveBeenCalledWith(new FetchInstitutionById('inst-1'));
+ expect(store.dispatch).toHaveBeenCalledWith(
+ new SetDefaultFilterValue('affiliation,isContainedBy.affiliation', MOCK_INSTITUTION.iris.join(','))
+ );
+ expect(component.defaultSearchFiltersInitialized()).toBe(true);
});
- it('should not fetch institution on ngOnInit when institutionId is not provided', () => {
- activatedRouteMock.snapshot!.params = {};
-
+ it('should not dispatch init actions when institution id is missing', () => {
+ setup();
fixture.detectChanges();
- expect(store.dispatch).not.toHaveBeenCalled();
+ expect(store.dispatch).not.toHaveBeenCalledWith(new FetchInstitutionById('inst-1'));
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ new SetDefaultFilterValue('affiliation,isContainedBy.affiliation', MOCK_INSTITUTION.iris.join(','))
+ );
+ expect(component.defaultSearchFiltersInitialized()).toBe(false);
});
});
diff --git a/src/app/features/institutions/pages/institutions-search/institutions-search.component.ts b/src/app/features/institutions/pages/institutions-search/institutions-search.component.ts
index 8550e6916..419971f87 100644
--- a/src/app/features/institutions/pages/institutions-search/institutions-search.component.ts
+++ b/src/app/features/institutions/pages/institutions-search/institutions-search.component.ts
@@ -4,7 +4,6 @@ import { SafeHtmlPipe } from 'primeng/menu';
import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
-import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { GlobalSearchComponent } from '@osf/shared/components/global-search/global-search.component';
@@ -15,7 +14,7 @@ import { FetchInstitutionById, InstitutionsSearchSelectors } from '@osf/shared/s
@Component({
selector: 'osf-institutions-search',
- imports: [FormsModule, NgOptimizedImage, SafeHtmlPipe, LoadingSpinnerComponent, GlobalSearchComponent],
+ imports: [NgOptimizedImage, SafeHtmlPipe, LoadingSpinnerComponent, GlobalSearchComponent],
templateUrl: './institutions-search.component.html',
styleUrl: './institutions-search.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/features/meetings/components/meetings-feature-card/meetings-feature-card.component.spec.ts b/src/app/features/meetings/components/meetings-feature-card/meetings-feature-card.component.spec.ts
index 50eeef7dc..2bfb3bbfd 100644
--- a/src/app/features/meetings/components/meetings-feature-card/meetings-feature-card.component.spec.ts
+++ b/src/app/features/meetings/components/meetings-feature-card/meetings-feature-card.component.spec.ts
@@ -1,9 +1,8 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockPipe } from 'ng-mocks';
-
import { ComponentRef } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { MeetingsFeatureCardComponent } from './meetings-feature-card.component';
describe('MeetingsFeatureCardComponent', () => {
@@ -11,10 +10,11 @@ describe('MeetingsFeatureCardComponent', () => {
let componentRef: ComponentRef;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MeetingsFeatureCardComponent, MockPipe(TranslatePipe)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MeetingsFeatureCardComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MeetingsFeatureCardComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/meetings/meetings.component.spec.ts b/src/app/features/meetings/meetings.component.spec.ts
index 083b7b0ad..6238aa2ec 100644
--- a/src/app/features/meetings/meetings.component.spec.ts
+++ b/src/app/features/meetings/meetings.component.spec.ts
@@ -1,16 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { MeetingsComponent } from './meetings.component';
describe('MeetingsComponent', () => {
let component: MeetingsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [MeetingsComponent],
- }).compileComponents();
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MeetingsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/meetings/pages/meeting-details/meeting-details.component.html b/src/app/features/meetings/pages/meeting-details/meeting-details.component.html
index 8a1994f8d..fa14d8476 100644
--- a/src/app/features/meetings/pages/meeting-details/meeting-details.component.html
+++ b/src/app/features/meetings/pages/meeting-details/meeting-details.component.html
@@ -18,12 +18,9 @@
[paginator]="true"
[totalRecords]="tableParams().totalRecords"
paginatorDropdownAppendTo="body"
- [resizableColumns]="true"
- [autoLayout]="true"
[scrollable]="true"
[sortMode]="'single'"
[lazy]="true"
- [lazyLoadOnInit]="true"
(onPage)="onPageChange($event)"
(onSort)="onSort($event)"
[sortField]="sortColumn()"
diff --git a/src/app/features/meetings/pages/meeting-details/meeting-details.component.spec.ts b/src/app/features/meetings/pages/meeting-details/meeting-details.component.spec.ts
index 25138abe7..1f69ff7fe 100644
--- a/src/app/features/meetings/pages/meeting-details/meeting-details.component.spec.ts
+++ b/src/app/features/meetings/pages/meeting-details/meeting-details.component.spec.ts
@@ -1,134 +1,219 @@
import { Store } from '@ngxs/store';
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockComponents, MockPipe, MockProvider } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
-import { SortEvent } from 'primeng/api';
-import { TablePageEvent } from 'primeng/table';
+import { Mock } from 'vitest';
-import { of } from 'rxjs';
-
-import { DatePipe } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ActivatedRoute, Router } from '@angular/router';
+import { ActivatedRoute, provideRouter, Router } from '@angular/router';
-import { MeetingsSelectors } from '@osf/features/meetings/store';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
+import { SortOrder } from '@osf/shared/enums/sort-order.enum';
+
+import { MOCK_MEETING, MOCK_MEETING_SUBMISSIONS } from '@testing/mocks/meeting.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
+
+import { MEETING_SUBMISSIONS_TABLE_PARAMS } from '../../constants';
+import { Meeting, MeetingSubmission } from '../../models';
+import { GetMeetingById, GetMeetingSubmissions, MeetingsSelectors } from '../../store';
import { MeetingDetailsComponent } from './meeting-details.component';
-import { MOCK_MEETING, MOCK_MEETING_SUBMISSIONS } from '@testing/mocks/meeting.mock';
-import { MOCK_STORE } from '@testing/mocks/mock-store.mock';
-
-const mockActivatedRoute = {
- params: of({ id: 'test-meeting-id' }),
- queryParams: of({}),
- snapshot: {
- params: { id: 'test-meeting-id' },
- queryParams: {},
- },
-};
-
-const mockRouter = {
- navigate: jest.fn(),
- url: '/',
- createUrlTree: jest.fn(),
- navigateByUrl: jest.fn(),
- events: {
- subscribe: jest.fn(),
- },
-};
+interface SetupOverrides extends BaseSetupOverrides {
+ queryParams?: Record;
+ selectorOverrides?: SignalOverride[];
+ selectorSnapshotOverrides?: {
+ selector: unknown;
+ value: unknown;
+ }[];
+}
describe('MeetingDetailsComponent', () => {
let component: MeetingDetailsComponent;
let fixture: ComponentFixture;
-
- beforeEach(async () => {
- (MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
- if (selector === MeetingsSelectors.getAllMeetingSubmissions) return () => MOCK_MEETING_SUBMISSIONS;
- if (selector === MeetingsSelectors.getMeetingSubmissionsTotalCount) return () => MOCK_MEETING_SUBMISSIONS.length;
- if (selector === MeetingsSelectors.isMeetingSubmissionsLoading) return () => false;
- if (selector === MeetingsSelectors.getMeetingById) {
- return () => (id: string) => (id === MOCK_MEETING.id ? MOCK_MEETING : null);
- }
- return () => null;
- });
-
- (MOCK_STORE.selectSnapshot as jest.Mock).mockImplementation((selector) => {
- if (selector === MeetingsSelectors.getMeetingById) {
- return (id: string) => (id === MOCK_MEETING.id ? MOCK_MEETING : null);
- }
- return () => null;
- });
-
- await TestBed.configureTestingModule({
- imports: [
- MeetingDetailsComponent,
- ...MockComponents(SubHeaderComponent, SearchInputComponent),
- MockPipe(TranslatePipe),
- MockPipe(DatePipe),
- ],
+ let store: Store;
+ let mockRouter: RouterMockType;
+
+ const meetingByIdFn = (meeting: Meeting | undefined) => (meetingId: string) =>
+ meeting && meeting.id === meetingId ? meeting : undefined;
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: MeetingsSelectors.getMeetingById, value: meetingByIdFn(MOCK_MEETING) },
+ { selector: MeetingsSelectors.getAllMeetingSubmissions, value: MOCK_MEETING_SUBMISSIONS },
+ { selector: MeetingsSelectors.getMeetingSubmissionsTotalCount, value: 10 },
+ { selector: MeetingsSelectors.isMeetingSubmissionsLoading, value: false },
+ ];
+
+ const defaultSnapshotSelectors = [{ selector: MeetingsSelectors.getMeetingById, value: meetingByIdFn(MOCK_MEETING) }];
+
+ function setup(overrides: SetupOverrides = {}, detectChanges = true) {
+ const routeBuilder = ActivatedRouteMockBuilder.create();
+ if (overrides.routeParams) {
+ routeBuilder.withParams(overrides.routeParams);
+ }
+ if (overrides.queryParams) {
+ routeBuilder.withQueryParams(overrides.queryParams);
+ }
+ if (overrides.hasParent === false) {
+ routeBuilder.withNoParent();
+ }
+ const mockRoute = routeBuilder.build();
+ mockRouter = RouterMockBuilder.create().build();
+
+ TestBed.configureTestingModule({
+ imports: [MeetingDetailsComponent, ...MockComponents(SubHeaderComponent, SearchInputComponent)],
providers: [
- MockProvider(Store, MOCK_STORE),
- { provide: ActivatedRoute, useValue: mockActivatedRoute },
- { provide: Router, useValue: mockRouter },
+ provideOSFCore(),
+ provideRouter([]),
+ MockProvider(ActivatedRoute, mockRoute),
+ MockProvider(Router, mockRouter),
+ provideMockStore({
+ selectors: [...defaultSnapshotSelectors, ...(overrides.selectorSnapshotOverrides ?? [])],
+ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides),
+ }),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(MeetingDetailsComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
+ if (detectChanges) {
+ fixture.detectChanges();
+ }
+ }
+
+ afterEach(() => {
+ vi.useRealTimers();
});
it('should create', () => {
+ setup({
+ routeParams: { id: MOCK_MEETING.id },
+ queryParams: { page: '1', size: '10', search: '', sortColumn: 'title', sortOrder: 'asc' },
+ });
expect(component).toBeTruthy();
});
- it('should initialize with default table params', () => {
- expect(component.tableParams().rows).toBeDefined();
- expect(component.tableParams().firstRowIndex).toBe(0);
+ it('should dispatch meeting submissions action from query params effect', () => {
+ setup({
+ routeParams: { id: MOCK_MEETING.id },
+ queryParams: { page: '2', size: '5', search: 'biology', sortColumn: 'title', sortOrder: 'asc' },
+ });
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ new GetMeetingSubmissions(MOCK_MEETING.id, 2, 5, {
+ searchValue: 'biology',
+ searchFields: ['title', 'author_name', 'meeting_category'],
+ sortColumn: 'title',
+ sortOrder: SortOrder.Asc,
+ })
+ );
});
- it('should open download link if present', () => {
- const openSpy = jest.spyOn(window, 'open').mockImplementation();
- const event = { stopPropagation: jest.fn() } as unknown as Event;
- component.downloadSubmission(event, MOCK_MEETING_SUBMISSIONS[0]);
- expect(openSpy).toHaveBeenCalledWith('https://example.com/file.pdf', '_blank');
- openSpy.mockRestore();
+ it('should dispatch get meeting by id when meeting is not in store', () => {
+ setup({
+ routeParams: { id: MOCK_MEETING.id },
+ selectorOverrides: [{ selector: MeetingsSelectors.getMeetingById, value: meetingByIdFn(undefined) }],
+ selectorSnapshotOverrides: [{ selector: MeetingsSelectors.getMeetingById, value: meetingByIdFn(undefined) }],
+ });
+
+ expect(store.dispatch).toHaveBeenCalledWith(new GetMeetingById(MOCK_MEETING.id));
});
- it('should not open download link if not present', () => {
- const openSpy = jest.spyOn(window, 'open').mockImplementation();
- const event = { stopPropagation: jest.fn() } as unknown as Event;
- component.downloadSubmission(event, MOCK_MEETING_SUBMISSIONS[1]);
- expect(openSpy).not.toHaveBeenCalled();
- openSpy.mockRestore();
+ it('should navigate with page and size on page change', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+
+ component.onPageChange({ first: 20, rows: 10 } as { first: number; rows: number });
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith([], {
+ relativeTo: expect.anything(),
+ queryParams: { page: '3', size: '10' },
+ queryParamsHandling: 'merge',
+ });
});
- it('should update query params in router on page change', () => {
- const router = TestBed.inject(Router);
- const navigateSpy = jest.spyOn(router, 'navigate');
- component.onPageChange({ first: 10, rows: 10 } as TablePageEvent);
- expect(navigateSpy).toHaveBeenCalledWith(
- [],
- expect.objectContaining({
- queryParams: expect.objectContaining({ page: '2', size: '10' }),
- queryParamsHandling: 'merge',
- })
- );
+ it('should navigate with sort query params on sort change', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+
+ component.onSort({ field: 'title', order: SortOrder.Desc } as { field: string; order: SortOrder });
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith([], {
+ relativeTo: expect.anything(),
+ queryParams: { sortColumn: 'title', sortOrder: 'desc' },
+ queryParamsHandling: 'merge',
+ });
+ });
+
+ it('should not navigate on sort when field is missing', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+
+ component.onSort({ field: undefined, order: SortOrder.Asc } as { field?: string; order: SortOrder });
+
+ expect(mockRouter.navigate).not.toHaveBeenCalled();
});
- it('should update query params in router on sort', () => {
- const router = TestBed.inject(Router);
- const navigateSpy = jest.spyOn(router, 'navigate');
- component.onSort({ field: 'title', order: 1 } as SortEvent);
- expect(navigateSpy).toHaveBeenCalledWith(
+ it('should update query params from search control after debounce', () => {
+ vi.useFakeTimers();
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+ (mockRouter.navigate as Mock).mockClear();
+ vi.advanceTimersByTime(300);
+ (mockRouter.navigate as Mock).mockClear();
+
+ component.searchControl.setValue('open science');
+ vi.advanceTimersByTime(300);
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith(
[],
expect.objectContaining({
- queryParams: expect.objectContaining({ sortColumn: 'title', sortOrder: 'asc' }),
+ queryParams: { search: 'open science', page: '1' },
queryParamsHandling: 'merge',
})
);
});
+
+ it('should open submission download link in new tab', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+ const stopPropagation = vi.fn();
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
+
+ component.downloadSubmission({ stopPropagation } as unknown as Event, MOCK_MEETING_SUBMISSIONS[0]);
+
+ expect(stopPropagation).toHaveBeenCalled();
+ expect(openSpy).toHaveBeenCalledWith(MOCK_MEETING_SUBMISSIONS[0].downloadLink, '_blank');
+ });
+
+ it('should not open new tab when submission has no download link', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+ const stopPropagation = vi.fn();
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
+ const meetingSubmission: MeetingSubmission = { ...MOCK_MEETING_SUBMISSIONS[1], downloadLink: null };
+
+ component.downloadSubmission({ stopPropagation } as unknown as Event, meetingSubmission);
+
+ expect(stopPropagation).toHaveBeenCalled();
+ expect(openSpy).not.toHaveBeenCalled();
+ });
+
+ it('should expose expected default table params', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+
+ expect(component.tableParams().rows).toBe(MEETING_SUBMISSIONS_TABLE_PARAMS.rows);
+ expect(component.tableParams().firstRowIndex).toBe(0);
+ });
+
+ it('should build page description from meeting dates and location', () => {
+ setup({ routeParams: { id: MOCK_MEETING.id } }, false);
+
+ expect(component.pageDescription()).toContain('New York | Jan 15, 2024');
+ expect(component.pageDescription()).toContain('- Jan 16, 2024');
+ });
});
diff --git a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.spec.ts b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.spec.ts
index cd19ec1ac..e2845b6b6 100644
--- a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.spec.ts
+++ b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.spec.ts
@@ -1,207 +1,192 @@
-import { provideStore } from '@ngxs/store';
+import { Store } from '@ngxs/store';
-import { TranslatePipe, TranslateService } from '@ngx-translate/core';
-import { MockComponents, MockPipe, MockProvider } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
-import { of } from 'rxjs';
+import { Mock } from 'vitest';
-import { provideHttpClient } from '@angular/common/http';
-import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
-import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants/default-table-params.constants';
-import { parseQueryFilterParams } from '@osf/shared/helpers/http.helper';
+import { DEFAULT_TABLE_PARAMS } from '@shared/constants/default-table-params.constants';
import { SortOrder } from '@shared/enums/sort-order.enum';
-import { MeetingsFeatureCardComponent } from '../../components';
-import { MEETINGS_FEATURE_CARDS, PARTNER_ORGANIZATIONS } from '../../constants';
-import { MeetingsState } from '../../store';
-
-import { MeetingsLandingComponent } from './meetings-landing.component';
-
import { MOCK_MEETING } from '@testing/mocks/meeting.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
-const mockQueryParams = {
- page: 1,
- size: 10,
- search: '',
- sortColumn: 'name',
- sortOrder: SortOrder.Asc,
-};
+import { MeetingsFeatureCardComponent } from '../../components';
+import { GetAllMeetings, MeetingsSelectors } from '../../store';
-const mockActivatedRoute = {
- queryParams: of(mockQueryParams),
-};
+import { MeetingsLandingComponent } from './meetings-landing.component';
-const mockRouter = {
- navigate: jest.fn(),
-};
+interface SetupOverrides extends BaseSetupOverrides {
+ queryParams?: Record;
+ selectorOverrides?: SignalOverride[];
+}
describe('MeetingsLandingComponent', () => {
let component: MeetingsLandingComponent;
let fixture: ComponentFixture;
- let router: Router;
- const mockMeeting = MOCK_MEETING;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ let store: Store;
+ let mockRouter: RouterMockType;
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: MeetingsSelectors.getAllMeetings, value: [MOCK_MEETING] },
+ { selector: MeetingsSelectors.getMeetingsTotalCount, value: 10 },
+ { selector: MeetingsSelectors.isMeetingsLoading, value: false },
+ ];
+
+ function setup(overrides: SetupOverrides = {}, detectChanges = true) {
+ const routeBuilder = ActivatedRouteMockBuilder.create();
+ if (overrides.routeParams) {
+ routeBuilder.withParams(overrides.routeParams);
+ }
+ if (overrides.queryParams) {
+ routeBuilder.withQueryParams(overrides.queryParams);
+ }
+ if (overrides.hasParent === false) {
+ routeBuilder.withNoParent();
+ }
+ const mockRoute = routeBuilder.build();
+ mockRouter = RouterMockBuilder.create().build();
+
+ TestBed.configureTestingModule({
imports: [
MeetingsLandingComponent,
...MockComponents(SubHeaderComponent, SearchInputComponent, MeetingsFeatureCardComponent),
- MockPipe(TranslatePipe),
],
providers: [
- MockProvider(ActivatedRoute, mockActivatedRoute),
+ provideOSFCore(),
+ MockProvider(ActivatedRoute, mockRoute),
MockProvider(Router, mockRouter),
- MockProvider(TranslateService),
- provideStore([MeetingsState]),
- provideHttpClient(),
- provideHttpClientTesting(),
+ provideMockStore({
+ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides),
+ }),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
fixture = TestBed.createComponent(MeetingsLandingComponent);
component = fixture.componentInstance;
- router = TestBed.inject(Router);
+ if (detectChanges) {
+ fixture.detectChanges();
+ }
+ }
+
+ afterEach(() => {
+ vi.useRealTimers();
});
- it('should create and have correct initial signals', () => {
+ it('should create', () => {
+ setup({ queryParams: { page: '1', size: '10', search: '', sortColumn: 'name', sortOrder: 'asc' } });
expect(component).toBeTruthy();
- expect(component.searchControl).toBeInstanceOf(FormControl);
- expect(component.partnerOrganizations).toEqual(PARTNER_ORGANIZATIONS);
- expect(component.meetingsFeatureCards).toEqual(MEETINGS_FEATURE_CARDS);
- expect(component.skeletonData).toHaveLength(10);
- expect(component.tableParams().rows).toBe(DEFAULT_TABLE_PARAMS.rows);
- expect(component.tableParams().firstRowIndex).toBe(0);
- expect(component.currentPage()).toBe(1);
- expect(component.currentPageSize()).toBe(DEFAULT_TABLE_PARAMS.rows);
- expect(component.sortColumn()).toBe('');
- expect(component.sortOrder()).toBe(SortOrder.Asc);
});
- it('should navigate to meeting when navigateToMeeting is called', () => {
- component.navigateToMeeting(mockMeeting);
- expect(router.navigate).toHaveBeenCalledWith(['/meetings', '1']);
+ it('should dispatch get meetings from query params effect', () => {
+ setup({ queryParams: { page: '2', size: '5', search: 'open', sortColumn: 'name', sortOrder: 'asc' } });
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ new GetAllMeetings(2, 5, {
+ searchValue: 'open',
+ searchFields: ['name'],
+ sortColumn: 'name',
+ sortOrder: SortOrder.Asc,
+ })
+ );
+ });
+
+ it('should update current state from query params', () => {
+ setup({ queryParams: { page: '3', size: '25', search: 'meeting', sortColumn: 'name', sortOrder: 'asc' } });
+
+ expect(component.currentPage()).toBe(3);
+ expect(component.currentPageSize()).toBe(25);
+ expect(component.searchControl.value).toBe('meeting');
+ expect(component.sortColumn()).toBe('name');
+ expect(component.sortOrder()).toBe(SortOrder.Asc);
+ expect(component.tableParams().rows).toBe(25);
+ expect(component.tableParams().firstRowIndex).toBe(50);
});
- describe('router.navigate scenarios', () => {
- const cases = [
- {
- name: 'onPageChange',
- action: (c: MeetingsLandingComponent) => c.onPageChange({ first: 40, rows: 20 }),
- expected: { page: '3', size: '20' },
- },
- {
- name: 'onSort ascending',
- action: (c: MeetingsLandingComponent) => c.onSort({ field: 'location', order: 1 }),
- expected: { sortColumn: 'location', sortOrder: 'asc' },
- },
- {
- name: 'onSort descending',
- action: (c: MeetingsLandingComponent) => c.onSort({ field: 'location', order: -1 }),
- expected: { sortColumn: 'location', sortOrder: 'desc' },
- },
- {
- name: 'onSort with bad params (order=undefined)',
- action: (c: MeetingsLandingComponent) => c.onSort({ field: 'location', order: undefined }),
- expected: { sortColumn: 'location', sortOrder: 'asc' },
- },
- ];
- cases.forEach(({ name, action, expected }) => {
- it(`should call router.navigate with correct params: ${name}`, () => {
- jest.clearAllMocks();
- action(component);
- if (expected) {
- expect(router.navigate).toHaveBeenCalledWith(
- [],
- expect.objectContaining({
- queryParams: expect.objectContaining(expected),
- queryParamsHandling: 'merge',
- })
- );
- } else {
- expect(router.navigate).not.toHaveBeenCalled();
- }
- });
+ it('should update total records on table params effect', () => {
+ setup({
+ selectorOverrides: [{ selector: MeetingsSelectors.getMeetingsTotalCount, value: 42 }],
});
+
+ expect(component.tableParams().totalRecords).toBe(42);
});
- it('should call router.navigate with correct params on search', () => {
- jest.useFakeTimers();
- jest.clearAllMocks();
+ it('should navigate to meeting details page', () => {
+ setup({}, false);
- component.searchControl.setValue('test search');
- jest.advanceTimersByTime(450);
+ component.navigateToMeeting(MOCK_MEETING);
- expect(router.navigate).toHaveBeenCalledWith(
- [],
- expect.objectContaining({
- queryParams: expect.objectContaining({ search: 'test search', page: '1' }),
- queryParamsHandling: 'merge',
- })
- );
+ expect(mockRouter.navigate).toHaveBeenCalledWith(['/meetings', MOCK_MEETING.id]);
});
- it('should call router.navigate only once on second input', () => {
- jest.useFakeTimers();
- jest.clearAllMocks();
+ it('should navigate with page and size on page change', () => {
+ setup({}, false);
- component.searchControl.setValue('first');
+ component.onPageChange({ first: 20, rows: 10 } as { first: number; rows: number });
- jest.advanceTimersByTime(100);
+ expect(mockRouter.navigate).toHaveBeenCalledWith([], {
+ relativeTo: expect.anything(),
+ queryParams: { page: '3', size: '10' },
+ queryParamsHandling: 'merge',
+ });
+ });
- component.searchControl.setValue('second');
+ it('should navigate with sort query params on sort change', () => {
+ setup({}, false);
- jest.advanceTimersByTime(350);
+ component.onSort({ field: 'name', order: SortOrder.Desc } as { field: string; order: SortOrder });
- expect(router.navigate).toHaveBeenCalledTimes(1);
+ expect(mockRouter.navigate).toHaveBeenCalledWith([], {
+ relativeTo: expect.anything(),
+ queryParams: { sortColumn: 'name', sortOrder: 'desc' },
+ queryParamsHandling: 'merge',
+ });
});
- it('should not call router.navigate if onSort called with field undefined', () => {
- jest.clearAllMocks();
- component.onSort({ field: undefined, order: 1 });
- expect(router.navigate).not.toHaveBeenCalled();
- });
+ it('should not navigate when sort field is missing', () => {
+ setup({}, false);
- it('should not update query params when sort field is undefined', () => {
- jest.clearAllMocks();
- component.onSort({ field: undefined, order: 1 });
- expect(router.navigate).not.toHaveBeenCalled();
+ component.onSort({ field: undefined, order: SortOrder.Asc } as { field?: string; order: SortOrder });
+
+ expect(mockRouter.navigate).not.toHaveBeenCalled();
});
- it('should call router.navigate with only provided queryParams', () => {
- jest.clearAllMocks();
- component.onPageChange({ first: 0, rows: 10 });
- expect(router.navigate).toHaveBeenCalledWith(
- [],
- expect.objectContaining({
- queryParams: expect.objectContaining({ page: '1', size: '10' }),
- queryParamsHandling: 'merge',
- })
- );
- jest.clearAllMocks();
- component.onSort({ field: 'name', order: 1 });
- expect(router.navigate).toHaveBeenCalledWith(
+ it('should update query params from search control after debounce', () => {
+ vi.useFakeTimers();
+ setup({}, false);
+ (mockRouter.navigate as Mock).mockClear();
+ vi.advanceTimersByTime(300);
+ (mockRouter.navigate as Mock).mockClear();
+
+ component.searchControl.setValue('science');
+ vi.advanceTimersByTime(300);
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith(
[],
expect.objectContaining({
- queryParams: expect.objectContaining({ sortColumn: 'name', sortOrder: 'asc' }),
+ queryParams: { search: 'science', page: '1' },
queryParamsHandling: 'merge',
})
);
});
- it('should do nothing when queryParams is undefined', () => {
- const parseQueryFilterParamsSpy = jest.spyOn({ parseQueryFilterParams }, 'parseQueryFilterParams');
- jest.spyOn(component, 'queryParams').mockReturnValue(undefined);
-
- fixture.detectChanges();
-
- expect(parseQueryFilterParamsSpy).not.toHaveBeenCalled();
+ it('should initialize table params with defaults', () => {
+ setup({}, false);
- parseQueryFilterParamsSpy.mockRestore();
+ expect(component.tableParams().rows).toBe(DEFAULT_TABLE_PARAMS.rows);
+ expect(component.tableParams().firstRowIndex).toBe(0);
});
});
diff --git a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.spec.ts b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.spec.ts
index 15b023b33..a4fc5693e 100644
--- a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.spec.ts
+++ b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.spec.ts
@@ -1,23 +1,27 @@
+import { MockProvider } from 'ng-mocks';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute } from '@angular/router';
import { CedarMetadataHelper } from '@osf/features/metadata/helpers';
-import { CedarMetadataDataTemplateJsonApi } from '@osf/features/metadata/models';
-
-import { CedarTemplateFormComponent } from './cedar-template-form.component';
import { CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK } from '@testing/mocks/cedar-metadata-data-template-json-api.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+
+import { CedarTemplateFormComponent } from './cedar-template-form.component';
describe('CedarTemplateFormComponent', () => {
let component: CedarTemplateFormComponent;
let fixture: ComponentFixture;
- const mockTemplate: CedarMetadataDataTemplateJsonApi = CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK;
+ const mockTemplate = CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CedarTemplateFormComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CedarTemplateFormComponent],
+ providers: [provideOSFCore(), MockProvider(ActivatedRoute, ActivatedRouteMockBuilder.create().build())],
+ });
fixture = TestBed.createComponent(CedarTemplateFormComponent);
fixture.componentRef.setInput('template', mockTemplate);
@@ -60,7 +64,7 @@ describe('CedarTemplateFormComponent', () => {
});
it('should emit changeTemplate event', () => {
- const emitSpy = jest.spyOn(component.changeTemplate, 'emit');
+ const emitSpy = vi.spyOn(component.changeTemplate, 'emit');
component.changeTemplate.emit();
@@ -68,7 +72,7 @@ describe('CedarTemplateFormComponent', () => {
});
it('should emit toggleEditMode event', () => {
- const emitSpy = jest.spyOn(component.toggleEditMode, 'emit');
+ const emitSpy = vi.spyOn(component.toggleEditMode, 'emit');
component.toggleEditMode.emit();
diff --git a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts
index 96891e1e6..8b8ff079c 100644
--- a/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts
+++ b/src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts
@@ -6,7 +6,6 @@ import { Tooltip } from 'primeng/tooltip';
import { map, of } from 'rxjs';
-import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@@ -36,7 +35,7 @@ import {
@Component({
selector: 'osf-cedar-template-form',
- imports: [CommonModule, Button, TranslatePipe, Tooltip, Menu],
+ imports: [Button, TranslatePipe, Tooltip, Menu],
templateUrl: './cedar-template-form.component.html',
styleUrl: './cedar-template-form.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
diff --git a/src/app/features/metadata/components/index.ts b/src/app/features/metadata/components/index.ts
deleted file mode 100644
index b4eeb2184..000000000
--- a/src/app/features/metadata/components/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export { CedarTemplateFormComponent } from './cedar-template-form/cedar-template-form.component';
-export { MetadataAffiliatedInstitutionsComponent } from './metadata-affiliated-institutions/metadata-affiliated-institutions.component';
-export { MetadataContributorsComponent } from './metadata-contributors/metadata-contributors.component';
-export { MetadataDateInfoComponent } from './metadata-date-info/metadata-date-info.component';
-export { MetadataDescriptionComponent } from './metadata-description/metadata-description.component';
-export { MetadataFundingComponent } from './metadata-funding/metadata-funding.component';
-export { MetadataLicenseComponent } from './metadata-license/metadata-license.component';
-export { MetadataPublicationDoiComponent } from './metadata-publication-doi/metadata-publication-doi.component';
-export { MetadataRegistrationDoiComponent } from './metadata-registration-doi/metadata-registration-doi.component';
-export { MetadataResourceInformationComponent } from './metadata-resource-information/metadata-resource-information.component';
-export { MetadataSubjectsComponent } from './metadata-subjects/metadata-subjects.component';
-export { MetadataTagsComponent } from './metadata-tags/metadata-tags.component';
-export { MetadataTitleComponent } from './metadata-title/metadata-title.component';
diff --git a/src/app/features/metadata/components/metadata-affiliated-institutions/metadata-affiliated-institutions.component.spec.ts b/src/app/features/metadata/components/metadata-affiliated-institutions/metadata-affiliated-institutions.component.spec.ts
index 3baf28f7b..044300924 100644
--- a/src/app/features/metadata/components/metadata-affiliated-institutions/metadata-affiliated-institutions.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-affiliated-institutions/metadata-affiliated-institutions.component.spec.ts
@@ -4,10 +4,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AffiliatedInstitutionsViewComponent } from '@osf/shared/components/affiliated-institutions-view/affiliated-institutions-view.component';
-import { MetadataAffiliatedInstitutionsComponent } from './metadata-affiliated-institutions.component';
-
import { MOCK_PROJECT_AFFILIATED_INSTITUTIONS } from '@testing/mocks/project-overview.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataAffiliatedInstitutionsComponent } from './metadata-affiliated-institutions.component';
describe('MetadataAffiliatedInstitutionsComponent', () => {
let component: MetadataAffiliatedInstitutionsComponent;
@@ -15,14 +15,11 @@ describe('MetadataAffiliatedInstitutionsComponent', () => {
const mockAffiliatedInstitutions = MOCK_PROJECT_AFFILIATED_INSTITUTIONS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [
- MetadataAffiliatedInstitutionsComponent,
- MockComponent(AffiliatedInstitutionsViewComponent),
- OSFTestingModule,
- ],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataAffiliatedInstitutionsComponent, MockComponent(AffiliatedInstitutionsViewComponent)],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataAffiliatedInstitutionsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-collection-item/metadata-collection-item.component.spec.ts b/src/app/features/metadata/components/metadata-collection-item/metadata-collection-item.component.spec.ts
index 65616f04e..106fea114 100644
--- a/src/app/features/metadata/components/metadata-collection-item/metadata-collection-item.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-collection-item/metadata-collection-item.component.spec.ts
@@ -1,13 +1,12 @@
-import { MockComponents } from 'ng-mocks';
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
import { CollectionSubmissionReviewState } from '@osf/shared/enums/collection-submission-review-state.enum';
import { CollectionSubmission } from '@osf/shared/models/collections/collections.model';
-import { MetadataCollectionItemComponent } from './metadata-collection-item.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataCollectionItemComponent } from './metadata-collection-item.component';
describe('MetadataCollectionItemComponent', () => {
let component: MetadataCollectionItemComponent;
@@ -31,10 +30,11 @@ describe('MetadataCollectionItemComponent', () => {
gradeLevels: 'Graduate',
};
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataCollectionItemComponent, OSFTestingModule, ...MockComponents()],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataCollectionItemComponent],
+ providers: [provideOSFCore(), provideRouter([])],
+ });
fixture = TestBed.createComponent(MetadataCollectionItemComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-collections/metadata-collections.component.spec.ts b/src/app/features/metadata/components/metadata-collections/metadata-collections.component.spec.ts
index 81abf7bae..bd9568d2c 100644
--- a/src/app/features/metadata/components/metadata-collections/metadata-collections.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-collections/metadata-collections.component.spec.ts
@@ -1,19 +1,24 @@
+import { MockProvider } from 'ng-mocks';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
-
-import { MetadataCollectionsComponent } from './metadata-collections.component';
+import { ActivatedRoute } from '@angular/router';
import { MOCK_PROJECT_COLLECTION_SUBMISSIONS } from '@testing/data/collections/collection-submissions.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+
+import { MetadataCollectionsComponent } from './metadata-collections.component';
describe('MetadataCollectionsComponent', () => {
let component: MetadataCollectionsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataCollectionsComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataCollectionsComponent],
+ providers: [provideOSFCore(), MockProvider(ActivatedRoute, ActivatedRouteMockBuilder.create().build())],
+ });
fixture = TestBed.createComponent(MetadataCollectionsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.spec.ts b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.spec.ts
index 74d6fa72a..f7506031b 100644
--- a/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-contributors/metadata-contributors.component.spec.ts
@@ -6,25 +6,25 @@ import { ActivatedRoute } from '@angular/router';
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
import { ContributorModel } from '@shared/models/contributors/contributor.model';
-import { MetadataContributorsComponent } from './metadata-contributors.component';
-
import { MOCK_CONTRIBUTOR } from '@testing/mocks/contributors.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { MetadataContributorsComponent } from './metadata-contributors.component';
+
describe('MetadataContributorsComponent', () => {
let component: MetadataContributorsComponent;
let fixture: ComponentFixture;
let activatedRouteMock: ReturnType;
const mockContributors: ContributorModel[] = [MOCK_CONTRIBUTOR];
- beforeEach(async () => {
+ beforeEach(() => {
activatedRouteMock = ActivatedRouteMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [MetadataContributorsComponent, MockComponent(ContributorsListComponent), OSFTestingModule],
- providers: [MockProvider(ActivatedRoute, activatedRouteMock)],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [MetadataContributorsComponent, MockComponent(ContributorsListComponent)],
+ providers: [provideOSFCore(), MockProvider(ActivatedRoute, activatedRouteMock)],
+ });
fixture = TestBed.createComponent(MetadataContributorsComponent);
component = fixture.componentInstance;
@@ -54,7 +54,7 @@ describe('MetadataContributorsComponent', () => {
});
it('should emit openEditContributorDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditContributorDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditContributorDialog, 'emit');
component.openEditContributorDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-date-info/metadata-date-info.component.spec.ts b/src/app/features/metadata/components/metadata-date-info/metadata-date-info.component.spec.ts
index 9bb8f0464..4e8085ea6 100644
--- a/src/app/features/metadata/components/metadata-date-info/metadata-date-info.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-date-info/metadata-date-info.component.spec.ts
@@ -1,17 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MetadataDateInfoComponent } from './metadata-date-info.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataDateInfoComponent } from './metadata-date-info.component';
describe('MetadataDateInfoComponent', () => {
let component: MetadataDateInfoComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataDateInfoComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataDateInfoComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataDateInfoComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-description/metadata-description.component.spec.ts b/src/app/features/metadata/components/metadata-description/metadata-description.component.spec.ts
index d3a23d028..326a5a411 100644
--- a/src/app/features/metadata/components/metadata-description/metadata-description.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-description/metadata-description.component.spec.ts
@@ -1,8 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MetadataDescriptionComponent } from './metadata-description.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataDescriptionComponent } from './metadata-description.component';
describe('MetadataDescriptionComponent', () => {
let component: MetadataDescriptionComponent;
@@ -10,10 +10,11 @@ describe('MetadataDescriptionComponent', () => {
const mockDescription = 'This is a test description.';
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataDescriptionComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataDescriptionComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataDescriptionComponent);
component = fixture.componentInstance;
@@ -31,7 +32,7 @@ describe('MetadataDescriptionComponent', () => {
});
it('should emit openEditDescriptionDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditDescriptionDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditDescriptionDialog, 'emit');
component.openEditDescriptionDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-funding/metadata-funding.component.spec.ts b/src/app/features/metadata/components/metadata-funding/metadata-funding.component.spec.ts
index 758988586..dfeaa309f 100644
--- a/src/app/features/metadata/components/metadata-funding/metadata-funding.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-funding/metadata-funding.component.spec.ts
@@ -2,10 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Funder } from '@osf/features/metadata/models';
-import { MetadataFundingComponent } from './metadata-funding.component';
-
import { MOCK_FUNDERS } from '@testing/mocks/funder.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataFundingComponent } from './metadata-funding.component';
describe('MetadataFundingComponent', () => {
let component: MetadataFundingComponent;
@@ -13,10 +13,11 @@ describe('MetadataFundingComponent', () => {
const mockFunders: Funder[] = MOCK_FUNDERS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataFundingComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataFundingComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataFundingComponent);
component = fixture.componentInstance;
@@ -41,7 +42,7 @@ describe('MetadataFundingComponent', () => {
});
it('should emit openEditFundingDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditFundingDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditFundingDialog, 'emit');
component.openEditFundingDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-license/metadata-license.component.spec.ts b/src/app/features/metadata/components/metadata-license/metadata-license.component.spec.ts
index d1272d3a4..573b993f4 100644
--- a/src/app/features/metadata/components/metadata-license/metadata-license.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-license/metadata-license.component.spec.ts
@@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MetadataLicenseComponent } from './metadata-license.component';
-
import { MOCK_LICENSE } from '@testing/mocks/license.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataLicenseComponent } from './metadata-license.component';
describe('MetadataLicenseComponent', () => {
let component: MetadataLicenseComponent;
@@ -11,10 +11,11 @@ describe('MetadataLicenseComponent', () => {
const mockLicense = MOCK_LICENSE;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataLicenseComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataLicenseComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataLicenseComponent);
component = fixture.componentInstance;
@@ -44,7 +45,7 @@ describe('MetadataLicenseComponent', () => {
});
it('should emit openEditLicenseDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditLicenseDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditLicenseDialog, 'emit');
component.openEditLicenseDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-publication-doi/metadata-publication-doi.component.spec.ts b/src/app/features/metadata/components/metadata-publication-doi/metadata-publication-doi.component.spec.ts
index 1efb22a7a..f3ed0c6d5 100644
--- a/src/app/features/metadata/components/metadata-publication-doi/metadata-publication-doi.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-publication-doi/metadata-publication-doi.component.spec.ts
@@ -2,10 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IdentifierModel } from '@shared/models/identifiers/identifier.model';
-import { MetadataPublicationDoiComponent } from './metadata-publication-doi.component';
-
import { MOCK_PROJECT_IDENTIFIERS } from '@testing/mocks/project-overview.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataPublicationDoiComponent } from './metadata-publication-doi.component';
describe('MetadataPublicationDoiComponent', () => {
let component: MetadataPublicationDoiComponent;
@@ -13,10 +13,11 @@ describe('MetadataPublicationDoiComponent', () => {
const mockIdentifiers: IdentifierModel = MOCK_PROJECT_IDENTIFIERS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataPublicationDoiComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataPublicationDoiComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataPublicationDoiComponent);
component = fixture.componentInstance;
@@ -46,7 +47,7 @@ describe('MetadataPublicationDoiComponent', () => {
});
it('should emit openEditPublicationDoiDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditPublicationDoiDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditPublicationDoiDialog, 'emit');
component.openEditPublicationDoiDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-registration-doi/metadata-registration-doi.component.spec.ts b/src/app/features/metadata/components/metadata-registration-doi/metadata-registration-doi.component.spec.ts
index d21e4a03b..3133841fd 100644
--- a/src/app/features/metadata/components/metadata-registration-doi/metadata-registration-doi.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-registration-doi/metadata-registration-doi.component.spec.ts
@@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MetadataRegistrationDoiComponent } from './metadata-registration-doi.component';
-
import { MOCK_PROJECT_IDENTIFIERS } from '@testing/mocks/project-overview.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataRegistrationDoiComponent } from './metadata-registration-doi.component';
describe('MetadataRegistrationDoiComponent', () => {
let component: MetadataRegistrationDoiComponent;
@@ -11,10 +11,11 @@ describe('MetadataRegistrationDoiComponent', () => {
const mockIdentifiers = [MOCK_PROJECT_IDENTIFIERS];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataRegistrationDoiComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataRegistrationDoiComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataRegistrationDoiComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-registry-info/metadata-registry-info.component.spec.ts b/src/app/features/metadata/components/metadata-registry-info/metadata-registry-info.component.spec.ts
index 8dacb5d07..4a0f25eb2 100644
--- a/src/app/features/metadata/components/metadata-registry-info/metadata-registry-info.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-registry-info/metadata-registry-info.component.spec.ts
@@ -2,9 +2,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistryProviderDetails } from '@osf/shared/models/provider/registry-provider.model';
-import { MetadataRegistryInfoComponent } from './metadata-registry-info.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataRegistryInfoComponent } from './metadata-registry-info.component';
describe('MetadataRegistryInfoComponent', () => {
let component: MetadataRegistryInfoComponent;
@@ -18,12 +18,14 @@ describe('MetadataRegistryInfoComponent', () => {
brand: null,
iri: 'https://example.com/registry',
reviewsWorkflow: 'standard',
+ allowSubmissions: true,
};
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataRegistryInfoComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataRegistryInfoComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataRegistryInfoComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/components/metadata-resource-information/metadata-resource-information.component.spec.ts b/src/app/features/metadata/components/metadata-resource-information/metadata-resource-information.component.spec.ts
index cb6e6c922..6eebd9418 100644
--- a/src/app/features/metadata/components/metadata-resource-information/metadata-resource-information.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-resource-information/metadata-resource-information.component.spec.ts
@@ -2,9 +2,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomItemMetadataRecord } from '@osf/features/metadata/models';
-import { MetadataResourceInformationComponent } from './metadata-resource-information.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataResourceInformationComponent } from './metadata-resource-information.component';
describe('MetadataResourceInformationComponent', () => {
let component: MetadataResourceInformationComponent;
@@ -16,10 +16,11 @@ describe('MetadataResourceInformationComponent', () => {
funders: [],
};
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataResourceInformationComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataResourceInformationComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataResourceInformationComponent);
fixture.componentRef.setInput('customItemMetadata', mockCustomItemMetadata);
@@ -51,7 +52,7 @@ describe('MetadataResourceInformationComponent', () => {
});
it('should emit openEditResourceInformationDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditResourceInformationDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditResourceInformationDialog, 'emit');
component.openEditResourceInformationDialog.emit();
diff --git a/src/app/features/metadata/components/metadata-subjects/metadata-subjects.component.spec.ts b/src/app/features/metadata/components/metadata-subjects/metadata-subjects.component.spec.ts
index 2f8425aeb..35263cba8 100644
--- a/src/app/features/metadata/components/metadata-subjects/metadata-subjects.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-subjects/metadata-subjects.component.spec.ts
@@ -5,10 +5,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SubjectsComponent } from '@osf/shared/components/subjects/subjects.component';
import { SubjectModel } from '@osf/shared/models/subject/subject.model';
-import { MetadataSubjectsComponent } from './metadata-subjects.component';
-
import { SUBJECTS_MOCK } from '@testing/mocks/subject.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { MetadataSubjectsComponent } from './metadata-subjects.component';
describe('MetadataSubjectsComponent', () => {
let component: MetadataSubjectsComponent;
@@ -16,10 +16,11 @@ describe('MetadataSubjectsComponent', () => {
const mockSubjects: SubjectModel[] = SUBJECTS_MOCK;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataSubjectsComponent, MockComponent(SubjectsComponent), OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataSubjectsComponent, MockComponent(SubjectsComponent)],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataSubjectsComponent);
fixture.componentRef.setInput('selectedSubjects', mockSubjects);
@@ -54,7 +55,7 @@ describe('MetadataSubjectsComponent', () => {
});
it('should emit getSubjectChildren event', () => {
- const emitSpy = jest.spyOn(component.getSubjectChildren, 'emit');
+ const emitSpy = vi.spyOn(component.getSubjectChildren, 'emit');
const parentId = 'subject-1';
component.getSubjectChildren.emit(parentId);
@@ -63,7 +64,7 @@ describe('MetadataSubjectsComponent', () => {
});
it('should emit searchSubjects event', () => {
- const emitSpy = jest.spyOn(component.searchSubjects, 'emit');
+ const emitSpy = vi.spyOn(component.searchSubjects, 'emit');
const searchTerm = 'computer science';
component.searchSubjects.emit(searchTerm);
@@ -72,7 +73,7 @@ describe('MetadataSubjectsComponent', () => {
});
it('should emit updateSelectedSubjects event', () => {
- const emitSpy = jest.spyOn(component.updateSelectedSubjects, 'emit');
+ const emitSpy = vi.spyOn(component.updateSelectedSubjects, 'emit');
const updatedSubjects: SubjectModel[] = [
{
id: 'subject-7',
diff --git a/src/app/features/metadata/components/metadata-tags/metadata-tags.component.spec.ts b/src/app/features/metadata/components/metadata-tags/metadata-tags.component.spec.ts
index 9b81f03cb..865cf98d7 100644
--- a/src/app/features/metadata/components/metadata-tags/metadata-tags.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-tags/metadata-tags.component.spec.ts
@@ -3,24 +3,24 @@ import { MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
-import { MetadataTagsComponent } from './metadata-tags.component';
-
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { MetadataTagsComponent } from './metadata-tags.component';
+
describe('MetadataTagsComponent', () => {
let component: MetadataTagsComponent;
let fixture: ComponentFixture;
let routerMock: ReturnType;
const mockTags = ['tag1', 'tag2', 'tag3'];
- beforeEach(async () => {
+ beforeEach(() => {
routerMock = RouterMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [MetadataTagsComponent, OSFTestingModule],
- providers: [MockProvider(Router, routerMock)],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [MetadataTagsComponent],
+ providers: [provideOSFCore(), MockProvider(Router, routerMock)],
+ });
fixture = TestBed.createComponent(MetadataTagsComponent);
component = fixture.componentInstance;
@@ -50,7 +50,7 @@ describe('MetadataTagsComponent', () => {
});
it('should emit tagsChanged event', () => {
- const emitSpy = jest.spyOn(component.tagsChanged, 'emit');
+ const emitSpy = vi.spyOn(component.tagsChanged, 'emit');
const newTags = ['new-tag1', 'new-tag2'];
component.tagsChanged.emit(newTags);
diff --git a/src/app/features/metadata/components/metadata-title/metadata-title.component.spec.ts b/src/app/features/metadata/components/metadata-title/metadata-title.component.spec.ts
index c476bfea9..07600066a 100644
--- a/src/app/features/metadata/components/metadata-title/metadata-title.component.spec.ts
+++ b/src/app/features/metadata/components/metadata-title/metadata-title.component.spec.ts
@@ -1,8 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MetadataTitleComponent } from './metadata-title.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { MetadataTitleComponent } from './metadata-title.component';
describe('MetadataTitleComponent', () => {
let component: MetadataTitleComponent;
@@ -10,10 +10,11 @@ describe('MetadataTitleComponent', () => {
const mockTitle = 'Title';
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MetadataTitleComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MetadataTitleComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(MetadataTitleComponent);
component = fixture.componentInstance;
@@ -31,7 +32,7 @@ describe('MetadataTitleComponent', () => {
});
it('should emit openEditTitleDialog event', () => {
- const emitSpy = jest.spyOn(component.openEditTitleDialog, 'emit');
+ const emitSpy = vi.spyOn(component.openEditTitleDialog, 'emit');
component.openEditTitleDialog.emit();
diff --git a/src/app/features/metadata/dialogs/affiliated-institutions-dialog/affiliated-institutions-dialog.component.spec.ts b/src/app/features/metadata/dialogs/affiliated-institutions-dialog/affiliated-institutions-dialog.component.spec.ts
index aa55a264f..1421664a5 100644
--- a/src/app/features/metadata/dialogs/affiliated-institutions-dialog/affiliated-institutions-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/affiliated-institutions-dialog/affiliated-institutions-dialog.component.spec.ts
@@ -1,4 +1,4 @@
-import { MockComponent, MockProviders } from 'ng-mocks';
+import { MockComponent, MockProvider } from 'ng-mocks';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
@@ -8,13 +8,13 @@ import { AffiliatedInstitutionSelectComponent } from '@osf/shared/components/aff
import { Institution } from '@osf/shared/models/institutions/institutions.model';
import { InstitutionsSelectors } from '@osf/shared/stores/institutions';
-import { AffiliatedInstitutionsDialogComponent } from './affiliated-institutions-dialog.component';
-
import { MOCK_INSTITUTION } from '@testing/mocks/institution.mock';
-import { TranslateServiceMock } from '@testing/mocks/translate.service.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { AffiliatedInstitutionsDialogComponent } from './affiliated-institutions-dialog.component';
+
describe('AffiliatedInstitutionsDialogComponent', () => {
let component: AffiliatedInstitutionsDialogComponent;
let fixture: ComponentFixture;
@@ -22,16 +22,13 @@ describe('AffiliatedInstitutionsDialogComponent', () => {
let config: DynamicDialogConfig;
const mockInstitutions: Institution[] = [MOCK_INSTITUTION];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [
- AffiliatedInstitutionsDialogComponent,
- OSFTestingModule,
- MockComponent(AffiliatedInstitutionSelectComponent),
- ],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [AffiliatedInstitutionsDialogComponent, MockComponent(AffiliatedInstitutionSelectComponent)],
providers: [
- TranslateServiceMock,
- MockProviders(DynamicDialogRef, DynamicDialogConfig),
+ provideOSFCore(),
+ MockProvider(DynamicDialogConfig),
+ provideDynamicDialogRefMock(),
provideMockStore({
signals: [
{ selector: InstitutionsSelectors.getUserInstitutions, value: mockInstitutions },
@@ -39,7 +36,7 @@ describe('AffiliatedInstitutionsDialogComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(AffiliatedInstitutionsDialogComponent);
component = fixture.componentInstance;
@@ -66,21 +63,18 @@ describe('AffiliatedInstitutionsDialogComponent', () => {
});
it('should close dialog with selected institutions on save', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
const selectedInstitutions = [mockInstitutions[0]];
component.selectedInstitutions.set(selectedInstitutions);
component.save();
- expect(closeSpy).toHaveBeenCalledWith(selectedInstitutions);
+ expect(dialogRef.close).toHaveBeenCalledWith(selectedInstitutions);
});
it('should close dialog without data on cancel', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
component.cancel();
- expect(closeSpy).toHaveBeenCalled();
+ expect(dialogRef.close).toHaveBeenCalled();
});
it('should update selected institutions', () => {
diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts
index cbb3b0b73..059ef703d 100644
--- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.spec.ts
@@ -4,21 +4,22 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ContributorsTableComponent } from '@osf/shared/components/contributors/contributors-table/contributors-table.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
+import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
+import { ToastService } from '@osf/shared/services/toast.service';
import { ContributorsSelectors } from '@osf/shared/stores/contributors';
-import { ContributorsTableComponent } from '@shared/components/contributors';
import { ContributorModel } from '@shared/models/contributors/contributor.model';
-import { ContributorsDialogComponent } from './contributors-dialog.component';
-
import { MOCK_CONTRIBUTOR } from '@testing/mocks/contributors.mock';
-import { MockCustomConfirmationServiceProvider } from '@testing/mocks/custom-confirmation.service.mock';
-import { TranslateServiceMock } from '@testing/mocks/translate.service.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { ContributorsDialogComponent } from './contributors-dialog.component';
+
describe('ContributorsDialogComponent', () => {
let component: ContributorsDialogComponent;
let fixture: ComponentFixture;
@@ -28,18 +29,13 @@ describe('ContributorsDialogComponent', () => {
const mockContributors: ContributorModel[] = [MOCK_CONTRIBUTOR];
- beforeEach(async () => {
+ beforeEach(() => {
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [
- ContributorsDialogComponent,
- OSFTestingModule,
- ...MockComponents(SearchInputComponent, ContributorsTableComponent),
- ],
+ TestBed.configureTestingModule({
+ imports: [ContributorsDialogComponent, ...MockComponents(SearchInputComponent, ContributorsTableComponent)],
providers: [
- TranslateServiceMock,
- MockCustomConfirmationServiceProvider,
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: ContributorsSelectors.getContributors, value: mockContributors },
@@ -47,6 +43,8 @@ describe('ContributorsDialogComponent', () => {
{ selector: ContributorsSelectors.getContributorsTotalCount, value: mockContributors },
],
}),
+ MockProvider(CustomConfirmationService),
+ MockProvider(ToastService),
MockProvider(CustomDialogService, mockCustomDialogService),
MockProvider(DynamicDialogConfig, {
data: {
@@ -54,11 +52,9 @@ describe('ContributorsDialogComponent', () => {
resourceType: 1,
},
}),
- MockProvider(DynamicDialogRef, {
- close: jest.fn(),
- }),
+ provideDynamicDialogRefMock(),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(ContributorsDialogComponent);
component = fixture.componentInstance;
@@ -76,7 +72,7 @@ describe('ContributorsDialogComponent', () => {
});
it('should set search subscription on init', () => {
- const setSearchSubscriptionSpy = jest.spyOn(component as any, 'setSearchSubscription');
+ const setSearchSubscriptionSpy = vi.spyOn(component as any, 'setSearchSubscription');
component.ngOnInit();
@@ -93,7 +89,7 @@ describe('ContributorsDialogComponent', () => {
it('should remove contributor with confirmation', () => {
const contributor = mockContributors[0];
- const confirmDeleteSpy = jest.spyOn(component['customConfirmationService'], 'confirmDelete');
+ const confirmDeleteSpy = vi.spyOn(component['customConfirmationService'], 'confirmDelete');
component.removeContributor(contributor);
@@ -116,11 +112,9 @@ describe('ContributorsDialogComponent', () => {
});
it('should close dialog', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
component.onClose();
- expect(closeSpy).toHaveBeenCalled();
+ expect(dialogRef.close).toHaveBeenCalled();
});
it('should compute search placeholder for registration', () => {
diff --git a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts
index f9a6d0b3d..46e3a82f6 100644
--- a/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts
+++ b/src/app/features/metadata/dialogs/contributors-dialog/contributors-dialog.component.ts
@@ -22,11 +22,9 @@ import { FormControl, FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { UserSelectors } from '@core/store/user';
-import {
- AddContributorDialogComponent,
- AddUnregisteredContributorDialogComponent,
- ContributorsTableComponent,
-} from '@osf/shared/components/contributors';
+import { AddContributorDialogComponent } from '@osf/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component';
+import { AddUnregisteredContributorDialogComponent } from '@osf/shared/components/contributors/add-unregistered-contributor-dialog/add-unregistered-contributor-dialog.component';
+import { ContributorsTableComponent } from '@osf/shared/components/contributors/contributors-table/contributors-table.component';
import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants/default-table-params.constants';
import { AddContributorType } from '@osf/shared/enums/contributors/add-contributor-type.enum';
diff --git a/src/app/features/metadata/dialogs/description-dialog/description-dialog.component.spec.ts b/src/app/features/metadata/dialogs/description-dialog/description-dialog.component.spec.ts
index a7ffbc890..7920ef5f1 100644
--- a/src/app/features/metadata/dialogs/description-dialog/description-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/description-dialog/description-dialog.component.spec.ts
@@ -4,19 +4,19 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { DescriptionDialogComponent } from './description-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { DescriptionDialogComponent } from './description-dialog.component';
describe('DescriptionDialogComponent', () => {
let component: DescriptionDialogComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [DescriptionDialogComponent, OSFTestingModule],
- providers: [MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [DescriptionDialogComponent],
+ providers: [provideOSFCore(), MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
+ });
fixture = TestBed.createComponent(DescriptionDialogComponent);
component = fixture.componentInstance;
@@ -28,7 +28,7 @@ describe('DescriptionDialogComponent', () => {
it('should handle save with valid form', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- jest.spyOn(dialogRef, 'close');
+ vi.spyOn(dialogRef, 'close');
const validDescription = { value: 'Valid description' };
component.descriptionControl.setValue(validDescription.value);
@@ -39,7 +39,7 @@ describe('DescriptionDialogComponent', () => {
it('should handle cancel', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- jest.spyOn(dialogRef, 'close');
+ vi.spyOn(dialogRef, 'close');
component.cancel();
diff --git a/src/app/features/metadata/dialogs/edit-title-dialog/edit-title-dialog.component.spec.ts b/src/app/features/metadata/dialogs/edit-title-dialog/edit-title-dialog.component.spec.ts
index 7cd0fd268..3ae313dd6 100644
--- a/src/app/features/metadata/dialogs/edit-title-dialog/edit-title-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/edit-title-dialog/edit-title-dialog.component.spec.ts
@@ -6,9 +6,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TextInputComponent } from '@osf/shared/components/text-input/text-input.component';
-import { EditTitleDialogComponent } from './edit-title-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { EditTitleDialogComponent } from './edit-title-dialog.component';
describe('EditTitleDialogComponent', () => {
let component: EditTitleDialogComponent;
@@ -16,11 +17,11 @@ describe('EditTitleDialogComponent', () => {
let dialogRef: DynamicDialogRef;
let config: DynamicDialogConfig;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [EditTitleDialogComponent, MockComponent(TextInputComponent), OSFTestingModule],
- providers: [MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [EditTitleDialogComponent, MockComponent(TextInputComponent)],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig)],
+ });
fixture = TestBed.createComponent(EditTitleDialogComponent);
component = fixture.componentInstance;
@@ -47,21 +48,18 @@ describe('EditTitleDialogComponent', () => {
});
it('should close dialog with title value on save', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
const testTitle = 'New Project Title';
component.titleControl.setValue(testTitle);
component.save();
- expect(closeSpy).toHaveBeenCalledWith({ value: testTitle });
+ expect(dialogRef.close).toHaveBeenCalledWith({ value: testTitle });
});
it('should close dialog without data on cancel', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
component.cancel();
- expect(closeSpy).toHaveBeenCalledWith();
+ expect(dialogRef.close).toHaveBeenCalledWith();
});
it('should validate that title is required', () => {
diff --git a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.spec.ts b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.spec.ts
index 09bcf5492..d6bc9cc4c 100644
--- a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.spec.ts
@@ -1,334 +1,218 @@
import { Store } from '@ngxs/store';
-import { MockProvider, MockProviders } from 'ng-mocks';
+import { MockProvider } from 'ng-mocks';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
-import { DestroyRef, signal } from '@angular/core';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { Mock } from 'vitest';
-import { RorFunderOption } from '../../models/ror.model';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
+
+import { Funder, RorFunderOption } from '../../models';
import { GetFundersList, MetadataSelectors } from '../../store';
import { FundingDialogComponent } from './funding-dialog.component';
-import { MOCK_FUNDERS } from '@testing/mocks/funder.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
-const MOCK_ROR_FUNDERS: RorFunderOption[] = [{ id: 'https://ror.org/0test', name: 'Test Funder' }];
+interface SetupOverrides extends BaseSetupOverrides {
+ configFunders?: Funder[];
+}
describe('FundingDialogComponent', () => {
let component: FundingDialogComponent;
let fixture: ComponentFixture;
+ let dialogRef: DynamicDialogRef;
+ let store: Store;
+
+ const mockFundersList: RorFunderOption[] = [
+ { id: 'https://ror.org/111', name: 'Open Science Foundation' },
+ { id: 'https://ror.org/222', name: 'National Science Fund' },
+ ];
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: MetadataSelectors.getFundersList, value: mockFundersList },
+ { selector: MetadataSelectors.getFundersLoading, value: false },
+ ];
+
+ function setup(overrides: SetupOverrides = {}) {
+ const signals = mergeSignalOverrides(defaultSignals, overrides.selectorOverrides);
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [FundingDialogComponent, OSFTestingModule],
+ TestBed.configureTestingModule({
+ imports: [FundingDialogComponent],
providers: [
- MockProviders(DynamicDialogRef, DestroyRef),
- MockProvider(DynamicDialogConfig, { data: { funders: [] } }),
- provideMockStore({
- signals: [
- { selector: MetadataSelectors.getFundersList, value: MOCK_ROR_FUNDERS },
- { selector: MetadataSelectors.getFundersLoading, value: false },
- ],
- }),
+ provideOSFCore(),
+ provideDynamicDialogRefMock(),
+ MockProvider(DynamicDialogConfig, { data: { funders: overrides.configFunders ?? [] } }),
+ provideMockStore({ signals }),
],
- }).compileComponents();
+ });
+ store = TestBed.inject(Store);
+ dialogRef = TestBed.inject(DynamicDialogRef);
fixture = TestBed.createComponent(FundingDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
+ }
+
+ afterEach(() => {
+ vi.useRealTimers();
});
it('should create', () => {
+ setup();
+
expect(component).toBeTruthy();
});
- it('should not remove last funding entry and close dialog with empty result', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
- expect(component.fundingEntries.length).toBe(1);
-
- component.removeFundingEntry(0);
+ it('should initialize with one empty entry when config has no funders', () => {
+ setup();
expect(component.fundingEntries.length).toBe(1);
- expect(closeSpy).toHaveBeenCalledWith({ fundingEntries: [] });
+ expect(component.fundingEntries.at(0).get('funderName')?.value).toBeNull();
+ expect(component.fundingEntries.at(0).get('funderIdentifierType')?.value).toBe('DOI');
});
- it('should save valid form data', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
- const entry = component.fundingEntries.at(0);
- entry.patchValue({
- funderName: 'Test Funder',
- awardTitle: 'Test Award',
- awardUri: 'https://www.nsf.gov/awardsearch/showAward?AWD_ID=1234567',
- });
+ it('should initialize entries from config funders', () => {
+ const configFunders: Funder[] = [
+ {
+ funderName: 'Configured Funder',
+ funderIdentifier: '10.123/abc',
+ funderIdentifierType: 'DOI',
+ awardTitle: 'Configured Award',
+ awardUri: 'https://example.org/award',
+ awardNumber: 'A-100',
+ },
+ ];
- fixture.detectChanges();
+ setup({ configFunders });
- component.save();
-
- expect(closeSpy).toHaveBeenCalledWith({
- fundingEntries: [
- {
- funderName: 'Test Funder',
- funderIdentifier: '',
- funderIdentifierType: 'DOI',
- awardTitle: 'Test Award',
- awardUri: 'https://www.nsf.gov/awardsearch/showAward?AWD_ID=1234567',
- awardNumber: '',
- },
- ],
- });
+ expect(component.fundingEntries.length).toBe(1);
+ expect(component.fundingEntries.at(0).value).toEqual(configFunders[0]);
});
- it('should not save when form is invalid', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
- component.addFundingEntry();
- const entry = component.fundingEntries.at(0);
- entry.patchValue({
- funderName: '',
- awardTitle: '',
+ it('should return loading filter message when funders are loading', () => {
+ setup({
+ selectorOverrides: [{ selector: MetadataSelectors.getFundersLoading, value: true }],
});
- component.save();
-
- expect(closeSpy).not.toHaveBeenCalled();
+ expect(component.filterMessage()).toBe('project.metadata.funding.dialog.loadingFunders');
});
- it('should cancel dialog', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ it('should dispatch GetFundersList after debounced search', () => {
+ vi.useFakeTimers();
+ setup();
+ (store.dispatch as Mock).mockClear();
- component.cancel();
+ component.onFunderSearch('open');
+ vi.advanceTimersByTime(300);
- expect(closeSpy).toHaveBeenCalled();
+ expect(store.dispatch).toHaveBeenCalledWith(new GetFundersList('open'));
});
- it('should validate required fields', () => {
- component.addFundingEntry();
- const entry = component.fundingEntries.at(0);
-
- const funderNameControl = entry.get('funderName');
- const awardTitleControl = entry.get('awardTitle');
+ it('should not dispatch duplicate consecutive search terms', () => {
+ vi.useFakeTimers();
+ setup();
+ (store.dispatch as Mock).mockClear();
- expect(funderNameControl?.hasError('required')).toBe(true);
- expect(awardTitleControl?.hasError('required')).toBe(false);
+ component.onFunderSearch('same');
+ vi.advanceTimersByTime(300);
+ component.onFunderSearch('same');
+ vi.advanceTimersByTime(300);
- funderNameControl?.setValue('Test Funder');
- awardTitleControl?.setValue('Test Award');
-
- expect(funderNameControl?.hasError('required')).toBe(false);
- expect(awardTitleControl?.hasError('required')).toBe(false);
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+ expect(store.dispatch).toHaveBeenCalledWith(new GetFundersList('same'));
});
- it('should not update funding entry when funder is not found', () => {
- const entry = component.fundingEntries.at(0);
- const initialValues = {
- funderName: entry.get('funderName')?.value,
- funderIdentifier: entry.get('funderIdentifier')?.value,
- funderIdentifierType: entry.get('funderIdentifierType')?.value,
- };
+ it('should return selected current entry in options when missing in list', () => {
+ setup({
+ selectorOverrides: [{ selector: MetadataSelectors.getFundersList, value: [] }],
+ });
+ component.fundingEntries.at(0).patchValue({
+ funderName: 'Manual Funder',
+ funderIdentifier: 'manual-id',
+ });
- component.onFunderSelected('Non-existent Funder', 0);
+ const options = component.getOptionsForIndex(0);
- expect(entry.get('funderName')?.value).toBe(initialValues.funderName);
- expect(entry.get('funderIdentifier')?.value).toBe(initialValues.funderIdentifier);
- expect(entry.get('funderIdentifierType')?.value).toBe(initialValues.funderIdentifierType);
+ expect(options).toEqual([{ id: 'manual-id', name: 'Manual Funder' }]);
});
- it('should update funding entry when funder is selected from ROR list', () => {
- const entry = component.fundingEntries.at(0);
-
- component.onFunderSelected('Test Funder', 0);
+ it('should patch selected funder data when a funder is selected', () => {
+ setup();
- expect(entry.get('funderName')?.value).toBe('Test Funder');
- expect(entry.get('funderIdentifier')?.value).toBe('https://ror.org/0test');
- expect(entry.get('funderIdentifierType')?.value).toBe('ROR');
- });
-
- it('should remove funding entry when more than one exists', () => {
- component.addFundingEntry();
- expect(component.fundingEntries.length).toBe(2);
+ component.onFunderSelected('National Science Fund', 0);
- component.removeFundingEntry(0);
- expect(component.fundingEntries.length).toBe(1);
+ expect(component.fundingEntries.at(0).get('funderName')?.value).toBe('National Science Fund');
+ expect(component.fundingEntries.at(0).get('funderIdentifier')?.value).toBe('https://ror.org/222');
+ expect(component.fundingEntries.at(0).get('funderIdentifierType')?.value).toBe('ROR');
});
- it('should not remove funding entry when index is out of bounds', () => {
+ it('should remove entry when more than one funding entry exists', () => {
+ setup();
component.addFundingEntry();
- const initialLength = component.fundingEntries.length;
- component.removeFundingEntry(999);
- expect(component.fundingEntries.length).toBe(initialLength);
- });
-
- it('should create entry with supplement data when provided', () => {
- const supplement = {
- funderName: 'Test Funder',
- funderIdentifier: 'test-id',
- funderIdentifierType: 'ROR',
- title: 'Test Award',
- url: 'https://test.com',
- awardNumber: 'AWARD-123',
- };
-
- component.addFundingEntry(supplement);
-
- const entry = component.fundingEntries.at(component.fundingEntries.length - 1);
- expect(entry.get('funderName')?.value).toBe('Test Funder');
- expect(entry.get('funderIdentifier')?.value).toBe('test-id');
- expect(entry.get('funderIdentifierType')?.value).toBe('ROR');
- expect(entry.get('awardTitle')?.value).toBe('Test Award');
- expect(entry.get('awardUri')?.value).toBe('https://test.com');
- expect(entry.get('awardNumber')?.value).toBe('AWARD-123');
- });
-
- it('should create entry with supplement data using awardTitle fallback', () => {
- const supplement = {
- funderName: 'Test Funder',
- awardTitle: 'Test Award Title',
- url: 'https://test.com',
- };
-
- component.addFundingEntry(supplement);
+ component.removeFundingEntry(1);
- const entry = component.fundingEntries.at(component.fundingEntries.length - 1);
- expect(entry.get('awardTitle')?.value).toBe('Test Award Title');
+ expect(component.fundingEntries.length).toBe(1);
+ expect(dialogRef.close).not.toHaveBeenCalled();
});
- it('should create entry with supplement data using awardUri fallback', () => {
- const supplement = {
- funderName: 'Test Funder',
- awardUri: 'https://award.com',
- };
-
- component.addFundingEntry(supplement);
+ it('should close with empty result when removing the last entry', () => {
+ setup();
- const entry = component.fundingEntries.at(component.fundingEntries.length - 1);
- expect(entry.get('awardUri')?.value).toBe('https://award.com');
- });
-
- it('should create entry with default values when no supplement provided', () => {
- const initialLength = component.fundingEntries.length;
- component.addFundingEntry();
+ component.removeFundingEntry(0);
- const entry = component.fundingEntries.at(initialLength);
- expect(entry.get('funderName')?.value).toBe(null);
- expect(entry.get('funderIdentifier')?.value).toBe('');
- expect(entry.get('funderIdentifierType')?.value).toBe('DOI');
- expect(entry.get('awardTitle')?.value).toBe('');
- expect(entry.get('awardUri')?.value).toBe('');
- expect(entry.get('awardNumber')?.value).toBe('');
+ expect(dialogRef.close).toHaveBeenCalledWith({ fundingEntries: [] });
});
- it('should dispatch getFundersList after debounce when searching', fakeAsync(() => {
- const store = TestBed.inject(Store);
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ it('should close with funding data on save when form is valid', () => {
+ setup();
+ component.fundingEntries.at(0).patchValue({
+ funderName: 'Open Science Foundation',
+ funderIdentifier: 'https://ror.org/111',
+ funderIdentifierType: 'ROR',
+ awardTitle: '',
+ awardUri: '',
+ awardNumber: '',
+ });
- component.onFunderSearch('query');
- expect(dispatchSpy).not.toHaveBeenCalled();
- tick(300);
- expect(dispatchSpy).toHaveBeenCalledWith(new GetFundersList('query'));
- }));
+ component.save();
- it('should pre-populate entries from config funders on init', () => {
- TestBed.resetTestingModule();
- TestBed.configureTestingModule({
- imports: [FundingDialogComponent, OSFTestingModule],
- providers: [
- MockProviders(DynamicDialogRef, DestroyRef),
- MockProvider(DynamicDialogConfig, { data: { funders: [MOCK_FUNDERS[0]] } }),
- provideMockStore({
- signals: [
- { selector: MetadataSelectors.getFundersList, value: [] },
- { selector: MetadataSelectors.getFundersLoading, value: false },
- ],
- }),
+ expect(dialogRef.close).toHaveBeenCalledWith({
+ fundingEntries: [
+ {
+ funderName: 'Open Science Foundation',
+ funderIdentifier: 'https://ror.org/111',
+ funderIdentifierType: 'ROR',
+ awardTitle: '',
+ awardUri: '',
+ awardNumber: '',
+ },
],
- }).compileComponents();
- const f = TestBed.createComponent(FundingDialogComponent);
- f.detectChanges();
- const c = f.componentInstance;
- expect(c.fundingEntries.length).toBe(1);
- const entry = c.fundingEntries.at(0);
- expect(entry.get('funderName')?.value).toBe(MOCK_FUNDERS[0].funderName);
- expect(entry.get('funderIdentifier')?.value).toBe(MOCK_FUNDERS[0].funderIdentifier);
- expect(entry.get('funderIdentifierType')?.value).toBe(MOCK_FUNDERS[0].funderIdentifierType);
- expect(entry.get('awardTitle')?.value).toBe(MOCK_FUNDERS[0].awardTitle);
- expect(entry.get('awardUri')?.value).toBe(MOCK_FUNDERS[0].awardUri);
- expect(entry.get('awardNumber')?.value).toBe(MOCK_FUNDERS[0].awardNumber);
+ });
});
- it('getOptionsForIndex returns custom option plus list when entry name is not in list', () => {
- const entry = component.fundingEntries.at(0);
- entry.patchValue({ funderName: 'Custom Funder', funderIdentifier: 'custom-id' });
- const options = component.getOptionsForIndex(0);
- expect(options).toHaveLength(2);
- expect(options[0]).toEqual({ id: 'custom-id', name: 'Custom Funder' });
- expect(options[1]).toEqual(MOCK_ROR_FUNDERS[0]);
- });
+ it('should not close on save when form is invalid', () => {
+ setup();
- it('getOptionsForIndex returns list when entry has no name', () => {
- const options = component.getOptionsForIndex(0);
- expect(options).toEqual(MOCK_ROR_FUNDERS);
- });
+ component.save();
- it('filterMessage returns loading key when funders loading', () => {
- TestBed.resetTestingModule();
- const loadingSignal = signal(true);
- TestBed.configureTestingModule({
- imports: [FundingDialogComponent, OSFTestingModule],
- providers: [
- MockProviders(DynamicDialogRef, DestroyRef),
- MockProvider(DynamicDialogConfig, { data: { funders: [] } }),
- provideMockStore({
- signals: [
- { selector: MetadataSelectors.getFundersList, value: [] },
- { selector: MetadataSelectors.getFundersLoading, value: loadingSignal },
- ],
- }),
- ],
- }).compileComponents();
- const f = TestBed.createComponent(FundingDialogComponent);
- f.detectChanges();
- expect(f.componentInstance.filterMessage()).toBe('project.metadata.funding.dialog.loadingFunders');
- loadingSignal.set(false);
- expect(f.componentInstance.filterMessage()).toBe('project.metadata.funding.dialog.noFundersFound');
+ expect(dialogRef.close).not.toHaveBeenCalled();
});
- it('save returns only entries with at least one of funderName, awardTitle, awardUri, awardNumber', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
- component.addFundingEntry();
- component.fundingEntries.at(0).patchValue({ funderName: 'Funder A', awardTitle: 'Award A' });
- component.fundingEntries.at(1).patchValue({ funderName: 'Funder B', awardTitle: 'Award B' });
- fixture.detectChanges();
- component.save();
- expect(closeSpy).toHaveBeenCalledWith({
- fundingEntries: [
- expect.objectContaining({ funderName: 'Funder A', awardTitle: 'Award A' }),
- expect.objectContaining({ funderName: 'Funder B', awardTitle: 'Award B' }),
- ],
- });
- });
+ it('should close dialog on cancel', () => {
+ setup();
- it('should not save when awardUri is invalid', () => {
- const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
- const entry = component.fundingEntries.at(0);
- entry.patchValue({
- funderName: 'Test Funder',
- awardUri: 'not-a-valid-url',
- });
- fixture.detectChanges();
- component.save();
- expect(closeSpy).not.toHaveBeenCalled();
+ component.cancel();
+
+ expect(dialogRef.close).toHaveBeenCalledWith();
});
});
diff --git a/src/app/features/metadata/dialogs/index.ts b/src/app/features/metadata/dialogs/index.ts
deleted file mode 100644
index c3cd29a34..000000000
--- a/src/app/features/metadata/dialogs/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export { AffiliatedInstitutionsDialogComponent } from './affiliated-institutions-dialog/affiliated-institutions-dialog.component';
-export { ContributorsDialogComponent } from './contributors-dialog/contributors-dialog.component';
-export { DescriptionDialogComponent } from './description-dialog/description-dialog.component';
-export { FundingDialogComponent } from './funding-dialog/funding-dialog.component';
-export { LicenseDialogComponent } from './license-dialog/license-dialog.component';
-export { PublicationDoiDialogComponent } from './publication-doi-dialog/publication-doi-dialog.component';
-export { ResourceInformationDialogComponent } from './resource-information-dialog/resource-information-dialog.component';
-export { ResourceInfoTooltipComponent } from './resource-tooltip-info/resource-tooltip-info.component';
diff --git a/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.spec.ts b/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.spec.ts
index e693c2280..63f7e51c4 100644
--- a/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.spec.ts
@@ -8,20 +8,21 @@ import { LicenseComponent } from '@osf/shared/components/license/license.compone
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { LicensesSelectors } from '@shared/stores/licenses';
-import { LicenseDialogComponent } from './license-dialog.component';
-
import { MOCK_LICENSE } from '@testing/mocks/license.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { LicenseDialogComponent } from './license-dialog.component';
+
describe('LicenseDialogComponent', () => {
let component: LicenseDialogComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [LicenseDialogComponent, OSFTestingModule, ...MockComponents(LoadingSpinnerComponent, LicenseComponent)],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [LicenseDialogComponent, ...MockComponents(LoadingSpinnerComponent, LicenseComponent)],
providers: [
+ provideOSFCore(),
MockProvider(DynamicDialogRef),
MockProvider(DynamicDialogConfig),
provideMockStore({
@@ -31,7 +32,7 @@ describe('LicenseDialogComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(LicenseDialogComponent);
component = fixture.componentInstance;
@@ -49,7 +50,7 @@ describe('LicenseDialogComponent', () => {
it('should handle license creation with non-existent license', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
const createEvent = {
id: 'non-existent-license',
@@ -68,7 +69,7 @@ describe('LicenseDialogComponent', () => {
const mockLicenseComponent = {
selectedLicense: () => MOCK_LICENSE,
licenseForm: { invalid: false },
- saveLicense: jest.fn(),
+ saveLicense: vi.fn(),
};
Object.defineProperty(component, 'licenseComponent', {
@@ -83,7 +84,7 @@ describe('LicenseDialogComponent', () => {
it('should not save when no license is selected', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
component.selectedLicenseId.set(null);
@@ -94,7 +95,7 @@ describe('LicenseDialogComponent', () => {
it('should not save when selected license is not found', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
component.selectedLicenseId.set('non-existent-license');
@@ -105,10 +106,10 @@ describe('LicenseDialogComponent', () => {
it('should handle cancel', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
const mockLicenseComponent = {
- cancel: jest.fn(),
+ cancel: vi.fn(),
};
Object.defineProperty(component, 'licenseComponent', {
@@ -123,7 +124,7 @@ describe('LicenseDialogComponent', () => {
it('should handle cancel when license component is not available', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
Object.defineProperty(component, 'licenseComponent', {
get: () => () => null,
diff --git a/src/app/features/metadata/dialogs/publication-doi-dialog/publication-doi-dialog.component.spec.ts b/src/app/features/metadata/dialogs/publication-doi-dialog/publication-doi-dialog.component.spec.ts
index 9e4307c9a..5c8e5d921 100644
--- a/src/app/features/metadata/dialogs/publication-doi-dialog/publication-doi-dialog.component.spec.ts
+++ b/src/app/features/metadata/dialogs/publication-doi-dialog/publication-doi-dialog.component.spec.ts
@@ -4,9 +4,10 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { PublicationDoiDialogComponent } from './publication-doi-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { PublicationDoiDialogComponent } from './publication-doi-dialog.component';
describe('PublicationDoiDialogComponent', () => {
let component: PublicationDoiDialogComponent;
@@ -14,11 +15,11 @@ describe('PublicationDoiDialogComponent', () => {
let dialogRef: DynamicDialogRef;
let config: DynamicDialogConfig;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [PublicationDoiDialogComponent, OSFTestingModule],
- providers: [MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [PublicationDoiDialogComponent],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig)],
+ });
fixture = TestBed.createComponent(PublicationDoiDialogComponent);
component = fixture.componentInstance;
@@ -45,30 +46,26 @@ describe('PublicationDoiDialogComponent', () => {
});
it('should close dialog with DOI value on save', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
const testDoi = '10.1234/test.doi';
component.publicationDoiControl.setValue(testDoi);
component.save();
- expect(closeSpy).toHaveBeenCalledWith({ value: testDoi });
+ expect(dialogRef.close).toHaveBeenCalledWith({ value: testDoi });
});
it('should close dialog with null when DOI is empty on save', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
component.publicationDoiControl.setValue('');
component.save();
- expect(closeSpy).toHaveBeenCalledWith({ value: null });
+ expect(dialogRef.close).toHaveBeenCalledWith({ value: null });
});
it('should close dialog without data on cancel', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
-
component.cancel();
- expect(closeSpy).toHaveBeenCalledWith();
+ expect(dialogRef.close).toHaveBeenCalledWith();
});
it('should validate valid DOI format', () => {
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 e06aa5a09..65b895efb 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
@@ -4,19 +4,20 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ResourceInformationDialogComponent } from './resource-information-dialog.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { ResourceInformationDialogComponent } from './resource-information-dialog.component';
describe('ResourceInformationDialogComponent', () => {
let component: ResourceInformationDialogComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [ResourceInformationDialogComponent, OSFTestingModule],
- providers: [MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ResourceInformationDialogComponent],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig)],
+ });
fixture = TestBed.createComponent(ResourceInformationDialogComponent);
component = fixture.componentInstance;
@@ -38,7 +39,7 @@ describe('ResourceInformationDialogComponent', () => {
it('should not save when form is invalid', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
component.resourceForm.patchValue({
resourceType: 'dataset',
@@ -55,7 +56,7 @@ describe('ResourceInformationDialogComponent', () => {
it('should not save when resource type is missing', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
component.resourceForm.patchValue({
resourceType: '',
@@ -72,7 +73,7 @@ describe('ResourceInformationDialogComponent', () => {
it('should cancel dialog', () => {
const dialogRef = TestBed.inject(DynamicDialogRef);
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
component.cancel();
diff --git a/src/app/features/metadata/dialogs/resource-tooltip-info/resource-tooltip-info.component.spec.ts b/src/app/features/metadata/dialogs/resource-tooltip-info/resource-tooltip-info.component.spec.ts
index 4b34840d9..acbc737ee 100644
--- a/src/app/features/metadata/dialogs/resource-tooltip-info/resource-tooltip-info.component.spec.ts
+++ b/src/app/features/metadata/dialogs/resource-tooltip-info/resource-tooltip-info.component.spec.ts
@@ -4,9 +4,10 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ResourceInfoTooltipComponent } from './resource-tooltip-info.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { ResourceInfoTooltipComponent } from './resource-tooltip-info.component';
describe('ResourceInfoTooltipComponent', () => {
let component: ResourceInfoTooltipComponent;
@@ -14,11 +15,11 @@ describe('ResourceInfoTooltipComponent', () => {
let dialogRef: DynamicDialogRef;
let config: DynamicDialogConfig;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [ResourceInfoTooltipComponent, OSFTestingModule],
- providers: [MockProvider(DynamicDialogRef), MockProvider(DynamicDialogConfig)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ResourceInfoTooltipComponent],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock(), MockProvider(DynamicDialogConfig)],
+ });
fixture = TestBed.createComponent(ResourceInfoTooltipComponent);
component = fixture.componentInstance;
@@ -42,7 +43,7 @@ describe('ResourceInfoTooltipComponent', () => {
});
it('should close dialog when close is called', () => {
- const closeSpy = jest.spyOn(dialogRef, 'close');
+ const closeSpy = vi.spyOn(dialogRef, 'close');
dialogRef.close();
diff --git a/src/app/features/metadata/metadata.component.spec.ts b/src/app/features/metadata/metadata.component.spec.ts
index 93c00c539..30d951f5f 100644
--- a/src/app/features/metadata/metadata.component.spec.ts
+++ b/src/app/features/metadata/metadata.component.spec.ts
@@ -3,21 +3,6 @@ import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
-import {
- MetadataAffiliatedInstitutionsComponent,
- MetadataContributorsComponent,
- MetadataDateInfoComponent,
- MetadataDescriptionComponent,
- MetadataFundingComponent,
- MetadataLicenseComponent,
- MetadataPublicationDoiComponent,
- MetadataRegistrationDoiComponent,
- MetadataResourceInformationComponent,
- MetadataSubjectsComponent,
- MetadataTagsComponent,
- MetadataTitleComponent,
-} from '@osf/features/metadata/components';
-import { MetadataSelectors } from '@osf/features/metadata/store';
import { MetadataTabsComponent } from '@osf/shared/components/metadata-tabs/metadata-tabs.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
@@ -26,10 +11,8 @@ import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'
import { ToastService } from '@osf/shared/services/toast.service';
import { RegistrationProviderSelectors } from '@osf/shared/stores/registration-provider';
-import { MetadataComponent } from './metadata.component';
-
import { MOCK_PROJECT_METADATA } from '@testing/mocks/project-metadata.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom-confirmation-provider.mock';
import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
@@ -37,6 +20,21 @@ import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+import { MetadataAffiliatedInstitutionsComponent } from './components/metadata-affiliated-institutions/metadata-affiliated-institutions.component';
+import { MetadataContributorsComponent } from './components/metadata-contributors/metadata-contributors.component';
+import { MetadataDateInfoComponent } from './components/metadata-date-info/metadata-date-info.component';
+import { MetadataDescriptionComponent } from './components/metadata-description/metadata-description.component';
+import { MetadataFundingComponent } from './components/metadata-funding/metadata-funding.component';
+import { MetadataLicenseComponent } from './components/metadata-license/metadata-license.component';
+import { MetadataPublicationDoiComponent } from './components/metadata-publication-doi/metadata-publication-doi.component';
+import { MetadataRegistrationDoiComponent } from './components/metadata-registration-doi/metadata-registration-doi.component';
+import { MetadataResourceInformationComponent } from './components/metadata-resource-information/metadata-resource-information.component';
+import { MetadataSubjectsComponent } from './components/metadata-subjects/metadata-subjects.component';
+import { MetadataTagsComponent } from './components/metadata-tags/metadata-tags.component';
+import { MetadataTitleComponent } from './components/metadata-title/metadata-title.component';
+import { MetadataComponent } from './metadata.component';
+import { MetadataSelectors } from './store';
+
describe('MetadataComponent', () => {
let component: MetadataComponent;
let fixture: ComponentFixture;
@@ -49,7 +47,7 @@ describe('MetadataComponent', () => {
const mockMetadata = MOCK_PROJECT_METADATA;
const mockResourceId = 'test-resource-id';
- beforeEach(async () => {
+ beforeEach(() => {
activatedRouteMock = ActivatedRouteMockBuilder.create()
.withId(mockResourceId)
.withData({ resourceType: ResourceType.Project })
@@ -73,7 +71,7 @@ describe('MetadataComponent', () => {
customConfirmationServiceMock = CustomConfirmationServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
MetadataComponent,
...MockComponents(
@@ -92,9 +90,9 @@ describe('MetadataComponent', () => {
MetadataTitleComponent,
MetadataRegistrationDoiComponent
),
- OSFTestingModule,
],
providers: [
+ provideOSFCore(),
MockProvider(ActivatedRoute, activatedRouteMock),
MockProvider(Router, routerMock),
MockProvider(CustomDialogService, customDialogServiceMock),
@@ -111,7 +109,7 @@ describe('MetadataComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(MetadataComponent);
component = fixture.componentInstance;
@@ -123,7 +121,7 @@ describe('MetadataComponent', () => {
it('should handle tab change for OSF tab', () => {
const tabId = 'osf';
- const navigateSpy = jest.spyOn(routerMock, 'navigate');
+ const navigateSpy = vi.spyOn(routerMock, 'navigate');
component.onTabChange(tabId);
@@ -146,7 +144,7 @@ describe('MetadataComponent', () => {
});
it('should open edit contributor dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
expect(openSpy).toHaveBeenCalledTimes(0);
expect(() => component.openEditContributorDialog()).toThrow();
@@ -154,7 +152,7 @@ describe('MetadataComponent', () => {
});
it('should open edit title dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditTitleDialog();
@@ -162,7 +160,7 @@ describe('MetadataComponent', () => {
});
it('should open edit description dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditDescriptionDialog();
@@ -170,7 +168,7 @@ describe('MetadataComponent', () => {
});
it('should open edit resource information dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditResourceInformationDialog();
@@ -178,7 +176,7 @@ describe('MetadataComponent', () => {
});
it('should show resource info tooltip', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.onShowResourceInfo();
@@ -186,7 +184,7 @@ describe('MetadataComponent', () => {
});
it('should open edit license dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditLicenseDialog();
@@ -194,7 +192,7 @@ describe('MetadataComponent', () => {
});
it('should open edit funding dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditFundingDialog();
@@ -202,7 +200,7 @@ describe('MetadataComponent', () => {
});
it('should open edit affiliated institutions dialog', () => {
- const openSpy = jest.spyOn(customDialogServiceMock, 'open');
+ const openSpy = vi.spyOn(customDialogServiceMock, 'open');
component.openEditAffiliatedInstitutionsDialog();
@@ -222,7 +220,7 @@ describe('MetadataComponent', () => {
});
it('should handle edit DOI for project', () => {
- const confirmSpy = jest.spyOn(customConfirmationServiceMock, 'confirmDelete');
+ const confirmSpy = vi.spyOn(customConfirmationServiceMock, 'confirmDelete');
component.handleEditDoi();
@@ -230,7 +228,7 @@ describe('MetadataComponent', () => {
});
it('should open add record', () => {
- const navigateSpy = jest.spyOn(routerMock, 'navigate');
+ const navigateSpy = vi.spyOn(routerMock, 'navigate');
component.openAddRecord();
@@ -238,7 +236,7 @@ describe('MetadataComponent', () => {
});
it('should handle cedar form change template', () => {
- const navigateSpy = jest.spyOn(routerMock, 'navigate');
+ const navigateSpy = vi.spyOn(routerMock, 'navigate');
component.onCedarFormChangeTemplate();
diff --git a/src/app/features/metadata/metadata.component.ts b/src/app/features/metadata/metadata.component.ts
index 5e4dc966d..ad6f68623 100644
--- a/src/app/features/metadata/metadata.component.ts
+++ b/src/app/features/metadata/metadata.component.ts
@@ -50,33 +50,29 @@ import {
import { MetadataTabsModel } from '@shared/models/metadata-tabs.model';
import { SubjectModel } from '@shared/models/subject/subject.model';
+import { MetadataAffiliatedInstitutionsComponent } from './components/metadata-affiliated-institutions/metadata-affiliated-institutions.component';
import { MetadataCollectionsComponent } from './components/metadata-collections/metadata-collections.component';
+import { MetadataContributorsComponent } from './components/metadata-contributors/metadata-contributors.component';
+import { MetadataDateInfoComponent } from './components/metadata-date-info/metadata-date-info.component';
+import { MetadataDescriptionComponent } from './components/metadata-description/metadata-description.component';
+import { MetadataFundingComponent } from './components/metadata-funding/metadata-funding.component';
+import { MetadataLicenseComponent } from './components/metadata-license/metadata-license.component';
+import { MetadataPublicationDoiComponent } from './components/metadata-publication-doi/metadata-publication-doi.component';
+import { MetadataRegistrationDoiComponent } from './components/metadata-registration-doi/metadata-registration-doi.component';
import { MetadataRegistryInfoComponent } from './components/metadata-registry-info/metadata-registry-info.component';
+import { MetadataResourceInformationComponent } from './components/metadata-resource-information/metadata-resource-information.component';
+import { MetadataSubjectsComponent } from './components/metadata-subjects/metadata-subjects.component';
+import { MetadataTagsComponent } from './components/metadata-tags/metadata-tags.component';
+import { MetadataTitleComponent } from './components/metadata-title/metadata-title.component';
+import { AffiliatedInstitutionsDialogComponent } from './dialogs/affiliated-institutions-dialog/affiliated-institutions-dialog.component';
+import { ContributorsDialogComponent } from './dialogs/contributors-dialog/contributors-dialog.component';
+import { DescriptionDialogComponent } from './dialogs/description-dialog/description-dialog.component';
import { EditTitleDialogComponent } from './dialogs/edit-title-dialog/edit-title-dialog.component';
-import {
- MetadataAffiliatedInstitutionsComponent,
- MetadataContributorsComponent,
- MetadataDateInfoComponent,
- MetadataDescriptionComponent,
- MetadataFundingComponent,
- MetadataLicenseComponent,
- MetadataPublicationDoiComponent,
- MetadataRegistrationDoiComponent,
- MetadataResourceInformationComponent,
- MetadataSubjectsComponent,
- MetadataTagsComponent,
- MetadataTitleComponent,
-} from './components';
-import {
- AffiliatedInstitutionsDialogComponent,
- ContributorsDialogComponent,
- DescriptionDialogComponent,
- FundingDialogComponent,
- LicenseDialogComponent,
- PublicationDoiDialogComponent,
- ResourceInformationDialogComponent,
- ResourceInfoTooltipComponent,
-} from './dialogs';
+import { FundingDialogComponent } from './dialogs/funding-dialog/funding-dialog.component';
+import { LicenseDialogComponent } from './dialogs/license-dialog/license-dialog.component';
+import { PublicationDoiDialogComponent } from './dialogs/publication-doi-dialog/publication-doi-dialog.component';
+import { ResourceInformationDialogComponent } from './dialogs/resource-information-dialog/resource-information-dialog.component';
+import { ResourceInfoTooltipComponent } from './dialogs/resource-tooltip-info/resource-tooltip-info.component';
import {
CedarMetadataDataTemplateJsonApi,
CedarMetadataRecordData,
diff --git a/src/app/features/metadata/pages/add-metadata/add-metadata.component.spec.ts b/src/app/features/metadata/pages/add-metadata/add-metadata.component.spec.ts
index b6e1594c4..ad9b2eae5 100644
--- a/src/app/features/metadata/pages/add-metadata/add-metadata.component.spec.ts
+++ b/src/app/features/metadata/pages/add-metadata/add-metadata.component.spec.ts
@@ -3,22 +3,22 @@ import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
-import { CedarTemplateFormComponent } from '@osf/features/metadata/components';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { ToastService } from '@osf/shared/services/toast.service';
-import { MetadataSelectors } from '../../store';
-
-import { AddMetadataComponent } from './add-metadata.component';
-
import { CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK } from '@testing/mocks/cedar-metadata-data-template-json-api.mock';
import { MOCK_CEDAR_METADATA_RECORD_DATA } from '@testing/mocks/cedar-metadata-record.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
-import { ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+import { ToastServiceMock, ToastServiceMockBuilder } from '@testing/providers/toast-provider.mock';
+
+import { CedarTemplateFormComponent } from '../../components/cedar-template-form/cedar-template-form.component';
+import { MetadataSelectors } from '../../store';
+
+import { AddMetadataComponent } from './add-metadata.component';
describe('AddMetadataComponent', () => {
let component: AddMetadataComponent;
@@ -42,11 +42,11 @@ describe('AddMetadataComponent', () => {
const mockCedarRecords = [mockRecord];
- beforeEach(async () => {
- toastService = ToastServiceMockBuilder.create().build();
+ beforeEach(() => {
+ toastService = ToastServiceMock.simple();
router = {
- navigate: jest.fn(),
+ navigate: vi.fn(),
};
const baseRoute = ActivatedRouteMockBuilder.create().build();
@@ -66,13 +66,13 @@ describe('AddMetadataComponent', () => {
} as any,
};
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
AddMetadataComponent,
- OSFTestingModule,
...MockComponents(SubHeaderComponent, CedarTemplateFormComponent, LoadingSpinnerComponent),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, router),
MockProvider(ActivatedRoute, activatedRoute),
MockProvider(ToastService, toastService),
@@ -85,7 +85,7 @@ describe('AddMetadataComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(AddMetadataComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/metadata/pages/add-metadata/add-metadata.component.ts b/src/app/features/metadata/pages/add-metadata/add-metadata.component.ts
index 63cd38edf..c60deb5be 100644
--- a/src/app/features/metadata/pages/add-metadata/add-metadata.component.ts
+++ b/src/app/features/metadata/pages/add-metadata/add-metadata.component.ts
@@ -24,7 +24,7 @@ import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { IS_MEDIUM } from '@osf/shared/helpers/breakpoints.tokens';
import { ToastService } from '@osf/shared/services/toast.service';
-import { CedarTemplateFormComponent } from '../../components';
+import { CedarTemplateFormComponent } from '../../components/cedar-template-form/cedar-template-form.component';
import { CedarMetadataDataTemplateJsonApi, CedarMetadataRecordData, CedarRecordDataBinding } from '../../models';
import {
CreateCedarMetadataRecord,
diff --git a/src/app/features/metadata/pages/index.ts b/src/app/features/metadata/pages/index.ts
deleted file mode 100644
index 264da8299..000000000
--- a/src/app/features/metadata/pages/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { AddMetadataComponent } from './add-metadata/add-metadata.component';
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 40a6e07a0..dc3aedd7e 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,183 +1,202 @@
import { Store } from '@ngxs/store';
-import { MockComponents, MockProvider } from 'ng-mocks';
+import { MockProvider } from 'ng-mocks';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { PaginatorState } from 'primeng/paginator';
-import { signal } from '@angular/core';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { Mock } from 'vitest';
-import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
-import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
-import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { AddModeratorType } from '../../enums';
-import { ModeratorAddModel } from '../../models';
-import { ModeratorsSelectors } from '../../store/moderators';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
-import { AddModeratorDialogComponent } from './add-moderator-dialog.component';
+import { AddModeratorType, ModeratorPermission } from '../../enums';
+import { ModeratorAddModel, ModeratorDialogAddModel } from '../../models';
+import { ClearUsers, ModeratorsSelectors, SearchUsers, SearchUsersPageChange } from '../../store/moderators';
-import { MOCK_USER } from '@testing/mocks/data.mock';
-import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { AddModeratorDialogComponent } from './add-moderator-dialog.component';
describe('AddModeratorDialogComponent', () => {
let component: AddModeratorDialogComponent;
let fixture: ComponentFixture;
- let dialogRef: jest.Mocked;
- let dialogConfig: DynamicDialogConfig;
+ let dialogRef: DynamicDialogRef;
let store: Store;
- const mockUsers = [MOCK_USER];
-
- beforeEach(async () => {
- dialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked;
-
- dialogConfig = {
- data: [],
- } as DynamicDialogConfig;
-
- await TestBed.configureTestingModule({
- imports: [
- AddModeratorDialogComponent,
- OSFTestingModule,
- ...MockComponents(SearchInputComponent, LoadingSpinnerComponent, CustomPaginatorComponent),
- ],
+ const mockUsers: ModeratorAddModel[] = [
+ {
+ id: 'u1',
+ fullName: 'User One',
+ email: 'user.one@example.org',
+ permission: ModeratorPermission.Moderator,
+ },
+ {
+ id: 'u2',
+ fullName: 'User Two',
+ email: 'user.two@example.org',
+ permission: ModeratorPermission.Admin,
+ },
+ ];
+
+ const defaultSignals: SignalOverride[] = [
+ { selector: ModeratorsSelectors.getUsers, value: mockUsers },
+ { selector: ModeratorsSelectors.isUsersLoading, value: false },
+ { selector: ModeratorsSelectors.getUsersTotalCount, value: 20 },
+ { selector: ModeratorsSelectors.getUsersNextLink, value: '/users?page=2' },
+ { selector: ModeratorsSelectors.getUsersPreviousLink, value: '/users?page=1' },
+ ];
+
+ function setup(overrides: BaseSetupOverrides = {}) {
+ const signals = mergeSignalOverrides(defaultSignals, overrides.selectorOverrides);
+
+ TestBed.configureTestingModule({
+ imports: [AddModeratorDialogComponent],
providers: [
- DynamicDialogRefMock,
- MockProvider(DynamicDialogConfig, dialogConfig),
- provideMockStore({
- signals: [
- { selector: ModeratorsSelectors.getUsers, value: signal(mockUsers) },
- { selector: ModeratorsSelectors.isUsersLoading, value: false },
- { selector: ModeratorsSelectors.getUsersTotalCount, value: 2 },
- { selector: ModeratorsSelectors.getUsersNextLink, value: signal(null) },
- { selector: ModeratorsSelectors.getUsersPreviousLink, value: signal(null) },
- ],
- }),
+ provideOSFCore(),
+ provideDynamicDialogRefMock(),
+ MockProvider(DynamicDialogConfig, { data: {} }),
+ provideMockStore({ signals }),
],
- }).compileComponents();
+ });
store = TestBed.inject(Store);
+ dialogRef = TestBed.inject(DynamicDialogRef);
fixture = TestBed.createComponent(AddModeratorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
- });
+ }
it('should create', () => {
+ setup();
+
expect(component).toBeTruthy();
});
- it('should initialize with default values', () => {
- 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 close with selected users on addModerator', () => {
+ setup();
+ component.selectedUsers.set([mockUsers[0]]);
+
+ component.addModerator();
- it('should load users from store', () => {
- expect(component.users()).toEqual(mockUsers);
- expect(component.isLoading()).toBe(false);
- expect(component.totalUsersCount()).toBe(2);
+ const expected: ModeratorDialogAddModel = { data: [mockUsers[0]], type: AddModeratorType.Search };
+ expect(dialogRef.close).toHaveBeenCalledWith(expected);
});
- it('should close dialog with correct data for addModerator', () => {
- const mockSelectedUsers: ModeratorAddModel[] = [
- {
- id: '1',
- fullName: 'John Doe',
- email: 'john@example.com',
- permission: 'read' as any,
- },
- ];
- component.selectedUsers.set(mockSelectedUsers);
+ it('should close with invite type on inviteModerator', () => {
+ setup();
- component.addModerator();
+ component.inviteModerator();
- expect(dialogRef.close).toHaveBeenCalledWith({
- data: mockSelectedUsers,
- type: AddModeratorType.Search,
- });
+ const expected: ModeratorDialogAddModel = { data: [], type: AddModeratorType.Invite };
+ expect(dialogRef.close).toHaveBeenCalledWith(expected);
});
- it('should close dialog with correct data for inviteModerator', () => {
- component.inviteModerator();
+ it('should dispatch ClearUsers on destroy', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
- expect(dialogRef.close).toHaveBeenCalledWith({
- data: [],
- type: AddModeratorType.Invite,
- });
+ component.ngOnDestroy();
+
+ expect(store.dispatch).toHaveBeenCalledWith(new ClearUsers());
});
- it('should handle pagination correctly', () => {
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ it('should dispatch SearchUsers for first page when search term exists', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
+ component.searchControl.setValue('alice');
- component.pageChanged({ first: 0 } as PaginatorState);
- expect(dispatchSpy).not.toHaveBeenCalled();
+ const pageEvent: PaginatorState = { page: 0, first: 0, rows: 10, pageCount: 2 };
+ component.pageChanged(pageEvent);
- component.searchControl.setValue('test');
- component.pageChanged({ page: 0, first: 0, rows: 10 } as PaginatorState);
- expect(dispatchSpy).toHaveBeenCalled();
+ expect(store.dispatch).toHaveBeenCalledWith(new SearchUsers('alice'));
expect(component.currentPage()).toBe(1);
expect(component.first()).toBe(0);
});
- it('should navigate to next page when link is available', () => {
- const nextLink = 'http://api.example.com/users?page=3';
- const originalSelect = store.select.bind(store);
- (store.select as jest.Mock) = jest.fn((selector) => {
- if (selector === ModeratorsSelectors.getUsersNextLink) {
- return signal(nextLink);
- }
- return originalSelect(selector);
- });
+ it('should not dispatch first-page search when search term is empty', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
+ component.searchControl.setValue(' ');
+
+ const pageEvent: PaginatorState = { page: 0, first: 0, rows: 10, pageCount: 2 };
+ component.pageChanged(pageEvent);
- Object.defineProperty(component, 'usersNextLink', {
- get: () => signal(nextLink),
- configurable: true,
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('should dispatch SearchUsersPageChange with next link', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
+
+ const pageEvent: PaginatorState = { page: 1, first: 10, rows: 10, pageCount: 2 };
+ component.pageChanged(pageEvent);
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SearchUsersPageChange('/users?page=2'));
+ expect(component.currentPage()).toBe(2);
+ expect(component.first()).toBe(10);
+ });
+
+ it('should dispatch SearchUsersPageChange with previous link', () => {
+ setup();
+ (store.dispatch as Mock).mockClear();
+ component.currentPage.set(3);
+
+ const pageEvent: PaginatorState = { page: 1, first: 10, rows: 10, pageCount: 3 };
+ component.pageChanged(pageEvent);
+
+ expect(store.dispatch).toHaveBeenCalledWith(new SearchUsersPageChange('/users?page=1'));
+ expect(component.currentPage()).toBe(2);
+ expect(component.first()).toBe(10);
+ });
+
+ it('should not dispatch page change when link is missing', () => {
+ setup({
+ selectorOverrides: [{ selector: ModeratorsSelectors.getUsersNextLink, value: null }],
});
+ (store.dispatch as Mock).mockClear();
- const dispatchSpy = jest.spyOn(store, 'dispatch');
- component.currentPage.set(2);
- component.pageChanged({ page: 2, first: 20, rows: 10 } as PaginatorState);
+ const pageEvent: PaginatorState = { page: 1, first: 10, rows: 10, pageCount: 2 };
+ component.pageChanged(pageEvent);
- expect(dispatchSpy).toHaveBeenCalled();
- expect(component.currentPage()).toBe(3);
- expect(component.first()).toBe(20);
+ expect(store.dispatch).not.toHaveBeenCalled();
});
- it('should debounce and filter search input', fakeAsync(() => {
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ it('should debounce search and clear selected users', () => {
+ vi.useFakeTimers();
+ setup();
+ (store.dispatch as Mock).mockClear();
+ component.selectedUsers.set([mockUsers[0]]);
- component.searchControl.setValue('t');
- tick(200);
- component.searchControl.setValue('test');
- tick(500);
+ component.searchControl.setValue('john');
+ vi.advanceTimersByTime(500);
- expect(dispatchSpy).toHaveBeenCalledTimes(1);
+ expect(store.dispatch).toHaveBeenCalledWith(new SearchUsers('john'));
expect(component.isInitialState()).toBe(false);
expect(component.selectedUsers()).toEqual([]);
- }));
- it('should not search empty or whitespace values', fakeAsync(() => {
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ vi.useRealTimers();
+ });
- component.searchControl.setValue('');
- tick(500);
- expect(dispatchSpy).not.toHaveBeenCalled();
+ it('should not dispatch duplicate consecutive search terms', () => {
+ vi.useFakeTimers();
+ setup();
+ (store.dispatch as Mock).mockClear();
- component.searchControl.setValue(' ');
- tick(500);
- expect(dispatchSpy).not.toHaveBeenCalled();
- }));
+ component.searchControl.setValue('same');
+ vi.advanceTimersByTime(500);
+ component.searchControl.setValue('same');
+ vi.advanceTimersByTime(500);
- it('should clear users on destroy', () => {
- const dispatchSpy = jest.spyOn(store, 'dispatch');
- component.ngOnDestroy();
- expect(dispatchSpy).toHaveBeenCalled();
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+ expect(store.dispatch).toHaveBeenCalledWith(new SearchUsers('same'));
+
+ vi.useRealTimers();
});
});
diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts
index 8008762d9..e62244d93 100644
--- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts
+++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts
@@ -46,6 +46,7 @@ export class AddModeratorDialogComponent implements OnInit, OnDestroy {
totalUsersCount = select(ModeratorsSelectors.getUsersTotalCount);
usersNextLink = select(ModeratorsSelectors.getUsersNextLink);
usersPreviousLink = select(ModeratorsSelectors.getUsersPreviousLink);
+
isInitialState = signal(true);
currentPage = signal(1);
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 54dc588b9..4cce555f6 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,19 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { BYTES_IN_MB, FILE_TYPES } from '../../constants';
import { BulkUploadComponent } from './bulk-upload.component';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-
describe('BulkUploadComponent', () => {
let component: BulkUploadComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [BulkUploadComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [BulkUploadComponent],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(BulkUploadComponent);
component = fixture.componentInstance;
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 cf5243f73..0140081a5 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
@@ -3,24 +3,24 @@ import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
-import { CollectionSubmissionsListComponent } from '@osf/features/moderation/components';
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SelectComponent } from '@osf/shared/components/select/select.component';
import { CollectionsSelectors } from '@osf/shared/stores/collections';
-import { SubmissionReviewStatus } from '../../enums';
-import { CollectionsModerationSelectors } from '../../store/collections-moderation';
-
-import { CollectionModerationSubmissionsComponent } from './collection-moderation-submissions.component';
-
import { MOCK_PROVIDER } from '@testing/mocks/provider.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { SubmissionReviewStatus } from '../../enums';
+import { CollectionsModerationSelectors } from '../../store/collections-moderation';
+import { CollectionSubmissionsListComponent } from '../collection-submissions-list/collection-submissions-list.component';
+
+import { CollectionModerationSubmissionsComponent } from './collection-moderation-submissions.component';
+
describe('CollectionModerationSubmissionsComponent', () => {
let component: CollectionModerationSubmissionsComponent;
let fixture: ComponentFixture;
@@ -36,16 +36,15 @@ describe('CollectionModerationSubmissionsComponent', () => {
},
];
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create()
.withQueryParams({ status: 'pending', sortBy: 'date_created', page: '1' })
.build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
CollectionModerationSubmissionsComponent,
- OSFTestingModule,
...MockComponents(
SelectComponent,
CollectionSubmissionsListComponent,
@@ -55,6 +54,7 @@ describe('CollectionModerationSubmissionsComponent', () => {
),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
@@ -67,7 +67,7 @@ describe('CollectionModerationSubmissionsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionModerationSubmissionsComponent);
component = fixture.componentInstance;
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 81c1c24db..612a93311 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
@@ -8,16 +8,16 @@ import { CollectionSubmissionWithGuid } from '@osf/shared/models/collections/col
import { CollectionsSelectors } from '@osf/shared/stores/collections';
import { DateAgoPipe } from '@shared/pipes/date-ago.pipe';
-import { SubmissionReviewStatus } from '../../enums';
-
-import { CollectionSubmissionItemComponent } from './collection-submission-item.component';
-
import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { SubmissionReviewStatus } from '../../enums';
+
+import { CollectionSubmissionItemComponent } from './collection-submission-item.component';
+
describe('CollectionSubmissionItemComponent', () => {
let component: CollectionSubmissionItemComponent;
let fixture: ComponentFixture;
@@ -31,25 +31,21 @@ describe('CollectionSubmissionItemComponent', () => {
name: 'Test Provider',
};
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create().withQueryParams({ status: 'pending' }).build();
- await TestBed.configureTestingModule({
- imports: [
- CollectionSubmissionItemComponent,
- OSFTestingModule,
- ...MockComponents(IconComponent),
- MockPipe(DateAgoPipe),
- ],
+ TestBed.configureTestingModule({
+ imports: [CollectionSubmissionItemComponent, ...MockComponents(IconComponent), MockPipe(DateAgoPipe)],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
signals: [{ selector: CollectionsSelectors.getCollectionProvider, value: mockCollectionProvider }],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionSubmissionItemComponent);
component = fixture.componentInstance;
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 aad71b9cb..dc5ea4bb8 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,4 +1,4 @@
-import { MockComponents } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
@@ -6,29 +6,26 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ProjectOverviewComponent } from '@osf/features/project/overview/project-overview.component';
import { Mode } from '@osf/shared/enums/mode.enum';
-import { CollectionSubmissionOverviewComponent } from './collection-submission-overview.component';
-
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { CollectionSubmissionOverviewComponent } from './collection-submission-overview.component';
+
describe('CollectionSubmissionOverviewComponent', () => {
let component: CollectionSubmissionOverviewComponent;
let fixture: ComponentFixture;
let mockRouter: ReturnType;
let mockActivatedRoute: ReturnType;
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create().withQueryParams({ mode: Mode.Moderation }).build();
- await TestBed.configureTestingModule({
- imports: [CollectionSubmissionOverviewComponent, OSFTestingModule, ...MockComponents(ProjectOverviewComponent)],
- providers: [
- { provide: Router, useValue: mockRouter },
- { provide: ActivatedRoute, useValue: mockActivatedRoute },
- ],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [CollectionSubmissionOverviewComponent, ...MockComponents(ProjectOverviewComponent)],
+ providers: [provideOSFCore(), MockProvider(Router, mockRouter), MockProvider(ActivatedRoute, mockActivatedRoute)],
+ });
fixture = TestBed.createComponent(CollectionSubmissionOverviewComponent);
component = fixture.componentInstance;
@@ -58,7 +55,10 @@ describe('CollectionSubmissionOverviewComponent', () => {
component = fixture.componentInstance;
fixture.detectChanges();
- expect(mockRouter.navigate).toHaveBeenCalledWith(['../'], { relativeTo: mockActivatedRoute });
+ expect(mockRouter.navigate).toHaveBeenCalledWith(
+ ['../'],
+ expect.objectContaining({ relativeTo: expect.any(ActivatedRoute) })
+ );
});
it('should not navigate when in moderation mode', () => {
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 e86fbea75..bc5e654f7 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
@@ -2,31 +2,31 @@ import { MockComponent } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CollectionSubmissionItemComponent } from '@osf/features/moderation/components';
+import { MOCK_COLLECTION_SUBMISSION_WITH_GUID } from '@testing/mocks/submission.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
import { CollectionsModerationSelectors } from '../../store/collections-moderation';
+import { CollectionSubmissionItemComponent } from '../collection-submission-item/collection-submission-item.component';
import { CollectionSubmissionsListComponent } from './collection-submissions-list.component';
-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('CollectionSubmissionsListComponent', () => {
let component: CollectionSubmissionsListComponent;
let fixture: ComponentFixture;
const mockSubmissions = [MOCK_COLLECTION_SUBMISSION_WITH_GUID];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [CollectionSubmissionsListComponent, OSFTestingModule, MockComponent(CollectionSubmissionItemComponent)],
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CollectionSubmissionsListComponent, MockComponent(CollectionSubmissionItemComponent)],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [{ selector: CollectionsModerationSelectors.getCollectionSubmissions, value: mockSubmissions }],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(CollectionSubmissionsListComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/moderation/components/index.ts b/src/app/features/moderation/components/index.ts
deleted file mode 100644
index 4884d20bb..000000000
--- a/src/app/features/moderation/components/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export { AddModeratorDialogComponent } from './add-moderator-dialog/add-moderator-dialog.component';
-export { BulkUploadComponent } from './bulk-upload/bulk-upload.component';
-export { CollectionModerationSubmissionsComponent } from './collection-moderation-submissions/collection-moderation-submissions.component';
-export { InviteModeratorDialogComponent } from './invite-moderator-dialog/invite-moderator-dialog.component';
-export { ModeratorsListComponent } from './moderators-list/moderators-list.component';
-export { ModeratorsTableComponent } from './moderators-table/moderators-table.component';
-export { MyReviewingNavigationComponent } from './my-reviewing-navigation/my-reviewing-navigation.component';
-export { NotificationSettingsComponent } from './notification-settings/notification-settings.component';
-export { PreprintModerationSettingsComponent } from './preprint-moderation-settings/preprint-moderation-settings.component';
-export { PreprintRecentActivityListComponent } from './preprint-recent-activity-list/preprint-recent-activity-list.component';
-export { PreprintSubmissionItemComponent } from './preprint-submission-item/preprint-submission-item.component';
-export { PreprintSubmissionsComponent } from './preprint-submissions/preprint-submissions.component';
-export { PreprintWithdrawalSubmissionsComponent } from './preprint-withdrawal-submissions/preprint-withdrawal-submissions.component';
-export { RegistryPendingSubmissionsComponent } from './registry-pending-submissions/registry-pending-submissions.component';
-export { RegistrySettingsComponent } from './registry-settings/registry-settings.component';
-export { RegistrySubmissionItemComponent } from './registry-submission-item/registry-submission-item.component';
-export { RegistrySubmissionsComponent } from './registry-submissions/registry-submissions.component';
-export { CollectionSubmissionItemComponent } from '@osf/features/moderation/components/collection-submission-item/collection-submission-item.component';
-export { CollectionSubmissionsListComponent } from '@osf/features/moderation/components/collection-submissions-list/collection-submissions-list.component';
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 fe6f3d8ea..7d054a615 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
@@ -7,39 +7,30 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormSelectComponent } from '@osf/shared/components/form-select/form-select.component';
import { TextInputComponent } from '@osf/shared/components/text-input/text-input.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
+
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;
+ let mockDialogRef: DynamicDialogRef;
- beforeEach(async () => {
- mockDialogRef = DynamicDialogRefMock.useValue as unknown as jest.Mocked;
-
- await TestBed.configureTestingModule({
- imports: [
- InviteModeratorDialogComponent,
- OSFTestingModule,
- ...MockComponents(TextInputComponent, FormSelectComponent),
- ],
- providers: [DynamicDialogRefMock],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [InviteModeratorDialogComponent, ...MockComponents(TextInputComponent, FormSelectComponent)],
+ providers: [provideOSFCore(), provideDynamicDialogRefMock()],
+ });
fixture = TestBed.createComponent(InviteModeratorDialogComponent);
component = fixture.componentInstance;
+ mockDialogRef = TestBed.inject(DynamicDialogRef);
fixture.detectChanges();
});
- afterEach(() => {
- jest.clearAllMocks();
- });
-
it('should create', () => {
expect(component).toBeTruthy();
});
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 f19023271..a2551f57e 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
@@ -11,23 +11,23 @@ import { SearchInputComponent } from '@osf/shared/components/search-input/search
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
-
-import { ModeratorPermission } from '../../enums';
-import { ModeratorModel } from '../../models';
-import { ModeratorsSelectors } from '../../store/moderators';
-import { ModeratorsTableComponent } from '../moderators-table/moderators-table.component';
-
-import { ModeratorsListComponent } from './moderators-list.component';
+import { ToastService } from '@osf/shared/services/toast.service';
import { MOCK_USER } from '@testing/mocks/data.mock';
import { MOCK_MODERATORS } from '@testing/mocks/moderator.mock';
-import { TranslateServiceMock } from '@testing/mocks/translate.service.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomConfirmationServiceMockBuilder } from '@testing/providers/custom-confirmation-provider.mock';
import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { ModeratorPermission } from '../../enums';
+import { ModeratorModel } from '../../models';
+import { ModeratorsSelectors } from '../../store/moderators';
+import { ModeratorsTableComponent } from '../moderators-table/moderators-table.component';
+
+import { ModeratorsListComponent } from './moderators-list.component';
+
describe('ModeratorsListComponent', () => {
let component: ModeratorsListComponent;
let fixture: ComponentFixture;
@@ -41,7 +41,7 @@ describe('ModeratorsListComponent', () => {
const mockModerators: ModeratorModel[] = MOCK_MODERATORS;
- beforeEach(async () => {
+ beforeEach(() => {
mockActivatedRoute = ActivatedRouteMockBuilder.create()
.withParams({ providerId: mockProviderId })
.withData({ resourceType: mockResourceType })
@@ -49,17 +49,14 @@ describe('ModeratorsListComponent', () => {
customConfirmationServiceMock = CustomConfirmationServiceMockBuilder.create().build();
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [
- ModeratorsListComponent,
- OSFTestingModule,
- ...MockComponents(ModeratorsTableComponent, SearchInputComponent),
- ],
+ TestBed.configureTestingModule({
+ imports: [ModeratorsListComponent, ...MockComponents(ModeratorsTableComponent, SearchInputComponent)],
providers: [
+ provideOSFCore(),
MockProvider(ActivatedRoute, mockActivatedRoute),
MockProvider(CustomConfirmationService, customConfirmationServiceMock),
MockProvider(CustomDialogService, mockCustomDialogService),
- TranslateServiceMock,
+ MockProvider(ToastService),
provideMockStore({
signals: [
{ selector: UserSelectors.getCurrentUser, value: mockCurrentUser },
@@ -69,7 +66,7 @@ describe('ModeratorsListComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(ModeratorsListComponent);
component = fixture.componentInstance;
@@ -119,7 +116,7 @@ describe('ModeratorsListComponent', () => {
});
it('should load moderators on initialization', () => {
- const loadModeratorsSpy = jest.fn();
+ const loadModeratorsSpy = vi.fn();
component.actions = {
...component.actions,
loadModerators: loadModeratorsSpy,
@@ -131,7 +128,7 @@ describe('ModeratorsListComponent', () => {
});
it('should set search subscription on initialization', () => {
- const setSearchSubscriptionSpy = jest.fn();
+ const setSearchSubscriptionSpy = vi.fn();
(component as any).setSearchSubscription = setSearchSubscriptionSpy;
component.ngOnInit();
@@ -140,10 +137,10 @@ describe('ModeratorsListComponent', () => {
});
it('should handle search control value changes', () => {
- jest.useFakeTimers();
+ vi.useFakeTimers();
fixture.detectChanges();
- const updateSearchValueSpy = jest.fn();
- const loadModeratorsSpy = jest.fn().mockReturnValue(of({}));
+ const updateSearchValueSpy = vi.fn();
+ const loadModeratorsSpy = vi.fn().mockReturnValue(of({}));
component.actions = {
...component.actions,
updateSearchValue: updateSearchValueSpy,
@@ -152,19 +149,19 @@ describe('ModeratorsListComponent', () => {
component.searchControl.setValue('test search');
- jest.advanceTimersByTime(600);
+ vi.advanceTimersByTime(600);
expect(updateSearchValueSpy).toHaveBeenCalledWith('test search');
expect(loadModeratorsSpy).toHaveBeenCalledWith(mockProviderId, mockResourceType);
- jest.useRealTimers();
+ vi.useRealTimers();
});
it('should handle empty search value', () => {
- jest.useFakeTimers();
+ vi.useFakeTimers();
fixture.detectChanges();
- const updateSearchValueSpy = jest.fn();
- const loadModeratorsSpy = jest.fn().mockReturnValue(of({}));
+ const updateSearchValueSpy = vi.fn();
+ const loadModeratorsSpy = vi.fn().mockReturnValue(of({}));
component.actions = {
...component.actions,
updateSearchValue: updateSearchValueSpy,
@@ -173,12 +170,12 @@ describe('ModeratorsListComponent', () => {
component.searchControl.setValue('');
- jest.advanceTimersByTime(600);
+ vi.advanceTimersByTime(600);
expect(updateSearchValueSpy).toHaveBeenCalledWith(null);
expect(loadModeratorsSpy).toHaveBeenCalledWith(mockProviderId, mockResourceType);
- jest.useRealTimers();
+ vi.useRealTimers();
});
it('should have actions defined', () => {
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 0c8fac6ce..af142c0e0 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,19 +1,20 @@
import { MockComponent, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
import { SelectComponent } from '@osf/shared/components/select/select.component';
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
import { TableParameters } from '@shared/models/table-parameters.model';
+import { MOCK_MODERATORS } from '@testing/mocks/moderator.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
+
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';
-import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
-
describe('ModeratorsTableComponent', () => {
let component: ModeratorsTableComponent;
let fixture: ComponentFixture;
@@ -31,13 +32,13 @@ describe('ModeratorsTableComponent', () => {
defaultSortColumn: null,
};
- beforeEach(async () => {
+ beforeEach(() => {
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
- await TestBed.configureTestingModule({
- imports: [ModeratorsTableComponent, OSFTestingModule, MockComponent(SelectComponent)],
- providers: [MockProvider(CustomDialogService, mockCustomDialogService)],
- }).compileComponents();
+ TestBed.configureTestingModule({
+ imports: [ModeratorsTableComponent, MockComponent(SelectComponent)],
+ providers: [provideOSFCore(), provideRouter([]), MockProvider(CustomDialogService, mockCustomDialogService)],
+ });
fixture = TestBed.createComponent(ModeratorsTableComponent);
component = fixture.componentInstance;
@@ -69,7 +70,7 @@ describe('ModeratorsTableComponent', () => {
});
it('should emit update event when updatePermission is called', () => {
- jest.spyOn(component.update, 'emit');
+ vi.spyOn(component.update, 'emit');
const moderator = mockModerators[0];
component.updatePermission(moderator);
@@ -78,7 +79,7 @@ describe('ModeratorsTableComponent', () => {
});
it('should emit remove event when removeModerator is called', () => {
- jest.spyOn(component.remove, 'emit');
+ vi.spyOn(component.remove, 'emit');
const moderator = mockModerators[0];
component.removeModerator(moderator);
@@ -98,7 +99,7 @@ describe('ModeratorsTableComponent', () => {
it('should open education history dialog', () => {
const moderator = mockModerators[0];
- jest.spyOn(component.customDialogService, 'open');
+ vi.spyOn(component.customDialogService, 'open');
component.openEducationHistory(moderator);
@@ -107,7 +108,7 @@ describe('ModeratorsTableComponent', () => {
it('should open employment history dialog', () => {
const moderator = mockModerators[0];
- jest.spyOn(component.customDialogService, 'open');
+ vi.spyOn(component.customDialogService, 'open');
component.openEmploymentHistory(moderator);
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 785c5a7a4..43705fddf 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,22 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
+
+import { MOCK_PROVIDER } from '@testing/mocks/provider.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { PreprintModerationTab } from '../../enums';
import { MyReviewingNavigationComponent } from './my-reviewing-navigation.component';
-import { MOCK_PROVIDER } from '@testing/mocks/provider.mock';
-import { OSFTestingModule } from '@testing/osf.testing.module';
-
describe('MyReviewingNavigationComponent', () => {
let component: MyReviewingNavigationComponent;
let fixture: ComponentFixture;
const mockProvider = MOCK_PROVIDER;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [MyReviewingNavigationComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MyReviewingNavigationComponent],
+ providers: [provideOSFCore(), provideRouter([])],
+ });
fixture = TestBed.createComponent(MyReviewingNavigationComponent);
component = fixture.componentInstance;
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 a5411f8d7..074b2bbac 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,17 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
-import { NotificationSettingsComponent } from './notification-settings.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { NotificationSettingsComponent } from './notification-settings.component';
describe('NotificationSettingsComponent', () => {
let component: NotificationSettingsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [NotificationSettingsComponent, OSFTestingModule],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [NotificationSettingsComponent],
+ providers: [provideOSFCore(), provideRouter([])],
+ });
fixture = TestBed.createComponent(NotificationSettingsComponent);
component = fixture.componentInstance;
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 155d6bf3e..1a24d0ede 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
@@ -5,18 +5,17 @@ import { ActivatedRoute } from '@angular/router';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
+import { MOCK_PREPRINT_PROVIDER_MODERATION_INFO } from '@testing/mocks/preprint-provider-moderation-info.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { SettingsSectionControl } from '../../enums';
import { PreprintProviderModerationInfo } from '../../models';
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';
-
describe('PreprintModerationSettingsComponent', () => {
let component: PreprintModerationSettingsComponent;
let fixture: ComponentFixture;
@@ -25,14 +24,14 @@ describe('PreprintModerationSettingsComponent', () => {
const mockProviderId = 'test-provider-id';
const mockSettings: PreprintProviderModerationInfo = MOCK_PREPRINT_PROVIDER_MODERATION_INFO;
- beforeEach(async () => {
+ beforeEach(() => {
mockActivatedRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: mockProviderId }).build();
- await TestBed.configureTestingModule({
- imports: [PreprintModerationSettingsComponent, OSFTestingModule, MockComponent(LoadingSpinnerComponent)],
+ TestBed.configureTestingModule({
+ imports: [PreprintModerationSettingsComponent, MockComponent(LoadingSpinnerComponent)],
providers: [
+ provideOSFCore(),
MockProvider(ActivatedRoute, mockActivatedRoute),
- EnvironmentTokenMock,
provideMockStore({
signals: [
{ selector: PreprintModerationSelectors.arePreprintProviderLoading, value: false },
@@ -41,7 +40,7 @@ describe('PreprintModerationSettingsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(PreprintModerationSettingsComponent);
component = fixture.componentInstance;
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 b1f8bf48e..455bd275d 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
@@ -5,27 +5,24 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
+import { MOCK_PREPRINT_REVIEW_ACTIONS } from '@testing/mocks/preprint-review-action.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
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[] = MOCK_PREPRINT_REVIEW_ACTIONS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [
- PreprintRecentActivityListComponent,
- OSFTestingModule,
- ...MockComponents(IconComponent, CustomPaginatorComponent),
- ],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [PreprintRecentActivityListComponent, ...MockComponents(IconComponent, CustomPaginatorComponent)],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(PreprintRecentActivityListComponent);
component = fixture.componentInstance;
@@ -55,23 +52,50 @@ describe('PreprintRecentActivityListComponent', () => {
});
it('should emit page change event', () => {
- jest.spyOn(component.pageChanged, 'emit');
+ vi.spyOn(component.pageChanged, 'emit');
const mockEvent = { page: 2, first: 10, rows: 10 };
component.onPageChange(mockEvent);
- expect(component.pageChanged.emit).toHaveBeenCalledWith(2);
+ expect(component.first()).toBe(10);
+ expect(component.rows()).toBe(10);
+ expect(component.pageChanged.emit).toHaveBeenCalledWith(3);
});
- it('should emit page 1 when page is undefined', () => {
- jest.spyOn(component.pageChanged, 'emit');
+ it('should not emit page change when page is undefined', () => {
+ vi.spyOn(component.pageChanged, 'emit');
const mockEvent = { page: undefined, first: 0, rows: 10 };
component.onPageChange(mockEvent);
+ expect(component.first()).toBe(0);
+ expect(component.rows()).toBe(10);
+ expect(component.pageChanged.emit).not.toHaveBeenCalled();
+ });
+
+ it('should fallback first to 0 when event.first is undefined', () => {
+ vi.spyOn(component.pageChanged, 'emit');
+ const mockEvent = { page: 0, first: undefined, rows: 10 };
+
+ component.onPageChange(mockEvent);
+
+ expect(component.first()).toBe(0);
+ expect(component.rows()).toBe(10);
expect(component.pageChanged.emit).toHaveBeenCalledWith(1);
});
+ it('should keep current rows when event.rows is undefined', () => {
+ vi.spyOn(component.pageChanged, 'emit');
+ component.rows.set(25);
+ const mockEvent = { page: 1, first: 25, rows: undefined };
+
+ component.onPageChange(mockEvent);
+
+ expect(component.first()).toBe(25);
+ expect(component.rows()).toBe(25);
+ expect(component.pageChanged.emit).toHaveBeenCalledWith(2);
+ });
+
it('should accept custom input values', () => {
const customReviews = [
...mockReviews,
diff --git a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts
index abd51ec05..14bb3f030 100644
--- a/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts
+++ b/src/app/features/moderation/components/preprint-recent-activity-list/preprint-recent-activity-list.component.ts
@@ -2,7 +2,6 @@ import { TranslatePipe } from '@ngx-translate/core';
import { PaginatorState } from 'primeng/paginator';
import { Skeleton } from 'primeng/skeleton';
-import { TableModule } from 'primeng/table';
import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core';
@@ -15,7 +14,7 @@ import { PreprintReviewActionModel } from '../../models';
@Component({
selector: 'osf-preprint-recent-activity-list',
- imports: [TableModule, DatePipe, TranslatePipe, IconComponent, Skeleton, CustomPaginatorComponent],
+ imports: [Skeleton, DatePipe, TranslatePipe, IconComponent, CustomPaginatorComponent],
templateUrl: './preprint-recent-activity-list.component.html',
styleUrl: './preprint-recent-activity-list.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -29,10 +28,18 @@ export class PreprintRecentActivityListComponent {
first = signal(0);
rows = signal(10);
+
readonly reviewStatusIcon = ReviewStatusIcon;
readonly preprintReviewStatus = PreprintReviewStatus;
onPageChange(event: PaginatorState) {
- this.pageChanged.emit(event.page ?? 1);
+ this.first.set(event.first ?? 0);
+ this.rows.set(event.rows ?? this.rows());
+
+ if (event.page === undefined) {
+ return;
+ }
+
+ this.pageChanged.emit(event.page + 1);
}
}
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 d5dd8258b..8b577a58f 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,5 +1,4 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockComponents, MockPipes } from 'ng-mocks';
+import { MockComponents, MockPipe } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@@ -7,29 +6,29 @@ import { ContributorsListComponent } from '@osf/shared/components/contributors-l
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { DateAgoPipe } from '@osf/shared/pipes/date-ago.pipe';
+import { MOCK_PREPRINT_SUBMISSION } from '@testing/mocks/submission.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { SubmissionReviewStatus } from '../../enums';
import { PreprintSubmissionModel } 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: PreprintSubmissionModel = MOCK_PREPRINT_SUBMISSION;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [
PreprintSubmissionItemComponent,
- OSFTestingModule,
...MockComponents(IconComponent, ContributorsListComponent),
- MockPipes(DateAgoPipe, TranslatePipe),
+ MockPipe(DateAgoPipe),
],
- }).compileComponents();
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(PreprintSubmissionItemComponent);
component = fixture.componentInstance;
@@ -69,7 +68,7 @@ describe('PreprintSubmissionItemComponent', () => {
});
it('should emit selected event', () => {
- jest.spyOn(component.selected, 'emit');
+ vi.spyOn(component.selected, 'emit');
component.selected.emit();
expect(component.selected.emit).toHaveBeenCalled();
});
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 647cf91e8..08cbb1f1a 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
@@ -10,6 +10,12 @@ import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SelectComponent } from '@osf/shared/components/select/select.component';
+import { MOCK_PREPRINT_SUBMISSIONS } from '@testing/mocks/preprint-submission.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums';
import { PreprintSubmissionModel } from '../../models';
import {
@@ -18,13 +24,8 @@ import {
PreprintModerationSelectors,
} from '../../store/preprint-moderation';
import { PreprintSubmissionItemComponent } from '../preprint-submission-item/preprint-submission-item.component';
-import { PreprintSubmissionsComponent } from '..';
-import { MOCK_PREPRINT_SUBMISSIONS } from '@testing/mocks/preprint-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';
+import { PreprintSubmissionsComponent } from './preprint-submissions.component';
describe('PreprintSubmissionsComponent', () => {
let component: PreprintSubmissionsComponent;
@@ -36,17 +37,16 @@ describe('PreprintSubmissionsComponent', () => {
const mockProviderId = 'test-provider-id';
const mockSubmissions: PreprintSubmissionModel[] = MOCK_PREPRINT_SUBMISSIONS;
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create()
.withParams({ providerId: mockProviderId })
.withQueryParams({ status: 'pending' })
.build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
PreprintSubmissionsComponent,
- OSFTestingModule,
...MockComponents(
SelectComponent,
IconComponent,
@@ -56,6 +56,7 @@ describe('PreprintSubmissionsComponent', () => {
),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
@@ -69,7 +70,7 @@ describe('PreprintSubmissionsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(PreprintSubmissionsComponent);
component = fixture.componentInstance;
@@ -156,7 +157,7 @@ describe('PreprintSubmissionsComponent', () => {
it('should load contributors for a submission', () => {
const mockItem = mockSubmissions[0];
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ const dispatchSpy = vi.spyOn(store, 'dispatch');
component.loadContributors(mockItem);
@@ -165,7 +166,7 @@ describe('PreprintSubmissionsComponent', () => {
it('should load more contributors for a submission', () => {
const mockItem = mockSubmissions[0];
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ const dispatchSpy = vi.spyOn(store, 'dispatch');
component.loadMoreContributors(mockItem);
diff --git a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.ts b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.ts
index af97cb132..b62f78a23 100644
--- a/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.ts
+++ b/src/app/features/moderation/components/preprint-submissions/preprint-submissions.component.ts
@@ -13,7 +13,6 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
-import { PreprintSubmissionItemComponent } from '@osf/features/moderation/components';
import { PREPRINT_SORT_OPTIONS, SUBMISSION_REVIEW_OPTIONS } from '@osf/features/moderation/constants';
import { PreprintSubmissionsSort, SubmissionReviewStatus } from '@osf/features/moderation/enums';
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
@@ -29,6 +28,7 @@ import {
LoadMorePreprintSubmissionContributors,
PreprintModerationSelectors,
} from '../../store/preprint-moderation';
+import { PreprintSubmissionItemComponent } from '../preprint-submission-item/preprint-submission-item.component';
@Component({
selector: 'osf-preprint-submissions',
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 97a2e3707..0c64302e6 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
@@ -10,6 +10,12 @@ import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SelectComponent } from '@osf/shared/components/select/select.component';
+import { MOCK_PREPRINT_WITHDRAWAL_SUBMISSIONS } from '@testing/mocks/preprint-withdrawal-submission.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { PreprintSubmissionsSort, SubmissionReviewStatus } from '../../enums';
import { PreprintWithdrawalSubmission } from '../../models';
import {
@@ -21,12 +27,6 @@ import { PreprintSubmissionItemComponent } from '../preprint-submission-item/pre
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';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('PreprintWithdrawalSubmissionsComponent', () => {
let component: PreprintWithdrawalSubmissionsComponent;
let fixture: ComponentFixture;
@@ -37,12 +37,12 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
const mockProviderId = 'test-provider-id';
const mockSubmissions: PreprintWithdrawalSubmission[] = MOCK_PREPRINT_WITHDRAWAL_SUBMISSIONS;
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
- mockRouter.serializeUrl = jest.fn(
+ mockRouter.serializeUrl = vi.fn(
(urlTree: any) => `/preprints/${mockProviderId}/${urlTree.segments?.[2] || 'test-id'}?mode=moderator`
);
- mockRouter.createUrlTree = jest.fn(
+ mockRouter.createUrlTree = vi.fn(
(commands: any[], extras?: any) =>
({
segments: commands,
@@ -55,10 +55,9 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
.withQueryParams({ status: 'pending' })
.build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
PreprintWithdrawalSubmissionsComponent,
- OSFTestingModule,
...MockComponents(
SelectComponent,
IconComponent,
@@ -68,6 +67,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
@@ -80,7 +80,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(PreprintWithdrawalSubmissionsComponent);
component = fixture.componentInstance;
@@ -153,7 +153,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
it('should navigate to preprint', () => {
const mockItem = mockSubmissions[0];
- const windowOpenSpy = jest.spyOn(window, 'open').mockImplementation(() => null);
+ const windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
component.navigateToPreprint(mockItem);
@@ -178,7 +178,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
it('should load contributors for a withdrawal submission', () => {
const mockItem = mockSubmissions[0];
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ const dispatchSpy = vi.spyOn(store, 'dispatch');
component.loadContributors(mockItem);
@@ -189,7 +189,7 @@ describe('PreprintWithdrawalSubmissionsComponent', () => {
it('should load more contributors for a withdrawal submission', () => {
const mockItem = mockSubmissions[0];
- const dispatchSpy = jest.spyOn(store, 'dispatch');
+ const dispatchSpy = vi.spyOn(store, 'dispatch');
component.loadMoreContributors(mockItem);
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 666cc14db..9ae0cc0ad 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,5 +1,4 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockComponents, MockPipe, MockProvider } from 'ng-mocks';
+import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
@@ -10,18 +9,18 @@ import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SelectComponent } from '@osf/shared/components/select/select.component';
+import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { RegistrySort, SubmissionReviewStatus } from '../../enums';
import { RegistryModerationSelectors } from '../../store/registry-moderation';
import { RegistrySubmissionItemComponent } from '../registry-submission-item/registry-submission-item.component';
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';
-import { provideMockStore } from '@testing/providers/store-provider.mock';
-
describe('RegistryPendingSubmissionsComponent', () => {
let component: RegistryPendingSubmissionsComponent;
let fixture: ComponentFixture;
@@ -31,17 +30,16 @@ describe('RegistryPendingSubmissionsComponent', () => {
const mockProviderId = 'test-provider-id';
const mockSubmissions: RegistryModeration[] = MOCK_REGISTRY_MODERATIONS;
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create()
.withParams({ providerId: mockProviderId })
.withQueryParams({ status: 'pending' })
.build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
RegistryPendingSubmissionsComponent,
- OSFTestingModule,
...MockComponents(
SelectComponent,
IconComponent,
@@ -49,9 +47,9 @@ describe('RegistryPendingSubmissionsComponent', () => {
RegistrySubmissionItemComponent,
CustomPaginatorComponent
),
- MockPipe(TranslatePipe),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
@@ -62,7 +60,7 @@ describe('RegistryPendingSubmissionsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(RegistryPendingSubmissionsComponent);
component = fixture.componentInstance;
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 dd06d8fdc..5b855005e 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,20 +1,19 @@
-import { TranslatePipe } from '@ngx-translate/core';
-import { MockPipe } from 'ng-mocks';
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
-import { RegistrySettingsComponent } from './registry-settings.component';
+import { provideOSFCore } from '@testing/osf.testing.provider';
-import { OSFTestingModule } from '@testing/osf.testing.module';
+import { RegistrySettingsComponent } from './registry-settings.component';
describe('RegistrySettingsComponent', () => {
let component: RegistrySettingsComponent;
let fixture: ComponentFixture;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [RegistrySettingsComponent, OSFTestingModule, MockPipe(TranslatePipe)],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [RegistrySettingsComponent],
+ providers: [provideOSFCore(), provideRouter([])],
+ });
fixture = TestBed.createComponent(RegistrySettingsComponent);
component = fixture.componentInstance;
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 a4c08224b..994383c12 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,4 +1,3 @@
-import { TranslatePipe } from '@ngx-translate/core';
import { MockComponents, MockPipe } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
@@ -6,30 +5,25 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { DateAgoPipe } from '@osf/shared/pipes/date-ago.pipe';
+import { MOCK_REGISTRY_MODERATIONS } from '@testing/mocks/registry-moderation.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
import { SubmissionReviewStatus } from '../../enums';
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 = MOCK_REGISTRY_MODERATIONS[0];
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [
- RegistrySubmissionItemComponent,
- OSFTestingModule,
- ...MockComponents(IconComponent),
- MockPipe(DateAgoPipe),
- MockPipe(TranslatePipe),
- ],
- }).compileComponents();
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [RegistrySubmissionItemComponent, ...MockComponents(IconComponent), MockPipe(DateAgoPipe)],
+ providers: [provideOSFCore()],
+ });
fixture = TestBed.createComponent(RegistrySubmissionItemComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts
index 93b331b09..4a6bb0be3 100644
--- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts
+++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts
@@ -7,10 +7,10 @@ import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
+import { FunderAwardsListComponent } from '@osf/shared/components/funder-awards-list/funder-awards-list.component';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { TruncatedTextComponent } from '@osf/shared/components/truncated-text/truncated-text.component';
import { DateAgoPipe } from '@osf/shared/pipes/date-ago.pipe';
-import { FunderAwardsListComponent } from '@shared/funder-awards-list/funder-awards-list.component';
import { REGISTRY_ACTION_LABEL, ReviewStatusIcon } from '../../constants';
import { ActionStatus, SubmissionReviewStatus } from '../../enums';
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 9bb0297db..0cddde95f 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
@@ -3,24 +3,24 @@ import { MockComponents, MockProvider } from 'ng-mocks';
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 } from '@osf/shared/components/custom-paginator/custom-paginator.component';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
import { SelectComponent } from '@osf/shared/components/select/select.component';
-import { RegistrySort, SubmissionReviewStatus } from '../../enums';
-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 { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { RegistrySort, SubmissionReviewStatus } from '../../enums';
+import { RegistryModerationSelectors } from '../../store/registry-moderation';
+import { RegistrySubmissionItemComponent } from '../registry-submission-item/registry-submission-item.component';
+
+import { RegistrySubmissionsComponent } from './registry-submissions.component';
+
describe('RegistrySubmissionsComponent', () => {
let component: RegistrySubmissionsComponent;
let fixture: ComponentFixture;
@@ -30,17 +30,16 @@ describe('RegistrySubmissionsComponent', () => {
const mockProviderId = 'test-provider-id';
const mockSubmissions: RegistryModeration[] = MOCK_REGISTRY_MODERATIONS;
- beforeEach(async () => {
+ beforeEach(() => {
mockRouter = RouterMockBuilder.create().build();
mockActivatedRoute = ActivatedRouteMockBuilder.create()
.withParams({ providerId: mockProviderId })
.withQueryParams({ status: 'pending' })
.build();
- await TestBed.configureTestingModule({
+ TestBed.configureTestingModule({
imports: [
RegistrySubmissionsComponent,
- OSFTestingModule,
...MockComponents(
SelectComponent,
IconComponent,
@@ -50,6 +49,7 @@ describe('RegistrySubmissionsComponent', () => {
),
],
providers: [
+ provideOSFCore(),
MockProvider(Router, mockRouter),
MockProvider(ActivatedRoute, mockActivatedRoute),
provideMockStore({
@@ -60,7 +60,7 @@ describe('RegistrySubmissionsComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(RegistrySubmissionsComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts
index 3271d565f..64ed44bc7 100644
--- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts
+++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts
@@ -31,7 +31,7 @@ import {
LoadMoreRegistrySubmissionContributors,
RegistryModerationSelectors,
} from '../../store/registry-moderation';
-import { RegistrySubmissionItemComponent } from '..';
+import { RegistrySubmissionItemComponent } from '../registry-submission-item/registry-submission-item.component';
@Component({
selector: 'osf-registry-submissions',
diff --git a/src/app/features/moderation/mappers/preprint-moderation.mapper.ts b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts
index 5fb0cc752..621d929c1 100644
--- a/src/app/features/moderation/mappers/preprint-moderation.mapper.ts
+++ b/src/app/features/moderation/mappers/preprint-moderation.mapper.ts
@@ -28,8 +28,8 @@ export class PreprintModerationMapper {
name: creator?.fullName || '',
},
preprint: {
- id: response.embeds.target.data.id,
- name: response.embeds.target.data.attributes.title,
+ id: response.embeds.target?.data?.id || '',
+ name: response.embeds.target?.data?.attributes?.title || '',
},
provider: {
id: response.embeds.provider.data.id,
diff --git a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts
index 7755fc5f6..4c481516b 100644
--- a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts
+++ b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.spec.ts
@@ -1,6 +1,10 @@
+import { Store } from '@ngxs/store';
+
import { MockComponents, MockProvider } from 'ng-mocks';
-import { BehaviorSubject } from 'rxjs';
+import { of } from 'rxjs';
+
+import { Mock } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
@@ -8,189 +12,112 @@ import { ActivatedRoute, Router } from '@angular/router';
import { SelectComponent } from '@osf/shared/components/select/select.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
import { IS_MEDIUM } from '@osf/shared/helpers/breakpoints.tokens';
+import { GetCollectionProvider } from '@osf/shared/stores/collections';
+
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
import { CollectionModerationTab } from '../../enums';
import { CollectionModerationComponent } from './collection-moderation.component';
-import { OSFTestingStoreModule } from '@testing/osf.testing.module';
-import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
-import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
-
-describe('Component: Collection Moderation', () => {
+describe('CollectionModerationComponent', () => {
let component: CollectionModerationComponent;
let fixture: ComponentFixture;
- let isMediumSubject: BehaviorSubject;
- let mockRouter: ReturnType;
- let mockActivatedRoute: ReturnType;
-
- beforeEach(async () => {
- isMediumSubject = new BehaviorSubject(true);
- mockRouter = RouterMockBuilder.create().build();
- mockActivatedRoute = ActivatedRouteMockBuilder.create()
- .withParams({ providerId: 'test-provider-id' })
- .withData({ tab: CollectionModerationTab.AllItems })
- .build();
-
- await TestBed.configureTestingModule({
- imports: [
- CollectionModerationComponent,
- OSFTestingStoreModule,
- ...MockComponents(SubHeaderComponent, SelectComponent),
- ],
- providers: [
- MockProvider(ActivatedRoute, mockActivatedRoute),
- MockProvider(Router, mockRouter),
- MockProvider(IS_MEDIUM, isMediumSubject),
- ],
- }).compileComponents();
-
- fixture = TestBed.createComponent(CollectionModerationComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
+ let store: Store;
+ let dispatchMock: Mock;
+ let mockRouter: RouterMockType;
- it('should initialize with default values', () => {
- expect(component.selectedTab).toBeUndefined();
- expect(component.tabOptions).toBeDefined();
- expect(component.isMedium).toBeDefined();
- });
+ interface SetupOptions {
+ providerId?: string;
+ tab?: CollectionModerationTab;
+ }
- it('should initialize selected tab from route data', async () => {
- const mockFirstChild = {
- data: { tab: CollectionModerationTab.Moderators },
- };
+ function setup(options: SetupOptions = {}) {
+ const { providerId = 'provider-1', tab = CollectionModerationTab.AllItems } = options;
+ const routeBuilder = ActivatedRouteMockBuilder.create().withFirstChild((child) => child.withData({ tab }));
- const routeWithFirstChild = ActivatedRouteMockBuilder.create()
- .withParams({ providerId: 'test-provider-id' })
- .build();
+ if (providerId) {
+ routeBuilder.withParams({ providerId });
+ }
- Object.defineProperty(routeWithFirstChild.snapshot, 'firstChild', {
- value: mockFirstChild,
- writable: true,
- });
+ const route = routeBuilder.build() as Partial;
+ mockRouter = RouterMockBuilder.create().withUrl('/collections/provider-1/moderation/all-items').build();
- await TestBed.configureTestingModule({
- imports: [
- CollectionModerationComponent,
- OSFTestingStoreModule,
- ...MockComponents(SubHeaderComponent, SelectComponent),
- ],
+ TestBed.configureTestingModule({
+ imports: [CollectionModerationComponent, ...MockComponents(SubHeaderComponent, SelectComponent)],
providers: [
- MockProvider(ActivatedRoute, routeWithFirstChild),
+ provideOSFCore(),
+ provideMockStore(),
+ MockProvider(ActivatedRoute, route),
MockProvider(Router, mockRouter),
- MockProvider(IS_MEDIUM, isMediumSubject),
+ MockProvider(IS_MEDIUM, of(true)),
],
- }).compileComponents();
+ });
- const testFixture = TestBed.createComponent(CollectionModerationComponent);
- const testComponent = testFixture.componentInstance;
+ fixture = TestBed.createComponent(CollectionModerationComponent);
+ component = fixture.componentInstance;
+ store = TestBed.inject(Store);
+ dispatchMock = store.dispatch as Mock;
+ }
- testComponent.ngOnInit();
+ it('should create', () => {
+ setup();
- expect(testComponent.selectedTab).toBe(CollectionModerationTab.Moderators);
+ expect(component).toBeTruthy();
});
- it('should navigate to not-found when providerId is missing', async () => {
- const routeWithoutProviderId = ActivatedRouteMockBuilder.create().withParams({}).build();
-
- await TestBed.configureTestingModule({
- imports: [
- CollectionModerationComponent,
- OSFTestingStoreModule,
- ...MockComponents(SubHeaderComponent, SelectComponent),
- ],
- providers: [
- MockProvider(ActivatedRoute, routeWithoutProviderId),
- MockProvider(Router, mockRouter),
- MockProvider(IS_MEDIUM, isMediumSubject),
- ],
- }).compileComponents();
-
- const testFixture = TestBed.createComponent(CollectionModerationComponent);
- const testComponent = testFixture.componentInstance;
+ it('should initialize selectedTab from route child data', () => {
+ setup({ tab: CollectionModerationTab.Moderators });
- testComponent.ngOnInit();
+ component.ngOnInit();
- expect(mockRouter.navigate).toHaveBeenCalledWith(['/not-found']);
+ expect(component.selectedTab).toBe(CollectionModerationTab.Moderators);
});
- it('should call getCollectionProvider action on init when providerId exists', () => {
- const getCollectionProviderSpy = jest.fn();
- component.actions = {
- ...component.actions,
- getCollectionProvider: getCollectionProviderSpy,
- };
+ it('should navigate to not-found when providerId is missing', () => {
+ setup({ providerId: '' });
+ dispatchMock.mockClear();
component.ngOnInit();
- expect(getCollectionProviderSpy).toHaveBeenCalledWith('test-provider-id');
+ expect(mockRouter.navigate).toHaveBeenCalledWith(['/not-found']);
+ expect(dispatchMock).not.toHaveBeenCalledWith(expect.any(GetCollectionProvider));
});
- it('should handle tab change and navigate to new tab', () => {
- const newTab = CollectionModerationTab.Moderators;
+ it('should dispatch GetCollectionProvider on init when providerId exists', () => {
+ setup({ providerId: 'provider-1' });
+ dispatchMock.mockClear();
- component.onTabChange(newTab);
+ component.ngOnInit();
- expect(component.selectedTab).toBe(newTab);
- expect(mockRouter.navigate).toHaveBeenCalledWith([newTab], { relativeTo: expect.any(Object) });
+ expect(dispatchMock).toHaveBeenCalledWith(new GetCollectionProvider('provider-1'));
});
- it('should call clearCurrentProvider on destroy', () => {
- const clearCurrentProviderSpy = jest.fn();
- component.actions = {
- ...component.actions,
- clearCurrentProvider: clearCurrentProviderSpy,
- };
+ it('should clear current provider on destroy', () => {
+ setup();
+ dispatchMock.mockClear();
component.ngOnDestroy();
- expect(clearCurrentProviderSpy).toHaveBeenCalled();
- });
-
- it('should have actions defined', () => {
- expect(component.actions).toBeDefined();
- expect(component.actions.getCollectionProvider).toBeDefined();
- expect(component.actions.clearCurrentProvider).toBeDefined();
- });
+ const actionTypes = dispatchMock.mock.calls.map((call) => {
+ const action = call[0] as { constructor: { name: string } };
+ return action.constructor.name;
+ });
- it('should have tab options defined', () => {
- expect(component.tabOptions).toBeDefined();
- expect(component.tabOptions.length).toBeGreaterThan(0);
+ expect(actionTypes).toContain('ClearCurrentProvider');
});
- it('should handle isMedium observable', () => {
- expect(component.isMedium()).toBe(true);
-
- isMediumSubject.next(false);
- fixture.detectChanges();
+ it('should update selectedTab and navigate on tab change', () => {
+ setup();
- expect(component.isMedium()).toBe(false);
- });
+ component.onTabChange(CollectionModerationTab.Settings);
- it('should handle tab change with different tab values', () => {
- const tabs = [
- CollectionModerationTab.AllItems,
- CollectionModerationTab.Moderators,
- CollectionModerationTab.Settings,
- ];
-
- tabs.forEach((tab) => {
- component.onTabChange(tab);
- expect(component.selectedTab).toBe(tab);
- expect(mockRouter.navigate).toHaveBeenCalledWith([tab], { relativeTo: expect.any(Object) });
+ expect(component.selectedTab).toBe(CollectionModerationTab.Settings);
+ expect(mockRouter.navigate).toHaveBeenCalledWith([CollectionModerationTab.Settings], {
+ relativeTo: component.route,
});
});
-
- it('should not navigate when providerId is present', () => {
- mockActivatedRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'valid-id' }).build();
-
- component.ngOnInit();
-
- expect(mockRouter.navigate).not.toHaveBeenCalledWith(['/not-found']);
- });
});
diff --git a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts
index cd84b78ef..1eea72cb6 100644
--- a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts
+++ b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.spec.ts
@@ -2,21 +2,19 @@ import { MockComponents } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import {
- MyReviewingNavigationComponent,
- PreprintRecentActivityListComponent,
-} from '@osf/features/moderation/components';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
-import { PreprintModerationSelectors } from '../../store/preprint-moderation';
-
-import { MyPreprintReviewingComponent } from './my-preprint-reviewing.component';
-
import { MOCK_PREPRINT_PROVIDER_MODERATION_INFO } from '@testing/mocks/preprint-provider-moderation-info.mock';
import { MOCK_PREPRINT_REVIEW_ACTIONS } from '@testing/mocks/preprint-review-action.mock';
-import { OSFTestingStoreModule } from '@testing/osf.testing.module';
+import { provideOSFCore } from '@testing/osf.testing.provider';
import { provideMockStore } from '@testing/providers/store-provider.mock';
+import { MyReviewingNavigationComponent } from '../../components/my-reviewing-navigation/my-reviewing-navigation.component';
+import { PreprintRecentActivityListComponent } from '../../components/preprint-recent-activity-list/preprint-recent-activity-list.component';
+import { PreprintModerationSelectors } from '../../store/preprint-moderation';
+
+import { MyPreprintReviewingComponent } from './my-preprint-reviewing.component';
+
describe('MyPreprintReviewingComponent', () => {
let component: MyPreprintReviewingComponent;
let fixture: ComponentFixture;
@@ -24,14 +22,14 @@ describe('MyPreprintReviewingComponent', () => {
const mockPreprintProviders = [MOCK_PREPRINT_PROVIDER_MODERATION_INFO];
const mockPreprintReviews = MOCK_PREPRINT_REVIEW_ACTIONS;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
+ beforeEach(() => {
+ TestBed.configureTestingModule({
imports: [
MyPreprintReviewingComponent,
- OSFTestingStoreModule,
...MockComponents(SubHeaderComponent, PreprintRecentActivityListComponent, MyReviewingNavigationComponent),
],
providers: [
+ provideOSFCore(),
provideMockStore({
signals: [
{ selector: PreprintModerationSelectors.getPreprintProviders, value: mockPreprintProviders },
@@ -42,7 +40,7 @@ describe('MyPreprintReviewingComponent', () => {
],
}),
],
- }).compileComponents();
+ });
fixture = TestBed.createComponent(MyPreprintReviewingComponent);
component = fixture.componentInstance;
diff --git a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts
index a80e3772d..9bf4a9a54 100644
--- a/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts
+++ b/src/app/features/moderation/pages/my-preprint-reviewing/my-preprint-reviewing.component.ts
@@ -9,7 +9,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
-import { MyReviewingNavigationComponent, PreprintRecentActivityListComponent } from '../../components';
+import { MyReviewingNavigationComponent } from '../../components/my-reviewing-navigation/my-reviewing-navigation.component';
+import { PreprintRecentActivityListComponent } from '../../components/preprint-recent-activity-list/preprint-recent-activity-list.component';
import {
GetPreprintProviders,
GetPreprintReviewActions,
diff --git a/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.spec.ts b/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.spec.ts
index a190edfc6..028455003 100644
--- a/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.spec.ts
+++ b/src/app/features/moderation/pages/preprint-moderation/preprint-moderation.component.spec.ts
@@ -1,184 +1,118 @@
+import { Store } from '@ngxs/store';
+
import { MockComponents, MockProvider } from 'ng-mocks';
-import { BehaviorSubject } from 'rxjs';
+import { of } from 'rxjs';
+
+import { Mock } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { SelectComponent } from '@osf/shared/components/select/select.component';
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
+import { ResourceType } from '@osf/shared/enums/resource-type.enum';
import { IS_MEDIUM } from '@osf/shared/helpers/breakpoints.tokens';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
import { PreprintModerationTab } from '../../enums';
+import { GetPreprintProvider } from '../../store/preprint-moderation';
import { PreprintModerationComponent } from './preprint-moderation.component';
-import { OSFTestingStoreModule } from '@testing/osf.testing.module';
-import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
-import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
-
describe('PreprintModerationComponent', () => {
let component: PreprintModerationComponent;
let fixture: ComponentFixture;
- let isMediumSubject: BehaviorSubject;
- let mockRouter: ReturnType;
- let mockActivatedRoute: ReturnType;
-
- beforeEach(async () => {
- isMediumSubject = new BehaviorSubject(true);
- mockRouter = RouterMockBuilder.create().build();
- mockActivatedRoute = ActivatedRouteMockBuilder.create()
- .withParams({ providerId: 'test-provider-id' })
- .withData({ tab: PreprintModerationTab.Submissions })
- .build();
-
- await TestBed.configureTestingModule({
- imports: [
- PreprintModerationComponent,
- OSFTestingStoreModule,
- ...MockComponents(SubHeaderComponent, SelectComponent),
- ],
- providers: [
- MockProvider(ActivatedRoute, mockActivatedRoute),
- MockProvider(Router, mockRouter),
- MockProvider(IS_MEDIUM, isMediumSubject),
- ],
- }).compileComponents();
-
- fixture = TestBed.createComponent(PreprintModerationComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
+ let store: Store;
+ let dispatchMock: Mock;
+ let mockRouter: RouterMockType;
- it('should create', () => {
- expect(component).toBeTruthy();
- });
+ interface SetupOptions {
+ providerId?: string;
+ tab?: PreprintModerationTab;
+ }
- it('should initialize with default values', () => {
- expect(component.resourceType).toBe(5);
- expect(component.selectedTab).toBeUndefined();
- expect(component.tabOptions).toBeDefined();
- expect(component.isMedium).toBeDefined();
- });
-
- it('should initialize selected tab from route data', async () => {
- const mockFirstChild = {
- data: { tab: PreprintModerationTab.Settings },
- };
+ function setup(options: SetupOptions = {}) {
+ const { providerId = 'provider-1', tab = PreprintModerationTab.Submissions } = options;
+ const routeBuilder = ActivatedRouteMockBuilder.create().withFirstChild((child) => child.withData({ tab }));
- const routeWithFirstChild = ActivatedRouteMockBuilder.create()
- .withParams({ providerId: 'test-provider-id' })
- .build();
+ if (providerId) {
+ routeBuilder.withParams({ providerId });
+ }
- Object.defineProperty(routeWithFirstChild.snapshot, 'firstChild', {
- value: mockFirstChild,
- writable: true,
- });
+ const route = routeBuilder.build() as Partial