|
| 1 | +import { Store } from '@ngxs/store'; |
| 2 | + |
1 | 3 | import { MockComponents, MockProvider } from 'ng-mocks'; |
2 | 4 |
|
| 5 | +import { Mock } from 'vitest'; |
| 6 | + |
3 | 7 | import { ComponentFixture, TestBed } from '@angular/core/testing'; |
4 | 8 | import { ActivatedRoute } from '@angular/router'; |
5 | 9 |
|
| 10 | +import { ENVIRONMENT } from '@core/provider/environment.provider'; |
| 11 | +import { GlobalSearchComponent } from '@osf/shared/components/global-search/global-search.component'; |
6 | 12 | import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component'; |
7 | 13 | import { SearchInputComponent } from '@osf/shared/components/search-input/search-input.component'; |
8 | 14 | import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; |
9 | 15 | import { ToastService } from '@osf/shared/services/toast.service'; |
10 | 16 | import { CollectionsSelectors } from '@shared/stores/collections'; |
| 17 | +import { SetDefaultFilterValue, SetExtraFilters } from '@shared/stores/global-search'; |
11 | 18 |
|
12 | 19 | import { MOCK_PROVIDER } from '@testing/mocks/provider.mock'; |
13 | 20 | import { provideOSFCore } from '@testing/osf.testing.provider'; |
14 | 21 | import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock'; |
15 | 22 | import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock'; |
16 | 23 | import { provideMockStore } from '@testing/providers/store-provider.mock'; |
17 | | -import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock'; |
| 24 | +import { ToastServiceMock } from '@testing/providers/toast-provider.mock'; |
18 | 25 |
|
19 | 26 | import { CollectionsQuerySyncService } from '../../services'; |
20 | 27 | import { CollectionsMainContentComponent } from '../collections-main-content/collections-main-content.component'; |
21 | 28 |
|
22 | 29 | import { CollectionsDiscoverComponent } from './collections-discover.component'; |
23 | 30 |
|
24 | | -describe('CollectionsDiscoverComponent', () => { |
25 | | - let component: CollectionsDiscoverComponent; |
26 | | - let fixture: ComponentFixture<CollectionsDiscoverComponent>; |
27 | | - let toastServiceMock: ToastServiceMockType; |
28 | | - let mockCustomDialogService: ReturnType<CustomDialogServiceMockBuilder['build']>; |
29 | | - let mockRoute: ReturnType<ActivatedRouteMockBuilder['build']>; |
30 | | - |
31 | | - beforeEach(() => { |
32 | | - toastServiceMock = ToastServiceMock.simple(); |
33 | | - mockCustomDialogService = CustomDialogServiceMockBuilder.create().build(); |
34 | | - mockRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'provider-1' }).build(); |
35 | | - |
36 | | - TestBed.configureTestingModule({ |
37 | | - imports: [ |
38 | | - CollectionsDiscoverComponent, |
39 | | - ...MockComponents(SearchInputComponent, CollectionsMainContentComponent, LoadingSpinnerComponent), |
40 | | - ], |
41 | | - providers: [ |
42 | | - provideOSFCore(), |
43 | | - MockProvider(ToastService, toastServiceMock), |
44 | | - MockProvider(CustomDialogService, mockCustomDialogService), |
45 | | - MockProvider(ActivatedRoute, mockRoute), |
46 | | - provideMockStore({ |
47 | | - signals: [ |
48 | | - { selector: CollectionsSelectors.getCollectionProvider, value: MOCK_PROVIDER }, |
49 | | - { selector: CollectionsSelectors.getCollectionDetails, value: null }, |
50 | | - { selector: CollectionsSelectors.getAllSelectedFilters, value: {} }, |
51 | | - { selector: CollectionsSelectors.getSortBy, value: 'date' }, |
52 | | - { selector: CollectionsSelectors.getSearchText, value: '' }, |
53 | | - { selector: CollectionsSelectors.getPageNumber, value: '1' }, |
54 | | - { selector: CollectionsSelectors.getCollectionProviderLoading, value: false }, |
55 | | - ], |
56 | | - }), |
57 | | - ], |
58 | | - }).overrideComponent(CollectionsDiscoverComponent, { |
59 | | - set: { |
60 | | - providers: [MockProvider(CollectionsQuerySyncService)], |
| 31 | +const MOCK_COLLECTION_PROVIDER = { |
| 32 | + ...MOCK_PROVIDER, |
| 33 | + primaryCollection: { id: 'collection-1', type: 'collections' }, |
| 34 | + requiredMetadataTemplate: null, |
| 35 | +}; |
| 36 | + |
| 37 | +const MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE = { |
| 38 | + ...MOCK_COLLECTION_PROVIDER, |
| 39 | + requiredMetadataTemplate: { |
| 40 | + id: 'template-1', |
| 41 | + type: 'cedar-metadata-templates' as const, |
| 42 | + attributes: { |
| 43 | + schema_name: 'Test', |
| 44 | + cedar_id: 'cedar-1', |
| 45 | + template: { |
| 46 | + '@id': 'https://repo.metadatacenter.org/templates/test', |
| 47 | + '@type': 'https://schema.metadatacenter.org/core/Template', |
| 48 | + type: 'object', |
| 49 | + title: 'Test', |
| 50 | + description: '', |
| 51 | + $schema: 'http://json-schema.org/draft-04/schema', |
| 52 | + '@context': {} as never, |
| 53 | + required: [], |
| 54 | + properties: {}, |
| 55 | + _ui: { |
| 56 | + order: ['field1'], |
| 57 | + propertyLabels: { field1: 'Field One' }, |
| 58 | + propertyDescriptions: {}, |
| 59 | + }, |
61 | 60 | }, |
62 | | - }); |
| 61 | + }, |
| 62 | + }, |
| 63 | +}; |
63 | 64 |
|
64 | | - fixture = TestBed.createComponent(CollectionsDiscoverComponent); |
65 | | - component = fixture.componentInstance; |
66 | | - fixture.detectChanges(); |
67 | | - }); |
| 65 | +interface SetupOptions { |
| 66 | + collectionSubmissionWithCedar?: boolean; |
| 67 | + provider?: typeof MOCK_COLLECTION_PROVIDER | typeof MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE; |
| 68 | +} |
68 | 69 |
|
69 | | - it('should create', () => { |
70 | | - expect(component).toBeTruthy(); |
71 | | - }); |
| 70 | +function setup(options: SetupOptions = {}) { |
| 71 | + const { collectionSubmissionWithCedar = false, provider = MOCK_COLLECTION_PROVIDER } = options; |
| 72 | + |
| 73 | + const toastServiceMock = ToastServiceMock.simple(); |
| 74 | + const mockCustomDialogService = CustomDialogServiceMockBuilder.create().build(); |
| 75 | + const mockRoute = ActivatedRouteMockBuilder.create().withParams({ providerId: 'provider-1' }).build(); |
72 | 76 |
|
73 | | - it('should initialize with default values', () => { |
74 | | - expect(component.providerId()).toBe('provider-1'); |
75 | | - expect(component.searchControl.value).toBe(''); |
| 77 | + TestBed.configureTestingModule({ |
| 78 | + imports: [ |
| 79 | + CollectionsDiscoverComponent, |
| 80 | + ...MockComponents( |
| 81 | + SearchInputComponent, |
| 82 | + CollectionsMainContentComponent, |
| 83 | + GlobalSearchComponent, |
| 84 | + LoadingSpinnerComponent |
| 85 | + ), |
| 86 | + ], |
| 87 | + providers: [ |
| 88 | + provideOSFCore(), |
| 89 | + { provide: ENVIRONMENT, useValue: { apiDomainUrl: 'http://localhost:8000', collectionSubmissionWithCedar } }, |
| 90 | + MockProvider(ToastService, toastServiceMock), |
| 91 | + MockProvider(CustomDialogService, mockCustomDialogService), |
| 92 | + MockProvider(ActivatedRoute, mockRoute), |
| 93 | + provideMockStore({ |
| 94 | + signals: [ |
| 95 | + { selector: CollectionsSelectors.getCollectionProvider, value: provider }, |
| 96 | + { selector: CollectionsSelectors.getCollectionDetails, value: null }, |
| 97 | + { selector: CollectionsSelectors.getAllSelectedFilters, value: {} }, |
| 98 | + { selector: CollectionsSelectors.getSortBy, value: 'date' }, |
| 99 | + { selector: CollectionsSelectors.getSearchText, value: '' }, |
| 100 | + { selector: CollectionsSelectors.getPageNumber, value: '1' }, |
| 101 | + { selector: CollectionsSelectors.getCollectionProviderLoading, value: false }, |
| 102 | + ], |
| 103 | + }), |
| 104 | + ], |
| 105 | + }).overrideComponent(CollectionsDiscoverComponent, { |
| 106 | + set: { |
| 107 | + providers: [MockProvider(CollectionsQuerySyncService)], |
| 108 | + }, |
76 | 109 | }); |
77 | 110 |
|
78 | | - it('should handle search triggered', () => { |
79 | | - const searchValue = 'test search'; |
| 111 | + const fixture = TestBed.createComponent(CollectionsDiscoverComponent); |
| 112 | + const component = fixture.componentInstance; |
| 113 | + const store = TestBed.inject(Store); |
| 114 | + fixture.detectChanges(); |
80 | 115 |
|
81 | | - component.onSearchTriggered(searchValue); |
| 116 | + return { fixture, component, store }; |
| 117 | +} |
82 | 118 |
|
83 | | - expect(component).toBeTruthy(); |
84 | | - }); |
| 119 | +describe('CollectionsDiscoverComponent', () => { |
| 120 | + describe('legacy mode (collectionSubmissionWithCedar = false)', () => { |
| 121 | + let component: CollectionsDiscoverComponent; |
| 122 | + let fixture: ComponentFixture<CollectionsDiscoverComponent>; |
85 | 123 |
|
86 | | - it('should have provider id signal', () => { |
87 | | - expect(component.providerId()).toBe('provider-1'); |
88 | | - }); |
| 124 | + beforeEach(() => { |
| 125 | + ({ fixture, component } = setup()); |
| 126 | + }); |
89 | 127 |
|
90 | | - it('should have collection provider data', () => { |
91 | | - expect(component.collectionProvider()).toEqual(MOCK_PROVIDER); |
92 | | - }); |
| 128 | + it('should create', () => { |
| 129 | + expect(component).toBeTruthy(); |
| 130 | + }); |
93 | 131 |
|
94 | | - it('should have collection details', () => { |
95 | | - expect(component.collectionDetails()).toBeNull(); |
96 | | - }); |
| 132 | + it('should set useShareTroveSearch to false', () => { |
| 133 | + expect(component.useShareTroveSearch).toBe(false); |
| 134 | + }); |
97 | 135 |
|
98 | | - it('should have selected filters', () => { |
99 | | - expect(component.selectedFilters()).toEqual({}); |
100 | | - }); |
| 136 | + it('should initialize with default values', () => { |
| 137 | + expect(component.providerId()).toBe('provider-1'); |
| 138 | + expect(component.searchControl.value).toBe(''); |
| 139 | + }); |
101 | 140 |
|
102 | | - it('should have sort by value', () => { |
103 | | - expect(component.sortBy()).toBe('date'); |
104 | | - }); |
| 141 | + it('should have collection provider data', () => { |
| 142 | + expect(component.collectionProvider()).toEqual(MOCK_COLLECTION_PROVIDER); |
| 143 | + }); |
105 | 144 |
|
106 | | - it('should have search text', () => { |
107 | | - expect(component.searchText()).toBe(''); |
108 | | - }); |
| 145 | + it('should have collection details as null', () => { |
| 146 | + expect(component.collectionDetails()).toBeNull(); |
| 147 | + }); |
109 | 148 |
|
110 | | - it('should have page number', () => { |
111 | | - expect(component.pageNumber()).toBe('1'); |
112 | | - }); |
| 149 | + it('should have selected filters', () => { |
| 150 | + expect(component.selectedFilters()).toEqual({}); |
| 151 | + }); |
113 | 152 |
|
114 | | - it('should have loading state', () => { |
115 | | - expect(component.isProviderLoading()).toBe(false); |
116 | | - }); |
| 153 | + it('should have sort by value', () => { |
| 154 | + expect(component.sortBy()).toBe('date'); |
| 155 | + }); |
| 156 | + |
| 157 | + it('should have search text', () => { |
| 158 | + expect(component.searchText()).toBe(''); |
| 159 | + }); |
117 | 160 |
|
118 | | - it('should compute primary collection id', () => { |
119 | | - expect(component.primaryCollectionId()).toBe(MOCK_PROVIDER.primaryCollection?.id); |
| 161 | + it('should have page number', () => { |
| 162 | + expect(component.pageNumber()).toBe('1'); |
| 163 | + }); |
| 164 | + |
| 165 | + it('should have loading state', () => { |
| 166 | + expect(component.isProviderLoading()).toBe(false); |
| 167 | + }); |
| 168 | + |
| 169 | + it('should compute primary collection id', () => { |
| 170 | + expect(component.primaryCollectionId()).toBe('collection-1'); |
| 171 | + }); |
| 172 | + |
| 173 | + it('should handle search control value changes', () => { |
| 174 | + component.searchControl.setValue('new search value'); |
| 175 | + expect(component.searchControl.value).toBe('new search value'); |
| 176 | + }); |
| 177 | + |
| 178 | + it('should not initialize default search filters', () => { |
| 179 | + expect(component.defaultSearchFiltersInitialized()).toBe(false); |
| 180 | + }); |
| 181 | + |
| 182 | + it('should render CollectionsMainContentComponent', () => { |
| 183 | + const el = fixture.nativeElement as HTMLElement; |
| 184 | + expect(el.querySelector('osf-collections-main-content')).toBeTruthy(); |
| 185 | + expect(el.querySelector('osf-global-search')).toBeNull(); |
| 186 | + }); |
| 187 | + |
| 188 | + it('should dispatch setSearchValue and setPageNumber on search triggered', () => { |
| 189 | + const { component: localComponent, store: localStore } = setup(); |
| 190 | + (localStore.dispatch as Mock).mockClear(); |
| 191 | + |
| 192 | + localComponent.onSearchTriggered('my query'); |
| 193 | + |
| 194 | + const calls = (localStore.dispatch as Mock).mock.calls.flat(); |
| 195 | + expect(calls.some((c: unknown) => c instanceof SetDefaultFilterValue)).toBe(false); |
| 196 | + }); |
120 | 197 | }); |
121 | 198 |
|
122 | | - it('should handle search control value changes', () => { |
123 | | - const searchValue = 'new search value'; |
| 199 | + describe('shtrove mode (collectionSubmissionWithCedar = true)', () => { |
| 200 | + it('should set useShareTroveSearch to true', () => { |
| 201 | + const { component } = setup({ collectionSubmissionWithCedar: true }); |
| 202 | + expect(component.useShareTroveSearch).toBe(true); |
| 203 | + }); |
| 204 | + |
| 205 | + it('should initialize default search filters', () => { |
| 206 | + const { component } = setup({ collectionSubmissionWithCedar: true }); |
| 207 | + expect(component.defaultSearchFiltersInitialized()).toBe(true); |
| 208 | + }); |
| 209 | + |
| 210 | + it('should dispatch SetDefaultFilterValue with collection IRI', () => { |
| 211 | + const { store } = setup({ collectionSubmissionWithCedar: true }); |
| 212 | + const dispatched = (store.dispatch as Mock).mock.calls.flat(); |
| 213 | + const setDefaultFilter = dispatched.find( |
| 214 | + (c: unknown) => c instanceof SetDefaultFilterValue |
| 215 | + ) as SetDefaultFilterValue; |
| 216 | + |
| 217 | + expect(setDefaultFilter).toBeDefined(); |
| 218 | + expect(setDefaultFilter.filterKey).toBe('isContainedBy'); |
| 219 | + expect(setDefaultFilter.value).toBe('http://localhost:8000/v2/collections/collection-1/'); |
| 220 | + }); |
| 221 | + |
| 222 | + it('should not dispatch SetExtraFilters when provider has no requiredMetadataTemplate', () => { |
| 223 | + const { store } = setup({ collectionSubmissionWithCedar: true }); |
| 224 | + const dispatched = (store.dispatch as Mock).mock.calls.flat(); |
124 | 225 |
|
125 | | - component.searchControl.setValue(searchValue); |
| 226 | + expect(dispatched.some((c: unknown) => c instanceof SetExtraFilters)).toBe(false); |
| 227 | + }); |
| 228 | + |
| 229 | + it('should dispatch SetExtraFilters when provider has a requiredMetadataTemplate', () => { |
| 230 | + const { store } = setup({ |
| 231 | + collectionSubmissionWithCedar: true, |
| 232 | + provider: MOCK_COLLECTION_PROVIDER_WITH_TEMPLATE, |
| 233 | + }); |
126 | 234 |
|
127 | | - expect(component.searchControl.value).toBe(searchValue); |
| 235 | + const dispatched = (store.dispatch as Mock).mock.calls.flat(); |
| 236 | + const setExtraFilters = dispatched.find((c: unknown) => c instanceof SetExtraFilters) as SetExtraFilters; |
| 237 | + |
| 238 | + expect(setExtraFilters).toBeDefined(); |
| 239 | + expect(setExtraFilters.filters).toHaveLength(1); |
| 240 | + expect(setExtraFilters.filters[0].key).toBe('field1'); |
| 241 | + expect(setExtraFilters.filters[0].label).toBe('Field One'); |
| 242 | + }); |
| 243 | + |
| 244 | + it('should render GlobalSearchComponent when filters are initialized', () => { |
| 245 | + const { fixture } = setup({ collectionSubmissionWithCedar: true }); |
| 246 | + const el = fixture.nativeElement as HTMLElement; |
| 247 | + |
| 248 | + expect(el.querySelector('osf-global-search')).toBeTruthy(); |
| 249 | + expect(el.querySelector('osf-collections-main-content')).toBeNull(); |
| 250 | + }); |
| 251 | + |
| 252 | + it('should not dispatch any action on onSearchTriggered in shtrove mode', () => { |
| 253 | + const { component, store } = setup({ collectionSubmissionWithCedar: true }); |
| 254 | + (store.dispatch as Mock).mockClear(); |
| 255 | + |
| 256 | + component.onSearchTriggered('query'); |
| 257 | + |
| 258 | + expect(store.dispatch).not.toHaveBeenCalled(); |
| 259 | + }); |
128 | 260 | }); |
129 | 261 | }); |
0 commit comments