mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 07:50:08 +08:00
Add message modal component
- Add re-usable message modal component -- Allow for passing a message labelKey -- Allow for configuring an array of buttons - Declare component as an entry component - Add karma/jasmine tests
This commit is contained in:
parent
690162a2ce
commit
b6bbbf26ed
6 changed files with 258 additions and 2 deletions
|
@ -40,6 +40,8 @@ import {ColumnChooserComponent} from '@components/columnchooser/columnchooser.co
|
|||
import {AppInit} from '@app/app-initializer';
|
||||
import {AuthService} from '@services/auth/auth.service';
|
||||
import {GraphQLError} from 'graphql';
|
||||
import {MessageModalComponent} from '@components/modal/components/message-modal/message-modal.component';
|
||||
import {MessageModalModule} from '@components/modal/components/message-modal/message-modal.module';
|
||||
|
||||
export const initializeApp = (appInitService: AppInit) => (): Promise<any> => appInitService.init();
|
||||
|
||||
|
@ -67,7 +69,8 @@ export const initializeApp = (appInitService: AppInit) => (): Promise<any> => ap
|
|||
ImageModule,
|
||||
BrowserAnimationsModule,
|
||||
NgbModule,
|
||||
FullPageSpinnerModule
|
||||
FullPageSpinnerModule,
|
||||
MessageModalModule
|
||||
],
|
||||
providers: [
|
||||
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
|
||||
|
@ -82,7 +85,7 @@ export const initializeApp = (appInitService: AppInit) => (): Promise<any> => ap
|
|||
}
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
entryComponents: [ColumnChooserComponent]
|
||||
entryComponents: [ColumnChooserComponent, MessageModalComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(apollo: Apollo, httpLink: HttpLink, protected auth: AuthService) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<scrm-modal [closable]="true"
|
||||
[close]="closeButton"
|
||||
[title]="titleKey"
|
||||
klass="message-modal">
|
||||
|
||||
<div class="p-3" modal-body>
|
||||
<span><scrm-label [labelKey]="textKey"></scrm-label></span>
|
||||
</div>
|
||||
|
||||
<div modal-footer>
|
||||
<scrm-modal-button-group [activeModal]="activeModal"
|
||||
[config$]="buttonGroup$">
|
||||
</scrm-modal-button-group>
|
||||
</div>
|
||||
</scrm-modal>
|
|
@ -0,0 +1,159 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MessageModalComponent} from './message-modal.component';
|
||||
import {MessageModalModule} from '@components/modal/components/message-modal/message-modal.module';
|
||||
import {LanguageStore} from '@store/language/language.store';
|
||||
import {languageStoreMock} from '@store/language/language.store.spec.mock';
|
||||
import {SystemConfigStore} from '@store/system-config/system-config.store';
|
||||
import {systemConfigStoreMock} from '@store/system-config/system-config.store.spec.mock';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ModalButtonInterface} from '@app-common/components/modal/modal.model';
|
||||
import {NgbModalRef} from '@ng-bootstrap/ng-bootstrap/modal/modal-ref';
|
||||
|
||||
@Component({
|
||||
selector: 'messages-modal-test-host-component',
|
||||
template: '<div></div>'
|
||||
})
|
||||
class MessageModalTestHostComponent implements OnInit {
|
||||
modal: NgbModalRef;
|
||||
cancelClicked = 0;
|
||||
okClicked = 0;
|
||||
|
||||
constructor(public modalService: NgbModal) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.modal = this.modalService.open(MessageModalComponent);
|
||||
|
||||
this.modal.componentInstance.textKey = 'WARN_UNSAVED_CHANGES';
|
||||
this.modal.componentInstance.buttons = [
|
||||
{
|
||||
labelKey: 'LBL_CANCEL',
|
||||
klass: ['btn-secondary'],
|
||||
onClick: activeModal => {
|
||||
this.cancelClicked++;
|
||||
activeModal.dismiss();
|
||||
}
|
||||
} as ModalButtonInterface,
|
||||
{
|
||||
labelKey: 'LBL_OK',
|
||||
klass: ['btn-main'],
|
||||
onClick: activeModal => {
|
||||
this.okClicked++;
|
||||
activeModal.close();
|
||||
}
|
||||
} as ModalButtonInterface,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
describe('MessageModalComponent', () => {
|
||||
let component: MessageModalTestHostComponent;
|
||||
let fixture: ComponentFixture<MessageModalTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MessageModalTestHostComponent],
|
||||
imports: [
|
||||
MessageModalModule,
|
||||
],
|
||||
providers: [
|
||||
{provide: LanguageStore, useValue: languageStoreMock},
|
||||
{provide: SystemConfigStore, useValue: systemConfigStoreMock}
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MessageModalTestHostComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should have modal', () => {
|
||||
expect(component).toBeTruthy();
|
||||
|
||||
const modal = document.getElementsByClassName('message-modal');
|
||||
|
||||
expect(modal).toBeTruthy();
|
||||
expect(modal.length).toEqual(1);
|
||||
component.modal.close();
|
||||
});
|
||||
|
||||
it('should have message', () => {
|
||||
expect(component).toBeTruthy();
|
||||
|
||||
const modal = document.getElementsByClassName('message-modal');
|
||||
|
||||
expect(modal).toBeTruthy();
|
||||
expect(modal.length).toEqual(1);
|
||||
|
||||
const body = modal.item(0).getElementsByClassName('modal-body');
|
||||
|
||||
expect(body).toBeTruthy();
|
||||
expect(body.length).toEqual(1);
|
||||
|
||||
expect(body.item(0).textContent).toContain('Are you sure you want to navigate away from this record?');
|
||||
|
||||
component.modal.close();
|
||||
});
|
||||
|
||||
it('should have a clickable cancel button', () => {
|
||||
expect(component).toBeTruthy();
|
||||
|
||||
const modal = document.getElementsByClassName('message-modal');
|
||||
|
||||
expect(modal).toBeTruthy();
|
||||
expect(modal.length).toEqual(1);
|
||||
|
||||
const footer = modal.item(0).getElementsByClassName('modal-footer');
|
||||
|
||||
expect(footer).toBeTruthy();
|
||||
expect(footer.length).toEqual(1);
|
||||
|
||||
const buttons = footer.item(0).getElementsByTagName('button');
|
||||
|
||||
expect(buttons).toBeTruthy();
|
||||
expect(buttons.length).toEqual(2);
|
||||
|
||||
const cancelButton = buttons.item(0);
|
||||
|
||||
expect(cancelButton.textContent).toContain('Cancel');
|
||||
expect(cancelButton.className).toContain('btn-secondary');
|
||||
|
||||
cancelButton.click();
|
||||
|
||||
expect(component.cancelClicked).toEqual(1);
|
||||
});
|
||||
|
||||
it('should have a clickable ok button', () => {
|
||||
expect(component).toBeTruthy();
|
||||
|
||||
const modal = document.getElementsByClassName('message-modal');
|
||||
|
||||
expect(modal).toBeTruthy();
|
||||
expect(modal.length).toEqual(1);
|
||||
|
||||
const footer = modal.item(0).getElementsByClassName('modal-footer');
|
||||
|
||||
expect(footer).toBeTruthy();
|
||||
expect(footer.length).toEqual(1);
|
||||
|
||||
const buttons = footer.item(0).getElementsByTagName('button');
|
||||
|
||||
expect(buttons).toBeTruthy();
|
||||
expect(buttons.length).toEqual(2);
|
||||
|
||||
const okButton = buttons.item(1);
|
||||
|
||||
expect(okButton.textContent).toContain('OK');
|
||||
expect(okButton.className).toContain('btn-main');
|
||||
|
||||
okButton.click();
|
||||
|
||||
expect(component.okClicked).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {animate, transition, trigger} from '@angular/animations';
|
||||
import {ButtonInterface} from '@app-common/components/button/button.model';
|
||||
import {
|
||||
AnyModalButtonInterface,
|
||||
ModalButtonGroupInterface,
|
||||
ModalCloseFeedBack
|
||||
} from '@app-common/components/modal/modal.model';
|
||||
import {Observable, of} from 'rxjs';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-message-modal',
|
||||
templateUrl: './message-modal.component.html',
|
||||
styleUrls: [],
|
||||
animations: [
|
||||
trigger('modalFade', [
|
||||
transition('void <=> *', [
|
||||
animate('800ms')
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
export class MessageModalComponent implements OnInit {
|
||||
|
||||
@Input() titleKey: string;
|
||||
@Input() textKey: string;
|
||||
@Input() buttons: AnyModalButtonInterface[] = [];
|
||||
|
||||
buttonGroup$: Observable<ModalButtonGroupInterface>;
|
||||
closeButton: ButtonInterface;
|
||||
|
||||
constructor(public activeModal: NgbActiveModal) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.buttonGroup$ = of({
|
||||
buttons: this.buttons
|
||||
} as ModalButtonGroupInterface);
|
||||
|
||||
this.closeButton = {
|
||||
klass: ['btn', 'btn-outline-light', 'btn-sm'],
|
||||
onClick: (): void => {
|
||||
this.activeModal.close({
|
||||
type: 'close-button'
|
||||
} as ModalCloseFeedBack);
|
||||
}
|
||||
} as ButtonInterface;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MessageModalComponent} from './message-modal.component';
|
||||
import {ModalModule} from '@components/modal/components/modal/modal.module';
|
||||
import {ButtonGroupModule} from '@components/button-group/button-group.module';
|
||||
import {LabelModule} from '@components/label/label.module';
|
||||
import {ModalButtonGroupModule} from '@components/modal/components/modal-button-group/modal-button-group.module';
|
||||
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [MessageModalComponent],
|
||||
exports: [MessageModalComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ModalModule,
|
||||
ButtonGroupModule,
|
||||
LabelModule,
|
||||
ModalButtonGroupModule,
|
||||
NgbModule
|
||||
]
|
||||
})
|
||||
export class MessageModalModule {
|
||||
}
|
|
@ -11,6 +11,7 @@ export const languageMockData = {
|
|||
LBL_HISTORY: 'History',
|
||||
LBL_SAVE_BUTTON_LABEL: 'Save',
|
||||
LBL_CANCEL: 'Cancel',
|
||||
LBL_OK: 'OK',
|
||||
LBL_SEARCH_REAULTS_TITLE: 'Results',
|
||||
ERR_SEARCH_INVALID_QUERY: 'An error has occurred while performing the search. Your query syntax might not be valid.',
|
||||
ERR_SEARCH_NO_RESULTS: 'No results matching your search criteria. Try broadening your search.',
|
||||
|
@ -45,6 +46,7 @@ export const languageMockData = {
|
|||
LBL_OPPORTUNITIES_TOTAL: 'Total Opportunity Value',
|
||||
LBL_INSIGHTS: 'Insights',
|
||||
LBL_NO_DATA: 'No Data',
|
||||
WARN_UNSAVED_CHANGES: 'Are you sure you want to navigate away from this record?',
|
||||
},
|
||||
appListStrings: {
|
||||
// eslint-disable-next-line camelcase,@typescript-eslint/camelcase
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue