mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-1986] Content matadata editing phase II. (#2796)
* Aspects collection * Fetch only those metadata aspects which are defined in the application config * Aspect property filtering first round * Addig wildcard support for preset, default preset fallback to wildcard, and logging * Add white list service * Renaming services * ContentMetadataService and CardViewItemDispatcherComponent update * Observables... Observables everywhere... * Propers CardViewAspect * Defining more interfaces * Dummy data and expansions panels * Fix build attempt & proper panel expansion * Folder restructuring * Add different type mappings * Restructuring Card view component * Fix build * More ECM types supported * Validators first phase, extraction of interfaces, world domination preparation * Validators phase II. * Integer and float validators * Hide empty text items and validation message foundation * Validation error messages for text item view, small style changes * Update date item * bool item component * Datetimepicker npm module * Datetime model * Add mapping for datetime * Small fixes * Passing down preset * Adding forgotten package.json entry * Adding some tests for wrapper card component * content-metadata.component tests * Covering some edge cases, adding some tests * Fix cardview component's test * Add datetimepicker to demoshell * card view component show empty values by default * displayEmpty dependency injection * remove table like design from cardview * Using alfresco-js-api instead of spike * Remove spike folder and contents * Fix test * Cardview updated docs * Content metadata md * Fix review issues * Fix the packagr issue
This commit is contained in:
committed by
Eugenio Romano
parent
994041fb23
commit
783f7f0497
@@ -0,0 +1,10 @@
|
||||
<ng-container *ngIf="!property.isEmpty() || isEditable()">
|
||||
<div class="adf-property-label">{{ property.label | translate }}</div>
|
||||
<div class="adf-property-value">
|
||||
<mat-checkbox
|
||||
[checked]="property.displayValue"
|
||||
[disabled]="!isEditable()"
|
||||
(change)="changed($event)">
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</ng-container>
|
@@ -0,0 +1,2 @@
|
||||
.adf {
|
||||
}
|
@@ -0,0 +1,243 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { MatCheckboxChange, MatCheckbox } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { LogService } from '../../../services/log.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewBoolItemComponent } from './card-view-boolitem.component';
|
||||
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
||||
|
||||
describe('CardViewBoolItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewBoolItemComponent>;
|
||||
let component: CardViewBoolItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MaterialModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewBoolItemComponent
|
||||
],
|
||||
providers: [
|
||||
AppConfigService,
|
||||
CardViewUpdateService,
|
||||
LogService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewBoolItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.property = new CardViewBoolItemModel({
|
||||
label: 'Boolean label',
|
||||
value: true,
|
||||
key: 'boolkey',
|
||||
default: false,
|
||||
editable: false
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
|
||||
it('should render the label and value if the property is editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let label = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(label).not.toBeNull();
|
||||
expect(label.nativeElement.innerText).toBe('Boolean label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT render the label and value if the property is NOT editable and doesn\'t have a proper boolean value set' , () => {
|
||||
component.editable = true;
|
||||
component.property.value = undefined;
|
||||
component.property.editable = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let label = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(label).toBeNull();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the label and value if the property is NOT editable but has a proper boolean value set' , () => {
|
||||
component.editable = true;
|
||||
component.property.value = false;
|
||||
component.property.editable = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let label = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(label).not.toBeNull();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should render ticked checkbox if property\'s value is true', () => {
|
||||
component.property.value = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should render ticked checkbox if property\'s value is not set but default is true and editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = undefined;
|
||||
component.property.default = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should render unticked checkbox if property\'s value is false', () => {
|
||||
component.property.value = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('should render unticked checkbox if property\'s value is not set but default is false and editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = undefined;
|
||||
component.property.default = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('should render enabled checkbox if property and component are both editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.hasAttribute('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('should render disabled checkbox if property is not editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = false;
|
||||
component.property.value = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.hasAttribute('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render disabled checkbox if component is not editable', () => {
|
||||
component.editable = false;
|
||||
component.property.editable = true;
|
||||
component.property.value = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.hasAttribute('disabled')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = undefined;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should trigger the update event when changing the checkbox', () => {
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
|
||||
component.changed(<MatCheckboxChange> {checked: true});
|
||||
|
||||
expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, true);
|
||||
});
|
||||
|
||||
it('should update the propery\'s value after a changed', async(() => {
|
||||
component.property.value = true;
|
||||
|
||||
component.changed(<MatCheckboxChange> {checked: false});
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.property.value).toBe(false);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should trigger an update event on the CardViewUpdateService [integration]', (done) => {
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
component.property.value = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({ boolkey: true });
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
const labelElement = fixture.debugElement.query(By.directive(MatCheckbox)).nativeElement.querySelector('label');
|
||||
labelElement.click();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view-boolitem',
|
||||
templateUrl: './card-view-boolitem.component.html',
|
||||
styleUrls: ['./card-view-boolitem.component.scss']
|
||||
})
|
||||
|
||||
export class CardViewBoolItemComponent {
|
||||
|
||||
@Input()
|
||||
property: CardViewBoolItemModel;
|
||||
|
||||
@Input()
|
||||
editable: boolean;
|
||||
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||
|
||||
isEditable() {
|
||||
return this.editable && this.property.editable;
|
||||
}
|
||||
|
||||
changed(change: MatCheckboxChange) {
|
||||
this.cardViewUpdateService.update(this.property, change.checked );
|
||||
this.property.value = change.checked;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<div class="adf-property-label" *ngIf="showProperty() || isEditable()">{{ property.label | translate }}</div>
|
||||
<div class="adf-property-value">
|
||||
<span *ngIf="!isEditable()">
|
||||
<span [attr.data-automation-id]="'card-dateitem-' + property.key">
|
||||
<span *ngIf="showProperty()">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
</span>
|
||||
<div *ngIf="isEditable()" class="adf-dateitem-editable">
|
||||
<div class="adf-dateitem-editable-controls">
|
||||
<span
|
||||
class="adf-datepicker-toggle"
|
||||
[attr.data-automation-id]="'datepicker-label-toggle-' + property.key"
|
||||
(click)="showDatePicker($event)">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<mat-datetimepicker-toggle
|
||||
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||
[for]="datetimePicker">
|
||||
</mat-datetimepicker-toggle>
|
||||
</div>
|
||||
|
||||
<input class="adf-invisible-date-input"
|
||||
[matDatetimepicker]="datetimePicker"
|
||||
[value]="valueDate"
|
||||
(dateChange)="onDateChanged($event)">
|
||||
|
||||
<mat-datetimepicker #datetimePicker
|
||||
[type]="property.type"
|
||||
timeInterval="5"
|
||||
[attr.data-automation-id]="'datepicker-' + property.key"
|
||||
[startAt]="valueDate">
|
||||
</mat-datetimepicker>
|
||||
</div>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
{{ property.default | translate }}
|
||||
</ng-template>
|
||||
</div>
|
@@ -0,0 +1,41 @@
|
||||
@mixin adf-card-view-dateitem-theme($theme) {
|
||||
|
||||
.adf {
|
||||
&-invisible-date-input {
|
||||
height: 2px;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: right;
|
||||
}
|
||||
|
||||
&-dateitem-editable {
|
||||
cursor: pointer;
|
||||
|
||||
&-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
button.mat-icon-button {
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover mat-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,233 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDatepickerModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import moment from 'moment-es6';
|
||||
import { AppConfigService } from '../../../index';
|
||||
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewDateItemComponent } from './card-view-dateitem.component';
|
||||
|
||||
describe('CardViewDateItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewDateItemComponent>;
|
||||
let component: CardViewDateItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
MatDatepickerModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
MatNativeDatetimeModule,
|
||||
MatDatetimepickerModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewDateItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardViewUpdateService,
|
||||
AppConfigService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewDateItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.property = new CardViewDateItemModel({
|
||||
label: 'Date label',
|
||||
value: new Date('07/10/2017'),
|
||||
key: 'datekey',
|
||||
default: '',
|
||||
format: '',
|
||||
editable: false
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render the label and value', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('Date label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('Jul 10 2017');
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, editable:false and displayEmpty is false', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
format: '',
|
||||
editable: false
|
||||
});
|
||||
component.editable = true;
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, editable:false and displayEmpty is true', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
format: '',
|
||||
editable: false
|
||||
});
|
||||
component.editable = true;
|
||||
component.displayEmpty = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and editable:true', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
format: '',
|
||||
editable: true
|
||||
});
|
||||
component.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should render value when editable:true', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('Jul 10 2017');
|
||||
});
|
||||
|
||||
it('should render the picker and toggle in case of editable:true', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||
expect(datePickerToggle).not.toBeNull('Datepicker toggle should be shown');
|
||||
});
|
||||
|
||||
it('should NOT render the picker and toggle in case of editable:false', () => {
|
||||
component.property.editable = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||
expect(datePickerToggle).toBeNull('Datepicker toggle should NOT be shown');
|
||||
});
|
||||
|
||||
it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => {
|
||||
component.editable = false;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||
expect(datePickerToggle).toBeNull('Datepicker toggle should NOT be shown');
|
||||
});
|
||||
|
||||
it('should open the dateXXXpicker when clicking on the label', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
spyOn(component.datepicker, 'open');
|
||||
|
||||
let datePickerLabelToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-label-toggle-${component.property.key}"]`));
|
||||
datePickerLabelToggle.triggerEventHandler('click', {});
|
||||
|
||||
expect(component.datepicker.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should trigger an update event on the CardViewUpdateService', (done) => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
const expectedDate = moment('Jul 10 2017', 'MMM DD YY');
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({datekey: expectedDate.toDate()});
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
component.onDateChanged({value: expectedDate});
|
||||
});
|
||||
|
||||
it('should update the propery\'s value after a succesful update attempt', async(() => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = null;
|
||||
const expectedDate = moment('Jul 10 2017', 'MMM DD YY');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.onDateChanged({value: expectedDate});
|
||||
|
||||
fixture.whenStable().then(
|
||||
(updateNotification) => {
|
||||
expect(component.property.value).toEqual(expectedDate.toDate());
|
||||
}
|
||||
);
|
||||
}));
|
||||
});
|
@@ -0,0 +1,95 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material';
|
||||
import { MatDatetimepicker } from '@mat-datetimepicker/core';
|
||||
import moment from 'moment-es6';
|
||||
import { Moment } from 'moment';
|
||||
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { UserPreferencesService } from '../../../services/user-preferences.service';
|
||||
import { MomentDateAdapter } from '../../../utils/momentDateAdapter';
|
||||
import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model';
|
||||
|
||||
@Component({
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }],
|
||||
selector: 'adf-card-view-dateitem',
|
||||
templateUrl: './card-view-dateitem.component.html',
|
||||
styleUrls: ['./card-view-dateitem.component.scss']
|
||||
})
|
||||
export class CardViewDateItemComponent implements OnInit {
|
||||
|
||||
public SHOW_FORMAT: string = 'MMM DD YY';
|
||||
|
||||
@Input()
|
||||
property: CardViewDateItemModel;
|
||||
|
||||
@Input()
|
||||
editable: boolean = false;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
|
||||
@ViewChild(MatDatetimepicker)
|
||||
public datepicker: MatDatetimepicker<any>;
|
||||
|
||||
valueDate: Moment;
|
||||
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService,
|
||||
private dateAdapter: DateAdapter<Moment>,
|
||||
private preferences: UserPreferencesService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preferences.locale$.subscribe((locale) => {
|
||||
this.dateAdapter.setLocale(locale);
|
||||
});
|
||||
let momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
|
||||
momentDateAdapter.overrideDisplyaFormat = this.SHOW_FORMAT;
|
||||
|
||||
if (this.property.value) {
|
||||
this.valueDate = moment(this.property.value, this.SHOW_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
showProperty() {
|
||||
return this.displayEmpty || !this.property.isEmpty();
|
||||
}
|
||||
|
||||
isEditable() {
|
||||
return this.editable && this.property.editable;
|
||||
}
|
||||
|
||||
showDatePicker() {
|
||||
this.datepicker.open();
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue) {
|
||||
if (newDateValue) {
|
||||
let momentDate = moment(newDateValue.value, this.SHOW_FORMAT, true);
|
||||
if (momentDate.isValid()) {
|
||||
this.valueDate = momentDate;
|
||||
this.cardViewUpdateService.update(this.property, momentDate.toDate());
|
||||
this.property.value = momentDate.toDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { Component, Input, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||
import { CardViewItem } from '../../interfaces/card-view-item.interface';
|
||||
import { CardItemTypeService } from '../../services/card-item-types.service';
|
||||
import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive';
|
||||
import { CardViewItemDispatcherComponent } from '../card-view-item-dispatcher/card-view-item-dispatcher.component';
|
||||
|
||||
@Component({
|
||||
selector: 'whatever-you-want-to-have',
|
||||
template: '<div data-automation-id="found-me">Hey I am shiny!</div>'
|
||||
})
|
||||
export class CardViewShinyCustomElementItemComponent {
|
||||
@Input() property: CardViewItem;
|
||||
@Input() editable: boolean;
|
||||
}
|
||||
|
||||
describe('CardViewItemDispatcherComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewItemDispatcherComponent>;
|
||||
let cardItemTypeService: CardItemTypeService;
|
||||
let component: CardViewItemDispatcherComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
cardItemTypeService = new CardItemTypeService();
|
||||
cardItemTypeService.setComponentTypeResolver('shiny-custom-element', () => CardViewShinyCustomElementItemComponent);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
declarations: [
|
||||
CardViewItemDispatcherComponent,
|
||||
CardViewShinyCustomElementItemComponent,
|
||||
CardViewContentProxyDirective
|
||||
],
|
||||
providers: [ { provide: CardItemTypeService, useValue: cardItemTypeService } ]
|
||||
});
|
||||
|
||||
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
|
||||
// https://github.com/angular/angular/issues/10760
|
||||
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||
set: { entryComponents: [ CardViewShinyCustomElementItemComponent ] }
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewItemDispatcherComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.property = <CardViewItem> {
|
||||
type: 'shiny-custom-element',
|
||||
label: 'Shiny custom element',
|
||||
value: null,
|
||||
key: 'customkey',
|
||||
default: '',
|
||||
editable: false,
|
||||
get displayValue(): string {
|
||||
return 'custom value displayed';
|
||||
}
|
||||
};
|
||||
component.editable = true;
|
||||
component.displayEmpty = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Subcompomnent creation', () => {
|
||||
|
||||
it('should load the CardViewShinyCustomElementItemComponent', () => {
|
||||
const innerElement = fixture.debugElement.query(By.css('[data-automation-id="found-me"]'));
|
||||
expect(innerElement).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should load the CardViewShinyCustomElementItemComponent only ONCE', () => {
|
||||
component.ngOnChanges({});
|
||||
component.ngOnChanges({});
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
|
||||
const shinyCustomElementItemComponent = fixture.debugElement.queryAll(By.css('whatever-you-want-to-have'));
|
||||
|
||||
expect(shinyCustomElementItemComponent.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should pass through the property, editable and displayEmpty parameters', () => {
|
||||
const shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance;
|
||||
|
||||
expect(shinyCustomElementItemComponent.property).toBe(component.property);
|
||||
expect(shinyCustomElementItemComponent.editable).toBe(component.editable);
|
||||
expect(shinyCustomElementItemComponent.displayEmpty).toBe(component.displayEmpty);
|
||||
});
|
||||
|
||||
it('should update the subcomponent\'s input parameters', () => {
|
||||
const expectedEditable = false,
|
||||
expectedDisplayEmpty = true,
|
||||
expectedProperty = <CardViewItem> {},
|
||||
expectedCustomInput = 1;
|
||||
|
||||
component.ngOnChanges({
|
||||
editable: new SimpleChange(true, expectedEditable, false),
|
||||
displayEmpty: new SimpleChange(false, expectedDisplayEmpty, false),
|
||||
property: new SimpleChange(null, expectedProperty, false),
|
||||
customInput: new SimpleChange(0, expectedCustomInput, false)
|
||||
});
|
||||
|
||||
const shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance;
|
||||
expect(shinyCustomElementItemComponent.property).toBe(expectedProperty);
|
||||
expect(shinyCustomElementItemComponent.editable).toBe(expectedEditable);
|
||||
expect(shinyCustomElementItemComponent.displayEmpty).toBe(expectedDisplayEmpty);
|
||||
expect(shinyCustomElementItemComponent.customInput).toBe(expectedCustomInput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Angular lifecycle methods', () => {
|
||||
|
||||
let shinyCustomElementItemComponent;
|
||||
|
||||
const lifeCycleMethods = [
|
||||
'ngOnChanges',
|
||||
'ngOnInit',
|
||||
'ngDoCheck',
|
||||
'ngAfterContentInit',
|
||||
'ngAfterContentChecked',
|
||||
'ngAfterViewInit',
|
||||
'ngAfterViewChecked',
|
||||
'ngOnDestroy'
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance;
|
||||
});
|
||||
|
||||
it('should call through the lifecycle methods', () => {
|
||||
lifeCycleMethods.forEach((lifeCycleMethod) => {
|
||||
shinyCustomElementItemComponent[lifeCycleMethod] = () => {};
|
||||
spyOn(shinyCustomElementItemComponent, lifeCycleMethod);
|
||||
const param = {};
|
||||
|
||||
component[lifeCycleMethod].call(component, param);
|
||||
|
||||
expect(shinyCustomElementItemComponent[lifeCycleMethod]).toHaveBeenCalledWith(param);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT call through the lifecycle methods if the method does not exist (no error should be thrown)', () => {
|
||||
const param = {};
|
||||
lifeCycleMethods.forEach((lifeCycleMethod) => {
|
||||
shinyCustomElementItemComponent[lifeCycleMethod] = undefined;
|
||||
|
||||
const execution = () => {
|
||||
component[lifeCycleMethod].call(component, param);
|
||||
};
|
||||
|
||||
expect(execution).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,102 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChange,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { CardViewItem } from '../../interfaces/card-view-item.interface';
|
||||
import { CardItemTypeService } from '../../services/card-item-types.service';
|
||||
import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view-item-dispatcher',
|
||||
template: '<ng-template adf-card-view-content-proxy></ng-template>'
|
||||
})
|
||||
export class CardViewItemDispatcherComponent implements OnChanges {
|
||||
@Input()
|
||||
property: CardViewItem;
|
||||
|
||||
@Input()
|
||||
editable: boolean;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
|
||||
@ViewChild(CardViewContentProxyDirective)
|
||||
private content: CardViewContentProxyDirective;
|
||||
|
||||
private loaded: boolean = false;
|
||||
private componentReference: any = null;
|
||||
|
||||
public ngOnInit;
|
||||
public ngDoCheck;
|
||||
|
||||
constructor(private cardItemTypeService: CardItemTypeService,
|
||||
private resolver: ComponentFactoryResolver) {
|
||||
const dynamicLifecycleMethods = [
|
||||
'ngOnInit',
|
||||
'ngDoCheck',
|
||||
'ngAfterContentInit',
|
||||
'ngAfterContentChecked',
|
||||
'ngAfterViewInit',
|
||||
'ngAfterViewChecked',
|
||||
'ngOnDestroy'
|
||||
];
|
||||
|
||||
dynamicLifecycleMethods.forEach((dynamicLifecycleMethod) => {
|
||||
this[dynamicLifecycleMethod] = this.proxy.bind(this, dynamicLifecycleMethod);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this.loaded) {
|
||||
this.loadComponent();
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
Object.keys(changes)
|
||||
.map(changeName => [changeName, changes[changeName]])
|
||||
.forEach(([inputParamName, simpleChange]: [string, SimpleChange]) => {
|
||||
this.componentReference.instance[inputParamName] = simpleChange.currentValue;
|
||||
});
|
||||
|
||||
this.proxy('ngOnChanges', changes);
|
||||
}
|
||||
|
||||
private loadComponent() {
|
||||
const factoryClass = this.cardItemTypeService.resolveComponentType(this.property);
|
||||
|
||||
const factory = this.resolver.resolveComponentFactory(factoryClass);
|
||||
this.componentReference = this.content.viewContainerRef.createComponent(factory);
|
||||
|
||||
this.componentReference.instance.editable = this.editable;
|
||||
this.componentReference.instance.property = this.property;
|
||||
this.componentReference.instance.displayEmpty = this.displayEmpty;
|
||||
}
|
||||
|
||||
private proxy(methodName, ...args) {
|
||||
if (this.componentReference.instance[methodName]) {
|
||||
this.componentReference.instance[methodName].apply(this.componentReference.instance, args);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<div class="adf-property-label" *ngIf="showProperty()">{{ property.label | translate }}</div>
|
||||
<div class="adf-property-value">
|
||||
<div>
|
||||
<span *ngIf="!isClickable(); else elseBlock" [attr.data-automation-id]="'card-mapitem-value-' + property.key">
|
||||
<span *ngIf="showProperty();">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="adf-mapitem-clickable-value" (click)="clicked()" [attr.data-automation-id]="'card-mapitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
{{ property.default | translate }}
|
||||
</ng-template>
|
||||
</div>
|
@@ -0,0 +1,5 @@
|
||||
.adf {
|
||||
&-mapitem-clickable-value {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { CardViewMapItemModel } from '../../models/card-view-mapitem.model';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { LogService } from '../../../services/log.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewMapItemComponent } from './card-view-mapitem.component';
|
||||
|
||||
describe('CardViewMapItemComponent', () => {
|
||||
let service: CardViewUpdateService;
|
||||
|
||||
let fixture: ComponentFixture<CardViewMapItemComponent>;
|
||||
let component: CardViewMapItemComponent;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MaterialModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewMapItemComponent
|
||||
],
|
||||
providers: [
|
||||
AppConfigService,
|
||||
CardViewUpdateService,
|
||||
LogService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewMapItemComponent);
|
||||
service = TestBed.get(CardViewUpdateService);
|
||||
component = fixture.componentInstance;
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render the default if the value is empty and displayEmpty is true', () => {
|
||||
component.property = new CardViewMapItemModel({
|
||||
label: 'Map label',
|
||||
value: null,
|
||||
key: 'mapkey',
|
||||
default: 'Fake default',
|
||||
clickable: false
|
||||
});
|
||||
component.displayEmpty = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = debug.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('Map label');
|
||||
|
||||
let value = debug.query(By.css(`[data-automation-id="card-mapitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('Fake default');
|
||||
});
|
||||
|
||||
it('should NOT render the default if the value is empty and displayEmpty is false', () => {
|
||||
component.property = new CardViewMapItemModel({
|
||||
label: 'Map label',
|
||||
value: null,
|
||||
key: 'mapkey',
|
||||
default: 'Fake default',
|
||||
clickable: false
|
||||
});
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = debug.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).toBeNull();
|
||||
|
||||
let value = debug.query(By.css(`[data-automation-id="card-mapitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the label and value', () => {
|
||||
component.property = new CardViewMapItemModel({
|
||||
label: 'Map label',
|
||||
value: new Map([['999', 'fakeProcessName']]),
|
||||
key: 'mapkey',
|
||||
default: ''
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = debug.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('Map label');
|
||||
|
||||
let value = debug.query(By.css(`[data-automation-id="card-mapitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('fakeProcessName');
|
||||
});
|
||||
|
||||
it('should render a clickable value', (done) => {
|
||||
component.property = new CardViewMapItemModel({
|
||||
label: 'Map label',
|
||||
value: new Map([['999', 'fakeProcessName']]),
|
||||
key: 'mapkey',
|
||||
default: 'Fake default',
|
||||
clickable: true
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
let value: any = element.querySelector('.adf-mapitem-clickable-value');
|
||||
|
||||
service.itemClicked$.subscribe((response) => {
|
||||
expect(response.target).not.toBeNull();
|
||||
expect(response.target.type).toEqual('map');
|
||||
expect(response.target.clickable).toBeTruthy();
|
||||
expect(response.target.displayValue).toEqual('fakeProcessName');
|
||||
done();
|
||||
});
|
||||
|
||||
value.click();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,48 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CardViewMapItemModel } from '../../models/card-view-mapitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view-mapitem',
|
||||
templateUrl: './card-view-mapitem.component.html',
|
||||
styleUrls: ['./card-view-mapitem.component.scss']
|
||||
})
|
||||
|
||||
export class CardViewMapItemComponent {
|
||||
@Input()
|
||||
property: CardViewMapItemModel;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||
|
||||
showProperty() {
|
||||
return this.displayEmpty || !this.property.isEmpty();
|
||||
}
|
||||
|
||||
isClickable() {
|
||||
return this.property.clickable;
|
||||
}
|
||||
|
||||
clicked(): void {
|
||||
this.cardViewUpdateService.clicked(this.property);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<div class="adf-property-label" *ngIf="showProperty() || isEditable()">{{ property.label | translate }}</div>
|
||||
<div class="adf-property-value">
|
||||
<span *ngIf="!isEditable()">
|
||||
<span *ngIf="!isClickable(); else elseBlock" [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty()">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="adf-textitem-clickable-value" (click)="clicked()" [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
</span>
|
||||
<span *ngIf="isEditable()">
|
||||
<div *ngIf="!inEdit" (click)="setEditMode(true)" class="adf-textitem-readonly" [attr.data-automation-id]="'card-textitem-edit-toggle-' + property.key" fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<span [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<mat-icon fxFlex="0 0 auto" [attr.data-automation-id]="'card-textitem-edit-icon-' + property.key" class="adf-textitem-icon">create</mat-icon>
|
||||
</div>
|
||||
<div *ngIf="inEdit" class="adf-textitem-editable">
|
||||
<div class="adf-textitem-editable-controls">
|
||||
<mat-form-field floatPlaceholder="never" class="adf-input-container">
|
||||
<input *ngIf="!property.multiline" #editorInput
|
||||
matInput
|
||||
class="adf-input"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
[attr.data-automation-id]="'card-textitem-editinput-' + property.key">
|
||||
<textarea *ngIf="property.multiline" #editorInput
|
||||
matInput
|
||||
matTextareaAutosize
|
||||
matAutosizeMaxRows="1"
|
||||
matAutosizeMaxRows="5"
|
||||
class="adf-textarea"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
[attr.data-automation-id]="'card-textitem-edittextarea-' + property.key"></textarea>
|
||||
</mat-form-field>
|
||||
<mat-icon
|
||||
class="adf-textitem-icon adf-update-icon"
|
||||
(click)="update()"
|
||||
[attr.data-automation-id]="'card-textitem-update-' + property.key">done</mat-icon>
|
||||
<mat-icon
|
||||
class="adf-textitem-icon adf-reset-icon"
|
||||
(click)="reset()"
|
||||
[attr.data-automation-id]="'card-textitem-reset-' + property.key">clear</mat-icon>
|
||||
</div>
|
||||
<mat-error class="adf-textitem-editable-error" *ngIf="hasErrors()">
|
||||
<ul>
|
||||
<li *ngFor="let errorMessage of errorMessages">{{ errorMessage | translate }}</li>
|
||||
</ul>
|
||||
</mat-error>
|
||||
</div>
|
||||
</span>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
<span class="adf-textitem-default-value">{{ property.default | translate }}</span>
|
||||
</ng-template>
|
||||
</div>
|
@@ -0,0 +1,116 @@
|
||||
@mixin adf-card-view-textitem-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.adf {
|
||||
&-textitem-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
padding-left: 8px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&-update-icon {
|
||||
padding-left: 13px;
|
||||
}
|
||||
|
||||
&-textitem-readonly {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover mat-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-textitem-clickable-value {
|
||||
cursor: pointer;
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
&-textitem-editable {
|
||||
|
||||
&-controls {
|
||||
display: flex;
|
||||
|
||||
mat-icon:hover {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
border: 1px solid mat-color($foreground, text, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
font-size: 12px;
|
||||
padding-top: 4px;
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-textitem-default-value {
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-wrapper {
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-underline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-infix {
|
||||
padding: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-placeholder-wrapper {
|
||||
padding-top: 2em;
|
||||
position: static;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-placeholder {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-element {
|
||||
font-family: inherit;
|
||||
position: relative;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
&-textitem-editable .mat-input-element:focus {
|
||||
padding: 5px;
|
||||
left: -6px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&-textitem-editable input.mat-input-element {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&-textitem-editable input.mat-input-element:focus {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,325 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { LogService } from '../../../services/log.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewTextItemComponent } from './card-view-textitem.component';
|
||||
|
||||
describe('CardViewTextItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewTextItemComponent>;
|
||||
let component: CardViewTextItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MatDatepickerModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewTextItemComponent
|
||||
],
|
||||
providers: [
|
||||
AppConfigService,
|
||||
CardViewUpdateService,
|
||||
LogService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewTextItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: 'Lorem ipsum',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
editable: false
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
|
||||
it('should render the label and value', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('Text label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum');
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, editable is false and displayEmpty is false', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
editable: false
|
||||
});
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, editable is false and displayEmpty is true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
editable: false
|
||||
});
|
||||
component.displayEmpty = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and editable true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
editable: true
|
||||
});
|
||||
component.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, clickable is false and displayEmpty is false', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
clickable: false
|
||||
});
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, clickable is false and displayEmpty is true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
clickable: false
|
||||
});
|
||||
component.displayEmpty = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and clickable true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
default: 'FAKE-DEFAULT-KEY',
|
||||
clickable: true
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY');
|
||||
});
|
||||
|
||||
it('should render value when editable:true', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum');
|
||||
});
|
||||
|
||||
it('should render the edit icon in case of editable:true', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||
expect(editIcon).not.toBeNull('Edit icon should be shown');
|
||||
});
|
||||
|
||||
it('should NOT render the edit icon in case of editable:false', () => {
|
||||
component.editable = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||
expect(editIcon).toBeNull('Edit icon should NOT be shown');
|
||||
});
|
||||
|
||||
it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => {
|
||||
component.editable = false;
|
||||
component.property.editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||
expect(editIcon).toBeNull('Edit icon should NOT be shown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.inEdit = true;
|
||||
component.editedValue = 'updated-value';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should call the isValid method with the edited value', () => {
|
||||
spyOn(component.property, 'isValid');
|
||||
component.editedValue = 'updated-value';
|
||||
|
||||
component.update();
|
||||
|
||||
expect(component.property.isValid).toHaveBeenCalledWith('updated-value');
|
||||
});
|
||||
|
||||
it('should trigger the update event if the editedValue is valid', () => {
|
||||
component.property.isValid = () => true;
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
component.editedValue = 'updated-value';
|
||||
|
||||
component.update();
|
||||
|
||||
expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, 'updated-value');
|
||||
});
|
||||
|
||||
it('should NOT trigger the update event if the editedValue is invalid', () => {
|
||||
component.property.isValid = () => false;
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
|
||||
component.update();
|
||||
|
||||
expect(cardViewUpdateService.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set the errorMessages properly if the editedValue is invalid', () => {
|
||||
const expectedErrorMessages = ['Something went wrong'];
|
||||
component.property.isValid = () => false;
|
||||
component.property.getValidationErrors = () => expectedErrorMessages;
|
||||
|
||||
component.update();
|
||||
|
||||
expect(component.errorMessages).toBe(expectedErrorMessages);
|
||||
});
|
||||
|
||||
it('should update the propery\'s value after a succesful update attempt', async(() => {
|
||||
component.property.isValid = () => true;
|
||||
component.update();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.property.value).toBe(component.editedValue);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should switch back to readonly mode after an update attempt', async(() => {
|
||||
component.property.isValid = () => true;
|
||||
component.update();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.inEdit).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should trigger an update event on the CardViewUpdateService [integration]', (done) => {
|
||||
component.inEdit = false;
|
||||
component.property.isValid = () => true;
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
const expectedText = 'changed text';
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({ textkey: expectedText });
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-toggle-${component.property.key}"]`));
|
||||
editIcon.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
let editInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-editinput-${component.property.key}"]`));
|
||||
editInput.nativeElement.value = expectedText;
|
||||
editInput.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
let updateInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-update-${component.property.key}"]`));
|
||||
updateInput.triggerEventHandler('click', null);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
|
||||
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view-textitem',
|
||||
templateUrl: './card-view-textitem.component.html',
|
||||
styleUrls: ['./card-view-textitem.component.scss']
|
||||
})
|
||||
export class CardViewTextItemComponent implements OnChanges {
|
||||
@Input()
|
||||
property: CardViewTextItemModel;
|
||||
|
||||
@Input()
|
||||
editable: boolean = false;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
|
||||
@ViewChild('editorInput')
|
||||
private editorInput: any;
|
||||
|
||||
inEdit: boolean = false;
|
||||
editedValue: string;
|
||||
errorMessages: string[];
|
||||
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.editedValue = this.property.value;
|
||||
}
|
||||
|
||||
showProperty(): boolean {
|
||||
return this.displayEmpty || !this.property.isEmpty();
|
||||
}
|
||||
|
||||
isEditable(): boolean {
|
||||
return this.editable && this.property.editable;
|
||||
}
|
||||
|
||||
isClickable(): boolean {
|
||||
return this.property.clickable;
|
||||
}
|
||||
|
||||
hasErrors(): number {
|
||||
return this.errorMessages && this.errorMessages.length;
|
||||
}
|
||||
|
||||
setEditMode(editStatus: boolean): void {
|
||||
this.inEdit = editStatus;
|
||||
setTimeout(() => {
|
||||
if (this.editorInput) {
|
||||
this.editorInput.nativeElement.click();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.editedValue = this.property.value;
|
||||
this.setEditMode(false);
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (this.property.isValid(this.editedValue)) {
|
||||
this.cardViewUpdateService.update(this.property, this.editedValue );
|
||||
this.property.value = this.editedValue;
|
||||
this.setEditMode(false);
|
||||
} else {
|
||||
this.errorMessages = this.property.getValidationErrors(this.editedValue);
|
||||
}
|
||||
}
|
||||
|
||||
clicked(): void {
|
||||
this.cardViewUpdateService.clicked(this.property);
|
||||
}
|
||||
}
|
23
lib/core/card-view/components/card-view.components.ts
Normal file
23
lib/core/card-view/components/card-view.components.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './card-view/card-view.component';
|
||||
export * from './card-view-boolitem/card-view-boolitem.component';
|
||||
export * from './card-view-dateitem/card-view-dateitem.component';
|
||||
export * from './card-view-item-dispatcher/card-view-item-dispatcher.component';
|
||||
export * from './card-view-mapitem/card-view-mapitem.component';
|
||||
export * from './card-view-textitem/card-view-textitem.component';
|
@@ -0,0 +1,11 @@
|
||||
<div class="adf-property-list">
|
||||
<div *ngFor="let property of properties">
|
||||
<div [attr.data-automation-id]="'header-'+property.key" class="adf-property">
|
||||
<adf-card-view-item-dispatcher
|
||||
[property]="property"
|
||||
[editable]="editable"
|
||||
[displayEmpty]="displayEmpty">
|
||||
</adf-card-view-item-dispatcher>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,22 @@
|
||||
|
||||
@mixin adf-card-view-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.adf-property-list {
|
||||
.adf-property {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.adf-property-label {
|
||||
font-size: 12px;
|
||||
color: mat-color($foreground, text, 0.4);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.adf-property-value {
|
||||
font-size: 14px;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
|
||||
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive';
|
||||
import { CardViewDateItemComponent } from '../card-view-dateitem/card-view-dateitem.component';
|
||||
import { CardItemTypeService } from '../../services/card-item-types.service';
|
||||
import { CardViewItemDispatcherComponent } from '../card-view-item-dispatcher/card-view-item-dispatcher.component';
|
||||
import { CardViewTextItemComponent } from '../card-view-textitem/card-view-textitem.component';
|
||||
import { CardViewComponent } from './card-view.component';
|
||||
|
||||
describe('CardViewComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewComponent>;
|
||||
let component: CardViewComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
MatDatepickerModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
MatDatetimepickerModule,
|
||||
MatNativeDatetimeModule,
|
||||
FormsModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewComponent,
|
||||
CardViewItemDispatcherComponent,
|
||||
CardViewContentProxyDirective,
|
||||
CardViewTextItemComponent,
|
||||
CardViewDateItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardItemTypeService,
|
||||
CardViewUpdateService,
|
||||
AppConfigService
|
||||
]
|
||||
});
|
||||
|
||||
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
|
||||
// https://github.com/angular/angular/issues/10760
|
||||
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||
set: { entryComponents: [ CardViewTextItemComponent, CardViewDateItemComponent ] }
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render the label and value', async(() => {
|
||||
component.properties = [new CardViewTextItemModel({label: 'My label', value: 'My value', key: 'some key'})];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('My label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText).toBe('My value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should pass through editable property to the items', () => {
|
||||
component.editable = true;
|
||||
component.properties = [new CardViewDateItemModel({
|
||||
label: 'My date label',
|
||||
value: '2017-06-14',
|
||||
key: 'some-key',
|
||||
editable: true
|
||||
})];
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-some-key"]`));
|
||||
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||
});
|
||||
|
||||
it('should render the date in the correct format', async(() => {
|
||||
component.properties = [new CardViewDateItemModel({
|
||||
label: 'My date label', value: '2017-06-14', key: 'some key',
|
||||
format: 'MMM DD YYYY'
|
||||
})];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('My date label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText).toBe('Jun 14 2017');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT render anything if the value is empty, not editable and displayEmpty is false', async(() => {
|
||||
component.properties = [new CardViewTextItemModel({
|
||||
label: 'My default label',
|
||||
value: null,
|
||||
default: 'default value',
|
||||
key: 'some-key',
|
||||
editable: false
|
||||
})];
|
||||
component.editable = true;
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).toBeNull();
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should render the default value if the value is empty, not editable and displayEmpty is true', async(() => {
|
||||
component.properties = [new CardViewTextItemModel({
|
||||
label: 'My default label',
|
||||
value: null,
|
||||
default: 'default value',
|
||||
key: 'some-key',
|
||||
editable: false
|
||||
})];
|
||||
component.editable = true;
|
||||
component.displayEmpty = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('My default label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value [data-automation-id="card-textitem-value-some-key"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('default value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should render the default value if the value is empty and is editable', async(() => {
|
||||
component.properties = [new CardViewTextItemModel({
|
||||
label: 'My default label',
|
||||
value: null,
|
||||
default: 'default value',
|
||||
key: 'some-key',
|
||||
editable: true
|
||||
})];
|
||||
component.editable = true;
|
||||
component.displayEmpty = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||
expect(labelValue).not.toBeNull();
|
||||
expect(labelValue.nativeElement.innerText).toBe('My default label');
|
||||
|
||||
let value = fixture.debugElement.query(By.css('.adf-property-value [data-automation-id="card-textitem-value-some-key"]'));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('default value');
|
||||
});
|
||||
}));
|
||||
});
|
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CardViewItem } from '../../interfaces/card-view-item.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view',
|
||||
templateUrl: './card-view.component.html',
|
||||
styleUrls: ['./card-view.component.scss']
|
||||
})
|
||||
export class CardViewComponent {
|
||||
@Input()
|
||||
properties: CardViewItem [];
|
||||
|
||||
@Input()
|
||||
editable: boolean;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
}
|
Reference in New Issue
Block a user