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

sunng87 / handlebars-rust / 20657384441

02 Jan 2026 11:59AM UTC coverage: 83.474% (-0.2%) from 83.707%
20657384441

Pull #733

github

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

23 of 23 new or added lines in 4 files covered. (100.0%)

11 existing lines in 3 files now uncovered.

1677 of 2009 relevant lines covered (83.47%)

7.25 hits per line

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

91.23
/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>(
7✔
17
    rc: &RenderContext<'reg, 'rc>,
18
    r: &'reg Registry<'reg>,
19
    d: &Decorator<'rc>,
20
    name: &str,
21
) -> Result<Option<&'rc Template>, RenderError> {
22
    if let Some(partial) = rc.get_partial(name) {
6✔
23
        return Ok(Some(partial));
5✔
24
    }
25

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

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

34
    if let Some(tpl) = d.template() {
2✔
35
        return Ok(Some(tpl));
2✔
36
    }
37

38
    Ok(None)
1✔
39
}
40

41
pub fn expand_partial<'reg: 'rc, 'rc>(
6✔
42
    d: &Decorator<'rc>,
43
    r: &'reg Registry<'reg>,
44
    ctx: &'rc Context,
45
    rc: &mut RenderContext<'reg, 'rc>,
46
    out: &mut dyn Output,
47
) -> Result<(), RenderError> {
48
    let tname = d.name();
7✔
49

50
    let current_template_before = rc.get_current_template_name();
6✔
51
    let indent_before = rc.get_indent_string().cloned();
7✔
52

53
    if rc.is_current_template(tname) {
13✔
54
        return Err(RenderErrorReason::CannotIncludeSelf.into());
2✔
55
    }
56

57
    // check if referencing partial_block
58
    if tname == PARTIAL_BLOCK {
13✔
59
        if let Some(Some(content)) = rc.peek_partial_block() {
5✔
60
            out.write(content.as_str())?;
1✔
61
            Ok(())
1✔
62
        } else {
63
            // no partial_block for this scope
64
            Err(RenderErrorReason::PartialBlockNotFound.into())
2✔
65
        }
66
    } else {
67
        // normal partial
68
        let partial = find_partial(rc, r, d, tname)?;
12✔
69
        let Some(partial) = partial else {
7✔
70
            return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
2✔
71
        };
72

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

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

87
        // evaluate context for partial
88
        let merged_context = if let Some(p) = d.param(0) {
6✔
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() {
13✔
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)
27✔
112
            }
113
        };
114
        partial_include_block.set_base_value(merged_context);
7✔
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);
7✔
119

120
        // check if this inclusion has a block
121
        if let Some(current_parital_block) = d.template() {
6✔
122
            let mut tmp_out = StringOutput::new();
2✔
123
            // render will also eval the block, so any inline directives will be
124
            // evaluated
125
            current_parital_block.render(r, ctx, rc, &mut tmp_out)?;
6✔
126
            rc.push_partial_block(Some(tmp_out.into_string()?));
3✔
127
        } else {
128
            rc.push_partial_block(None);
13✔
129
        }
130

131
        // indent
132
        rc.set_indent_string(d.indent().cloned());
14✔
133

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

136
        // cleanup
137
        let trailing_newline = rc.get_trailine_newline();
14✔
138

139
        // remove current partial_block
140
        rc.pop_partial_block();
7✔
141

142
        // Remove partial's context block
143
        rc.pop_block();
8✔
144
        rc.set_trailing_newline(trailing_newline);
9✔
145
        rc.set_current_template_name(current_template_before);
9✔
146
        rc.set_indent_string(indent_before);
9✔
147

148
        result
9✔
149
    }
150
}
151

152
#[cfg(test)]
153
mod test {
154
    use crate::context::Context;
155
    use crate::error::RenderError;
156
    use crate::output::Output;
157
    use crate::registry::Registry;
158
    use crate::render::{Helper, RenderContext};
159
    use crate::{Decorator, RenderErrorReason};
160

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

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

220
    #[test]
221
    fn test_include_partial_block() {
222
        let t0 = "hello {{> @partial-block}}";
223
        let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
224

225
        let mut handlebars = Registry::new();
226
        assert!(handlebars.register_template_string("t0", t0).is_ok());
227
        assert!(handlebars.register_template_string("t1", t1).is_ok());
228

229
        let r0 = handlebars.render("t1", &true);
230
        assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
231
    }
232

233
    #[test]
234
    fn test_self_inclusion() {
235
        let t0 = "hello {{> t1}} {{> t0}}";
236
        let t1 = "some template";
237
        let mut handlebars = Registry::new();
238
        assert!(handlebars.register_template_string("t0", t0).is_ok());
239
        assert!(handlebars.register_template_string("t1", t1).is_ok());
240

241
        let r0 = handlebars.render("t0", &true);
242
        assert!(r0.is_err());
243
    }
244

245
    #[test]
246
    fn test_issue_143() {
247
        let main_template = "one{{> two }}three{{> two }}";
248
        let two_partial = "--- two ---";
249

250
        let mut handlebars = Registry::new();
251
        assert!(handlebars
252
            .register_template_string("template", main_template)
253
            .is_ok());
254
        assert!(handlebars
255
            .register_template_string("two", two_partial)
256
            .is_ok());
257

258
        let r0 = handlebars.render("template", &true);
259
        assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
260
    }
261

262
    #[test]
263
    fn test_hash_context_outscope() {
264
        let main_template = "In: {{> p a=2}} Out: {{a}}";
265
        let p_partial = "{{a}}";
266

267
        let mut handlebars = Registry::new();
268
        assert!(handlebars
269
            .register_template_string("template", main_template)
270
            .is_ok());
271
        assert!(handlebars.register_template_string("p", p_partial).is_ok());
272

273
        let r0 = handlebars.render("template", &true);
274
        assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
275
    }
276

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

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

303
    #[test]
304
    fn test_partial_subexpression_context_hash() {
305
        let mut hbs = Registry::new();
306
        hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
307
            .unwrap();
308
        hbs.register_template_string("two", "Lets test {{name}}")
309
            .unwrap();
310

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

331
    #[test]
332
    fn test_nested_partial_scope() {
333
        let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
334
        let data = json!({"c": [{"b": true}, {"b": false}]});
335

336
        let mut handlebars = Registry::new();
337
        assert!(handlebars.register_template_string("t", t).is_ok());
338
        let r0 = handlebars.render("t", &data);
339
        assert_eq!(r0.ok().unwrap(), "2 true2 false");
340
    }
341

342
    #[test]
343
    fn test_nested_partial_block() {
344
        let mut handlebars = Registry::new();
345
        let template1 = "<outer>{{> @partial-block }}</outer>";
346
        let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
347
        let template3 = "{{#> t2 }}Hello{{/ t2 }}";
348

349
        handlebars
350
            .register_template_string("t1", template1)
351
            .unwrap();
352
        handlebars
353
            .register_template_string("t2", template2)
354
            .unwrap();
355

356
        let page = handlebars.render_template(template3, &json!({})).unwrap();
357
        assert_eq!("<outer><inner>Hello</inner></outer>", page);
358
    }
359

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

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

402
        let page = handlebars.render_template(template3, &json!({})).unwrap();
403
        assert_eq!("<outer><inner>Hello</inner></outer> World", page);
404
    }
405

406
    #[test]
407
    fn test_up_to_partial_level() {
408
        let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
409
        let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
410

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

413
        let mut handlebars = Registry::new();
414
        handlebars.register_template_string("outer", outer).unwrap();
415
        handlebars.register_template_string("inner", inner).unwrap();
416

417
        assert_eq!(
418
            handlebars.render("outer", &data).unwrap(),
419
            "fruit: carrot,fruit: tomato,"
420
        );
421
    }
422

423
    #[test]
424
    fn line_stripping_with_inline_and_partial() {
425
        let tpl0 = r#"{{#*inline "foo"}}foo
426
{{/inline}}
427
{{> foo}}
428
{{> foo}}
429
{{> foo}}"#;
430
        let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
431
{{> foo}}
432
{{> foo}}
433
{{> foo}}"#;
434

435
        let hbs = Registry::new();
436
        assert_eq!(
437
            r"foo
438
foo
439
foo
440
",
441
            hbs.render_template(tpl0, &json!({})).unwrap()
442
        );
443
        assert_eq!(
444
            r"
445
foofoofoo",
446
            hbs.render_template(tpl1, &json!({})).unwrap()
447
        );
448
    }
449

450
    #[test]
451
    fn test_partial_indent() {
452
        let outer = r"                {{> inner inner_solo}}
453

454
{{#each inners}}
455
                {{> inner}}
456
{{/each}}
457

458
        {{#each inners}}
459
        {{> inner}}
460
        {{/each}}
461
";
462
        let inner = r"name: {{name}}
463
";
464

465
        let mut hbs = Registry::new();
466

467
        hbs.register_template_string("inner", inner).unwrap();
468
        hbs.register_template_string("outer", outer).unwrap();
469

470
        let result = hbs
471
            .render(
472
                "outer",
473
                &json!({
474
                    "inner_solo": {"name": "inner_solo"},
475
                    "inners": [
476
                        {"name": "hello"},
477
                        {"name": "there"}
478
                    ]
479
                }),
480
            )
481
            .unwrap();
482

483
        assert_eq!(
484
            result,
485
            r"                name: inner_solo
486

487
                name: hello
488
                name: there
489

490
        name: hello
491
        name: there
492
"
493
        );
494
    }
495
    // Rule::partial_expression should not trim leading indent  by default
496

497
    #[test]
498
    fn test_partial_prevent_indent() {
499
        let outer = r"                {{> inner inner_solo}}
500

501
{{#each inners}}
502
                {{> inner}}
503
{{/each}}
504

505
        {{#each inners}}
506
        {{> inner}}
507
        {{/each}}
508
";
509
        let inner = r"name: {{name}}
510
";
511

512
        let mut hbs = Registry::new();
513
        hbs.set_prevent_indent(true);
514

515
        hbs.register_template_string("inner", inner).unwrap();
516
        hbs.register_template_string("outer", outer).unwrap();
517

518
        let result = hbs
519
            .render(
520
                "outer",
521
                &json!({
522
                    "inner_solo": {"name": "inner_solo"},
523
                    "inners": [
524
                        {"name": "hello"},
525
                        {"name": "there"}
526
                    ]
527
                }),
528
            )
529
            .unwrap();
530

531
        assert_eq!(
532
            result,
533
            r"                name: inner_solo
534

535
                name: hello
536
                name: there
537

538
        name: hello
539
        name: there
540
"
541
        );
542
    }
543

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

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

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

596
        hb.register_template_string(
597
            "t2",
598
            r#"{{~#*inline "displayName"~}}
599
Template:{{this}}
600
{{/inline}}
601
{{#each data as |name|}}
602
Name:{{name}}
603
{{>displayName}}
604
{{/each}}"#,
605
        )
606
        .unwrap();
607

608
        let data = json!({
609
            "data": ["hudel", "test"]
610
        });
611

612
        assert_eq!(
613
            r"Name:hudel
614
Template:aaaa
615
Name:test
616
Template:aaaa
617
",
618
            hb.render("t1", &data).unwrap()
619
        );
620
        assert_eq!(
621
            r"Name:hudel
622
Template:hudel
623
Name:test
624
Template:test
625
",
626
            hb.render("t2", &data).unwrap()
627
        );
628
    }
629

630
    #[test]
631
    fn test_multiline_partial_indent() {
632
        let mut hb = Registry::new();
633

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

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

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

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

699
        let mut hb2 = Registry::new();
700
        hb2.set_prevent_indent(true);
701

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

720
    #[test]
721
    fn test_indent_level_on_nested_partials() {
722
        let nested_partial = "
723
<div>
724
    content
725
</div>
726
";
727
        let partial = "
728
<div>
729
    {{>nested_partial}}
730
</div>
731
";
732

733
        let partial_indented = "
734
<div>
735
    {{>partial}}
736
</div>
737
";
738

739
        let result = "
740
<div>
741
    <div>
742
        <div>
743
            content
744
        </div>
745
    </div>
746
</div>
747
";
748

749
        let mut hb = Registry::new();
750
        hb.register_template_string("nested_partial", nested_partial.trim_start())
751
            .unwrap();
752
        hb.register_template_string("partial", partial.trim_start())
753
            .unwrap();
754
        hb.register_template_string("partial_indented", partial_indented.trim_start())
755
            .unwrap();
756

757
        let s = hb.render("partial_indented", &()).unwrap();
758

759
        assert_eq!(&s, result.trim_start());
760
    }
761

762
    #[test]
763
    fn test_issue_534() {
764
        let t1 = "{{title}}";
765
        let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";
766

767
        let data = json!({
768
          "modules": [
769
            {"module": "t1", "content": {"title": "foo"}},
770
            {"module": "t1", "content": {"title": "bar"}},
771
          ]
772
        });
773

774
        let mut hbs = Registry::new();
775
        hbs.register_template_string("t1", t1).unwrap();
776
        hbs.register_template_string("t2", t2).unwrap();
777

778
        assert_eq!("foobar", hbs.render("t2", &data).unwrap());
779
    }
780

781
    #[test]
782
    fn test_partial_not_found() {
783
        let t1 = "{{> bar}}";
784
        let hbs = Registry::new();
785
        assert!(hbs.render_template(t1, &()).is_err());
786
    }
787

788
    #[test]
789
    fn test_issue_643_this_context() {
790
        let t1 = "{{this}}";
791
        let t2 = "{{> t1 \"hello world\"}}";
792

793
        let mut hbs = Registry::new();
794
        hbs.register_template_string("t1", t1).unwrap();
795
        hbs.register_template_string("t2", t2).unwrap();
796

797
        assert_eq!("hello world", hbs.render("t2", &()).unwrap());
798

799
        let t1 = "{{a}} {{[0]}} {{[1]}}";
800
        let t2 = "{{> t1 \"hello world\" a=1}}";
801

802
        let mut hbs = Registry::new();
803
        hbs.register_template_string("t1", t1).unwrap();
804
        hbs.register_template_string("t2", t2).unwrap();
805

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

808
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
809
        let t2 = "{{> t1 a=1}}";
810

811
        let mut hbs = Registry::new();
812
        hbs.register_template_string("t1", t1).unwrap();
813
        hbs.register_template_string("t2", t2).unwrap();
814

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

817
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
818
        let t2 = "{{> t1 a=1}}";
819

820
        let mut hbs = Registry::new();
821
        hbs.register_template_string("t1", t1).unwrap();
822
        hbs.register_template_string("t2", t2).unwrap();
823

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

826
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
827
        let t2 = "{{> t1 b a=1}}";
828

829
        let mut hbs = Registry::new();
830
        hbs.register_template_string("t1", t1).unwrap();
831
        hbs.register_template_string("t2", t2).unwrap();
832

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

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

845
        assert!(matches!(
846
            hs.render("current", &()).unwrap_err().reason(),
847
            RenderErrorReason::PartialBlockNotFound
848
        ));
849

850
        let mut hs = Registry::new();
851
        hs.register_template_string("primary", "{{> @partial-block }}")
852
            .unwrap();
853
        hs.register_template_string("secondary", "{{#*inline \"inl\"}}Bug{{/inline}}{{#>primary}}{{> @partial-block }}{{>inl}}{{/primary}}").unwrap();
854
        hs.register_template_string("current", "{{#>secondary}}Not a {{/secondary}}")
855
            .unwrap();
856

857
        assert_eq!(hs.render("current", &()).unwrap(), "Not a Bug");
858
    }
859

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

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

889
        let mut handlebars = Registry::new();
890
        handlebars.register_decorator("set", Box::new(set_decorator));
891

892
        handlebars_helper!(lower: |s: str| s.to_lowercase());
893
        handlebars.register_helper("lower", Box::new(lower));
894
        handlebars
895
            .register_template_string(
896
                "an-included-file",
897
                "This file is included.\n\nSee {{lower somevalue}}\n",
898
            )
899
            .unwrap();
900

901
        let data = serde_json::json!({});
902
        assert_eq!(
903
            handlebars
904
                .render_template(
905
                    r#"
906
{{~*set somevalue="Example"}}
907
{{> an-included-file }}
908
"#,
909
                    &data
910
                )
911
                .unwrap(),
912
            "This file is included.\n\nSee example\n"
913
        );
914
    }
915
}
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