From 2517611a241cbaea579f246d3b6586739962588c Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Thu, 9 Nov 2017 13:48:30 +0100 Subject: [PATCH 1/5] Template engine improvements. --- package-lock.json | 6 ++ package.json | 1 + src/index.ts | 2 + src/lib/App.ts | 18 +----- src/lib/Router.ts | 21 +++++++ src/lib/http/HttpResponse.ts | 5 +- .../renderEngine/DefaultTemplateLoader.ts | 19 ++++--- .../http/renderEngine/DevTemplateLoader.ts | 34 +++++++++++ src/lib/http/renderEngine/Template.ts | 30 +--------- src/lib/http/renderEngine/TemplateEngine.ts | 25 ++------ src/lib/types/IApp.ts | 25 ++------ src/lib/types/IRouter.ts | 18 ++++++ src/lib/types/http/IHttpResponse.ts | 6 ++ src/lib/types/http/renderEngine/ITemplate.ts | 2 - .../http/renderEngine/ITemplateEngine.ts | 2 - .../http/renderEngine/ITemplateLoader.ts | 4 +- .../http/renderEngine/ITemplateRenderer.ts | 4 +- .../DefaultTemplateLoader.spec.ts | 22 ++++--- .../renderEngine/DevTemplateLoader.spec.ts | 42 ++++++++++++++ test/http/renderEngine/Template.spec.ts | 57 ++----------------- test/http/renderEngine/TemplateEngine.spec.ts | 45 ++++----------- 21 files changed, 192 insertions(+), 196 deletions(-) create mode 100644 src/lib/http/renderEngine/DevTemplateLoader.ts create mode 100644 test/http/renderEngine/DevTemplateLoader.spec.ts diff --git a/package-lock.json b/package-lock.json index 9b6a5cc..b834fd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -898,6 +898,12 @@ } } }, + "mock-fs": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.4.2.tgz", + "integrity": "sha512-dF+yxZSojSiI8AXGoxj5qdFWpucndc54Ug+TwlpHFaV7j22MGG+OML2+FVa6xAZtjb/OFFQhOC37Jegx2GbEwA==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/package.json b/package.json index 05d4d16..62c6be5 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "chai": "^4.1.2", "coveralls": "^3.0.0", "mocha": "^3.5.3", + "mock-fs": "^4.4.2", "tslint": "^5.7.0", "tslint-microsoft-contrib": "^5.0.1", "typescript": "^2.5.2" diff --git a/src/index.ts b/src/index.ts index 2f3f079..f4b06c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,3 +37,5 @@ export { default as ITemplateEngine } from "./lib/types/http/renderEngine/ITempl export { default as ITemplateLoader } from "./lib/types/http/renderEngine/ITemplateLoader"; export { default as ITemplateRenderer } from "./lib/types/http/renderEngine/ITemplateRenderer"; export { default as DefaultTemplateLoader } from "./lib/http/renderEngine/DefaultTemplateLoader"; +export { default as DevTemplateLoader } from "./lib/http/renderEngine/DevTemplateLoader"; +export { default as Template } from "./lib/http/renderEngine/Template"; diff --git a/src/lib/App.ts b/src/lib/App.ts index 8fc6176..c0442d4 100644 --- a/src/lib/App.ts +++ b/src/lib/App.ts @@ -7,8 +7,6 @@ import HttpError from "./exceptions/HttpError"; import httpFinalHandler from "./http/httpFinalHandler"; import HttpRequest from "./http/HttpRequest"; import HttpResponse from "./http/HttpResponse"; -import DefaultTemplateLoader from "./http/renderEngine/DefaultTemplateLoader"; -import TemplateEngine from "./http/renderEngine/TemplateEngine"; import Router from "./Router"; import IEventHandler from "./types/event/IEventHandler"; import IEventRequest from "./types/event/IEventRequest"; @@ -18,8 +16,6 @@ import IHttpPlaceholderHandler from "./types/http/IHttpPlaceholderHandler"; import IHttpRequest from "./types/http/IHttpRequest"; import IHttpResponse from "./types/http/IHttpResponse"; import IHttpRoute from "./types/http/IHttpRoute"; -import ITemplateEngine from "./types/http/renderEngine/ITemplateEngine"; -import ITemplateLoader from "./types/http/renderEngine/ITemplateLoader"; import ITemplateRenderer from "./types/http/renderEngine/ITemplateRenderer"; import IApp from "./types/IApp"; import IRouter from "./types/IRouter"; @@ -32,21 +28,12 @@ export default class App implements IApp { private _settings: object; private _router: IRouter; - private _templateEngine: ITemplateEngine; constructor() { this._settings = {}; this._router = new Router(); } - get templateEngine(): ITemplateEngine { - return this._templateEngine; - } - - get templateLoader(): ITemplateLoader { - return this._templateEngine ? this._templateEngine.loader : null; - } - public init(settings?: object): void { this.initDefaultConfiguration(settings); } @@ -115,9 +102,8 @@ export default class App implements IApp { return this; } - public addTemplateEngine(bucket: string, renderer: ITemplateRenderer, ttl?: number): IApp { - const templateLoader: ITemplateLoader = new DefaultTemplateLoader(bucket, ttl); - this._templateEngine = new TemplateEngine(bucket, renderer, templateLoader); + public addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IApp { + this._router.addTemplateEngine(renderer, engineConfiguration); return this; } diff --git a/src/lib/Router.ts b/src/lib/Router.ts index 4688a13..44624a8 100644 --- a/src/lib/Router.ts +++ b/src/lib/Router.ts @@ -3,6 +3,7 @@ import EventRouterExecutor from "./event/EventRouterExecutor"; import HttpLayer from "./http/HttpLayer"; import HttpRoute from "./http/HttpRoute"; import HttpRouterExecutor from "./http/HttpRouterExecutor"; +import TemplateEngine from "./http/renderEngine/TemplateEngine"; import IEventHandler from "./types/event/IEventHandler"; import IEventLayer from "./types/event/IEventLayer"; import IEventRequest from "./types/event/IEventRequest"; @@ -15,6 +16,8 @@ import IHttpRequest from "./types/http/IHttpRequest"; import IHttpResponse from "./types/http/IHttpResponse"; import IHttpRoute from "./types/http/IHttpRoute"; import IHttpRouterExecutor from "./types/http/IHttpRouterExecutor"; +import ITemplateEngine from "./types/http/renderEngine/ITemplateEngine"; +import ITemplateRenderer from "./types/http/renderEngine/ITemplateRenderer"; import INext from "./types/INext"; import IRouter from "./types/IRouter"; @@ -46,6 +49,7 @@ export default class Router implements IRouter { private _strict: boolean; private _subpath: string; private _parent: IRouter; + private _templateEngine: ITemplateEngine; constructor(opts?: {[name: string]: any}) { const options = opts || {}; @@ -101,6 +105,16 @@ export default class Router implements IRouter { } } + get templateEngine(): ITemplateEngine { + if (this._templateEngine) { + return this._templateEngine; + } else if (this._parent) { + return this._parent.templateEngine; + } else { + return null; + } + } + public param(name: string, handler: IHttpPlaceholderHandler): IRouter { if (!this._params[name]) { this._params[name] = []; @@ -181,6 +195,7 @@ export default class Router implements IRouter { } } else { const routerExecutor: IHttpRouterExecutor = new HttpRouterExecutor(this, req, res, out); + res.router = this; routerExecutor.next(); } } @@ -271,4 +286,10 @@ export default class Router implements IRouter { routerExecutor.next(); } + public addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IRouter { + this._templateEngine = new TemplateEngine(renderer, engineConfiguration); + + return this; + } + } diff --git a/src/lib/http/HttpResponse.ts b/src/lib/http/HttpResponse.ts index 6fc1464..ea789a0 100644 --- a/src/lib/http/HttpResponse.ts +++ b/src/lib/http/HttpResponse.ts @@ -13,6 +13,7 @@ import IHttpResponse from "./../types/http/IHttpResponse"; import ITemplateEngine from "./../types/http/renderEngine/ITemplateEngine"; import IApp from "./../types/IApp"; import INext from "./../types/INext"; +import IRouter from "./../types/IRouter"; import { merge, normalizeType, setCharset, stringify } from "./../utils/utils"; /** @@ -20,6 +21,8 @@ import { merge, normalizeType, setCharset, stringify } from "./../utils/utils"; */ export default class HttpResponse implements IHttpResponse { + public router: IRouter; + private _statusCode: number; private _app: IApp; private _request: IHttpRequest; @@ -352,7 +355,7 @@ export default class HttpResponse implements IHttpResponse { } public render(view: string, params: {[name: string]: any}, callback?: (err: Error, html: string) => void): void { - const templateEngine: ITemplateEngine = this._app.templateEngine; + const templateEngine: ITemplateEngine = this.router ? this.router.templateEngine : null; if (templateEngine == null) { throw new Error("The template engine must to be added in `app.addTemplateEngine` if you want to use render."); } else { diff --git a/src/lib/http/renderEngine/DefaultTemplateLoader.ts b/src/lib/http/renderEngine/DefaultTemplateLoader.ts index ede7890..54b0410 100644 --- a/src/lib/http/renderEngine/DefaultTemplateLoader.ts +++ b/src/lib/http/renderEngine/DefaultTemplateLoader.ts @@ -1,5 +1,7 @@ import * as S3 from "aws-sdk/clients/s3"; import * as NodeCache from "node-cache"; +import Template from "./../../http/renderEngine/Template"; +import ITemplate from "./../../types/http/renderEngine/ITemplate"; import ITemplateLoader from "./../../types/http/renderEngine/ITemplateLoader"; /** @@ -19,8 +21,8 @@ export default class DefaultTemplateLoader implements ITemplateLoader { } } - public load(fileName: string, callback: (err: Error, content: string) => void): void { - this.getFromCache(fileName, (cacheErr: Error, cacheValue: string) => { + public load(fileName: string, callback: (err: Error, template: ITemplate) => void): void { + this.getFromCache(fileName, (cacheErr: Error, cacheValue: ITemplate) => { if (cacheErr) { callback(cacheErr, null); } else { @@ -35,8 +37,9 @@ export default class DefaultTemplateLoader implements ITemplateLoader { callback(err, null); } else { const content: string = data.Body.toString(); - this.setToCache(fileName, content); - callback(null, content); + const template: ITemplate = new Template(fileName, content); + this.setToCache(template); + callback(null, template); } } ); @@ -47,7 +50,7 @@ export default class DefaultTemplateLoader implements ITemplateLoader { }); } - private getFromCache(fileName: string, callback: (cacheErr: Error, cacheValue: string) => void): void { + private getFromCache(fileName: string, callback: (cacheErr: Error, cacheValue: ITemplate) => void): void { if (this._cache) { this._cache.get(fileName, callback); } else { @@ -55,11 +58,11 @@ export default class DefaultTemplateLoader implements ITemplateLoader { } } - private setToCache(fileName: string, content: string): void { + private setToCache(template: ITemplate): void { if (this._cache) { - this._cache.set(fileName, (err: Error, success: boolean) => { + this._cache.set(template.fileName, template, (err: Error, success: boolean) => { if (err || !success) { - console.error("Error saving template into cache: " + fileName, err); + console.error("Error saving template into cache: " + template.fileName, err); } }); } diff --git a/src/lib/http/renderEngine/DevTemplateLoader.ts b/src/lib/http/renderEngine/DevTemplateLoader.ts new file mode 100644 index 0000000..d08322f --- /dev/null +++ b/src/lib/http/renderEngine/DevTemplateLoader.ts @@ -0,0 +1,34 @@ +import * as fs from "fs"; +import * as path from "path"; +import Template from "./../../http/renderEngine/Template"; +import ITemplate from "./../../types/http/renderEngine/ITemplate"; +import ITemplateLoader from "./../../types/http/renderEngine/ITemplateLoader"; + +/** + * Loads the template from file system. + */ +export default class DevTemplateLoader implements ITemplateLoader { + + private _basePath: string; + private _encoding: string; + + constructor(basePath?: string, encoding?: string) { + this._basePath = basePath; + this._encoding = encoding; + } + + public load(fileName: string, callback: (err: Error, template: ITemplate) => void): void { + const basePath = this._basePath || ""; + const filePath = path.join(basePath, fileName); + const encoding = this._encoding || "utf8"; + fs.readFile(filePath, encoding, (err: Error, content: string) => { + if (err) { + callback(err, null); + } else { + const template: ITemplate = new Template(fileName, content); + callback(null, template); + } + }); + } + +} diff --git a/src/lib/http/renderEngine/Template.ts b/src/lib/http/renderEngine/Template.ts index 2542912..e17bbed 100644 --- a/src/lib/http/renderEngine/Template.ts +++ b/src/lib/http/renderEngine/Template.ts @@ -1,5 +1,4 @@ import ITemplate from "./../../types/http/renderEngine/ITemplate"; -import ITemplateLoader from "./../../types/http/renderEngine/ITemplateLoader"; /** * Representation of a template file. @@ -8,13 +7,10 @@ export default class Template implements ITemplate { private _fileName: string; private _content: string; - private _loaded: boolean; - private _templateLoader: ITemplateLoader; - constructor(fileName: string, templateLoader: ITemplateLoader) { + constructor(fileName: string, content: string) { this._fileName = fileName; - this._loaded = false; - this._templateLoader = templateLoader; + this._content = content; } get fileName(): string { @@ -22,29 +18,7 @@ export default class Template implements ITemplate { } get content(): string { - if (!this._loaded) { - throw new Error("The template " + this._fileName + " has not been loaded."); - } return this._content; } - public load(callback: (err?: Error) => void): void { - if (!this._loaded) { - this._templateLoader.load( - this._fileName, - (err: Error, content: string) => { - if (err) { - callback(err); - } else { - this._content = content; - this._loaded = true; - callback(); - } - } - ); - } else { - callback(); - } - } - } diff --git a/src/lib/http/renderEngine/TemplateEngine.ts b/src/lib/http/renderEngine/TemplateEngine.ts index 56d5aa0..1f3800f 100644 --- a/src/lib/http/renderEngine/TemplateEngine.ts +++ b/src/lib/http/renderEngine/TemplateEngine.ts @@ -1,38 +1,21 @@ -import ITemplate from "./../../types/http/renderEngine/ITemplate"; import ITemplateEngine from "./../../types/http/renderEngine/ITemplateEngine"; -import ITemplateLoader from "./../../types/http/renderEngine/ITemplateLoader"; import ITemplateRenderer from "./../../types/http/renderEngine/ITemplateRenderer"; -import Template from "./Template"; /** * A class that render a template with a template renderer. */ export default class TemplateEngine implements ITemplateEngine { - private _bucket: string; + private _engineConfiguration: {[name: string]: any}; private _templateRenderer: ITemplateRenderer; - private _templateLoader: ITemplateLoader; - constructor(bucket: string, templateRenderer: ITemplateRenderer, templateLoader: ITemplateLoader) { - this._bucket = bucket; + constructor(templateRenderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}) { this._templateRenderer = templateRenderer; - this._templateLoader = templateLoader; - } - - public get loader(): ITemplateLoader { - return this._templateLoader; + this._engineConfiguration = engineConfiguration; } public render(fileName: string, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void): void { - const template: ITemplate = new Template(fileName, this._templateLoader); - - template.load((err: Error) => { - if (err) { - callback(err, null); - } else { - this._templateRenderer(template, params, callback); - } - }); + this._templateRenderer(fileName, params, this._engineConfiguration, callback); } } diff --git a/src/lib/types/IApp.ts b/src/lib/types/IApp.ts index 54d8d48..3719ab4 100644 --- a/src/lib/types/IApp.ts +++ b/src/lib/types/IApp.ts @@ -4,8 +4,6 @@ import IEventRoutePredicate from "./event/IEventRoutePredicate"; import IHttpHandler from "./http/IHttpHandler"; import IHttpPlaceholderHandler from "./http/IHttpPlaceholderHandler"; import IHttpRoute from "./http/IHttpRoute"; -import ITemplateEngine from "./http/renderEngine/ITemplateEngine"; -import ITemplateLoader from "./http/renderEngine/ITemplateLoader"; import ITemplateRenderer from "./http/renderEngine/ITemplateRenderer"; import IRouter from "./IRouter"; @@ -16,13 +14,6 @@ import IRouter from "./IRouter"; */ export default interface IApp { - /** - * Returns the created templated engine with the method `addTemplateEngine`. - */ - readonly templateEngine: ITemplateEngine; - - readonly templateLoader: ITemplateLoader; - /** * Initialize the framework with the configuration of `settings`. If * no `settings`are given, the framework is initialized with the @@ -133,16 +124,12 @@ export default interface IApp { /** * Add a render engine to be used in `IHttpResponse.request`. * - * @param {string} bucket The S3 bucket where the template - * files are. - * @param {ITemplateRenderer} renderer The function to render the template - * file with the params. - * @param {number} ttl The time to caching the template - * in case that the lambda function - * still alive. In debug mode, the - * templates are not cached. - * @return {IApp} + * @param {ITemplateRenderer} renderer The function to render the template + * file with the params. + * @param {{[name: string]: any}} engineConfiguration Conf params that will be passed to template + * renderer in each call. + * @return {IRouter} */ - addTemplateEngine(bucket: string, renderer: ITemplateRenderer, ttl?: number): IApp; + addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IApp; } diff --git a/src/lib/types/IRouter.ts b/src/lib/types/IRouter.ts index 90c0bc9..514d689 100644 --- a/src/lib/types/IRouter.ts +++ b/src/lib/types/IRouter.ts @@ -8,6 +8,8 @@ import IHttpPlaceholderHandler from "./http/IHttpPlaceholderHandler"; import IHttpRequest from "./http/IHttpRequest"; import IHttpResponse from "./http/IHttpResponse"; import IHttpRoute from "./http/IHttpRoute"; +import ITemplateEngine from "./http/renderEngine/ITemplateEngine"; +import ITemplateRenderer from "./http/renderEngine/ITemplateRenderer"; import INext from "./INext"; /** @@ -17,6 +19,11 @@ import INext from "./INext"; */ export default interface IRouter { + /** + * Returns the used template engine in the current router. + */ + readonly templateEngine: ITemplateEngine; + readonly httpStack: IHttpLayer[]; readonly eventStack: IEventLayer[]; @@ -141,4 +148,15 @@ export default interface IRouter { */ eventHandle(req: IEventRequest, next: INext): void; + /** + * Add a render engine to be used in `IHttpResponse.request`. + * + * @param {ITemplateRenderer} renderer The function to render the template + * file with the params. + * @param {{[name: string]: any}} engineConfiguration Conf params that will be passed to template + * renderer in each call. + * @return {IRouter} + */ + addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IRouter; + } diff --git a/src/lib/types/http/IHttpResponse.ts b/src/lib/types/http/IHttpResponse.ts index 01bc7e8..5aa4822 100644 --- a/src/lib/types/http/IHttpResponse.ts +++ b/src/lib/types/http/IHttpResponse.ts @@ -1,5 +1,6 @@ import IHttpError from "./../exceptions/IHttpError"; import INext from "./../INext"; +import IRouter from "./../IRouter"; import IHttpHandler from "./IHttpHandler"; /** @@ -7,6 +8,11 @@ import IHttpHandler from "./IHttpHandler"; */ export default interface IHttpResponse { + /** + * The current router. + */ + router: IRouter; + /** * The response HTTP status code. */ diff --git a/src/lib/types/http/renderEngine/ITemplate.ts b/src/lib/types/http/renderEngine/ITemplate.ts index a26a117..b5f82a6 100644 --- a/src/lib/types/http/renderEngine/ITemplate.ts +++ b/src/lib/types/http/renderEngine/ITemplate.ts @@ -7,6 +7,4 @@ export default interface ITemplate { readonly content: string; - load(callback: (err: Error) => void): void; - } diff --git a/src/lib/types/http/renderEngine/ITemplateEngine.ts b/src/lib/types/http/renderEngine/ITemplateEngine.ts index 98a8570..42975a0 100644 --- a/src/lib/types/http/renderEngine/ITemplateEngine.ts +++ b/src/lib/types/http/renderEngine/ITemplateEngine.ts @@ -5,8 +5,6 @@ import ITemplateLoader from "./ITemplateLoader"; */ export default interface ITemplateEngine { - readonly loader: ITemplateLoader; - render(fileName: string, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void): void; } diff --git a/src/lib/types/http/renderEngine/ITemplateLoader.ts b/src/lib/types/http/renderEngine/ITemplateLoader.ts index 369ab03..d265bab 100644 --- a/src/lib/types/http/renderEngine/ITemplateLoader.ts +++ b/src/lib/types/http/renderEngine/ITemplateLoader.ts @@ -1,6 +1,8 @@ +import ITemplate from "./ITemplate"; + /** * Implements the object that retrieve the content of a template file. */ export default interface ITemplateLoader { - load(fileName: string, callback: (err: Error, content: string) => void): void; + load(fileName: string, callback: (err: Error, template: ITemplate) => void): void; } diff --git a/src/lib/types/http/renderEngine/ITemplateRenderer.ts b/src/lib/types/http/renderEngine/ITemplateRenderer.ts index 2bf72ee..c8794bb 100644 --- a/src/lib/types/http/renderEngine/ITemplateRenderer.ts +++ b/src/lib/types/http/renderEngine/ITemplateRenderer.ts @@ -1,7 +1,5 @@ -import ITemplate from "./ITemplate"; - /** * The interface that has to implement the function that render a template. */ -type ITemplateRenderer = (template: ITemplate, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => void; +type ITemplateRenderer = (fileName: string, params: {[name: string]: any}, engineConfiguration: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => void; export default ITemplateRenderer; diff --git a/test/http/renderEngine/DefaultTemplateLoader.spec.ts b/test/http/renderEngine/DefaultTemplateLoader.spec.ts index c807336..97a228d 100644 --- a/test/http/renderEngine/DefaultTemplateLoader.spec.ts +++ b/test/http/renderEngine/DefaultTemplateLoader.spec.ts @@ -3,6 +3,8 @@ import * as Chai from "chai"; import * as NodeCache from "node-cache"; import { stub, SinonStub } from "sinon"; import DefaultTemplateLoader from "./../../../src/lib/http/renderEngine/DefaultTemplateLoader"; +import Template from "./../../../src/lib/http/renderEngine/Template"; +import ITemplate from "./../../../src/lib/types/http/renderEngine/ITemplate"; import ITemplateLoader from "./../../../src/lib/types/http/renderEngine/ITemplateLoader"; /** @@ -21,11 +23,13 @@ describe("DefaultTemplateLoader", () => { describe("load", () => { it("should load from cache the template if has been previously loaded and run the callback with the cached content.", (done) => { getCacheStub.callsFake((key: string, callback) => { - callback(null, "Cached content"); + callback(null, new Template("prueba.pug", "Cached content.")); }); - loader.load("prueba.pug", (err: Error, content: string) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { Chai.expect(getCacheStub.calledOnce).to.be.true; Chai.expect(getObjectStub.calledOnce).to.be.false; + Chai.expect(template.fileName).to.be.equal("prueba.pug"); + Chai.expect(template.content).to.be.equal("Cached content."); done(); }); }); @@ -35,7 +39,7 @@ describe("DefaultTemplateLoader", () => { getCacheStub.callsFake((key: string, callback) => { callback(returnedError, null); }); - loader.load("prueba.pug", (err: Error, content: string) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { Chai.expect(err).to.be.equal(returnedError); done(); }); @@ -48,9 +52,10 @@ describe("DefaultTemplateLoader", () => { Body: "Test content." }); }); - loader.load("prueba.pug", (err: Error, content: string) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { Chai.expect(getObjectStub.called).to.be.true; - Chai.expect(content).to.be.equal("Test content."); + Chai.expect(template.fileName).to.be.equal("prueba.pug"); + Chai.expect(template.content).to.be.equal("Test content."); done(); }); }); @@ -64,10 +69,11 @@ describe("DefaultTemplateLoader", () => { getCacheStub.callsFake((key: string, callback) => { callback(null, undefined); }); - loader.load("prueba.pug", (err: Error, content: string) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { Chai.expect(getCacheStub.called).to.be.true; Chai.expect(getObjectStub.called).to.be.true; - Chai.expect(content).to.be.equal("Test content."); + Chai.expect(template.fileName).to.be.equal("prueba.pug"); + Chai.expect(template.content).to.be.equal("Test content."); done(); }); }); @@ -80,7 +86,7 @@ describe("DefaultTemplateLoader", () => { getCacheStub.callsFake((key: string, callback) => { callback(null, undefined); }); - loader.load("prueba.pug", (err: Error, content: string) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { Chai.expect(err).to.be.equal(returnedError); done(); }); diff --git a/test/http/renderEngine/DevTemplateLoader.spec.ts b/test/http/renderEngine/DevTemplateLoader.spec.ts new file mode 100644 index 0000000..178b825 --- /dev/null +++ b/test/http/renderEngine/DevTemplateLoader.spec.ts @@ -0,0 +1,42 @@ +import * as Chai from "chai"; +import * as mock from "mock-fs"; +import DevTemplateLoader from "./../../../src/lib/http/renderEngine/DevTemplateLoader"; +import ITemplate from "./../../../src/lib/types/http/renderEngine/ITemplate"; +import ITemplateLoader from "./../../../src/lib/types/http/renderEngine/ITemplateLoader"; + +/** + * Test for DevTemplateLoader. + */ +describe("DevTemplateLoader", () => { + const loader: ITemplateLoader = new DevTemplateLoader("/base/path"); + + beforeEach(() => { + mock({ + "/base/path": { + "prueba.pug": new Buffer("Test content.", "utf8") + }, + }); + }); + + afterEach(mock.restore); + + describe("load", () => { + it("should return the template with the `fileName` and the file `content` if the file exists.", (done) => { + loader.load("prueba.pug", (err: Error, template: ITemplate) => { + Chai.expect(err).to.be.null; + Chai.expect(template.fileName).to.be.equal("prueba.pug"); + Chai.expect(template.content).to.be.equal("Test content."); + done(); + }); + }); + + it("should return an error if the file doesn't exist.", (done) => { + loader.load("not-existing-file.pug", (err: Error, template: ITemplate) => { + Chai.expect(err).to.be.not.null; + Chai.expect(template).to.be.null; + done(); + }); + }); + }); + +}); diff --git a/test/http/renderEngine/Template.spec.ts b/test/http/renderEngine/Template.spec.ts index efd96eb..5955bb1 100644 --- a/test/http/renderEngine/Template.spec.ts +++ b/test/http/renderEngine/Template.spec.ts @@ -1,65 +1,16 @@ import * as Chai from "chai"; -import { stub, SinonStub } from "sinon"; -import DefaultTemplateLoader from "./../../../src/lib/http/renderEngine/DefaultTemplateLoader"; import Template from "./../../../src/lib/http/renderEngine/Template"; import ITemplate from "./../../../src/lib/types/http/renderEngine/ITemplate"; -import ITemplateLoader from "./../../../src/lib/types/http/renderEngine/ITemplateLoader"; /** * Test for Template. */ describe("Template", () => { - const templateLoader: ITemplateLoader = new DefaultTemplateLoader(null, null); - const templateLoaderFunc: SinonStub = templateLoader.load = stub(); - let template: ITemplate; + const template: ITemplate = new Template("file.pug", "The content."); - beforeEach(() => { - template = new Template("test.pug", templateLoader); - templateLoaderFunc.callsFake((fileName: string, callback: (Error, string) => void) => callback(null, "Test content.")); - }); - - afterEach(() => { - templateLoaderFunc.reset(); - }); - - describe("content", () => { - it("should throw an exception if the template is not previously loaded.", () => { - Chai.expect(() => template.content).to.throw("The template test.pug has not been loaded."); - }); - - it("should return the content of the template if has been previously loaded.", (done) => { - template.load(() => { - Chai.expect(() => template.content).to.not.throw("The template test.pug has not been loaded."); - done(); - }); - }); - }); - - describe("load", () => { - it("should load the template calling the template loader if it has NOT been previously loaded.", (done) => { - template.load(() => { - Chai.expect(templateLoaderFunc.called).to.be.true; - done(); - }); - }); - - it("should NOT call the template loader if it has been previously loaded.", (done) => { - template.load(() => { - template.load(() => { - Chai.expect(templateLoaderFunc.calledOnce).to.be.true; - done(); - }); - }); - }); - - it("should NOT call callback with an error if there was an error loading the template.", (done) => { - const expectedError: Error = new Error("Test."); - templateLoaderFunc.callsFake((fileName: string, callback: (Error, string) => void) => callback(expectedError, null)); - template.load((err: Error) => { - Chai.expect(err).to.be.equal(expectedError); - done(); - }); - }); + it("should return the data with which it is constructed.", () => { + Chai.expect(template.fileName).to.be.be.equal("file.pug"); + Chai.expect(template.content).to.be.be.equal("The content."); }); }); diff --git a/test/http/renderEngine/TemplateEngine.spec.ts b/test/http/renderEngine/TemplateEngine.spec.ts index 6d42b9e..ee4edab 100644 --- a/test/http/renderEngine/TemplateEngine.spec.ts +++ b/test/http/renderEngine/TemplateEngine.spec.ts @@ -1,10 +1,10 @@ import * as Chai from "chai"; import { SinonStub, stub } from "sinon"; import DefaultTemplateLoader from "./../../../src/lib/http/renderEngine/DefaultTemplateLoader"; +import Template from "./../../../src/lib/http/renderEngine/Template"; import TemplateEngine from "./../../../src/lib/http/renderEngine/TemplateEngine"; import ITemplate from "./../../../src/lib/types/http/renderEngine/ITemplate"; import ITemplateEngine from "./../../../src/lib/types/http/renderEngine/ITemplateEngine"; -import ITemplateLoader from "./../../../src/lib/types/http/renderEngine/ITemplateLoader"; import ITemplateRenderer from "./../../../src/lib/types/http/renderEngine/ITemplateRenderer"; /** @@ -12,63 +12,40 @@ import ITemplateRenderer from "./../../../src/lib/types/http/renderEngine/ITempl */ describe("TemplateEngine", () => { const templateRenderer: SinonStub = stub(); - const templateLoader: ITemplateLoader = new DefaultTemplateLoader(null, null); - const templateLoaderFunc: SinonStub = templateLoader.load = stub(); - const templateEngine: ITemplateEngine = new TemplateEngine("bucketName", templateRenderer, templateLoader); + const expectedEngineConfig: {[name: string]: any} = {conf1: "value 1"}; + const templateEngine: ITemplateEngine = new TemplateEngine( templateRenderer, expectedEngineConfig); afterEach(() => { - templateLoaderFunc.reset(); templateRenderer.reset(); }); describe("render", () => { - it("should call the `callback` function with an error if there is an error loading the template.", (done) => { - const returnedError: Error = new Error("Forced error"); - - templateLoaderFunc.callsFake((fileName: string, callback: (err: Error, content: string) => void) => { - callback(returnedError, null); - }); - - templateEngine.render("fileName", {}, (err: Error, parsedHtml: string) => { - Chai.expect(err).to.be.equal(returnedError); - Chai.expect(parsedHtml).to.be.null; - done(); - }); - }); - it("should call the `callback` function with an error if there is an error rendering the template.", (done) => { const returnedError: Error = new Error("Forced error"); - templateRenderer.callsFake((template: ITemplate, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => { + templateRenderer.callsFake((template: ITemplate, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => { callback(returnedError, null); }); - templateLoaderFunc.callsFake((fileName: string, callback: (err: Error, content: string) => void) => { - callback(null, "PRUEBA"); - }); - - templateEngine.render("fileName", {}, (err: Error, parsedHtml: string) => { + templateEngine.render("fileName.pug", {}, (err: Error, parsedHtml: string) => { Chai.expect(err).to.be.equal(returnedError); Chai.expect(parsedHtml).to.be.null; done(); }); }); - it("should call the `templateRenderer` function with the `template`, the `params` and the `callback` if the template has been successfully loaded.", (done) => { + it("should call the `templateRenderer` function with the `fileName`, the `params`, the `engineConfig`, and the `callback` if the template has been successfully loaded.", (done) => { const expectedContent = "PRUEBA"; const expectedParams = {param1: "value1"}; - templateRenderer.callsFake((template: ITemplate, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => { - Chai.expect(template.content).to.be.equal(expectedContent); + templateRenderer.callsFake((fileName: string, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, template: ITemplate) => void) => { + Chai.expect(fileName).to.be.equal("fileName.pug"); Chai.expect(params).to.be.equal(expectedParams); - callback(null, template.content); - }); - - templateLoaderFunc.callsFake((fileName: string, callback: (err: Error, content: string) => void) => { - callback(null, expectedContent); + Chai.expect(engineConfig).to.be.equal(expectedEngineConfig); + callback(null, new Template(fileName, "Test content.")); }); - templateEngine.render("fileName", expectedParams, (err: Error, parsedHtml: string) => { + templateEngine.render("fileName.pug", expectedParams, (err: Error, parsedHtml: string) => { done(); }); }); From c4d62da52baa3d1f6ae2bf87fad24c877fa1cd91 Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Thu, 9 Nov 2017 14:17:05 +0100 Subject: [PATCH 2/5] Removed unused import. --- src/lib/types/http/renderEngine/ITemplateEngine.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/types/http/renderEngine/ITemplateEngine.ts b/src/lib/types/http/renderEngine/ITemplateEngine.ts index 42975a0..1303287 100644 --- a/src/lib/types/http/renderEngine/ITemplateEngine.ts +++ b/src/lib/types/http/renderEngine/ITemplateEngine.ts @@ -1,5 +1,3 @@ -import ITemplateLoader from "./ITemplateLoader"; - /** * A class that render a template with a template renderer. */ From 82621a9a8d18703d3e52a873ba8eefc0b1738e50 Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Fri, 10 Nov 2017 17:41:38 +0100 Subject: [PATCH 3/5] Changed render from function to class. --- src/lib/types/http/renderEngine/ITemplateRenderer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/types/http/renderEngine/ITemplateRenderer.ts b/src/lib/types/http/renderEngine/ITemplateRenderer.ts index c8794bb..62a3791 100644 --- a/src/lib/types/http/renderEngine/ITemplateRenderer.ts +++ b/src/lib/types/http/renderEngine/ITemplateRenderer.ts @@ -1,5 +1,8 @@ /** * The interface that has to implement the function that render a template. */ -type ITemplateRenderer = (fileName: string, params: {[name: string]: any}, engineConfiguration: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => void; -export default ITemplateRenderer; +export default interface ITemplateRenderer { + + render(fileName: string, params: {[name: string]: any}, engineConfiguration: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void): void; + +} From 962fcb09a452f9530ccc3e67eec625bbbdeab0f9 Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Fri, 10 Nov 2017 17:48:30 +0100 Subject: [PATCH 4/5] Adapt new renderer format. --- package-lock.json | 2 +- src/lib/http/renderEngine/TemplateEngine.ts | 2 +- test/http/renderEngine/TemplateEngine.spec.ts | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b834fd4..dea1d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lambda-framework", - "version": "1.0.16", + "version": "1.0.17", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/lib/http/renderEngine/TemplateEngine.ts b/src/lib/http/renderEngine/TemplateEngine.ts index 1f3800f..3c5d89a 100644 --- a/src/lib/http/renderEngine/TemplateEngine.ts +++ b/src/lib/http/renderEngine/TemplateEngine.ts @@ -15,7 +15,7 @@ export default class TemplateEngine implements ITemplateEngine { } public render(fileName: string, params: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void): void { - this._templateRenderer(fileName, params, this._engineConfiguration, callback); + this._templateRenderer.render(fileName, params, this._engineConfiguration, callback); } } diff --git a/test/http/renderEngine/TemplateEngine.spec.ts b/test/http/renderEngine/TemplateEngine.spec.ts index ee4edab..2d0175e 100644 --- a/test/http/renderEngine/TemplateEngine.spec.ts +++ b/test/http/renderEngine/TemplateEngine.spec.ts @@ -11,19 +11,20 @@ import ITemplateRenderer from "./../../../src/lib/types/http/renderEngine/ITempl * Test for TemplateEngine. */ describe("TemplateEngine", () => { - const templateRenderer: SinonStub = stub(); + const templateRenderer: ITemplateRenderer = stub(); + const templateRendererFunc = templateRenderer.render = stub(); const expectedEngineConfig: {[name: string]: any} = {conf1: "value 1"}; - const templateEngine: ITemplateEngine = new TemplateEngine( templateRenderer, expectedEngineConfig); + const templateEngine: ITemplateEngine = new TemplateEngine(templateRenderer, expectedEngineConfig); afterEach(() => { - templateRenderer.reset(); + templateRendererFunc.reset(); }); describe("render", () => { it("should call the `callback` function with an error if there is an error rendering the template.", (done) => { const returnedError: Error = new Error("Forced error"); - templateRenderer.callsFake((template: ITemplate, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => { + templateRendererFunc.callsFake((template: ITemplate, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, parsedHtml: string) => void) => { callback(returnedError, null); }); @@ -38,7 +39,7 @@ describe("TemplateEngine", () => { const expectedContent = "PRUEBA"; const expectedParams = {param1: "value1"}; - templateRenderer.callsFake((fileName: string, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, template: ITemplate) => void) => { + templateRendererFunc.callsFake((fileName: string, params: {[name: string]: any}, engineConfig: {[name: string]: any}, callback: (err: Error, template: ITemplate) => void) => { Chai.expect(fileName).to.be.equal("fileName.pug"); Chai.expect(params).to.be.equal(expectedParams); Chai.expect(engineConfig).to.be.equal(expectedEngineConfig); From 8cb9251ceb564ab9ff7afee8cdc0bf24b5743d68 Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Fri, 10 Nov 2017 18:27:29 +0100 Subject: [PATCH 5/5] Added render engine tests to increment coverture. --- src/lib/http/HttpResponse.ts | 2 +- test/app.spec.ts | 16 ++++++++-- test/http/HttpResponse.spec.ts | 57 +++++++++++++++++++++++++++++++--- test/router.spec.ts | 34 ++++++++++++++++++++ 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/lib/http/HttpResponse.ts b/src/lib/http/HttpResponse.ts index ea789a0..587ec9d 100644 --- a/src/lib/http/HttpResponse.ts +++ b/src/lib/http/HttpResponse.ts @@ -354,7 +354,7 @@ export default class HttpResponse implements IHttpResponse { } } - public render(view: string, params: {[name: string]: any}, callback?: (err: Error, html: string) => void): void { + public render(view: string, params?: {[name: string]: any}, callback?: (err: Error, html: string) => void): void { const templateEngine: ITemplateEngine = this.router ? this.router.templateEngine : null; if (templateEngine == null) { throw new Error("The template engine must to be added in `app.addTemplateEngine` if you want to use render."); diff --git a/test/app.spec.ts b/test/app.spec.ts index 25226f8..416200d 100644 --- a/test/app.spec.ts +++ b/test/app.spec.ts @@ -1,13 +1,16 @@ +import * as Chai from 'chai' +import { stub } from "sinon"; import App from '../src/index' +import Router from "../src/lib/Router"; import { configuration } from '../src/index' import defaultConfiguration from '../src/lib/configuration/defaultConfiguration' -import * as Chai from 'chai' /** * Test for App. */ describe('App', () => { - let app + let app; + beforeEach(() => { app = new App() }); @@ -41,4 +44,13 @@ describe('App', () => { app.set('option1', 'value1') Chai.expect(app.get('option1')).to.be.equal('value1') }); + + describe("#addTemplateEngine", () => { + it("delegates the action to the default router.", () => { + const addTepmlateEngineStub = stub(Router.prototype, "addTemplateEngine"); + app.addTemplateEngine(null); + Chai.expect(addTepmlateEngineStub.calledOnce).to.be.true; + addTepmlateEngineStub.restore(); + }); + }); }); diff --git a/test/http/HttpResponse.spec.ts b/test/http/HttpResponse.spec.ts index 6564b1d..c4798aa 100644 --- a/test/http/HttpResponse.spec.ts +++ b/test/http/HttpResponse.spec.ts @@ -1,10 +1,14 @@ import * as Chai from 'chai' +import { stub, SinonStub } from "sinon"; +import Router from './../../src/lib/Router' +import IRouter from './../../src/lib/types/IRouter' import configuration from './../../src/lib/configuration/configuration' import HttpRequest from './../../src/lib/http/HttpRequest' import IHttpRequest from './../../src/lib/types/http/IHttpRequest' import HttpResponse from './../../src/lib/http/HttpResponse' import IHttpResponse from './../../src/lib/types/http/IHttpResponse' import HttpRoute from './../../src/lib/http/HttpRoute' +import TemplateEngine from './../../src/lib/http/renderEngine/TemplateEngine' import App from './../../src/lib/App' import IApp from './../../src/lib/types/IApp' import { APIGatewayEvent } from 'aws-lambda' @@ -335,24 +339,67 @@ describe('HttpResponse', () => { }); describe("render", () => { - it("should throw an exception if the `templateEngine` hasn't been set in the `App`.", () => { + let renderStub: SinonStub; + const router: IRouter = new Router(); + beforeEach(() => { + renderStub = stub(TemplateEngine.prototype, "render"); + response.router = router; + router.addTemplateEngine(null); }); - it("should call the method `render` of `templateEngine` if it has been set in the `App`.", () => { + afterEach(() => { + renderStub.restore(); + response.router = undefined; + }); + it("should throw an exception if the `templateEngine` hasn't been set in the `App`.", () => { + response.router = undefined; + Chai.expect(() => response.render("fileName")).to.throw("The template engine must to be added in `app.addTemplateEngine` if you want to use render."); }); - it("should call the `callback` with the result of `templateEngine.render`.", () => { + it("should call the method `render` of `templateEngine` if it has been set in the `App`.", () => { + response.render("fileName"); + Chai.expect(renderStub.calledOnce).to.be.true; + }); + it("should call the `callback` with the result of `templateEngine.render`.", (done) => { + const expectedResult: string = "Result of render."; + renderStub.callsFake((view, params, callback) => callback(null, expectedResult)); + response.render("fileName", {}, (err, result) => { + Chai.expect(result).to.be.equal(expectedResult); + Chai.expect(err).to.be.null; + done(); + }); }); - it("should call the `next` handler with the error if the `templateEngine.render` method returns an error and no `callback` is given.", () => { + it("should call the `callback` with the error if the `templateEngine.render` method returns an error and a `callback` is given.", (done) => { + const expectedError: Error = new Error("Render error."); + renderStub.callsFake((view, params, callback) => callback(expectedError, null)); + response.render("fileName", {}, (err, result) => { + Chai.expect(err).to.be.equal(expectedError); + Chai.expect(result).to.be.null; + done(); + }); + }); + it("should call the `next` handler with the error if the `templateEngine.render` method returns an error and no `callback` is given.", (done) => { + const expectedError: Error = new Error("Render error."); + renderStub.callsFake((view, params, callback) => callback(expectedError, null)); + request.next = (err) => { + Chai.expect(err).to.be.equal(expectedError); + request.next = undefined; + done(); + }; + response.render("fileName"); }); it("should response with the html code and the 'text/html' content-type header if the `templateEngine.render` method returns no error and no `callback` is given.", () => { - + const expectedResult: string = "Result of render."; + renderStub.callsFake((view, params, callback) => callback(null, expectedResult)); + response.render("fileName"); + Chai.expect(succResult.headers["Content-Type"]).to.contain("text/html"); + Chai.expect(succResult.body).to.be.equal(expectedResult); }); }); diff --git a/test/router.spec.ts b/test/router.spec.ts index a072a04..72b1990 100644 --- a/test/router.spec.ts +++ b/test/router.spec.ts @@ -319,4 +319,38 @@ describe('Router', () => { }); }); + describe("#templateEngine", () => { + it("returns the templateEngine of a parent router if it has no one.", () => { + const subrouter: IRouter = new Router(); + router.mount(subrouter); + + const subsubrouter: IRouter = new Router(); + subrouter.mount(subsubrouter); + + router.addTemplateEngine(null); + + Chai.expect(subrouter.templateEngine).to.be.equal(router.templateEngine); + Chai.expect(subsubrouter.templateEngine).to.be.equal(router.templateEngine); + }); + + it("returns the its templateEngine if it has one.", () => { + const subrouter: IRouter = new Router(); + router.mount(subrouter); + + router.addTemplateEngine(null); + subrouter.addTemplateEngine(null); + + Chai.expect(subrouter.templateEngine).to.be.not.equal(router.templateEngine); + Chai.expect(subrouter.templateEngine).to.be.not.null; + }); + + it("returns `null` if any parent has one.", () => { + const subrouter: IRouter = new Router(); + router.mount(subrouter); + + Chai.expect(subrouter.templateEngine).to.be.null; + Chai.expect(subrouter.templateEngine).to.be.null; + }); + }); + });