Skip to content

Commit 024e762

Browse files
committed
test: add extensive unit tests for Render method in Theme
Added comprehensive unit tests for the `Render` method, covering various scenarios, including simple templates, nil data, parsing errors, caching, parent themes, unicode content, special characters, concurrent access, and more. Validates correctness, robustness, and error handling.
1 parent 991b5cf commit 024e762

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

theme_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,300 @@ func TestTheme_WithComplexData(t *testing.T) {
486486

487487
mockStore.AssertExpectations(t)
488488
}
489+
490+
func TestTheme_Render_SimpleTemplate(t *testing.T) {
491+
mockStore := &MockStore{}
492+
theme := NewTheme("test", mockStore)
493+
494+
ctx := context.Background()
495+
496+
// Create a simple template
497+
templateContent := `<h1>{{.Title}}</h1>`
498+
testTemplate := createTestTemplate("test", "simple", templateContent)
499+
500+
mockStore.On("Find", ctx, "test", "simple").Return(testTemplate, nil).Once()
501+
502+
data := map[string]string{"Title": "Hello World"}
503+
result, err := theme.Render(ctx, "simple", data)
504+
505+
assert.NoError(t, err)
506+
assert.Equal(t, "<h1>Hello World</h1>", string(result))
507+
assert.IsType(t, []byte{}, result, "Render should return []byte")
508+
509+
mockStore.AssertExpectations(t)
510+
}
511+
512+
func TestTheme_Render_WithNilData(t *testing.T) {
513+
mockStore := &MockStore{}
514+
theme := NewTheme("test", mockStore)
515+
516+
ctx := context.Background()
517+
518+
// Create a template that works with nil data
519+
templateContent := `<div>Static content</div>`
520+
testTemplate := createTestTemplate("test", "static", templateContent)
521+
522+
mockStore.On("Find", ctx, "test", "static").Return(testTemplate, nil).Once()
523+
524+
result, err := theme.Render(ctx, "static", nil)
525+
526+
assert.NoError(t, err)
527+
assert.Equal(t, "<div>Static content</div>", string(result))
528+
529+
mockStore.AssertExpectations(t)
530+
}
531+
532+
func TestTheme_Render_TemplateNotFoundError(t *testing.T) {
533+
mockStore := &MockStore{}
534+
theme := NewTheme("test", mockStore)
535+
536+
ctx := context.Background()
537+
538+
mockStore.On("Find", ctx, "test", "missing").Return(nil, ErrTemplateNotFound).Once()
539+
540+
result, err := theme.Render(ctx, "missing", nil)
541+
542+
assert.Error(t, err)
543+
assert.Nil(t, result, "Render should return nil on error")
544+
assert.Contains(t, err.Error(), "failed to find template test/missing")
545+
546+
mockStore.AssertExpectations(t)
547+
}
548+
549+
func TestTheme_Render_WithParseError(t *testing.T) {
550+
mockStore := &MockStore{}
551+
theme := NewTheme("test", mockStore)
552+
553+
ctx := context.Background()
554+
555+
// Create a template with invalid syntax
556+
invalidContent := `<h1>{{.Title</h1>`
557+
invalidTemplate := createTestTemplate("test", "invalid", invalidContent)
558+
559+
mockStore.On("Find", ctx, "test", "invalid").Return(invalidTemplate, nil).Once()
560+
561+
result, err := theme.Render(ctx, "invalid", map[string]string{"Title": "Test"})
562+
563+
assert.Error(t, err)
564+
assert.Nil(t, result, "Render should return nil on parse error")
565+
assert.Contains(t, err.Error(), "template:")
566+
567+
mockStore.AssertExpectations(t)
568+
}
569+
570+
func TestTheme_Render_WithEmptyContent(t *testing.T) {
571+
mockStore := &MockStore{}
572+
theme := NewTheme("test", mockStore)
573+
574+
ctx := context.Background()
575+
576+
// Create a template with empty content
577+
emptyTemplate := createTestTemplate("test", "empty", "")
578+
579+
mockStore.On("Find", ctx, "test", "empty").Return(emptyTemplate, nil).Once()
580+
581+
result, err := theme.Render(ctx, "empty", nil)
582+
583+
assert.NoError(t, err)
584+
assert.Equal(t, []byte(nil), result, "Render should return nil byte slice for empty content")
585+
586+
mockStore.AssertExpectations(t)
587+
}
588+
589+
func TestTheme_Render_WithComplexData(t *testing.T) {
590+
mockStore := &MockStore{}
591+
theme := NewTheme("test", mockStore)
592+
593+
ctx := context.Background()
594+
595+
// Create a template with complex data
596+
complexContent := `{{range .Items}}{{$index := .Index}}{{$value := .Value}}Item {{$index}}: {{$value}} {{end}}`
597+
complexTemplate := createTestTemplate("test", "complex", complexContent)
598+
599+
mockStore.On("Find", ctx, "test", "complex").Return(complexTemplate, nil).Once()
600+
601+
type Item struct {
602+
Index int
603+
Value string
604+
}
605+
606+
data := map[string][]Item{
607+
"Items": {
608+
{Index: 1, Value: "First"},
609+
{Index: 2, Value: "Second"},
610+
{Index: 3, Value: "Third"},
611+
},
612+
}
613+
614+
result, err := theme.Render(ctx, "complex", data)
615+
616+
assert.NoError(t, err)
617+
assert.Contains(t, string(result), "Item 1: First")
618+
assert.Contains(t, string(result), "Item 2: Second")
619+
assert.Contains(t, string(result), "Item 3: Third")
620+
621+
mockStore.AssertExpectations(t)
622+
}
623+
624+
func TestTheme_Render_WithDebugMode(t *testing.T) {
625+
mockStore := &MockStore{}
626+
theme := NewTheme("test", mockStore)
627+
theme.SetDebug(true)
628+
629+
ctx := context.Background()
630+
631+
// Create a simple template
632+
templateContent := `<h1>{{.Title}}</h1>`
633+
testTemplate := createTestTemplate("test", "simple", templateContent)
634+
635+
mockStore.On("Find", ctx, "test", "simple").Return(testTemplate, nil).Once()
636+
637+
data := map[string]string{"Title": "Hello World"}
638+
result, err := theme.Render(ctx, "simple", data)
639+
640+
assert.NoError(t, err)
641+
assert.Equal(t, "<h1>Hello World</h1>", string(result))
642+
643+
mockStore.AssertExpectations(t)
644+
}
645+
646+
func TestTheme_Render_WithCache(t *testing.T) {
647+
mockStore := &MockStore{}
648+
theme := NewTheme("test", mockStore)
649+
650+
ctx := context.Background()
651+
652+
// Create a simple template
653+
templateContent := `<h1>{{.Title}}</h1>`
654+
testTemplate := createTestTemplate("test", "simple", templateContent)
655+
656+
// The template should only be fetched once (first call caches it)
657+
mockStore.On("Find", ctx, "test", "simple").Return(testTemplate, nil).Once()
658+
659+
data := map[string]string{"Title": "Hello World"}
660+
661+
// First call - should fetch from store
662+
result1, err := theme.Render(ctx, "simple", data)
663+
assert.NoError(t, err)
664+
assert.Equal(t, "<h1>Hello World</h1>", string(result1))
665+
666+
// Second call - should use cache (no additional store.Find call)
667+
result2, err := theme.Render(ctx, "simple", data)
668+
assert.NoError(t, err)
669+
assert.Equal(t, "<h1>Hello World</h1>", string(result2))
670+
671+
// Both results should be the same
672+
assert.Equal(t, result1, result2)
673+
674+
mockStore.AssertExpectations(t)
675+
}
676+
677+
func TestTheme_Render_WithParentTheme(t *testing.T) {
678+
parentStore := &MockStore{}
679+
childStore := &MockStore{}
680+
681+
parentTheme := NewTheme("parent", parentStore)
682+
childTheme := NewTheme("child", childStore)
683+
childTheme.SetParent(parentTheme)
684+
685+
ctx := context.Background()
686+
687+
// Template exists in parent theme
688+
templateContent := `<h1>{{.Title}}</h1>`
689+
parentTemplate := createTestTemplate("parent", "inherited", templateContent)
690+
691+
// Child theme doesn't have this template
692+
childStore.On("Find", ctx, "child", "inherited").Return(nil, ErrTemplateNotFound).Once()
693+
parentStore.On("Find", ctx, "parent", "inherited").Return(parentTemplate, nil).Once()
694+
695+
result, err := childTheme.Render(ctx, "inherited", map[string]string{"Title": "Inherited Template"})
696+
697+
assert.NoError(t, err)
698+
assert.Equal(t, "<h1>Inherited Template</h1>", string(result))
699+
700+
childStore.AssertExpectations(t)
701+
parentStore.AssertExpectations(t)
702+
}
703+
704+
func TestTheme_Render_WithUnicodeContent(t *testing.T) {
705+
mockStore := &MockStore{}
706+
theme := NewTheme("test", mockStore)
707+
708+
ctx := context.Background()
709+
710+
// Create a template with unicode content
711+
templateContent := `<h1>{{.Title}}</h1><p>{{.Message}}</p>`
712+
testTemplate := createTestTemplate("test", "unicode", templateContent)
713+
714+
mockStore.On("Find", ctx, "test", "unicode").Return(testTemplate, nil).Once()
715+
716+
data := map[string]string{
717+
"Title": "🎉 Unicode Test",
718+
"Message": "こんにちは世界 🌍",
719+
}
720+
721+
result, err := theme.Render(ctx, "unicode", data)
722+
723+
assert.NoError(t, err)
724+
assert.Contains(t, string(result), "🎉 Unicode Test")
725+
assert.Contains(t, string(result), "こんにちは世界 🌍")
726+
727+
mockStore.AssertExpectations(t)
728+
}
729+
730+
func TestTheme_Render_WithSpecialCharacters(t *testing.T) {
731+
mockStore := &MockStore{}
732+
theme := NewTheme("test", mockStore)
733+
734+
ctx := context.Background()
735+
736+
// Create a template with special HTML characters
737+
templateContent := `<div>{{.Content}}</div>`
738+
testTemplate := createTestTemplate("test", "special", templateContent)
739+
740+
mockStore.On("Find", ctx, "test", "special").Return(testTemplate, nil).Once()
741+
742+
data := map[string]string{"Content": `<script>alert("XSS")</script>`}
743+
744+
result, err := theme.Render(ctx, "special", data)
745+
746+
assert.NoError(t, err)
747+
// Go's html/template auto-escapes HTML by default
748+
assert.Contains(t, string(result), "&lt;script&gt;")
749+
750+
mockStore.AssertExpectations(t)
751+
}
752+
753+
func TestTheme_Render_ConcurrentAccess(t *testing.T) {
754+
mockStore := &MockStore{}
755+
theme := NewTheme("test", mockStore)
756+
757+
ctx := context.Background()
758+
759+
// Create a simple template
760+
templateContent := `<h1>{{.Title}}</h1>`
761+
testTemplate := createTestTemplate("test", "simple", templateContent)
762+
763+
mockStore.On("Find", ctx, "test", "simple").Return(testTemplate, nil).Maybe()
764+
765+
var wg sync.WaitGroup
766+
numGoroutines := 10
767+
numIterations := 5
768+
769+
// Test concurrent renders
770+
for i := 0; i < numGoroutines; i++ {
771+
wg.Add(1)
772+
go func(id int) {
773+
defer wg.Done()
774+
for j := 0; j < numIterations; j++ {
775+
data := map[string]string{"Title": "Test"}
776+
result, err := theme.Render(ctx, "simple", data)
777+
assert.NoError(t, err)
778+
assert.Equal(t, "<h1>Test</h1>", string(result))
779+
assert.IsType(t, []byte{}, result)
780+
}
781+
}(i)
782+
}
783+
784+
wg.Wait()
785+
}

0 commit comments

Comments
 (0)