fix template test renderer, add category

This commit is contained in:
41666 2025-04-06 22:27:42 -07:00
parent d9146750ba
commit e4317ec4fd
15 changed files with 171 additions and 103 deletions

View file

@ -19,19 +19,24 @@ import (
"git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/templates"
"git.sapphic.engineer/roleypoly/v4/testing" "git.sapphic.engineer/roleypoly/v4/testing"
"git.sapphic.engineer/roleypoly/v4/types" "git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/utils"
) )
func CreateFiberApp() *fiber.App { func CreateFiberApp() *fiber.App {
viewEngine := html.NewFileSystem(http.FS(templates.FS), ".html")
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
Views: viewEngine, Views: CreateViewEngine(),
ViewsLayout: "layouts/main", ViewsLayout: "layouts/main",
}) })
return app return app
} }
func CreateViewEngine() *html.Engine {
viewEngine := html.NewFileSystem(http.FS(templates.FS), ".html")
viewEngine.AddFuncMap(utils.TemplateFuncs())
return viewEngine
}
func oneStatic(c fiber.Ctx) error { func oneStatic(c fiber.Ctx) error {
path := strings.Replace(c.OriginalURL(), "/", "", 1) path := strings.Replace(c.OriginalURL(), "/", "", 1)
f, err := staticfs.FS.Open(path) f, err := staticfs.FS.Open(path)

View file

@ -0,0 +1 @@
<h1>hello world</h1>

View file

@ -0,0 +1,16 @@
package components
import (
"git.sapphic.engineer/roleypoly/v4/types"
)
type CategoryTemplateData struct {
ID string
Name string
Type types.CategoryType
Roles []RoleTemplateData
}
func Category(cat *types.Category, roles []*types.Role) CategoryTemplateData {
return CategoryTemplateData{}
}

View file

@ -0,0 +1,14 @@
<section class="category" data-testid="category-{{.ID}}">
<h3>{{.Name}}</h3>
<div class="role-container">
{{ range $_, $role := .Roles }} {{template "components/role" $role}} {{end}}
{{ if eq .Type "single" }}
<input
type="radio"
name="{{roleInputName .ID}}"
id="{{roleInputID .ID .ID}}"
/>
<label for="{{roleInputID .ID .ID}}">None</label>
{{ end }}
</div>
</section>

View file

@ -0,0 +1,43 @@
package components_test
import (
"fmt"
"testing"
"git.sapphic.engineer/roleypoly/v4/templates/components"
"git.sapphic.engineer/roleypoly/v4/templates/templatetesting"
"git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
"git.sapphic.engineer/roleypoly/v4/utils"
"github.com/stretchr/testify/assert"
)
func TestCategoryTemplate(t *testing.T) {
c := components.CategoryTemplateData{
ID: "123",
Name: "Multi",
Type: types.CategoryMultiple,
Roles: []components.RoleTemplateData{
components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true, true),
},
}
html := templatetesting.Template(t, "components/category", c)
assert.Contains(t, html, "<h3>Multi</h3>", "has header")
assert.Contains(t, html, `data-testid="category-123"`, "has testid")
assert.NotContains(t, html, `<input type="radio"`, "has no radios")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("123", fixtures.RoleWithDarkColor.ID)), "has the role")
c = components.CategoryTemplateData{
ID: "456",
Name: "Single",
Roles: []components.RoleTemplateData{
components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, true, true),
},
}
html = templatetesting.Template(t, "components/category", c)
assert.Contains(t, html, "<h3>Single</h3>", "has header")
assert.Contains(t, html, `data-testid="category-456"`, "has testid")
assert.NotContains(t, html, `<input type="checkbox"`, "has no checkboxes")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("456", "456")), "has no checkboxes")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("456", fixtures.RoleWithDarkColor.ID)), "has the role")
}

View file

@ -10,6 +10,7 @@ type RoleTemplateData struct {
CategoryID string CategoryID string
Name string Name string
Selected bool Selected bool
Safe bool
InputType InputType InputType InputType
Colors RoleColors Colors RoleColors
} }
@ -26,7 +27,7 @@ const (
InputRadio InputType = "radio" InputRadio InputType = "radio"
) )
func Role(category *types.Category, role *types.Role, selected bool) RoleTemplateData { func Role(category *types.Category, role *types.Role, selected bool, safe bool) RoleTemplateData {
inputType := InputCheckbox inputType := InputCheckbox
if category.Type == types.CategorySingle { if category.Type == types.CategorySingle {
inputType = InputRadio inputType = InputRadio
@ -41,6 +42,7 @@ func Role(category *types.Category, role *types.Role, selected bool) RoleTemplat
Selected: selected, Selected: selected,
InputType: inputType, InputType: inputType,
Colors: colors, Colors: colors,
Safe: safe,
} }
} }

View file

@ -5,6 +5,7 @@
data-testid="{{$for}}" data-testid="{{$for}}"
> >
<input type="{{.InputType}}" id="{{$for}}" {{if eq .InputType <input type="{{.InputType}}" id="{{$for}}" {{if eq .InputType
"radio"}}name="category_group_{{.CategoryID}}"{{end}} /> "radio"}}name="category_group_{{.CategoryID}}"{{end}} {{if not
.Safe}}disabled="disabled"{{end}}/>
<label for="{{$for}}">{{.Name}}</label> <label for="{{$for}}">{{.Name}}</label>
</div> </div>

View file

@ -14,39 +14,44 @@ import (
func TestRoleTemplate(t *testing.T) { func TestRoleTemplate(t *testing.T) {
c := &fixtures.CategoryMulti c := &fixtures.CategoryMulti
r := &fixtures.RoleWithDarkColor r := &fixtures.RoleWithDarkColor
data := components.Role(c, r, true) data := components.Role(c, r, true, true)
html := templatetesting.Template(t, "components/role", data) html := templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, "--role-color: #a20000;", "role color is set") assert.Contains(t, html, "--role-color: #a20000;", "role color is set")
assert.Contains(t, html, `type="checkbox"`, "multi has input type=checkbox") assert.Contains(t, html, `type="checkbox"`, "multi has input type=checkbox")
assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(162, 0, 0))), "contrast color is set") assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(162, 0, 0))), "contrast color is set")
assert.Contains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID(c, r)), "input has ID attr") assert.Contains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID(c.ID, r.ID)), "input has ID attr")
assert.Contains(t, html, fmt.Sprintf(`for="%s"`, utils.RoleInputID(c, r)), "label has for attr") assert.Contains(t, html, fmt.Sprintf(`for="%s"`, utils.RoleInputID(c.ID, r.ID)), "label has for attr")
assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c)), "multi has no name attr") assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c.ID)), "multi has no name attr")
// TODO: selected? // TODO: selected?
c = &fixtures.CategorySingle c = &fixtures.CategorySingle
r = &fixtures.RoleWithLightColor r = &fixtures.RoleWithLightColor
data = components.Role(c, r, false) data = components.Role(c, r, false, true)
html = templatetesting.Template(t, "components/role", data) html = templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, `type="radio"`, "single has input type=radio") assert.Contains(t, html, `type="radio"`, "single has input type=radio")
assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(0xff, 0xaa, 0x88))), "contrast color") assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(0xff, 0xaa, 0x88))), "contrast color")
assert.Contains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c)), "single has name attr") assert.Contains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c.ID)), "single has name attr")
data = components.Role(c, r, false, false)
html = templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, `disabled="disabled"`)
} }
func TestRole(t *testing.T) { func TestRole(t *testing.T) {
r := components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true) r := components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true, true)
assert.Equal(t, fixtures.RoleWithDarkColor.ID, r.ID) assert.Equal(t, fixtures.RoleWithDarkColor.ID, r.ID)
assert.Equal(t, fixtures.RoleWithDarkColor.Name, r.Name) assert.Equal(t, fixtures.RoleWithDarkColor.Name, r.Name)
assert.Equal(t, components.InputCheckbox, r.InputType) assert.Equal(t, components.InputCheckbox, r.InputType)
assert.Equal(t, "#a20000", r.Colors.Main) assert.Equal(t, "#a20000", r.Colors.Main)
assert.True(t, r.Selected) assert.True(t, r.Selected)
r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, false) r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, false, true)
assert.Equal(t, components.InputRadio, r.InputType) assert.Equal(t, components.InputRadio, r.InputType)
assert.False(t, r.Selected) assert.False(t, r.Selected)
r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor, true) r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor, true, false)
assert.True(t, r.Selected) assert.True(t, r.Selected)
assert.False(t, r.Safe)
} }
func TestNewRoleColors(t *testing.T) { func TestNewRoleColors(t *testing.T) {

View file

@ -8,9 +8,9 @@ import (
) )
func TestMainLayout(t *testing.T) { func TestMainLayout(t *testing.T) {
r := templatetesting.Template(t, "layouts/main", struct{ HeadTitle string }{HeadTitle: "roleypoly"}) r := templatetesting.Template(t, "components/blank", struct{ HeadTitle string }{HeadTitle: "roleypoly"}, "layouts/main")
assert.Contains(t, r, "%%EMBED%%", "has {{embed}}")
assert.Contains(t, r, "<nav>", "loaded navigation (open)") assert.Contains(t, r, "<nav>", "loaded navigation (open)")
assert.Contains(t, r, "</nav>", "loaded navigation (close)") assert.Contains(t, r, "</nav>", "loaded navigation (close)")
assert.Contains(t, r, "<title>roleypoly</title>", "sets title") assert.Contains(t, r, "<title>roleypoly</title>", "sets title")
assert.Contains(t, r, "<h1>hello world</h1>", "has {{embed}}")
} }

View file

@ -3,64 +3,20 @@ package templatetesting
import ( import (
"bytes" "bytes"
"html/template"
"io/fs"
"strings"
"testing" "testing"
"git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/roleypoly"
"github.com/stretchr/testify/assert"
) )
var ( var (
funcMap = template.FuncMap{ viewEngine = roleypoly.CreateViewEngine()
"embed": func() string {
return "%%EMBED%%"
},
}
Templates *template.Template = template.New("index").Funcs(funcMap)
) )
func init() { func Template(t *testing.T, name string, data interface{}, layout ...string) string {
fs.WalkDir(templates.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
fiberName := strings.Replace(path, ".html", "", 1)
f, err := templates.FS.ReadFile(path)
if err != nil {
return err
}
if fiberName == "index" {
Templates, err = Templates.Parse(string(f))
} else {
_, err = Templates.New(fiberName).Parse(string(f))
}
return err
},
)
}
func Template(t *testing.T, name string, data interface{}) string {
buf := bytes.Buffer{} buf := bytes.Buffer{}
err := Templates.ExecuteTemplate(&buf, name, data)
if err != nil {
debugTemplates(t)
t.Fatal("failed to render: ", err)
}
err := viewEngine.Render(&buf, name, data, layout...)
assert.NoError(t, err)
return buf.String() return buf.String()
} }
func debugTemplates(t *testing.T) {
for i, tmpl := range Templates.Templates() {
t.Logf("template %d: name=%s", i, tmpl.Name())
}
}

View file

@ -1,16 +0,0 @@
package templatetesting_test
import (
"testing"
"git.sapphic.engineer/roleypoly/v4/templates/templatetesting"
"github.com/stretchr/testify/assert"
)
func TestRender(t *testing.T) {
for _, template := range templatetesting.Templates.Templates() {
assert.NotPanicsf(t, func() {
templatetesting.Template(t, template.Name(), nil)
}, "rendering %s", template.Name())
}
}

View file

@ -5,12 +5,24 @@
</style> </style>
<div class="container"> <div class="container">
<div class="cat-multi"> <div>
{{ template "components/role" .TestRole }} {{ template "components/role" <h1>Multi</h1>
.TestRole2 }} {{ template "components/role" .TestRole3 }} {{ range $_, $r := .Category1 }} {{ template "components/role" $r }} {{ end
}}
</div> </div>
<div class="cat-single"> <div>
{{ template "components/role" .TestRole4 }} {{ template "components/role" <h1>Single</h1>
.TestRole5 }} {{ template "components/role" .TestRole6 }} {{ range $_, $r := .Category2 }} {{ template "components/role" $r }} {{ end
}}
</div>
<div>
<h1>Bad</h1>
{{ range $_, $r := .Category3 }} {{ template "components/role" $r }} {{ end
}}
</div>
<div>
<h1>Hmm</h1>
{{ range $_, $r := .Category4 }} {{ template "components/role" $r }} {{ end
}}
</div> </div>
</div> </div>

View file

@ -38,13 +38,37 @@ func (t *TestingController) TestTemplate(c fiber.Ctx) error {
which := c.Params("which") which := c.Params("which")
cat1 := fixtures.Category(fixtures.CategoryMulti) cat1 := fixtures.Category(fixtures.CategoryMulti)
cat2 := fixtures.Category(fixtures.CategorySingle) cat2 := fixtures.Category(fixtures.CategorySingle)
cat3 := fixtures.Category(fixtures.CategoryMulti)
cat4 := fixtures.Category(fixtures.CategorySingle)
return c.Render("tests/"+which, fiber.Map{ return c.Render("tests/"+which, fiber.Map{
"TestRole": components.Role(cat1, &fixtures.RoleWithDarkColor, false), // multi
"TestRole2": components.Role(cat1, &fixtures.RoleWithDarkMediumColor, false), "Category1": []components.RoleTemplateData{
"TestRole3": components.Role(cat1, &fixtures.RoleWithLightColor, true), components.Role(cat1, &fixtures.RoleWithDarkColor, true, true),
"TestRole4": components.Role(cat1, &fixtures.RoleWithDarkColor, false), components.Role(cat1, &fixtures.RoleWithDarkMediumColor, true, true),
"TestRole5": components.Role(cat2, &fixtures.RoleWithLightColor, true), components.Role(cat1, &fixtures.RoleWithLightMediumColor, true, true),
"TestRole6": components.Role(cat1, &fixtures.RoleWithLightMediumColor, false), components.Role(cat1, &fixtures.RoleWithLightColor, true, true),
},
// single
"Category2": []components.RoleTemplateData{
components.Role(cat2, &fixtures.RoleWithDarkColor, true, true),
components.Role(cat2, &fixtures.RoleWithDarkMediumColor, true, true),
components.Role(cat2, &fixtures.RoleWithLightMediumColor, true, true),
components.Role(cat2, &fixtures.RoleWithLightColor, true, true),
},
// unsafe
"Category3": []components.RoleTemplateData{
components.Role(cat3, &fixtures.RoleWithDarkColor, true, false),
components.Role(cat3, &fixtures.RoleWithDarkMediumColor, false, false),
components.Role(cat3, &fixtures.RoleWithLightMediumColor, false, false),
components.Role(cat3, &fixtures.RoleWithLightColor, true, false),
},
// unselected
"Category4": []components.RoleTemplateData{
components.Role(cat4, &fixtures.RoleWithDarkColor, true, true),
components.Role(cat4, &fixtures.RoleWithDarkMediumColor, false, true),
components.Role(cat4, &fixtures.RoleWithLightMediumColor, false, true),
components.Role(cat4, &fixtures.RoleWithLightColor, false, true),
},
}, "layouts/main") }, "layouts/main")
} }

View file

@ -2,14 +2,20 @@ package utils
import ( import (
"fmt" "fmt"
"html/template"
"git.sapphic.engineer/roleypoly/v4/types"
) )
func RoleInputID(c *types.Category, r *types.Role) string { func RoleInputID(c string, r string) string {
return fmt.Sprintf("category-%s_role-%s", c.ID, r.ID) return fmt.Sprintf("category-%s_role-%s", c, r)
} }
func RoleInputName(c *types.Category) string { func RoleInputName(c string) string {
return fmt.Sprintf("category_group_%s", c.ID) return fmt.Sprintf("category_group_%s", c)
}
func TemplateFuncs() template.FuncMap {
return template.FuncMap{
"roleInputID": RoleInputID,
"roleInputName": RoleInputName,
}
} }

View file

@ -3,15 +3,14 @@ package utils_test
import ( import (
"testing" "testing"
"git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/utils" "git.sapphic.engineer/roleypoly/v4/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestRoleInputID(t *testing.T) { func TestRoleInputID(t *testing.T) {
assert.Equal(t, "category-123_role-456", utils.RoleInputID(&types.Category{ID: "123"}, &types.Role{ID: "456"})) assert.Equal(t, "category-123_role-456", utils.RoleInputID("123", "456"))
} }
func TestRoleInputName(t *testing.T) { func TestRoleInputName(t *testing.T) {
assert.Equal(t, "category_group_123", utils.RoleInputName(&types.Category{ID: "123"})) assert.Equal(t, "category_group_123", utils.RoleInputName("123"))
} }