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

sunng87 / handlebars-rust / 20639969489

01 Jan 2026 02:08PM UTC coverage: 83.707% (+0.03%) from 83.673%
20639969489

Pull #732

github

web-flow
Merge abe1b6ce9 into c349c3955
Pull Request #732: fix: correct partial-block render

39 of 43 new or added lines in 2 files covered. (90.7%)

1 existing line in 1 file now uncovered.

1680 of 2007 relevant lines covered (83.71%)

6.82 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, VecDeque};
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, Evaluable, 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>(
5✔
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));
3✔
24
    }
25

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

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

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

38
    Ok(None)
1✔
39
}
40

41
pub fn expand_partial<'reg: 'rc, 'rc>(
5✔
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
    // try eval inline partials first
49
    if let Some(t) = d.template() {
6✔
50
        t.eval(r, ctx, rc)?;
1✔
51
    }
52

53
    let tname = d.name();
5✔
54

55
    let current_template_before = rc.get_current_template_name();
6✔
56
    let indent_before = rc.get_indent_string().cloned();
5✔
57

58
    if rc.is_current_template(tname) {
11✔
59
        return Err(RenderErrorReason::CannotIncludeSelf.into());
2✔
60
    }
61

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

78
        // check if this inclusion has a block
79
        if let Some(current_parital_block) = d.template() {
11✔
80
            let mut tmp_out = StringOutput::new();
1✔
81
            current_parital_block.render(r, ctx, rc, &mut tmp_out)?;
3✔
82
            rc.push_partial_block(Some(tmp_out.into_string()?));
2✔
83
        } else {
84
            rc.push_partial_block(None);
11✔
85
        }
86

87
        // hash
NEW
88
        let hash_ctx = d
×
89
            .hash()
90
            .iter()
91
            .map(|(k, v)| (*k, v.value()))
10✔
92
            .collect::<HashMap<&str, &Json>>();
93

94
        let mut partial_include_block = BlockContext::new();
5✔
95
        // evaluate context for partial
96
        let merged_context = if let Some(p) = d.param(0) {
11✔
97
            if let Some(relative_path) = p.relative_path() {
6✔
98
                // path as parameter provided
99
                if let Some(rc_context) = rc.context() {
6✔
100
                    merge_json(
NEW
101
                        rc.evaluate(&rc_context, relative_path)?.as_json(),
×
NEW
102
                        &hash_ctx,
×
103
                    )
104
                } else {
105
                    merge_json(rc.evaluate(ctx, relative_path)?.as_json(), &hash_ctx)
9✔
106
                }
107
            } else {
108
                // literal provided
109
                merge_json(p.value(), &hash_ctx)
2✔
110
            }
111
        } else {
112
            // use current path
113
            if let Some(rc_context) = rc.context() {
11✔
114
                merge_json(
115
                    rc.evaluate2(&rc_context, &Path::current())?.as_json(),
3✔
NEW
116
                    &hash_ctx,
×
117
                )
118
            } else {
119
                merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx)
21✔
120
            }
121
        };
122
        partial_include_block.set_base_value(merged_context);
5✔
123

124
        // replace and hold blocks from current render context
125
        let current_blocks = rc.replace_blocks(VecDeque::with_capacity(1));
5✔
126
        rc.push_block(partial_include_block);
6✔
127

128
        // indent
129
        rc.set_indent_string(d.indent().cloned());
5✔
130

131
        let result = partial.render(r, ctx, rc, out);
5✔
132

133
        // cleanup
134
        let trailing_newline = rc.get_trailine_newline();
12✔
135

136
        // remove current partial_block
137
        rc.pop_partial_block();
6✔
138

139
        let _ = rc.replace_blocks(current_blocks);
7✔
140
        rc.set_trailing_newline(trailing_newline);
7✔
141
        rc.set_current_template_name(current_template_before);
7✔
142
        rc.set_indent_string(indent_before);
7✔
143

144
        result
7✔
145
    }
146
}
147

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

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

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

216
    #[test]
217
    fn test_include_partial_block() {
218
        let t0 = "hello {{> @partial-block}}";
219
        let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
220

221
        let mut handlebars = Registry::new();
222
        assert!(handlebars.register_template_string("t0", t0).is_ok());
223
        assert!(handlebars.register_template_string("t1", t1).is_ok());
224

225
        let r0 = handlebars.render("t1", &true);
226
        assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
227
    }
228

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

237
        let r0 = handlebars.render("t0", &true);
238
        assert!(r0.is_err());
239
    }
240

241
    #[test]
242
    fn test_issue_143() {
243
        let main_template = "one{{> two }}three{{> two }}";
244
        let two_partial = "--- two ---";
245

246
        let mut handlebars = Registry::new();
247
        assert!(handlebars
248
            .register_template_string("template", main_template)
249
            .is_ok());
250
        assert!(handlebars
251
            .register_template_string("two", two_partial)
252
            .is_ok());
253

254
        let r0 = handlebars.render("template", &true);
255
        assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
256
    }
257

258
    #[test]
259
    fn test_hash_context_outscope() {
260
        let main_template = "In: {{> p a=2}} Out: {{a}}";
261
        let p_partial = "{{a}}";
262

263
        let mut handlebars = Registry::new();
264
        assert!(handlebars
265
            .register_template_string("template", main_template)
266
            .is_ok());
267
        assert!(handlebars.register_template_string("p", p_partial).is_ok());
268

269
        let r0 = handlebars.render("template", &true);
270
        assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
271
    }
272

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

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

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

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

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

332
        let mut handlebars = Registry::new();
333
        assert!(handlebars.register_template_string("t", t).is_ok());
334
        let r0 = handlebars.render("t", &data);
335
        assert_eq!(r0.ok().unwrap(), "2 true2 false");
336
    }
337

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

345
        handlebars
346
            .register_template_string("t1", template1)
347
            .unwrap();
348
        handlebars
349
            .register_template_string("t2", template2)
350
            .unwrap();
351

352
        let page = handlebars.render_template(template3, &json!({})).unwrap();
353
        assert_eq!("<outer><inner>Hello</inner></outer>", page);
354
    }
355

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

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

398
        let page = handlebars.render_template(template3, &json!({})).unwrap();
399
        assert_eq!("<outer><inner>Hello</inner></outer> World", page);
400
    }
401

402
    #[test]
403
    fn test_up_to_partial_level() {
404
        let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
405
        let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
406

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

409
        let mut handlebars = Registry::new();
410
        handlebars.register_template_string("outer", outer).unwrap();
411
        handlebars.register_template_string("inner", inner).unwrap();
412

413
        assert_eq!(
414
            handlebars.render("outer", &data).unwrap(),
415
            "fruit: carrot,fruit: tomato,"
416
        );
417
    }
418

419
    #[test]
420
    fn line_stripping_with_inline_and_partial() {
421
        let tpl0 = r#"{{#*inline "foo"}}foo
422
{{/inline}}
423
{{> foo}}
424
{{> foo}}
425
{{> foo}}"#;
426
        let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
427
{{> foo}}
428
{{> foo}}
429
{{> foo}}"#;
430

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

446
    #[test]
447
    fn test_partial_indent() {
448
        let outer = r"                {{> inner inner_solo}}
449

450
{{#each inners}}
451
                {{> inner}}
452
{{/each}}
453

454
        {{#each inners}}
455
        {{> inner}}
456
        {{/each}}
457
";
458
        let inner = r"name: {{name}}
459
";
460

461
        let mut hbs = Registry::new();
462

463
        hbs.register_template_string("inner", inner).unwrap();
464
        hbs.register_template_string("outer", outer).unwrap();
465

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

479
        assert_eq!(
480
            result,
481
            r"                name: inner_solo
482

483
                name: hello
484
                name: there
485

486
        name: hello
487
        name: there
488
"
489
        );
490
    }
491
    // Rule::partial_expression should not trim leading indent  by default
492

493
    #[test]
494
    fn test_partial_prevent_indent() {
495
        let outer = r"                {{> inner inner_solo}}
496

497
{{#each inners}}
498
                {{> inner}}
499
{{/each}}
500

501
        {{#each inners}}
502
        {{> inner}}
503
        {{/each}}
504
";
505
        let inner = r"name: {{name}}
506
";
507

508
        let mut hbs = Registry::new();
509
        hbs.set_prevent_indent(true);
510

511
        hbs.register_template_string("inner", inner).unwrap();
512
        hbs.register_template_string("outer", outer).unwrap();
513

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

527
        assert_eq!(
528
            result,
529
            r"                name: inner_solo
530

531
                name: hello
532
                name: there
533

534
        name: hello
535
        name: there
536
"
537
        );
538
    }
539

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

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

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

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

604
        let data = json!({
605
            "data": ["hudel", "test"]
606
        });
607

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

626
    #[test]
627
    fn test_multiline_partial_indent() {
628
        let mut hb = Registry::new();
629

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

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

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

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

695
        let mut hb2 = Registry::new();
696
        hb2.set_prevent_indent(true);
697

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

716
    #[test]
717
    fn test_indent_level_on_nested_partials() {
718
        let nested_partial = "
719
<div>
720
    content
721
</div>
722
";
723
        let partial = "
724
<div>
725
    {{>nested_partial}}
726
</div>
727
";
728

729
        let partial_indented = "
730
<div>
731
    {{>partial}}
732
</div>
733
";
734

735
        let result = "
736
<div>
737
    <div>
738
        <div>
739
            content
740
        </div>
741
    </div>
742
</div>
743
";
744

745
        let mut hb = Registry::new();
746
        hb.register_template_string("nested_partial", nested_partial.trim_start())
747
            .unwrap();
748
        hb.register_template_string("partial", partial.trim_start())
749
            .unwrap();
750
        hb.register_template_string("partial_indented", partial_indented.trim_start())
751
            .unwrap();
752

753
        let s = hb.render("partial_indented", &()).unwrap();
754

755
        assert_eq!(&s, result.trim_start());
756
    }
757

758
    #[test]
759
    fn test_issue_534() {
760
        let t1 = "{{title}}";
761
        let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";
762

763
        let data = json!({
764
          "modules": [
765
            {"module": "t1", "content": {"title": "foo"}},
766
            {"module": "t1", "content": {"title": "bar"}},
767
          ]
768
        });
769

770
        let mut hbs = Registry::new();
771
        hbs.register_template_string("t1", t1).unwrap();
772
        hbs.register_template_string("t2", t2).unwrap();
773

774
        assert_eq!("foobar", hbs.render("t2", &data).unwrap());
775
    }
776

777
    #[test]
778
    fn test_partial_not_found() {
779
        let t1 = "{{> bar}}";
780
        let hbs = Registry::new();
781
        assert!(hbs.render_template(t1, &()).is_err());
782
    }
783

784
    #[test]
785
    fn test_issue_643_this_context() {
786
        let t1 = "{{this}}";
787
        let t2 = "{{> t1 \"hello world\"}}";
788

789
        let mut hbs = Registry::new();
790
        hbs.register_template_string("t1", t1).unwrap();
791
        hbs.register_template_string("t2", t2).unwrap();
792

793
        assert_eq!("hello world", hbs.render("t2", &()).unwrap());
794

795
        let t1 = "{{a}} {{[0]}} {{[1]}}";
796
        let t2 = "{{> t1 \"hello world\" a=1}}";
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!("1 h e", hbs.render("t2", &()).unwrap());
803

804
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
805
        let t2 = "{{> t1 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!("a:1,", 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,b:2,", hbs.render("t2", &json!({"b": 2})).unwrap());
821

822
        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
823
        let t2 = "{{> t1 b 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,", hbs.render("t2", &json!({"b": 2})).unwrap());
830
    }
831

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

841
        assert!(matches!(
842
            hs.render("current", &()).unwrap_err().reason(),
843
            RenderErrorReason::PartialBlockNotFound
844
        ));
845

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

853
        assert_eq!(hs.render("current", &()).unwrap(), "Not a Bug");
854
    }
855

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

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

885
        let mut handlebars = Registry::new();
886
        handlebars.register_decorator("set", Box::new(set_decorator));
887

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

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