diff --git a/core/app/src/components/list-container/list-container.component.spec.ts b/core/app/src/components/list-container/list-container.component.spec.ts index 4e8407b6f..a791cd1b6 100644 --- a/core/app/src/components/list-container/list-container.component.spec.ts +++ b/core/app/src/components/list-container/list-container.component.spec.ts @@ -15,6 +15,8 @@ import {ListViewStore} from '@store/list-view/list-view.store'; import {listviewStoreMock} from '@store/list-view/list-view.store.spec.mock'; import {LanguageStore} from '@store/language/language.store'; import {languageMockData} from '@store/language/language.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; +import {metadataMockData} from '@store/metadata/metadata.store.spec.mock'; describe('ListcontainerUiComponent', () => { @@ -47,6 +49,13 @@ describe('ListcontainerUiComponent', () => { appStrings$: of(languageMockData.appStrings).pipe(take(1)) } }, + { + provide: MetadataStore, useValue: { + listMetadata$: of({ + fields: metadataMockData.listView + }).pipe(take(1)), + } + }, ], declarations: [ListcontainerUiComponent] }) diff --git a/core/app/src/components/table/table-body/table-body.component.html b/core/app/src/components/table/table-body/table-body.component.html index a421c5eb0..f918f0145 100644 --- a/core/app/src/components/table/table-body/table-body.component.html +++ b/core/app/src/components/table/table-body/table-body.component.html @@ -15,7 +15,7 @@ - + {{ vm.language.appStrings[column.label] || column.label}} diff --git a/core/app/src/components/table/table-body/table-body.component.spec.ts b/core/app/src/components/table/table-body/table-body.component.spec.ts index 2901281c3..fc7cae34e 100644 --- a/core/app/src/components/table/table-body/table-body.component.spec.ts +++ b/core/app/src/components/table/table-body/table-body.component.spec.ts @@ -1,13 +1,19 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {TableBodyComponent} from './table-body.component'; import {CdkTableModule} from '@angular/cdk/table'; import {ApolloTestingModule} from 'apollo-angular/testing'; import {Component} from '@angular/core'; import {ListViewStore} from '@store/list-view/list-view.store'; import {listviewStoreMock} from '@store/list-view/list-view.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; +import {metadataMockData} from '@store/metadata/metadata.store.spec.mock'; +import {LanguageStore} from '@store/language/language.store'; +import {languageMockData} from '@store/language/language.store.spec.mock'; +import {of} from 'rxjs'; +import {take} from 'rxjs/operators'; @Component({ - selector: 'tabke-body-ui-test-host-component', + selector: 'table-body-ui-test-host-component', template: '' }) class TableBodyUITestHostComponent { @@ -26,29 +32,46 @@ describe('TablebodyUiComponent', () => { ], declarations: [TableBodyComponent, TableBodyUITestHostComponent], providers: [ + {provide: ListViewStore, useValue: listviewStoreMock}, { - provide: ListViewStore, useValue: listviewStoreMock + provide: MetadataStore, useValue: { + listMetadata$: of({ + fields: metadataMockData.listView + }).pipe(take(1)), + } + }, + { + provide: LanguageStore, useValue: { + vm$: of(languageMockData).pipe(take(1)), + appListStrings$: of(languageMockData.appListStrings).pipe(take(1)), + appStrings$: of(languageMockData.appStrings).pipe(take(1)) + } }, ], }) .compileComponents(); }); - beforeEach(() => { + beforeEach(async(() => { testHostFixture = TestBed.createComponent(TableBodyUITestHostComponent); testHostComponent = testHostFixture.componentInstance; testHostFixture.detectChanges(); - }); + })); - it('should create', () => { - expect(testHostComponent).toBeTruthy(); - }); + it('should create', async(() => { + testHostFixture.whenStable().then(() => { + expect(testHostComponent).toBeTruthy(); + }); + })); - it('should have table body', () => { - const tableBodyElement = testHostFixture.nativeElement.querySelector('table'); + it('should have table body', async(() => { - expect(testHostComponent).toBeTruthy(); - expect(tableBodyElement).toBeTruthy(); - expect(tableBodyElement.outerHTML).toContain('aria-describedby="table-body"'); - }); + testHostFixture.whenStable().then(() => { + const tableBodyElement = testHostFixture.nativeElement.querySelector('table'); + + expect(testHostComponent).toBeTruthy(); + expect(tableBodyElement).toBeTruthy(); + expect(tableBodyElement.outerHTML).toContain('aria-describedby="table-body"'); + }); + })); }); diff --git a/core/app/src/components/table/table-body/table-body.component.ts b/core/app/src/components/table/table-body/table-body.component.ts index c8531491f..8f0f54b4e 100644 --- a/core/app/src/components/table/table-body/table-body.component.ts +++ b/core/app/src/components/table/table-body/table-body.component.ts @@ -1,7 +1,7 @@ import {Component, Input} from '@angular/core'; import {combineLatest, Observable} from 'rxjs'; import {LanguageStore, LanguageStrings} from '@store/language/language.store'; -import {ListViewMeta, ListViewMetaStore} from '@store/list-view-meta/list-view-meta.store'; +import {ListViewMeta, MetadataStore} from '@store/metadata/metadata.store.service'; import {map} from 'rxjs/operators'; import {ListViewStore, RecordSelection} from '@store/list-view/list-view.store'; import {SelectionStatus} from '@components/bulk-action-menu/bulk-action-menu.component'; @@ -13,31 +13,31 @@ import {SelectionStatus} from '@components/bulk-action-menu/bulk-action-menu.com export class TableBodyComponent { @Input() module; language$: Observable = this.language.vm$; - metadata$: Observable = this.metadata.vm$; + listMetadata$: Observable = this.metadata.listMetadata$; selection$: Observable = this.data.selection$; dataSource$: ListViewStore = this.data; vm$ = combineLatest([ this.language$, - this.metadata$, + this.listMetadata$, this.selection$ ]).pipe( map(( [ language, - metadata, + listMetadata, selection ] ) => { const displayedColumns: string[] = ['checkbox']; - metadata.fields.forEach((field) => { + listMetadata.fields.forEach((field) => { displayedColumns.push(field.fieldName); }); return { language, - metadata, + listMetadata, selected: selection.selected, selectionStatus: selection.status, displayedColumns @@ -47,7 +47,7 @@ export class TableBodyComponent { constructor( protected language: LanguageStore, - protected metadata: ListViewMetaStore, + protected metadata: MetadataStore, protected data: ListViewStore ) { } diff --git a/core/app/src/components/table/table.component.spec.ts b/core/app/src/components/table/table.component.spec.ts index fa0827c50..092fc5d4b 100644 --- a/core/app/src/components/table/table.component.spec.ts +++ b/core/app/src/components/table/table.component.spec.ts @@ -14,6 +14,8 @@ import {themeImagesMockData} from '@store/theme-images/theme-images.store.spec.m import {take} from 'rxjs/operators'; import {ListViewStore} from '@store/list-view/list-view.store'; import {listviewStoreMock} from '@store/list-view/list-view.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; +import {metadataMockData} from '@store/metadata/metadata.store.spec.mock'; describe('TableUiComponent', () => { let component: TableUiComponent; @@ -40,6 +42,13 @@ describe('TableUiComponent', () => { images$: of(themeImagesMockData).pipe(take(1)) } }, + { + provide: MetadataStore, useValue: { + listMetadata$: of({ + fields: metadataMockData.listView + }).pipe(take(1)), + } + }, ], }) .compileComponents(); diff --git a/core/app/src/services/metadata/base-list.resolver.ts b/core/app/src/services/metadata/base-list.resolver.ts index ce418e95f..b13b6238a 100644 --- a/core/app/src/services/metadata/base-list.resolver.ts +++ b/core/app/src/services/metadata/base-list.resolver.ts @@ -8,7 +8,7 @@ import {NavigationStore} from '@base/store/navigation/navigation.store'; import {UserPreferenceStore} from '@base/store/user-preference/user-preference.store'; import {ThemeImagesStore} from '@base/store/theme-images/theme-images.store'; import {AppStateStore} from '@base/store/app-state/app-state.store'; -import {ListViewMetaStore} from '@store/list-view-meta/list-view-meta.store'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; import {BaseModuleResolver} from '@services/metadata/base-module.resolver'; import {forkJoin, Observable} from 'rxjs'; @@ -19,7 +19,7 @@ export class BaseListResolver extends BaseModuleResolver { protected systemConfigStore: SystemConfigStore, protected languageStore: LanguageStore, protected navigationStore: NavigationStore, - protected listViewMetaStore: ListViewMetaStore, + protected metadataStore: MetadataStore, protected userPreferenceStore: UserPreferenceStore, protected themeImagesStore: ThemeImagesStore, protected moduleNameMapper: ModuleNameMapper, @@ -41,7 +41,7 @@ export class BaseListResolver extends BaseModuleResolver { resolve(route: ActivatedRouteSnapshot): Observable { return forkJoin({ base: super.resolve(route), - metadata: this.listViewMetaStore.load(route.params.module), + metadata: this.metadataStore.load(route.params.module, this.metadataStore.getMetadataTypes()), }); } } diff --git a/core/app/src/store/list-view-meta/list-view-meta.store.ts b/core/app/src/store/list-view-meta/list-view-meta.store.ts deleted file mode 100644 index 7db495afa..000000000 --- a/core/app/src/store/list-view-meta/list-view-meta.store.ts +++ /dev/null @@ -1,151 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, combineLatest, Observable} from 'rxjs'; -import {distinctUntilChanged, map, shareReplay, tap} from 'rxjs/operators'; -import {RecordGQL} from '@services/api/graphql-api/api.record.get'; -import {deepClone} from '@base/utils/object-utils'; -import {StateStore} from '@base/store/state'; -import {AppStateStore} from '@store/app-state/app-state.store'; - -export interface ListViewMeta { - fields: Field[]; -} - -export interface Field { - fieldName: string; - width: string; - label: string; - link: boolean; - default: boolean; - module: string; - id: string; - sortable: boolean; -} - -const initialState: ListViewMeta = { - fields: [] -}; - -let internalState: ListViewMeta = deepClone(initialState); - -let cache$: Observable = null; -let loadedModule: string; - -@Injectable({ - providedIn: 'root', -}) -export class ListViewMetaStore implements StateStore { - protected store = new BehaviorSubject(internalState); - protected state$ = this.store.asObservable(); - protected resourceName = 'viewDefinition'; - protected fieldsMetadata = { - fields: [ - 'id', - '_id', - 'listView' - ] - }; - - /** - * Public long-lived observable streams - */ - fields$ = this.state$.pipe(map(state => state.fields), distinctUntilChanged()); - - - /** - * ViewModel that resolves once all the data is ready (or updated)... - */ - vm$: Observable = combineLatest( - [ - this.fields$, - ]) - .pipe( - map(( - [ - fields, - ]) => ({fields}) - ) - ); - - constructor(protected recordGQL: RecordGQL, protected appState: AppStateStore) { - } - - - /** - * Clear state - */ - public clear(): void { - loadedModule = ''; - cache$ = null; - this.updateState(deepClone(initialState)); - } - - - /** - * Initial ListViewMeta load if not cached and update state. - * - * @param {string} module to fetch - */ - public load(moduleID: string): any { - return this.getListViewMeta(moduleID).pipe( - tap(listViewMeta => { - loadedModule = moduleID; - this.updateState({ - ...internalState, - fields: listViewMeta.fields, - }); - }) - ) - } - - /** - * Update the state - * - * @param {{}} state to set - */ - protected updateState(state: ListViewMeta): void { - this.store.next(internalState = state); - } - - /** - * Get ListViewMeta cached Observable or call the backend - * - * @param {string} module to fetch - * @returns {{}} Observable - */ - protected getListViewMeta(moduleID: string): Observable { - if (cache$ == null || loadedModule != moduleID) { - cache$ = this.fetchViewDef(moduleID).pipe( - shareReplay(1) - ); - } - - return cache$; - } - - /** - * Fetch the ListViewMeta from the backend - * - * @param {string} module to fetch - * @returns {{}} Observable<{}> - */ - protected fetchViewDef(moduleID: string): Observable<{}> { - return this.recordGQL.fetch(this.resourceName, `/api/metadata/view-definitions/${moduleID}`, this.fieldsMetadata) - .pipe( - map(({data}) => { - let listViewMeta: ListViewMeta = { - fields: [] - }; - - if (data && data.viewDefinition.listView) { - data.viewDefinition.listView.forEach((field: Field) => { - listViewMeta.fields.push( - field - ) - }); - } - - return listViewMeta; - }) - ); - } -} diff --git a/core/app/src/store/metadata/metadata.store.service.ts b/core/app/src/store/metadata/metadata.store.service.ts new file mode 100644 index 000000000..5b7981d1b --- /dev/null +++ b/core/app/src/store/metadata/metadata.store.service.ts @@ -0,0 +1,243 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {distinctUntilChanged, map, shareReplay, tap} from 'rxjs/operators'; +import {RecordGQL} from '@services/api/graphql-api/api.record.get'; +import {deepClone} from '@base/utils/object-utils'; +import {StateStore} from '@base/store/state'; +import {AppStateStore} from '@store/app-state/app-state.store'; + +export interface ListViewMeta { + fields: Field[]; +} + +export interface Field { + fieldName: string; + width: string; + label: string; + link: boolean; + default: boolean; + module: string; + id: string; + sortable: boolean; +} + +export interface SearchMetaField { + name?: string; + type?: string; + label?: string; + default?: boolean; + options?: string; +} + +export interface SearchMeta { + layout: { + basic?: { [key: string]: SearchMetaField }; + advanced?: { [key: string]: SearchMetaField }; + }; +} + +export interface Metadata { + detailView?: any; + editView?: any; + listView?: ListViewMeta; + search?: SearchMeta; +} + +const initialState: Metadata = { + detailView: {}, + editView: {}, + listView: {} as ListViewMeta, + search: {} as SearchMeta +}; + +let internalState: Metadata = deepClone(initialState); + + +export interface MetadataCache { + [key: string]: BehaviorSubject; +} + +const initialCache: MetadataCache = {} as MetadataCache; + +let cache: MetadataCache = deepClone(initialCache); + +@Injectable({ + providedIn: 'root', +}) +export class MetadataStore implements StateStore { + + /** + * Public long-lived observable streams + */ + fields$: Observable; + listMetadata$: Observable; + + protected store = new BehaviorSubject(internalState); + protected state$ = this.store.asObservable(); + protected resourceName = 'viewDefinition'; + protected fieldsMetadata = { + fields: [ + 'id', + '_id', + ] + }; + protected types = [ + 'listView', + 'search' + ]; + + constructor(protected recordGQL: RecordGQL, protected appState: AppStateStore) { + this.fields$ = this.state$.pipe(map(state => state.listView.fields), distinctUntilChanged()); + this.listMetadata$ = this.state$.pipe(map(state => state.listView), distinctUntilChanged()); + } + + /** + * Clear state + */ + public clear(): void { + Object.keys(cache).forEach(key => { + cache[key].unsubscribe(); + }); + cache = deepClone(initialCache); + this.updateState(deepClone(initialState)); + } + + /** + * Get all metadata types + * + * @returns {string[]} metadata types + */ + public getMetadataTypes(): string[] { + return this.types; + } + + /** + * Initial ListViewMeta load if not cached and update state. + * + * @param {string} moduleID to fetch + * @param {string[]} types to fetch + * @returns {any} data + */ + public load(moduleID: string, types: string[]): any { + + if (!types) { + types = this.getMetadataTypes(); + } + + return this.getMetadata(moduleID, types).pipe( + tap((metadata: Metadata) => { + this.updateState(metadata); + }) + ); + } + + /** + * Update the state + * + * @param {object} state to set + */ + protected updateState(state: Metadata): void { + this.store.next(internalState = state); + } + + /** + * Get ListViewMeta cached Observable or call the backend + * + * @param {string} module to fetch + * @param {string[]} types to retrieve + * @returns {object} Observable + */ + protected getMetadata(module: string, types: string[]): Observable { + + let metadataCache: BehaviorSubject = null; + // check for currently missing and + const missing = {}; + const loadedTypes = {}; + + if (cache[module]) { + metadataCache = cache[module]; + + types.forEach(type => { + + const cached = metadataCache.value; + + if (!cached[type]) { + missing[type] = type; + return; + } + + if (Object.keys(cached[type]).length === 0) { + missing[type] = type; + } else { + loadedTypes[type] = cached[type]; + } + }); + + if (Object.keys(missing).length === 0) { + return metadataCache.asObservable(); + } + } else { + cache[module] = new BehaviorSubject({} as Metadata); + } + + return this.fetchMetadata(module, types).pipe( + map((value: Metadata) => { + + Object.keys(loadedTypes).forEach((type) => { + value[type] = loadedTypes[type]; + }); + + return value; + }), + shareReplay(1), + tap((value: Metadata) => { + cache[module].next(value); + }) + ); + } + + /** + * Fetch the Metadata from the backend + * + * @param {string} module to fetch + * @param {string[]} types to retrieve + * @returns {object} Observable<{}> + */ + protected fetchMetadata(module: string, types: string[]): Observable { + + const fieldsToRetrieve = { + fields: [ + ...this.fieldsMetadata.fields, + ...types + ] + }; + + return this.recordGQL.fetch(this.resourceName, `/api/metadata/view-definitions/${module}`, fieldsToRetrieve) + .pipe( + map(({data}) => { + + const metadata: Metadata = {} as Metadata; + + if (data && data.viewDefinition.listView) { + const listViewMeta: ListViewMeta = { + fields: [] + }; + + data.viewDefinition.listView.forEach((field: Field) => { + listViewMeta.fields.push( + field + ); + }); + + metadata.listView = listViewMeta; + } + + if (data && data.viewDefinition.search) { + metadata.search = data.viewDefinition.search; + } + + return metadata; + }) + ); + } +} diff --git a/core/app/src/store/metadata/metadata.store.spec.mock.ts b/core/app/src/store/metadata/metadata.store.spec.mock.ts new file mode 100644 index 000000000..a0ee947cc --- /dev/null +++ b/core/app/src/store/metadata/metadata.store.spec.mock.ts @@ -0,0 +1,212 @@ +import {RecordGQL} from '@services/api/graphql-api/api.record.get'; +import {Observable, of} from 'rxjs'; +import {shareReplay} from 'rxjs/operators'; +import {appStateStoreMock} from '@store/app-state/app-state.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; + +/* eslint-disable camelcase, @typescript-eslint/camelcase */ +export const metadataMockData = { + search: { + layout: { + basic: { + name: { + name: 'name', + default: true, + width: '10%' + }, + current_user_only: { + name: 'current_user_only', + label: 'LBL_CURRENT_USER_FILTER', + type: 'bool', + default: true, + width: '10%' + }, + favorites_only: { + name: 'favorites_only', + label: 'LBL_FAVORITES_FILTER', + type: 'bool' + } + }, + advanced: { + name: { + name: 'name', + default: true, + width: '10%' + }, + website: { + name: 'website', + default: true, + width: '10%' + }, + phone: { + name: 'phone', + label: 'LBL_ANY_PHONE', + type: 'name', + default: true, + width: '10%' + }, + email: { + name: 'email', + label: 'LBL_ANY_EMAIL', + type: 'name', + default: true, + width: '10%' + }, + address_street: { + name: 'address_street', + label: 'LBL_ANY_ADDRESS', + type: 'name', + default: true, + width: '10%' + }, + address_city: { + name: 'address_city', + label: 'LBL_CITY', + type: 'name', + default: true, + width: '10%' + }, + address_state: { + name: 'address_state', + label: 'LBL_STATE', + type: 'name', + default: true, + width: '10%' + }, + address_postalcode: { + name: 'address_postalcode', + label: 'LBL_POSTAL_CODE', + type: 'name', + default: true, + width: '10%' + }, + billing_address_country: { + name: 'billing_address_country', + label: 'LBL_COUNTRY', + type: 'name', + options: 'countries_dom', + default: true, + width: '10%' + }, + account_type: { + name: 'account_type', + default: true, + width: '10%' + }, + industry: { + name: 'industry', + default: true, + width: '10%' + }, + assigned_user_id: { + name: 'assigned_user_id', + type: 'enum', + label: 'LBL_ASSIGNED_TO', + function: { + name: 'get_user_array', + params: [ + false + ] + }, + default: true, + width: '10%' + } + } + } + }, + listView: [ + { + fieldName: 'name', + width: '20%', + label: 'LBL_LIST_ACCOUNT_NAME', + link: true, + default: true, + module: '', + id: '', + sortable: false + }, + { + fieldName: 'billing_address_city', + width: '10%', + label: 'LBL_LIST_CITY', + link: false, + default: true, + module: '', + id: '', + sortable: false + }, + { + fieldName: 'billing_address_country', + width: '10%', + label: 'LBL_BILLING_ADDRESS_COUNTRY', + link: false, + default: true, + module: '', + id: '', + sortable: false + }, + { + fieldName: 'phone_office', + width: '10%', + label: 'LBL_LIST_PHONE', + link: false, + default: true, + module: '', + id: '', + sortable: false + }, + { + fieldName: 'assigned_user_name', + width: '10%', + label: 'LBL_LIST_ASSIGNED_USER', + link: false, + default: true, + module: 'Employees', + id: 'ASSIGNED_USER_ID', + sortable: false + }, + { + fieldName: 'email1', + width: '15%', + label: 'LBL_EMAIL_ADDRESS', + link: true, + default: true, + module: '', + id: '', + sortable: false, + customCode: '{$EMAIL1_LINK}' + }, + { + fieldName: 'date_entered', + width: '5%', + label: 'LBL_DATE_ENTERED', + link: false, + default: true, + module: '', + id: '', + sortable: false + } + ] +}; + +/* eslint-enable camelcase, @typescript-eslint/camelcase */ + + +class MetadataRecordGQLSpy extends RecordGQL { + + constructor() { + super(null); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public fetch(module: string, id: string, metadata: { fields: string[] }): Observable { + + return of({ + data: { + viewDefinition: metadataMockData + } + }).pipe(shareReplay(1)); + } +} + +export const metadataStoreMock = new MetadataStore(new MetadataRecordGQLSpy(), appStateStoreMock); diff --git a/core/app/src/store/metadata/metadata.store.spec.ts b/core/app/src/store/metadata/metadata.store.spec.ts new file mode 100644 index 000000000..966072632 --- /dev/null +++ b/core/app/src/store/metadata/metadata.store.spec.ts @@ -0,0 +1,20 @@ +import {metadataMockData, metadataStoreMock} from '@store/metadata/metadata.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; +import {take} from 'rxjs/operators'; + +describe('Metadata Store', () => { + const service: MetadataStore = metadataStoreMock; + + beforeEach(() => { + }); + + it('#load', (done: DoneFn) => { + service.load('accounts', metadataStoreMock.getMetadataTypes()).pipe(take(1)).subscribe(data => { + + expect(data.listView.fields).toEqual(metadataMockData.listView); + expect(data.search).toEqual(metadataMockData.search); + done(); + }); + }); +}); + diff --git a/core/app/src/store/state-manager.ts b/core/app/src/store/state-manager.ts index 8db545252..5632673a4 100644 --- a/core/app/src/store/state-manager.ts +++ b/core/app/src/store/state-manager.ts @@ -6,7 +6,7 @@ import {SystemConfigStore} from '@store/system-config/system-config.store'; import {ThemeImagesStore} from '@store/theme-images/theme-images.store'; import {UserPreferenceStore} from '@store/user-preference/user-preference.store'; import {StateStore, StateStoreMap, StateStoreMapEntry} from '@base/store/state'; -import {ListViewMetaStore} from '@store/list-view-meta/list-view-meta.store'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; @Injectable({ providedIn: 'root', @@ -17,16 +17,16 @@ export class StateManager { constructor( protected appStore: AppStateStore, protected languageStore: LanguageStore, - protected listViewMetaStore: ListViewMetaStore, + protected metadataStore: MetadataStore, protected navigationStore: NavigationStore, protected systemConfigStore: SystemConfigStore, protected themeImagesStore: ThemeImagesStore, protected userPreferenceStore: UserPreferenceStore ) { this.stateStores.appStore = this.buildMapEntry(appStore, false); - this.stateStores.languageStore = this.buildMapEntry(languageStore, false) - this.stateStores.listViewMetaStore = this.buildMapEntry(listViewMetaStore, false) this.stateStores.navigationStore = this.buildMapEntry(navigationStore, true); + this.stateStores.languageStore = this.buildMapEntry(languageStore, false); + this.stateStores.listViewMetaStore = this.buildMapEntry(metadataStore, false); this.stateStores.systemConfigStore = this.buildMapEntry(systemConfigStore, false); this.stateStores.themeImagesStore = this.buildMapEntry(themeImagesStore, false); this.stateStores.userPreferenceStore = this.buildMapEntry(userPreferenceStore, true); diff --git a/core/app/views/list/list.component.spec.ts b/core/app/views/list/list.component.spec.ts index 682cdbaa6..81993b41f 100644 --- a/core/app/views/list/list.component.spec.ts +++ b/core/app/views/list/list.component.spec.ts @@ -26,6 +26,8 @@ import {listviewStoreMock} from '@store/list-view/list-view.store.spec.mock'; import {systemConfigStoreMock} from '@store/system-config/system-config.store.spec.mock'; import {UserPreferenceStore} from '@store/user-preference/user-preference.store'; import {userPreferenceStoreMock} from '@store/user-preference/user-preference.store.spec.mock'; +import {MetadataStore} from '@store/metadata/metadata.store.service'; +import {metadataMockData} from '@store/metadata/metadata.store.spec.mock'; @Component({ selector: 'list-test-host-component', @@ -75,7 +77,7 @@ describe('ListComponent', () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars updateLoading: (key: string, loading: boolean): void => { } - } + } as AppStateStore }, { provide: LanguageStore, useValue: { @@ -94,7 +96,14 @@ describe('ListComponent', () => { }, { provide: UserPreferenceStore, useValue: userPreferenceStoreMock - } + }, + { + provide: MetadataStore, useValue: { + listMetadata$: of({ + fields: metadataMockData.listView + }).pipe(take(1)), + } + }, ], }) .compileComponents();