From b55bc42779912c395856a7ea8d1d1725c9ee86ea Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 1 Apr 2020 20:53:30 +0300 Subject: [PATCH] feat(core): add fromLazyLoad utility function --- .../src/lib/tests/lazy-load-utils.spec.ts | 105 ++++++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + .../core/src/lib/utils/lazy-load-utils.ts | 54 +++++++++ 3 files changed, 160 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts new file mode 100644 index 0000000000..657afeead1 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -0,0 +1,105 @@ +import { DomStrategy, DOM_STRATEGY } from '../strategies'; +import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; +import { uuid } from '../utils'; +import { fromLazyLoad } from '../utils/lazy-load-utils'; + +describe('Lazy Load Utils', () => { + describe('#fromLazyLoad', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should append to head by default', () => { + const element = document.createElement('link'); + const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + + fromLazyLoad(element); + expect(spy).toHaveBeenCalledWith('beforeend', element); + }); + + it('should allow setting a dom strategy', () => { + const element = document.createElement('link'); + const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + + fromLazyLoad(element, DOM_STRATEGY.PrependToHead()); + expect(spy).toHaveBeenCalledWith('afterbegin', element); + }); + + it('should set crossorigin to "anonymous" by default', () => { + const element = document.createElement('link'); + + fromLazyLoad(element); + + expect(element.crossOrigin).toBe('anonymous'); + }); + + it('should allow setting a crossorigin strategy', () => { + const element = document.createElement('link'); + + const integrity = uuid(); + + fromLazyLoad(element, undefined, CROSS_ORIGIN_STRATEGY.UseCredentials(integrity)); + + expect(element.crossOrigin).toBe('use-credentials'); + expect(element.getAttribute('integrity')).toBe(integrity); + }); + + it('should emit error event on fail and clear callbacks', done => { + const error = new CustomEvent('error'); + const parentNode = { removeChild: jest.fn() }; + const element = ({ parentNode } as any) as HTMLLinkElement; + + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onerror(error); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(el: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + error: value => { + expect(value).toBe(error); + expect(parentNode.removeChild).toHaveBeenCalledWith(element); + expect(element.onerror).toBeNull(); + done(); + }, + }); + }); + + it('should emit load event on success and clear callbacks', done => { + const success = new CustomEvent('load'); + const parentNode = { removeChild: jest.fn() }; + const element = ({ parentNode } as any) as HTMLLinkElement; + + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onload(success); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(el: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + next: value => { + expect(value).toBe(success); + expect(parentNode.removeChild).not.toHaveBeenCalled(); + expect(element.onload).toBeNull(); + done(); + }, + }); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index 0043152ada..a0404f7568 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -1,5 +1,6 @@ export * from './common-utils'; export * from './generator-utils'; export * from './initial-utils'; +export * from './lazy-load-utils'; export * from './route-utils'; export * from './rxjs-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts new file mode 100644 index 0000000000..5aee3bc715 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -0,0 +1,54 @@ +import { Observable, Observer } from 'rxjs'; +import { + CrossOriginStrategy, + CROSS_ORIGIN_STRATEGY, + DomStrategy, + DOM_STRATEGY, +} from '../strategies'; + +export function fromLazyLoad( + element: HTMLScriptElement | HTMLLinkElement, + domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), + crossOriginStrategy: CrossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(), +): Observable { + crossOriginStrategy.setCrossOrigin(element); + domStrategy.insertElement(element); + + return Observable.create((observer: Observer) => { + element.onload = event => { + clearCallbacks(element); + observer.next(event); + observer.complete(); + }; + + const handleError = createErrorHandler(observer, element); + + element.onerror = handleError; + element.onabort = handleError; + element.onemptied = handleError; + element.onstalled = handleError; + element.onsuspend = handleError; + + return () => { + clearCallbacks(element); + observer.complete(); + }; + }); +} + +function createErrorHandler(observer: Observer, element: HTMLElement) { + return function(event: Event | string) { + clearCallbacks(element); + element.parentNode.removeChild(element); + observer.error(event); + }; +} + +function clearCallbacks(element: HTMLElement) { + element.onload = null; + element.onerror = null; + element.onabort = null; + element.onemptied = null; + element.onstalled = null; + element.onsuspend = null; +}