- pact.ContentDescriptor gains Kind and Tools with the ContentKind and ContentTool constants - pact.ContentRecord gains PublishedAt, Parents, Authors and TopHeading; pact.ContentLocale gains Images, Videos and Headings, with the value types ContentRef, ContentPerson, ContentImage, ContentVideo and ContentHeading; every field is optional - TestContentDescriptorFields proves signpost hands them through and that a type setting none of them describes as before - the runnable content example, the pact and signpost READMEs and the menu item types page document the fields
200 lines
7.2 KiB
Go
200 lines
7.2 KiB
Go
package signpost_test
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// fullRecord is a record that sets every optional descriptor fact: first
|
|
// publication, two candidate parents, an author with a page of their own,
|
|
// two images (one decorative with an empty alt), a video and a heading
|
|
// outline under a level-2 top heading.
|
|
func fullRecord(id string) *pact.ContentRecord {
|
|
published := time.Date(2026, 10, 1, 9, 30, 0, 0, time.UTC)
|
|
changed := time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)
|
|
words := 420
|
|
return &pact.ContentRecord{
|
|
ID: id,
|
|
Published: true,
|
|
LastMod: &changed,
|
|
PublishedAt: &published,
|
|
Parents: []pact.ContentRef{
|
|
{Type: "acme-category", ID: "3"},
|
|
{Type: "acme-category", ID: "8"},
|
|
},
|
|
Authors: []pact.ContentPerson{{Name: "Ada Lovelace", Ref: pact.ContentRef{Type: "acme-author", ID: "12"}}},
|
|
TopHeading: 2,
|
|
Locales: []pact.ContentLocale{{
|
|
Locale: "en",
|
|
URL: "/articles/" + id,
|
|
Title: "Engines",
|
|
Words: &words,
|
|
Images: []pact.ContentImage{
|
|
{URL: "/media/engine.jpg", Alt: "The analytical engine", Width: 1200, Height: 800},
|
|
{URL: "/media/rule.png", Decorative: true},
|
|
},
|
|
Videos: []pact.ContentVideo{{Provider: "youtube", ID: "abc123"}},
|
|
Headings: []pact.ContentHeading{
|
|
{Level: 2, Text: "Origins"},
|
|
{Level: 3, Text: "The mill"},
|
|
{Level: 2, Text: "Legacy"},
|
|
},
|
|
}},
|
|
}
|
|
}
|
|
|
|
// TestContentDescriptorFields: a type that sets the optional descriptor
|
|
// facts (Kind, Tools and the record and locale fields) hands every value
|
|
// through signpost.Describe and signpost.Records unchanged, and ContentType
|
|
// exposes Kind and Tools; a type that sets none of them describes exactly
|
|
// as before, with nil and empty slices treated alike.
|
|
func TestContentDescriptorFields(t *testing.T) {
|
|
full := pact.MenuItemType{
|
|
Key: "acme-article",
|
|
DefaultPattern: "/articles/:slug",
|
|
Resolve: echo,
|
|
Content: &pact.ContentDescriptor{
|
|
Model: "acme_articles",
|
|
Kind: pact.ContentKindArticle,
|
|
Tools: map[string]string{pact.ContentToolEdit: "acme_update_page"},
|
|
Describe: func(_ context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) {
|
|
return fullRecord(in.ID), nil
|
|
},
|
|
Records: func(_ context.Context, in pact.ContentList) (pact.ContentPage, error) {
|
|
return pact.ContentPage{Records: []pact.ContentRecord{*fullRecord("1")}}, nil
|
|
},
|
|
},
|
|
}
|
|
plainRecord := func(id string) *pact.ContentRecord {
|
|
return &pact.ContentRecord{ID: id, Published: true, Locales: []pact.ContentLocale{{Locale: "en", URL: "/pages/" + id, Title: "About"}}}
|
|
}
|
|
plain := pact.MenuItemType{
|
|
Key: "acme-page",
|
|
DefaultPattern: "/pages/:slug",
|
|
Resolve: echo,
|
|
Content: &pact.ContentDescriptor{
|
|
Model: "acme_pages",
|
|
Describe: func(_ context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) {
|
|
return plainRecord(in.ID), nil
|
|
},
|
|
Records: func(_ context.Context, in pact.ContentList) (pact.ContentPage, error) {
|
|
return pact.ContentPage{Records: []pact.ContentRecord{*plainRecord("1")}}, nil
|
|
},
|
|
},
|
|
}
|
|
reg := contentRegistry(t, "", full, plain)
|
|
ctx := context.Background()
|
|
|
|
t.Run("full type hands every value through Describe", func(t *testing.T) {
|
|
got, err := reg.Describe(ctx, "acme-article", "5", "")
|
|
if err != nil {
|
|
t.Fatalf("Describe: %v", err)
|
|
}
|
|
if want := fullRecord("5"); !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("Describe = %+v, want %+v", got, want)
|
|
}
|
|
if got.TopHeading != 2 || len(got.Parents) != 2 || got.Parents[0].ID != "3" {
|
|
t.Fatalf("record facts lost: TopHeading %d, Parents %+v", got.TopHeading, got.Parents)
|
|
}
|
|
if a := got.Authors[0]; a.Name != "Ada Lovelace" || a.Ref != (pact.ContentRef{Type: "acme-author", ID: "12"}) {
|
|
t.Fatalf("author = %+v", a)
|
|
}
|
|
loc := got.Locales[0]
|
|
if len(loc.Images) != 2 || !loc.Images[1].Decorative || loc.Images[1].Alt != "" || loc.Images[0].Width != 1200 {
|
|
t.Fatalf("images = %+v", loc.Images)
|
|
}
|
|
if len(loc.Videos) != 1 || loc.Videos[0] != (pact.ContentVideo{Provider: "youtube", ID: "abc123"}) {
|
|
t.Fatalf("videos = %+v", loc.Videos)
|
|
}
|
|
if len(loc.Headings) != 3 || loc.Headings[1] != (pact.ContentHeading{Level: 3, Text: "The mill"}) {
|
|
t.Fatalf("headings = %+v", loc.Headings)
|
|
}
|
|
})
|
|
|
|
t.Run("full type hands every value through Records", func(t *testing.T) {
|
|
page, err := reg.Records(ctx, "acme-article", "", 10, "")
|
|
if err != nil {
|
|
t.Fatalf("Records: %v", err)
|
|
}
|
|
if len(page.Records) != 1 || !reflect.DeepEqual(page.Records[0], *fullRecord("1")) {
|
|
t.Fatalf("Records = %+v", page.Records)
|
|
}
|
|
})
|
|
|
|
t.Run("ContentType exposes Kind and Tools", func(t *testing.T) {
|
|
typ, ok := reg.ContentType("acme-article")
|
|
if !ok {
|
|
t.Fatal("ContentType(acme-article) not found")
|
|
}
|
|
if typ.Content.Kind != "article" {
|
|
t.Fatalf("Kind = %q", typ.Content.Kind)
|
|
}
|
|
if !reflect.DeepEqual(typ.Content.Tools, map[string]string{"edit": "acme_update_page"}) {
|
|
t.Fatalf("Tools = %v", typ.Content.Tools)
|
|
}
|
|
})
|
|
|
|
t.Run("type without the facts describes as before", func(t *testing.T) {
|
|
got, err := reg.Describe(ctx, "acme-page", "4", "")
|
|
if err != nil {
|
|
t.Fatalf("Describe: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got, plainRecord("4")) {
|
|
t.Fatalf("Describe = %+v", got)
|
|
}
|
|
if got.PublishedAt != nil || got.Parents != nil || got.Authors != nil || got.TopHeading != 0 {
|
|
t.Fatalf("plain record gained facts: %+v", got)
|
|
}
|
|
loc := got.Locales[0]
|
|
if loc.Images != nil || loc.Videos != nil || loc.Headings != nil {
|
|
t.Fatalf("plain locale gained facts: %+v", loc)
|
|
}
|
|
page, err := reg.Records(ctx, "acme-page", "", 10, "")
|
|
if err != nil || len(page.Records) != 1 || !reflect.DeepEqual(page.Records[0], *plainRecord("1")) {
|
|
t.Fatalf("Records = %+v, %v", page, err)
|
|
}
|
|
typ, _ := reg.ContentType("acme-page")
|
|
if typ.Content.Kind != "" || typ.Content.Tools != nil {
|
|
t.Fatalf("plain type: Kind %q, Tools %v", typ.Content.Kind, typ.Content.Tools)
|
|
}
|
|
if k, _ := reg.ContentTypeForModel("acme_pages"); k.Key != "acme-page" {
|
|
t.Fatalf("ContentTypeForModel = %q", k.Key)
|
|
}
|
|
})
|
|
|
|
t.Run("nil and empty slices are handed through alike", func(t *testing.T) {
|
|
empty := pact.MenuItemType{
|
|
Key: "acme-note",
|
|
DefaultPattern: "/notes/:slug",
|
|
Resolve: echo,
|
|
Content: &pact.ContentDescriptor{
|
|
Model: "acme_notes",
|
|
Tools: map[string]string{},
|
|
Describe: func(_ context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) {
|
|
return &pact.ContentRecord{
|
|
ID: in.ID, Parents: []pact.ContentRef{}, Authors: []pact.ContentPerson{},
|
|
Locales: []pact.ContentLocale{{Locale: "en", Images: []pact.ContentImage{}, Videos: []pact.ContentVideo{}, Headings: []pact.ContentHeading{}}},
|
|
}, nil
|
|
},
|
|
},
|
|
}
|
|
r := contentRegistry(t, "", empty)
|
|
got, err := r.Describe(ctx, "acme-note", "1", "")
|
|
if err != nil {
|
|
t.Fatalf("Describe: %v", err)
|
|
}
|
|
if len(got.Parents) != 0 || len(got.Authors) != 0 || len(got.Locales[0].Images) != 0 ||
|
|
len(got.Locales[0].Videos) != 0 || len(got.Locales[0].Headings) != 0 {
|
|
t.Fatalf("empty slices grew: %+v", got)
|
|
}
|
|
typ, _ := r.ContentType("acme-note")
|
|
if len(typ.Content.Tools) != 0 {
|
|
t.Fatalf("Tools = %v", typ.Content.Tools)
|
|
}
|
|
})
|
|
}
|