Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/compile-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function compileToString(
let include = (__eta_t, __eta_d) => this.render(__eta_t, {...${config.varName}, ...(__eta_d ?? {})}, options);
let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...${config.varName}, ...(__eta_d ?? {})}, options);

let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction${
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction, blocks: {}${
config.debug
? ', line: 1, templateStr: "' +
str.replace(/\\|"/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n") +
Expand All @@ -41,14 +41,16 @@ function layout(path, data) {
function ${config.outputFunctionName}(s){__eta.res+=s;}
function capture(fn){const s=__eta.res;__eta.res='';try{fn();return __eta.res}finally{__eta.res=s;}}
async function captureAsync(fn){const s=__eta.res;__eta.res='';try{await fn();return __eta.res}finally{__eta.res=s;}}
function block(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=capture(fn);}return '';}const b=${config.varName}.__blocks||{};if(name in b){return b[name];}return fn?capture(fn):'';}
async function blockAsync(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=await captureAsync(fn);}return '';}const b=${config.varName}.__blocks||{};if(name in b){return b[name];}return fn?await captureAsync(fn):'';}

${compileBody.call(this, buffer)}
if (__eta.layout) {
__eta.res = ${
isAsync ? "await includeAsync" : "include"
} (__eta.layout, {...${
config.varName
}, body: __eta.res, ...__eta.layoutData});
}, body: __eta.res, ...__eta.layoutData, __blocks: __eta.blocks});
}
${config.useWith ? "}" : ""}${
config.debug
Expand Down
24 changes: 16 additions & 8 deletions test/compile-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("Compile to String test", () => {
let include = (__eta_t, __eta_d) => this.render(__eta_t, {...it, ...(__eta_d ?? {})}, options);
let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...it, ...(__eta_d ?? {})}, options);

let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction};
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction, blocks: {}};

function layout(path, data) {
__eta.layout = path;
Expand All @@ -27,12 +27,14 @@ function layout(path, data) {
function output(s){__eta.res+=s;}
function capture(fn){const s=__eta.res;__eta.res='';try{fn();return __eta.res}finally{__eta.res=s;}}
async function captureAsync(fn){const s=__eta.res;__eta.res='';try{await fn();return __eta.res}finally{__eta.res=s;}}
function block(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=capture(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?capture(fn):'';}
async function blockAsync(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=await captureAsync(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?await captureAsync(fn):'';}

__eta.res+='hi ';
__eta.res+=__eta.e(it.name);

if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData, __blocks: __eta.blocks});
}

return __eta.res;
Expand All @@ -45,7 +47,7 @@ return __eta.res;
let include = (__eta_t, __eta_d) => this.render(__eta_t, {...it, ...(__eta_d ?? {})}, options);
let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...it, ...(__eta_d ?? {})}, options);

let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction};
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction, blocks: {}};

function layout(path, data) {
__eta.layout = path;
Expand All @@ -55,12 +57,14 @@ function layout(path, data) {
function output(s){__eta.res+=s;}
function capture(fn){const s=__eta.res;__eta.res='';try{fn();return __eta.res}finally{__eta.res=s;}}
async function captureAsync(fn){const s=__eta.res;__eta.res='';try{await fn();return __eta.res}finally{__eta.res=s;}}
function block(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=capture(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?capture(fn):'';}
async function blockAsync(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=await captureAsync(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?await captureAsync(fn):'';}

__eta.res+='hi ';
__eta.res+=it.name;

if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData, __blocks: __eta.blocks});
}

return __eta.res;
Expand All @@ -75,7 +79,7 @@ return __eta.res;
let include = (__eta_t, __eta_d) => this.render(__eta_t, {...it, ...(__eta_d ?? {})}, options);
let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...it, ...(__eta_d ?? {})}, options);

let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction};
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction, blocks: {}};

function layout(path, data) {
__eta.layout = path;
Expand All @@ -85,13 +89,15 @@ function layout(path, data) {
function output(s){__eta.res+=s;}
function capture(fn){const s=__eta.res;__eta.res='';try{fn();return __eta.res}finally{__eta.res=s;}}
async function captureAsync(fn){const s=__eta.res;__eta.res='';try{await fn();return __eta.res}finally{__eta.res=s;}}
function block(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=capture(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?capture(fn):'';}
async function blockAsync(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=await captureAsync(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?await captureAsync(fn):'';}

__eta.res+='hi';
__eta.res+=__eta.e(it.firstname);
__eta.res+=__eta.e(it.lastname);

if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData, __blocks: __eta.blocks});
}

return __eta.res;
Expand All @@ -104,7 +110,7 @@ return __eta.res;
let include = (__eta_t, __eta_d) => this.render(__eta_t, {...it, ...(__eta_d ?? {})}, options);
let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...it, ...(__eta_d ?? {})}, options);

let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction};
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction, blocks: {}};

function layout(path, data) {
__eta.layout = path;
Expand All @@ -114,6 +120,8 @@ function layout(path, data) {
function output(s){__eta.res+=s;}
function capture(fn){const s=__eta.res;__eta.res='';try{fn();return __eta.res}finally{__eta.res=s;}}
async function captureAsync(fn){const s=__eta.res;__eta.res='';try{await fn();return __eta.res}finally{__eta.res=s;}}
function block(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=capture(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?capture(fn):'';}
async function blockAsync(name,fn){if(__eta.layout){if(fn){__eta.blocks[name]=await captureAsync(fn);}return '';}const b=it.__blocks||{};if(name in b){return b[name];}return fn?await captureAsync(fn):'';}

__eta.res+='Hi\\n';
console.log("Hope you like Eta!")
Expand All @@ -140,7 +148,7 @@ __eta.res+='\\nThis is a partial: ';
__eta.res+=include("mypartial");

if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData, __blocks: __eta.blocks});
}

return __eta.res;
Expand Down
109 changes: 97 additions & 12 deletions test/render.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,106 @@ describe("forEach loops in included templates", () => {
});
});

describe("capture", () => {
describe("block", () => {
const eta = new Eta();

it("captures inline HTML and passes it to an included template", () => {
it("child overrides layout default", () => {
eta.loadTemplate(
"@block-layout",
`<head><%~ block('styles', () => { %><link href="default.css"><% }) %></head><%~ it.body %>`,
);

const res = eta.renderString(
`<% layout("@block-layout") %><p>Body</p><% block('styles', () => { %><link href="page.css"><% }) %>`,
{},
);

expect(res).toEqual('<head><link href="page.css"></head><p>Body</p>');
});

it("block with default content when no override", () => {
eta.loadTemplate(
"@block-default",
`<head><%~ block('styles', () => { %><link href="default.css"><% }) %></head><%~ it.body %>`,
);

const res = eta.renderString(
`<% layout("@block-default") %><p>Body</p>`,
{},
);

expect(res).toEqual('<head><link href="default.css"></head><p>Body</p>');
});

it("empty block with no default and no override", () => {
eta.loadTemplate("@block-empty", `[<%~ block('sidebar') %>]<%~ it.body %>`);

const res = eta.renderString(`<% layout("@block-empty") %>main`, {});

expect(res).toEqual("[]main");
});

it("multiple blocks in one layout", () => {
eta.loadTemplate(
"@block-multi",
`<head><%~ block('styles') %></head><footer><%~ block('scripts') %></footer><%~ it.body %>`,
);

const res = eta.renderString(
`<% layout("@block-multi") %>body<% block('styles', () => { %>S<% }) %><% block('scripts', () => { %>J<% }) %>`,
{},
);

expect(res).toEqual("<head>S</head><footer>J</footer>body");
});

it("block accessing outer scope data", () => {
eta.loadTemplate("@block-data", `<%~ block('title') %>|<%~ it.body %>`);

const res = eta.renderString(
`<% layout("@block-data") %>body<% block('title', () => { %><%= it.name %><% }) %>`,
{ name: "Ada" },
);

expect(res).toEqual("Ada|body");
});

it("block doesn't leak into it.body", () => {
eta.loadTemplate(
"@block-noleak",
`[<%~ block('extra') %>][<%~ it.body %>]`,
);

const res = eta.renderString(
`<% layout("@block-noleak") %>body<% block('extra', () => { %>X<% }) %>`,
{},
);

expect(res).toEqual("[X][body]");
});

it("blockAsync with async content", async () => {
eta.loadTemplate(
"@wrapper",
`<div id="wrapper"><%~ it.content %></div>`,
"@block-async-layout",
`<%~ await blockAsync('content') %>|<%~ it.body %>`,
{ async: true },
);

const res = await eta.renderStringAsync(
`<% layout("@block-async-layout") %>body<% await blockAsync('content', async () => { %><%= await it.getData() %><% }) %>`,
{ getData: () => Promise.resolve("async-val") },
);

expect(res).toEqual("async-val|body");
});
});

describe("capture", () => {
const eta = new Eta();

it("captures inline HTML and passes it to an included template", () => {
eta.loadTemplate("@wrapper", `<div id="wrapper"><%~ it.content %></div>`);

const res = eta.renderString(
`<%~ include("@wrapper", {content: capture(() => { %><h1>Hello</h1><% })}) %>`,
{},
Expand Down Expand Up @@ -427,10 +518,7 @@ describe("capture", () => {
});

it("works with async rendering", async () => {
eta.loadTemplate(
"@async-wrapper",
`<div><%~ it.content %></div>`,
);
eta.loadTemplate("@async-wrapper", `<div><%~ it.content %></div>`);

const res = await eta.renderStringAsync(
`<%~ include("@async-wrapper", {content: capture(() => { %><p>async content</p><% })}) %>`,
Expand Down Expand Up @@ -463,10 +551,7 @@ describe("capture", () => {
});

it("captureAsync works with async content", async () => {
eta.loadTemplate(
"@async-slot",
`<section><%~ it.content %></section>`,
);
eta.loadTemplate("@async-slot", `<section><%~ it.content %></section>`);

const res = await eta.renderStringAsync(
`<%~ include("@async-slot", {content: await captureAsync(async () => { %><p><%= await it.getData() %></p><% })}) %>`,
Expand Down
Loading