From 078925261afa8755e0e5c7cd54f17ed2b8dcb41d Mon Sep 17 00:00:00 2001 From: Mykyta Maliarchuk <84377976+nikita-web-ua@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:27:11 +0200 Subject: [PATCH] =?UTF-8?q?Revert=20"[ACS-9374][ACS-9590]=20Prevent=20sear?= =?UTF-8?q?ch=20popup=20flicker=20on=20Enter=20key=20press=20=E2=80=A6"=20?= =?UTF-8?q?(#4714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c4938247782ac87803f5398ca336a9af9c660479. --- projects/aca-content/assets/i18n/en.json | 4 +- .../search-input-control.component.html | 7 +- .../search-input-control.component.spec.ts | 28 ++-- .../search-input-control.component.ts | 75 +-------- .../search-input/search-input.component.html | 7 +- .../search-input/search-input.component.scss | 8 +- .../search-input.component.spec.ts | 150 ------------------ .../search-input/search-input.component.ts | 28 +--- .../no-whitespace.validator.spec.ts | 50 ------ .../lib/validators/no-whitespace.validator.ts | 37 ----- projects/aca-shared/src/public-api.ts | 6 +- 11 files changed, 35 insertions(+), 365 deletions(-) delete mode 100644 projects/aca-content/src/lib/components/search/search-input/search-input.component.spec.ts delete mode 100644 projects/aca-shared/src/lib/validators/no-whitespace.validator.spec.ts delete mode 100644 projects/aca-shared/src/lib/validators/no-whitespace.validator.ts diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index d7c539e78..8f1445966 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -574,9 +574,7 @@ "FILES": "Files", "FOLDERS": "Folders", "LIBRARIES": "Libraries", - "MIN_LENGTH": "Search input must have at least 2 alphanumeric characters.", - "REQUIRED": "Search input is required.", - "WHITESPACE": "Search input cannot be only whitespace." + "HINT": "Search input must have at least 2 alphanumeric characters." }, "SORT": { "SORTING_OPTION": "Sort by", diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html index 168c01dee..609f50d88 100644 --- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html +++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html @@ -6,7 +6,6 @@ matPrefix class="app-search-button" (click)="searchSubmit()" - (keydown.enter)="searchSubmit()" [title]="'SEARCH.BUTTON.TOOLTIP' | translate" > search @@ -19,16 +18,14 @@ [type]="inputType" id="app-control-input" [formControl]="searchFieldFormControl" - (keydown.enter)="searchSubmit()" - (blur)="onBlur()" + (keyup.enter)="searchSubmit()" [placeholder]="'SEARCH.INPUT.PLACEHOLDER' | translate" autocomplete="off" /> -
- \ No newline at end of file + diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts index b6dfac27a..ea035e75b 100644 --- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts +++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts @@ -30,44 +30,40 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('SearchInputControlComponent', () => { let fixture: ComponentFixture; let component: SearchInputControlComponent; + beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, SearchInputControlComponent], schemas: [NO_ERRORS_SCHEMA] }); + fixture = TestBed.createComponent(SearchInputControlComponent); component = fixture.componentInstance; fixture.detectChanges(); }); - it('should emit submit event if form is valid', () => { - component.searchTerm = 'valid'; - spyOn(component.submit, 'emit'); + it('should emit submit event on searchSubmit', () => { + component.searchTerm = 'mock-search-term'; + + let submittedSearchTerm = ''; + component.submit.subscribe((searchTerm) => (submittedSearchTerm = searchTerm)); component.searchSubmit(); - - expect(component.submit.emit).toHaveBeenCalledWith('valid'); - }); - - it('should not emit submit event if form is invalid', () => { - component.searchTerm = ''; - spyOn(component.submit, 'emit'); - - component.searchSubmit(); - - expect(component.submit.emit).not.toHaveBeenCalled(); + expect(submittedSearchTerm).toBe('mock-search-term'); }); it('should emit searchChange event on inputChange', () => { let emittedSearchTerm = ''; component.searchChange.subscribe((searchTerm) => (emittedSearchTerm = searchTerm)); component.searchTerm = 'mock-search-term'; + expect(emittedSearchTerm).toBe('mock-search-term'); }); it('should emit searchChange event on clear', () => { let emittedSearchTerm: string = null; component.searchChange.subscribe((searchTerm) => (emittedSearchTerm = searchTerm)); + component.clear(); expect(emittedSearchTerm).toBe(''); }); @@ -75,14 +71,18 @@ describe('SearchInputControlComponent', () => { it('should clear searchTerm', () => { component.searchTerm = 'c'; fixture.detectChanges(); + component.clear(); expect(component.searchTerm).toBe(''); }); it('should check if searchTerm has a length less than 2', () => { + expect(component.isTermTooShort()).toBe(false); + component.searchTerm = 'd'; fixture.detectChanges(); expect(component.isTermTooShort()).toBe(true); + component.searchTerm = 'dd'; fixture.detectChanges(); expect(component.isTermTooShort()).toBe(false); diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts index d326d67c2..6b73d6052 100644 --- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts +++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts @@ -22,29 +22,15 @@ * from Hyland Software. If not, see . */ -import { - Component, - EventEmitter, - Input, - Output, - ViewEncapsulation, - ViewChild, - ElementRef, - OnInit, - inject, - DestroyRef, - OnChanges, - SimpleChanges -} from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewEncapsulation, ViewChild, ElementRef, OnInit, inject, DestroyRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslatePipe } from '@ngx-translate/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { FormControl, FormsModule, ReactiveFormsModule, StatusChangeEvent, TouchedChangeEvent, Validators } from '@angular/forms'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { noWhitespaceValidator } from '@alfresco/aca-shared'; @Component({ imports: [CommonModule, TranslatePipe, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule], @@ -54,20 +40,13 @@ import { noWhitespaceValidator } from '@alfresco/aca-shared'; encapsulation: ViewEncapsulation.None, host: { class: 'app-search-control' } }) -export class SearchInputControlComponent implements OnInit, OnChanges { +export class SearchInputControlComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); /** Type of the input field to render, e.g. "search" or "text" (default). */ @Input() inputType = 'text'; - /** - * Indicates whether the search is constrained by libraries. - * If true, specific error messaging or validation behavior may be triggered. - */ - @Input() - hasLibrariesConstraint = false; - /** Emitted when the search is submitted pressing ENTER button. * The search term is provided as value of the event. */ @@ -83,14 +62,10 @@ export class SearchInputControlComponent implements OnInit, OnChanges { @Output() searchChange: EventEmitter = new EventEmitter(); - /** Emitted when the input control has a validation error. */ - @Output() - validationError = new EventEmitter(); - @ViewChild('searchInput', { static: true }) searchInput: ElementRef; - searchFieldFormControl = new FormControl('', [Validators.required, noWhitespaceValidator()]); + searchFieldFormControl = new FormControl(''); get searchTerm(): string { return this.searchFieldFormControl.value.replace('text:', 'TEXT:'); @@ -105,30 +80,11 @@ export class SearchInputControlComponent implements OnInit, OnChanges { this.searchFieldFormControl.markAsTouched(); this.searchChange.emit(searchTermValue); }); - - this.searchFieldFormControl.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { - if (event instanceof TouchedChangeEvent || event instanceof StatusChangeEvent) { - if (this.searchFieldFormControl.touched) { - this.emitValidationError(); - } else { - this.validationError.emit(''); - } - } - }); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes['hasLibrariesConstraint'] && !changes['hasLibrariesConstraint'].firstChange) { - this.emitValidationError(); - } } searchSubmit() { - this.searchFieldFormControl.markAsTouched(); - - const trimmedTerm = this.searchTerm?.trim(); - if (this.searchFieldFormControl.valid && trimmedTerm) { - this.submit.emit(trimmedTerm); + if (!this.searchFieldFormControl.errors) { + this.submit.emit(this.searchTerm); } } @@ -137,24 +93,7 @@ export class SearchInputControlComponent implements OnInit, OnChanges { this.searchChange.emit(''); } - onBlur() { - this.searchFieldFormControl.markAsUntouched(); - } - isTermTooShort() { - return this.searchTerm.trim()?.length < 2; - } - - emitValidationError(): void { - const errors = this.searchFieldFormControl.errors; - if (errors?.whitespace) { - this.validationError.emit('SEARCH.INPUT.WHITESPACE'); - } else if (errors?.required) { - this.validationError.emit('SEARCH.INPUT.REQUIRED'); - } else if (this.hasLibrariesConstraint && this.isTermTooShort()) { - this.validationError.emit('SEARCH.INPUT.MIN_LENGTH'); - } else { - this.validationError.emit(''); - } + return !!(this.searchTerm && this.searchTerm.length < 2); } } diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html index 5f4db0969..e5a2ef805 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html @@ -35,12 +35,9 @@ (click)="$event.stopPropagation()" (submit)="onSearchSubmit($event)" (searchChange)="onSearchChange($event)" - (validationError)="error = $event" - [hasLibrariesConstraint]="hasLibrariesConstraint" /> - - {{ error | translate }} - + {{ 'SEARCH.INPUT.HINT' | translate }} +
. - */ - -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { UnitTestingUtils } from '@alfresco/adf-core'; -import { MatError } from '@angular/material/form-field'; -import { AppStore } from '@alfresco/aca-shared/store'; -import { AppTestingModule } from '../../../testing/app-testing.module'; -import { SearchInputComponent } from './search-input.component'; -import { Store } from '@ngrx/store'; -import { of } from 'rxjs'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; -import { MatMenuHarness } from '@angular/material/menu/testing'; -import { MatCheckboxHarness } from '@angular/material/checkbox/testing'; - -describe('SearchInputComponent', () => { - let fixture: ComponentFixture; - let component: SearchInputComponent; - let store: jasmine.SpyObj>; - let unitTestingUtils: UnitTestingUtils; - let loader: HarnessLoader; - - function getFirstError(): string { - const error = unitTestingUtils.getByDirective(MatError); - return error?.nativeElement.textContent.trim(); - } - - async function openMenu(): Promise { - const menu = await loader.getHarness(MatMenuHarness); - await menu.open(); - return menu; - } - - function getCheckbox(id: string): Promise { - const overlayLoader = TestbedHarnessEnvironment.documentRootLoader(fixture); - return overlayLoader.getHarness(MatCheckboxHarness.with({ selector: `#${id}` })); - } - - async function uncheckAllCheckboxes() { - const checkboxIds = ['libraries', 'folder', 'content']; - for (const id of checkboxIds) { - try { - const checkbox = await getCheckbox(id); - if (await checkbox.isChecked()) { - await checkbox.uncheck(); - fixture.detectChanges(); - } - } catch (err) { - fail(`Checkbox with id ${id} not found`); - throw err; - } - } - } - - beforeEach(async () => { - const storeSpy = jasmine.createSpyObj>('Store', ['dispatch', 'pipe']); - - await TestBed.configureTestingModule({ - imports: [AppTestingModule, SearchInputComponent], - providers: [{ provide: Store, useValue: storeSpy }] - }).compileComponents(); - - fixture = TestBed.createComponent(SearchInputComponent); - component = fixture.componentInstance; - store = TestBed.inject(Store) as jasmine.SpyObj>; - store.pipe.and.returnValue(of([])); - fixture.detectChanges(); - unitTestingUtils = new UnitTestingUtils(fixture.debugElement); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should show required error when field is empty and touched', async () => { - await openMenu(); - - component.searchInputControl.searchFieldFormControl.setValue(''); - component.searchInputControl.searchFieldFormControl.markAsTouched(); - fixture.detectChanges(); - - expect(getFirstError()).toBe('SEARCH.INPUT.REQUIRED'); - }); - - it('should not show error when field has value', async () => { - await openMenu(); - - component.searchInputControl.searchFieldFormControl.setValue('not giving up'); - component.searchInputControl.searchFieldFormControl.markAsTouched(); - fixture.detectChanges(); - - const error = unitTestingUtils.getByDirective(MatError); - expect(error).toBeNull(); - }); - - it('should not show error when field is untouched', async () => { - await openMenu(); - - component.searchInputControl.searchFieldFormControl.setValue(''); - component.searchInputControl.searchFieldFormControl.markAsUntouched(); - fixture.detectChanges(); - - const error = unitTestingUtils.getByDirective(MatError); - expect(error).toBeNull(); - }); - - it('should dispatch action when Libraries checkbox selected and term is entered', async () => { - await openMenu(); - const checkbox = await getCheckbox('libraries'); - await checkbox.check(); - fixture.detectChanges(); - - component.onSearchSubmit({ target: { value: 'happy faces only' } }); - expect(store.dispatch).toHaveBeenCalled(); - }); - - it('should not dispatch SearchByTermAction when no checkboxes are selected and term is empty', async () => { - store.dispatch.calls.reset(); - - await openMenu(); - await uncheckAllCheckboxes(); - - expect(component.searchOptions.every((option) => !option.value)).toBeTrue(); - - component.searchedWord = ''; - fixture.detectChanges(); - - component.onSearchSubmit({ target: { value: '' } }); - expect(store.dispatch).not.toHaveBeenCalled(); - }); -}); diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts b/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts index 2e2eb7bb2..204998302 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.ts @@ -73,8 +73,6 @@ export class SearchInputComponent implements OnInit, OnDestroy { has400LibraryError = false; hasLibrariesConstraint = false; searchOnChange: boolean; - isTrimmedWordEmpty = false; - error = ''; searchedWord: string = null; searchOptions: Array = [ @@ -153,8 +151,8 @@ export class SearchInputComponent implements OnInit, OnDestroy { showInputValue() { this.appService.setAppNavbarMode('collapsed'); this.has400LibraryError = false; - this.searchedWord = this.getUrlSearchTerm(); this.hasLibrariesConstraint = this.evaluateLibrariesConstraint(); + this.searchedWord = this.getUrlSearchTerm(); if (this.searchInputControl) { this.searchInputControl.searchTerm = this.searchedWord; @@ -179,15 +177,10 @@ export class SearchInputComponent implements OnInit, OnDestroy { */ onSearchSubmit(event: any) { const searchTerm = event.target ? (event.target as HTMLInputElement).value : event; - const trimmedTerm = searchTerm.trim(); + if (searchTerm) { + this.searchedWord = searchTerm; - if (trimmedTerm) { - this.searchedWord = trimmedTerm; - if (this.isLibrariesChecked() && this.searchInputControl.isTermTooShort()) { - return; - } else { - this.searchByOption(); - } + this.searchByOption(); } else { this.notificationService.showError('APP.BROWSE.SEARCH.EMPTY_SEARCH'); } @@ -203,26 +196,15 @@ export class SearchInputComponent implements OnInit, OnDestroy { } this.has400LibraryError = false; - this.searchedWord = searchTerm; this.hasLibrariesConstraint = this.evaluateLibrariesConstraint(); + this.searchedWord = searchTerm; } searchByOption() { this.syncInputValues(); this.has400LibraryError = false; - - this.searchInputControl.emitValidationError(); - - if (!this.searchedWord.trim()) { - return; - } - if (this.isLibrariesChecked()) { this.hasLibrariesConstraint = this.evaluateLibrariesConstraint(); - - if (this.hasLibrariesConstraint) { - return; - } if (this.onLibrariesSearchResults && this.isSameSearchTerm()) { this.queryLibrariesBuilder.update(); } else if (this.searchedWord) { diff --git a/projects/aca-shared/src/lib/validators/no-whitespace.validator.spec.ts b/projects/aca-shared/src/lib/validators/no-whitespace.validator.spec.ts deleted file mode 100644 index 4454834f2..000000000 --- a/projects/aca-shared/src/lib/validators/no-whitespace.validator.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Alfresco Example Content Application - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * from Hyland Software. If not, see . - */ - -import { FormControl } from '@angular/forms'; -import { noWhitespaceValidator } from './no-whitespace.validator'; - -describe('noWhitespaceValidator', () => { - const validatorFn = noWhitespaceValidator(); - - it('should return null for valid non-whitespace input', () => { - const control = new FormControl('valid input'); - expect(validatorFn(control)).toBeNull(); - }); - - it('should return error for input with only spaces', () => { - const control = new FormControl(' '); - expect(validatorFn(control)).toEqual({ whitespace: true }); - }); - - it('should return error for empty string', () => { - const control = new FormControl(''); - expect(validatorFn(control)).toBeNull(); - }); - - it('should return null for input with leading and trailing spaces but valid content inside', () => { - const control = new FormControl(' valid '); - expect(validatorFn(control)).toBeNull(); - }); -}); diff --git a/projects/aca-shared/src/lib/validators/no-whitespace.validator.ts b/projects/aca-shared/src/lib/validators/no-whitespace.validator.ts deleted file mode 100644 index 2e48d1479..000000000 --- a/projects/aca-shared/src/lib/validators/no-whitespace.validator.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Alfresco Example Content Application - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * from Hyland Software. If not, see . - */ - -import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; - -export const noWhitespaceValidator = (): ValidatorFn => { - return (control: AbstractControl): ValidationErrors | null => { - const rawValue = control.value; - if (!rawValue) { - return null; - } - - const trimmedValue = rawValue.toString().trim(); - return trimmedValue.length === 0 ? { whitespace: true } : null; - }; -}; diff --git a/projects/aca-shared/src/public-api.ts b/projects/aca-shared/src/public-api.ts index 791835901..d3edd7b69 100644 --- a/projects/aca-shared/src/public-api.ts +++ b/projects/aca-shared/src/public-api.ts @@ -40,7 +40,6 @@ export * from './lib/components/document-base-page/document-base-page.component' export * from './lib/components/document-base-page/document-base-page.service'; export * from './lib/components/open-in-app/open-in-app.component'; export * from './lib/constants'; - export * from './lib/directives/contextmenu/contextmenu.directive'; export * from './lib/directives/pagination.directive'; @@ -61,8 +60,5 @@ export * from './lib/services/app-settings.service'; export * from './lib/services/user-profile.service'; export * from './lib/services/navigation-history.service'; -export * from './lib/testing/lib-testing-module'; - export * from './lib/utils/node.utils'; - -export * from './lib/validators/no-whitespace.validator'; +export * from './lib/testing/lib-testing-module';