mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 20:30:39 -07:00
feat: iteration protocols
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { isNil, isString, isObject, isArray } from './underscore'
|
import { isNil, isString, isObject, isArray, isIterable } from './underscore'
|
||||||
|
|
||||||
export function toEnumerable (val: any) {
|
export function toEnumerable (val: any) {
|
||||||
if (isArray(val)) return val
|
if (isArray(val)) return val
|
||||||
if (isString(val) && val.length > 0) return [val]
|
if (isString(val) && val.length > 0) return [val]
|
||||||
|
if (isIterable(val)) return Array.from(val)
|
||||||
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]])
|
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]])
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export function isArray (value: any): value is any[] {
|
|||||||
return toString.call(value) === '[object Array]'
|
return toString.call(value) === '[object Array]'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isIterable (value: any): value is Iterable<any> {
|
||||||
|
return isObject(value) && Symbol.iterator in value
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property.
|
* Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property.
|
||||||
* The iteratee is invoked with three arguments: (value, key, object).
|
* The iteratee is invoked with three arguments: (value, key, object).
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Liquid } from '../../../../src/liquid'
|
import { Drop, Liquid } from '../../../../src/liquid'
|
||||||
import { expect, use } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import * as chaiAsPromised from 'chai-as-promised'
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
import { Scope } from '../../../../src/context/scope'
|
import { Scope } from '../../../../src/context/scope'
|
||||||
@@ -304,4 +304,66 @@ describe('tags/for', function () {
|
|||||||
return expect(html).to.equal('b')
|
return expect(html).to.equal('b')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('iterables', function () {
|
||||||
|
class MockIterable {
|
||||||
|
* [Symbol.iterator] () {
|
||||||
|
yield 'a'
|
||||||
|
yield 'b'
|
||||||
|
yield 'c'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockEmptyIterable {
|
||||||
|
* [Symbol.iterator] () {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockIterableDrop extends Drop {
|
||||||
|
* [Symbol.iterator] () {
|
||||||
|
yield 'a'
|
||||||
|
yield 'b'
|
||||||
|
yield 'c'
|
||||||
|
}
|
||||||
|
|
||||||
|
public valueOf (): string {
|
||||||
|
return 'MockIterableDrop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should loop over iterable objects', function () {
|
||||||
|
const src = '{% for i in someIterable %}{{i}}{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
|
||||||
|
return expect(html).to.equal('abc')
|
||||||
|
})
|
||||||
|
it('should loop over iterable drops', function () {
|
||||||
|
const src = '{{ someDrop }}: {% for i in someDrop %}{{i}}{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() })
|
||||||
|
return expect(html).to.equal('MockIterableDrop: abc')
|
||||||
|
})
|
||||||
|
it('should loop over iterable objects with a limit', function () {
|
||||||
|
const src = '{% for i in someIterable limit:2 %}{{i}}{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
|
||||||
|
return expect(html).to.equal('ab')
|
||||||
|
})
|
||||||
|
it('should loop over iterable objects with an offset', function () {
|
||||||
|
const src = '{% for i in someIterable offset:1 %}{{i}}{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
|
||||||
|
return expect(html).to.equal('bc')
|
||||||
|
})
|
||||||
|
it('should loop over iterable objects in reverse', function () {
|
||||||
|
const src = '{% for i in someIterable reversed %}{{i}}{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
|
||||||
|
return expect(html).to.equal('cba')
|
||||||
|
})
|
||||||
|
it('should go to else for an empty iterable', function () {
|
||||||
|
const src = '{% for i in emptyIterable reversed %}{{i}}{%else%}EMPTY{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { emptyIterable: new MockEmptyIterable() })
|
||||||
|
return expect(html).to.equal('EMPTY')
|
||||||
|
})
|
||||||
|
it('should support iterable names', function () {
|
||||||
|
const src = '{% for i in someDrop %}{{forloop.name}} {%else%}EMPTY{%endfor%}'
|
||||||
|
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() })
|
||||||
|
return expect(html).to.equal('i-someDrop i-someDrop i-someDrop ')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -155,6 +155,20 @@ describe('tags/render', function () {
|
|||||||
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
|
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
|
||||||
expect(html).to.equal('1: red\n2: green\n')
|
expect(html).to.equal('1: red\n2: green\n')
|
||||||
})
|
})
|
||||||
|
it('should support for <iterable> as', async function () {
|
||||||
|
class MockIterable {
|
||||||
|
* [Symbol.iterator] () {
|
||||||
|
yield 'red'
|
||||||
|
yield 'green'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock({
|
||||||
|
'/index.html': '{% render "item" for colors as color %}',
|
||||||
|
'/item.html': '{{forloop.index}}: {{color}}\n'
|
||||||
|
})
|
||||||
|
const html = await liquid.renderFile('index.html', { colors: new MockIterable() })
|
||||||
|
expect(html).to.equal('1: red\n2: green\n')
|
||||||
|
})
|
||||||
it('should support for <non-array> as', async function () {
|
it('should support for <non-array> as', async function () {
|
||||||
mock({
|
mock({
|
||||||
'/index.html': '{% render "item" for "green" as color %}',
|
'/index.html': '{% render "item" for "green" as color %}',
|
||||||
|
|||||||
@@ -24,6 +24,23 @@ describe('tags/tablerow', function () {
|
|||||||
return expect(html).to.equal(dst)
|
return expect(html).to.equal(dst)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support iterables', async function () {
|
||||||
|
class MockIterable {
|
||||||
|
* [Symbol.iterator] () {
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
yield 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const src = '{% tablerow i in someIterable %}{{ i }}{% endtablerow %}'
|
||||||
|
const ctx = {
|
||||||
|
someIterable: new MockIterable()
|
||||||
|
}
|
||||||
|
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
|
||||||
|
const html = await liquid.parseAndRender(src, ctx)
|
||||||
|
return expect(html).to.equal(dst)
|
||||||
|
})
|
||||||
|
|
||||||
it('should support cols', async function () {
|
it('should support cols', async function () {
|
||||||
const src = '{% tablerow i in alpha cols:2 %}{{ i }}{% endtablerow %}'
|
const src = '{% tablerow i in alpha cols:2 %}{{ i }}{% endtablerow %}'
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
|||||||
Reference in New Issue
Block a user