Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ <h1 class="collections-heading flex align-items-center">{{ collectionProvider()?
</div>

<div class="content-container flex-1">
<osf-collections-main-content />
@if (useShtrovSearch) {
@if (defaultSearchFiltersInitialized()) {
<osf-global-search [searchControlInput]="searchControl" />
}
} @else {
<osf-collections-main-content />
}
</div>
</section>
} @else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,129 +1,261 @@
import { Store } from '@ngxs/store';

import { MockComponents, MockProvider } from 'ng-mocks';

import { Mock } from 'vitest';

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';

import { ENVIRONMENT } from '@core/provider/environment.provider';
import { GlobalSearchComponent } from '@osf/shared/components/global-search/global-search.component';
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 { SetDefaultFilterValue, SetExtraFilters } from '@shared/stores/global-search';

import { MOCK_PROVIDER } from '@testing/mocks/provider.mock';
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 { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';
import { ToastServiceMock } 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<CollectionsDiscoverComponent>;
let toastServiceMock: ToastServiceMockType;
let mockCustomDialogService: ReturnType<CustomDialogServiceMockBuilder['build']>;
let mockRoute: ReturnType<ActivatedRouteMockBuilder['build']>;

beforeEach(() => {
toastServiceMock = ToastServiceMock.simple();
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
mockRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'provider-1' }).build();

TestBed.configureTestingModule({
imports: [
CollectionsDiscoverComponent,
...MockComponents(SearchInputComponent, CollectionsMainContentComponent, LoadingSpinnerComponent),
],
providers: [
provideOSFCore(),
MockProvider(ToastService, toastServiceMock),
MockProvider(CustomDialogService, mockCustomDialogService),
MockProvider(ActivatedRoute, mockRoute),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getCollectionProvider, value: MOCK_PROVIDER },
{ selector: CollectionsSelectors.getCollectionDetails, value: null },
{ selector: CollectionsSelectors.getAllSelectedFilters, value: {} },
{ selector: CollectionsSelectors.getSortBy, value: 'date' },
{ selector: CollectionsSelectors.getSearchText, value: '' },
{ selector: CollectionsSelectors.getPageNumber, value: '1' },
{ selector: CollectionsSelectors.getCollectionProviderLoading, value: false },
],
}),
],
}).overrideComponent(CollectionsDiscoverComponent, {
set: {
providers: [MockProvider(CollectionsQuerySyncService)],
const MOCK_COLLECTION_PROVIDER = {
...MOCK_PROVIDER,
primaryCollection: { id: 'collection-1', type: 'collections' },
requiredMetadataTemplate: null,
};

const MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE = {
...MOCK_COLLECTION_PROVIDER,
requiredMetadataTemplate: {
id: 'template-1',
type: 'cedar-metadata-templates' as const,
attributes: {
schema_name: 'Test',
cedar_id: 'cedar-1',
template: {
'@id': 'https://repo.metadatacenter.org/templates/test',
'@type': 'https://schema.metadatacenter.org/core/Template',
type: 'object',
title: 'Test',
description: '',
$schema: 'http://json-schema.org/draft-04/schema',
'@context': {} as never,
required: [],
properties: {},
_ui: {
order: ['field1'],
propertyLabels: { field1: 'Field One' },
propertyDescriptions: {},
},
},
});
},
},
};

fixture = TestBed.createComponent(CollectionsDiscoverComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
interface SetupOptions {
collectionSubmissionWithCedar?: boolean;
provider?: typeof MOCK_COLLECTION_PROVIDER | typeof MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE;
}

it('should create', () => {
expect(component).toBeTruthy();
});
function setup(options: SetupOptions = {}) {
const { collectionSubmissionWithCedar = false, provider = MOCK_COLLECTION_PROVIDER } = options;

const toastServiceMock = ToastServiceMock.simple();
const mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
const mockRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'provider-1' }).build();

it('should initialize with default values', () => {
expect(component.providerId()).toBe('provider-1');
expect(component.searchControl.value).toBe('');
TestBed.configureTestingModule({
imports: [
CollectionsDiscoverComponent,
...MockComponents(
SearchInputComponent,
CollectionsMainContentComponent,
GlobalSearchComponent,
LoadingSpinnerComponent
),
],
providers: [
provideOSFCore(),
{ provide: ENVIRONMENT, useValue: { apiDomainUrl: 'http://localhost:8000', collectionSubmissionWithCedar } },
MockProvider(ToastService, toastServiceMock),
MockProvider(CustomDialogService, mockCustomDialogService),
MockProvider(ActivatedRoute, mockRoute),
provideMockStore({
signals: [
{ selector: CollectionsSelectors.getCollectionProvider, value: provider },
{ selector: CollectionsSelectors.getCollectionDetails, value: null },
{ selector: CollectionsSelectors.getAllSelectedFilters, value: {} },
{ selector: CollectionsSelectors.getSortBy, value: 'date' },
{ selector: CollectionsSelectors.getSearchText, value: '' },
{ selector: CollectionsSelectors.getPageNumber, value: '1' },
{ selector: CollectionsSelectors.getCollectionProviderLoading, value: false },
],
}),
],
}).overrideComponent(CollectionsDiscoverComponent, {
set: {
providers: [MockProvider(CollectionsQuerySyncService)],
},
});

it('should handle search triggered', () => {
const searchValue = 'test search';
const fixture = TestBed.createComponent(CollectionsDiscoverComponent);
const component = fixture.componentInstance;
const store = TestBed.inject(Store);
fixture.detectChanges();

component.onSearchTriggered(searchValue);
return { fixture, component, store };
}

expect(component).toBeTruthy();
});
describe('CollectionsDiscoverComponent', () => {
describe('legacy mode (collectionSubmissionWithCedar = false)', () => {
let component: CollectionsDiscoverComponent;
let fixture: ComponentFixture<CollectionsDiscoverComponent>;

it('should have provider id signal', () => {
expect(component.providerId()).toBe('provider-1');
});
beforeEach(() => {
({ fixture, component } = setup());
});

it('should have collection provider data', () => {
expect(component.collectionProvider()).toEqual(MOCK_PROVIDER);
});
it('should create', () => {
expect(component).toBeTruthy();
});

it('should have collection details', () => {
expect(component.collectionDetails()).toBeNull();
});
it('should set useShtrovSearch to false', () => {
expect(component.useShtrovSearch).toBe(false);
});

it('should have selected filters', () => {
expect(component.selectedFilters()).toEqual({});
});
it('should initialize with default values', () => {
expect(component.providerId()).toBe('provider-1');
expect(component.searchControl.value).toBe('');
});

it('should have sort by value', () => {
expect(component.sortBy()).toBe('date');
});
it('should have collection provider data', () => {
expect(component.collectionProvider()).toEqual(MOCK_COLLECTION_PROVIDER);
});

it('should have search text', () => {
expect(component.searchText()).toBe('');
});
it('should have collection details as null', () => {
expect(component.collectionDetails()).toBeNull();
});

it('should have page number', () => {
expect(component.pageNumber()).toBe('1');
});
it('should have selected filters', () => {
expect(component.selectedFilters()).toEqual({});
});

it('should have loading state', () => {
expect(component.isProviderLoading()).toBe(false);
});
it('should have sort by value', () => {
expect(component.sortBy()).toBe('date');
});

it('should have search text', () => {
expect(component.searchText()).toBe('');
});

it('should compute primary collection id', () => {
expect(component.primaryCollectionId()).toBe(MOCK_PROVIDER.primaryCollection?.id);
it('should have page number', () => {
expect(component.pageNumber()).toBe('1');
});

it('should have loading state', () => {
expect(component.isProviderLoading()).toBe(false);
});

it('should compute primary collection id', () => {
expect(component.primaryCollectionId()).toBe('collection-1');
});

it('should handle search control value changes', () => {
component.searchControl.setValue('new search value');
expect(component.searchControl.value).toBe('new search value');
});

it('should not initialize default search filters', () => {
expect(component.defaultSearchFiltersInitialized()).toBe(false);
});

it('should render CollectionsMainContentComponent', () => {
const el = fixture.nativeElement as HTMLElement;
expect(el.querySelector('osf-collections-main-content')).toBeTruthy();
expect(el.querySelector('osf-global-search')).toBeNull();
});

it('should dispatch setSearchValue and setPageNumber on search triggered', () => {
const { component: localComponent, store: localStore } = setup();
(localStore.dispatch as Mock).mockClear();

localComponent.onSearchTriggered('my query');

const calls = (localStore.dispatch as Mock).mock.calls.flat();
expect(calls.some((c: unknown) => c instanceof SetDefaultFilterValue)).toBe(false);
});
});

it('should handle search control value changes', () => {
const searchValue = 'new search value';
describe('shtrove mode (collectionSubmissionWithCedar = true)', () => {
it('should set useShtrovSearch to true', () => {
const { component } = setup({ collectionSubmissionWithCedar: true });
expect(component.useShtrovSearch).toBe(true);
});

it('should initialize default search filters', () => {
const { component } = setup({ collectionSubmissionWithCedar: true });
expect(component.defaultSearchFiltersInitialized()).toBe(true);
});

it('should dispatch SetDefaultFilterValue with collection IRI', () => {
const { store } = setup({ collectionSubmissionWithCedar: true });
const dispatched = (store.dispatch as Mock).mock.calls.flat();
const setDefaultFilter = dispatched.find(
(c: unknown) => c instanceof SetDefaultFilterValue
) as SetDefaultFilterValue;

expect(setDefaultFilter).toBeDefined();
expect(setDefaultFilter.filterKey).toBe('isContainedBy');
expect(setDefaultFilter.value).toBe('http://localhost:8000/v2/collections/collection-1/');
});

it('should not dispatch SetExtraFilters when provider has no requiredMetadataTemplate', () => {
const { store } = setup({ collectionSubmissionWithCedar: true });
const dispatched = (store.dispatch as Mock).mock.calls.flat();

component.searchControl.setValue(searchValue);
expect(dispatched.some((c: unknown) => c instanceof SetExtraFilters)).toBe(false);
});

it('should dispatch SetExtraFilters when provider has a requiredMetadataTemplate', () => {
const { store } = setup({
collectionSubmissionWithCedar: true,
provider: MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE,
});

expect(component.searchControl.value).toBe(searchValue);
const dispatched = (store.dispatch as Mock).mock.calls.flat();
const setExtraFilters = dispatched.find((c: unknown) => c instanceof SetExtraFilters) as SetExtraFilters;

expect(setExtraFilters).toBeDefined();
expect(setExtraFilters.filters).toHaveLength(1);
expect(setExtraFilters.filters[0].key).toBe('field1');
expect(setExtraFilters.filters[0].label).toBe('Field One');
});

it('should render GlobalSearchComponent when filters are initialized', () => {
const { fixture } = setup({ collectionSubmissionWithCedar: true });
const el = fixture.nativeElement as HTMLElement;

expect(el.querySelector('osf-global-search')).toBeTruthy();
expect(el.querySelector('osf-collections-main-content')).toBeNull();
});

it('should not dispatch any action on onSearchTriggered in shtrove mode', () => {
const { component, store } = setup({ collectionSubmissionWithCedar: true });
(store.dispatch as Mock).mockClear();

component.onSearchTriggered('query');

expect(store.dispatch).not.toHaveBeenCalled();
});
});
});
Loading
Loading