mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-01 08:00:47 +08:00
Retrieve Navigation Menu data from API
- Add Navigation Api Service - Add Metadata Facade Service - Build Navigation menu using fetched menu items - Fix submenu item link on template - Cleanup code warnings - Remove get method from RecordGQL -- we won't be using GraphQL cache for the moment - Fix classic view karma tests - Fix Filter karma tests - Add set of base jasmine tests for: -- navbar component -- metadata service -- navigation metadata service
This commit is contained in:
parent
7ed07c6091
commit
e3ce6d85ef
15 changed files with 413 additions and 333 deletions
|
@ -25,7 +25,7 @@ module.exports = function (config) {
|
|||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
browsers: ['Chrome', 'Chromium'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
|
|
|
@ -5,16 +5,25 @@ import {RouterTestingModule} from '@angular/router/testing';
|
|||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||
|
||||
import {ClassicViewUiComponent} from './classic-view.component';
|
||||
import {ApiService} from '../../services/api/api.service';
|
||||
import {ApiService} from '@services/api/api.service';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
describe('ClassicViewUiComponent', () => {
|
||||
let component: ClassicViewUiComponent;
|
||||
let fixture: ComponentFixture<ClassicViewUiComponent>;
|
||||
const route = ({
|
||||
data: { view: { html: '<h1>haha</h1>' }},
|
||||
snapshot: {
|
||||
data: { view: { html: '<h1>haha</h1>' }}
|
||||
}
|
||||
} as any) as ActivatedRoute;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [RouterTestingModule, HttpClientTestingModule, FormsModule],
|
||||
providers: [{ provide: ActivatedRoute, useValue: route }],
|
||||
declarations: [ClassicViewUiComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@ -28,6 +37,15 @@ describe('ClassicViewUiComponent', () => {
|
|||
|
||||
it(`should create`, async(inject([HttpTestingController],
|
||||
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
|
||||
|
||||
expect(component).toBeTruthy();
|
||||
expect(component.data.view.html).toEqual('<h1>haha</h1>');
|
||||
})));
|
||||
|
||||
it(`should display provided html`, async(inject([HttpTestingController],
|
||||
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
|
||||
|
||||
const classicElement: HTMLElement = fixture.nativeElement;
|
||||
expect(classicElement.innerHTML).toContain('<h1>haha</h1>');
|
||||
})));
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import {ActivatedRoute} from '@angular/router';
|
|||
export class ClassicViewUiComponent {
|
||||
data: any;
|
||||
|
||||
@ViewChild('dataContainer', {static:true}) dataContainer: ElementRef;
|
||||
@ViewChild('dataContainer', {static: true}) dataContainer: ElementRef;
|
||||
public element: any;
|
||||
|
||||
renderHtml(data) {
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FilterViewUiComponent} from './filter-view.component';
|
||||
import {FilterUiComponent} from './filter.component';
|
||||
|
||||
describe('FilterViewUiComponent', () => {
|
||||
let component: FilterViewUiComponent;
|
||||
let fixture: ComponentFixture<FilterViewUiComponent>;
|
||||
describe('FilterUiComponent', () => {
|
||||
let component: FilterUiComponent;
|
||||
let fixture: ComponentFixture<FilterUiComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FilterViewUiComponent]
|
||||
declarations: [FilterUiComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterViewUiComponent);
|
||||
fixture = TestBed.createComponent(FilterUiComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
|
|
@ -14,8 +14,6 @@ import {HttpClientModule} from '@angular/common/http';
|
|||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
|
||||
import {ListViewUiComponent} from './list-view.component';
|
||||
import {SvgIconUiComponent} from '../svg-icon/svg-icon.component';
|
||||
import {ModalViewUiComponent} from '../modal-view/modal-view.component';
|
||||
|
||||
let mockRouter: any;
|
||||
|
||||
|
|
|
@ -11,4 +11,5 @@ export interface NavbarModel {
|
|||
currentUser: CurrentUserModel;
|
||||
all: AllMenuModel;
|
||||
menu: any;
|
||||
buildMenu(items: any, menuItemThreshold: number): void;
|
||||
}
|
||||
|
|
|
@ -7,27 +7,27 @@ export class NavbarAbstract implements NavbarModel {
|
|||
useGroupTabs = true;
|
||||
globalActions = [
|
||||
{
|
||||
'link': {
|
||||
'url': '',
|
||||
'label': 'Employees'
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Employees'
|
||||
}
|
||||
},
|
||||
{
|
||||
'link': {
|
||||
'url': '',
|
||||
'label': 'Admin'
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Admin'
|
||||
}
|
||||
},
|
||||
{
|
||||
'link': {
|
||||
'url': '',
|
||||
'label': 'Support Forums'
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Support Forums'
|
||||
}
|
||||
},
|
||||
{
|
||||
'link': {
|
||||
'url': '',
|
||||
'label': 'About'
|
||||
link: {
|
||||
url: '',
|
||||
label: 'About'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -39,263 +39,65 @@ export class NavbarAbstract implements NavbarModel {
|
|||
modules: [],
|
||||
extra: [],
|
||||
};
|
||||
menu = [
|
||||
{
|
||||
"link": {
|
||||
"label": "Accounts",
|
||||
"url": ''
|
||||
},
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Account",
|
||||
"url": "",
|
||||
"iconRef": {"resolved": "home_page"}
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": [],
|
||||
menu = [];
|
||||
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Account", "url": "/#/Accounts/index"
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Accounts", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Contacts", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Contact",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Contact", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Contacts", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Opportunities", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Opportunity",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Opportunity", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Opportunities", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Leads", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Lead",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Lead", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Leads", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Quotes", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Quote",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Quote", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Quotes", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Calendar", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Calendar",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Calendar", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Calendars", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Documents", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Document",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Document", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Documents", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Emails", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Email",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Email", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Emails", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"link": { "label": "Spots", "url": '' }, "icon": "home_page",
|
||||
"submenu":
|
||||
[
|
||||
{
|
||||
"link": {
|
||||
"label": "Create Spot",
|
||||
"url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "View Spot", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
{
|
||||
"link": {
|
||||
"label": "Import Spots", "url": ""
|
||||
},
|
||||
"icon": "plus",
|
||||
"submenu": []
|
||||
},
|
||||
]
|
||||
public buildMenu(items: {}, threshold: number): void {
|
||||
const navItems = [];
|
||||
const moreItems = [];
|
||||
|
||||
if (!items || Object.keys(items).length === 0) {
|
||||
this.menu = navItems;
|
||||
this.all.extra = moreItems;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
Object.keys(items).forEach(module => {
|
||||
|
||||
if (count <= threshold) {
|
||||
navItems.push(this.buildMenuItem(module, items[module]));
|
||||
} else {
|
||||
moreItems.push(this.buildMenuItem(module, items[module]));
|
||||
}
|
||||
|
||||
count++;
|
||||
});
|
||||
|
||||
this.menu = navItems;
|
||||
this.all.modules = moreItems;
|
||||
}
|
||||
|
||||
public buildMenuItem(module: string, label: string): any {
|
||||
|
||||
return {
|
||||
link: { label, url: `/#/${module}/index` }, icon: 'home_page',
|
||||
submenu:
|
||||
[
|
||||
{
|
||||
link: {
|
||||
label: `Create ${label}`,
|
||||
url: `/#/${module}/edit`
|
||||
},
|
||||
icon: 'plus',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
link: {
|
||||
label: `View ${label}`,
|
||||
url: `/#/${module}/list`
|
||||
},
|
||||
icon: 'view',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
link: {
|
||||
label: `Import ${label}`,
|
||||
url: `/#/${module}/import`
|
||||
},
|
||||
icon: 'upload',
|
||||
submenu: []
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -137,7 +137,10 @@
|
|||
</span>
|
||||
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink" [ngbCollapse]="subNavCollapse">
|
||||
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
|
||||
<a class="nav-link action-link dropdown-item dropdown-toggle"
|
||||
<a class="nav-link action-link" *ngIf="!sub.submenu.length" [href]="sub.link.url">
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
<a class="nav-link action-link dropdown-item dropdown-toggle" *ngIf="sub.submenu.length"
|
||||
(click)="subItemCollapse = !subItemCollapse">
|
||||
{{ sub.link.label }}
|
||||
</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed, inject, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||
|
@ -6,28 +6,77 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht
|
|||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import {NavbarUiComponent} from './navbar.component';
|
||||
import {of} from 'rxjs';
|
||||
import {Metadata} from '@services/metadata/metadata.service';
|
||||
|
||||
describe('NavbarUiComponent', () => {
|
||||
let component: NavbarUiComponent;
|
||||
let fixture: ComponentFixture<NavbarUiComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [RouterTestingModule, HttpClientTestingModule, NgbModule],
|
||||
declarations: [NavbarUiComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NavbarUiComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
describe('Test with mock service', () => {
|
||||
let component: NavbarUiComponent;
|
||||
let fixture: ComponentFixture<NavbarUiComponent>;
|
||||
let service: Metadata;
|
||||
|
||||
const navigationMockData = {
|
||||
navbar: {
|
||||
NonGroupedTabs: {
|
||||
contacts: 'Contacts',
|
||||
},
|
||||
groupedTabs: {
|
||||
Sales: {
|
||||
modules: {
|
||||
Accounts: 'Accounts',
|
||||
}
|
||||
},
|
||||
},
|
||||
userActionMenu: [
|
||||
{
|
||||
label: 'Profile',
|
||||
url: 'index.php?module=Users&action=EditView&record=1',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
label: 'Employees',
|
||||
url: 'index.php?module=Employees&action=index',
|
||||
submenu: []
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
// Create a fake TwainService object with a 'getQuote()' spy
|
||||
const metadata = jasmine.createSpyObj('Metadata', ['getNavigation']);
|
||||
// Make the spy return a synchronous Observable with the test data
|
||||
const getMetadata = metadata.getNavigation.and.returnValue(of(navigationMockData.navbar));
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [RouterTestingModule, HttpClientTestingModule, NgbModule],
|
||||
providers: [{provide: Metadata, useValue: metadata}],
|
||||
declarations: [NavbarUiComponent]
|
||||
}).compileComponents();
|
||||
service = TestBed.get(Metadata);
|
||||
fixture = TestBed.createComponent(NavbarUiComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges(); // onInit()
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get metadata', async (() => {
|
||||
fixture.detectChanges(); // onInit()
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.navigationMetadata).toEqual(jasmine.objectContaining(navigationMockData.navbar));
|
||||
});
|
||||
|
||||
component.ngOnInit();
|
||||
}));
|
||||
});
|
||||
|
||||
it(`should create`, async(inject([HttpTestingController],
|
||||
(httpClient: HttpTestingController) => {
|
||||
expect(component).toBeTruthy();
|
||||
})));
|
||||
});
|
||||
|
|
|
@ -3,6 +3,9 @@ import {Router, NavigationEnd} from '@angular/router';
|
|||
import {ApiService} from '../../services/api/api.service';
|
||||
import {NavbarModel} from './navbar-model';
|
||||
import {NavbarAbstract} from './navbar.abstract';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { Metadata } from '../../services/metadata/metadata.service';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-navbar-ui',
|
||||
|
@ -10,7 +13,10 @@ import {NavbarAbstract} from './navbar.abstract';
|
|||
styleUrls: []
|
||||
})
|
||||
export class NavbarUiComponent implements OnInit {
|
||||
constructor(protected api: ApiService, protected router: Router) {
|
||||
private navbarSubscription: Subscription;
|
||||
public navigationMetadata: any;
|
||||
|
||||
constructor(protected metadata: Metadata, protected api: ApiService, protected router: Router) {
|
||||
NavbarUiComponent.instances.push(this);
|
||||
}
|
||||
|
||||
|
@ -70,8 +76,23 @@ export class NavbarUiComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
let navbar = new NavbarAbstract();
|
||||
|
||||
const menuItemThreshold = 5;
|
||||
/*
|
||||
* TODO : this call should be moved elsewhere once
|
||||
* we have the final structure using cache
|
||||
*/
|
||||
this.navbarSubscription = this.metadata
|
||||
.getNavigation()
|
||||
.subscribe((data) => {
|
||||
this.navigationMetadata = data;
|
||||
if (data && data.NonGroupedTabs) {
|
||||
this.navbar.buildMenu(data.NonGroupedTabs, menuItemThreshold);
|
||||
}
|
||||
});
|
||||
|
||||
const navbar = new NavbarAbstract();
|
||||
this.setNavbar(navbar);
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {Apollo, QueryRef} from 'apollo-angular';
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RecordGQL {
|
||||
|
||||
|
@ -12,15 +12,15 @@ export class RecordGQL {
|
|||
|
||||
/**
|
||||
* Fetch data either from backend or cache
|
||||
* @param module
|
||||
* @param id
|
||||
* @param metadata
|
||||
* @param module to get from
|
||||
* @param id of the record
|
||||
* @param metadata with the fields to ask for
|
||||
*/
|
||||
public fetch(module: string, id: string, metadata: { fields: string[] }): QueryRef<any> {
|
||||
const fields = metadata.fields;
|
||||
const iriId = this.formatId(module, id);
|
||||
|
||||
let queryOptions = {
|
||||
const queryOptions = {
|
||||
query: gql`
|
||||
query ${module}($id: ID!) {
|
||||
${module}(id: $id) {
|
||||
|
@ -36,32 +36,10 @@ export class RecordGQL {
|
|||
return this.apollo.watchQuery(queryOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get in memory cache data
|
||||
* @param module
|
||||
* @param id
|
||||
* @param metadata
|
||||
*/
|
||||
public get(module: string, id: string, metadata: { fields: string[] }): any {
|
||||
const fields = metadata.fields;
|
||||
const iriId = this.formatId(module, id);
|
||||
|
||||
let queryOptions = {
|
||||
id: iriId,
|
||||
fragment: gql`
|
||||
fragment my${module} on ${module} {
|
||||
${fields.join('\n')}
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
return this.apollo.getClient().readFragment(queryOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format id
|
||||
* @param module
|
||||
* @param id
|
||||
* @param module name
|
||||
* @param id of the record
|
||||
*/
|
||||
protected formatId(module: string, id: string) {
|
||||
return `/api/${module}s/${id}`;
|
||||
|
|
18
core/app/src/services/metadata/metadata.service.ts
Normal file
18
core/app/src/services/metadata/metadata.service.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
|
||||
import { NavigationMetadata } from './navigation/navigation.metadata.service';
|
||||
|
||||
|
||||
/**
|
||||
* Facade like service to retrieve metadata
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class Metadata {
|
||||
constructor(private navigationMetadata: NavigationMetadata) {}
|
||||
|
||||
public getNavigation() {
|
||||
return this.navigationMetadata.fetch();
|
||||
}
|
||||
}
|
74
core/app/src/services/metadata/metadata.spec.ts
Normal file
74
core/app/src/services/metadata/metadata.spec.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {async, ComponentFixture, getTestBed, inject, TestBed} from '@angular/core/testing';
|
||||
import {Metadata} from '@services/metadata/metadata.service';
|
||||
import {NavigationMetadata} from '@services/metadata/navigation/navigation.metadata.service';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
describe('Metadata Service', () => {
|
||||
let injector: TestBed;
|
||||
let service: Metadata;
|
||||
|
||||
describe('Navigation Metadata', () => {
|
||||
|
||||
const navigationMockData = {
|
||||
navbar: {
|
||||
NonGroupedTabs: {
|
||||
Contacts: 'Contacts',
|
||||
Accounts: 'Accounts'
|
||||
},
|
||||
groupedTabs: {
|
||||
Sales: {
|
||||
modules: {
|
||||
Accounts: 'Accounts',
|
||||
Contacts: 'Contacts',
|
||||
}
|
||||
},
|
||||
},
|
||||
userActionMenu: [
|
||||
{
|
||||
label: 'Profile',
|
||||
url: 'index.php?module=Users&action=EditView&record=1',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
label: 'Employees',
|
||||
url: 'index.php?module=Employees&action=index',
|
||||
submenu: []
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
const navigationMetadata = ({
|
||||
fetch() {
|
||||
return of(navigationMockData.navbar);
|
||||
}
|
||||
} as any) as NavigationMetadata;
|
||||
*/
|
||||
|
||||
// Create a fake TwainService object with a `getQuote()` spy
|
||||
const navigationMetadata = jasmine.createSpyObj('NavigationMetadata', ['fetch']);
|
||||
// Make the spy return a synchronous Observable with the test data
|
||||
navigationMetadata.fetch.and.returnValue( of(navigationMockData.navbar) );
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: NavigationMetadata, useValue: navigationMetadata}],
|
||||
});
|
||||
|
||||
injector = getTestBed();
|
||||
service = injector.get(Metadata);
|
||||
});
|
||||
|
||||
|
||||
it('#getNavigation', (done: DoneFn) => {
|
||||
service.getNavigation().subscribe(data => {
|
||||
expect(data).toEqual(jasmine.objectContaining(navigationMockData.navbar));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
import { RecordGQL } from '../../api/graphql-api/api.record.get';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NavigationMetadata {
|
||||
protected resourceName = 'navbar';
|
||||
protected fieldsMetadata = {
|
||||
fields: [
|
||||
'NonGroupedTabs',
|
||||
'groupedTabs',
|
||||
'userActionMenu'
|
||||
]
|
||||
};
|
||||
|
||||
constructor(private recordGQL: RecordGQL) {}
|
||||
|
||||
public fetch(): Observable<any> {
|
||||
const id = '1';
|
||||
|
||||
return this.recordGQL
|
||||
.fetch(this.resourceName, id, this.fieldsMetadata)
|
||||
.valueChanges.pipe(map(({data}) => data.navbar));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import {getTestBed, TestBed} from '@angular/core/testing';
|
||||
import {
|
||||
ApolloTestingModule,
|
||||
ApolloTestingController,
|
||||
} from 'apollo-angular/testing';
|
||||
import gql from 'graphql-tag';
|
||||
import {NavigationMetadata} from '@services/metadata/navigation/navigation.metadata.service';
|
||||
|
||||
describe('Navigation Metadata Service', () => {
|
||||
let injector: TestBed;
|
||||
let service: NavigationMetadata;
|
||||
let controller: ApolloTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ApolloTestingModule],
|
||||
});
|
||||
|
||||
injector = getTestBed();
|
||||
controller = injector.get(ApolloTestingController);
|
||||
service = injector.get(NavigationMetadata);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
controller.verify();
|
||||
});
|
||||
|
||||
it('#fetch',
|
||||
(done: DoneFn) => {
|
||||
|
||||
const navigationMockData = {
|
||||
navbar: {
|
||||
NonGroupedTabs: {
|
||||
Contacts: 'Contacts',
|
||||
Accounts: 'Accounts'
|
||||
},
|
||||
groupedTabs: {
|
||||
Sales: {
|
||||
modules: {
|
||||
Accounts: 'Accounts',
|
||||
Contacts: 'Contacts',
|
||||
}
|
||||
},
|
||||
},
|
||||
userActionMenu: [
|
||||
{
|
||||
label: 'Profile',
|
||||
url: 'index.php?module=Users&action=EditView&record=1',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
label: 'Employees',
|
||||
url: 'index.php?module=Employees&action=index',
|
||||
submenu: []
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
service.fetch().subscribe(data => {
|
||||
expect(data).toEqual(jasmine.objectContaining(navigationMockData.navbar));
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
const op = controller.expectOne(gql`
|
||||
query navbar($id: ID!) {
|
||||
navbar(id: $id) {
|
||||
NonGroupedTabs
|
||||
groupedTabs
|
||||
userActionMenu
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
// Assert that one of variables is Mr Apollo.
|
||||
expect(op.operation.variables.id).toEqual('/api/navbars/1');
|
||||
|
||||
// Respond with mock data, causing Observable to resolve.
|
||||
op.flush({
|
||||
data: navigationMockData
|
||||
});
|
||||
|
||||
// Finally, assert that there are no outstanding operations.
|
||||
controller.verify();
|
||||
});
|
||||
});
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue