From d9f42e017a5b8eff8073d209ad8a3e2ef15e5ead Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 23 Apr 2026 21:19:08 +0200 Subject: [PATCH 1/7] Fix a11y issues in file upload component --- playwright/cps-accessibility.spec.ts | 2 +- .../src/app/api-data/cps-file-upload.json | 36 +++++ .../file-upload-page.component.html | 42 ++++- .../file-upload-page.component.scss | 15 ++ .../file-upload-page.component.ts | 26 ++- .../cps-file-upload.component.html | 76 ++++++--- .../cps-file-upload.component.scss | 151 +++++++++++++++--- .../cps-file-upload.component.ts | 109 +++++++++++-- .../cps-progress-linear.component.ts | 9 +- 9 files changed, 402 insertions(+), 64 deletions(-) diff --git a/playwright/cps-accessibility.spec.ts b/playwright/cps-accessibility.spec.ts index 82f78fb6..06cfad90 100644 --- a/playwright/cps-accessibility.spec.ts +++ b/playwright/cps-accessibility.spec.ts @@ -56,7 +56,7 @@ const components: ComponentEntry[] = [ // name: 'Expansion panel', // selector: 'cps-expansion-panel' // }, - // { route: '/file-upload', name: 'File upload', selector: 'cps-file-upload' }, + { route: '/file-upload', name: 'File upload', selector: 'cps-file-upload' }, // { route: '/icon', name: 'Icon', selector: 'cps-icon' }, // { route: '/info-circle', name: 'Info circle', selector: 'cps-info-circle' }, // { route: '/input', name: 'Input', selector: 'cps-input' }, diff --git a/projects/composition/src/app/api-data/cps-file-upload.json b/projects/composition/src/app/api-data/cps-file-upload.json index c0bbedd5..b55209f8 100644 --- a/projects/composition/src/app/api-data/cps-file-upload.json +++ b/projects/composition/src/app/api-data/cps-file-upload.json @@ -21,6 +21,14 @@ "default": "Any file", "description": "Expected file description. E.g. 'Word document'." }, + { + "name": "ariaLabel", + "optional": false, + "readonly": false, + "type": "string", + "default": "Upload file", + "description": "Aria label for the component, used for accessibility." + }, { "name": "width", "optional": false, @@ -37,6 +45,14 @@ "default": "", "description": "Expected file info block, explaining some extra stuff about file." }, + { + "name": "disabled", + "optional": false, + "readonly": false, + "type": "boolean", + "default": "false", + "description": "Whether the component is disabled." + }, { "name": "fileProcessingCallback", "optional": true, @@ -86,6 +102,26 @@ ], "description": "Callback to invoke when file upload fails." }, + { + "name": "fileProcessed", + "parameters": [ + { + "name": "value", + "type": "File" + } + ], + "description": "Callback to invoke when file is processed." + }, + { + "name": "fileProcessingFailed", + "parameters": [ + { + "name": "value", + "type": "string" + } + ], + "description": "Callback to invoke when file processing fails." + }, { "name": "uploadedFileRemoved", "parameters": [ diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html index c005f6cd..b49624cc 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html @@ -3,7 +3,7 @@

File upload component with the dependency on selected file extension

-
+


+
- -

File upload component with extra info

-
+
+

+ + +

+ +
+ +

Disabled upload component

+

+ +
diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss index e69de29b..93f4a016 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss @@ -0,0 +1,15 @@ +:host { + .section-title { + color: var(--cps-color-calm); + } + .section-title:first-child { + margin-top: 0; + } + .section-title, + .section-body { + margin-left: 0.5rem; + } + .section-body:last-child { + margin-bottom: 0.5rem; + } +} diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts index d7fb9387..a0ccb56d 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts @@ -1,11 +1,12 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { CpsFileUploadComponent, CpsButtonToggleComponent, CpsButtonToggleOption, - CpsDividerComponent + CpsDividerComponent, + CpsButtonComponent } from 'cps-ui-kit'; -import { Observable, catchError, from, map, of } from 'rxjs'; +import { Observable, catchError, delay, from, map, of } from 'rxjs'; import ComponentData from '../../api-data/cps-file-upload.json'; import { ComponentDocsViewerComponent } from '../../components/component-docs-viewer/component-docs-viewer.component'; @@ -14,6 +15,7 @@ import { ComponentDocsViewerComponent } from '../../components/component-docs-vi selector: 'app-file-upload-page', imports: [ CpsButtonToggleComponent, + CpsButtonComponent, CpsFileUploadComponent, ComponentDocsViewerComponent, CpsDividerComponent @@ -23,6 +25,8 @@ import { ComponentDocsViewerComponent } from '../../components/component-docs-vi host: { class: 'composition-page' } }) export class FileUploadPageComponent { + @ViewChild('fileUpload') fileUpload?: CpsFileUploadComponent; + componentData = ComponentData; fileUploadOptions: CpsButtonToggleOption[] = [ @@ -33,11 +37,14 @@ export class FileUploadPageComponent { selectedFileUploadType: CpsButtonToggleOption = this.fileUploadOptions[0]; + isDisabled = true; + fileInfo: string = 'The file should be a small sample file to infer the schema, which will be shown in the next step'; processUploadedFile(file: File): Observable { return from(file.text()).pipe( + delay(2000), map((fileContentsAsText) => { console.log(fileContentsAsText); return true; @@ -57,11 +64,20 @@ export class FileUploadPageComponent { console.log('File upload failed', fileName); } + onFileProcessed(file: File) { + console.log('File processed', file?.name); + } + + onFileProcessingFailed(fileName: string) { + console.log('File processing failed', fileName); + } + onUploadedFileRemoved(fileName: string) { console.log('File removed: ', fileName); } onFileExtensionChanged(event: string) { + this.fileUpload?.resetState(); const foundSelectedItem = this.fileUploadOptions.find( (item) => item.value === event ); @@ -69,4 +85,8 @@ export class FileUploadPageComponent { this.selectedFileUploadType = foundSelectedItem; } } + + toggleDisabled() { + this.isDisabled = !this.isDisabled; + } } diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html index 42974e52..ee37d612 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html @@ -1,23 +1,44 @@ -
-
+
+ @if (errorMessage) { + File upload failed + } @else if (uploadedFile) { + @if (isProcessingFile) { + File is being processed + } @else { + File successfully uploaded + } + } +
+
+ + @if (errorMessage) { +
+ + {{ errorMessage }} +
+ } @if (uploadedFile) {
@@ -52,7 +85,9 @@ + [color]=" + disabled ? 'text-light' : isProcessingFile ? 'warn' : 'success' + ">
- @if (!isProcessingFile) { + @if (!isProcessingFile && !disabled) { + (click)="onRemoveUploadedFile($event)" + (keydown.enter)="onRemoveUploadedFile($event)" + (keydown.space)="onRemoveUploadedFile($event)"> }
diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss index 7a10425b..3a8f9701 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss @@ -1,30 +1,58 @@ +@use '../../../../styles/mixins' as *; + $color-calm: var(--cps-color-calm); -$border-dashed-2px: 2px dashed $color-calm; -$border-dashed-1px: 1px dashed $color-calm; +$border-dashed-2px: 0.125rem dashed $color-calm; +$border-dashed-1px: 0.0625rem dashed $color-calm; $text-color-dark: var(--cps-color-text-dark); $text-color-darkest: var(--cps-color-text-darkest); +$highlight-hover: var(--cps-color-highlight-hover); +$highlight-selected: var(--cps-color-highlight-selected); +$color-error: var(--cps-color-error); +$color-error-darken1: var(--cps-color-error-darken1); +$text-color-mild: var(--cps-color-text-mild); +$border-color-disabled: var(--cps-color-line-darkest); +$bg-disabled: var(--cps-color-bg-disabled); :host { display: flex; .cps-file-upload { + min-width: 0; + position: relative; .cps-file-upload-dropzone { + background: transparent; font-family: 'Source Sans Pro', sans-serif; - min-height: 20px; + min-height: 1.25rem; padding: 2rem; text-align: center; border: $border-dashed-2px; position: relative; border-radius: 1rem; + display: block; + width: 100%; + box-sizing: border-box; + cursor: pointer; + user-select: none; &:hover { - background-color: var(--cps-color-highlight-hover); + background-color: $highlight-hover; + } + &:focus-visible { + @include focus-ring(0.25rem, 0.375rem, inherit); // already rem-based + } + &:focus-visible:not(.processing) { + @include focus-ring(0.25rem, 0.375rem, inherit); + background-color: $highlight-hover; + } + &:active:not(.processing) { + background-color: $highlight-selected; } - &:active { - background-color: var(--cps-color-highlight-selected); + &.processing { + pointer-events: none; + cursor: default; } &.dragged-over { - background-color: var(--cps-color-highlight-selected); + background-color: $highlight-selected; } - &.with-uploads { + &.with-bottom-section { border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-bottom: $border-dashed-1px; @@ -34,32 +62,32 @@ $text-color-darkest: var(--cps-color-text-darkest); cursor: pointer; opacity: 0; position: absolute; + inset: 0; width: 100%; height: 100%; - top: 0; - left: 0; } .cps-file-upload-dropzone-title { - font-size: 16px; - margin: 8px 0; + font-size: 1rem; + margin: 0.5rem 0; color: $color-calm; font-weight: 600; } .cps-file-upload-dropzone-file-desc { - font-size: 18px; + font-size: 1.125rem; color: $text-color-dark; } .cps-file-upload-dropzone-content { - margin-top: 16px; + margin-top: 1rem; font-size: 1rem; + color: $text-color-dark; } .cps-file-upload-progress-bar { position: absolute; - bottom: -2px; + bottom: -0.125rem; left: 0; } } @@ -67,16 +95,16 @@ $text-color-darkest: var(--cps-color-text-darkest); border-bottom: $border-dashed-2px; border-left: $border-dashed-2px; border-right: $border-dashed-2px; - border-bottom-right-radius: 16px; - border-bottom-left-radius: 16px; - background-color: var(--cps-color-highlight-hover); + border-bottom-right-radius: 1rem; + border-bottom-left-radius: 1rem; + background-color: $highlight-hover; .cps-file-upload-uploaded-file { cursor: default; display: flex; align-items: center; justify-content: space-between; - padding: 8px 12px 8px 12px; + padding: 0.5rem 0.75rem; .cps-file-upload-uploaded-file-title { display: flex; align-items: center; @@ -84,25 +112,100 @@ $text-color-darkest: var(--cps-color-text-darkest); min-width: 0; .cps-file-upload-uploaded-file-name { color: $text-color-darkest; - font-size: 20px; - margin: 0 8px; + font-size: 1.25rem; + margin: 0 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - max-width: calc(100% - 60px); + max-width: calc(100% - 3.75rem); } } .cps-file-upload-uploaded-file-remove-icon { cursor: pointer; flex-shrink: 0; - margin-left: 8px; + margin-left: 0.5rem; + &:focus { + outline: none; + } + &:focus-visible { + @include focus-ring(); + } &:hover { ::ng-deep .cps-icon { - color: var(--cps-color-error-darken1) !important; + color: $color-error-darken1 !important; + } + } + } + } + } + + .cps-file-upload-error { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + overflow: hidden; + border-bottom: $border-dashed-2px; + border-left: $border-dashed-2px; + border-right: $border-dashed-2px; + border-bottom-right-radius: 1rem; + border-bottom-left-radius: 1rem; + background-color: $highlight-hover; + color: $color-error; + font-size: 1.25rem; + + span { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + &.disabled { + pointer-events: none; + .cps-file-upload-dropzone { + border-color: $border-color-disabled; + cursor: not-allowed; + .cps-file-upload-dropzone-title { + color: $text-color-mild; + } + .cps-file-upload-dropzone-file-desc { + color: $text-color-mild; + } + .cps-file-upload-dropzone-content { + color: $text-color-mild; + } + } + .cps-file-upload-uploaded-files { + border-color: $border-color-disabled; + background-color: $bg-disabled; + .cps-file-upload-uploaded-file { + .cps-file-upload-uploaded-file-title { + .cps-file-upload-uploaded-file-name { + color: $text-color-mild; } } } } + .cps-file-upload-error { + border-color: $border-color-disabled; + background-color: $bg-disabled; + color: $text-color-mild; + } + } + + .sr-only { + position: absolute; + width: 0.0625rem; + height: 0.0625rem; + padding: 0; + margin: -0.0625rem; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } } } diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts index f6520957..4d0df7e1 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { + booleanAttribute, Component, ElementRef, EventEmitter, @@ -11,7 +12,7 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { catchError, Observable, of, take } from 'rxjs'; +import { catchError, Observable, of, Subject, take, takeUntil } from 'rxjs'; import { convertSize } from '../../utils/internal/size-utils'; import { CpsIconComponent } from '../cps-icon/cps-icon.component'; import { @@ -48,6 +49,12 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { */ @Input() fileDesc = 'Any file'; + /** + * Aria label for the component, used for accessibility. + * @group Props + */ + @Input() ariaLabel = 'Upload file'; + /** * Width of the component, a number denoting pixels or a string. * @group Props @@ -60,6 +67,12 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { */ @Input() fileInfo: string = ''; + /** + * Whether the component is disabled. + * @group Props + */ + @Input({ transform: booleanAttribute }) disabled = false; + /** * Callback for uploaded file processing. * @group Props @@ -93,6 +106,20 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { */ @Output() fileUploadFailed = new EventEmitter(); + /** + * Callback to invoke when file is processed. + * @param {File} File + * @group Emits + */ + @Output() fileProcessed = new EventEmitter(); + + /** + * Callback to invoke when file processing fails. + * @param {string} - file name + * @group Emits + */ + @Output() fileProcessingFailed = new EventEmitter(); + /** * Callback to invoke when uploaded file is removed. * @param {string} - file name @@ -109,6 +136,10 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { cvtWidth = ''; isProcessingFile = false; + errorMessage = ''; + + private dragCounter = 0; + private readonly cancelProcessing$ = new Subject(); ngOnInit(): void { this.updateExtensionsString(); @@ -121,6 +152,49 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { } } + resetState() { + this.cancelProcessing$.next(); + this.removeUploadedFile(); + this.errorMessage = ''; + this.isProcessingFile = false; + this.dragCounter = 0; + } + + openFilePicker(): void { + if (this.isProcessingFile) return; + this.fileInput?.nativeElement.click(); + } + + onDragEnter() { + this.dragCounter++; + this.isDragoverFile = true; + } + + onDragLeave() { + this.dragCounter--; + if (this.dragCounter <= 0) { + this.isDragoverFile = false; + this.dragCounter = 0; + } + } + + onDragEnd() { + this.dragCounter = 0; + this.isDragoverFile = false; + } + + onDragOver(event: DragEvent) { + event.preventDefault(); + this.isDragoverFile = true; + } + + onDrop(event: Event) { + event.preventDefault(); + this.dragCounter = 0; + this.isDragoverFile = false; + this.tryUploadFile(event); + } + updateExtensionsString(): void { this.extensions = this.extensions.map((ext) => ext.startsWith('.') ? ext.toLowerCase() : '.' + ext.toLowerCase() @@ -135,7 +209,10 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { event.preventDefault(); event.stopPropagation(); + if (this.isProcessingFile) return; + this.isDragoverFile = false; + this.errorMessage = ''; let file: File | undefined; if (event.type === 'drop') { @@ -147,6 +224,7 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { } if (!this._isFileExtensionValid(file)) { + this.errorMessage = 'Unsupported file type'; this.fileUploadFailed.emit(file?.name ?? ''); return; } @@ -159,25 +237,41 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { this.fileProcessingCallback(this.uploadedFile) .pipe( take(1), + takeUntil(this.cancelProcessing$), catchError(() => { return of(false); }) ) .subscribe((res) => { - if (!res) this.removeUploadedFile(); this.isProcessingFile = false; + if (res) { + this.fileProcessed.emit(this.uploadedFile); + } else { + this.errorMessage = 'File processing failed'; + this.fileProcessingFailed.emit(this.uploadedFile?.name ?? ''); + this.removeUploadedFile(); + } }); } } } + onRemoveUploadedFile(event: Event) { + event.preventDefault(); + event.stopPropagation(); + this.removeUploadedFile(); + } + removeUploadedFile() { const name = this.uploadedFile?.name ?? ''; this.uploadedFile = undefined; - this.uploadedFileRemoved.emit(name); + if (name) { + this.uploadedFileRemoved.emit(name); + } - if (this.fileInput) { - this.fileInput.nativeElement.value = ''; + const inputEl = this.fileInput?.nativeElement; + if (inputEl) { + inputEl.value = ''; } } @@ -186,10 +280,7 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { if (this.extensions.length < 1) return true; const fileNameLowerCase = file.name.toLowerCase(); - for (const ext of this.extensions) { - if (fileNameLowerCase.endsWith(ext)) return true; - } - return false; + return this.extensions.some((ext) => fileNameLowerCase.endsWith(ext)); } } diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts index af4fe6d4..78c3b0d2 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts @@ -1,5 +1,5 @@ import { CommonModule, DOCUMENT } from '@angular/common'; -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnChanges, OnInit } from '@angular/core'; import { getCSSColor } from '../../utils/colors-utils'; import { convertSize } from '../../utils/internal/size-utils'; @@ -13,7 +13,7 @@ import { convertSize } from '../../utils/internal/size-utils'; templateUrl: './cps-progress-linear.component.html', styleUrls: ['./cps-progress-linear.component.scss'] }) -export class CpsProgressLinearComponent implements OnInit { +export class CpsProgressLinearComponent implements OnInit, OnChanges { /** * Width of the progress bar, of type number denoting pixels or string. * @group Props @@ -61,4 +61,9 @@ export class CpsProgressLinearComponent implements OnInit { this.color = getCSSColor(this.color, this.document); this.bgColor = getCSSColor(this.bgColor, this.document); } + + ngOnChanges(): void { + this.color = getCSSColor(this.color, this.document); + this.bgColor = getCSSColor(this.bgColor, this.document); + } } From cc83c832d2fb980e7925ee5778270814e2d7aff4 Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 23 Apr 2026 21:21:27 +0200 Subject: [PATCH 2/7] update section title --- .../app/pages/file-upload-page/file-upload-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html index b49624cc..b2f716b5 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html @@ -47,7 +47,7 @@

File upload component with extra info

-

Disabled upload component

+

Disabled file upload component

Date: Thu, 23 Apr 2026 21:26:44 +0200 Subject: [PATCH 3/7] update sections colors --- .../src/app/pages/divider-page/divider-page.component.scss | 2 +- .../app/pages/file-upload-page/file-upload-page.component.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/composition/src/app/pages/divider-page/divider-page.component.scss b/projects/composition/src/app/pages/divider-page/divider-page.component.scss index a5577ad9..aac7a0ca 100644 --- a/projects/composition/src/app/pages/divider-page/divider-page.component.scss +++ b/projects/composition/src/app/pages/divider-page/divider-page.component.scss @@ -1,6 +1,6 @@ :host { .section-title { - color: var(--cps-color-calm); + color: var(--cps-color-depth); margin-top: 0; } diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss index 93f4a016..09d9af55 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.scss @@ -1,6 +1,6 @@ :host { .section-title { - color: var(--cps-color-calm); + color: var(--cps-color-depth); } .section-title:first-child { margin-top: 0; From 76e6c8dd264b35bc0c37600e2dd353bb4a11c158 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 24 Apr 2026 10:09:43 +0200 Subject: [PATCH 4/7] move progress bar color updates to ngOnChanges --- .../cps-progress-linear/cps-progress-linear.component.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts index 78c3b0d2..b7b1cca7 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts @@ -57,9 +57,6 @@ export class CpsProgressLinearComponent implements OnInit, OnChanges { this.width = convertSize(this.width); this.height = convertSize(this.height); this.radius = convertSize(this.radius); - - this.color = getCSSColor(this.color, this.document); - this.bgColor = getCSSColor(this.bgColor, this.document); } ngOnChanges(): void { From 7b207aaff04f960d9663309a2d5c81c2ab8eeca1 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 24 Apr 2026 10:15:10 +0200 Subject: [PATCH 5/7] address remaining copilot feedback --- .../cps-file-upload/cps-file-upload.component.html | 2 +- .../cps-file-upload/cps-file-upload.component.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html index ee37d612..52147816 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html @@ -4,7 +4,7 @@ [ngStyle]="{ width: cvtWidth }">

@if (errorMessage) { - File upload failed + {{ errorMessage }} } @else if (uploadedFile) { @if (isProcessingFile) { File is being processed diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts index 4d0df7e1..7a6e0731 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts @@ -7,6 +7,7 @@ import { Input, numberAttribute, OnChanges, + OnDestroy, OnInit, Output, SimpleChanges, @@ -36,7 +37,7 @@ import { CpsProgressLinearComponent } from '../cps-progress-linear/cps-progress- templateUrl: './cps-file-upload.component.html', styleUrls: ['./cps-file-upload.component.scss'] }) -export class CpsFileUploadComponent implements OnInit, OnChanges { +export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { /** * Expected extensions of a file to be uploaded. E.g. 'doc or .doc'. * @group Props @@ -146,6 +147,11 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { this.cvtWidth = convertSize(this.width); } + ngOnDestroy(): void { + this.cancelProcessing$.next(); + this.cancelProcessing$.complete(); + } + ngOnChanges(changes: SimpleChanges): void { if (changes.extensions) { this.updateExtensionsString(); @@ -158,6 +164,7 @@ export class CpsFileUploadComponent implements OnInit, OnChanges { this.errorMessage = ''; this.isProcessingFile = false; this.dragCounter = 0; + this.isDragoverFile = false; } openFilePicker(): void { From 4f8588f40bc9bfe85730b2f0ec6e82ec1811a956 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 24 Apr 2026 13:24:55 +0200 Subject: [PATCH 6/7] address feedback --- api-generator/api-generator.js | 26 +++++++--- .../src/app/api-data/cps-divider.json | 16 +++--- .../src/app/api-data/cps-file-upload.json | 26 +++++++--- .../file-upload-page.component.html | 2 + .../file-upload-page.component.ts | 6 ++- .../cps-divider/cps-divider.component.ts | 4 ++ .../cps-file-upload.component.html | 41 ++++++++++----- .../cps-file-upload.component.scss | 3 +- .../cps-file-upload.component.ts | 51 ++++++++++++++----- .../cps-progress-linear.component.html | 18 +++---- .../cps-progress-linear.component.ts | 43 ++++++++-------- .../lib/utils/internal/accessibility-utils.ts | 6 +++ 12 files changed, 159 insertions(+), 83 deletions(-) diff --git a/api-generator/api-generator.js b/api-generator/api-generator.js index 827f2306..24e6e329 100644 --- a/api-generator/api-generator.js +++ b/api-generator/api-generator.js @@ -136,26 +136,30 @@ async function main() { }; component_props_group.children.forEach((prop) => { + const rawType = + prop.getSignature && prop.getSignature.type + ? prop.getSignature.type.toString() + : prop.type + ? prop.type.toString() + : null; + const isSignalInput = rawType?.startsWith('InputSignal<'); props.values.push({ name: prop.name, optional: prop.flags.isOptional, readonly: prop.flags.isReadonly, - type: - prop.getSignature && prop.getSignature.type - ? prop.getSignature.type.toString() - : prop.type - ? prop.type.toString() - : null, + type: unwrapSignalType(rawType), default: (prop.type && prop.type.name === 'boolean' && !prop.defaultValue ? 'false' - : prop.defaultValue + : prop.defaultValue && + !(isSignalInput && prop.defaultValue === '...') ? prop.defaultValue.replace(/^'|'$/g, '') : undefined) ?? getDefaultValue(prop.setSignature) ?? - getDefaultValue(prop.getSignature), + getDefaultValue(prop.getSignature) ?? + getDefaultValue(prop), description: ( prop.getSignature?.comment?.summary || prop.setSignature?.comment?.summary || @@ -622,6 +626,12 @@ function extractParameter(emitter) { } } +const unwrapSignalType = (type) => { + if (!type) return type; + const match = type.match(/^InputSignal<(.+)>$/); + return match ? match[1] : type; +}; + const isProcessable = (value) => { return value && value.children && value.children.length; }; diff --git a/projects/composition/src/app/api-data/cps-divider.json b/projects/composition/src/app/api-data/cps-divider.json index 99d895fa..5c6ca081 100644 --- a/projects/composition/src/app/api-data/cps-divider.json +++ b/projects/composition/src/app/api-data/cps-divider.json @@ -9,32 +9,32 @@ "name": "vertical", "optional": false, "readonly": false, - "type": "InputSignal", - "default": "...", + "type": "boolean", + "default": "false", "description": "Determines whether the divider is vertically aligned." }, { "name": "color", "optional": false, "readonly": false, - "type": "InputSignal", - "default": "...", + "type": "string", + "default": "line-mid", "description": "Color of the divider." }, { "name": "type", "optional": false, "readonly": false, - "type": "InputSignal", - "default": "...", + "type": "CpsDividerType", + "default": "solid", "description": "Type of the divider." }, { "name": "thickness", "optional": false, "readonly": false, - "type": "InputSignal", - "default": "...", + "type": "string | number", + "default": "1px", "description": "Thickness of the divider, a number denoting pixels or a string." } ] diff --git a/projects/composition/src/app/api-data/cps-file-upload.json b/projects/composition/src/app/api-data/cps-file-upload.json index b55209f8..d442e426 100644 --- a/projects/composition/src/app/api-data/cps-file-upload.json +++ b/projects/composition/src/app/api-data/cps-file-upload.json @@ -29,14 +29,6 @@ "default": "Upload file", "description": "Aria label for the component, used for accessibility." }, - { - "name": "width", - "optional": false, - "readonly": false, - "type": "string | number", - "default": "100%", - "description": "Width of the component, a number denoting pixels or a string." - }, { "name": "fileInfo", "optional": false, @@ -76,6 +68,14 @@ "type": "number", "default": "12", "description": "File name tooltip offset for styling." + }, + { + "name": "width", + "optional": false, + "readonly": false, + "type": "string | number", + "default": "100%", + "description": "Width of the component, a number denoting pixels or a string." } ] }, @@ -122,6 +122,16 @@ ], "description": "Callback to invoke when file processing fails." }, + { + "name": "fileProcessingCancelled", + "parameters": [ + { + "name": "value", + "type": "string" + } + ], + "description": "Callback to invoke when file processing is cancelled." + }, { "name": "uploadedFileRemoved", "parameters": [ diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html index b2f716b5..fecb34c5 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.html @@ -38,6 +38,7 @@

File upload component with extra info

fileNameTooltipOffset="15" [fileProcessingCallback]="processUploadedFile" (fileProcessingFailed)="onFileProcessingFailed($event)" + (fileProcessingCancelled)="onFileProcessingCancelled($event)" (fileUploadFailed)="onFileUploadFailed($event)" (fileUploaded)="onFileUploaded($event)" (fileProcessed)="onFileProcessed($event)" @@ -59,6 +60,7 @@

Disabled file upload component

[disabled]="isDisabled" [fileProcessingCallback]="processUploadedFile" (fileProcessingFailed)="onFileProcessingFailed($event)" + (fileProcessingCancelled)="onFileProcessingCancelled($event)" (fileUploadFailed)="onFileUploadFailed($event)" (fileUploaded)="onFileUploaded($event)" (fileProcessed)="onFileProcessed($event)" diff --git a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts index a0ccb56d..27cf5a73 100644 --- a/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts +++ b/projects/composition/src/app/pages/file-upload-page/file-upload-page.component.ts @@ -44,7 +44,7 @@ export class FileUploadPageComponent { processUploadedFile(file: File): Observable { return from(file.text()).pipe( - delay(2000), + delay(3000), map((fileContentsAsText) => { console.log(fileContentsAsText); return true; @@ -72,6 +72,10 @@ export class FileUploadPageComponent { console.log('File processing failed', fileName); } + onFileProcessingCancelled(fileName: string) { + console.log('File processing cancelled', fileName); + } + onUploadedFileRemoved(fileName: string) { console.log('File removed: ', fileName); } diff --git a/projects/cps-ui-kit/src/lib/components/cps-divider/cps-divider.component.ts b/projects/cps-ui-kit/src/lib/components/cps-divider/cps-divider.component.ts index 184f1076..dfc6135b 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-divider/cps-divider.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-divider/cps-divider.component.ts @@ -35,24 +35,28 @@ export class CpsDividerComponent { /** * Determines whether the divider is vertically aligned. * @group Props + * @default false */ vertical = input(false); /** * Color of the divider. * @group Props + * @default line-mid */ color = input('line-mid'); /** * Type of the divider. * @group Props + * @default solid */ type = input('solid'); /** * Thickness of the divider, a number denoting pixels or a string. * @group Props + * @default 1px */ thickness = input('1px'); diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html index 52147816..ff9c8b93 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.html @@ -1,7 +1,7 @@
+ [style.width]="cvtWidth()">
@if (errorMessage) { {{ errorMessage }} @@ -14,6 +14,7 @@ }
diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss index 3a8f9701..95a1f17c 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.scss @@ -120,7 +120,8 @@ $bg-disabled: var(--cps-color-bg-disabled); max-width: calc(100% - 3.75rem); } } - .cps-file-upload-uploaded-file-remove-icon { + .cps-file-upload-uploaded-file-remove-icon, + .cps-file-upload-uploaded-file-cancel-process-icon { cursor: pointer; flex-shrink: 0; margin-left: 0.5rem; diff --git a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts index 7a6e0731..f5d8be56 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-file-upload/cps-file-upload.component.ts @@ -2,9 +2,11 @@ import { CommonModule } from '@angular/common'; import { booleanAttribute, Component, + computed, ElementRef, EventEmitter, Input, + input, numberAttribute, OnChanges, OnDestroy, @@ -15,6 +17,7 @@ import { } from '@angular/core'; import { catchError, Observable, of, Subject, take, takeUntil } from 'rxjs'; import { convertSize } from '../../utils/internal/size-utils'; +import { focusElement } from '../../utils/internal/accessibility-utils'; import { CpsIconComponent } from '../cps-icon/cps-icon.component'; import { CpsTooltipDirective, @@ -56,12 +59,6 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { */ @Input() ariaLabel = 'Upload file'; - /** - * Width of the component, a number denoting pixels or a string. - * @group Props - */ - @Input() width: number | string = '100%'; - /** * Expected file info block, explaining some extra stuff about file. * @group Props @@ -93,6 +90,13 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { */ @Input({ transform: numberAttribute }) fileNameTooltipOffset: number = 12; + /** + * Width of the component, a number denoting pixels or a string. + * @group Props + * @default 100% + */ + width = input('100%'); + /** * Callback to invoke when file is uploaded. * @param {File} File @@ -121,6 +125,13 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { */ @Output() fileProcessingFailed = new EventEmitter(); + /** + * Callback to invoke when file processing is cancelled. + * @param {string} - file name + * @group Emits + */ + @Output() fileProcessingCancelled = new EventEmitter(); + /** * Callback to invoke when uploaded file is removed. * @param {string} - file name @@ -129,12 +140,13 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { @Output() uploadedFileRemoved = new EventEmitter(); @ViewChild('fileInput') fileInput?: ElementRef; + @ViewChild('dropzoneButton') dropzoneButton?: ElementRef; isDragoverFile = false; uploadedFile?: File; extensionsString = ''; extensionsStringAsterisks = ''; - cvtWidth = ''; + cvtWidth = computed(() => convertSize(this.width())); isProcessingFile = false; errorMessage = ''; @@ -144,7 +156,6 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { ngOnInit(): void { this.updateExtensionsString(); - this.cvtWidth = convertSize(this.width); } ngOnDestroy(): void { @@ -159,10 +170,8 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { } resetState() { - this.cancelProcessing$.next(); - this.removeUploadedFile(); + this.cancelFileProcessing(); this.errorMessage = ''; - this.isProcessingFile = false; this.dragCounter = 0; this.isDragoverFile = false; } @@ -267,10 +276,28 @@ export class CpsFileUploadComponent implements OnInit, OnChanges, OnDestroy { event.preventDefault(); event.stopPropagation(); this.removeUploadedFile(); + focusElement(this.dropzoneButton?.nativeElement); + } + + onCancelFileProcessing(event: Event) { + event.preventDefault(); + event.stopPropagation(); + this.cancelFileProcessing(); + focusElement(this.dropzoneButton?.nativeElement); + } + + cancelFileProcessing() { + this.cancelProcessing$.next(); + this.isProcessingFile = false; + const name = this.uploadedFile?.name; + if (name) { + this.fileProcessingCancelled.emit(name); + } + this.removeUploadedFile(); } removeUploadedFile() { - const name = this.uploadedFile?.name ?? ''; + const name = this.uploadedFile?.name; this.uploadedFile = undefined; if (name) { this.uploadedFileRemoved.emit(name); diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.html b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.html index 2e9a96b1..e2cae6e7 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.html @@ -1,17 +1,15 @@
+ [style.max-width]="cvtWidth()" + [style.height]="cvtHeight()" + [style.border-radius]="cvtRadius()" + [style.background]="cssBgColor()">
+ [style.background]="cssColor()" + [style.opacity]="opacity()">
+ [style.background]="cssColor()" + [style.opacity]="opacity()">
diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts index b7b1cca7..16b1de9f 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.ts @@ -1,5 +1,5 @@ -import { CommonModule, DOCUMENT } from '@angular/common'; -import { Component, Inject, Input, OnChanges, OnInit } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { Component, computed, inject, input } from '@angular/core'; import { getCSSColor } from '../../utils/colors-utils'; import { convertSize } from '../../utils/internal/size-utils'; @@ -8,59 +8,58 @@ import { convertSize } from '../../utils/internal/size-utils'; * @group Components */ @Component({ - imports: [CommonModule], selector: 'cps-progress-linear', templateUrl: './cps-progress-linear.component.html', styleUrls: ['./cps-progress-linear.component.scss'] }) -export class CpsProgressLinearComponent implements OnInit, OnChanges { +export class CpsProgressLinearComponent { /** * Width of the progress bar, of type number denoting pixels or string. * @group Props + * @default 100% */ - @Input() width: number | string = '100%'; + width = input('100%'); /** * Height of the progress bar, of type number denoting pixels or string. * @group Props + * @default 0.5rem */ - @Input() height: number | string = '0.5rem'; + height = input('0.5rem'); /** * Color of the progress bar. * @group Props + * @default var(--cps-accent-primary) */ - @Input() color = 'var(--cps-accent-primary)'; + color = input('var(--cps-accent-primary)'); /** * Background color of the progress bar. * @group Props + * @default white */ - @Input() bgColor = 'white'; + bgColor = input('white'); /** * Option to control the opacity of the progress bar. * @group Props + * @default 1 */ - @Input() opacity: number | string = 1; + opacity = input(1); /** * Border radius of the progress bar, of type number denoting pixels or string. * @group Props + * @default 0 */ - @Input() radius: number | string = 0; + radius = input(0); - // eslint-disable-next-line no-useless-constructor - constructor(@Inject(DOCUMENT) private document: Document) {} + private readonly document = inject(DOCUMENT); - ngOnInit(): void { - this.width = convertSize(this.width); - this.height = convertSize(this.height); - this.radius = convertSize(this.radius); - } - - ngOnChanges(): void { - this.color = getCSSColor(this.color, this.document); - this.bgColor = getCSSColor(this.bgColor, this.document); - } + cvtWidth = computed(() => convertSize(this.width())); + cvtHeight = computed(() => convertSize(this.height())); + cvtRadius = computed(() => convertSize(this.radius())); + cssColor = computed(() => getCSSColor(this.color(), this.document)); + cssBgColor = computed(() => getCSSColor(this.bgColor(), this.document)); } diff --git a/projects/cps-ui-kit/src/lib/utils/internal/accessibility-utils.ts b/projects/cps-ui-kit/src/lib/utils/internal/accessibility-utils.ts index fafe4f0c..d6d6e327 100644 --- a/projects/cps-ui-kit/src/lib/utils/internal/accessibility-utils.ts +++ b/projects/cps-ui-kit/src/lib/utils/internal/accessibility-utils.ts @@ -26,3 +26,9 @@ export const getComputedLabel = (context: { return parts.length > 0 ? parts.join(' ') : null; }; + +export const focusElement = (element?: HTMLElement): void => { + if (element) { + setTimeout(() => element.focus()); + } +}; From 33f9f83b2fa02bd6cadf6596154a4c2fe486aaac Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 24 Apr 2026 13:38:44 +0200 Subject: [PATCH 7/7] fix uts --- .../cps-progress-linear.component.spec.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.spec.ts b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.spec.ts index c4949746..4e3aa770 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.spec.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-progress-linear/cps-progress-linear.component.spec.ts @@ -20,66 +20,66 @@ describe('CpsProgressLinearComponent', () => { }); it('should have default values', () => { - expect(component.width).toBe('100%'); - expect(component.height).toBe('0.5rem'); - expect(component.color).toBeTruthy(); - expect(component.bgColor).toBeTruthy(); - expect(component.opacity).toBe(1); - expect(component.radius).toBe('0px'); + expect(component.width()).toBe('100%'); + expect(component.height()).toBe('0.5rem'); + expect(component.color()).toBeTruthy(); + expect(component.bgColor()).toBeTruthy(); + expect(component.opacity()).toBe(1); + expect(component.cvtRadius()).toBe('0px'); }); - it('should convert width on init', () => { - component.width = 200; - component.ngOnInit(); - expect(component.width).toBe('200px'); + it('should convert width to px', () => { + fixture.componentRef.setInput('width', 200); + fixture.detectChanges(); + expect(component.cvtWidth()).toBe('200px'); }); it('should keep width as string if already string', () => { fixture.componentRef.setInput('width', '50%'); fixture.detectChanges(); - expect(component.width).toBe('50%'); + expect(component.cvtWidth()).toBe('50%'); }); - it('should convert height on init', () => { - component.height = 10; - component.ngOnInit(); - expect(component.height).toBe('10px'); + it('should convert height to px', () => { + fixture.componentRef.setInput('height', 10); + fixture.detectChanges(); + expect(component.cvtHeight()).toBe('10px'); }); it('should keep height as string if already string', () => { fixture.componentRef.setInput('height', '1rem'); fixture.detectChanges(); - expect(component.height).toBe('1rem'); + expect(component.cvtHeight()).toBe('1rem'); }); - it('should convert radius on init', () => { - component.radius = 4; - component.ngOnInit(); - expect(component.radius).toBe('4px'); + it('should convert radius to px', () => { + fixture.componentRef.setInput('radius', 4); + fixture.detectChanges(); + expect(component.cvtRadius()).toBe('4px'); }); it('should keep radius as string if already string', () => { fixture.componentRef.setInput('radius', '0.25rem'); fixture.detectChanges(); - expect(component.radius).toBe('0.25rem'); + expect(component.cvtRadius()).toBe('0.25rem'); }); it('should set custom color', () => { fixture.componentRef.setInput('color', 'primary'); fixture.detectChanges(); - expect(component.color).toBeTruthy(); + expect(component.cssColor()).toBeTruthy(); }); it('should set custom background color', () => { fixture.componentRef.setInput('bgColor', 'line-light'); fixture.detectChanges(); - expect(component.bgColor).toBeTruthy(); + expect(component.cssBgColor()).toBeTruthy(); }); it('should set custom opacity', () => { fixture.componentRef.setInput('opacity', 0.5); fixture.detectChanges(); - expect(component.opacity).toBe(0.5); + expect(component.opacity()).toBe(0.5); }); it('should display progress bar', () => {