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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,4 @@ require (
gorm.io/plugin/dbresolver v1.6.2 // indirect
)

replace github.com/goravel/framework => github.com/goravel/framework v1.18.1-0.20260925125002-93850001fe6f
replace github.com/goravel/framework => github.com/darakanoit/framework v1.18.1-0.20260927233735-13001ebed1a1
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/darakanoit/framework v1.18.1-0.20260927233735-13001ebed1a1 h1:ABBjKcQq9jbYjePRdmgO28fEhsJXaKNafazhr+vTjAk=
github.com/darakanoit/framework v1.18.1-0.20260927233735-13001ebed1a1/go.mod h1:7Phix1gVUYzA1muoX1o92AJszuDgBUgO31ICCCJ7+Xw=
github.com/dave/dst v0.28.0 h1:EIEuRC5XmVKuZF2OeyMahXX9ARVt6Xxr8vI0WtIY7K8=
github.com/dave/dst v0.28.0/go.mod h1:C4mfimBeSHfsgskhR/mUYSE05s2ih+PW6g9FP3c49tE=
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
Expand Down Expand Up @@ -317,8 +319,6 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR
github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg=
github.com/goravel/fiber v1.18.1-0.20260901101548-72e06e6c5635 h1:iqzZ0P+KC8uBFNQ7PISL81U8CUc9myzS14NRYfftOB4=
github.com/goravel/fiber v1.18.1-0.20260901101548-72e06e6c5635/go.mod h1:fk0YaivBle83SlSsPziy5+TRMavqlAr/QeVNo6pRmJA=
github.com/goravel/framework v1.18.1-0.20260925125002-93850001fe6f h1:/hWkvKBhtOPb6k1awR5scXcaLTHS+r1Rh3pS9O0DVIg=
github.com/goravel/framework v1.18.1-0.20260925125002-93850001fe6f/go.mod h1:7Phix1gVUYzA1muoX1o92AJszuDgBUgO31ICCCJ7+Xw=
github.com/goravel/gemini v1.18.0 h1:Tv6IJ3KvBvH5QD7kAM3XFQR0g8IUfkqMbRGi7dtlWUo=
github.com/goravel/gemini v1.18.0/go.mod h1:b4FueB0O0Qkz71eVWLiSaKghp+Eb/1hNNICkk7vMgrQ=
github.com/goravel/gin v1.18.1-0.20260926005436-467f03c48058 h1:EId8wBSolUZQ00QQz4fyPrRWN+Tz/TZAw4u65XwSINY=
Expand Down
1 change: 1 addition & 0 deletions resources/views/define_only.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ define "elsewhere.tmpl" }}x{{ end }}
1 change: 1 addition & 0 deletions resources/views/greeting.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ define "greeting.tmpl" }}<p>Hello, {{ .Name }}</p>{{ end }}
153 changes: 153 additions & 0 deletions tests/feature/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package feature
import (
"testing"

frameworkerrors "github.com/goravel/framework/errors"
"github.com/goravel/framework/support/path"
"github.com/stretchr/testify/suite"

Expand Down Expand Up @@ -61,3 +62,155 @@ func (s *ViewTestSuite) TestRender_SharedViewOverride() {
s.Require().NoError(err)
s.Contains(content, "<p>app-shared: Goravel</p>")
}

// TestMake_RendersAppView verifies a view renders to a string outside a request.
func (s *ViewTestSuite) TestMake_RendersAppView() {
html, err := facades.View().Make("shared.tmpl", map[string]any{"name": "Goravel"}).Render()
s.Require().NoError(err)
s.Equal("<p>app-shared: Goravel</p>", html)
}

// TestMake_RendersPackageView verifies package views registered with
// LoadViewsFrom are reachable through Make.
func (s *ViewTestSuite) TestMake_RendersPackageView() {
html, err := facades.View().Make("package_only.tmpl", map[string]any{"name": "Goravel"}).Render()
s.Require().NoError(err)
s.Equal("<p>package-only: Goravel</p>", html)
}

// TestMake_RendersNestedView verifies partials/layouts resolve outside a request.
// csrf_token is request-bound and absent here; it renders as an empty string.
func (s *ViewTestSuite) TestMake_RendersNestedView() {
html, err := facades.View().Make("admin/index.tmpl", map[string]any{"name": "Goravel"}).Render()
s.Require().NoError(err)
s.Equal("\n \n<html>\n <body>\n <p>I'm the header</p>\n\n <p>Hello, Goravel</p>\n <p> CSRF Token: </p>\n \n <p>I'm the footer</p>\n </body>\n</html>\n\n", html)
}

// TestMake_MatchesHttpResponse verifies Render produces exactly what the driver
// sends for the same view.
func (s *ViewTestSuite) TestMake_MatchesHttpResponse() {
resp, err := s.Http(s.T()).Get("/shared")
s.Require().NoError(err)
resp.AssertSuccessful()

content, err := resp.Content()
s.Require().NoError(err)

html, err := facades.View().Make("shared.tmpl", map[string]any{"name": "Goravel"}).Render()
s.Require().NoError(err)
s.Equal(content, html)
}

// TestMake_StructData verifies struct and pointer-to-struct data render.
func (s *ViewTestSuite) TestMake_StructData() {
type greeting struct {
Name string
}

tests := []struct {
name string
data any
}{
{name: "struct value", data: greeting{Name: "Goravel"}},
{name: "pointer to struct", data: &greeting{Name: "Goravel"}},
}

for _, tt := range tests {
s.Run(tt.name, func() {
html, err := facades.View().Make("greeting.tmpl", tt.data).Render()
s.Require().NoError(err)
s.Equal("<p>Hello, Goravel</p>", html)
})
}
}

// TestMake_WithOverridesDataAndShared verifies With overrides passed data and
// that shared data is merged at render time.
func (s *ViewTestSuite) TestMake_WithOverridesDataAndShared() {
facades.View().Share("Name", "Shared")
s.T().Cleanup(func() {
facades.View().Share("Name", nil)
})

template := facades.View().Make("greeting.tmpl", map[string]any{"Name": "data"}).
With("Name", "With")

s.Equal("greeting.tmpl", template.Name())
s.Equal(map[string]any{"Name": "With"}, template.Data())

html, err := template.Render()
s.Require().NoError(err)
s.Equal("<p>Hello, With</p>", html)

// Shared data is used when the template supplies none of its own.
html, err = facades.View().Make("greeting.tmpl").Render()
s.Require().NoError(err)
s.Equal("<p>Hello, Shared</p>", html)
}

// TestMake_MissingView verifies an unknown view fails at Render.
func (s *ViewTestSuite) TestMake_MissingView() {
_, err := facades.View().Make("missing.tmpl").Render()
s.ErrorIs(err, frameworkerrors.ViewTemplateNotExist)
}

// TestMake_InvalidData verifies unsupported data is reported at Render.
func (s *ViewTestSuite) TestMake_InvalidData() {
_, err := facades.View().Make("shared.tmpl", 1).Render()
s.ErrorIs(err, frameworkerrors.ViewInvalidData)

_, err = facades.View().Make("shared.tmpl", map[int]string{1: "a"}).Render()
s.ErrorIs(err, frameworkerrors.ViewInvalidData)
}

// TestFirst verifies First picks the first renderable candidate.
func (s *ViewTestSuite) TestFirst() {
data := map[string]any{"name": "Goravel"}

// define_only.tmpl exists but holds only a define block, so it is not
// renderable under its own name and must be skipped for a later candidate.
s.True(facades.View().Exists("define_only.tmpl"))

tests := []struct {
name string
candidates []string
wantName string
wantHTML string
}{
{
name: "skips missing views and returns the first existing candidate",
candidates: []string{"missing.tmpl", "package_only.tmpl", "shared.tmpl"},
wantName: "package_only.tmpl",
wantHTML: "<p>package-only: Goravel</p>",
},
{
name: "skips a view that exists but is not renderable under its own name",
candidates: []string{"define_only.tmpl", "package_only.tmpl"},
wantName: "package_only.tmpl",
wantHTML: "<p>package-only: Goravel</p>",
},
{
name: "returns the first candidate when it is already renderable",
candidates: []string{"package_only.tmpl", "shared.tmpl"},
wantName: "package_only.tmpl",
wantHTML: "<p>package-only: Goravel</p>",
},
}

for _, tt := range tests {
s.Run(tt.name, func() {
template := facades.View().First(tt.candidates, data)
s.Equal(tt.wantName, template.Name())

html, err := template.Render()
s.Require().NoError(err)
s.Equal(tt.wantHTML, html)
})
}
}

// TestFirst_NoneExist verifies First reports when no candidate exists.
func (s *ViewTestSuite) TestFirst_NoneExist() {
_, err := facades.View().First([]string{"missing.tmpl", "other.tmpl"}).Render()
s.ErrorIs(err, frameworkerrors.ViewNoneExist)
}
Loading