From 852c6ad45399546c8d4c9a60400f5533dc47199d Mon Sep 17 00:00:00 2001 From: harttle Date: Tue, 19 Feb 2019 22:28:05 +0800 Subject: [PATCH] refactor: remove mock-fs from dev dependency --- .travis.yml | 4 +- rollup.config.js | 4 +- src/builtin/filters/index.ts | 5 +- src/builtin/tags/include.ts | 2 +- src/builtin/tags/layout.ts | 2 +- .../template-browser.ts => fs/browser.ts} | 27 +- src/fs/ifs.ts | 5 + src/fs/node.ts | 22 + src/liquid-options.ts | 22 +- src/liquid.ts | 67 ++- src/parser/template.ts | 31 -- src/parser/whitespace-ctrl.ts | 3 +- src/scope/scope.ts | 17 +- src/util/error.ts | 4 +- src/util/strftime.ts | 2 - test/e2e/express.ts | 53 +-- test/e2e/render-file.ts | 62 ++- test/stub/mockfs.ts | 31 +- test/stub/partials/bar.html | 1 + test/stub/render.ts | 18 + test/stub/root/bar | 1 + test/stub/root/foo.html | 1 + test/stub/views/bar.html | 1 + test/stub/views/include.html | 1 + test/stub/views/name.html | 1 + test/unit/builtin/filters/array.ts | 66 +++ test/unit/builtin/filters/date.ts | 20 + test/unit/builtin/filters/html.ts | 43 ++ test/unit/builtin/filters/math.ts | 64 +++ test/unit/builtin/filters/object.ts | 8 + test/unit/builtin/filters/string.ts | 161 +++++++ test/unit/builtin/filters/url.ts | 15 + test/unit/{ => builtin}/tags/assign.ts | 0 test/unit/{ => builtin}/tags/capture.ts | 0 test/unit/{ => builtin}/tags/case.ts | 0 test/unit/{ => builtin}/tags/comment.ts | 0 test/unit/{ => builtin}/tags/cycle.ts | 0 test/unit/{ => builtin}/tags/decrement.ts | 0 test/unit/{ => builtin}/tags/for.ts | 0 test/unit/{ => builtin}/tags/if.ts | 0 test/unit/{ => builtin}/tags/include.ts | 0 test/unit/{ => builtin}/tags/increment.ts | 0 test/unit/{ => builtin}/tags/layout.ts | 0 test/unit/{ => builtin}/tags/raw.ts | 0 test/unit/{ => builtin}/tags/tablerow.ts | 0 test/unit/{ => builtin}/tags/unless.ts | 0 test/unit/filters.ts | 425 ------------------ test/unit/fs/browser.ts | 84 ++++ test/unit/fs/node.ts | 47 ++ test/unit/{options => liquid}/cache.ts | 0 test/unit/{ => liquid}/liquid.ts | 7 +- test/unit/{options => liquid}/strict.ts | 0 test/unit/{options => liquid}/trimming.ts | 2 +- test/unit/{ => parser}/lexical.ts | 2 +- test/unit/{ => parser}/tokenizer.ts | 0 test/unit/{ => render}/render.ts | 6 +- test/unit/{ => render}/syntax.ts | 0 test/unit/{ => scope}/scope.ts | 0 test/unit/template-browser.ts | 83 ---- test/unit/template.ts | 24 - test/unit/{ => template}/filter.ts | 0 test/unit/{ => template}/output.ts | 4 +- test/unit/{ => template}/tag.ts | 0 test/unit/{ => template}/value.ts | 2 +- test/unit/util/assert.ts | 2 +- test/unit/util/promise.ts | 2 +- test/unit/util/strftime.ts | 2 +- test/unit/util/underscore.ts | 4 +- test/unit/xhr.ts | 44 -- tsconfig.json | 2 +- 70 files changed, 711 insertions(+), 795 deletions(-) rename src/{parser/template-browser.ts => fs/browser.ts} (69%) create mode 100644 src/fs/ifs.ts create mode 100644 src/fs/node.ts delete mode 100644 src/parser/template.ts create mode 100644 test/stub/partials/bar.html create mode 100644 test/stub/render.ts create mode 100644 test/stub/root/bar create mode 100644 test/stub/root/foo.html create mode 100644 test/stub/views/bar.html create mode 100644 test/stub/views/include.html create mode 100644 test/stub/views/name.html create mode 100644 test/unit/builtin/filters/array.ts create mode 100644 test/unit/builtin/filters/date.ts create mode 100644 test/unit/builtin/filters/html.ts create mode 100644 test/unit/builtin/filters/math.ts create mode 100644 test/unit/builtin/filters/object.ts create mode 100644 test/unit/builtin/filters/string.ts create mode 100644 test/unit/builtin/filters/url.ts rename test/unit/{ => builtin}/tags/assign.ts (100%) rename test/unit/{ => builtin}/tags/capture.ts (100%) rename test/unit/{ => builtin}/tags/case.ts (100%) rename test/unit/{ => builtin}/tags/comment.ts (100%) rename test/unit/{ => builtin}/tags/cycle.ts (100%) rename test/unit/{ => builtin}/tags/decrement.ts (100%) rename test/unit/{ => builtin}/tags/for.ts (100%) rename test/unit/{ => builtin}/tags/if.ts (100%) rename test/unit/{ => builtin}/tags/include.ts (100%) rename test/unit/{ => builtin}/tags/increment.ts (100%) rename test/unit/{ => builtin}/tags/layout.ts (100%) rename test/unit/{ => builtin}/tags/raw.ts (100%) rename test/unit/{ => builtin}/tags/tablerow.ts (100%) rename test/unit/{ => builtin}/tags/unless.ts (100%) delete mode 100644 test/unit/filters.ts create mode 100644 test/unit/fs/browser.ts create mode 100644 test/unit/fs/node.ts rename test/unit/{options => liquid}/cache.ts (100%) rename test/unit/{ => liquid}/liquid.ts (87%) rename test/unit/{options => liquid}/strict.ts (100%) rename test/unit/{options => liquid}/trimming.ts (98%) rename test/unit/{ => parser}/lexical.ts (98%) rename test/unit/{ => parser}/tokenizer.ts (100%) rename test/unit/{ => render}/render.ts (85%) rename test/unit/{ => render}/syntax.ts (100%) rename test/unit/{ => scope}/scope.ts (100%) delete mode 100644 test/unit/template-browser.ts delete mode 100644 test/unit/template.ts rename test/unit/{ => template}/filter.ts (100%) rename test/unit/{ => template}/output.ts (96%) rename test/unit/{ => template}/tag.ts (100%) rename test/unit/{ => template}/value.ts (98%) delete mode 100644 test/unit/xhr.ts diff --git a/.travis.yml b/.travis.yml index 2303d5e71..966d2dc08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: node_js -node_js: - - "8" - - "6" +node_js: "lts/*" jobs: include: - stage: test diff --git a/rollup.config.js b/rollup.config.js index a91dfecca..86dc2a8b8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -50,7 +50,7 @@ export default [{ exclude: [ 'test' ], compilerOptions: { module: 'ES2015', - paths: { 'template': ['src/parser/template-browser'] } + paths: { 'src/fs': ['src/fs/browser'] } } } }) @@ -71,7 +71,7 @@ export default [{ exclude: [ 'test' ], compilerOptions: { module: 'ES2015', - paths: { 'template': ['src/parser/template-browser'] } + paths: { 'src/fs': ['src/fs/browser'] } } } }), diff --git a/src/builtin/filters/index.ts b/src/builtin/filters/index.ts index e47aa10d8..c3bdc588a 100644 --- a/src/builtin/filters/index.ts +++ b/src/builtin/filters/index.ts @@ -1,4 +1,3 @@ -import { assign } from 'src/util/underscore' import html from './html' import str from './string' import math from './math' @@ -7,6 +6,4 @@ import array from './array' import date from './date' import obj from './object' -const filters = assign({}, html, str, math, url, date, obj, array) - -export default filters +export default { ...html, ...str, ...math, ...url, ...date, ...obj, ...array } diff --git a/src/builtin/tags/include.ts b/src/builtin/tags/include.ts index 45d4f6298..615d65d84 100644 --- a/src/builtin/tags/include.ts +++ b/src/builtin/tags/include.ts @@ -45,7 +45,7 @@ export default { if (this.with) { hash[filepath] = evalValue(this.with, scope) } - const templates = await this.liquid.getTemplate(filepath, scope.opts.root) + const templates = await this.liquid.getTemplate(filepath, scope.opts) scope.push(hash) const html = await this.liquid.renderer.renderTemplates(templates, scope) scope.pop(hash) diff --git a/src/builtin/tags/layout.ts b/src/builtin/tags/layout.ts index 8fb229acf..69942e102 100644 --- a/src/builtin/tags/layout.ts +++ b/src/builtin/tags/layout.ts @@ -31,7 +31,7 @@ export default { if (scope.blocks[''] === undefined) { scope.blocks[''] = html } - const templates = await this.liquid.getTemplate(layout, scope.opts.root) + const templates = await this.liquid.getTemplate(layout, scope.opts) scope.push(hash) scope.blockMode = BlockMode.OUTPUT const partial = await this.liquid.renderer.renderTemplates(templates, scope) diff --git a/src/parser/template-browser.ts b/src/fs/browser.ts similarity index 69% rename from src/parser/template-browser.ts rename to src/fs/browser.ts index 37aa91eb8..50356b34f 100644 --- a/src/parser/template-browser.ts +++ b/src/fs/browser.ts @@ -1,4 +1,5 @@ -import { last, isArray } from '../util/underscore' +import { last } from '../util/underscore' +import IFS from './ifs' function domResolve (root, path) { const base = document.createElement('base') @@ -15,25 +16,17 @@ function domResolve (root, path) { return resolved } -export function resolve (filepath, root, options) { - root = root || options.root - if (isArray(root)) { - root = root[0] - } - if (root.length && last(root) !== '/') { - root += '/' - } +function resolve (root, filepath, ext) { + if (root.length && last(root) !== '/') root += '/' const url = domResolve(root, filepath) return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => { const last = path.split('/').pop() - if (/\.\w+$/.test(last)) { - return str - } - return origin + path + options.extname + if (/\.\w+$/.test(last)) return str + return origin + path + ext }) } -export async function read (url: string): Promise { +async function readFile (url: string): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.onload = () => { @@ -50,3 +43,9 @@ export async function read (url: string): Promise { xhr.send() }) } + +async function exists () { + return true +} + +export default { readFile, resolve, exists } as IFS diff --git a/src/fs/ifs.ts b/src/fs/ifs.ts new file mode 100644 index 000000000..637a0e7c4 --- /dev/null +++ b/src/fs/ifs.ts @@ -0,0 +1,5 @@ +export default interface IFS { + exists: (filepath?: string) => Promise + readFile: (filepath:string) => Promise + resolve: (root: string, file: string, ext: string) => string +} diff --git a/src/fs/node.ts b/src/fs/node.ts new file mode 100644 index 000000000..7ee93c966 --- /dev/null +++ b/src/fs/node.ts @@ -0,0 +1,22 @@ +import * as _ from '../util/underscore' +import { resolve, extname } from 'path' +import { stat, readFile } from 'fs' +import IFS from './ifs' + +const statAsync = _.promisify(stat) as (filepath: string) => Promise +const readFileAsync = _.promisify(readFile) as (filepath: string, encoding: string) => Promise + +const fs: IFS = { + exists: filepath => { + return statAsync(filepath).then(() => true).catch(() => false) + }, + readFile: filepath => { + return readFileAsync(filepath, 'utf8') + }, + resolve: (root: string, file: string, ext: string) => { + if (!extname(file)) file += ext + return resolve(root, file) + } +} + +export default fs diff --git a/src/liquid-options.ts b/src/liquid-options.ts index bf9ee75c2..84d49f900 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -1,3 +1,5 @@ +import * as _ from './util/underscore' + export interface LiquidOptions { /** `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`. If an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */ root?: string | string[] @@ -23,7 +25,11 @@ export interface LiquidOptions { greedy?: boolean } -export const defaultOptions: LiquidOptions = { +export interface NormalizedOptions extends LiquidOptions { + root?: string[] +} + +export const defaultOptions: NormalizedOptions = { root: ['.'], cache: false, extname: '', @@ -36,3 +42,17 @@ export const defaultOptions: LiquidOptions = { strict_filters: false, strict_variables: false } + +export function normalize (options: LiquidOptions): NormalizedOptions { + options = options || {} + if (options.hasOwnProperty('root')) { + options.root = normalizeStringArray(options.root) + } + return options as NormalizedOptions +} + +function normalizeStringArray (value: string | string[]): string[] { + if (_.isArray(value)) return value as string[] + if (_.isString(value)) return [value as string] + return [] +} diff --git a/src/liquid.ts b/src/liquid.ts index c559d7f81..e296688f0 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -1,6 +1,6 @@ import Scope from './scope/scope' import * as Types from './types' -import * as template from 'template' +import fs from 'src/fs' import * as _ from './util/underscore' import ITemplate from './template/itemplate' import Tokenizer from './parser/tokenizer' @@ -13,19 +13,17 @@ import Value from './template/value' import { isTruthy, isFalsy, evalExp, evalValue } from './render/syntax' import builtinTags from './builtin/tags' import builtinFilters from './builtin/filters' -import { LiquidOptions, defaultOptions } from './liquid-options' +import { LiquidOptions, NormalizedOptions, defaultOptions, normalize } from './liquid-options' export default class Liquid { - public options: LiquidOptions + public options: NormalizedOptions private cache: object private parser: Parser private renderer: Render private tokenizer: Tokenizer - constructor (options: LiquidOptions = {}) { - options = _.assign({}, defaultOptions, options) - options.root = normalizeStringArray(options.root) - + constructor (opts: LiquidOptions = {}) { + const options = { ...defaultOptions, ...normalize(opts) } if (options.cache) { this.cache = {} } @@ -42,37 +40,38 @@ export default class Liquid { return this.parser.parse(tokens) } render (tpl: Array, ctx?: object, opts?: LiquidOptions) { - opts = _.assign({}, this.options, opts) - const scope = new Scope(ctx, opts) + const options = { ...this.options, ...normalize(opts) } + const scope = new Scope(ctx, options) return this.renderer.renderTemplates(tpl, scope) } async parseAndRender (html: string, ctx?: object, opts?: LiquidOptions) { const tpl = await this.parse(html) return this.render(tpl, ctx, opts) } - async getTemplate (file, root) { - const filepath = await template.resolve(file, root, this.options) - return this.respectCache(filepath, async () => { - const str = await template.read(filepath) - return this.parse(str, filepath) - }) + async getTemplate (file, opts?: LiquidOptions) { + const options = normalize(opts) + const roots = options.root ? [...options.root, ...this.options.root] : this.options.root + const paths = roots.map(root => fs.resolve(root, file, this.options.extname)) + + for (const filepath of paths) { + if (!(await fs.exists(filepath))) continue + + if (this.options.cache && this.cache[filepath]) return this.cache[filepath] + const value = this.parse(await fs.readFile(filepath), filepath) + if (this.options.cache) this.cache[filepath] = value + return value + } + + const err = new Error('ENOENT') as any + err.message = `ENOENT: Failed to lookup "${file}" in "${roots}"` + err.code = 'ENOENT' + throw err } async renderFile (file, ctx?: object, opts?: LiquidOptions) { - opts = _.assign({}, opts) - const templates = await this.getTemplate(file, opts.root) + const options = normalize(opts) + const templates = await this.getTemplate(file, options) return this.render(templates, ctx, opts) } - async respectCache (key, getter) { - const cacheEnabled = this.options.cache - if (cacheEnabled && this.cache[key]) { - return this.cache[key] - } - const value = await getter() - if (cacheEnabled) { - this.cache[key] = value - } - return value - } evalValue (str: string, scope: Scope) { return new Value(str, this.options.strict_filters).value(scope) } @@ -85,10 +84,10 @@ export default class Liquid { plugin (plugin) { return plugin.call(this, Liquid) } - express (opts: LiquidOptions = {}) { + express () { const self = this - return function (filePath, ctx, cb) { - opts.root = this.root + return function (filePath: string, ctx: object, cb: (err: Error, html?: string) => void) { + const opts = { root: this.root } self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb) } } @@ -99,9 +98,3 @@ export default class Liquid { static evalValue = evalValue static Types = Types } - -function normalizeStringArray (value) { - if (_.isArray(value)) return value - if (_.isString(value)) return [value] - throw new TypeError('illegal root: ' + value) -} diff --git a/src/parser/template.ts b/src/parser/template.ts deleted file mode 100644 index ef0d4ff7b..000000000 --- a/src/parser/template.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as _ from '../util/underscore' -import * as path from 'path' -import { anySeries } from '../util/promise' -import { stat, readFile } from 'fs' - -export const fs = { - stat: _.promisify(stat) as ((filepath: string) => Promise), - readFile: _.promisify(readFile) as ((filepath: string, encoding: string) => Promise) -} - -export async function resolve (filepath, root, options) { - if (!path.extname(filepath)) { - filepath += options.extname - } - root = options.root.concat(root || []) - root = _.uniq(root) - const paths = root.map(root => path.resolve(root, filepath)) - return anySeries(paths, async path => { - try { - await fs.stat(path) - return path - } catch (e) { - e.message = `${e.code}: Failed to lookup ${filepath} in: ${root}` - throw e - } - }) -} - -export async function read (filepath): Promise { - return fs.readFile(filepath, 'utf8') -} diff --git a/src/parser/whitespace-ctrl.ts b/src/parser/whitespace-ctrl.ts index 65479ac01..27667b48f 100644 --- a/src/parser/whitespace-ctrl.ts +++ b/src/parser/whitespace-ctrl.ts @@ -1,11 +1,10 @@ -import { assign } from 'src/util/underscore' import DelimitedToken from 'src/parser/delimited-token' import Token from 'src/parser/token' import TagToken from 'src/parser/tag-token' import { LiquidOptions } from 'src/liquid-options' export default function whiteSpaceCtrl (tokens: Token[], options: LiquidOptions) { - options = assign({ greedy: true }, options) + options = { greedy: true, ...options } let inRaw = false tokens.forEach((token: Token, i: number) => { diff --git a/src/scope/scope.ts b/src/scope/scope.ts index e96ccf7d8..f7f72ea50 100644 --- a/src/scope/scope.ts +++ b/src/scope/scope.ts @@ -1,21 +1,16 @@ import * as _ from '../util/underscore' import * as lexical from '../parser/lexical' import assert from '../util/assert' -import { LiquidOptions, defaultOptions } from '../liquid-options' +import { NormalizedOptions, defaultOptions } from '../liquid-options' import BlockMode from './block-mode' export default class Scope { - opts: LiquidOptions + opts: NormalizedOptions contexts: Array blocks: object = {} blockMode: BlockMode = BlockMode.OUTPUT - constructor (ctx: object = {}, opts: LiquidOptions = defaultOptions) { - this.opts = _.assign({ - dynamicPartials: true, - strict_variables: false, - strict_filters: false, - root: [] - }, opts) + constructor (ctx: object = {}, opts: NormalizedOptions = defaultOptions) { + this.opts = { ...defaultOptions, ...opts } this.contexts = [ctx || {}] } getAll () { @@ -43,10 +38,10 @@ export default class Scope { scope = scope[key] }) } - unshift (ctx: object): any { + unshift (ctx: object) { return this.contexts.unshift(ctx) } - push (ctx: object): any { + push (ctx: object) { return this.contexts.push(ctx) } pop (ctx?: object): object { diff --git a/src/util/error.ts b/src/util/error.ts index ebb0112f9..42ffc9ba2 100644 --- a/src/util/error.ts +++ b/src/util/error.ts @@ -48,7 +48,7 @@ TokenizationError.prototype.constructor = TokenizationError export class ParseError extends LiquidError { constructor (err, token) { super(err, token) - _.assign(this, err) + this.message = err.message super.captureStackTrace(this) } } @@ -58,7 +58,7 @@ ParseError.prototype.constructor = ParseError export class RenderError extends LiquidError { constructor (err, tpl) { super(err, tpl.token) - _.assign(this, err) + this.message = err.message super.captureStackTrace(this) } } diff --git a/src/util/strftime.ts b/src/util/strftime.ts index bf4012a31..a4a90ce13 100644 --- a/src/util/strftime.ts +++ b/src/util/strftime.ts @@ -35,8 +35,6 @@ const _date = { return num + d.getDate() }, - // Startday is an integer of which day to start the week measuring from - // TODO: that comment was retarted. fix it. getWeekOfYear: function (d, startDay) { // Skip to startDay of this week const now = this.getDayOfYear(d) + (startDay - d.getDay()) diff --git a/test/e2e/express.ts b/test/e2e/express.ts index fb38ec579..3d0fa3260 100644 --- a/test/e2e/express.ts +++ b/test/e2e/express.ts @@ -1,16 +1,19 @@ import { expect } from 'chai' import * as request from 'supertest' import * as express from 'express' -import { mock, restore } from 'test/stub/mockfs' +import { resolve } from 'path' import Liquid from '../..' describe('express()', function () { - var app, engine + const root = resolve(__dirname, '../stub/root') + const views = resolve(__dirname, '../stub/views') + const partials = resolve(__dirname, '../stub/partials') + let app, engine beforeEach(function () { app = express() engine = new Liquid({ - root: '/root', + root, extname: '.html' }) @@ -24,14 +27,18 @@ describe('express()', function () { file: req.params.file })) }) - after(restore) - it('should render express views', function (done) { - mock({ '/views/name.html': 'My name is {{name}}.' }) - app.set('views', ['/views']) + it('should respect express views(array)', function (done) { + app.set('views', [views]) request(app).get('/name') .expect('My name is harttle.') .expect(200, done) }) + it('should respect express views(string)', function (done) { + app.set('views', views) + request(app).get('/include/bar') + .expect('bar') + .expect(200, done) + }) it('should pass error when file not found', function (done) { const view = { root: [] @@ -49,41 +56,13 @@ describe('express()', function () { }) }) it('should respect root option when lookup', function (done) { - mock({ - '/root/foo.html': 'foo', - '/views/include.html': '{% include file %}' - }) - app.set('views', ['/views']) + app.set('views', [views]) request(app).get('/include/foo') .expect('foo') .expect(200, done) }) it('should respect express views (Array) when lookup', function (done) { - mock({ - '/views/include.html': '{% include file %}', - '/partials/bar.html': 'bar' - }) - app.set('views', ['/views', '/partials']) - request(app).get('/include/bar') - .expect('bar') - .expect(200, done) - }) - it('should respect express views (String) when lookup', function (done) { - mock({ - '/views/include.html': '{% include file %}', - '/views/bar.html': 'bar' - }) - app.set('views', '/views') - request(app).get('/include/bar') - .expect('bar') - .expect(200, done) - }) - it('should respect express views (undefined) when lookup', function (done) { - const files = {} - files[process.cwd() + '/views/include.html'] = '{% include file %}' - files[process.cwd() + '/views/bar.html'] = 'bar' - mock(files) - + app.set('views', [views, partials]) request(app).get('/include/bar') .expect('bar') .expect(200, done) diff --git a/test/e2e/render-file.ts b/test/e2e/render-file.ts index 3c7da317b..699ade7b0 100644 --- a/test/e2e/render-file.ts +++ b/test/e2e/render-file.ts @@ -1,61 +1,53 @@ -import { expect } from 'chai' import Liquid from '../..' -import { mock, restore } from '../stub/mockfs' +import { expect, use } from 'chai' +import * as chaiAsPromised from 'chai-as-promised' +import { resolve } from 'path' + +use(chaiAsPromised) describe('#renderFile()', function () { - var engine + const root = resolve(__dirname, '../stub/root') + const views = resolve(__dirname, '../stub/views') + let engine beforeEach(function () { engine = new Liquid({ - root: '/root/', + root, extname: '.html' }) - mock({ - '/root/files/bar': 'bar', - '/root/files/foo.html': 'foo', - '/root/files/name.html': 'My name is {{name}}.', - '/un-readable.html': { mode: '0000' } - }) }) - afterEach(restore) it('should render file', async function () { - const html = await engine.renderFile('/root/files/foo.html', {}) + const html = await engine.renderFile(resolve(root, 'foo.html'), {}) return expect(html).to.equal('foo') }) it('should find files without extname', async function () { - var engine = new Liquid({ root: '/root' }) - const html = await engine.renderFile('/root/files/bar', {}) + var engine = new Liquid({ root }) + const html = await engine.renderFile(resolve(root, 'bar'), {}) return expect(html).to.equal('bar') }) it('should accept relative path', async function () { - const html = await engine.renderFile('files/foo.html') + const html = await engine.renderFile('foo.html') return expect(html).to.equal('foo') }) - it('should resolve array as root', async function () { - engine = new Liquid({ - root: ['/boo', '/root/'], - extname: '.html' - }) - const html = await engine.renderFile('files/foo.html') - return expect(html).to.equal('foo') - }) - it('should default root to cwd', async function () { - var files = {} - files[process.cwd() + '/foo.html'] = 'FOO' - mock(files) - + it('should traverse root array', async function () { engine = new Liquid({ + root: ['/boo', root], extname: '.html' }) const html = await engine.renderFile('foo.html') - return expect(html).to.equal('FOO') + return expect(html).to.equal('foo') + }) + it('should default root to cwd', async function () { + engine = new Liquid() + const html = await engine.renderFile('package.json') + return expect(html).to.contain('"name": "liquidjs"') }) it('should render file with context', async function () { - const html = await engine.renderFile('/root/files/name.html', { name: 'harttle' }) + const html = await engine.renderFile(resolve(views, 'name.html'), { name: 'harttle' }) return expect(html).to.equal('My name is harttle.') }) it('should use default extname', async function () { - const html = await engine.renderFile('files/name', { name: 'harttle' }) - return expect(html).to.equal('My name is harttle.') + const html = await engine.renderFile(resolve(root, 'foo')) + return expect(html).to.equal('foo') }) it('should throw with lookup list when file not exist', function () { engine = new Liquid({ @@ -63,10 +55,6 @@ describe('#renderFile()', function () { extname: '.html' }) return expect(engine.renderFile('/not/exist.html')).to - .be.rejectedWith(/failed to lookup \/not\/exist.html in: \/boo,\/root\//i) - }) - it('should throw when file not readable', function () { - return expect(engine.renderFile('/un-readable.html')).to - .be.rejectedWith(/EACCES/) + .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) }) }) diff --git a/test/stub/mockfs.ts b/test/stub/mockfs.ts index 2ad63f01f..4668bbd60 100644 --- a/test/stub/mockfs.ts +++ b/test/stub/mockfs.ts @@ -1,31 +1,34 @@ -import { fs } from 'src/parser/template' import { isString } from 'src/util/underscore' - -const readFile = fs.readFile -const stat = fs.stat +import fs from 'src/fs' type fileDescriptor = { mode: string, content: string } -export function mock (files: { [path: string]: (string | fileDescriptor) }) { - for (const [key, val] of Object.entries(files)) { +let files: { [path: string]: fileDescriptor } = {} +const readFile = fs.readFile +const exists = fs.exists + +export function mock (options: { [path: string]: (string | fileDescriptor) }) { + for (const [key, val] of Object.entries(options)) { files[key] = isString(val) ? { mode: '33188', content: val as string } - : val + : val as fileDescriptor } fs.readFile = async function (path) { - const file = files[path] as fileDescriptor + console.log('mock fs read called', path) + const file = files[path] if (file === undefined) throw new Error('ENOENT') - if (file.mode === '000') throw new Error('EACCES') + if (file.mode === '0000') throw new Error('EACCES') return file.content } - fs.stat = async function (path) { - const file = files[path] as fileDescriptor - if (file === undefined) throw new Error('ENOENT') - return file + + fs.exists = async function (path) { + console.log('mock fs exists called', path) + return !!files[path] } } export function restore () { + files = {} fs.readFile = readFile - fs.stat = stat + fs.exists = exists } diff --git a/test/stub/partials/bar.html b/test/stub/partials/bar.html new file mode 100644 index 000000000..ba0e162e1 --- /dev/null +++ b/test/stub/partials/bar.html @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/test/stub/render.ts b/test/stub/render.ts new file mode 100644 index 000000000..fc8f152ea --- /dev/null +++ b/test/stub/render.ts @@ -0,0 +1,18 @@ +import Liquid from 'src/liquid' +import { expect } from 'chai' + +export const liquid = new Liquid() + +export const ctx = { + date: new Date(), + foo: 'bar', + arr: [-2, 'a'], + obj: { foo: 'bar' }, + func: function () {}, + posts: [{ category: 'foo' }, { category: 'bar' }] +} + +export async function test (src, dst) { + const html = await liquid.parseAndRender(src, ctx) + return expect(html).to.equal(dst) +} diff --git a/test/stub/root/bar b/test/stub/root/bar new file mode 100644 index 000000000..ba0e162e1 --- /dev/null +++ b/test/stub/root/bar @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/test/stub/root/foo.html b/test/stub/root/foo.html new file mode 100644 index 000000000..191028156 --- /dev/null +++ b/test/stub/root/foo.html @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/test/stub/views/bar.html b/test/stub/views/bar.html new file mode 100644 index 000000000..ba0e162e1 --- /dev/null +++ b/test/stub/views/bar.html @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/test/stub/views/include.html b/test/stub/views/include.html new file mode 100644 index 000000000..43ed74df6 --- /dev/null +++ b/test/stub/views/include.html @@ -0,0 +1 @@ +{% include file %} \ No newline at end of file diff --git a/test/stub/views/name.html b/test/stub/views/name.html new file mode 100644 index 000000000..6de4c619d --- /dev/null +++ b/test/stub/views/name.html @@ -0,0 +1 @@ +My name is {{name}}. \ No newline at end of file diff --git a/test/unit/builtin/filters/array.ts b/test/unit/builtin/filters/array.ts new file mode 100644 index 000000000..625060511 --- /dev/null +++ b/test/unit/builtin/filters/array.ts @@ -0,0 +1,66 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/array', function () { + describe('join', function () { + it('should support join', function () { + const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + + '{{ beatles | join: " and " }}' + return test(src, 'John and Paul and George and Ringo') + }) + it('should default separator to space', function () { + const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + + '{{ beatles | join }}' + return test(src, 'John Paul George Ringo') + }) + }) + it('should support split/last', function () { + const src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' + + '{{ my_array|last }}' + return test(src, 'tiger') + }) + it('should support map', function () { + return test('{{posts | map: "category"}}', '["foo","bar"]') + }) + it('should support reverse', function () { + return test('{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}', + '.moT rojaM ot lortnoc dnuorG') + }) + describe('size', function () { + it('should return string length', + () => test('{{ "Ground control to Major Tom." | size }}', '28')) + it('should return array size', function () { + return test('{% assign my_array = "apples, oranges, peaches, plums"' + + ' | split: ", " %}{{ my_array | size }}', + '4') + }) + it('should also be used with dot notation - string', + () => test('{% assign my_string = "Ground control to Major Tom." %}{{ my_string.size }}', '28')) + it('should also be used with dot notation - array', + () => test('{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}{{ my_array.size }}', '4')) + }) + describe('slice', function () { + it('should slice first char by 0', () => test('{{ "Liquid" | slice: 0 }}', 'L')) + it('should slice third char by 2', () => test('{{ "Liquid" | slice: 2 }}', 'q')) + it('should slice substr by 2,5', () => test('{{ "Liquid" | slice: 2, 5 }}', 'quid')) + it('should slice substr by -3,2', () => test('{{ "Liquid" | slice: -3, 2 }}', 'ui')) + it('should support array', () => test('{{ "1,2,3,4" | split: "," | slice: 1,2 | join }}', '2 3')) + }) + it('should support sort', function () { + return test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' + + ' | split: ", " %}' + + '{{ my_array | sort | join: ", " }}', + 'Sally Snake, giraffe, octopus, zebra') + }) + describe('uniq', function () { + it('should uniq string list', function () { + return test( + '{% assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}' + + '{{ my_array | uniq | join: ", " }}', + 'ants, bugs, bees' + ) + }) + it('should uniq falsy value', function () { + return test('{{"" | uniq | join: ","}}', '') + }) + }) +}) diff --git a/test/unit/builtin/filters/date.ts b/test/unit/builtin/filters/date.ts new file mode 100644 index 000000000..0fbd6638d --- /dev/null +++ b/test/unit/builtin/filters/date.ts @@ -0,0 +1,20 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/date', function () { + it('should support date: %a %b %d %Y', function () { + const str = ctx.date.toDateString() + return test('{{ date | date:"%a %b %d %Y"}}', str) + }) + it('should create a new Date when given "now"', function () { + return test('{{ "now" | date: "%Y"}}', (new Date()).getFullYear().toString()) + }) + it('should parse as Date when given UTC string', function () { + return test('{{ "1991-02-22T00:00:00" | date: "%Y"}}', '1991') + }) + it('should render string as string if not valid', function () { + return test('{{ "foo" | date: "%Y"}}', 'foo') + }) + it('should render object as string if not valid', function () { + return test('{{ obj | date: "%Y"}}', '{"foo":"bar"}') + }) +}) diff --git a/test/unit/builtin/filters/html.ts b/test/unit/builtin/filters/html.ts new file mode 100644 index 000000000..846329015 --- /dev/null +++ b/test/unit/builtin/filters/html.ts @@ -0,0 +1,43 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/html', function () { + describe('escape', function () { + it('should escape \' and &', function () { + return test('{{ "Have you read \'James & the Giant Peach\'?" | escape }}', + 'Have you read 'James & the Giant Peach'?') + }) + it('should escape normal string', function () { + return test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara') + }) + it('should escape function', function () { + return test('{{ func | escape }}', 'function () { }') + }) + }) + describe('escape_once', function () { + it('should do escape', () => + test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')) + it('should not escape twice', + () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')) + }) + describe('strip_html', function () { + it('should strip all tags', function () { + return test('{{ "Have you read Ulysses?" | strip_html }}', + 'Have you read Ulysses?') + }) + it('should strip all comment tags', function () { + return test('{{ "Ulysses?" | strip_html }}', + 'Ulysses?') + }) + it('should strip all style tags and their contents', function () { + return test('{{ "Ulysses?" | strip_html }}', + 'Ulysses?') + }) + it('should strip all scripts tags and their contents', function () { + return test('{{ "Ulysses?" | strip_html }}', + 'Ulysses?') + }) + it('should strip until empty', function () { + return test('{{"

< p >

" | strip_html }}', '') + }) + }) +}) diff --git a/test/unit/builtin/filters/math.ts b/test/unit/builtin/filters/math.ts new file mode 100644 index 000000000..942302e67 --- /dev/null +++ b/test/unit/builtin/filters/math.ts @@ -0,0 +1,64 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/math', function () { + describe('abs', function () { + it('should return 3 for -3', () => test('{{ -3 | abs }}', '3')) + it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2')) + it('should return convert string', () => test('{{ "-3" | abs }}', '3')) + }) + describe('ceil', function () { + it('should return "2" for 1.2', () => test('{{ 1.2 | ceil }}', '2')) + it('should return "2" for 2.0', () => test('{{ 2.0 | ceil }}', '2')) + it('should return "4" for 3.5', () => test('{{ "3.5" | ceil }}', '4')) + it('should return "184" for 183.357', () => test('{{ 183.357 | ceil }}', '184')) + }) + describe('divided_by', function () { + it('should return 2 for 4,2', () => test('{{4 | divided_by: 2}}', '2')) + it('should return 4 for 16,4', () => test('{{16 | divided_by: 4}}', '4')) + it('should return 1 for 5,3', () => test('{{5 | divided_by: 3}}', (5 / 3).toString())) + it('should convert string to number', () => test('{{"6" | divided_by: "3"}}', '2')) + }) + describe('floor', function () { + it('should return "1" for 1.2', () => test('{{ 1.2 | floor }}', '1')) + it('should return "2" for 2.0', () => test('{{ 2.0 | floor }}', '2')) + it('should return "183" for 183.357', () => test('{{ 183.357 | floor }}', '183')) + it('should return "3" for 3.5', () => test('{{ "3.5" | floor }}', '3')) + }) + describe('minus', function () { + it('should return "2" for 4,2', () => test('{{ 4 | minus: 2 }}', '2')) + it('should return "12" for 16,4', () => test('{{ 16 | minus: 4 }}', '12')) + it('should return "171.357" for 183.357,12', + () => test('{{ 183.357 | minus: 12 }}', '171.357')) + it('should convert first arg as number', () => test('{{ "4" | minus: 1 }}', '3')) + it('should convert both args as number', () => test('{{ "4" | minus: "1" }}', '3')) + }) + describe('modulo', function () { + it('should return "1" for 3,2', () => test('{{ 3 | modulo: 2 }}', '1')) + it('should return "3" for 24,7', () => test('{{ 24 | modulo: 7 }}', '3')) + it('should return "3.357" for 183.357,12', + () => test('{{ 183.357 | modulo: 12 }}', '3.357')) + it('should convert string', () => test('{{ "24" | modulo: "7" }}', '3')) + }) + describe('plus', function () { + it('should return "6" for 4,2', () => test('{{ 4 | plus: 2 }}', '6')) + it('should return "20" for 16,4', () => test('{{ 16 | plus: 4 }}', '20')) + it('should return "195.357" for 183.357,12', + () => test('{{ 183.357 | plus: 12 }}', '195.357')) + it('should convert first arg as number', () => test('{{ "4" | plus: 2 }}', '6')) + it('should convert both args as number', () => test('{{ "4" | plus: "2" }}', '6')) + }) + describe('round', function () { + it('should return "1" for 1.2', () => test('{{1.2|round}}', '1')) + it('should return "3" for 2.7', () => test('{{2.7|round}}', '3')) + it('should return "183.36" for 183.357,2', + () => test('{{183.357|round: 2}}', '183.36')) + it('should convert string to number', () => test('{{"2.7"|round}}', '3')) + }) + describe('times', function () { + it('should return "6" for 3,2', () => test('{{ 3 | times: 2 }}', '6')) + it('should return "168" for 24,7', () => test('{{ 24 | times: 7 }}', '168')) + it('should return "2200.284" for 183.357,12', + () => test('{{ 183.357 | times: 12 }}', '2200.284')) + it('should convert string to number', () => test('{{ "24" | times: "7" }}', '168')) + }) +}) diff --git a/test/unit/builtin/filters/object.ts b/test/unit/builtin/filters/object.ts new file mode 100644 index 000000000..671edf27f --- /dev/null +++ b/test/unit/builtin/filters/object.ts @@ -0,0 +1,8 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/object', function () { + describe('default', function () { + it('should use default when falsy', () => test('{{false |default: "a"}}', 'a')) + it('should not use default when truthy', () => test('{{true |default: "a"}}', 'true')) + }) +}) diff --git a/test/unit/builtin/filters/string.ts b/test/unit/builtin/filters/string.ts new file mode 100644 index 000000000..c8baf3435 --- /dev/null +++ b/test/unit/builtin/filters/string.ts @@ -0,0 +1,161 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/string', function () { + describe('append', function () { + it('should return "-3abc" for -3, "abc"', + () => test('{{ -3 | append: "abc" }}', '-3abc')) + it('should return "abar" for "a",foo', () => test('{{ "a" | append: foo }}', 'abar')) + }) + describe('capitalize', function () { + it('should capitalize first', () => test('{{ "i am good" | capitalize }}', 'I am good')) + }) + describe('concat', function () { + it('should concat arrays', () => test(` + {%- assign fruits = "apples, oranges, peaches" | split: ", " -%} + {%- assign vegetables = "carrots, turnips, potatoes" | split: ", " -%} + + {%- assign everything = fruits | concat: vegetables -%} + + {%- for item in everything -%} + - {{ item }} + {% endfor -%}`, `- apples + - oranges + - peaches + - carrots + - turnips + - potatoes + `)) + it('should support chained concat', () => test(` + {%- assign fruits = "apples, oranges, peaches" | split: ", " -%} + {%- assign vegetables = "carrots, turnips, potatoes" | split: ", " -%} + {%- assign furniture = "chairs, tables, shelves" | split: ", " -%} + {%- assign everything = fruits | concat: vegetables | concat: furniture -%} + + {%- for item in everything -%} + - {{ item }} + {% endfor -%}`, `- apples + - oranges + - peaches + - carrots + - turnips + - potatoes + - chairs + - tables + - shelves + `)) + }) + describe('downcase', function () { + it('should return "parker moore" for "Parker Moore"', + () => test('{{ "Parker Moore" | downcase }}', 'parker moore')) + it('should return "apple" for "apple"', + () => test('{{ "apple" | downcase }}', 'apple')) + }) + describe('split', function () { + it('should support split/first', function () { + const src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + + '{{ my_array | first }}' + return test(src, 'apples') + }) + }) + it('should support upcase', () => test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE')) + it('should support lstrip', function () { + const src = '{{ " So much room for activities! " | lstrip }}' + return test(src, 'So much room for activities! ') + }) + it('should support string_with_newlines', function () { + const src = '{% capture string_with_newlines %}\n' + + 'Hello\n' + + 'there\n' + + '{% endcapture %}' + + '{{ string_with_newlines | newline_to_br }}' + const dst = '
' + + 'Hello
' + + 'there
' + return test(src, dst) + }) + it('should support prepend', function () { + return test('{% assign url = "liquidmarkup.com" %}' + + '{{ "/index.html" | prepend: url }}', + 'liquidmarkup.com/index.html') + }) + it('should support remove', function () { + return test('{{ "I strained to see the train through the rain" | remove: "rain" }}', + 'I sted to see the t through the ') + }) + it('should support remove_first', function () { + return test('{{ "I strained to see the train through the rain" | remove_first: "rain" }}', + 'I sted to see the train through the rain') + }) + it('should support replace', function () { + return test('{{ "Take my protein pills and put my helmet on" | replace: "my", "your" }}', + 'Take your protein pills and put your helmet on') + }) + it('should support replace_first', function () { + return test('{% assign my_string = "Take my protein pills and put my helmet on" %}\n' + + '{{ my_string | replace_first: "my", "your" }}', + '\nTake your protein pills and put my helmet on') + }) + it('should support rstrip', function () { + return test('{{ " So much room for activities! " | rstrip }}', + ' So much room for activities!') + }) + it('should support split', function () { + return test('{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + + '{% for member in beatles %}' + + '{{ member }} ' + + '{% endfor %}', + 'John Paul George Ringo ') + }) + it('should support strip', function () { + return test('{{ " So much room for activities! " | strip }}', + 'So much room for activities!') + }) + it('should support strip_newlines', function () { + return test('{% capture string_with_newlines %}\n' + + 'Hello\nthere\n{% endcapture %}' + + '{{ string_with_newlines | strip_newlines }}', + 'Hellothere') + }) + describe('truncate', function () { + it('should truncate when string too long', function () { + return test('{{ "Ground control to Major Tom." | truncate: 20 }}', + 'Ground control to...') + }) + it('should not truncate when string not long enough', function () { + return test('{{ "Ground control to Major Tom." | truncate: 80 }}', + 'Ground control to Major Tom.') + }) + it('should truncate with custom ellipsis', function () { + return test('{{ "Ground control to Major Tom." | truncate: 25,", and so on" }}', + 'Ground control, and so on') + }) + it('should truncate with empty custom ellipsis', function () { + return test('{{ "Ground control to Major Tom." | truncate: 20, "" }}', + 'Ground control to Ma') + }) + it('should not truncate when short enough', function () { + return test('{{ "12345" | truncate: 5 }}', '12345') + }) + it('should default to 16', function () { + return test('{{ "1234567890abcdefghi" | truncate }}', '1234567890abc...') + }) + }) + describe('truncatewords', function () { + it('should truncate when too many words', function () { + return test('{{ "Ground control to Major Tom." | truncatewords: 3 }}', + 'Ground control to...') + }) + it('should not truncate when not enough words', function () { + return test('{{ "Ground control to Major Tom." | truncatewords: 8 }}', + 'Ground control to Major Tom.') + }) + it('should truncate with custom ellipsis', function () { + return test('{{ "Ground control to Major Tom." | truncatewords: 3, "--" }}', + 'Ground control to--') + }) + it('should truncate with empty custom ellipsis', function () { + return test('{{ "Ground control to Major Tom." | truncatewords: 3, "" }}', + 'Ground control to') + }) + }) +}) diff --git a/test/unit/builtin/filters/url.ts b/test/unit/builtin/filters/url.ts new file mode 100644 index 000000000..1c791c8db --- /dev/null +++ b/test/unit/builtin/filters/url.ts @@ -0,0 +1,15 @@ +import { test, ctx, liquid } from 'test/stub/render' + +describe('filters/url', function () { + describe('url_decode', function () { + it('should decode %xx and +', + () => test('{{ "%27Stop%21%27+said+Fred" | url_decode }}', "'Stop!' said Fred")) + }) + + describe('url_encode', function () { + it('should encode @', + () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')) + it('should encode ', + () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro+Takara')) + }) +}) diff --git a/test/unit/tags/assign.ts b/test/unit/builtin/tags/assign.ts similarity index 100% rename from test/unit/tags/assign.ts rename to test/unit/builtin/tags/assign.ts diff --git a/test/unit/tags/capture.ts b/test/unit/builtin/tags/capture.ts similarity index 100% rename from test/unit/tags/capture.ts rename to test/unit/builtin/tags/capture.ts diff --git a/test/unit/tags/case.ts b/test/unit/builtin/tags/case.ts similarity index 100% rename from test/unit/tags/case.ts rename to test/unit/builtin/tags/case.ts diff --git a/test/unit/tags/comment.ts b/test/unit/builtin/tags/comment.ts similarity index 100% rename from test/unit/tags/comment.ts rename to test/unit/builtin/tags/comment.ts diff --git a/test/unit/tags/cycle.ts b/test/unit/builtin/tags/cycle.ts similarity index 100% rename from test/unit/tags/cycle.ts rename to test/unit/builtin/tags/cycle.ts diff --git a/test/unit/tags/decrement.ts b/test/unit/builtin/tags/decrement.ts similarity index 100% rename from test/unit/tags/decrement.ts rename to test/unit/builtin/tags/decrement.ts diff --git a/test/unit/tags/for.ts b/test/unit/builtin/tags/for.ts similarity index 100% rename from test/unit/tags/for.ts rename to test/unit/builtin/tags/for.ts diff --git a/test/unit/tags/if.ts b/test/unit/builtin/tags/if.ts similarity index 100% rename from test/unit/tags/if.ts rename to test/unit/builtin/tags/if.ts diff --git a/test/unit/tags/include.ts b/test/unit/builtin/tags/include.ts similarity index 100% rename from test/unit/tags/include.ts rename to test/unit/builtin/tags/include.ts diff --git a/test/unit/tags/increment.ts b/test/unit/builtin/tags/increment.ts similarity index 100% rename from test/unit/tags/increment.ts rename to test/unit/builtin/tags/increment.ts diff --git a/test/unit/tags/layout.ts b/test/unit/builtin/tags/layout.ts similarity index 100% rename from test/unit/tags/layout.ts rename to test/unit/builtin/tags/layout.ts diff --git a/test/unit/tags/raw.ts b/test/unit/builtin/tags/raw.ts similarity index 100% rename from test/unit/tags/raw.ts rename to test/unit/builtin/tags/raw.ts diff --git a/test/unit/tags/tablerow.ts b/test/unit/builtin/tags/tablerow.ts similarity index 100% rename from test/unit/tags/tablerow.ts rename to test/unit/builtin/tags/tablerow.ts diff --git a/test/unit/tags/unless.ts b/test/unit/builtin/tags/unless.ts similarity index 100% rename from test/unit/tags/unless.ts rename to test/unit/builtin/tags/unless.ts diff --git a/test/unit/filters.ts b/test/unit/filters.ts deleted file mode 100644 index daedd8cb5..000000000 --- a/test/unit/filters.ts +++ /dev/null @@ -1,425 +0,0 @@ -import { expect } from 'chai' -import Liquid from '../../src/liquid' - -const ctx = { - date: new Date(), - foo: 'bar', - arr: [-2, 'a'], - obj: { - foo: 'bar' - }, - func: function () {}, - posts: [{ - category: 'foo' - }, { - category: 'bar' - }] -} -let liquid - -async function test (src, dst) { - const html = await liquid.parseAndRender(src, ctx) - return expect(html).to.equal(dst) -} - -describe('filters', function () { - before(() => { liquid = new Liquid() }) - describe('abs', function () { - it('should return 3 for -3', () => test('{{ -3 | abs }}', '3')) - it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2')) - it('should return convert string', () => test('{{ "-3" | abs }}', '3')) - }) - - describe('append', function () { - it('should return "-3abc" for -3, "abc"', - () => test('{{ -3 | append: "abc" }}', '-3abc')) - it('should return "abar" for "a",foo', () => test('{{ "a" | append: foo }}', 'abar')) - }) - - it('should support capitalize', () => test('{{ "i am good" | capitalize }}', 'I am good')) - - describe('ceil', function () { - it('should return "2" for 1.2', () => test('{{ 1.2 | ceil }}', '2')) - it('should return "2" for 2.0', () => test('{{ 2.0 | ceil }}', '2')) - it('should return "4" for 3.5', () => test('{{ "3.5" | ceil }}', '4')) - it('should return "184" for 183.357', () => test('{{ 183.357 | ceil }}', '184')) - }) - - describe('concat', function () { - it('should concat arrays', () => test(` - {%- assign fruits = "apples, oranges, peaches" | split: ", " -%} - {%- assign vegetables = "carrots, turnips, potatoes" | split: ", " -%} - - {%- assign everything = fruits | concat: vegetables -%} - - {%- for item in everything -%} - - {{ item }} - {% endfor -%}`, `- apples - - oranges - - peaches - - carrots - - turnips - - potatoes - `)) - it('should support chained concat', () => test(` - {%- assign fruits = "apples, oranges, peaches" | split: ", " -%} - {%- assign vegetables = "carrots, turnips, potatoes" | split: ", " -%} - {%- assign furniture = "chairs, tables, shelves" | split: ", " -%} - {%- assign everything = fruits | concat: vegetables | concat: furniture -%} - - {%- for item in everything -%} - - {{ item }} - {% endfor -%}`, `- apples - - oranges - - peaches - - carrots - - turnips - - potatoes - - chairs - - tables - - shelves - `)) - }) - - describe('date', function () { - it('should support date: %a %b %d %Y', function () { - const str = ctx.date.toDateString() - return test('{{ date | date:"%a %b %d %Y"}}', str) - }) - it('should create a new Date when given "now"', function () { - return test('{{ "now" | date: "%Y"}}', (new Date()).getFullYear().toString()) - }) - it('should parse as Date when given UTC string', function () { - return test('{{ "1991-02-22T00:00:00" | date: "%Y"}}', '1991') - }) - it('should render string as string if not valid', function () { - return test('{{ "foo" | date: "%Y"}}', 'foo') - }) - it('should render object as string if not valid', function () { - return test('{{ obj | date: "%Y"}}', '{"foo":"bar"}') - }) - }) - - describe('default', function () { - it('should use default when falsy', () => test('{{false |default: "a"}}', 'a')) - it('should not use default when truthy', () => test('{{true |default: "a"}}', 'true')) - }) - - describe('divided_by', function () { - it('should return 2 for 4,2', () => test('{{4 | divided_by: 2}}', '2')) - it('should return 4 for 16,4', () => test('{{16 | divided_by: 4}}', '4')) - it('should return 1 for 5,3', () => test('{{5 | divided_by: 3}}', (5 / 3).toString())) - it('should convert string to number', () => test('{{"6" | divided_by: "3"}}', '2')) - }) - - describe('downcase', function () { - it('should return "parker moore" for "Parker Moore"', - () => test('{{ "Parker Moore" | downcase }}', 'parker moore')) - it('should return "apple" for "apple"', - () => test('{{ "apple" | downcase }}', 'apple')) - }) - - describe('escape', function () { - it('should escape \' and &', function () { - return test('{{ "Have you read \'James & the Giant Peach\'?" | escape }}', - 'Have you read 'James & the Giant Peach'?') - }) - it('should escape normal string', function () { - return test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara') - }) - it('should escape function', function () { - return test('{{ func | escape }}', 'function () { }') - }) - }) - - describe('escape_once', function () { - it('should do escape', () => - test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')) - it('should not escape twice', - () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')) - }) - - it('should support split/first', function () { - const src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + - '{{ my_array | first }}' - return test(src, 'apples') - }) - - describe('floor', function () { - it('should return "1" for 1.2', () => test('{{ 1.2 | floor }}', '1')) - it('should return "2" for 2.0', () => test('{{ 2.0 | floor }}', '2')) - it('should return "183" for 183.357', () => test('{{ 183.357 | floor }}', '183')) - it('should return "3" for 3.5', () => test('{{ "3.5" | floor }}', '3')) - }) - - describe('join', function () { - it('should support join', function () { - const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + - '{{ beatles | join: " and " }}' - return test(src, 'John and Paul and George and Ringo') - }) - it('should default separator to space', function () { - const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + - '{{ beatles | join }}' - return test(src, 'John Paul George Ringo') - }) - }) - - it('should support split/last', function () { - const src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' + - '{{ my_array|last }}' - return test(src, 'tiger') - }) - - it('should support lstrip', function () { - const src = '{{ " So much room for activities! " | lstrip }}' - return test(src, 'So much room for activities! ') - }) - - it('should support map', function () { - return test('{{posts | map: "category"}}', '["foo","bar"]') - }) - - describe('minus', function () { - it('should return "2" for 4,2', () => test('{{ 4 | minus: 2 }}', '2')) - it('should return "12" for 16,4', () => test('{{ 16 | minus: 4 }}', '12')) - it('should return "171.357" for 183.357,12', - () => test('{{ 183.357 | minus: 12 }}', '171.357')) - it('should convert first arg as number', () => test('{{ "4" | minus: 1 }}', '3')) - it('should convert both args as number', () => test('{{ "4" | minus: "1" }}', '3')) - }) - - describe('modulo', function () { - it('should return "1" for 3,2', () => test('{{ 3 | modulo: 2 }}', '1')) - it('should return "3" for 24,7', () => test('{{ 24 | modulo: 7 }}', '3')) - it('should return "3.357" for 183.357,12', - () => test('{{ 183.357 | modulo: 12 }}', '3.357')) - it('should convert string', () => test('{{ "24" | modulo: "7" }}', '3')) - }) - - it('should support string_with_newlines', function () { - const src = '{% capture string_with_newlines %}\n' + - 'Hello\n' + - 'there\n' + - '{% endcapture %}' + - '{{ string_with_newlines | newline_to_br }}' - const dst = '
' + - 'Hello
' + - 'there
' - return test(src, dst) - }) - - describe('plus', function () { - it('should return "6" for 4,2', () => test('{{ 4 | plus: 2 }}', '6')) - it('should return "20" for 16,4', () => test('{{ 16 | plus: 4 }}', '20')) - it('should return "195.357" for 183.357,12', - () => test('{{ 183.357 | plus: 12 }}', '195.357')) - it('should convert first arg as number', () => test('{{ "4" | plus: 2 }}', '6')) - it('should convert both args as number', () => test('{{ "4" | plus: "2" }}', '6')) - }) - - it('should support prepend', function () { - return test('{% assign url = "liquidmarkup.com" %}' + - '{{ "/index.html" | prepend: url }}', - 'liquidmarkup.com/index.html') - }) - - it('should support remove', function () { - return test('{{ "I strained to see the train through the rain" | remove: "rain" }}', - 'I sted to see the t through the ') - }) - - it('should support remove_first', function () { - return test('{{ "I strained to see the train through the rain" | remove_first: "rain" }}', - 'I sted to see the train through the rain') - }) - - it('should support replace', function () { - return test('{{ "Take my protein pills and put my helmet on" | replace: "my", "your" }}', - 'Take your protein pills and put your helmet on') - }) - - it('should support replace_first', function () { - return test('{% assign my_string = "Take my protein pills and put my helmet on" %}\n' + - '{{ my_string | replace_first: "my", "your" }}', - '\nTake your protein pills and put my helmet on') - }) - - it('should support reverse', function () { - return test('{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}', - '.moT rojaM ot lortnoc dnuorG') - }) - - describe('round', function () { - it('should return "1" for 1.2', () => test('{{1.2|round}}', '1')) - it('should return "3" for 2.7', () => test('{{2.7|round}}', '3')) - it('should return "183.36" for 183.357,2', - () => test('{{183.357|round: 2}}', '183.36')) - it('should convert string to number', () => test('{{"2.7"|round}}', '3')) - }) - - it('should support rstrip', function () { - return test('{{ " So much room for activities! " | rstrip }}', - ' So much room for activities!') - }) - - describe('size', function () { - it('should return string length', - () => test('{{ "Ground control to Major Tom." | size }}', '28')) - it('should return array size', function () { - return test('{% assign my_array = "apples, oranges, peaches, plums"' + - ' | split: ", " %}{{ my_array | size }}', - '4') - }) - it('should also be used with dot notation - string', - () => test('{% assign my_string = "Ground control to Major Tom." %}{{ my_string.size }}', '28')) - it('should also be used with dot notation - array', - () => test('{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}{{ my_array.size }}', '4')) - }) - - describe('slice', function () { - it('should slice first char by 0', () => test('{{ "Liquid" | slice: 0 }}', 'L')) - it('should slice third char by 2', () => test('{{ "Liquid" | slice: 2 }}', 'q')) - it('should slice substr by 2,5', () => test('{{ "Liquid" | slice: 2, 5 }}', 'quid')) - it('should slice substr by -3,2', () => test('{{ "Liquid" | slice: -3, 2 }}', 'ui')) - it('should support array', () => test('{{ "1,2,3,4" | split: "," | slice: 1,2 | join }}', '2 3')) - }) - - it('should support sort', function () { - return test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' + - ' | split: ", " %}' + - '{{ my_array | sort | join: ", " }}', - 'Sally Snake, giraffe, octopus, zebra') - }) - - it('should support split', function () { - return test('{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + - '{% for member in beatles %}' + - '{{ member }} ' + - '{% endfor %}', - 'John Paul George Ringo ') - }) - - it('should support strip', function () { - return test('{{ " So much room for activities! " | strip }}', - 'So much room for activities!') - }) - - describe('strip_html', function () { - it('should strip all tags', function () { - return test('{{ "Have you read Ulysses?" | strip_html }}', - 'Have you read Ulysses?') - }) - it('should strip all comment tags', function () { - return test('{{ "Ulysses?" | strip_html }}', - 'Ulysses?') - }) - it('should strip all style tags and their contents', function () { - return test('{{ "Ulysses?" | strip_html }}', - 'Ulysses?') - }) - it('should strip all scripts tags and their contents', function () { - return test('{{ "Ulysses?" | strip_html }}', - 'Ulysses?') - }) - it('should strip until empty', function () { - return test('{{"

< p >

" | strip_html }}', '') - }) - }) - - it('should support strip_newlines', function () { - return test('{% capture string_with_newlines %}\n' + - 'Hello\nthere\n{% endcapture %}' + - '{{ string_with_newlines | strip_newlines }}', - 'Hellothere') - }) - - describe('times', function () { - it('should return "6" for 3,2', () => test('{{ 3 | times: 2 }}', '6')) - it('should return "168" for 24,7', () => test('{{ 24 | times: 7 }}', '168')) - it('should return "2200.284" for 183.357,12', - () => test('{{ 183.357 | times: 12 }}', '2200.284')) - it('should convert string to number', () => test('{{ "24" | times: "7" }}', '168')) - }) - - describe('truncate', function () { - it('should truncate when string too long', function () { - return test('{{ "Ground control to Major Tom." | truncate: 20 }}', - 'Ground control to...') - }) - it('should not truncate when string not long enough', function () { - return test('{{ "Ground control to Major Tom." | truncate: 80 }}', - 'Ground control to Major Tom.') - }) - it('should truncate with custom ellipsis', function () { - return test('{{ "Ground control to Major Tom." | truncate: 25,", and so on" }}', - 'Ground control, and so on') - }) - it('should truncate with empty custom ellipsis', function () { - return test('{{ "Ground control to Major Tom." | truncate: 20, "" }}', - 'Ground control to Ma') - }) - it('should not truncate when short enough', function () { - return test('{{ "12345" | truncate: 5 }}', '12345') - }) - it('should default to 16', function () { - return test('{{ "1234567890abcdefghi" | truncate }}', '1234567890abc...') - }) - }) - - describe('truncatewords', function () { - it('should truncate when too many words', function () { - return test('{{ "Ground control to Major Tom." | truncatewords: 3 }}', - 'Ground control to...') - }) - it('should not truncate when not enough words', function () { - return test('{{ "Ground control to Major Tom." | truncatewords: 8 }}', - 'Ground control to Major Tom.') - }) - it('should truncate with custom ellipsis', function () { - return test('{{ "Ground control to Major Tom." | truncatewords: 3, "--" }}', - 'Ground control to--') - }) - it('should truncate with empty custom ellipsis', function () { - return test('{{ "Ground control to Major Tom." | truncatewords: 3, "" }}', - 'Ground control to') - }) - }) - - describe('uniq', function () { - it('should uniq string list', function () { - return test( - '{% assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}' + - '{{ my_array | uniq | join: ", " }}', - 'ants, bugs, bees' - ) - }) - it('should uniq falsy value', function () { - return test('{{"" | uniq | join: ","}}', '') - }) - }) - - it('should support upcase', () => test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE')) - - describe('url_decode', function () { - it('should decode %xx and +', - () => test('{{ "%27Stop%21%27+said+Fred" | url_decode }}', "'Stop!' said Fred")) - }) - - describe('url_encode', function () { - it('should encode @', - () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')) - it('should encode ', - () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro+Takara')) - }) - - describe('obj_test', function () { - before(() => { - liquid.registerFilter('obj_test', function () { - return Array.prototype.slice.call(arguments).join(',') - }) - }) - it('should support object', () => test(`{{ "a" | obj_test: k1: "v1", k2: foo }}`, 'a,k1,v1,k2,bar')) - it('should support mixed object', () => test(`{{ "a" | obj_test: "something", k1: "v1", k2: foo }}`, 'a,something,k1,v1,k2,bar')) - }) -}) diff --git a/test/unit/fs/browser.ts b/test/unit/fs/browser.ts new file mode 100644 index 000000000..3dd4a8de4 --- /dev/null +++ b/test/unit/fs/browser.ts @@ -0,0 +1,84 @@ +import fs from 'src/fs/browser' +import * as sinon from 'sinon' +import { expect, use } from 'chai' +import * as chaiAsPromised from 'chai-as-promised' + +use(chaiAsPromised) +const resolve = fs.resolve + +describe('fs/browser', function () { + describe('#resolve()', function () { + if (+process.version.match(/^v(\d+)/)[1] < 8) { + console.info('jsdom not supported, skipping template-browser...') + return + } + const JSDOM = require('jsdom').JSDOM + beforeEach(function () { + const dom = new JSDOM(``, { + url: 'https://example.com/foo/bar/', + contentType: 'text/html', + includeNodeLocations: true + }); + (global as any).document = dom.window.document + }) + afterEach(function () { + delete (global as any).document + }) + it('should support relative root', function () { + expect(resolve('./views/', 'foo', '')).to.equal('https://example.com/foo/bar/views/foo') + }) + it('should treat root as directory', function () { + expect(resolve('./views', 'foo', '')).to.equal('https://example.com/foo/bar/views/foo') + }) + it('should support absolute root', function () { + expect(resolve('/views', 'foo', '')).to.equal('https://example.com/views/foo') + }) + it('should support empty root', function () { + expect(resolve('', 'page.html', '')).to.equal('https://example.com/foo/bar/page.html') + }) + it('should support full url as root', function () { + expect(resolve('https://example.com/views/', 'page.html', '')).to.equal('https://example.com/views/page.html') + }) + it('should add extname when absent', function () { + expect(resolve('https://example.com/views/', 'page', '.html')).to.equal('https://example.com/views/page.html') + }) + it('should add extname for urls have searchParams', function () { + expect(resolve('https://example.com/views/', 'page?foo=bar', '.html')).to.equal('https://example.com/views/page.html?foo=bar') + }) + it('should not add extname when full url is given', function () { + expect(resolve('https://example.com/views/', 'https://google.com/page.php', '.html')).to.equal('https://google.com/page.php') + }) + it('should not add extname when already have one', function () { + expect(resolve('https://example.com/views/', 'page.php', '.html')).to.equal('https://example.com/views/page.php') + }) + }) + + describe('#readFile()', () => { + let server + beforeEach(() => { + server = sinon.createFakeServer() + server.autoRespond = true + server.respondWith('GET', 'https://example.com/views/hello.html', + [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']); + (global as any).XMLHttpRequest = sinon.useFakeXMLHttpRequest() + }) + afterEach(() => { + server.restore() + delete (global as any).XMLHttpRequest + }) + it('should get corresponding text', async function () { + const html = await fs.readFile('https://example.com/views/hello.html') + return expect(html).to.equal('hello {{name}}') + }) + it('should throw 404', () => { + return expect(fs.readFile('https://example.com/not/exist.html')) + .to.be.rejectedWith('Not Found') + }) + it('should throw error', function () { + const result = expect(fs.readFile('https://example.com/views/hello.html')) + .to.be.rejectedWith('An error occurred whilst receiving the response.') + server.requests[0].error() + return result + }) + }) +}) diff --git a/test/unit/fs/node.ts b/test/unit/fs/node.ts new file mode 100644 index 000000000..c7ec20212 --- /dev/null +++ b/test/unit/fs/node.ts @@ -0,0 +1,47 @@ +import fs from 'src/fs/node' +import * as path from 'path' +import { expect, use } from 'chai' +import * as chaiAsPromised from 'chai-as-promised' +import { mock, restore } from 'test/stub/mockfs' + +use(chaiAsPromised) + +describe('fs', function () { + before(() => mock({ + '/foo/bar.html': 'bar', + '/un-readable.html': { mode: '0000', content: '' } + })) + after(restore) + + describe('#resolve()', function () { + it('should resolve based on root', async function () { + const filepath = fs.resolve('/foo', 'bar.html', '.liquid') + const expected = path.resolve('/foo/bar.html') + return expect(filepath).to.equal(expected) + }) + it('should add extension if it has no extension', async function () { + const filepath = fs.resolve('/foo', 'bar', '.liquid') + const expected = path.resolve('/foo/bar.liquid') + return expect(filepath).to.equal(expected) + }) + }) + describe('#exists', () => { + it('should resolve as false if not exists', async () => { + const result = await fs.exists('/foo/foo.html') + return expect(result).to.be.false + }) + it('should resolve as true if exists', async () => { + const result = await fs.exists('/foo/bar.html') + return expect(result).to.be.true + }) + }) + describe('#readFile', function () { + it('should throw when not exist', function () { + return expect(fs.readFile('/foo/foo.html')).to.rejectedWith('ENOENT') + }) + it('should throw when file not readable', function () { + return expect(fs.readFile('/un-readable.html')).to + .be.rejectedWith(/EACCES/) + }) + }) +}) diff --git a/test/unit/options/cache.ts b/test/unit/liquid/cache.ts similarity index 100% rename from test/unit/options/cache.ts rename to test/unit/liquid/cache.ts diff --git a/test/unit/liquid.ts b/test/unit/liquid/liquid.ts similarity index 87% rename from test/unit/liquid.ts rename to test/unit/liquid/liquid.ts index a12014b57..6535fc3d8 100644 --- a/test/unit/liquid.ts +++ b/test/unit/liquid/liquid.ts @@ -1,15 +1,10 @@ -import Liquid from '../../src/liquid' +import Liquid from 'src/liquid' import * as chai from 'chai' import { mock, restore } from 'test/stub/mockfs' const expect = chai.expect describe('Liquid', function () { - describe('#constructor()', function () { - it('should throw on illegal root', function () { - expect(() => new (Liquid as any)({ root: {} })).to.throw(/illegal root/) - }) - }) describe('#plugin()', function () { it('should call plugin on the instance', async function () { const engine = new Liquid() diff --git a/test/unit/options/strict.ts b/test/unit/liquid/strict.ts similarity index 100% rename from test/unit/options/strict.ts rename to test/unit/liquid/strict.ts diff --git a/test/unit/options/trimming.ts b/test/unit/liquid/trimming.ts similarity index 98% rename from test/unit/options/trimming.ts rename to test/unit/liquid/trimming.ts index 63666c4be..55e6d5043 100644 --- a/test/unit/options/trimming.ts +++ b/test/unit/liquid/trimming.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import Liquid from '../../../src/liquid' +import Liquid from 'src/liquid' describe('LiquidOptions#trimming', function () { const ctx = { name: 'harttle' } diff --git a/test/unit/lexical.ts b/test/unit/parser/lexical.ts similarity index 98% rename from test/unit/lexical.ts rename to test/unit/parser/lexical.ts index 67d7d1736..bfb0bb325 100644 --- a/test/unit/lexical.ts +++ b/test/unit/parser/lexical.ts @@ -1,7 +1,7 @@ import * as chai from 'chai' const expect = chai.expect -const lexical = require('../../src/parser/lexical') +const lexical = require('src/parser/lexical') describe('lexical', function () { it('should test filter syntax', function () { diff --git a/test/unit/tokenizer.ts b/test/unit/parser/tokenizer.ts similarity index 100% rename from test/unit/tokenizer.ts rename to test/unit/parser/tokenizer.ts diff --git a/test/unit/render.ts b/test/unit/render/render.ts similarity index 85% rename from test/unit/render.ts rename to test/unit/render/render.ts index f80345cea..8d66f3b91 100644 --- a/test/unit/render.ts +++ b/test/unit/render/render.ts @@ -1,9 +1,9 @@ import { expect } from 'chai' -import Scope from '../../src/scope/scope' -import Token from '../../src/parser/token' +import Scope from 'src/scope/scope' +import Token from 'src/parser/token' import Tag from 'src/template/tag/tag' import Filter from 'src/template/filter' -import Render from '../../src/render/render' +import Render from 'src/render/render' import HTML from 'src/template/html' describe('render', function () { diff --git a/test/unit/syntax.ts b/test/unit/render/syntax.ts similarity index 100% rename from test/unit/syntax.ts rename to test/unit/render/syntax.ts diff --git a/test/unit/scope.ts b/test/unit/scope/scope.ts similarity index 100% rename from test/unit/scope.ts rename to test/unit/scope/scope.ts diff --git a/test/unit/template-browser.ts b/test/unit/template-browser.ts deleted file mode 100644 index 8989c3d02..000000000 --- a/test/unit/template-browser.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { resolve } from '../../src/parser/template-browser' -import { expect } from 'chai' - -describe('template-browser', function () { - if (+process.version.match(/^v(\d+)/)[1] < 8) { - console.info('jsdom not supported, skipping template-browser...') - return - } - const JSDOM = require('jsdom').JSDOM - beforeEach(function () { - const dom = new JSDOM(``, { - url: 'https://example.com/foo/bar/', - contentType: 'text/html', - includeNodeLocations: true - }); - (global as any).document = dom.window.document - }) - afterEach(function () { - delete (global as any).document - }) - describe('resolve', function () { - it('should support relative root', function () { - expect(resolve('foo', './views/', { - extname: '', - root: ['.'] - })).to.equal('https://example.com/foo/bar/views/foo') - }) - it('should treat root as directory', function () { - expect(resolve('foo', './views', { - extname: '', - root: ['.'] - })).to.equal('https://example.com/foo/bar/views/foo') - }) - it('should support absolute root', function () { - expect(resolve('foo', '/views', { - extname: '', - root: ['.'] - })).to.equal('https://example.com/views/foo') - }) - it('should support empty root', function () { - expect(resolve('page.html', '', { - extname: '', - root: ['.'] - })).to.equal('https://example.com/foo/bar/page.html') - }) - it('should support full url as root', function () { - expect(resolve('page.html', 'https://example.com/views/', { - extname: '', - root: ['.'] - })).to.equal('https://example.com/views/page.html') - }) - it('should use options.root when root argument absent', function () { - expect(resolve('page.html', null, { - extname: '', - root: ['https://example.com/views', 'https://google.com/views'] - })).to.equal('https://example.com/views/page.html') - }) - it('should add extname when absent', function () { - expect(resolve('page', 'https://example.com/views/', { - extname: '.html', - root: ['.'] - })).to.equal('https://example.com/views/page.html') - }) - it('should add extname for urls have searchParams', function () { - expect(resolve('page?foo=bar', 'https://example.com/views/', { - extname: '.html', - root: ['.'] - })).to.equal('https://example.com/views/page.html?foo=bar') - }) - it('should not add extname when full url is given', function () { - expect(resolve('https://google.com/page.php', 'https://example.com/views/', { - extname: '.html', - root: ['.'] - })).to.equal('https://google.com/page.php') - }) - it('should not add extname when already have one', function () { - expect(resolve('page.php', 'https://example.com/views/', { - extname: '.html', - root: ['.'] - })).to.equal('https://example.com/views/page.php') - }) - }) -}) diff --git a/test/unit/template.ts b/test/unit/template.ts deleted file mode 100644 index d23102e7b..000000000 --- a/test/unit/template.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { resolve } from '../../src/parser/template' -import * as path from 'path' -import { expect, use } from 'chai' -import * as chaiAsPromised from 'chai-as-promised' -import { mock, restore } from '../stub/mockfs' - -use(chaiAsPromised) - -describe('template', function () { - before(() => mock({ '/foo/bar.html': 'bar' })) - after(restore) - - describe('#resolve()', function () { - it('should resolve based on root', async function () { - const filepath = await resolve('bar.html', '/foo', { root: [] }) - const expected = path.resolve('/foo/bar.html') - return expect(filepath).to.equal(expected) - }) - it('should resolve based on root', function () { - return expect(resolve('foo.html', '/foo', { root: [] })) - .to.rejectedWith(/Failed to lookup foo.html in: \/foo/) - }) - }) -}) diff --git a/test/unit/filter.ts b/test/unit/template/filter.ts similarity index 100% rename from test/unit/filter.ts rename to test/unit/template/filter.ts diff --git a/test/unit/output.ts b/test/unit/template/output.ts similarity index 96% rename from test/unit/output.ts rename to test/unit/template/output.ts index 0d970af41..6a1cd3874 100644 --- a/test/unit/output.ts +++ b/test/unit/template/output.ts @@ -1,6 +1,6 @@ import * as chai from 'chai' -import Scope from '../../src/scope/scope' -import Output from '../../src/template/output' +import Scope from 'src/scope/scope' +import Output from 'src/template/output' import OutputToken from 'src/parser/output-token' import Filter from 'src/template/filter' diff --git a/test/unit/tag.ts b/test/unit/template/tag.ts similarity index 100% rename from test/unit/tag.ts rename to test/unit/template/tag.ts diff --git a/test/unit/value.ts b/test/unit/template/value.ts similarity index 98% rename from test/unit/value.ts rename to test/unit/template/value.ts index 2941081dc..bfa46d5f6 100644 --- a/test/unit/value.ts +++ b/test/unit/template/value.ts @@ -1,7 +1,7 @@ import * as chai from 'chai' import * as sinonChai from 'sinon-chai' import * as sinon from 'sinon' -import Scope from '../../src/scope/scope' +import Scope from 'src/scope/scope' import Filter from 'src/template/filter' import Value from 'src/template/value' diff --git a/test/unit/util/assert.ts b/test/unit/util/assert.ts index 046b06ed5..acf94ff4e 100644 --- a/test/unit/util/assert.ts +++ b/test/unit/util/assert.ts @@ -1,5 +1,5 @@ import * as chai from 'chai' -import assert from '../../../src/util/assert' +import assert from 'src/util/assert' const expect = chai.expect diff --git a/test/unit/util/promise.ts b/test/unit/util/promise.ts index 8747abb69..661e02e18 100644 --- a/test/unit/util/promise.ts +++ b/test/unit/util/promise.ts @@ -5,7 +5,7 @@ import * as sinonChai from 'sinon-chai' const expect = chai.expect chai.use(sinonChai) -const P = require('../../../src/util/promise') +const P = require('src/util/promise') describe('util/promise', function () { describe('.anySeries()', function () { diff --git a/test/unit/util/strftime.ts b/test/unit/util/strftime.ts index 035f47e17..16ccbd1e4 100644 --- a/test/unit/util/strftime.ts +++ b/test/unit/util/strftime.ts @@ -1,5 +1,5 @@ import * as chai from 'chai' -import t from '../../../src/util/strftime' +import t from 'src/util/strftime' const expect = chai.expect diff --git a/test/unit/util/underscore.ts b/test/unit/util/underscore.ts index 1e0076d85..f03540dbf 100644 --- a/test/unit/util/underscore.ts +++ b/test/unit/util/underscore.ts @@ -1,8 +1,8 @@ import * as chai from 'chai' import * as sinonChai from 'sinon-chai' import * as sinon from 'sinon' -import { RenderError, RenderBreakError } from '../../../src/util/error' -import * as _ from '../../../src/util/underscore' +import { RenderError, RenderBreakError } from 'src/util/error' +import * as _ from 'src/util/underscore' const expect = chai.expect chai.use(sinonChai) diff --git a/test/unit/xhr.ts b/test/unit/xhr.ts deleted file mode 100644 index 94bdc6d77..000000000 --- a/test/unit/xhr.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { read } from 'src/parser/template-browser' -import * as sinon from 'sinon' -import { expect, use } from 'chai' -import * as chaiAsPromised from 'chai-as-promised' - -use(chaiAsPromised) - -describe('xhr', () => { - if (+process.version.match(/^v(\d+)/)[1] < 8) { - console.info('jsdom not supported, skipping xhr...') - return - } - let server - beforeEach(() => { - server = sinon.createFakeServer() - server.autoRespond = true - server.respondWith('GET', 'https://example.com/views/hello.html', - [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']); - (global as any).XMLHttpRequest = sinon.useFakeXMLHttpRequest() - }) - afterEach(() => { - server.restore() - delete (global as any).XMLHttpRequest - }) - describe('#read()', () => { - it('should get corresponding text', async function () { - const html = await read('https://example.com/views/hello.html') - return expect(html).to.equal('hello {{name}}') - }) - it('should throw 404', () => { - return expect(read('https://example.com/not/exist.html')) - .to.be.rejectedWith('Not Found') - }) - it('should throw error', function (done) { - read('https://example.com/views/hello.html') - .then(() => done('should not be resolved')) - .catch(function (e) { - expect(e.message).to.equal('An error occurred whilst receiving the response.') - done() - }) - server.requests[0].error() - }) - }) -}) diff --git a/tsconfig.json b/tsconfig.json index c8317eb48..7cc23e7ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "emitDecoratorMetadata": true, "baseUrl": ".", "paths": { - "template": ["src/parser/template"], + "src/fs": ["src/fs/node"], "src/*": ["src/*"], "test/*": ["test/*"] }