mirror of
https://github.com/harttle/liquidjs.git
synced 2026-10-03 17:15:14 -07:00
test: coverage to 100%
This commit is contained in:
@@ -16,13 +16,10 @@ export default {
|
|||||||
|
|
||||||
while (!tokenizer.end()) {
|
while (!tokenizer.end()) {
|
||||||
const value = tokenizer.readValue()
|
const value = tokenizer.readValue()
|
||||||
if (value) {
|
this.cases.push({
|
||||||
this.cases.push({
|
val: value,
|
||||||
val: value,
|
templates: p
|
||||||
templates: p
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenizer.readTo(',')
|
tokenizer.readTo(',')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+14
-19
@@ -1,4 +1,4 @@
|
|||||||
import { Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
|
import { Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template, TagImplOptions } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||||
@@ -6,34 +6,29 @@ export default {
|
|||||||
this.elseTemplates = []
|
this.elseTemplates = []
|
||||||
|
|
||||||
let p
|
let p
|
||||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('start', () => this.branches.push({
|
.on('start', () => this.branches.push({
|
||||||
cond: new Value(tagToken.args, this.liquid),
|
predicate: new Value(tagToken.args, this.liquid),
|
||||||
|
templates: (p = [])
|
||||||
|
}))
|
||||||
|
.on('tag:elsif', (token: TagToken) => this.branches.push({
|
||||||
|
predicate: new Value(token.args, this.liquid),
|
||||||
templates: (p = [])
|
templates: (p = [])
|
||||||
}))
|
}))
|
||||||
.on('tag:elsif', (token: TagToken) => {
|
|
||||||
this.branches.push({
|
|
||||||
cond: new Value(token.args, this.liquid),
|
|
||||||
templates: p = []
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.on('tag:else', () => (p = this.elseTemplates))
|
.on('tag:else', () => (p = this.elseTemplates))
|
||||||
.on('tag:endif', () => stream.stop())
|
.on('tag:endif', function () { this.stop() })
|
||||||
.on('template', (tpl: Template) => p.push(tpl))
|
.on('template', (tpl: Template) => p.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||||
throw new Error(`tag ${tagToken.getText()} not closed`)
|
.start()
|
||||||
})
|
|
||||||
|
|
||||||
stream.start()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function * (ctx: Context, emitter: Emitter) {
|
render: function * (ctx: Context, emitter: Emitter) {
|
||||||
const r = this.liquid.renderer
|
const r = this.liquid.renderer
|
||||||
|
|
||||||
for (const branch of this.branches) {
|
for (const { predicate, templates } of this.branches) {
|
||||||
const cond = yield branch.cond.value(ctx, ctx.opts.lenientIf)
|
const value = yield predicate.value(ctx, ctx.opts.lenientIf)
|
||||||
if (isTruthy(cond, ctx)) {
|
if (isTruthy(value, ctx)) {
|
||||||
yield r.renderTemplates(branch.templates, ctx, emitter)
|
yield r.renderTemplates(templates, ctx, emitter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ export default {
|
|||||||
const keyword = tokenizer.readIdentifier()
|
const keyword = tokenizer.readIdentifier()
|
||||||
if (keyword.content === 'with' || keyword.content === 'for') {
|
if (keyword.content === 'with' || keyword.content === 'for') {
|
||||||
tokenizer.skipBlank()
|
tokenizer.skipBlank()
|
||||||
|
// can be normal key/value pair, like "with: true"
|
||||||
if (tokenizer.peek() !== ':') {
|
if (tokenizer.peek() !== ':') {
|
||||||
const value = tokenizer.readValue()
|
const value = tokenizer.readValue()
|
||||||
|
// can be normal key, like "with,"
|
||||||
if (value) {
|
if (value) {
|
||||||
const beforeAs = tokenizer.p
|
const beforeAs = tokenizer.p
|
||||||
const asStr = tokenizer.readIdentifier()
|
const asStr = tokenizer.readIdentifier()
|
||||||
@@ -30,10 +32,14 @@ export default {
|
|||||||
this[keyword.content] = { value, alias: alias && alias.content }
|
this[keyword.content] = { value, alias: alias && alias.content }
|
||||||
tokenizer.skipBlank()
|
tokenizer.skipBlank()
|
||||||
if (tokenizer.peek() === ',') tokenizer.advance()
|
if (tokenizer.peek() === ',') tokenizer.advance()
|
||||||
|
// matched!
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* restore cursor if with/for not matched
|
||||||
|
*/
|
||||||
tokenizer.p = begin
|
tokenizer.p = begin
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -83,14 +89,11 @@ export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFile
|
|||||||
const file = tokenizer.readValue()
|
const file = tokenizer.readValue()
|
||||||
if (file === undefined) throw new TypeError(`illegal argument "${tokenizer.input}"`)
|
if (file === undefined) throw new TypeError(`illegal argument "${tokenizer.input}"`)
|
||||||
if (file.getText() === 'none') return null
|
if (file.getText() === 'none') return null
|
||||||
// for filenames like "files/{{file}}", eval as liquid template
|
|
||||||
if (TypeGuards.isQuotedToken(file)) {
|
if (TypeGuards.isQuotedToken(file)) {
|
||||||
|
// for filenames like "files/{{file}}", eval as liquid template
|
||||||
const tpls = liquid.parse(evalQuotedToken(file))
|
const tpls = liquid.parse(evalQuotedToken(file))
|
||||||
// for filenames like "files/file.liquid", extract the string directly
|
// for filenames like "files/file.liquid", extract the string directly
|
||||||
if (tpls.length === 1) {
|
if (tpls.length === 1 && TypeGuards.isHTMLToken(tpls[0].token)) return tpls[0].token.getContent()
|
||||||
const first = tpls[0]
|
|
||||||
if (TypeGuards.isHTMLToken(first)) return first.getText()
|
|
||||||
}
|
|
||||||
return tpls
|
return tpls
|
||||||
}
|
}
|
||||||
return file
|
return file
|
||||||
|
|||||||
+19
-29
@@ -1,45 +1,35 @@
|
|||||||
import { Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, ParseStream, Context, TagImplOptions, TagToken } from '../../types'
|
import { Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, Context, TagImplOptions, TagToken } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||||
this.templates = []
|
|
||||||
this.branches = []
|
this.branches = []
|
||||||
this.elseTemplates = []
|
this.elseTemplates = []
|
||||||
let p
|
let p
|
||||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('start', () => {
|
.on('start', () => this.branches.push({
|
||||||
p = this.templates
|
predicate: new Value(tagToken.args, this.liquid),
|
||||||
this.cond = new Value(tagToken.args, this.liquid)
|
test: isFalsy,
|
||||||
})
|
templates: (p = [])
|
||||||
.on('tag:elsif', (token: TagToken) => {
|
}))
|
||||||
this.branches.push({
|
.on('tag:elsif', (token: TagToken) => this.branches.push({
|
||||||
cond: new Value(token.args, this.liquid),
|
predicate: new Value(token.args, this.liquid),
|
||||||
templates: p = []
|
test: isTruthy,
|
||||||
})
|
templates: (p = [])
|
||||||
})
|
}))
|
||||||
.on('tag:else', () => (p = this.elseTemplates))
|
.on('tag:else', () => (p = this.elseTemplates))
|
||||||
.on('tag:endunless', () => stream.stop())
|
.on('tag:endunless', function () { this.stop() })
|
||||||
.on('template', (tpl: Template) => p.push(tpl))
|
.on('template', (tpl: Template) => p.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||||
throw new Error(`tag ${tagToken.getText()} not closed`)
|
.start()
|
||||||
})
|
|
||||||
|
|
||||||
stream.start()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function * (ctx: Context, emitter: Emitter) {
|
render: function * (ctx: Context, emitter: Emitter) {
|
||||||
const r = this.liquid.renderer
|
const r = this.liquid.renderer
|
||||||
const cond = yield this.cond.value(ctx, ctx.opts.lenientIf)
|
|
||||||
|
|
||||||
if (isFalsy(cond, ctx)) {
|
for (const { predicate, test, templates } of this.branches) {
|
||||||
yield r.renderTemplates(this.templates, ctx, emitter)
|
const value = yield predicate.value(ctx, ctx.opts.lenientIf)
|
||||||
return
|
if (test(value, ctx)) {
|
||||||
}
|
yield r.renderTemplates(templates, ctx, emitter)
|
||||||
|
|
||||||
for (const branch of this.branches) {
|
|
||||||
const cond = yield branch.cond.value(ctx, ctx.opts.lenientIf)
|
|
||||||
if (isTruthy(cond, ctx)) {
|
|
||||||
yield r.renderTemplates(branch.templates, ctx, emitter)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export class ParseStream<T extends Token = TopLevelToken> {
|
|||||||
this.tokens = tokens
|
this.tokens = tokens
|
||||||
this.parseToken = parseToken
|
this.parseToken = parseToken
|
||||||
}
|
}
|
||||||
public on<T2 extends Template | T | undefined> (name: string, cb: (arg: T2) => void): ParseStream<T> {
|
public on<T2 extends Template | T | undefined> (name: string, cb: (this: ParseStream, arg: T2) => void): ParseStream<T> {
|
||||||
this.handlers[name] = cb
|
this.handlers[name] = cb
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
private trigger <T extends Token | Template> (event: string, arg?: T) {
|
private trigger <T extends Token | Template> (event: string, arg?: T) {
|
||||||
const h = this.handlers[event]
|
const h = this.handlers[event]
|
||||||
return h ? (h(arg), true) : false
|
return h ? (h.call(this, arg), true) : false
|
||||||
}
|
}
|
||||||
public start () {
|
public start () {
|
||||||
this.trigger('start')
|
this.trigger('start')
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ describe('filters/date', function () {
|
|||||||
it('should apply numeric timezone offset (+2.30)', function () {
|
it('should apply numeric timezone offset (+2.30)', function () {
|
||||||
return test('{{ "1990-12-31T23:00:00+02:30" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
|
return test('{{ "1990-12-31T23:00:00+02:30" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
|
||||||
})
|
})
|
||||||
|
it('should automatically work when timezone not specified', function () {
|
||||||
|
return test('{{ "1990-12-31T23:00:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
it('should render string as string if not valid', function () {
|
it('should render string as string if not valid', function () {
|
||||||
return test('{{ "foo" | date: "%Y"}}', 'foo')
|
return test('{{ "foo" | date: "%Y"}}', 'foo')
|
||||||
|
|||||||
@@ -191,6 +191,15 @@ describe('tags/layout', function () {
|
|||||||
return expect(html).to.equal('blackA')
|
return expect(html).to.equal('blackA')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support none', async function () {
|
||||||
|
mock({
|
||||||
|
'/main.html': '{% layout none %}foo'
|
||||||
|
})
|
||||||
|
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
|
||||||
|
const html = await staticLiquid.renderFile('/main.html')
|
||||||
|
return expect(html).to.equal('foo')
|
||||||
|
})
|
||||||
|
|
||||||
it('should support subpaths', async function () {
|
it('should support subpaths', async function () {
|
||||||
mock({
|
mock({
|
||||||
'/foo/parent.html': '{{color}}{%block%}{%endblock%}',
|
'/foo/parent.html': '{{color}}{%block%}{%endblock%}',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Liquid, Drop } from '../../../../src/liquid'
|
import { Liquid, Drop } from '../../../../src/liquid'
|
||||||
import { expect } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import { mock, restore } from '../../../stub/mockfs'
|
import { mock, restore } from '../../../stub/mockfs'
|
||||||
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('tags/render', function () {
|
describe('tags/render', function () {
|
||||||
let liquid: Liquid
|
let liquid: Liquid
|
||||||
@@ -93,6 +95,22 @@ describe('tags/render', function () {
|
|||||||
const html = await liquid.renderFile('with.html')
|
const html = await liquid.renderFile('with.html')
|
||||||
expect(html).to.equal('color:red, shape:rect')
|
expect(html).to.equal('color:red, shape:rect')
|
||||||
})
|
})
|
||||||
|
it('should treat as normal key/value if followed by ":"', async () => {
|
||||||
|
mock({
|
||||||
|
'/with.html': '{% render "color" with: "foo" %}',
|
||||||
|
'/color.html': 'color:{{color}}, with:{{with}}'
|
||||||
|
})
|
||||||
|
const html = await liquid.renderFile('with.html')
|
||||||
|
expect(html).to.equal('color:, with:foo')
|
||||||
|
})
|
||||||
|
it('should treat as normal key if with value not specified', async () => {
|
||||||
|
mock({
|
||||||
|
'/with.html': '{% render "color" with, shape: "rect" %}',
|
||||||
|
'/color.html': 'color:{{color}}, with:{{with}}, shape:{{shape}}'
|
||||||
|
})
|
||||||
|
const html = await liquid.renderFile('with.html')
|
||||||
|
expect(html).to.equal('color:, with:true, shape:rect')
|
||||||
|
})
|
||||||
it('should support with...as', async function () {
|
it('should support with...as', async function () {
|
||||||
mock({
|
mock({
|
||||||
'/with.html': '{% render "color" with color as c %}',
|
'/with.html': '{% render "color" with color as c %}',
|
||||||
@@ -271,7 +289,7 @@ describe('tags/render', function () {
|
|||||||
const html = liquid.renderFileSync('with.html')
|
const html = liquid.renderFileSync('with.html')
|
||||||
expect(html).to.equal('color:red, shape:rect')
|
expect(html).to.equal('color:red, shape:rect')
|
||||||
})
|
})
|
||||||
it('should support filename with extention', function () {
|
it('should support filename with extension', function () {
|
||||||
mock({
|
mock({
|
||||||
'/parent.html': 'X{% render child.html color:"red" %}Y',
|
'/parent.html': 'X{% render child.html color:"red" %}Y',
|
||||||
'/child.html': 'child with {{color}}'
|
'/child.html': 'child with {{color}}'
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { expect } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import { Liquid } from '../../../src/liquid'
|
import { Liquid } from '../../../src/liquid'
|
||||||
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('LiquidOptions#fs', function () {
|
describe('LiquidOptions#fs', function () {
|
||||||
let engine: Liquid
|
let engine: Liquid
|
||||||
@@ -31,4 +33,13 @@ describe('LiquidOptions#fs', function () {
|
|||||||
const html = engine.renderFileSync('notexist/foo')
|
const html = engine.renderFileSync('notexist/foo')
|
||||||
expect(html).to.equal('content for /root/files/fallback')
|
expect(html).to.equal('content for /root/files/fallback')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw lookup failure if fallback not specified', function () {
|
||||||
|
const engine = new Liquid({
|
||||||
|
root: '/root/',
|
||||||
|
fs: { ...fs, fallback: undefined }
|
||||||
|
} as any)
|
||||||
|
return expect(engine.renderFile('notexist/foo'))
|
||||||
|
.to.be.rejectedWith('Failed to lookup')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -130,4 +130,13 @@ describe('Liquid', function () {
|
|||||||
.to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
|
.to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#enderToNodeStream', function () {
|
||||||
|
const engine = new Liquid()
|
||||||
|
it('should render a simple value', function (done) {
|
||||||
|
const stream = engine.renderToNodeStream(engine.parse('{{"foo"}}'))
|
||||||
|
let html = ''
|
||||||
|
stream.on('data', data => { html += data })
|
||||||
|
stream.on('end', () => { expect(html).to.equal('foo'); done() })
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { normalize } from '../../src/liquid-options'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
|
||||||
|
describe('liquid-options', () => {
|
||||||
|
describe('.normalize()', () => {
|
||||||
|
it('should return plain object for empty input', () => {
|
||||||
|
const options = normalize()
|
||||||
|
expect(JSON.stringify(options)).to.equal('{}')
|
||||||
|
})
|
||||||
|
it('should set falsy cache to undefined', () => {
|
||||||
|
const options = normalize({ cache: false })
|
||||||
|
expect(JSON.stringify(options)).to.equal('{}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user