mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
ACS-11154 Viewer stabilisation, lint fixes (#11660)
* refactor: clean up code in SuperagentHttpClient and upload.spec - Removed unnecessary eslint-disable comment in SuperagentHttpClient for improved code clarity. - Refactored promise handling in upload.spec to utilize an array for better management of multiple promises during file upload error handling. * chore: update cspell and ESLint configurations - Added "webscript" to the cspell dictionary for improved spell checking. - Updated ESLint configuration to disable the "@typescript-eslint/no-explicit-any" rule, allowing more flexibility in type definitions. * fix: enhance ImgViewerComponent to handle cleanup and prevent errors after destruction - Added a `destroyed` flag to manage component lifecycle and prevent operations on a destroyed instance. - Implemented `afterEach` hooks in tests to ensure proper fixture cleanup. - Updated key event handlers and methods to check for the `destroyed` state before executing actions, improving stability and preventing errors. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: add aria-hidden attribute to notification history button for accessibility - Updated the notification history button to include the `aria-hidden` attribute, improving accessibility for screen readers and enhancing user experience. * fix: improve key event handling in ImgViewerComponent - Updated key event handlers to check for the presence of the cropper before executing actions, enhancing stability and preventing errors when the component is destroyed. - Removed redundant checks from individual arrow key handlers, streamlining the code. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-1
@@ -145,7 +145,8 @@
|
|||||||
"Whitespaces",
|
"Whitespaces",
|
||||||
"xdescribe",
|
"xdescribe",
|
||||||
"xsrf",
|
"xsrf",
|
||||||
"zestiria"
|
"zestiria",
|
||||||
|
"webscript"
|
||||||
],
|
],
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
"html",
|
"html",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
id="adf-notification-history-open-button"
|
id="adf-notification-history-open-button"
|
||||||
(menuOpened)="onMenuOpened()"
|
(menuOpened)="onMenuOpened()"
|
||||||
>
|
>
|
||||||
<mat-icon matBadge="⁠" [matBadgeHidden]="!notifications.length" class="adf-notification-history-menu_button-icon" matBadgeColor="accent" matBadgeSize="small" adf-icon="notifications" />
|
<mat-icon aria-hidden="false" matBadge="⁠" [matBadgeHidden]="!notifications.length" class="adf-notification-history-menu_button-icon" matBadgeColor="accent" matBadgeSize="small" adf-icon="notifications" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu"
|
<mat-menu #menu="matMenu"
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
describe('default value', () => {
|
describe('default value', () => {
|
||||||
it('should use default zoom if is not present a custom zoom in the app.config', () => {
|
it('should use default zoom if is not present a custom zoom in the app.config', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -103,6 +107,10 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should display current scale as percent string', () => {
|
it('should display current scale as percent string', () => {
|
||||||
component.scale = 0.5;
|
component.scale = 0.5;
|
||||||
expect(component.currentScaleText).toBe('50%');
|
expect(component.currentScaleText).toBe('50%');
|
||||||
@@ -127,6 +135,10 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should thrown an error if no url or blob are passed', () => {
|
it('should thrown an error if no url or blob are passed', () => {
|
||||||
const change = new SimpleChange(null, null, true);
|
const change = new SimpleChange(null, null, true);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
@@ -181,6 +193,10 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should update scales on zoom in', fakeAsync(() => {
|
it('should update scales on zoom in', fakeAsync(() => {
|
||||||
spyOn(component, 'zoomIn').and.callThrough();
|
spyOn(component, 'zoomIn').and.callThrough();
|
||||||
spyOn(component.cropper, 'zoom');
|
spyOn(component.cropper, 'zoom');
|
||||||
@@ -377,6 +393,10 @@ describe('Test Img viewer component ', () => {
|
|||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should conditionally display rotate and crop buttons based on allowedEditActions', () => {
|
it('should conditionally display rotate and crop buttons based on allowedEditActions', () => {
|
||||||
component.readOnly = false;
|
component.readOnly = false;
|
||||||
component.allowedEditActions = { rotate: true, crop: true };
|
component.allowedEditActions = { rotate: true, crop: true };
|
||||||
@@ -401,7 +421,14 @@ describe('Test Img viewer component ', () => {
|
|||||||
fixture = TestBed.createComponent(ImgViewerComponent);
|
fixture = TestBed.createComponent(ImgViewerComponent);
|
||||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.urlFile =
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
component.ngAfterViewInit();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rotate the image when r key is pressed', () => {
|
it('should rotate the image when r key is pressed', () => {
|
||||||
@@ -428,101 +455,119 @@ describe('Test Img viewer component ', () => {
|
|||||||
expect(component.zoomIn).toHaveBeenCalled();
|
expect(component.zoomIn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move the cropper when arrow keys are pressed', () => {
|
it('should move the cropper when arrow keys are pressed', fakeAsync(() => {
|
||||||
spyOn(component.cropper, 'move');
|
spyOn(component.cropper, 'move');
|
||||||
dispatchKeyboardEvent('ArrowLeft');
|
dispatchKeyboardEvent('ArrowLeft');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.move).toHaveBeenCalledWith(-3, 0);
|
expect(component.cropper.move).toHaveBeenCalledWith(-3, 0);
|
||||||
|
|
||||||
dispatchKeyboardEvent('ArrowRight');
|
dispatchKeyboardEvent('ArrowRight');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.move).toHaveBeenCalledWith(3, 0);
|
expect(component.cropper.move).toHaveBeenCalledWith(3, 0);
|
||||||
|
|
||||||
dispatchKeyboardEvent('ArrowUp');
|
dispatchKeyboardEvent('ArrowUp');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.move).toHaveBeenCalledWith(0, -3);
|
expect(component.cropper.move).toHaveBeenCalledWith(0, -3);
|
||||||
|
|
||||||
dispatchKeyboardEvent('ArrowDown');
|
dispatchKeyboardEvent('ArrowDown');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.move).toHaveBeenCalledWith(0, 3);
|
expect(component.cropper.move).toHaveBeenCalledWith(0, 3);
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('should increase crop box area when arrow keys with shift are pressed', () => {
|
it('should increase crop box area when arrow keys with shift are pressed', fakeAsync(() => {
|
||||||
component.cropImage();
|
component.cropImage();
|
||||||
spyOn(component.cropper, 'setCropBoxData');
|
spyOn(component.cropper, 'setCropBoxData');
|
||||||
let expectedCropBoxData = getExpectedCropBoxData(component.cropper, -3, 3, 0, 0);
|
let expectedCropBoxData = getExpectedCropBoxData(component.cropper, -3, 3, 0, 0);
|
||||||
dispatchKeyboardEvent('ArrowLeft', true);
|
dispatchKeyboardEvent('ArrowLeft', true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 3, 0, 0);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 3, 0, 0);
|
||||||
dispatchKeyboardEvent('ArrowRight', true);
|
dispatchKeyboardEvent('ArrowRight', true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, -3, 3);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, -3, 3);
|
||||||
dispatchKeyboardEvent('ArrowUp', true);
|
dispatchKeyboardEvent('ArrowUp', true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 0, 3);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 0, 3);
|
||||||
dispatchKeyboardEvent('ArrowDown', true);
|
dispatchKeyboardEvent('ArrowDown', true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('should decrease crop box area when arrow keys with alt are pressed', () => {
|
it('should decrease crop box area when arrow keys with alt are pressed', fakeAsync(() => {
|
||||||
component.cropImage();
|
component.cropImage();
|
||||||
spyOn(component.cropper, 'setCropBoxData');
|
spyOn(component.cropper, 'setCropBoxData');
|
||||||
let expectedCropBoxData = getExpectedCropBoxData(component.cropper, 3, -3, 0, 0);
|
let expectedCropBoxData = getExpectedCropBoxData(component.cropper, 3, -3, 0, 0);
|
||||||
dispatchKeyboardEvent('ArrowLeft', false, true);
|
dispatchKeyboardEvent('ArrowLeft', false, true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, -3, 0, 0);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, -3, 0, 0);
|
||||||
dispatchKeyboardEvent('ArrowRight', false, true);
|
dispatchKeyboardEvent('ArrowRight', false, true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 3, -3);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 3, -3);
|
||||||
dispatchKeyboardEvent('ArrowUp', false, true);
|
dispatchKeyboardEvent('ArrowUp', false, true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
|
|
||||||
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 0, -3);
|
expectedCropBoxData = getExpectedCropBoxData(component.cropper, 0, 0, 0, -3);
|
||||||
dispatchKeyboardEvent('ArrowDown', false, true);
|
dispatchKeyboardEvent('ArrowDown', false, true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
expect(component.cropper.setCropBoxData).toHaveBeenCalledWith(expectedCropBoxData);
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
it('should prevent default for all arrow keys events', fakeAsync(() => {
|
||||||
|
spyOn(component.cropper, 'move');
|
||||||
|
|
||||||
it('should prevent default for all arrow keys events', () => {
|
|
||||||
const leftEvent = dispatchKeyboardEvent('ArrowLeft');
|
const leftEvent = dispatchKeyboardEvent('ArrowLeft');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
expect(leftEvent.preventDefault).toHaveBeenCalled();
|
expect(leftEvent.preventDefault).toHaveBeenCalled();
|
||||||
|
|
||||||
const rightEvent = dispatchKeyboardEvent('ArrowRight');
|
const rightEvent = dispatchKeyboardEvent('ArrowRight');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
expect(rightEvent.preventDefault).toHaveBeenCalled();
|
expect(rightEvent.preventDefault).toHaveBeenCalled();
|
||||||
|
|
||||||
const upEvent = dispatchKeyboardEvent('ArrowUp');
|
const upEvent = dispatchKeyboardEvent('ArrowUp');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
expect(upEvent.preventDefault).toHaveBeenCalled();
|
expect(upEvent.preventDefault).toHaveBeenCalled();
|
||||||
|
|
||||||
const downEvent = dispatchKeyboardEvent('ArrowDown');
|
const downEvent = dispatchKeyboardEvent('ArrowDown');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
expect(downEvent.preventDefault).toHaveBeenCalled();
|
expect(downEvent.preventDefault).toHaveBeenCalled();
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
@HostListener('document:keyup', ['$event'])
|
@HostListener('document:keyup', ['$event'])
|
||||||
onKeyDown(event: KeyboardEvent) {
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.destroyed || !this.cropper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
this.handleArrowLeftKey(event);
|
this.handleArrowLeftKey(event);
|
||||||
@@ -130,6 +133,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
scale: number = 1.0;
|
scale: number = 1.0;
|
||||||
cropper: Cropper;
|
cropper: Cropper;
|
||||||
isEditing: boolean = false;
|
isEditing: boolean = false;
|
||||||
|
private destroyed: boolean = false;
|
||||||
|
|
||||||
get currentScaleText(): string {
|
get currentScaleText(): string {
|
||||||
return Math.round(this.scale * 100) + '%';
|
return Math.round(this.scale * 100) + '%';
|
||||||
@@ -178,7 +182,11 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
this.destroyed = true;
|
||||||
|
if (this.cropper) {
|
||||||
this.cropper.destroy();
|
this.cropper.destroy();
|
||||||
|
this.cropper = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeScaling() {
|
initializeScaling() {
|
||||||
@@ -189,11 +197,17 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
zoomIn() {
|
zoomIn() {
|
||||||
|
if (this.destroyed || !this.cropper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.cropper.zoom(0.2);
|
this.cropper.zoom(0.2);
|
||||||
this.scale = +(this.scale + 0.2).toFixed(1);
|
this.scale = +(this.scale + 0.2).toFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut() {
|
zoomOut() {
|
||||||
|
if (this.destroyed || !this.cropper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.scale > 0.2) {
|
if (this.scale > 0.2) {
|
||||||
this.cropper.zoom(-0.2);
|
this.cropper.zoom(-0.2);
|
||||||
this.scale = +(this.scale - 0.2).toFixed(1);
|
this.scale = +(this.scale - 0.2).toFixed(1);
|
||||||
@@ -201,6 +215,9 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rotateImage() {
|
rotateImage() {
|
||||||
|
if (this.destroyed || !this.cropper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.isEditing = true;
|
this.isEditing = true;
|
||||||
this.cropper.rotate(-90);
|
this.cropper.rotate(-90);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
"@typescript-eslint/consistent-type-definitions": "off",
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
"no-redeclare": "off",
|
"no-redeclare": "off",
|
||||||
"space-before-function-paren": "off",
|
"space-before-function-paren": "off",
|
||||||
"@typescript-eslint/no-empty-interface": "warn"
|
"@typescript-eslint/no-empty-interface": "warn",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ export class SuperagentHttpClient implements HttpClient {
|
|||||||
queryParams: { [key: string]: any },
|
queryParams: { [key: string]: any },
|
||||||
headerParams: { [key: string]: any },
|
headerParams: { [key: string]: any },
|
||||||
formParams: { [key: string]: any },
|
formParams: { [key: string]: any },
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
bodyParam: string | object,
|
||||||
bodyParam: string | Object,
|
|
||||||
contentType: string,
|
contentType: string,
|
||||||
accept: string,
|
accept: string,
|
||||||
responseType: string,
|
responseType: string,
|
||||||
|
|||||||
@@ -301,21 +301,20 @@ describe('Upload', () => {
|
|||||||
|
|
||||||
uploadMock.get401Response();
|
uploadMock.get401Response();
|
||||||
|
|
||||||
let promiseProgressOne = {};
|
const promises: Promise<string>[] = [];
|
||||||
let promiseProgressTwo = {};
|
|
||||||
|
|
||||||
const uploadPromise: any = uploadApi.uploadFile(file);
|
const uploadPromise: any = uploadApi.uploadFile(file);
|
||||||
uploadPromise.catch(() => {});
|
uploadPromise.catch(() => {});
|
||||||
|
|
||||||
uploadPromise
|
uploadPromise
|
||||||
.once('error', () => {
|
.once('error', () => {
|
||||||
promiseProgressOne = Promise.resolve('Resolving');
|
promises.push(Promise.resolve('Resolving'));
|
||||||
})
|
})
|
||||||
.once('unauthorized', () => {
|
.once('unauthorized', () => {
|
||||||
promiseProgressTwo = Promise.resolve('Resolving');
|
promises.push(Promise.resolve('Resolving'));
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise.all([promiseProgressOne, promiseProgressTwo]).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user