From fd884ea1bb555882c3ca0d80384af10b7949e728 Mon Sep 17 00:00:00 2001 From: thetutlage Date: Fri, 6 Jul 2018 23:49:16 +0530 Subject: [PATCH] feat(edge): add main public interface of edge --- .editorconfig | 2 +- examples/index.js | 2 +- examples/views/alert.edge | 2 +- examples/views/user.edge | 2 +- index.js | 4 +- japaFile.js | 3 +- package-lock.json | 27 +++++++++ package.json | 3 +- src/Compiler/index.ts | 59 +++++++++++------- src/Contracts/index.ts | 21 ++++++- src/Edge/index.ts | 122 +++++++++++++++++++++++++++++++++----- src/Loader/index.ts | 12 ++-- src/Tags/Component.ts | 1 + src/Tags/Each.ts | 1 + src/Tags/Else.ts | 1 + src/Tags/ElseIf.ts | 1 + src/Tags/If.ts | 1 + src/Tags/Include.ts | 1 + src/Tags/Slot.ts | 1 + src/Template/index.ts | 24 +++++--- test/compiler.spec.ts | 44 ++++++++++++-- test/edge.spec.ts | 84 ++++++++++++++++++++++++++ test/fixtures.spec.ts | 2 +- test/loader.spec.ts | 25 ++++++-- test/tags.spec.ts | 10 ++-- test/template.spec.ts | 25 +++++--- 26 files changed, 400 insertions(+), 80 deletions(-) create mode 100644 test/edge.spec.ts diff --git a/.editorconfig b/.editorconfig index 0294b29..d8fb4dc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,5 +21,5 @@ indent_style = tab [*.md] trim_trailing_whitespace = false -[fixtures/**/*] +[{fixtures,examples}/**/*] insert_final_newline = ignore diff --git a/examples/index.js b/examples/index.js index 8f24849..865ec93 100644 --- a/examples/index.js +++ b/examples/index.js @@ -3,5 +3,5 @@ const edge = require('..') const { join } = require('path') -edge.mount('default', join(__dirname, './views')) +edge.mount(join(__dirname, './views')) console.log(edge.render('user', { title: 'Hello' })) diff --git a/examples/views/alert.edge b/examples/views/alert.edge index 756e6c8..29020e1 100644 --- a/examples/views/alert.edge +++ b/examples/views/alert.edge @@ -1 +1 @@ -

{{ title }}

+

{{ title }}

\ No newline at end of file diff --git a/examples/views/user.edge b/examples/views/user.edge index 1181f29..732771f 100644 --- a/examples/views/user.edge +++ b/examples/views/user.edge @@ -1,2 +1,2 @@ @!component('alert', title = 'Hey') -@!component('alert', title = 'Wow') +@!component('alert', title = 'Wow') \ No newline at end of file diff --git a/index.js b/index.js index 7bcd5ec..3a21911 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,6 @@ */ const { Edge } = require('./build/src/Edge') -const view = new Edge() -module.exports = view +module.exports = Edge +module.exports.utils = require('./build/src/utils') diff --git a/japaFile.js b/japaFile.js index c28bb83..aa43bb7 100644 --- a/japaFile.js +++ b/japaFile.js @@ -6,9 +6,8 @@ const os = require('os') Assertion.use((chai, utils) => { chai.assert.stringEqual = function (val, exp, msg) { - console.log(val) new chai.Assertion(val.split(/\r\n|\n/), msg).to.deep.equal(exp.split(/\r\n|\n/)) } }) -cli.run('test/*.spec.ts') +cli.run('test/edge.spec.ts') diff --git a/package-lock.json b/package-lock.json index 4f7b0d1..28423ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -465,6 +465,19 @@ "os-homedir": "^1.0.1" } }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -5833,6 +5846,15 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, "requireg": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.1.8.tgz", @@ -5863,6 +5885,11 @@ "global-modules": "^0.2.3" } }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", diff --git a/package.json b/package.json index 9b34ffd..5f13f9b 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "edge-parser": "^1.0.9", "he": "^1.1.1", "macroable": "^1.0.0", - "node-exceptions": "^3.0.0" + "node-exceptions": "^3.0.0", + "require-uncached": "^1.0.3" } } diff --git a/src/Compiler/index.ts b/src/Compiler/index.ts index 112648e..05ad936 100644 --- a/src/Compiler/index.ts +++ b/src/Compiler/index.ts @@ -7,44 +7,63 @@ * file that was distributed with this source code. */ -import { ILoader } from '../Contracts' import { Parser } from 'edge-parser' -import { ITag } from 'edge-parser/build/src/Contracts' +import { ILoader, IPresenterConstructor, ICompiler, Tags } from '../Contracts' -export class Compiler { - private cache: Map = new Map() +export class Compiler implements ICompiler { + private cacheStore: Map = new Map() - constructor (private loader: ILoader, private tags: { [key: string]: ITag }, private shouldCache: boolean = true) { + constructor (private loader: ILoader, private tags: Tags, public cache: boolean = true) { } /** - * Compiles a given template by loading it using the loader + * Returns the template and the presenter class from the + * cache. If caching is disabled, then it will + * return undefined */ - private _compile (templatePath: string, inline: boolean = false): string { - const template = this.loader.resolve(templatePath) - const parser = new Parser(this.tags) - return parser.parseTemplate(template, !inline) + private _getFromCache (templatePath: string): undefined | { template: string, Presenter?: IPresenterConstructor } { + if (!this.cache) { + return + } + + return this.cacheStore.get(templatePath) + } + + /** + * Set's the template path and the payload to the cache. If + * cache is disable, then it will never be set. + */ + private _setInCache (templatePath: string, payload: { template: string, Presenter?: IPresenterConstructor }) { + if (!this.cache) { + return + } + + this.cacheStore.set(templatePath, payload) } /** * Compiles a given template by loading it using the loader, also caches * the template and returns from the cache (if exists). + * + * When the `inline` property is set to true, the Presenter resolution + * will not happen, since Presenters are tied to the top level + * views and not partials. */ - public compile (templatePath: string, inline: boolean = false): string { + public compile (templatePath: string, inline: boolean): { template: string, Presenter?: IPresenterConstructor } { templatePath = this.loader.makePath(templatePath) - /** - * Compile right away when cache is disabled - */ - if (!this.shouldCache) { - return this._compile(templatePath, inline) + const cachedResponse = this._getFromCache(templatePath) + if (cachedResponse) { + return cachedResponse } - /* istanbul ignore else */ - if (!this.cache.get(templatePath)) { - this.cache.set(templatePath, this._compile(templatePath, inline)) + const { template, Presenter } = this.loader.resolve(templatePath, !inline) + const payload = { + template: new Parser(this.tags).parseTemplate(template, !inline), + Presenter, } - return this.cache.get(templatePath)! + this._setInCache(templatePath, payload) + return payload } } diff --git a/src/Contracts/index.ts b/src/Contracts/index.ts index 06e3e20..69aa959 100644 --- a/src/Contracts/index.ts +++ b/src/Contracts/index.ts @@ -1,11 +1,30 @@ +import { ITag as BaseTag } from 'edge-parser/build/src/Contracts' + +export interface ILoaderConstructor { + new (): ILoader +} + export interface ILoader { mounted: object mount (diskName: string, dirPath: string): void unmount (diskName: string): void - resolve (templatePath: string): { template: string, Presenter?: IPresenterConstructor } + resolve (templatePath: string, withResolver: boolean): { template: string, Presenter?: IPresenterConstructor } makePath (templatePath: string): string } +export interface ICompiler { + cache: boolean + compile (templatePath: string, inline: boolean): { template: string, Presenter?: IPresenterConstructor } +} + +export interface ITag extends BaseTag { + tagName: string +} + +export type Tags = { + [key: string]: ITag, +} + export interface IPresenterConstructor { new (state: any): IPresenter } diff --git a/src/Edge/index.ts b/src/Edge/index.ts index 187e7f0..ebaae35 100644 --- a/src/Edge/index.ts +++ b/src/Edge/index.ts @@ -7,32 +7,126 @@ * file that was distributed with this source code. */ +import { merge } from 'lodash' import * as Tags from '../Tags' import { Compiler } from '../Compiler' import { Loader } from '../Loader' +import { ILoaderConstructor, ILoader, ITag } from '../Contracts' import { Template } from '../Template' -import { Presenter } from '../Presenter' + +let loader: null | ILoader = null +let compiler: null | Compiler = null + +type configOptions = { + Loader?: ILoaderConstructor, + cache?: boolean, +} export class Edge { - private loader: Loader - private compiler: Compiler + private static globals: any = {} + private locals: any = {} + + /** + * Returns the instance of loader in use. Make use of + * `configure` method to define a custom loader + */ + public static get loader (): ILoader { + return loader! + } + + /** + * Returns the instance of compiler in use. + */ + public static get compiler (): Compiler { + return compiler! + } + + /** + * Configure edge + */ + public static configure (options: configOptions) { + loader = new (options.Loader || Loader)() + compiler = new Compiler(loader!, Tags, options.cache || true) + } + + public static mount (diskName: string, dirPath: string): void + public static mount (dirPath: string): void + + /** + * Mount a disk to the loader + */ + public static mount (diskName: string, dirPath?: string): void { + if (!this.compiler) { + this.configure({}) + } + + if (!dirPath) { + dirPath = diskName + diskName = 'default' + } + + loader!.mount(diskName, dirPath) + } + + /** + * Un Mount a disk from the loader + */ + public static unmount (diskName: string): void { + loader!.unmount(diskName) + } + + /** + * Add a new global to the edge globals + */ + public static global (name: string, value: any): void { + this.globals[name] = value + } + + /** + * Add a new tag to the tags list + */ + public static tag (Tag: ITag) { + Tags[Tag.tagName] = Tag + } + + /** + * Shorthand to `new Edge().render()` or `Edge.newUp().render()` + */ + public static render (templatePath: string, state: any): string { + return new this().render(templatePath, state) + } - constructor () { - this.loader = new Loader() - this.compiler = new Compiler(this.loader, Tags) + /** + * Returns a new instance of edge. The instance + * can be used to define locals. + */ + public static newUp (): Edge { + return new this() } - public mount (diskName: string, dirPath: string) { - this.loader.mount(diskName, dirPath) + /** + * Clears registered globals, loader and + * the compiler instance + */ + public static clear () { + this.globals = {} + loader = null + compiler = null } - public unmount (diskName: string) { - this.loader.unmount(diskName) + /** + * Render template with state + */ + public render (templatePath: string, state: any): string { + const template = new Template(compiler!, (this.constructor as typeof Edge).globals, this.locals) + return template.render(templatePath, state) } - public render (template: string, state: any): string { - const templateInstance = new Template(this.compiler, {}) - const presenter = new Presenter(state) - return templateInstance.render(template, presenter) + /** + * Share locals with the current view context + */ + public share (data: any): this { + merge(this.locals, data) + return this } } diff --git a/src/Loader/index.ts b/src/Loader/index.ts index 8887da9..3699515 100644 --- a/src/Loader/index.ts +++ b/src/Loader/index.ts @@ -9,6 +9,8 @@ import { join, isAbsolute, extname } from 'path' import { readFileSync } from 'fs' +import * as requireUncached from 'require-uncached' + import { ILoader, IPresenterConstructor } from '../Contracts' import { extractDiskAndTemplateName } from '../utils' @@ -65,11 +67,11 @@ export class Loader implements ILoader { /** * Resolves a template from disk and returns it as a string */ - public resolve (templatePath: string): { template: string, Presenter?: IPresenterConstructor } { + public resolve (templatePath: string, withPresenter: boolean): { template: string, Presenter?: IPresenterConstructor } { try { templatePath = isAbsolute(templatePath) ? templatePath : this.makePath(templatePath) const template = readFileSync(templatePath, 'utf-8') - const Presenter = this._getPresenterForTemplate(templatePath) + const Presenter = withPresenter ? this._getPresenterForTemplate(templatePath) : undefined return { template, Presenter } } catch (error) { if (error.code === 'ENOENT') { @@ -86,9 +88,11 @@ export class Loader implements ILoader { */ private _getPresenterForTemplate (templatePath: string): IPresenterConstructor | undefined { try { - return require(templatePath.replace(extname(templatePath), '.presenter.js')) + return requireUncached(templatePath.replace(extname(templatePath), '.presenter.js')) } catch (error) { - // ignore if presenter missing + if (['ENOENT', 'MODULE_NOT_FOUND'].indexOf(error.code) === -1) { + throw error + } } } } diff --git a/src/Tags/Component.ts b/src/Tags/Component.ts index 5dd37ea..ed059a7 100644 --- a/src/Tags/Component.ts +++ b/src/Tags/Component.ts @@ -16,6 +16,7 @@ export class ComponentTag { public static block = true public static seekable = true public static selfclosed = true + public static tagName = 'component' /** * Compiles else block node to Javascript else statement diff --git a/src/Tags/Each.ts b/src/Tags/Each.ts index 4c5948d..080b916 100644 --- a/src/Tags/Each.ts +++ b/src/Tags/Each.ts @@ -17,6 +17,7 @@ export class EachTag { public static block = true public static seekable = true public static selfclosed = true + public static tagName = 'each' private allowedExpressions = ['BinaryExpression'] diff --git a/src/Tags/Else.ts b/src/Tags/Else.ts index 15014ab..10ffa1f 100644 --- a/src/Tags/Else.ts +++ b/src/Tags/Else.ts @@ -15,6 +15,7 @@ export class ElseTag { public static block = false public static seekable = false public static selfclosed = false + public static tagName = 'else' /** * Compiles else block node to Javascript else statement diff --git a/src/Tags/ElseIf.ts b/src/Tags/ElseIf.ts index 22f0527..6eb69b5 100644 --- a/src/Tags/ElseIf.ts +++ b/src/Tags/ElseIf.ts @@ -16,6 +16,7 @@ export class ElseIfTag { public static block = false public static seekable = true public static selfclosed = false + public static tagName = 'elseif' protected bannedExpressions = ['SequenceExpression'] diff --git a/src/Tags/If.ts b/src/Tags/If.ts index 5c1b9ca..0097656 100644 --- a/src/Tags/If.ts +++ b/src/Tags/If.ts @@ -16,6 +16,7 @@ export class IfTag { public static block = true public static seekable = true public static selfclosed = false + public static tagName = 'if' /** * Expressions which are not allowed by the sequence diff --git a/src/Tags/Include.ts b/src/Tags/Include.ts index 7675f11..24d4b08 100644 --- a/src/Tags/Include.ts +++ b/src/Tags/Include.ts @@ -16,6 +16,7 @@ export class IncludeTag { public static block = false public static seekable = true public static selfclosed = false + public static tagName = 'include' /** * Expressions which are not allowed by the sequence diff --git a/src/Tags/Slot.ts b/src/Tags/Slot.ts index 49db9bb..320678e 100644 --- a/src/Tags/Slot.ts +++ b/src/Tags/Slot.ts @@ -15,6 +15,7 @@ export class SlotTag { public static block = true public static seekable = true public static selfclosed = false + public static tagName = 'slot' /** * Compiles else block node to Javascript else statement diff --git a/src/Template/index.ts b/src/Template/index.ts index 59145ec..3f9e329 100644 --- a/src/Template/index.ts +++ b/src/Template/index.ts @@ -11,7 +11,6 @@ import { merge } from 'lodash' import { Context } from '../Context' import { Compiler } from '../Compiler' import { Presenter as BasePresenter } from '../Presenter' -import { IPresenterConstructor } from '../Contracts' export class Template { private sharedState: any @@ -20,21 +19,32 @@ export class Template { this.sharedState = merge({}, globals, locals) } + /** + * Render the inline template (aka partials) + */ public renderInline (templatePath: string): Function { - return new Function('template', 'ctx', this.compiler.compile(templatePath, true)) + return new Function('template', 'ctx', this.compiler.compile(templatePath, true).template) } + /** + * Renders an inline template, but with isolated state. This is + * mainly used by the components + */ public renderWithState (template: string, state: object, slots: object): string { - const compiledTemplate = this.compiler.compile(template) - const presenter = new BasePresenter(Object.assign(state, { $slots: slots })) + const { template: compiledTemplate, Presenter } = this.compiler.compile(template, false) + const presenter = new (Presenter || BasePresenter)(Object.assign(state, { $slots: slots })) const ctx = new Context(presenter, this.sharedState) return new Function('template', 'ctx', `return ${compiledTemplate}`)(this, ctx) } - public render (template: string, state: object, Presenter: IPresenterConstructor = BasePresenter): string { - const compiledTemplate = this.compiler.compile(template) - const presenter = new Presenter(state) + /** + * Renders the template by using the template path and the state. The presenter + * is resolved by the loader itself + */ + public render (template: string, state: object): string { + const { template: compiledTemplate, Presenter } = this.compiler.compile(template, false) + const presenter = new (Presenter || BasePresenter)(state) const ctx = new Context(presenter, this.sharedState) return new Function('template', 'ctx', `return ${compiledTemplate}`)(this, ctx) diff --git a/test/compiler.spec.ts b/test/compiler.spec.ts index 0344a7b..bb4e45f 100644 --- a/test/compiler.spec.ts +++ b/test/compiler.spec.ts @@ -20,6 +20,7 @@ const tags = { public static block = true public static seekable = true public static selfclosed = false + public static tagName = 'if' }, } const viewsDir = join(__dirname, 'views') @@ -35,7 +36,7 @@ test.group('Compiler', (group) => { loader.mount('default', viewsDir) const compiler = new Compiler(loader, tags) - assert.equal(compiler.compile('foo'), `(function (template, ctx) { + assert.equal(compiler.compile('foo', false).template, `(function (template, ctx) { let out = '' out += 'Hello ' out += \`\${ctx.escape(ctx.resolve('username'))}\` @@ -50,7 +51,18 @@ test.group('Compiler', (group) => { loader.mount('default', viewsDir) const compiler = new Compiler(loader, tags) - assert.equal(compiler.compile('foo'), compiler['cache'].get(join(viewsDir, 'foo.edge'))) + assert.equal(compiler.compile('foo', false), compiler['cacheStore'].get(join(viewsDir, 'foo.edge'))) + }) + + test('save template and presenter both to the cache', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ username }}') + await fs.outputFile(join(viewsDir, 'foo.presenter.js'), 'module.exports = class Foo {}') + const loader = new Loader() + loader.mount('default', viewsDir) + + const compiler = new Compiler(loader, tags) + compiler.compile('foo', false) + assert.equal(compiler['cacheStore'].get(join(viewsDir, 'foo.edge'))!.Presenter!['name'], 'Foo') }) test('do not cache when disabled', async (assert) => { @@ -59,7 +71,31 @@ test.group('Compiler', (group) => { loader.mount('default', viewsDir) const compiler = new Compiler(loader, tags, false) - compiler.compile('foo') - assert.isUndefined(compiler['cache'].get(join(viewsDir, 'foo.edge'))) + compiler.compile('foo', false) + assert.isUndefined(compiler['cacheStore'].get(join(viewsDir, 'foo.edge'))) + }) + + test('compile template as inline', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ username }}') + const loader = new Loader() + loader.mount('default', viewsDir) + + const compiler = new Compiler(loader, tags) + assert.equal(compiler.compile('foo', true).template, ` + let out = '' + out += 'Hello ' + out += \`\${ctx.escape(ctx.resolve('username'))}\` + out += '${EOL === '\n' ? '\\n' : '\\r\\n'}' + return out`) + }) + + test('do not load presenter when inline', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ username }}') + await fs.outputFile(join(viewsDir, 'foo.presenter.js'), '') + const loader = new Loader() + loader.mount('default', viewsDir) + + const compiler = new Compiler(loader, tags) + assert.isUndefined(compiler.compile('foo', true).Presenter) }) }) diff --git a/test/edge.spec.ts b/test/edge.spec.ts new file mode 100644 index 0000000..7cd5a08 --- /dev/null +++ b/test/edge.spec.ts @@ -0,0 +1,84 @@ +/* +* edge +* +* (c) Harminder Virk +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +import * as test from 'japa' +import * as fs from 'fs-extra' + +import { join } from 'path' + +import { Edge } from '../src/Edge' +import { Loader } from '../src/Loader' +import { Compiler } from '../src/Compiler' + +const viewsDir = join(__dirname, 'views') + +test.group('Template', (group) => { + group.afterEach(async () => { + Edge.clear() + await fs.remove(viewsDir) + }) + + test('calling configure should setup the loader and compiler', (assert) => { + Edge.configure({}) + assert.instanceOf(Edge.loader, Loader) + assert.instanceOf(Edge.compiler, Compiler) + }) + + test('mount default disk', (assert) => { + Edge.mount(viewsDir) + assert.deepEqual(Edge.loader.mounted, { default: viewsDir }) + }) + + test('mount named disk', (assert) => { + Edge.mount('foo', viewsDir) + assert.deepEqual(Edge.loader.mounted, { foo: viewsDir }) + }) + + test('unmount named disk', (assert) => { + Edge.mount('foo', viewsDir) + Edge.unmount('foo') + assert.deepEqual(Edge.loader.mounted, {}) + }) + + test('register globals', (assert) => { + Edge.global('foo', 'bar') + assert.deepEqual(Edge['globals'].foo, 'bar') + }) + + test('add a custom tag to the tags list', (assert) => { + class MyTag { + public static tagName = 'mytag' + public static block = true + public static seekable = true + public static selfclosed = true + } + Edge.tag(MyTag) + + Edge.configure({}) + assert.deepEqual(Edge.compiler['tags'].mytag, MyTag) + }) + + test('render a view using the static render method', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ username }}') + + Edge.mount(viewsDir) + assert.equal(Edge.render('foo', { username: 'virk' }).trim(), 'Hello virk') + }) + + test('pass locals to the view context', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), `Hello {{ username || 'guest' }}`) + + Edge.mount(viewsDir) + const tmpl = Edge.newUp() + tmpl.share({ username: 'nikk' }) + + assert.equal(tmpl.render('foo', {}).trim(), 'Hello nikk') + assert.equal(Edge.render('foo', {}).trim(), 'Hello guest') + }) +}) diff --git a/test/fixtures.spec.ts b/test/fixtures.spec.ts index 1632fd8..e6b446a 100644 --- a/test/fixtures.spec.ts +++ b/test/fixtures.spec.ts @@ -40,7 +40,7 @@ test.group('Fixtures', (group) => { test(dir, (assert) => { const template = new Template(compiler, {}, {}) - const compiled = compiler.compile(`${dir}/index.edge`) + const { template: compiled } = compiler.compile(`${dir}/index.edge`, false) const expectedCompiled = readFileSync(join(dirBasePath, 'compiled.js'), 'utf-8') assert.stringEqual(compiled, expectedCompiled) diff --git a/test/loader.spec.ts b/test/loader.spec.ts index 147b5b7..431b0af 100644 --- a/test/loader.spec.ts +++ b/test/loader.spec.ts @@ -36,7 +36,7 @@ test.group('Loader', (group) => { test('throw exception when resolve path from undefined location', (assert) => { const loader = new Loader() - const fn = () => loader.resolve('foo') + const fn = () => loader.resolve('foo', true) assert.throw(fn, 'Attempting to resolve foo.edge template for unmounted default location') }) @@ -46,7 +46,7 @@ test.group('Loader', (group) => { const loader = new Loader() loader.mount('default', viewsDir) - const { template } = loader.resolve('foo') + const { template } = loader.resolve('foo', false) assert.equal(template.trim(), 'Hello world') }) @@ -54,7 +54,7 @@ test.group('Loader', (group) => { const loader = new Loader() loader.mount('default', viewsDir) - const fn = () => loader.resolve('foo') + const fn = () => loader.resolve('foo', false) assert.throw(fn, `Cannot resolve ${join(viewsDir, 'foo.edge')}. Make sure file exists.`) }) @@ -64,7 +64,7 @@ test.group('Loader', (group) => { const loader = new Loader() loader.mount('default', viewsDir) - const { template } = loader.resolve('foo.edge') + const { template } = loader.resolve('foo.edge', false) assert.equal(template.trim(), 'Hello world') }) @@ -74,7 +74,7 @@ test.group('Loader', (group) => { const loader = new Loader() loader.mount('users', viewsDir) - const { template } = loader.resolve('users::foo.edge') + const { template } = loader.resolve('users::foo.edge', false) assert.equal(template.trim(), 'Hello world') }) @@ -118,8 +118,21 @@ test.group('Loader', (group) => { const loader = new Loader() loader.mount('users', viewsDir) - const { template, Presenter } = loader.resolve('users::foo.edge') + const { template, Presenter } = loader.resolve('users::foo.edge', true) assert.equal(template.trim(), 'Hello world') assert.equal(Presenter!['name'], 'Foo') }) + + test('do not resolve presenter if withPresenter is set to false', async (assert) => { + await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello world') + await fs.outputFile(join(viewsDir, 'foo.presenter.js'), `module.exports = class Foo { + }`) + + const loader = new Loader() + loader.mount('users', viewsDir) + + const { template, Presenter } = loader.resolve('users::foo.edge', false) + assert.equal(template.trim(), 'Hello world') + assert.isUndefined(Presenter) + }) }) diff --git a/test/tags.spec.ts b/test/tags.spec.ts index e479720..33a38b6 100644 --- a/test/tags.spec.ts +++ b/test/tags.spec.ts @@ -31,7 +31,7 @@ We are writing a bad if condition await fs.outputFile(join(viewsDir, 'foo.edge'), templateContent) try { - compiler.compile('foo') + compiler.compile('foo', true) } catch (error) { assert.equal(error.line, 4) } @@ -48,7 +48,7 @@ We are writing a bad if condition await fs.outputFile(join(viewsDir, 'foo.edge'), templateContent) try { - compiler.compile('foo') + compiler.compile('foo', true) } catch (error) { assert.equal(error.line, 4) assert.equal(error.message, 'E_UNALLOWED_EXPRESSION: SequenceExpression is not allowed for if tag\n> More details: https://err.sh/poppinss/edge-errors/E_UNALLOWED_EXPRESSION') @@ -65,7 +65,7 @@ We are writing a bad if condition await fs.outputFile(join(viewsDir, 'foo.edge'), templateContent) try { - compiler.compile('foo') + compiler.compile('foo', true) } catch (error) { assert.equal(error.message, 'Unclosed tag if') } @@ -81,7 +81,7 @@ test.group('Include', () => { await fs.outputFile(join(viewsDir, 'foo.edge'), templateContent) try { - compiler.compile('foo') + compiler.compile('foo', true) } catch (error) { assert.equal(error.line, 2) } @@ -94,7 +94,7 @@ test.group('Include', () => { await fs.outputFile(join(viewsDir, 'foo.edge'), templateContent) try { - compiler.compile('foo') + compiler.compile('foo', true) } catch (error) { assert.equal(error.line, 1) assert.equal(error.message, 'E_UNALLOWED_EXPRESSION: SequenceExpression is not allowed for if tag\n> More details: https://err.sh/poppinss/edge-errors/E_UNALLOWED_EXPRESSION') diff --git a/test/template.spec.ts b/test/template.spec.ts index 01564f8..d37ed48 100644 --- a/test/template.spec.ts +++ b/test/template.spec.ts @@ -12,7 +12,6 @@ import * as fs from 'fs-extra' import { join } from 'path' -import { Presenter } from '../src/Presenter' import { Template } from '../src/Template' import { Compiler } from '../src/Compiler' import { Loader } from '../src/Loader' @@ -23,6 +22,7 @@ const tags = { public static block = true public static seekable = true public static selfclosed = false + public static tagName = 'if' }, } @@ -43,26 +43,33 @@ test.group('Template', (group) => { test('run template with custom presenter', async (assert) => { await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ getUsername() }}') - class MyPresenter extends Presenter { - public getUsername () { + await fs.outputFile(join(viewsDir, 'foo.presenter.js'), `module.exports = class MyPresenter { + constructor (state) { + this.state = state + } + + getUsername () { return this.state.username.toUpperCase() } - } + }`) - const output = new Template(compiler, {}, {}).render('foo', { username: 'virk' }, MyPresenter) + const output = new Template(compiler, {}, {}).render('foo', { username: 'virk' }) assert.equal(output.trim(), 'Hello VIRK') }) test('run template with shared state', async (assert) => { await fs.outputFile(join(viewsDir, 'foo.edge'), 'Hello {{ getUsername() }}') + await fs.outputFile(join(viewsDir, 'foo.presenter.js'), `module.exports = class MyPresenter { + constructor (state) { + this.state = state + } - class MyPresenter extends Presenter { - public getUsername (ctx) { + getUsername (ctx) { return ctx.resolve('username').toUpperCase() } - } + }`) - const output = new Template(compiler, { username: 'virk' }, {}).render('foo', {}, MyPresenter) + const output = new Template(compiler, { username: 'virk' }, {}).render('foo', {}) assert.equal(output.trim(), 'Hello VIRK') }) })