
committed by
Miško Hevery

parent
46efd4b938
commit
87f60bccfd
@ -4,7 +4,7 @@
|
||||
"uncompressed": {
|
||||
"inline": 2062,
|
||||
"main": 467103,
|
||||
"polyfills": 55349,
|
||||
"polyfills": 54292,
|
||||
"embedded": 71711,
|
||||
"prettify": 14888
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ describe('CodeExampleComponent', () => {
|
||||
});
|
||||
|
||||
it('should be able to capture the code snippet provided in content', () => {
|
||||
expect(codeExampleComponent.code.trim()).toBe(`const foo = "bar";`);
|
||||
expect(codeExampleComponent.aioCode.code.trim()).toBe(`const foo = "bar";`);
|
||||
});
|
||||
|
||||
it('should change aio-code classes based on title presence', () => {
|
||||
|
@ -34,8 +34,6 @@ import { CodeComponent } from './code.component';
|
||||
export class CodeExampleComponent implements AfterViewInit {
|
||||
classes: {};
|
||||
|
||||
code: string;
|
||||
|
||||
@Input() language: string;
|
||||
|
||||
@Input() linenums: string;
|
||||
|
@ -9,8 +9,6 @@ import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
|
||||
|
||||
const actualCustomElements = window.customElements;
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
@ -29,21 +27,26 @@ class FakeComponentFactory extends ComponentFactory<any> {
|
||||
}
|
||||
|
||||
const FAKE_COMPONENT_FACTORIES = new Map([
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')]
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')],
|
||||
['element-b-module-path', new FakeComponentFactory('element-b-input')],
|
||||
]);
|
||||
|
||||
fdescribe('ElementsLoader', () => {
|
||||
describe('ElementsLoader', () => {
|
||||
let elementsLoader: ElementsLoader;
|
||||
let injectedModuleRef: NgModuleRef<any>;
|
||||
let fakeCustomElements;
|
||||
let actualCustomElementsDefine;
|
||||
let fakeCustomElementsDefine;
|
||||
|
||||
// ElementsLoader uses the window's customElements API. Provide a fake for this test.
|
||||
beforeEach(() => {
|
||||
fakeCustomElements = jasmine.createSpyObj('customElements', ['define', 'whenDefined']);
|
||||
window.customElements = fakeCustomElements;
|
||||
actualCustomElementsDefine = window.customElements.define;
|
||||
|
||||
fakeCustomElementsDefine = jasmine.createSpy('define');
|
||||
|
||||
window.customElements.define = fakeCustomElementsDefine;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.customElements = actualCustomElements;
|
||||
window.customElements.define = actualCustomElementsDefine;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -52,7 +55,8 @@ fdescribe('ElementsLoader', () => {
|
||||
ElementsLoader,
|
||||
{ provide: NgModuleFactoryLoader, useClass: FakeModuleFactoryLoader },
|
||||
{ provide: ELEMENT_MODULE_PATHS_TOKEN, useValue: new Map([
|
||||
['element-a-selector', 'element-a-module-path']
|
||||
['element-a-selector', 'element-a-module-path'],
|
||||
['element-b-selector', 'element-b-module-path']
|
||||
])},
|
||||
]
|
||||
});
|
||||
@ -71,7 +75,7 @@ fdescribe('ElementsLoader', () => {
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
|
||||
const defineArgs = fakeCustomElements.define.calls.argsFor(0);
|
||||
const defineArgs = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineArgs[0]).toBe('element-a-selector');
|
||||
|
||||
// Verify the right component was loaded/created
|
||||
@ -80,6 +84,30 @@ fdescribe('ElementsLoader', () => {
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should be able to register multiple elements', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
|
||||
const defineElementA = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineElementA[0]).toBe('element-a-selector');
|
||||
expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
|
||||
const defineElementB = fakeCustomElementsDefine.calls.argsFor(1);
|
||||
expect(defineElementB[0]).toBe('element-b-selector');
|
||||
expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should only register an element one time', fakeAsync(() => {
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
|
@ -8,14 +8,14 @@ import { FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.ser
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { CustomElementsModule } from 'app/custom-elements/custom-elements.module';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
import {
|
||||
MockTitle, MockTocService, ObservableWithSubscriptionSpies,
|
||||
TestDocViewerComponent, TestModule, TestParentComponent
|
||||
MockTitle, MockTocService, ObservableWithSubscriptionSpies,
|
||||
TestDocViewerComponent, TestModule, TestParentComponent, MockElementsLoader
|
||||
} from 'testing/doc-viewer-utils';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { DocViewerComponent, NO_ANIMATIONS } from './doc-viewer.component';
|
||||
|
||||
|
||||
describe('DocViewerComponent', () => {
|
||||
let parentFixture: ComponentFixture<TestParentComponent>;
|
||||
let parentComponent: TestParentComponent;
|
||||
@ -24,7 +24,7 @@ describe('DocViewerComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TestModule, CustomElementsModule],
|
||||
imports: [CustomElementsModule, TestModule],
|
||||
});
|
||||
|
||||
parentFixture = TestBed.createComponent(TestParentComponent);
|
||||
@ -294,12 +294,16 @@ describe('DocViewerComponent', () => {
|
||||
describe('#render()', () => {
|
||||
let prepareTitleAndTocSpy: jasmine.Spy;
|
||||
let swapViewsSpy: jasmine.Spy;
|
||||
let loadElementsSpy: jasmine.Spy;
|
||||
|
||||
const doRender = (contents: string | null, id = 'foo') =>
|
||||
new Promise<void>((resolve, reject) =>
|
||||
docViewer.render({contents, id}).subscribe(resolve, reject));
|
||||
|
||||
beforeEach(() => {
|
||||
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
|
||||
loadElementsSpy =
|
||||
elementsLoader.loadContainingCustomElements.and.returnValue(of([]));
|
||||
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
|
||||
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
|
||||
});
|
||||
@ -333,7 +337,7 @@ describe('DocViewerComponent', () => {
|
||||
expect(docViewerEl.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should prepare the title and ToC', async () => {
|
||||
it('should prepare the title and ToC (before embedding components)', async () => {
|
||||
prepareTitleAndTocSpy.and.callFake((targetEl: HTMLElement, docId: string) => {
|
||||
expect(targetEl.innerHTML).toBe('Some content');
|
||||
expect(docId).toBe('foo');
|
||||
@ -342,6 +346,7 @@ describe('DocViewerComponent', () => {
|
||||
await doRender('Some content', 'foo');
|
||||
|
||||
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
|
||||
expect(prepareTitleAndTocSpy).toHaveBeenCalledBefore(loadElementsSpy);
|
||||
});
|
||||
|
||||
it('should set the title and ToC (after the content has been set)', async () => {
|
||||
@ -384,6 +389,39 @@ describe('DocViewerComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('(embedding components)', () => {
|
||||
it('should embed components', async () => {
|
||||
await doRender('Some content');
|
||||
expect(loadElementsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadElementsSpy).toHaveBeenCalledWith(docViewer.nextViewContainer);
|
||||
});
|
||||
|
||||
it('should attempt to embed components even if the document is empty', async () => {
|
||||
await doRender('');
|
||||
await doRender(null);
|
||||
|
||||
expect(loadElementsSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loadElementsSpy.calls.argsFor(0)).toEqual([docViewer.nextViewContainer]);
|
||||
expect(loadElementsSpy.calls.argsFor(1)).toEqual([docViewer.nextViewContainer]);
|
||||
});
|
||||
|
||||
it('should unsubscribe from the previous "embed" observable when unsubscribed from', () => {
|
||||
const obs = new ObservableWithSubscriptionSpies();
|
||||
loadElementsSpy.and.returnValue(obs);
|
||||
|
||||
const renderObservable = docViewer.render({contents: 'Some content', id: 'foo'});
|
||||
const subscription = renderObservable.subscribe();
|
||||
|
||||
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled();
|
||||
|
||||
subscription.unsubscribe();
|
||||
|
||||
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(swapping views)', () => {
|
||||
it('should still swap the views if the document is empty', async () => {
|
||||
await doRender('');
|
||||
@ -444,6 +482,25 @@ describe('DocViewerComponent', () => {
|
||||
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
|
||||
});
|
||||
|
||||
it('when `EmbedComponentsService.embedInto()` fails', async () => {
|
||||
const error = Error('Typical `embedInto()` error');
|
||||
loadElementsSpy.and.callFake(() => {
|
||||
expect(docViewer.nextViewContainer.innerHTML).not.toBe('');
|
||||
throw error;
|
||||
});
|
||||
|
||||
await doRender('Some content', 'bar');
|
||||
|
||||
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadElementsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(swapViewsSpy).not.toHaveBeenCalled();
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
expect(logger.output.error).toEqual([
|
||||
[`[DocViewer] Error preparing document 'bar': ${error.stack}`],
|
||||
]);
|
||||
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
|
||||
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
|
||||
});
|
||||
|
||||
it('when `swapViews()` fails', async () => {
|
||||
const error = Error('Typical `swapViews()` error');
|
||||
@ -486,13 +543,24 @@ describe('DocViewerComponent', () => {
|
||||
});
|
||||
|
||||
describe('(events)', () => {
|
||||
it('should emit `docReady`', async () => {
|
||||
it('should emit `docReady` after loading elements', async () => {
|
||||
const onDocReadySpy = jasmine.createSpy('onDocReady');
|
||||
docViewer.docReady.subscribe(onDocReadySpy);
|
||||
|
||||
await doRender('Some content');
|
||||
|
||||
expect(onDocReadySpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadElementsSpy).toHaveBeenCalledBefore(onDocReadySpy);
|
||||
});
|
||||
|
||||
it('should emit `docReady` before swapping views', async () => {
|
||||
const onDocReadySpy = jasmine.createSpy('onDocReady');
|
||||
docViewer.docReady.subscribe(onDocReadySpy);
|
||||
|
||||
await doRender('Some content');
|
||||
|
||||
expect(onDocReadySpy).toHaveBeenCalledTimes(1);
|
||||
expect(onDocReadySpy).toHaveBeenCalledBefore(swapViewsSpy);
|
||||
});
|
||||
|
||||
it('should emit `docRendered` after swapping views', async () => {
|
||||
|
@ -67,12 +67,12 @@ export class DocViewerComponent implements OnDestroy {
|
||||
@Output() docRendered = new EventEmitter<void>();
|
||||
|
||||
constructor(
|
||||
elementRef: ElementRef,
|
||||
private logger: Logger,
|
||||
private titleService: Title,
|
||||
private metaService: Meta,
|
||||
private tocService: TocService,
|
||||
private elementsLoader: ElementsLoader) {
|
||||
elementRef: ElementRef,
|
||||
private logger: Logger,
|
||||
private titleService: Title,
|
||||
private metaService: Meta,
|
||||
private tocService: TocService,
|
||||
private elementsLoader: ElementsLoader) {
|
||||
this.hostElement = elementRef.nativeElement;
|
||||
// Security: the initialDocViewerContent comes from the prerendered DOM and is considered to be secure
|
||||
this.hostElement.innerHTML = initialDocViewerContent;
|
||||
|
@ -110,6 +110,15 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Custom elements should always rely on the polyfill to avoid having to include a shim that
|
||||
// handles downleveled ES2015 classes. Especially since that shim would break on IE11 which
|
||||
// can't even parse such code.
|
||||
if (window.customElements) {
|
||||
window.customElements['forcePolyfill'] = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -33,9 +33,6 @@ import './environments/environment';
|
||||
/** Add support for window.customElements */
|
||||
import '@webcomponents/custom-elements/custom-elements.min';
|
||||
|
||||
/** Required for custom elements for apps building to es5. */
|
||||
import '@webcomponents/custom-elements/src/native-shim';
|
||||
|
||||
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -54,6 +55,11 @@ export class MockTocService {
|
||||
reset = jasmine.createSpy('TocService#reset');
|
||||
}
|
||||
|
||||
export class MockElementsLoader {
|
||||
loadContainingCustomElements =
|
||||
jasmine.createSpy('MockElementsLoader#loadContainingCustomElements');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DocViewerComponent,
|
||||
@ -64,6 +70,7 @@ export class MockTocService {
|
||||
{ provide: Title, useClass: MockTitle },
|
||||
{ provide: Meta, useClass: MockMeta },
|
||||
{ provide: TocService, useClass: MockTocService },
|
||||
{ provide: ElementsLoader, useClass: MockElementsLoader },
|
||||
],
|
||||
})
|
||||
export class TestModule { }
|
||||
|
Reference in New Issue
Block a user