mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-12098] firefox headless race condition viewer fix (#12023)
* [ACS-12098] firefox headless race condition viewer fix * [ACS-12098] review fixes 1 * [ACS-12098] review fixes 2 * [ACS-12098] review fixes 3 * last firefox headless fixes
This commit is contained in:
@@ -32,7 +32,8 @@ import {
|
|||||||
ViewerMoreActionsComponent,
|
ViewerMoreActionsComponent,
|
||||||
ViewerToolbarActionsComponent,
|
ViewerToolbarActionsComponent,
|
||||||
NoopAuthModule,
|
NoopAuthModule,
|
||||||
NoopTranslateModule
|
NoopTranslateModule,
|
||||||
|
UnitTestingUtils
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { NodesApiService } from '../../common/services/nodes-api.service';
|
import { NodesApiService } from '../../common/services/nodes-api.service';
|
||||||
import { UploadService } from '../../common/services/upload.service';
|
import { UploadService } from '../../common/services/upload.service';
|
||||||
@@ -177,6 +178,7 @@ describe('AlfrescoViewerComponent', () => {
|
|||||||
let renditionService: RenditionService;
|
let renditionService: RenditionService;
|
||||||
let viewUtilService: ViewUtilService;
|
let viewUtilService: ViewUtilService;
|
||||||
let nodeActionsService: NodeActionsService;
|
let nodeActionsService: NodeActionsService;
|
||||||
|
let testingUtils: UnitTestingUtils;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -197,6 +199,7 @@ describe('AlfrescoViewerComponent', () => {
|
|||||||
fixture = TestBed.createComponent(AlfrescoViewerComponent);
|
fixture = TestBed.createComponent(AlfrescoViewerComponent);
|
||||||
element = fixture.nativeElement;
|
element = fixture.nativeElement;
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||||
uploadService = TestBed.inject(UploadService);
|
uploadService = TestBed.inject(UploadService);
|
||||||
nodesApiService = TestBed.inject(NodesApiService);
|
nodesApiService = TestBed.inject(NodesApiService);
|
||||||
dialog = TestBed.inject(MatDialog);
|
dialog = TestBed.inject(MatDialog);
|
||||||
@@ -398,12 +401,16 @@ describe('AlfrescoViewerComponent', () => {
|
|||||||
component.nodeId = 'id1';
|
component.nodeId = 'id1';
|
||||||
component.showViewer = true;
|
component.showViewer = true;
|
||||||
component.versionId = null;
|
component.versionId = null;
|
||||||
component.ngOnChanges(getSimpleChanges('id1'));
|
|
||||||
|
|
||||||
await fixture.whenStable();
|
await fixture.ngZone.run(async () => {
|
||||||
|
component.ngOnChanges(getSimpleChanges('id1'));
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.fileName).toBe('file1.pdf');
|
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||||
expect(component.blobFileContent).toBe(mockBlob);
|
expect(viewer.fileName).toBe('file1.pdf');
|
||||||
|
expect(viewer.blobFile).toBe(mockBlob);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change display name every time node`s version changes', fakeAsync(() => {
|
it('should change display name every time node`s version changes', fakeAsync(() => {
|
||||||
@@ -1063,4 +1070,127 @@ describe('AlfrescoViewerComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PDF blob fetch and atomic state assignment', () => {
|
||||||
|
it('should assign blobFileContent and null urlFileContent when PDF blob fetch succeeds', async () => {
|
||||||
|
const mockBlob = new Blob(['pdf content'], { type: 'application/pdf' });
|
||||||
|
const mockResponse = { ok: true, blob: () => Promise.resolve(mockBlob) } as Response;
|
||||||
|
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||||
|
|
||||||
|
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||||
|
Promise.resolve(
|
||||||
|
new NodeEntry({
|
||||||
|
entry: new Node({ name: 'test.pdf', id: 'node-1', content: new ContentInfo({ mimeType: 'application/pdf' }) })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
component.nodeId = 'node-1';
|
||||||
|
component.showViewer = true;
|
||||||
|
|
||||||
|
await fixture.ngZone.run(async () => {
|
||||||
|
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||||
|
expect(viewer.blobFile).toBe(mockBlob);
|
||||||
|
expect(viewer.urlFile).toBeFalsy();
|
||||||
|
expect(viewer.mimeType).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to URL-based viewing when PDF blob fetch fails', async () => {
|
||||||
|
spyOn(window, 'fetch').and.returnValue(Promise.reject(new Error('Network error')));
|
||||||
|
spyOn(component.contentApi, 'getContentUrl').and.returnValue('/content/url');
|
||||||
|
|
||||||
|
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||||
|
Promise.resolve(
|
||||||
|
new NodeEntry({
|
||||||
|
entry: new Node({
|
||||||
|
name: 'test.pdf',
|
||||||
|
id: 'node-1',
|
||||||
|
content: new ContentInfo({ mimeType: 'application/pdf' }),
|
||||||
|
properties: { 'cm:versionLabel': '1.0' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
component.nodeId = 'node-1';
|
||||||
|
component.showViewer = true;
|
||||||
|
|
||||||
|
await fixture.ngZone.run(async () => {
|
||||||
|
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||||
|
expect(viewer.blobFile).toBeFalsy();
|
||||||
|
expect(viewer.urlFile).toBeTruthy();
|
||||||
|
expect(viewer.mimeType).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to URL-based viewing when PDF fetch returns non-ok response', async () => {
|
||||||
|
const mockResponse = { ok: false, status: 403 } as Response;
|
||||||
|
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||||
|
spyOn(component.contentApi, 'getContentUrl').and.returnValue('/content/url');
|
||||||
|
|
||||||
|
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||||
|
Promise.resolve(
|
||||||
|
new NodeEntry({
|
||||||
|
entry: new Node({
|
||||||
|
name: 'test.pdf',
|
||||||
|
id: 'node-1',
|
||||||
|
content: new ContentInfo({ mimeType: 'application/pdf' }),
|
||||||
|
properties: { 'cm:versionLabel': '1.0' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
component.nodeId = 'node-1';
|
||||||
|
component.showViewer = true;
|
||||||
|
|
||||||
|
await fixture.ngZone.run(async () => {
|
||||||
|
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||||
|
expect(viewer.blobFile).toBeFalsy();
|
||||||
|
expect(viewer.urlFile).toBeTruthy();
|
||||||
|
expect(viewer.mimeType).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not expose intermediate null state during node setup', async () => {
|
||||||
|
const mockBlob = new Blob(['pdf content'], { type: 'application/pdf' });
|
||||||
|
const mockResponse = { ok: true, blob: () => Promise.resolve(mockBlob) } as Response;
|
||||||
|
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||||
|
|
||||||
|
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||||
|
Promise.resolve(
|
||||||
|
new NodeEntry({
|
||||||
|
entry: new Node({ name: 'test.pdf', id: 'node-1', content: new ContentInfo({ mimeType: 'application/pdf' }) })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
component.nodeId = 'node-1';
|
||||||
|
component.showViewer = true;
|
||||||
|
|
||||||
|
await fixture.ngZone.run(async () => {
|
||||||
|
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||||
|
expect(viewer.blobFile).toBe(mockBlob);
|
||||||
|
expect(viewer.urlFile).toBeFalsy();
|
||||||
|
expect(viewer.fileName).toBe('test.pdf');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -275,9 +275,9 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
|||||||
private async onNodeUpdated(node: Node) {
|
private async onNodeUpdated(node: Node) {
|
||||||
if (node && node.id === this.nodeId) {
|
if (node && node.id === this.nodeId) {
|
||||||
this.generateCacheBusterNumber();
|
this.generateCacheBusterNumber();
|
||||||
this.blobFileContent = null;
|
|
||||||
|
|
||||||
await this.setUpNodeFile(node);
|
await this.setUpNodeFile(node);
|
||||||
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +300,6 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
|||||||
|
|
||||||
private async setupNode() {
|
private async setupNode() {
|
||||||
try {
|
try {
|
||||||
this.blobFileContent = null;
|
|
||||||
this.nodeEntry = await this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
|
this.nodeEntry = await this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
|
||||||
if (this.versionId) {
|
if (this.versionId) {
|
||||||
this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId);
|
this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId);
|
||||||
@@ -318,6 +317,7 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
|||||||
this.canEditNode = this.contentService.hasAllowableOperations(nodeData, 'update');
|
this.canEditNode = this.contentService.hasAllowableOperations(nodeData, 'update');
|
||||||
let mimeType: string;
|
let mimeType: string;
|
||||||
let urlFileContent: string;
|
let urlFileContent: string;
|
||||||
|
let blobContent: Blob = null;
|
||||||
|
|
||||||
if (versionData?.content) {
|
if (versionData?.content) {
|
||||||
mimeType = versionData.content.mimeType;
|
mimeType = versionData.content.mimeType;
|
||||||
@@ -352,32 +352,24 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
|||||||
} else if (viewerType === 'media') {
|
} else if (viewerType === 'media') {
|
||||||
this.tracks = await this.renditionService.generateMediaTracksRendition(this.nodeId);
|
this.tracks = await this.renditionService.generateMediaTracksRendition(this.nodeId);
|
||||||
} else if (viewerType === 'pdf') {
|
} else if (viewerType === 'pdf') {
|
||||||
try {
|
const contentUrl = versionData
|
||||||
const contentUrl = versionData
|
? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id)
|
||||||
? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id)
|
: this.contentApi.getContentUrl(this.nodeId);
|
||||||
: this.contentApi.getContentUrl(this.nodeId);
|
|
||||||
|
|
||||||
// Fetch the content as Blob using fetch API with credentials
|
const blob = await fetch(contentUrl, { credentials: 'include', headers: { 'Cache-Control': 'no-cache' } })
|
||||||
const response = await fetch(contentUrl, {
|
.then((response) => (response.ok ? response.blob() : null))
|
||||||
credentials: 'include',
|
.catch(() => null);
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'no-cache'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blobFileContent = await response.blob();
|
if (blob) {
|
||||||
|
blobContent = blob;
|
||||||
urlFileContent = null;
|
urlFileContent = null;
|
||||||
} catch (error) {
|
|
||||||
console.error('[ADF DEBUG] Failed to fetch PDF as blob, falling back to URL', error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.nodeMimeType = nodeMimeType;
|
this.nodeMimeType = nodeMimeType;
|
||||||
this.fileName = versionData ? versionData.name : nodeData.name;
|
this.fileName = versionData ? versionData.name : nodeData.name;
|
||||||
|
this.blobFileContent = blobContent;
|
||||||
this.urlFileContent = urlFileContent ? urlFileContent + (this.cacheBusterNumber ? '&' + this.cacheBusterNumber : '') : null;
|
this.urlFileContent = urlFileContent ? urlFileContent + (this.cacheBusterNumber ? '&' + this.cacheBusterNumber : '') : null;
|
||||||
this.sidebarRightTemplateContext.node = nodeData;
|
this.sidebarRightTemplateContext.node = nodeData;
|
||||||
this.sidebarLeftTemplateContext.node = nodeData;
|
this.sidebarLeftTemplateContext.node = nodeData;
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ describe('ViewerComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(getMainLoader()).toBeNull();
|
expect(getMainLoader()).toBeNull();
|
||||||
expect(component.viewerType).toBe('media');
|
expect(testingUtils.getByDirective(MediaPlayerComponent)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display spinner when viewerType is pdf', () => {
|
it('should display spinner when viewerType is pdf', () => {
|
||||||
@@ -553,7 +553,7 @@ describe('ViewerComponent', () => {
|
|||||||
imgViewer.triggerEventHandler('pagesLoaded', null);
|
imgViewer.triggerEventHandler('pagesLoaded', null);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.viewerType).toBe('pdf');
|
expect(testingUtils.getByDirective(MockPdfViewerComponent)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show spinner until renderer calls markAsLoaded', () => {
|
it('should show spinner until renderer calls markAsLoaded', () => {
|
||||||
@@ -570,7 +570,7 @@ describe('ViewerComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(getMainLoader()).toBeNull();
|
expect(getMainLoader()).toBeNull();
|
||||||
expect(component.viewerType).toBe('image');
|
expect(testingUtils.getByDirective(ImgViewerComponent)).not.toBeNull();
|
||||||
expect(component.markAsLoaded).toHaveBeenCalled();
|
expect(component.markAsLoaded).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -582,7 +582,77 @@ describe('ViewerComponent', () => {
|
|||||||
component.onUnsupportedFile();
|
component.onUnsupportedFile();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(getMainLoader()).toBeNull();
|
expect(getMainLoader()).toBeNull();
|
||||||
expect(component.viewerType).toBe('unknown');
|
expect(testingUtils.getByCSS('adf-viewer-unknown-format')).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Blob MIME type handling', () => {
|
||||||
|
it('should resolve viewerType from blob type when blob has valid MIME type', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: 'application/pdf' });
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByDirective(MockPdfViewerComponent)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to mimeType input when blob type resolves to unknown', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: '' });
|
||||||
|
component.mimeType = 'application/pdf';
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByDirective(MockPdfViewerComponent)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remain unknown when both blob type and mimeType input are empty', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: '' });
|
||||||
|
component.mimeType = '';
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByCSS('adf-viewer-unknown-format')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip MIME type parameters (e.g. charset) from blob type before resolving viewer type', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: 'application/pdf;charset=utf-8' });
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByDirective(MockPdfViewerComponent)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip MIME type parameters from mimeType input during fallback', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: '' });
|
||||||
|
component.mimeType = 'application/pdf;charset=utf-8';
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByDirective(MockPdfViewerComponent)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve image viewer from blob type with parameters', () => {
|
||||||
|
component.blobFile = new Blob(['content'], { type: 'image/png; charset=binary' });
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(testingUtils.getByDirective(ImgViewerComponent)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit extensionChange with blob MIME type when available', () => {
|
||||||
|
spyOn(component.extensionChange, 'emit');
|
||||||
|
component.blobFile = new Blob(['content'], { type: 'application/pdf' });
|
||||||
|
component.ngOnChanges();
|
||||||
|
|
||||||
|
expect(component.extensionChange.emit).toHaveBeenCalledWith('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit extensionChange with mimeType input when blob type is empty', () => {
|
||||||
|
spyOn(component.extensionChange, 'emit');
|
||||||
|
component.blobFile = new Blob(['content'], { type: '' });
|
||||||
|
component.mimeType = 'application/pdf';
|
||||||
|
component.ngOnChanges();
|
||||||
|
|
||||||
|
expect(component.extensionChange.emit).toHaveBeenCalledWith('application/pdf');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -241,15 +241,23 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
|
|||||||
|
|
||||||
private setUpBlobData() {
|
private setUpBlobData() {
|
||||||
this.internalFileName = this.fileName;
|
this.internalFileName = this.fileName;
|
||||||
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type);
|
const blobMimeType = this.extractMimeTypeEssence(this.blobFile.type);
|
||||||
|
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(blobMimeType);
|
||||||
|
if (this.viewerType === 'unknown' && this.mimeType) {
|
||||||
|
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.extractMimeTypeEssence(this.mimeType));
|
||||||
|
}
|
||||||
if (this.viewerType === 'unknown') {
|
if (this.viewerType === 'unknown') {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.extensionChange.emit(this.blobFile.type);
|
this.extensionChange.emit(blobMimeType || this.extractMimeTypeEssence(this.mimeType));
|
||||||
this.scrollTop();
|
this.scrollTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractMimeTypeEssence(mimeType: string): string {
|
||||||
|
return (mimeType || '').split(';')[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
private setUpUrlFile() {
|
private setUpUrlFile() {
|
||||||
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
|
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
|
||||||
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
|
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { ViewerWithCustomToolbarComponent } from './mock/adf-viewer-container-to
|
|||||||
import { ViewerComponent } from './viewer.component';
|
import { ViewerComponent } from './viewer.component';
|
||||||
import { ThumbnailService } from '../../common/services/thumbnail.service';
|
import { ThumbnailService } from '../../common/services/thumbnail.service';
|
||||||
import { MatIconTestingModule } from '@angular/material/icon/testing';
|
import { MatIconTestingModule } from '@angular/material/icon/testing';
|
||||||
|
import { ViewerRenderComponent } from './viewer-render/viewer-render.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-dialog-dummy',
|
selector: 'adf-dialog-dummy',
|
||||||
@@ -113,8 +114,57 @@ describe('ViewerComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
component.ngOnChanges(mockSimpleChanges);
|
component.ngOnChanges(mockSimpleChanges);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.mimeType).toBe('image/png');
|
const viewerRender = testingUtils.getByDirective(ViewerRenderComponent).componentInstance as ViewerRenderComponent;
|
||||||
|
expect(viewerRender.mimeType).toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip MIME type parameters from blob type', () => {
|
||||||
|
const mockSimpleChanges: SimpleChanges = {
|
||||||
|
blobFile: new SimpleChange(null, { type: 'application/pdf;charset=utf-8' }, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
component.ngOnChanges(mockSimpleChanges);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewerRender = testingUtils.getByDirective(ViewerRenderComponent).componentInstance as ViewerRenderComponent;
|
||||||
|
expect(viewerRender.mimeType).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip MIME type parameters with spaces from blob type', () => {
|
||||||
|
const mockSimpleChanges: SimpleChanges = {
|
||||||
|
blobFile: new SimpleChange(null, { type: 'image/png; charset=binary' }, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
component.ngOnChanges(mockSimpleChanges);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewerRender = testingUtils.getByDirective(ViewerRenderComponent).componentInstance as ViewerRenderComponent;
|
||||||
|
expect(viewerRender.mimeType).toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty blob type gracefully', () => {
|
||||||
|
const mockSimpleChanges: SimpleChanges = {
|
||||||
|
blobFile: new SimpleChange(null, { type: '' }, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
component.ngOnChanges(mockSimpleChanges);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const viewerRender = testingUtils.getByDirective(ViewerRenderComponent).componentInstance as ViewerRenderComponent;
|
||||||
|
expect(viewerRender.mimeType).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set mimeTypeIconUrl with stripped MIME type from blob', () => {
|
||||||
|
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('icon-url');
|
||||||
|
const mockSimpleChanges: SimpleChanges = {
|
||||||
|
blobFile: new SimpleChange(null, { type: 'application/pdf;charset=utf-8' }, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
component.ngOnChanges(mockSimpleChanges);
|
||||||
|
|
||||||
|
expect(thumbnailService.getMimeTypeIcon).toHaveBeenCalledWith('application/pdf');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set mimeTypeIconUrl when mimeType changes and no nodeMimeType is provided', () => {
|
it('should set mimeTypeIconUrl when mimeType changes and no nodeMimeType is provided', () => {
|
||||||
|
|||||||
@@ -347,8 +347,9 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
|
|||||||
const { blobFile, urlFile, mimeType, nodeMimeType } = changes;
|
const { blobFile, urlFile, mimeType, nodeMimeType } = changes;
|
||||||
|
|
||||||
if (blobFile?.currentValue) {
|
if (blobFile?.currentValue) {
|
||||||
this.mimeType = blobFile.currentValue.type;
|
const blobMimeType = this.extractMimeTypeEssence(blobFile.currentValue.type);
|
||||||
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(blobFile.currentValue.type);
|
this.mimeType = blobMimeType;
|
||||||
|
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(blobMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlFile?.currentValue) {
|
if (urlFile?.currentValue) {
|
||||||
@@ -356,14 +357,18 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mimeType?.currentValue && !nodeMimeType?.currentValue) {
|
if (mimeType?.currentValue && !nodeMimeType?.currentValue) {
|
||||||
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(mimeType.currentValue);
|
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(this.extractMimeTypeEssence(mimeType.currentValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeMimeType?.currentValue) {
|
if (nodeMimeType?.currentValue) {
|
||||||
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(nodeMimeType.currentValue);
|
this.mimeTypeIconUrl = this.thumbnailService.getMimeTypeIcon(this.extractMimeTypeEssence(nodeMimeType.currentValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractMimeTypeEssence(mimeType: string): string {
|
||||||
|
return (mimeType || '').split(';')[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.closeOverlayManager();
|
this.closeOverlayManager();
|
||||||
this.configureAndInitDownloadPrompt();
|
this.configureAndInitDownloadPrompt();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumb
|
|||||||
import { PDFJS_MODULE, PDFJS_VIEWER_MODULE, PdfViewerComponent } from './pdf-viewer.component';
|
import { PDFJS_MODULE, PDFJS_VIEWER_MODULE, PdfViewerComponent } from './pdf-viewer.component';
|
||||||
import pdfjsLibraryMock, { annotations } from '../mock/pdfjs-lib.mock';
|
import pdfjsLibraryMock, { annotations } from '../mock/pdfjs-lib.mock';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { PDFDocumentLoadingTask } from 'pdfjs-dist/types/src/display/api';
|
||||||
|
|
||||||
declare const pdfjsLib: {
|
declare const pdfjsLib: {
|
||||||
PasswordResponses: {
|
PasswordResponses: {
|
||||||
@@ -354,6 +355,54 @@ describe('Test PdfViewer component', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Test PdfViewer - executePdf error handling', () => {
|
||||||
|
let fixture: ComponentFixture<PdfViewerComponent>;
|
||||||
|
let component: PdfViewerComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const failingLoadingTask = {
|
||||||
|
promise: Promise.reject(new Error('PDF load failed')),
|
||||||
|
onPassword: null,
|
||||||
|
onProgress: null,
|
||||||
|
destroy: () => Promise.resolve()
|
||||||
|
} as PDFDocumentLoadingTask;
|
||||||
|
|
||||||
|
const pdfjsLibMock = {
|
||||||
|
GlobalWorkerOptions: {},
|
||||||
|
getDocument: () => failingLoadingTask
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [PdfViewerComponent],
|
||||||
|
providers: [
|
||||||
|
provideCoreAuthTesting(),
|
||||||
|
{
|
||||||
|
provide: MatDialog,
|
||||||
|
useValue: {
|
||||||
|
open: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RenderingQueueServices,
|
||||||
|
{ provide: PDFJS_MODULE, useValue: pdfjsLibMock }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PdfViewerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit error event when PDF loading fails', fakeAsync(() => {
|
||||||
|
spyOn(component.error, 'emit');
|
||||||
|
// Pre-set the worker URL so the real setupPdfJsWorker logic runs without performing a network fetch
|
||||||
|
component.pdfJsWorkerUrl = URL.createObjectURL(new Blob([''], { type: 'application/javascript' }));
|
||||||
|
|
||||||
|
component.executePdf({ data: new ArrayBuffer(0) });
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(component.error.emit).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
describe('Test PdfViewer - Zoom customization', () => {
|
describe('Test PdfViewer - Zoom customization', () => {
|
||||||
let fixture: ComponentFixture<PdfViewerComponent>;
|
let fixture: ComponentFixture<PdfViewerComponent>;
|
||||||
let component: PdfViewerComponent;
|
let component: PdfViewerComponent;
|
||||||
|
|||||||
@@ -292,7 +292,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setTimeout(() => this.scalePage('init'));
|
setTimeout(() => this.scalePage('init'));
|
||||||
})
|
})
|
||||||
.catch(() => this.error.emit());
|
.catch(() => {
|
||||||
|
this.error.emit();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user