Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ dist/
.scannerwork/
**/*.log
*.tgz
coverage
.idea
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const customLaunchers = {
}
};


module.exports = function(config) {
config.set({
basePath: '',
Expand Down
120 changes: 120 additions & 0 deletions src/lib/layout/aem-component.directive.custom.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { AEMComponentDirective } from './aem-component.directive';
import { Component, Input } from '@angular/core';
import { ComponentMapping, MapTo, LazyMapTo, AbstractMappedComponent } from './component-mapping';
import { Utils } from './utils';
import { LazyComponentType } from "../test/lazy-component-wrapper/lazy.component";
import {CustomDefaultRenderer} from "../test/aem-component-directive/custom-default-renderer";
import {CustomDirective} from "../test/aem-component-directive/customdirective";





@Component({
selector: 'test-component',
template: `<ng-container CustomDirective [cqItem]='data'></ng-container>`
})
class AEMDirectiveTestComponent {
@Input() data;
}



@Component({
selector: 'directive-component',
host: {
'[attr.attr1]': 'attr1',
'[attr.attr2]': 'attr2',
'[class]': 'hostClasses'
},
template: `<div></div>`
})
class DirectiveComponent extends AbstractMappedComponent {
@Input() attr1;
@Input() attr2;

get hostClasses() {
return 'component-class';
}
}
MapTo<DirectiveComponent>('directive/comp')(DirectiveComponent);
LazyMapTo<LazyComponentType>('some/lazy/comp')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent));

describe('AEMComponentDirective - Custom', () => {

const EDIT_CONFIG_EMPTY_LABEL = 'Edit config empty label';

const TEST_EDIT_CONFIG_EMPTY = {
emptyLabel: EDIT_CONFIG_EMPTY_LABEL,
isEmpty: () => {
return true;
}
};

const TEST_EDIT_CONFIG_NOT_EMPTY = {
emptyLabel: EDIT_CONFIG_EMPTY_LABEL,
isEmpty: function() {
return false;
}
};

let component: AEMDirectiveTestComponent;
let fixture: ComponentFixture<AEMDirectiveTestComponent>;

let isInEditorSpy;
let getEditConfigSpy;

beforeEach(async(() => {
isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false);
getEditConfigSpy = spyOn(ComponentMapping, 'getEditConfig').and.returnValue(undefined);

TestBed.configureTestingModule({
declarations: [ AEMDirectiveTestComponent, DirectiveComponent,CustomDefaultRenderer,CustomDirective, AEMComponentDirective ]
}).overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [ DirectiveComponent, CustomDefaultRenderer,CustomDirective ]
}
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AEMDirectiveTestComponent);
component = fixture.componentInstance;
});

it('should render the fallback component', async () => {
const componentData = {
attr1: 'Some value',
attr2: 'Another value',
':customType': 'missing/directive/comp'
};

component.data = componentData;
fixture.detectChanges();

await fixture.whenStable();

const element = fixture.nativeElement;
const dynamicElement = element.firstElementChild;

expect(dynamicElement.innerHTML).toEqual("<div id=\"myCustomComponent\">test</div>")
});



});
30 changes: 30 additions & 0 deletions src/lib/layout/aem-component.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,36 @@ describe('AEMComponentDirective', () => {
expect(dynamicElement.getAttribute('attr1')).toEqual(componentData['attr1']);
expect(dynamicElement.getAttribute('attr2')).toEqual(componentData['attr2']);
});
it('should not render anything', () => {
const componentData = {
attr1: 'Some value',
attr2: 'Another value',
':type': 'missing/directive/comp'
};

component.data = componentData;
fixture.detectChanges();

const element = fixture.nativeElement;
const dynamicElement = element.firstElementChild;

expect(dynamicElement).toBeNull();
});
it('should render the fallback component', () => {
const componentData = {
attr1: 'Some value',
attr2: 'Another value',
':type': 'missing/directive/comp'
};

component.data = componentData;
fixture.detectChanges();

const element = fixture.nativeElement;
const dynamicElement = element.firstElementChild;

expect(dynamicElement).toBeNull();
});
it('should correctly pass the inputs for lazy component', async() => {
const componentData = {
some: 'Some value',
Expand Down
50 changes: 35 additions & 15 deletions src/lib/layout/aem-component.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ import {
ViewContainerRef
} from '@angular/core';

import { ComponentMapping, MappedComponentProperties } from './component-mapping';
import {
ComponentMapping,
ComponentMappingProvider,
ComponentMappingWithConfig,
MappedComponentProperties
} from './component-mapping';
import { Constants } from './constants';
import { Utils } from './utils';

Expand Down Expand Up @@ -93,18 +98,18 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,


constructor(
private renderer: Renderer2,
private viewContainer: ViewContainerRef,
private compiler: Compiler,
private injector: Injector,
private factoryResolver: ComponentFactoryResolver,
private _changeDetectorRef: ChangeDetectorRef) {
protected renderer: Renderer2,
protected viewContainer: ViewContainerRef,
protected compiler: Compiler,
protected injector: Injector,
protected factoryResolver: ComponentFactoryResolver,
protected _changeDetectorRef: ChangeDetectorRef) {
}

async ngOnInit() {

if (this.type) {
const mappedFn:Type<MappedComponentProperties> = ComponentMapping.get<MappedComponentProperties>(this.type);
if (this.getType()) {
const mappedFn:Type<unknown> = this.getComponentMappingProvider().get(this.getType());

if (mappedFn) {
this.renderComponent(mappedFn);
Expand All @@ -118,26 +123,41 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
}

async initializeAsync() {
const lazyMappedPromise: Promise<Type<MappedComponentProperties>> = ComponentMapping.lazyGet<MappedComponentProperties>(this.type);

try {
const lazyMappedPromise: Promise<Type<unknown>> = this.getComponentMappingProvider().lazyGet(this.getType());
const LazyResolvedComponent = await lazyMappedPromise;
this.renderComponent(LazyResolvedComponent);
this.loaded = true;
this._changeDetectorRef.detectChanges();
} catch (err) {
console.warn(err);

if(!!this.getFallbackComponent()){
this.renderComponent(this.getFallbackComponent());
this.loaded = true;
this._changeDetectorRef.detectChanges();
console.info("loaded fallback component. cause:", err);
}else{
console.warn(err);
}

}
}

ngOnChanges(): void {
this.updateComponentData();
}

protected getFallbackComponent():Type<unknown> | null{
return null;
}

protected getComponentMappingProvider():ComponentMappingProvider {
return ComponentMapping;
}
/**
* Returns the type of the cqItem if exists.
*/
get type(): string | undefined {
protected getType(): string | undefined {
return this.cqItem && this.cqItem[Constants.TYPE_PROP];
}

Expand All @@ -146,7 +166,7 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
*
* @param componentDefinition The component definition to render
*/
private renderComponent(componentDefinition: Type<MappedComponentProperties>) {
private renderComponent(componentDefinition: Type<unknown>) {
if (componentDefinition) {
const factory = this.factoryResolver.resolveComponentFactory(componentDefinition);

Expand Down Expand Up @@ -189,7 +209,7 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
this._component.instance.cqPath = this.cqPath;
this._component.instance.itemName = this.itemName;

const editConfig = ComponentMapping.getEditConfig(this.type);
const editConfig = ComponentMapping.getEditConfig(this.getType());

if (editConfig && Utils.isInEditor) {
this.setupPlaceholder(editConfig);
Expand Down
8 changes: 6 additions & 2 deletions src/lib/layout/component-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,16 @@ export abstract class AbstractMappedComponent implements MappedComponentProperti
@Input() itemName = '';
}

export interface ComponentMappingProvider {
get(resourceType:string):Type<unknown>
lazyGet(resourceType: string): Promise<Type<unknown>>
}
/**
* The current class extends the @adobe/cq-spa-component-mapping#Mapto library and features with Angular specifics such as
*
* - Storing the editing configurations for each resource type
*/
export class ComponentMappingWithConfig {
export class ComponentMappingWithConfig implements ComponentMappingProvider{
/**
* Store of EditConfig structures
*/
Expand Down Expand Up @@ -172,4 +176,4 @@ function LazyMapTo<Model extends MappedComponentProperties = any>(resourceTypes:
componentMapping.lazyMap(resourceTypes, lazyClassFunction, editConfig);
}

export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
14 changes: 14 additions & 0 deletions src/lib/test/aem-component-directive/custom-default-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Component, OnInit} from "@angular/core";

@Component({
selector: 'core-contentfragment-default-renderer-v1',
template: '<div id="myCustomComponent">test</div>'
})
export class CustomDefaultRenderer implements OnInit{
ngOnInit(): void {
console.log('init?')
}



}
20 changes: 20 additions & 0 deletions src/lib/test/aem-component-directive/customdirective.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {AEMComponentDirective} from "../../layout/aem-component.directive";
import {Directive, Type} from "@angular/core";
import {ComponentMappingProvider} from "../../layout/component-mapping";
import {CustomDefaultRenderer} from "./custom-default-renderer";
import {ComponentMapping} from "@adobe/aem-spa-component-mapping";

@Directive({
selector: '[CustomDirective]'
})
export class CustomDirective extends AEMComponentDirective {

getFallbackComponent():Type<unknown> | null{
return CustomDefaultRenderer;
}

getType(): string | undefined {
return this.cqItem && this.cqItem[":customType"];
}

}