mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-04 10:14:13 +08:00
Add searchdefs to metadata store
- Refactor listview store into metadata store to keep all defs -- Update the cache to be able to store metadata per module -- Update the cache to be able to sync requested metadata with existing one -- Update fetch methods to be able to fetch only requested definitions - Add search defs to the metadata store - Update karma/jasmine tests
This commit is contained in:
parent
b62d1907aa
commit
34ff62476b
12 changed files with 556 additions and 182 deletions
|
@ -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]
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of vm.metadata.fields" [cdkColumnDef]="column.fieldName">
|
||||
<ng-container *ngFor="let column of vm.listMetadata.fields" [cdkColumnDef]="column.fieldName">
|
||||
<th cdk-header-cell scope="col" class="primary-table-header"
|
||||
*cdkHeaderCellDef>{{
|
||||
vm.language.appStrings[column.label] || column.label}}
|
||||
|
|
|
@ -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: '<scrm-table-body [module]="module"></scrm-table-body>'
|
||||
})
|
||||
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"');
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -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<LanguageStrings> = this.language.vm$;
|
||||
metadata$: Observable<ListViewMeta> = this.metadata.vm$;
|
||||
listMetadata$: Observable<ListViewMeta> = this.metadata.listMetadata$;
|
||||
selection$: Observable<RecordSelection> = 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
|
||||
) {
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<any> {
|
||||
return forkJoin({
|
||||
base: super.resolve(route),
|
||||
metadata: this.listViewMetaStore.load(route.params.module),
|
||||
metadata: this.metadataStore.load(route.params.module, this.metadataStore.getMetadataTypes()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<any> = null;
|
||||
let loadedModule: string;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ListViewMetaStore implements StateStore {
|
||||
protected store = new BehaviorSubject<ListViewMeta>(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<ListViewMeta> = 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<any>
|
||||
*/
|
||||
protected getListViewMeta(moduleID: string): Observable<any> {
|
||||
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;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
243
core/app/src/store/metadata/metadata.store.service.ts
Normal file
243
core/app/src/store/metadata/metadata.store.service.ts
Normal file
|
@ -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<Metadata>;
|
||||
}
|
||||
|
||||
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<Field[]>;
|
||||
listMetadata$: Observable<ListViewMeta>;
|
||||
|
||||
protected store = new BehaviorSubject<Metadata>(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<any>
|
||||
*/
|
||||
protected getMetadata(module: string, types: string[]): Observable<Metadata> {
|
||||
|
||||
let metadataCache: BehaviorSubject<Metadata> = 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<Metadata> {
|
||||
|
||||
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;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
212
core/app/src/store/metadata/metadata.store.spec.mock.ts
Normal file
212
core/app/src/store/metadata/metadata.store.spec.mock.ts
Normal file
|
@ -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<any> {
|
||||
|
||||
return of({
|
||||
data: {
|
||||
viewDefinition: metadataMockData
|
||||
}
|
||||
}).pipe(shareReplay(1));
|
||||
}
|
||||
}
|
||||
|
||||
export const metadataStoreMock = new MetadataStore(new MetadataRecordGQLSpy(), appStateStoreMock);
|
20
core/app/src/store/metadata/metadata.store.spec.ts
Normal file
20
core/app/src/store/metadata/metadata.store.spec.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue