• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sunng87 / handlebars-rust / 20657583393

02 Jan 2026 12:10PM UTC coverage: 83.449% (-0.3%) from 83.707%
20657583393

Pull #733

github

web-flow
Merge 2226d6e72 into 718db6bb1
Pull Request #733: fix: block scoped inline

26 of 27 new or added lines in 4 files covered. (96.3%)

10 existing lines in 2 files now uncovered.

1679 of 2012 relevant lines covered (83.45%)

7.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/src/partial.rs
1
use std::collections::HashMap;
2

3
use serde_json::Value as Json;
4

5
use crate::block::BlockContext;
6
use crate::context::{merge_json, Context};
7
use crate::error::RenderError;
8
use crate::output::Output;
9
use crate::registry::Registry;
10
use crate::render::{Decorator, RenderContext, Renderable};
11
use crate::template::Template;
12
use crate::{Path, RenderErrorReason, StringOutput};
13

14
pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";
15

16
fn find_partial<'reg: 'rc, 'rc>(
6✔
17
    rc: &RenderContext<'reg, 'rc>,
18
    r: &'reg Registry<'reg>,
19
    name: &str,
20
) -> Result<Option<&'rc Template>, RenderError> {
21
    if let Some(partial) = rc.get_partial(name) {
7✔
22
        return Ok(Some(partial));
4✔
23
    }
24

25
    if let Some(t) = rc.get_dev_mode_template(name) {
4✔
26
        return Ok(Some(t));
×
27
    }
28

29
    if let Some(t) = r.get_template(name) {
4✔
30
        return Ok(Some(t));
4✔
31
    }
32

33
    Ok(None)
2✔
34
}
35

36
pub fn expand_partial<'reg: 'rc, 'rc>(
8✔
37
    d: &Decorator<'rc>,
38
    r: &'reg Registry<'reg>,
39
    ctx: &'rc Context,
40
    rc: &mut RenderContext<'reg, 'rc>,
41
    out: &mut dyn Output,
42
) -> Result<(), RenderError> {
43
    let tname = d.name();
8✔
44

45
    let current_template_before = rc.get_current_template_name();
8✔
46
    let indent_before = rc.get_indent_string().cloned();
8✔
47

48
    if rc.is_current_template(tname) {
16✔
49
        return Err(RenderErrorReason::CannotIncludeSelf.into());
2✔
50
    }
51

52
    // check if referencing partial_block
53
    if tname == PARTIAL_BLOCK {
16✔
54
        if let Some(Some(content)) = rc.peek_partial_block() {
5✔
55
            out.write(content.as_str())?;
1✔
56
            Ok(())
1✔
57
        } else {
58
            // no partial_block for this scope
59
            Err(RenderErrorReason::PartialBlockNotFound.into())
2✔
60
        }
61
    } else {
62
        // normal partial
63
        let original_partial = find_partial(rc, r, tname)?;
14✔
64

65
        let partial = if let Some(partial) = original_partial {
14✔
66
            partial
7✔
67
        } else if let Some(inner_template) = d.template() {
4✔
NEW
68
            inner_template
×
69
        } else {
70
            return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
2✔
71
        };
72

73
        // hash
74
        let hash_ctx = d
×
75
            .hash()
76
            .iter()
77
            .map(|(k, v)| (*k, v.value()))
13✔
78
            .collect::<HashMap<&str, &Json>>();
79

80
        let mut partial_include_block = BlockContext::new();
6✔
81
        // overwrite parent block's params
82
        for (name, value) in &hash_ctx {
13✔
83
            partial_include_block
3✔
84
                .set_block_param(name, crate::BlockParamHolder::Value((*value).clone()));
6✔
85
        }
86

87
        // evaluate context for partial
88
        let merged_context = if let Some(p) = d.param(0) {
7✔
89
            if let Some(relative_path) = p.relative_path() {
6✔
90
                // path as parameter provided
91
                if let Some(rc_context) = rc.context() {
6✔
92
                    merge_json(
93
                        rc.evaluate(&rc_context, relative_path)?.as_json(),
×
94
                        &hash_ctx,
×
95
                    )
96
                } else {
97
                    merge_json(rc.evaluate(ctx, relative_path)?.as_json(), &hash_ctx)
9✔
98
                }
99
            } else {
100
                // literal provided
101
                merge_json(p.value(), &hash_ctx)
2✔
102
            }
103
        } else {
104
            // use current path
105
            if let Some(rc_context) = rc.context() {
14✔
106
                merge_json(
107
                    rc.evaluate2(&rc_context, &Path::current())?.as_json(),
3✔
108
                    &hash_ctx,
×
109
                )
110
            } else {
111
                merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx)
25✔
112
            }
113
        };
114
        partial_include_block.set_base_value(merged_context);
6✔
115

116
        // Push partial's context block (doesn't clear parent blocks)
117
        // This allows inline partials from parent blocks to remain accessible
118
        rc.push_block(partial_include_block);
8✔
119

120
        // check if this inclusion has a block, make sure we are not rendering
121
        // the template itself
122
        if original_partial.is_some() {
8✔
123
            if let Some(current_parital_block) = d.template() {
18✔
124
                let mut tmp_out = StringOutput::new();
2✔
125
                // render will also eval the block, so any inline directives will be
126
                // evaluated
127
                current_parital_block.render(r, ctx, rc, &mut tmp_out)?;
6✔
128
                rc.push_partial_block(Some(tmp_out.into_string()?));
2✔
129
            } else {
130
                rc.push_partial_block(None);
16✔
131
            }
132
        } else {
133
            rc.push_partial_block(None);
4✔
134
        }
135

136
        // indent
137
        rc.set_indent_string(d.indent().cloned());
15✔
138

139
        let result = partial.render(r, ctx, rc, out);
7✔
140

141
        // cleanup
142
        let trailing_newline = rc.get_trailine_newline();
13✔
143

144
        // remove current partial_block
145
        rc.pop_partial_block();
7✔
146

147
        // Remove partial's context block
148
        rc.pop_block();
7✔
149
        rc.set_trailing_newline(trailing_newline);
8✔
150
        rc.set_current_template_name(current_template_before);
7✔
151
        rc.set_indent_string(indent_before);
8✔
152

153
        result
8✔
154
    }
155
}
156

157
#[cfg(test)]
158
mod test {
159
    use crate::context::Context;
160
    use crate::error::RenderError;
161
    use crate::output::Output;
162
    use crate::registry::Registry;
163
    use crate::render::{Helper, RenderContext};
164
    use crate::{Decorator, RenderErrorReason};
165

166
    #[test]
167
    fn test() {
168
        let mut handlebars = Registry::new();
169
        assert!(handlebars
170
            .register_template_string("t0", "{{> t1}}")
171
            .is_ok());
172
        assert!(handlebars
173
            .register_template_string("t1", "{{this}}")
174
            .is_ok());
175
        assert!(handlebars
176
            .register_template_string("t2", "{{#> t99}}not there{{/t99}}")
177
            .is_ok());
178
        assert!(handlebars
179
            .register_template_string("t3", "{{#*inline \"t31\"}}{{this}}{{/inline}}{{> t31}}")
180
            .is_ok());
181
        assert!(handlebars
182
            .register_template_string(
183
                "t4",
184
                "{{#> t5}}{{#*inline \"nav\"}}navbar{{/inline}}{{/t5}}"
185
            )
186
            .is_ok());
187
        assert!(handlebars
188
            .register_template_string("t5", "include {{> nav}}")
189
            .is_ok());
190
        assert!(handlebars
191
            .register_template_string("t6", "{{> t1 a}}")
192
            .is_ok());
193
        assert!(handlebars
194
            .register_template_string(
195
                "t7",
196
                "{{#*inline \"t71\"}}{{a}}{{/inline}}{{> t71 a=\"world\"}}"
197
            )
198
            .is_ok());
199
        assert!(handlebars.register_template_string("t8", "{{a}}").is_ok());
200
        assert!(handlebars
201
            .register_template_string("t9", "{{> t8 a=2}}")
202
            .is_ok());
203

204
        assert_eq!(handlebars.render("t0", &1).ok().unwrap(), "1".to_string());
205
        assert_eq!(
206
            handlebars.render("t2", &1).ok().unwrap(),
207
            "not there".to_string()
208
        );
209
        assert_eq!(handlebars.render("t3", &1).ok().unwrap(), "1".to_string());
210
        assert_eq!(
211
            handlebars.render("t4", &1).ok().unwrap(),
212
            "include navbar".to_string()
213
        );
214
        assert_eq!(
215
            handlebars.render("t6", &json!({"a": "2"})).ok().unwrap(),
216
            "2".to_string()
217
        );
218
        assert_eq!(
219
            handlebars.render("t7", &1).ok().unwrap(),
220
            "world".to_string()
221
        );
222
        assert_eq!(handlebars.render("t9", &1).ok().unwrap(), "2".to_string());
223
    }
224

225
    #[test]
226
    fn test_include_partial_block() {
227
        let t0 = "hello {{> @partial-block}}";
228
        let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
229

230
        let mut handlebars = Registry::new();
231
        assert!(handlebars.register_template_string("t0", t0).is_ok());
232
        assert!(handlebars.register_template_string("t1", t1).is_ok());
233

234
        let r0 = handlebars.render("t1", &true);
235
        assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
236
    }
237

238
    #[test]
239
    fn test_self_inclusion() {
240
        let t0 = "hello {{> t1}} {{> t0}}";
241
        let t1 = "some template";
242
        let mut handlebars = Registry::new();
243
        assert!(handlebars.register_template_string("t0", t0).is_ok());
244
        assert!(handlebars.register_template_string("t1", t1).is_ok());
245

246
        let r0 = handlebars.render("t0", &true);
247
        assert!(r0.is_err());
248
    }
249

250
    #[test]
251
    fn test_issue_143() {
252
        let main_template = "one{{> two }}three{{> two }}";
253
        let two_partial = "--- two ---";
254

255
        let mut handlebars = Registry::new();
256
        assert!(handlebars
257
            .register_template_string("template", main_template)
258
            .is_ok());
259
        assert!(handlebars
260
            .register_template_string("two", two_partial)
261
            .is_ok());
262

263
        let r0 = handlebars.render("template", &true);
264
        assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
265
    }
266

267
    #[test]
268
    fn test_hash_context_outscope() {
269
        let main_template = "In: {{> p a=2}} Out: {{a}}";
270
        let p_partial = "{{a}}";
271

272
        let mut handlebars = Registry::new();
273
        assert!(handlebars
274
            .register_template_string("template", main_template)
275
            .is_ok());
276
        assert!(handlebars.register_template_string("p", p_partial).is_ok());
277

278
        let r0 = handlebars.render("template", &true);
279
        assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
280
    }
281

282
    #[test]
283
    fn test_partial_context_hash() {
284
        let mut hbs = Registry::new();
285
        hbs.register_template_string("one", "This is a test. {{> two name=\"fred\" }}")
286
            .unwrap();
287
        hbs.register_template_string("two", "Lets test {{name}}")
288
            .unwrap();
289
        assert_eq!(
290
            "This is a test. Lets test fred",
291
            hbs.render("one", &0).unwrap()
292
        );
293
    }
294

295
    #[test]
296
    fn teset_partial_context_with_both_hash_and_param() {
297
        let mut hbs = Registry::new();
298
        hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}")
299
            .unwrap();
300
        hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}")
301
            .unwrap();
302
        assert_eq!(
303
            "This is a test. Lets test fred and tom",
304
            hbs.render("one", &json!({"root_name": "tom"})).unwrap()
305
        );
306
    }
307

308
    #[test]
309
    fn test_partial_subexpression_context_hash() {
310
        let mut hbs = Registry::new();
311
        hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
312
            .unwrap();
313
        hbs.register_template_string("two", "Lets test {{name}}")
314
            .unwrap();
315

316
        hbs.register_helper(
317
            "x",
318
            Box::new(
319
                |_: &Helper<'_>,
320
                 _: &Registry<'_>,
321
                 _: &Context,
322
                 _: &mut RenderContext<'_, '_>,
323
                 out: &mut dyn Output|
324
                 -> Result<(), RenderError> {
325
                    out.write("two")?;
326
                    Ok(())
327
                },
328
            ),
329
        );
330
        assert_eq!(
331
            "This is a test. Lets test fred",
332
            hbs.render("one", &0).unwrap()
333
        );
334
    }
335

336
    #[test]
337
    fn test_nested_partial_scope() {
338
        let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
339
        let data = json!({"c": [{"b": true}, {"b": false}]});
340

341
        let mut handlebars = Registry::new();
342
        assert!(handlebars.register_template_string("t", t).is_ok());
343
        let r0 = handlebars.render("t", &data);
344
        assert_eq!(r0.ok().unwrap(), "2 true2 false");
345
    }
346

347
    #[test]
348
    fn test_nested_partial_block() {
349
        let mut handlebars = Registry::new();
350
        let template1 = "<outer>{{> @partial-block }}</outer>";
351
        let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
352
        let template3 = "{{#> t2 }}Hello{{/ t2 }}";
353

354
        handlebars
355
            .register_template_string("t1", template1)
356
            .unwrap();
357
        handlebars
358
            .register_template_string("t2", template2)
359
            .unwrap();
360

361
        let page = handlebars.render_template(template3, &json!({})).unwrap();
362
        assert_eq!("<outer><inner>Hello</inner></outer>", page);
363
    }
364

365
    #[test]
366
    fn test_subexpression_partial_block() {
367
        let mut handlebars = Registry::new();
368
        let template1 = "<outer>{{> @partial-block }}</outer>";
369
        let template2 = "{{#> (x 'foo') }}<inner>{{> @partial-block }}</inner>{{/}}";
370
        let template3 = "{{#> (y this) }}Hello{{/}} World";
371

372
        handlebars.register_helper(
373
            "x",
374
            Box::new(
375
                |_: &Helper<'_>,
376
                 _: &Registry<'_>,
377
                 _: &Context,
378
                 _: &mut RenderContext<'_, '_>,
379
                 out: &mut dyn Output|
380
                 -> Result<(), RenderError> {
381
                    out.write("t1")?;
382
                    Ok(())
383
                },
384
            ),
385
        );
386
        handlebars.register_helper(
387
            "y",
388
            Box::new(
389
                |_: &Helper<'_>,
390
                 _: &Registry<'_>,
391
                 _: &Context,
392
                 _: &mut RenderContext<'_, '_>,
393
                 out: &mut dyn Output|
394
                 -> Result<(), RenderError> {
395
                    out.write("t2")?;
396
                    Ok(())
397
                },
398
            ),
399
        );
400
        handlebars
401
            .register_template_string("t1", template1)
402
            .unwrap();
403
        handlebars
404
            .register_template_string("t2", template2)
405
            .unwrap();
406

407
        let page = handlebars.render_template(template3, &json!({})).unwrap();
408
        assert_eq!("<outer><inner>Hello</inner></outer> World", page);
409
    }
410

411
    #[test]
412
    fn test_up_to_partial_level() {
413
        let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
414
        let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
415

416
        let data = json!({ "fruits": ["carrot", "tomato"] });
417

418
        let mut handlebars = Registry::new();
419
        handlebars.register_template_string("outer", outer).unwrap();
420
        handlebars.register_template_string("inner", inner).unwrap();
421

422
        assert_eq!(
423
            handlebars.render("outer", &data).unwrap(),
424
            "fruit: carrot,fruit: tomato,"
425
        );
426
    }
427

428
    #[test]
429
    fn line_stripping_with_inline_and_partial() {
430
        let tpl0 = r#"{{#*inline "foo"}}foo
431
{{/inline}}
432
{{> foo}}
433
{{> foo}}
434
{{> foo}}"#;
435
        let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
436
{{> foo}}
437
{{> foo}}
438
{{> foo}}"#;
439

440
        let hbs = Registry::new();
441
        assert_eq!(
442
            r"foo
443
foo
444
foo
445
",
446
            hbs.render_template(tpl0, &json!({})).unwrap()
447
        );
448
        assert_eq!(
449
            r"
450
foofoofoo",
451
            hbs.render_template(tpl1, &json!({})).unwrap()
452
        );
453
    }
454

455
    #[test]
456
    fn test_partial_indent() {
457
        let outer = r"                {{> inner inner_solo}}
458

459
{{#each inners}}
460
                {{> inner}}
461
{{/each}}
462

463
        {{#each inners}}
464
        {{> inner}}
465
        {{/each}}
466
";
467
        let inner = r"name: {{name}}
468
";
469

470
        let mut hbs = Registry::new();
471

472
        hbs.register_template_string("inner", inner).unwrap();
473
        hbs.register_template_string("outer", outer).unwrap();
474

475
        let result = hbs
476
            .render(
477
                "outer",
478
                &json!({
479
                    "inner_solo": {"name": "inner_solo"},
480
                    "inners": [
481
                        {"name": "hello"},
482
                        {"name": "there"}
483
                    ]
484
                }),
485
            )
486
            .unwrap();
487

488
        assert_eq!(
489
            result,
490
            r"                name: inner_solo
491

492
                name: hello
493
                name: there
494

495
        name: hello
496
        name: there
497
"
498
        );
499
    }
500
    // Rule::partial_expression should not trim leading indent  by default
501

502
    #[test]
503
    fn test_partial_prevent_indent() {
504
        let outer = r"                {{> inner inner_solo}}
505

506
{{#each inners}}
507
                {{> inner}}
508
{{/each}}
509

510
        {{#each inners}}
511
        {{> inner}}
512
        {{/each}}
513
";
514
        let inner = r"name: {{name}}
515
";
516

517
        let mut hbs = Registry::new();
518
        hbs.set_prevent_indent(true);
519

520
        hbs.register_template_string("inner", inner).unwrap();
521
        hbs.register_template_string("outer", outer).unwrap();
522

523
        let result = hbs
524
            .render(
525
                "outer",
526
                &json!({
527
                    "inner_solo": {"name": "inner_solo"},
528
                    "inners": [
529
                        {"name": "hello"},
530
                        {"name": "there"}
531
                    ]
532
                }),
533
            )
534
            .unwrap();
535

536
        assert_eq!(
537
            result,
538
            r"                name: inner_solo
539

540
                name: hello
541
                name: there
542

543
        name: hello
544
        name: there
545
"
546
        );
547
    }
548

549
    #[test]
550
    fn test_nested_partials() {
551
        let mut hb = Registry::new();
552
        hb.register_template_string("partial", "{{> @partial-block}}")
553
            .unwrap();
554
        hb.register_template_string(
555
            "index",
556
            r"{{#>partial}}
557
    Yo
558
    {{#>partial}}
559
    Yo 2
560
    {{/partial}}
561
{{/partial}}",
562
        )
563
        .unwrap();
564
        assert_eq!(
565
            r"    Yo
566
    Yo 2
567
",
568
            hb.render("index", &()).unwrap()
569
        );
570

571
        hb.register_template_string("partial2", "{{> @partial-block}}")
572
            .unwrap();
573
        let r2 = hb
574
            .render_template(
575
                r"{{#> partial}}
576
{{#> partial2}}
577
:(
578
{{/partial2}}
579
{{/partial}}",
580
                &(),
581
            )
582
            .unwrap();
583
        assert_eq!(":(\n", r2);
584
    }
585

586
    #[test]
587
    fn test_partial_context_issue_495() {
588
        let mut hb = Registry::new();
589
        hb.register_template_string(
590
            "t1",
591
            r#"{{~#*inline "displayName"~}}
592
Template:{{name}}
593
{{/inline}}
594
{{#each data as |name|}}
595
Name:{{name}}
596
{{>displayName name="aaaa"}}
597
{{/each}}"#,
598
        )
599
        .unwrap();
600

601
        hb.register_template_string(
602
            "t2",
603
            r#"{{~#*inline "displayName"~}}
604
Template:{{this}}
605
{{/inline}}
606
{{#each data as |name|}}
607
Name:{{name}}
608
{{>displayName}}
609
{{/each}}"#,
610
        )
611
        .unwrap();
612

613
        let data = json!({
614
            "data": ["hudel", "test"]
615
        });
616

617
        assert_eq!(
618
            r"Name:hudel
619
Template:aaaa
620
Name:test
621
Template:aaaa
622
",
623
            hb.render("t1", &data).unwrap()
624
        );
625
        assert_eq!(
626
            r"Name:hudel
627
Template:hudel
628
Name:test
629
Template:test
630
",
631
            hb.render("t2", &data).unwrap()
632
        );
633
    }
634

635
    #[test]
636
    fn test_multiline_partial_indent() {
637
        let mut hb = Registry::new();
638

639
        hb.register_template_string(
640
            "t1",
641
            r#"{{#*inline "thepartial"}}
642
  inner first line
643
  inner second line
644
{{/inline}}
645
  {{> thepartial}}
646
outer third line"#,
647
        )
648
        .unwrap();
649
        assert_eq!(
650
            r"    inner first line
651
    inner second line
652
outer third line",
653
            hb.render("t1", &()).unwrap()
654
        );
655

656
        hb.register_template_string(
657
            "t2",
658
            r#"{{#*inline "thepartial"}}inner first line
659
inner second line
660
{{/inline}}
661
  {{> thepartial}}
662
outer third line"#,
663
        )
664
        .unwrap();
665
        assert_eq!(
666
            r"  inner first line
667
  inner second line
668
outer third line",
669
            hb.render("t2", &()).unwrap()
670
        );
671

672
        hb.register_template_string(
673
            "t3",
674
            r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
675
  {{> thepartial}}
676
outer third line"#,
677
        )
678
        .unwrap();
679
        assert_eq!(
680
            r"
681
  inner first line
682
  inner second lineouter third line",
683
            hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
684
                .unwrap()
685
        );
686

687
        hb.register_template_string(
688
            "t4",
689
            r#"{{#*inline "thepartial"}}
690
  inner first line
691
  inner second line
692
{{/inline}}
693
  {{~> thepartial}}
694
outer third line"#,
695
        )
696
        .unwrap();
697
        assert_eq!(
698
            r"  inner first line
699
  inner second line
700
outer third line",
701
            hb.render("t4", &()).unwrap()
702
        );
703

704
        let mut hb2 = Registry::new();
705
        hb2.set_prevent_indent(true);
706

707
        hb2.register_template_string(
708
            "t1",
709
            r#"{{#*inline "thepartial"}}
710
  inner first line
711
  inner second line
712
{{/inline}}
713
  {{> thepartial}}
714
outer third line"#,
715
        )
716
        .unwrap();
717
        assert_eq!(
718
            r"    inner first line
719
  inner second line
720
outer third line",
721
            hb2.render("t1", &()).unwrap()
722
        );
723
    }
724

725
    #[test]
726
    fn test_indent_level_on_nested_partials() {
727
        let nested_partial = "
728
<div>
729
    content
730
</div>
731
";
732
        let partial = "
733
<div>
734
    {{>nested_partial}}
735
</div>
736
";
737

738
        let partial_indented = "
739
<div>
740
    {{>partial}}
741
</div>
742
";
743

744
        let result = "
745
<div>
746
    <div>
747
        <div>
748
            content
749
        </div>
750
    </div>
751
</div>
752
";
753

754
        let mut hb = Registry::new();
755
        hb.register_template_string("nested_partial", nested_partial.trim_start())
756
            .unwrap();
757
        hb.register_template_string("partial", partial.trim_start())
758
            .unwrap();
759
        hb.register_template_string("partial_indented", partial_indented.trim_start())
760
            .unwrap();
761

762
        let s = hb.render("partial_indented", &()).unwrap();
763

764
        assert_eq!(&s, result.trim_start());
765
    }
766

767
    #[test]
768
    fn test_issue_534() {
769
        let t1 = "{{title}}";
770
        let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";
771

772
        let data = json!({
773
          "modules": [
774
            {"module": "t1", "content": {"title": "foo"}},
775
            {"module": "t1", "content": {"title": "bar"}},
776
          ]
777
        });
778

779
        let mut hbs = Registry::new();
780
        hbs.register_template_string("t1", t1).unwrap();
781
        hbs.register_template_string("t2", t2).unwrap();
782

783
        assert_eq!("foobar", hbs.render("t2", &data).unwrap());
784
    }
785

786
    #[test]
787
    fn test_partial_not_found() {
788
        let t1 = "{{> bar}}";
789
        let hbs = Registry::new();
790
        assert!(hbs.render_template(t1, &()).is_err());
791
    }
792

793
    #[test]
794
    fn test_issue_643_this_context() {
795
        let t1 = "{{this}}";
796
        let t2 = "{{> t1 \"hello world\"}}";
797

798
        let mut hbs = Registry::new();
799
        hbs.register_template_string("t1", t1).unwrap();
800
        hbs.register_template_string("t2", t2).unwrap();
801

802
        assert_eq!("hello world", hbs.render("t2", &()).unwrap());
803

804
        let t1 = "{{a}} {{[0]}} {{[1]}}";
805
        let t2 = "{{> t1 \"hello world\" a=1}}";
806

807
        let mut hbs = Registry::new();
808
        hbs.register_template_string("t1", t1).unwrap();
809
        hbs.register_template_string("t2", t2).unwrap();
810

811
        assert_eq!("1 h e", hbs.render("t2", &()).unwrap());
812

813
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
814
        let t2 = "{{> t1 a=1}}";
815

816
        let mut hbs = Registry::new();
817
        hbs.register_template_string("t1", t1).unwrap();
818
        hbs.register_template_string("t2", t2).unwrap();
819

820
        assert_eq!("a:1,", hbs.render("t2", &()).unwrap());
821

822
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
823
        let t2 = "{{> t1 a=1}}";
824

825
        let mut hbs = Registry::new();
826
        hbs.register_template_string("t1", t1).unwrap();
827
        hbs.register_template_string("t2", t2).unwrap();
828

829
        assert_eq!("a:1,b:2,", hbs.render("t2", &json!({"b": 2})).unwrap());
830

831
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
832
        let t2 = "{{> t1 b a=1}}";
833

834
        let mut hbs = Registry::new();
835
        hbs.register_template_string("t1", t1).unwrap();
836
        hbs.register_template_string("t2", t2).unwrap();
837

838
        assert_eq!("a:1,", hbs.render("t2", &json!({"b": 2})).unwrap());
839
    }
840

841
    #[test]
842
    fn test_nested_partial_block_scope_issue() {
843
        let mut hs = Registry::new();
844
        hs.register_template_string("primary", "{{> @partial-block }}")
845
            .unwrap();
846
        hs.register_template_string("secondary", "{{#*inline \"inl\"}}Bug{{/inline}}{{#>primary}}{{> @partial-block }}{{>inl}}{{/primary}}").unwrap();
847
        hs.register_template_string("current", "{{>secondary}}")
848
            .unwrap();
849

850
        assert!(matches!(
851
            hs.render("current", &()).unwrap_err().reason(),
852
            RenderErrorReason::PartialBlockNotFound
853
        ));
854

855
        let mut hs = Registry::new();
856
        hs.register_template_string("primary", "{{> @partial-block }}")
857
            .unwrap();
858
        hs.register_template_string("secondary", "{{#*inline \"inl\"}}Bug{{/inline}}{{#>primary}}{{> @partial-block }}{{>inl}}{{/primary}}").unwrap();
859
        hs.register_template_string("current", "{{#>secondary}}Not a {{/secondary}}")
860
            .unwrap();
861

862
        assert_eq!(hs.render("current", &()).unwrap(), "Not a Bug");
863
    }
864

865
    #[test]
866
    fn test_referencing_data_in_partial() {
867
        fn set_decorator(
868
            d: &Decorator<'_>,
869
            _: &Registry<'_>,
870
            _ctx: &Context,
871
            rc: &mut RenderContext<'_, '_>,
872
        ) -> Result<(), RenderError> {
873
            let data_to_set = d.hash();
874
            for (k, v) in data_to_set {
875
                set_in_context(rc, k, v.value().clone());
876
            }
877
            Ok(())
878
        }
879

880
        /// Sets a variable to a value within the context.
881
        fn set_in_context(rc: &mut RenderContext<'_, '_>, key: &str, value: serde_json::Value) {
882
            let mut gctx = match rc.context() {
883
                Some(c) => (*c).clone(),
884
                None => Context::wraps(serde_json::Value::Object(serde_json::Map::new())).unwrap(),
885
            };
886
            if let serde_json::Value::Object(m) = gctx.data_mut() {
887
                m.insert(key.to_string(), value);
888
                rc.set_context(gctx);
889
            } else {
890
                panic!("expected object in context");
891
            }
892
        }
893

894
        let mut handlebars = Registry::new();
895
        handlebars.register_decorator("set", Box::new(set_decorator));
896

897
        handlebars_helper!(lower: |s: str| s.to_lowercase());
898
        handlebars.register_helper("lower", Box::new(lower));
899
        handlebars
900
            .register_template_string(
901
                "an-included-file",
902
                "This file is included.\n\nSee {{lower somevalue}}\n",
903
            )
904
            .unwrap();
905

906
        let data = serde_json::json!({});
907
        assert_eq!(
908
            handlebars
909
                .render_template(
910
                    r#"
911
{{~*set somevalue="Example"}}
912
{{> an-included-file }}
913
"#,
914
                    &data
915
                )
916
                .unwrap(),
917
            "This file is included.\n\nSee example\n"
918
        );
919
    }
920
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc