Skip to content
Merged
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
66 changes: 66 additions & 0 deletions atomx/atomx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package atomx

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_Atoms_String(t *testing.T) {
Expand Down Expand Up @@ -37,3 +39,67 @@ func Test_Atoms_Has(t *testing.T) {
t.Fatalf("expected %v, got %v", exp, act)
}
}

func Test_Atom_String(t *testing.T) {
t.Parallel()
r := require.New(t)

r.Equal("div", Div.String())
r.Equal("a", A.String())
}

func Test_Atom_Atom(t *testing.T) {
t.Parallel()
r := require.New(t)

r.Equal(Div, Div.Atom())
r.Equal(A, A.Atom())
}

func Test_Atom_Is(t *testing.T) {
t.Parallel()
r := require.New(t)

r.True(A.Is(A, B, P))
r.False(A.Is(B, P, Div))
r.False(A.Is())
}

func Test_IsAtom(t *testing.T) {
t.Parallel()
r := require.New(t)

r.False(IsAtom(nil, A))
r.True(IsAtom(A, A, B))
r.False(IsAtom(A, B, P))
}

func Test_Headings(t *testing.T) {
t.Parallel()
r := require.New(t)

h := Headings()
r.Len(h, 6)
r.True(h.Has(H1))
r.True(h.Has(H2))
r.True(h.Has(H3))
r.True(h.Has(H4))
r.True(h.Has(H5))
r.True(h.Has(H6))
r.False(h.Has(P))
}

func Test_Inlines(t *testing.T) {
t.Parallel()
r := require.New(t)

inl := Inlines()
r.True(inl.Has(A))
r.True(inl.Has(B))
r.True(inl.Has(Br))
r.True(inl.Has(Image))
r.True(inl.Has(Img))
r.True(inl.Has(Link))
r.True(inl.Has(Ref))
r.False(inl.Has(Div))
}
26 changes: 26 additions & 0 deletions binding/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package binding

import (
"errors"
"testing"

"github.com/stretchr/testify/require"
)

func TestErrPath_Error(t *testing.T) {
t.Parallel()
r := require.New(t)

e := ErrPath("/some/path")
r.Contains(e.Error(), "/some/path")
r.Contains(e.Error(), "could not parse section from")
}

func TestErrPath_Is(t *testing.T) {
t.Parallel()
r := require.New(t)

e := ErrPath("/path1")
r.True(e.Is(ErrPath("/path2")))
r.False(e.Is(errors.New("other error")))
}
128 changes: 128 additions & 0 deletions blog/article_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package blog

import (
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestArticle_IsPublished(t *testing.T) {
t.Parallel()

t.Run("past date returns true", func(t *testing.T) {
r := require.New(t)
a := Article{Published: time.Now().Add(-24 * time.Hour)}
r.True(a.IsPublished())
})

t.Run("future date returns false", func(t *testing.T) {
r := require.New(t)
a := Article{Published: time.Now().Add(24 * time.Hour)}
r.False(a.IsPublished())
})
}

func TestArticle_URL(t *testing.T) {
t.Parallel()
r := require.New(t)

a := Article{Slug: "my-post"}
r.Equal("/my-post/", a.URL())
}

func TestArticle_FormattedDate(t *testing.T) {
t.Parallel()
r := require.New(t)

a := Article{Published: time.Date(2024, time.March, 15, 0, 0, 0, 0, time.UTC)}
r.Equal("March 15, 2024", a.FormattedDate())
}

func TestArticle_ISODate(t *testing.T) {
t.Parallel()
r := require.New(t)

a := Article{Published: time.Date(2024, time.March, 15, 10, 30, 0, 0, time.UTC)}
iso := a.ISODate()
r.Contains(iso, "2024-03-15")
r.Contains(iso, "T10:30:00")
}

func TestCalculateReadingTime(t *testing.T) {
t.Parallel()

t.Run("200 words is 1 minute", func(t *testing.T) {
r := require.New(t)
words := strings.Repeat("word ", 200)
r.Equal(1, calculateReadingTime(words))
})

t.Run("400 words is 2 minutes", func(t *testing.T) {
r := require.New(t)
words := strings.Repeat("word ", 400)
r.Equal(2, calculateReadingTime(words))
})

t.Run("1 word rounds up to 1 minute", func(t *testing.T) {
r := require.New(t)
r.Equal(1, calculateReadingTime("hello"))
})

t.Run("empty is 0 minutes", func(t *testing.T) {
r := require.New(t)
r.Equal(0, calculateReadingTime(""))
})

t.Run("strips HTML before counting", func(t *testing.T) {
r := require.New(t)
html := "<p>" + strings.Repeat("word ", 200) + "</p>"
r.Equal(1, calculateReadingTime(html))
})
}

func TestStripHTML(t *testing.T) {
t.Parallel()

tests := []struct {
name string
in string
out string
}{
{"removes tags", "<p>hello</p>", "hello"},
{"nested tags", "<div><p>hello</p></div>", "hello"},
{"preserves text", "no tags here", "no tags here"},
{"empty", "", ""},
{"self closing", "a<br/>b", "ab"},
{"attributes", `<a href="x">link</a>`, "link"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
r.Equal(tt.out, stripHTML(tt.in))
})
}
}

func TestStripDetailsBlocks(t *testing.T) {
t.Parallel()

t.Run("removes details block", func(t *testing.T) {
r := require.New(t)
input := "before<details><summary>Click</summary>Hidden</details>after"
r.Equal("beforeafter", stripDetailsBlocks(input))
})

t.Run("preserves non-details content", func(t *testing.T) {
r := require.New(t)
r.Equal("hello world", stripDetailsBlocks("hello world"))
})

t.Run("removes multiple details blocks", func(t *testing.T) {
r := require.New(t)
input := "a<details>1</details>b<details>2</details>c"
r.Equal("abc", stripDetailsBlocks(input))
})
}
37 changes: 37 additions & 0 deletions blog/blog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package blog

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestValidateOutputDir(t *testing.T) {
t.Parallel()

tests := []struct {
name string
root string
outDir string
wantErr bool
errMsg string
}{
{"valid subdirectory", "/project", "/project/public", false, ""},
{"empty output dir", "/project", "", true, "output directory cannot be empty"},
{"same as root", "/project", "/project", true, "output directory cannot be the project root"},
{"outside root", "/project", "/tmp/output", true, "output directory must be inside the project root"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
err := validateOutputDir(tt.root, tt.outDir)
if tt.wantErr {
r.Error(err)
r.Contains(err.Error(), tt.errMsg)
} else {
r.NoError(err)
}
})
}
}
89 changes: 89 additions & 0 deletions blog/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package blog

import (
"testing"
"testing/fstest"

"github.com/stretchr/testify/require"
)

func TestDefaultConfig(t *testing.T) {
t.Parallel()
r := require.New(t)

cfg := DefaultConfig()

r.Equal("My Blog", cfg.Title)
r.Equal("A blog powered by hype", cfg.Description)
r.Equal("suspended", cfg.Theme)
r.Equal("monokai", cfg.Highlight.Style)
r.False(cfg.Highlight.LineNumbers)
r.Equal("summary_large_image", cfg.SEO.TwitterCard)
r.Equal("content", cfg.ContentDir)
r.Equal("public", cfg.OutputDir)
}

func TestLoadConfig(t *testing.T) {
t.Parallel()

t.Run("parses config.yaml", func(t *testing.T) {
r := require.New(t)
fsys := fstest.MapFS{
"config.yaml": &fstest.MapFile{
Data: []byte("title: Test Blog\ndescription: A test\nbaseURL: https://test.com\ncontentDir: posts\noutputDir: dist\n"),
},
}
cfg, err := LoadConfig(fsys)
r.NoError(err)
r.Equal("Test Blog", cfg.Title)
r.Equal("A test", cfg.Description)
r.Equal("https://test.com", cfg.BaseURL)
r.Equal("posts", cfg.ContentDir)
r.Equal("dist", cfg.OutputDir)
})

t.Run("falls back to config.yml", func(t *testing.T) {
r := require.New(t)
fsys := fstest.MapFS{
"config.yml": &fstest.MapFile{
Data: []byte("title: YML Blog\n"),
},
}
cfg, err := LoadConfig(fsys)
r.NoError(err)
r.Equal("YML Blog", cfg.Title)
})

t.Run("missing config returns error", func(t *testing.T) {
r := require.New(t)
fsys := fstest.MapFS{}
_, err := LoadConfig(fsys)
r.Error(err)
r.Contains(err.Error(), "failed to read config file")
})

t.Run("malformed YAML returns error", func(t *testing.T) {
r := require.New(t)
fsys := fstest.MapFS{
"config.yaml": &fstest.MapFile{
Data: []byte("title: [invalid yaml\n"),
},
}
_, err := LoadConfig(fsys)
r.Error(err)
r.Contains(err.Error(), "failed to parse config")
})

t.Run("empty contentDir defaults", func(t *testing.T) {
r := require.New(t)
fsys := fstest.MapFS{
"config.yaml": &fstest.MapFile{
Data: []byte("title: Blog\ncontentDir: \"\"\noutputDir: \"\"\n"),
},
}
cfg, err := LoadConfig(fsys)
r.NoError(err)
r.Equal("content", cfg.ContentDir)
r.Equal("public", cfg.OutputDir)
})
}
Loading
Loading