From e3559d123b210ea2c9f49a9589b7d691fd69fd45 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 19 Jan 2016 15:55:52 +0100 Subject: [PATCH] feat(testing/utils): create public helper methods for testing --- docs/API.md | 108 ++++++++++++++++++++++++++++++++++++++++++- src/testing/utils.ts | 74 +++++++++++++++++++++++++++++ testing.ts | 3 +- 3 files changed, 181 insertions(+), 4 deletions(-) diff --git a/docs/API.md b/docs/API.md index ce686cc..bebc18f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,13 +1,25 @@ ## API -Angular 1 container registration helper Methods: +Angular 1 boostraper: +`ng-metadata/platform` - [bootstrap](#bootstrap) + +Angular 1 container registration helper Methods: + +`ng-metadata/core` - [provide](#provide) - [getInjectableName](#getinjectablename) -Decorators(core): +Testing helpers: +`ng-metadata/testing` +- [renderFactory](#renderfactory) +- [getInput](#getinput) + +Decorators: + +`ng-metadata/core` - [@Component](#component) - [@Directive](#directive) - [@Input](#input) @@ -25,6 +37,7 @@ Decorators(core): Lifecycle hooks: +`ng-metadata/core` - [OnInit](#oninit) - [AfterContentInit](#aftercontentinit) - [AfterViewInit](#afterviewtinit) @@ -187,6 +200,97 @@ console.log(getInjectableName(MyService)); // 'myService48' ``` +## renderFactory `ng-metadata/testing` + +Helper for compiling Component/Directive classes within you unit test. +Use pattern shown in example: + - create local render variable with interface IRender to tell tsc what type it would be + - init it when you got $scope and $compile in beforeEach + - use it within the test + - if you want to override the inferred type from Directive argument use that via `<>` operator + +*Example:* + +```typescript +// my-component.ts +@Component({ + selector:'my-component', + template:'hello {{ $ctrl.greeting }}' +}) +class MyComponent{ + + greeting: string; + + constructor(@Inject('ngModel') @Host() private ngModel){} + + ngAfterViewInit(){ + this.ngModel.$render = ()=>{ + this.greeting = angular.copy(this.ngModel.$viewValue); + } + } +} + +// my-component.spec.ts +import { MyModule } from './my'; +import { MyComponent } from './my-component'; +import { renderFactory, IRender } from 'ng-metadata/testing'; + +let $compile: ng.ICompileService; +let $rootScope: ng.IRootScopeService; +let $scope; +let render: IRender; + +describe(`MyComponent`, ()=>{ + + beforeEach(() => { + + angular.mock.module(MyModule); + + }); + + beforeEach(angular.mock.inject((_$injector_: ng.auto.IInjectorService) => { + + const $injector = _$injector_; + + $compile = $injector.get('$compile'); + $rootScope = $injector.get('$rootScope'); + $scope = $rootScope.$new(); + + render = renderFactory($compile,$scope); + + })); + + it(`should create the DOM and compile`, ()=>{ + const attrs = { 'ng-model':'model'}; + $scope.model = 'Martin!'; + + // here we go! + // it returns instance and compiled DOM + const {compiledElement, ctrl} = render(MyComponent, {attrs}); + + expect(ctrl instanceOf MyComponent).to.equal(true); + expect(compiledElement[0]).to.equal('hello Martin!'); + }) + +}) + +``` + +###### Parameters + +| Parameter | Type | Description | +| ------------- | ------------------------------- |------------------------------------------ | +| **$compile** | `ng.ICompileService` | core angular 1 $compile service from ng.mock | +| **$scope** | `ng.IScope` | child scope for your component | + +returns render `Function` + +###### Behind the Scenes + +it builds whole DOM for component/directive, so you don't need to bother with html strings in your test. +Within it calls angular 1 well known $compile with provided $scope and re runs $digest loop to reflect the changes. + + ## @Component A decorator for adding component metadata to a class. diff --git a/src/testing/utils.ts b/src/testing/utils.ts index b04db7f..b210502 100644 --- a/src/testing/utils.ts +++ b/src/testing/utils.ts @@ -1,4 +1,78 @@ import {isFunction} from '../facade/lang'; +import {getInjectableName} from '../core/di/provider'; +import {StringWrapper} from '../facade/primitives'; + +// public helpers + + +export interface IRender{ + (Directive: T, {jqHost, attrs, jqChildren}?: { + jqHost?: ng.IAugmentedJQuery, + attrs?: { [key: string]: any }, + jqChildren?: ng.IAugmentedJQuery + } + ):{ + compiledElement: ng.IAugmentedJQuery, + ctrl: T + } +} +/** + * factory which will return function which will be used as your render method + */ +export function renderFactory( $compile: ng.ICompileService, $scope: any ) { + + return _compileAndDigest; + + function _compileAndDigest( + Directive: T, + {jqHost, attrs, jqChildren}:{ + jqHost?: ng.IAugmentedJQuery + attrs?: {[key:string]:any}, + jqChildren?: ng.IAugmentedJQuery + } = {} + ): { compiledElement: ng.IAugmentedJQuery, ctrl: T}{ + + const ctrlName = getInjectableName( Directive ); + const selector = StringWrapper.kebabCase( ctrlName ); + + // is Directive + if ( jqHost ) { + + jqHost.attr( selector, '' ) + + } else { + // is Component + + const hostElement = `<${selector}>`; + jqHost = angular.element( hostElement ); + + } + + jqHost.attr(attrs); + + if (jqChildren) { + jqHost.append(jqChildren); + } + + // angular api + const compiledElement = $compile(jqHost)($scope); + const ctrl = compiledElement.controller(ctrlName) as T; + $scope.$apply(); + + return { compiledElement, ctrl }; + + } + +} + +export function getInput(element: ng.IAugmentedJQuery) { + return element.find('input'); +} + +// ============================ +// _private helpers for testing +// ============================ + /** * * @internal diff --git a/testing.ts b/testing.ts index 2fcecc7..4f89f98 100644 --- a/testing.ts +++ b/testing.ts @@ -1,2 +1 @@ -// @TODO export public testing helpers -//import {} from './testing/utils'; +export {renderFactory,getInput,IRender} from './src/testing/utils';