mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-13 11:20:40 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5847f25e24 |
+1
-1
@@ -14,7 +14,7 @@
|
||||
"login": "harttle",
|
||||
"name": "Jun Yang",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/4427974?v=4",
|
||||
"profile": "https://harttle.land",
|
||||
"profile": "https://github.com/harttle",
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"code"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
description: Testing conventions — e2e uses built dist, integration uses src
|
||||
globs: test/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
## End-to-end tests (`test/e2e`)
|
||||
|
||||
- **Use the built package**, not TypeScript sources under `src/`.
|
||||
- Import the public API from the package root (for example `import { Liquid } from '../..'`), which resolves through `package.json` to **`dist/`** (`main`, `module`, etc.).
|
||||
- **Avoid** `import … from '../../src/liquid'` (or other `src/` paths) in `test/e2e/**` so e2e matches what consumers get from npm and you do not depend on an unbuilt tree.
|
||||
|
||||
## Integration and unit tests
|
||||
|
||||
- Tests under `test/integration/`, `src/**/*.spec.ts`, and similar may import from **`src/`** when the suite is meant to run against the current TypeScript sources (typical for this repo’s Jest setup).
|
||||
@@ -108,7 +108,7 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://harttle.land"><img src="https://avatars3.githubusercontent.com/u/4427974?v=4?s=100" width="100px;" alt="Jun Yang"/><br /><sub><b>Jun Yang</b></sub></a><br /><a href="#maintenance-harttle" title="Maintenance">🚧</a> <a href="https://github.com/harttle/liquidjs/commits?author=harttle" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/harttle"><img src="https://avatars3.githubusercontent.com/u/4427974?v=4?s=100" width="100px;" alt="Jun Yang"/><br /><sub><b>Jun Yang</b></sub></a><br /><a href="#maintenance-harttle" title="Maintenance">🚧</a> <a href="https://github.com/harttle/liquidjs/commits?author=harttle" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chenos"><img src="https://avatars0.githubusercontent.com/u/2993310?v=4?s=100" width="100px;" alt="chenos"/><br /><sub><b>chenos</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=chenos" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://zachleat.com/"><img src="https://avatars2.githubusercontent.com/u/39355?v=4?s=100" width="100px;" alt="Zach Leatherman"/><br /><sub><b>Zach Leatherman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/issues?q=author%3Azachleat" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/thardy"><img src="https://avatars3.githubusercontent.com/u/120636?v=4?s=100" width="100px;" alt="Tim Hardy"/><br /><sub><b>Tim Hardy</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thardy" title="Code">💻</a></td>
|
||||
|
||||
@@ -48,8 +48,8 @@ export class Context {
|
||||
this.memoryLimit = memoryLimit ?? new Limiter('memory alloc', renderOptions.memoryLimit ?? opts.memoryLimit)
|
||||
this.renderLimit = renderLimit ?? new Limiter('template render', getPerformance().now() + (renderOptions.renderLimit ?? opts.renderLimit))
|
||||
}
|
||||
public getRegister (key: string) {
|
||||
return (this.registers[key] = this.registers[key] || {})
|
||||
public getRegister<T> (key: string, defaultValue: T = undefined as T): T {
|
||||
return (this.registers[key] = this.registers[key] || defaultValue)
|
||||
}
|
||||
public setRegister (key: string, value: any) {
|
||||
return (this.registers[key] = value)
|
||||
|
||||
+8
-3
@@ -23,20 +23,25 @@ export default class extends Tag {
|
||||
* render (ctx: Context, emitter: Emitter) {
|
||||
const blockRender = this.getBlockRender(ctx)
|
||||
if (ctx.getRegister('blockMode') === BlockMode.STORE) {
|
||||
ctx.getRegister('blocks')[this.block] = blockRender
|
||||
ctx.getRegister('blocks', {} as Record<string, any>)[this.block] = blockRender
|
||||
} else {
|
||||
yield blockRender(new BlockDrop(), emitter)
|
||||
}
|
||||
}
|
||||
|
||||
private getBlockRender (ctx: Context) {
|
||||
const self = this as Tag
|
||||
const { liquid, templates } = this
|
||||
const renderChild = ctx.getRegister('blocks')[this.block]
|
||||
const renderChild = ctx.getRegister('blocks', {} as Record<string, any>)[this.block]
|
||||
const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) {
|
||||
// add {{ block.super }} support when rendering
|
||||
const stack: Tag[] = ctx.getRegister('blockStack', [])
|
||||
if (stack.includes(self)) throw new Error('block tag cannot be nested')
|
||||
|
||||
stack.push(self)
|
||||
ctx.push({ block: superBlock })
|
||||
yield liquid.renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
stack.pop()
|
||||
}
|
||||
return renderChild
|
||||
? (superBlock: BlockDrop, emitter: Emitter) => renderChild(
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export default class extends Tag {
|
||||
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
|
||||
const group = (yield evalToken(this.group, ctx)) as ValueToken
|
||||
const fingerprint = `cycle:${group}:` + this.candidates.join(',')
|
||||
const groups = ctx.getRegister('cycle')
|
||||
const groups = ctx.getRegister('cycle', {} as Record<string, number>)
|
||||
let idx = groups[fingerprint]
|
||||
|
||||
if (idx === undefined) {
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ export default class extends Tag {
|
||||
}
|
||||
|
||||
const continueKey = 'continue-' + this.variable + '-' + this.collection.getText()
|
||||
ctx.push({ continue: ctx.getRegister(continueKey) })
|
||||
ctx.push({ continue: ctx.getRegister(continueKey, {}) })
|
||||
const hash = yield this.hash.render(ctx)
|
||||
ctx.pop()
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export default class extends Tag {
|
||||
// render remaining contents and store rendered results
|
||||
ctx.setRegister('blockMode', BlockMode.STORE)
|
||||
const html = yield renderer.renderTemplates(this.templates, ctx)
|
||||
const blocks = ctx.getRegister('blocks')
|
||||
const blocks = ctx.getRegister('blocks', {} as Record<string, any>)
|
||||
|
||||
// set whole content to anonymous block if anonymous doesn't specified
|
||||
if (blocks[''] === undefined) blocks[''] = (parent: BlankDrop, emitter: Emitter) => emitter.write(html)
|
||||
|
||||
@@ -82,4 +82,39 @@ describe('.parseAndRender()', function () {
|
||||
expect(() => e.parseAndRenderSync('{% render "link" %}')).toThrow(/ENOENT|Failed to lookup/)
|
||||
})
|
||||
})
|
||||
describe('layout: nested {% block %} regression', function () {
|
||||
let root: string
|
||||
beforeEach(function () {
|
||||
root = mkdtempSync(join(tmpdir(), 'liquid-e2e-layout-nested-'))
|
||||
})
|
||||
afterEach(function () {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
it('should reject same-name {% block %} nested in child template (no hang / OOM)', async function () {
|
||||
writeFileSync(
|
||||
join(root, 'layout.html'),
|
||||
'<header>{% block a %}default-a{% endblock %}</header>' +
|
||||
'<main>{% block b %}default-b{% endblock %}</main>' +
|
||||
'<footer>{% block c %}default-c{% endblock %}</footer>'
|
||||
)
|
||||
writeFileSync(
|
||||
join(root, 'template.html'),
|
||||
'{% layout "layout" %}' +
|
||||
'{% block a %}outer-a {% block a %}inner-a{% endblock %}{% endblock %}' +
|
||||
'{% block b %}content-b{% endblock %}' +
|
||||
'{% block c %}content-c{% endblock %}'
|
||||
)
|
||||
const liquid = new Liquid({ root, extname: '.html' })
|
||||
await expect(liquid.renderFile('template')).rejects.toThrow(/block tag cannot be nested/)
|
||||
})
|
||||
it('should reject nested anonymous {% block %} in child template (no hang / OOM)', async function () {
|
||||
writeFileSync(join(root, 'parent.html'), 'X{%block%}{%endblock%}Y')
|
||||
writeFileSync(
|
||||
join(root, 'template.html'),
|
||||
'{% layout "parent" %}{%block%}A{%block%}B{%endblock%}{%endblock%}'
|
||||
)
|
||||
const liquid = new Liquid({ root, extname: '.html' })
|
||||
await expect(liquid.renderFile('template')).rejects.toThrow(/block tag cannot be nested/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('tags/include', function () {
|
||||
})
|
||||
return liquid.renderFile('/parent.html').catch(function (e) {
|
||||
expect(e.name).toBe('TokenizationError')
|
||||
expect(e.message).toMatch('illegal file path, file:/parent.html, line:1, col:11')
|
||||
expect(e.message).toMatch(/illegal file path, file:.*parent.html, line:1, col:11/)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -166,6 +166,27 @@ describe('tags/layout', function () {
|
||||
const html = await liquid.renderFile('/main.html')
|
||||
return expect(html).toBe('XAY')
|
||||
})
|
||||
it('should reject nested {% block %} with the same name (no OOM / hang)', function () {
|
||||
mock({
|
||||
'/layout.html':
|
||||
'<header>{% block a %}default-a{% endblock %}</header>' +
|
||||
'<main>{% block b %}default-b{% endblock %}</main>' +
|
||||
'<footer>{% block c %}default-c{% endblock %}</footer>',
|
||||
'/template.html':
|
||||
'{% layout "layout" %}' +
|
||||
'{% block a %}outer-a {% block a %}inner-a{% endblock %}{% endblock %}' +
|
||||
'{% block b %}content-b{% endblock %}' +
|
||||
'{% block c %}content-c{% endblock %}'
|
||||
})
|
||||
return expect(liquid.renderFile('/template.html')).rejects.toThrow(/block tag cannot be nested/)
|
||||
})
|
||||
it('should reject nested anonymous {% block %} (no OOM / hang)', function () {
|
||||
mock({
|
||||
'/parent.html': 'X{%block%}{%endblock%}Y'
|
||||
})
|
||||
const src = '{% layout "parent.html" %}{%block%}A{%block%}B{%endblock%}{%endblock%}'
|
||||
return expect(liquid.parseAndRender(src)).rejects.toThrow(/block tag cannot be nested/)
|
||||
})
|
||||
it('should not bleed scope into `include` layout', async function () {
|
||||
mock({
|
||||
'/parent.html': 'X{%block a%}{%endblock%}Y{%block b%}{%endblock%}Z',
|
||||
|
||||
Reference in New Issue
Block a user