[AAE-16968] Add boolean type (#8972)

* [AAE-16968] Update variable mapper service

* Update DetaColumnComponent

* [AAE-16968] Update DataColumnTypes interface

* [AAE-16968] Handle boolean column on UI

* [AAE-16968] Create BooleanCellComponent

* [AAE-16968] Delete comment

* [AAE-16968] Update BooleanCellComponent

* [AAE-16968] Delete unnecessary css classes

* [AAE-16968] Allow for string values

* [AAE-16968] Update test for datatable

* [AAE-16968] Update tests for boolean cell

* [AAE-16968] Update tests for VariableMapperService

* [AAE-16968] Add boolean pipe

* [AAE-16968] Update pipe

* [AAE-16968] Update code formatting

* [AAE-16968] Add doc for the boolean pipe

* [AAE-16968] Update DataColumnComponent doc

* [AAE-16968] Delete unnecessary class

* [AAE-16968] Fix title bug

* [AAE-16968] Update BooleanCell tests

* [AAE-16968] Update Boolean pipe

* [AAE-16968] Update tests for variable mapper service

* [AAE-16968] Update PipesModule

* [AAE-16968] Small fix

* [AAE-16968] Fix lint error
This commit is contained in:
Wiktor Danielewski
2023-10-19 09:00:27 +02:00
committed by GitHub
parent cda12730f6
commit b49c86fda5
15 changed files with 538 additions and 16 deletions

View File

@@ -0,0 +1,131 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { ComponentFixture, TestBed } from '@angular/core/testing';
import { BooleanCellComponent } from './boolean-cell.component';
import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
import { ObjectDataColumn } from '../../data/object-datacolumn.model';
describe('BooleanCellComponent', () => {
let component: BooleanCellComponent;
let fixture: ComponentFixture<BooleanCellComponent>;
const getBooleanCell = () => fixture.debugElement.nativeElement.querySelector('span');
const renderAndCheckResult = (value: any, expectedOccurrence: boolean, expectedLabel?: string) => {
component.value$.next(value);
fixture.detectChanges();
const booleanCell = getBooleanCell();
expectedOccurrence ? expect(booleanCell).toBeTruthy() : expect(booleanCell).toBeFalsy();
if (expectedLabel) {
expect(booleanCell.textContent.trim()).toBe(expectedLabel);
}
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [BooleanCellComponent]
});
fixture = TestBed.createComponent(BooleanCellComponent);
component = fixture.componentInstance;
});
describe('Initialization', () => {
let rowData: any;
let columnData: any;
let dataTableAdapter: ObjectDataTableAdapter;
let nextSpy: jasmine.Spy;
beforeEach(() => {
rowData = {
id: '1',
value: false
};
columnData = { type: 'boolean', key: 'value' };
dataTableAdapter = new ObjectDataTableAdapter([rowData], [new ObjectDataColumn(columnData)]);
nextSpy = spyOn(component.value$, 'next');
});
it('should setup inital value', () => {
component.column = dataTableAdapter.getColumns()[0];
component.row = dataTableAdapter.getRows()[0];
component.data = dataTableAdapter;
fixture.detectChanges();
expect(nextSpy).toHaveBeenCalledOnceWith(rowData.value);
});
it('should NOT setup inital value', () => {
fixture.detectChanges();
expect(nextSpy).not.toHaveBeenCalled();
});
});
describe('UI', () => {
describe('should render "true" inside cell when', () => {
it('boolean value is true', () => {
renderAndCheckResult(true, true, 'true');
});
it('exact string is provided', () => {
renderAndCheckResult('true', true, 'true');
});
});
describe('should render "false" inside cell when', () => {
it('boolean value is false', () => {
renderAndCheckResult(false, true, 'false');
});
it('exact string is provided', () => {
renderAndCheckResult('false', true, 'false');
});
});
describe('should NOT render value inside cell in case of', () => {
it('invalid string', () => {
renderAndCheckResult('tru', false);
});
it('number', () => {
renderAndCheckResult(0, false);
});
it('object', () => {
renderAndCheckResult({}, false);
});
it('null', () => {
renderAndCheckResult(null, false);
});
it('undefined', () => {
renderAndCheckResult(undefined, false);
});
it('empty string', () => {
renderAndCheckResult('', false);
});
it('NaN', () => {
renderAndCheckResult(NaN, false);
});
});
});
});

View File

@@ -0,0 +1,49 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { ChangeDetectionStrategy, Component, OnInit, Optional, ViewEncapsulation } from '@angular/core';
import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component';
import { CommonModule } from '@angular/common';
import { DataTableService } from '../../services/datatable.service';
import { BooleanPipe } from '../../../pipes/boolean.pipe';
@Component({
standalone: true,
imports: [CommonModule, BooleanPipe],
selector: 'adf-boolean-cell',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ng-container *ngIf="value$ | async | adfBoolean as value">
<span [title]="value">
{{ value }}
</span>
</ng-container>
`,
encapsulation: ViewEncapsulation.None,
host: { class: 'adf-datatable-content-cell' }
})
export class BooleanCellComponent extends DataTableCellComponent implements OnInit {
constructor(@Optional() dataTableService: DataTableService) {
super(dataTableService);
}
ngOnInit() {
if (this.column?.key && this.row && this.data) {
this.value$.next(this.data.getValue(this.row, this.column, this.resolverFn));
}
}
}

View File

@@ -287,6 +287,16 @@
[tooltip]="getCellTooltip(row, col)">
</adf-datatable-cell>
</div>
<div *ngSwitchCase="'boolean'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value"
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)">
<adf-boolean-cell
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)">
</adf-boolean-cell>
</div>
<div *ngSwitchCase="'json'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value">
<adf-json-cell
[editable]="col.editable"

View File

@@ -33,6 +33,7 @@ import { matIconRegistryMock } from '../../../mock/mat-icon-registry-mock';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { take } from 'rxjs/operators';
import { By } from '@angular/platform-browser';
import { mockCarsData, mockCarsSchemaDefinition } from '../mocks/datatable.mock';
@Component({ selector: 'adf-custom-column-template-component', template: ` <ng-template #tmplRef></ng-template> ` })
class CustomColumnTemplateComponent {
@@ -1195,6 +1196,17 @@ describe('DataTable', () => {
expect(rows3[0].isSelected).toBeFalsy();
expect(rows3[1].isSelected).toBeTruthy();
});
it('should be able to display column of type boolean', () => {
dataTable.data = new ObjectDataTableAdapter(mockCarsData, mockCarsSchemaDefinition);
fixture.detectChanges();
const rows = dataTable.data.getRows();
expect(rows[0].getValue('is_available')).toBe('false');
expect(rows[1].getValue('is_available')).toBe('true');
expect(rows[2].getValue('is_available')).toBe('true');
});
});
describe('Accesibility', () => {

View File

@@ -0,0 +1,115 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 const mockCarsData: any = [
{
car_id: 1,
car_name: 'Fiat 126p (Process)',
car_price: 599.00,
is_available: 'false',
production_start: '1972-04-23',
description: 'The Fiat 126 (Type 126) is a four-passenger, rear-engine, city car manufactured and marketed by Fiat over a twenty-eight year production run from 1972 until 2000, over a single generation.',
icon: 'airport_shuttle',
wikipedia_link: 'https://en.wikipedia.org/wiki/Fiat_126'
},
{
car_id: 2,
car_name: 'Citroen Xsara Picasso (Process)',
car_price: 10000,
is_available: 'true',
production_start: '2004-02-10T12:25:43.511Z',
description: 'The Citroën Xsara Picasso is a car produced by Citroën from 1999 to 2012. It has a five-seater five-door compact MPV design.',
icon: 'local_shipping',
wikipedia_link: 'https://en.wikipedia.org/wiki/Citro%C3%ABn_Xsara_Picasso'
},
{
car_id: 3,
car_name: 'Audi A3 (Process)',
car_price: 15000.12345,
is_available: 'true',
production_start: '1998-06-25T12:25:20',
description: 'The Audi A3 is a subcompact executive/small family car (C-segment) manufactured and marketed by the German automaker Audi AG since September 1996, currently in its fourth generation.',
icon: 'directions_car',
wikipedia_link: 'https://en.wikipedia.org/wiki/Audi_A3'
}
];
export const mockCarsSchemaDefinition: any[] = [
{
type: 'icon',
key: 'icon',
title: '',
sortable: true,
draggable: true
},
{
type: 'text',
key: 'car_id',
title: 'Car ID',
sortable: true,
draggable: true
},
{
type: 'text',
key: 'car_name',
title: 'Car Name',
sortable: true,
draggable: true
},
{
type: 'amount',
key: 'car_price',
title: 'Car Price',
sortable: true,
draggable: true,
currencyConfig: {
code: 'USA',
display: 'code',
digitsInfo: '1.0-2',
locale: 'en-US'
}
},
{
type: 'boolean',
key: 'is_available',
title: 'Available?',
sortable: true,
draggable: true
},
{
type: 'date',
key: 'production_start',
title: 'Production Start',
sortable: true,
draggable: true
},
{
type: 'json',
key: 'description',
title: 'Description',
sortable: true,
draggable: true
},
{
type: 'location',
format: '/somewhere',
key: 'wikipedia_link',
title: 'Wikipedia',
sortable: true,
draggable: true
}
];

View File

@@ -18,6 +18,7 @@
/* eslint-disable @angular-eslint/component-selector, @angular-eslint/no-input-rename */
import { Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
import { DataColumnType } from '../public-api';
@Component({
selector: 'data-column',
@@ -41,11 +42,11 @@ export class DataColumnComponent implements OnInit {
customData: any;
/**
* Value type for the column. Possible settings are 'text', 'image',
* 'date', 'fileSize', 'location', and 'json'.
* Value type for the column. Possible settings are defined via DataColumnType.
* Example types: `text`, `date`, `image`, `icon`, `boolean`.
*/
@Input()
type: string = 'text';
type: DataColumnType = 'text';
/** Value format (if supported by the parent component), for example format of the date. */
@Input()

View File

@@ -25,6 +25,8 @@ export interface DataColumnTypes {
icon: string;
fileSize: string;
location: string;
// eslint-disable-next-line id-blacklist
boolean: string;
}
export type DataColumnType = keyof DataColumnTypes;

View File

@@ -53,6 +53,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DataColumnComponent, DataColumnListComponent, DateColumnHeaderComponent } from './data-column';
import { ResizableModule } from './directives/resizable/resizable.module';
import { DataColumnModule } from './data-column/data-column.module';
import { BooleanCellComponent } from './components/boolean-cell/boolean-cell.component';
@NgModule({
imports: [
@@ -69,7 +70,8 @@ import { DataColumnModule } from './data-column/data-column.module';
FormsModule,
ReactiveFormsModule,
ResizableModule,
DataColumnModule
DataColumnModule,
BooleanCellComponent
],
declarations: [
DataTableComponent,

View File

@@ -0,0 +1,85 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { BooleanPipe } from './boolean.pipe';
describe('BooleanPipe', () => {
let pipe: BooleanPipe;
beforeEach(() => {
pipe = new BooleanPipe();
});
describe('should return "true" when', () => {
it('boolean value is true', () => {
expect(pipe.transform(true)).toBe('true');
});
it('exact string is provided', () => {
expect(pipe.transform('true')).toBe('true');
});
});
describe('should return "false" when', () => {
it('boolean value is false', () => {
expect(pipe.transform(false)).toBe('false');
});
it('exact string is provided', () => {
expect(pipe.transform('false')).toBe('false');
});
});
describe('should return empty string in case of', () => {
it('invalid string', () => {
expect(pipe.transform('fal')).toBe('');
expect(pipe.transform('truee')).toBe('');
expect(pipe.transform('0')).toBe('');
expect(pipe.transform('1')).toBe('');
expect(pipe.transform('122')).toBe('');
expect(pipe.transform('TRUE')).toBe('');
expect(pipe.transform('FALSE')).toBe('');
expect(pipe.transform(' false')).toBe('');
expect(pipe.transform(' true ')).toBe('');
});
it('falsy value (excluding false)', () => {
expect(pipe.transform(null)).toBe('');
expect(pipe.transform(undefined)).toBe('');
expect(pipe.transform(false)).not.toBe('');
expect(pipe.transform(NaN)).toBe('');
expect(pipe.transform(0)).toBe('');
expect(pipe.transform(-0)).toBe('');
expect(pipe.transform(BigInt(0))).toBe('');
expect(pipe.transform('')).toBe('');
});
it('number', () => {
expect(pipe.transform(-20.5)).toBe('');
expect(pipe.transform(-1)).toBe('');
expect(pipe.transform(0)).toBe('');
expect(pipe.transform(1)).toBe('');
expect(pipe.transform(100)).toBe('');
expect(pipe.transform(100.5)).toBe('');
});
it('object', () => {
expect(pipe.transform({})).toBe('');
expect(pipe.transform({ value: 'true'})).toBe('');
});
});
});

View File

@@ -0,0 +1,35 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'adfBoolean',
standalone: true
})
export class BooleanPipe implements PipeTransform {
transform(value: any): string {
if (typeof value === 'boolean') {
return value ? 'true' : 'false';
}
return this.isRecognizedAsBoolean(value) ? value : '';
}
private isRecognizedAsBoolean(value: any): boolean {
return value === 'true' || value === 'false';
}
}

View File

@@ -33,3 +33,4 @@ export * from './moment-datetime.pipe';
export * from './date-time.pipe';
export * from './filter-string.pipe';
export * from './filter-out-every-object-by-prop.pipe';
export * from './boolean.pipe';