diff --git a/katalon-chrome-scripts/ql-contacts.html b/katalon-chrome-scripts/ql-contacts.html new file mode 100644 index 0000000..c8db0b7 --- /dev/null +++ b/katalon-chrome-scripts/ql-contacts.html @@ -0,0 +1,38 @@ + + + + + + ql-contacts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fill Out Form
click//i
typeid=companyGoogle
typeid=firstNameDennis
typeid=lastNamePorter
typeid=phone3135551212
typeid=emailjohen@john.com
typeid=address1234 Easy St
typeid=cityDetroit
typeid=stateMi
typeid=zipCode12345
click//button[@id='create-contact-submit']/span
+ + \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b3bfaca..a0582a9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -29,6 +29,7 @@ import { ContactAddComponent } from './view/contacts/contact-add/contact-add.com import { NameCellComponent } from './view/contacts/contact-cells/name-cell/name-cell.component'; import { NameHeaderComponent } from './view/contacts/contact-cells/name-header/name-header.component'; import { PhoneCellComponent } from './view/contacts/contact-cells/phone-cell/phone-cell.component'; +import { ContactColumnsComponent } from './view/contacts/contact-columns/contact-columns.component'; import { ContactDetailsComponent } from './view/contacts/contact-details/contact-details.component'; import { DetailItemComponent } from './view/contacts/contact-details/detail-item/detail-item.component'; import { ContactFormComponent } from './view/contacts/contact-form/contact-form.component'; @@ -54,6 +55,7 @@ import { ViewComponent } from './view/view.component'; NameCellComponent, NameHeaderComponent, PhoneCellComponent, + ContactColumnsComponent, ], entryComponents: [ ContactFormComponent, diff --git a/src/app/interfaces/shared.interfaces.ts b/src/app/interfaces/shared.interfaces.ts index 3680f11..e7ee158 100644 --- a/src/app/interfaces/shared.interfaces.ts +++ b/src/app/interfaces/shared.interfaces.ts @@ -14,6 +14,11 @@ export interface IContact extends IBaseContact { index: number; } +export interface ITableColumnConfig { + name: string; + header: string; +} + export interface IFormContact extends IBaseContact { address2: string; city: string; diff --git a/src/app/view/contacts/contact-columns/contact-columns.component.html b/src/app/view/contacts/contact-columns/contact-columns.component.html new file mode 100644 index 0000000..23fa9c6 --- /dev/null +++ b/src/app/view/contacts/contact-columns/contact-columns.component.html @@ -0,0 +1,35 @@ + + + + + + +
+ + + + +
{{column.header}}
+ +
+ + + + + + +
+ + + + + + add_circle + +
{{data[column.name]}}
+ +
+ + + +
diff --git a/src/app/view/contacts/contact-columns/contact-columns.component.scss b/src/app/view/contacts/contact-columns/contact-columns.component.scss new file mode 100644 index 0000000..f84090f --- /dev/null +++ b/src/app/view/contacts/contact-columns/contact-columns.component.scss @@ -0,0 +1,29 @@ +@import 'src/scss/variables'; + +th.mat-header-cell, +td.mat-cell { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +th.mat-header-cell { + font-size: 1.4em; +} + +.mat-column-phone, +.mat-column-more { + text-align: center; +} + +.mat-column-more { + .show-more { + position: relative; + top: 5px; + color: lighten($color: $primary, $amount: 30); + } +} + +.mat-cell { + border-bottom-width: 0; +} diff --git a/src/app/view/contacts/contact-columns/contact-columns.component.spec.ts b/src/app/view/contacts/contact-columns/contact-columns.component.spec.ts new file mode 100644 index 0000000..1dc0c61 --- /dev/null +++ b/src/app/view/contacts/contact-columns/contact-columns.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContactColumnsComponent } from './contact-columns.component'; + +describe('ContactColumnsComponent', () => { + let component: ContactColumnsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContactColumnsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContactColumnsComponent); + component = fixture.componentInstance; + component.column = { + name: 'hey', + header: 'dude', + } + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/view/contacts/contact-columns/contact-columns.component.ts b/src/app/view/contacts/contact-columns/contact-columns.component.ts new file mode 100644 index 0000000..11b9d59 --- /dev/null +++ b/src/app/view/contacts/contact-columns/contact-columns.component.ts @@ -0,0 +1,24 @@ +import { AfterViewInit, Component, Input, Optional, ViewChild } from '@angular/core'; +import { MatColumnDef, MatTable } from '@angular/material/table'; +import { ITableColumnConfig } from 'src/app/interfaces/shared.interfaces'; + +@Component({ + selector: 'app-contact-columns', + templateUrl: './contact-columns.component.html', + styleUrls: [ './contact-columns.component.scss' ], +}) +export class ContactColumnsComponent implements AfterViewInit { + + @Input() mobile: boolean; + @Input() column: ITableColumnConfig; + + @ViewChild(MatColumnDef) columnDef: MatColumnDef; + + constructor(@Optional() public table: MatTable) { } + + ngAfterViewInit() { + if (this.table) { + this.table.addColumnDef(this.columnDef); + } + } +} diff --git a/src/app/view/contacts/contact-form/contact-form.component.spec.ts b/src/app/view/contacts/contact-form/contact-form.component.spec.ts index 9190c32..fb1506e 100644 --- a/src/app/view/contacts/contact-form/contact-form.component.spec.ts +++ b/src/app/view/contacts/contact-form/contact-form.component.spec.ts @@ -94,6 +94,8 @@ describe('ContactFormComponent', () => { }); it('should populate the state\'s autocomplete property', () => { + component.formData = CONTACT_SAMPLE_FORM_DATA; + fixture.detectChanges(); const stateObject = component.formData.find(n => n.config.id === 'state'); expect(stateObject.autocomplete.length).toBe(1); diff --git a/src/app/view/contacts/contacts-table.config.ts b/src/app/view/contacts/contacts-table.config.ts index 06770db..5f69146 100644 --- a/src/app/view/contacts/contacts-table.config.ts +++ b/src/app/view/contacts/contacts-table.config.ts @@ -1,22 +1,22 @@ export const CONTACT_TABLE_CONFIG = [ { - col: 'name', + name: 'name', header: 'Contact', }, { - col: 'phone', + name: 'phone', header: 'Phone', }, { - col: 'company', + name: 'company', header: 'Company', }, { - col: 'email', + name: 'email', header: 'Email', }, { - col: 'more', + name: 'more', header: '', }, ]; diff --git a/src/app/view/contacts/contacts.component.html b/src/app/view/contacts/contacts.component.html index be8ff06..93251b4 100644 --- a/src/app/view/contacts/contacts.component.html +++ b/src/app/view/contacts/contacts.component.html @@ -1,45 +1,18 @@
+ +
- - - - - - - - - + @@ -56,7 +29,6 @@ - diff --git a/src/app/view/contacts/contacts.component.scss b/src/app/view/contacts/contacts.component.scss index 7466f36..b849a9e 100644 --- a/src/app/view/contacts/contacts.component.scss +++ b/src/app/view/contacts/contacts.component.scss @@ -15,30 +15,6 @@ table.mat-table { width: 100%; - th.mat-header-cell, - td.mat-cell { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - - th.mat-header-cell { - font-size: 1.4em; - } - - .mat-column-phone, - .mat-column-more { - text-align: center; - } - - .mat-column-more { - .show-more { - position: relative; - top: 5px; - color: lighten($color: $primary, $amount: 30); - } - } - .mat-column-phone { a { color: $accent; diff --git a/src/app/view/contacts/contacts.component.spec.ts b/src/app/view/contacts/contacts.component.spec.ts index f4154fa..bcfb822 100644 --- a/src/app/view/contacts/contacts.component.spec.ts +++ b/src/app/view/contacts/contacts.component.spec.ts @@ -16,18 +16,9 @@ describe('ContactsComponent', () => { let component: ContactsComponent; let viewport: FakeViewportService; let fixture: ComponentFixture; - const oneContact: IContact = { - _id: '5de91c005b98615393e74931', - index: 0, - firstName: 'Browning', - lastName: 'Graham', - company: 'MELBACOR', - email: 'browninggraham@melbacor.com', - phone: '+1 (906) 585-2525', - address: '920 Hastings Street, Roosevelt, Puerto Rico, 5573', - }; + const oneElement = { test: 'value' }; const observers = []; - const contactsObservable: Observable = new Observable((observer) => { + const observableData: Observable = new Observable((observer) => { observers.push(observer); return { unsubscribe() { } }; }); @@ -44,9 +35,6 @@ describe('ContactsComponent', () => { // small third party components whose code doesn't need to be "double tested" // by us. So we mock them.) MockComponent({ selector: 'app-contact-details', inputs: [ 'contact' ] }), - MockComponent({ selector: 'app-name-cell', inputs: [ 'contact', 'mobile' ] }), - MockComponent({ selector: 'app-name-header', inputs: [ 'mobile' ] }), - MockComponent({ selector: 'app-phone-cell', inputs: [ 'phone' ] }), MockComponent({ selector: 'mat-icon' }), MockComponent({ selector: 'mat-spinner' }), MockComponent({ selector: 'mat-paginator', inputs: [ 'length', 'pageSize', 'pageIndex' ] }), @@ -76,7 +64,7 @@ describe('ContactsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ContactsComponent); component = fixture.componentInstance; - component.contacts = contactsObservable; + component.data = observableData; fixture.detectChanges(); }); @@ -100,26 +88,12 @@ describe('ContactsComponent', () => { }) ); - it('should user the viewport service for responsive data', - fakeAsync(() => { - viewport.setFullscreen(); - tick(); - expect(viewport.mobile()).toBe(false); - expect(component.mobile()).toBe(false); - - viewport.setMobile(); - tick(); - expect(viewport.mobile()).toBe(true); - expect(component.mobile()).toBe(true); - }) - ); - - it('retrieve the contacts list, and populate the data source', + it('retrieve data, and populate the data source', fakeAsync(() => { expect(component.dataLength).toBeUndefined(); expect(component.dataSource).toBeUndefined(); - observers.forEach(o => o.next([ oneContact ])); + observers.forEach(o => o.next([ oneElement ])); tick(); expect(component.dataLength).toBe(1); diff --git a/src/app/view/contacts/contacts.component.ts b/src/app/view/contacts/contacts.component.ts index 0bf33c7..86c75b5 100644 --- a/src/app/view/contacts/contacts.component.ts +++ b/src/app/view/contacts/contacts.component.ts @@ -1,3 +1,10 @@ +/** + * @TODO - Convert this component into a generic table component with: + * - A configurable expandable row/drawer + * - Configurable columns that collapse and have different views on mobile. + * - Usage of ng-content (mabye ngComponentOutlet?) so you can provide + * unique table cell templates. + */ import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; @@ -5,8 +12,7 @@ import { MatTable, MatTableDataSource } from '@angular/material/table'; import { Observable, Subscription } from 'rxjs'; import { skipWhile } from 'rxjs/operators'; -import { IContact, IViewportService } from 'src/app/interfaces/shared.interfaces'; -import { ViewportConstants } from 'src/app/services/viewport/viewport.constants'; +import { IContact, ITableColumnConfig, IViewportService } from 'src/app/interfaces/shared.interfaces'; import { ITOKENS } from 'src/app/shared/injection.tokens'; import { CONTACT_TABLE_CONFIG } from './contacts-table.config'; @@ -26,7 +32,7 @@ export class ContactsComponent implements OnInit, OnDestroy { @ViewChild(MatTable) table: MatTable; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; - @Input() public contacts: Observable; + @Input() public data: Observable; public dataSource: MatTableDataSource; public dataLength: number; @@ -35,13 +41,13 @@ export class ContactsComponent implements OnInit, OnDestroy { public detailColumns: string[] = []; public expanded: IContact; - public tableConfig = CONTACT_TABLE_CONFIG; - public fullColumns: string[] = [ 'name', 'phone', 'company', 'more' ]; + public columns: ITableColumnConfig[] = CONTACT_TABLE_CONFIG; + public fullscreenColumns: string[] = [ 'name', 'phone', 'company', 'more' ]; public mobileColumns: string[] = [ 'name' ]; public subscriptions: Subscription[] = []; constructor( - @Inject(ITOKENS.IViewportService) private viewport: IViewportService, + @Inject(ITOKENS.IViewportService) public viewport: IViewportService, ) { } ngOnInit() { @@ -51,7 +57,7 @@ export class ContactsComponent implements OnInit, OnDestroy { ); this.subscriptions.push( - this.contacts + this.data .pipe(skipWhile(c => !(Array.isArray(c) && c.length) )) .subscribe(this.contactsHandler.bind(this)) ); @@ -63,26 +69,14 @@ export class ContactsComponent implements OnInit, OnDestroy { this.dataSource.paginator = this.paginator; } - breakpointHandler(breakpoint) { - if (breakpoint === ViewportConstants.STATES.MOBILE) { - this.setMobileLayout(); + breakpointHandler() { + if (this.viewport.mobile()) { + this.displayedColumns = this.mobileColumns; } else { - this.setFullLayout(); + this.displayedColumns = this.fullscreenColumns; } } - mobile() { - return this.viewport.mobile(); - } - - setMobileLayout() { - this.displayedColumns = this.mobileColumns; - } - - setFullLayout() { - this.displayedColumns = this.fullColumns; - } - ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()); } diff --git a/src/app/view/view.component.html b/src/app/view/view.component.html index ea587d9..2a7637f 100644 --- a/src/app/view/view.component.html +++ b/src/app/view/view.component.html @@ -1,6 +1,6 @@
- +
- -
- - - -
{{cfg.header}}
- -
- -
- -
- - - - - - add_circle - -
{{data[cfg.col]}}
- -
- -