Skip to content

Commit 23364df

Browse files
committed
refactor: move Templ components to the components package
- Moved Templ components to a separate `components` package - Introduced `internal/viewmodel` with `ToasterViewModel` and `ToastViewModel` - Replaced direct access to `goaster.Queue`, `Toast`, and `Icons` in templates - Flattened render data via - Moved `GetToastEntranceClass` to `viewmodel` as a pure string-based helper
1 parent f24be86 commit 23364df

20 files changed

+449
-2005
lines changed

builder_test.go

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
package goaster_test
1+
package goaster
22

33
import (
44
"testing"
5-
6-
"github.com/indaco/goaster"
75
)
86

97
func TestToasterBuilder_Defaults(t *testing.T) {
10-
toaster := goaster.NewToasterBuilder().Build()
8+
toaster := NewToasterBuilder().Build()
119

1210
if toaster == nil {
1311
t.Fatal("expected toaster instance, got nil")
1412
}
15-
if toaster.Position != goaster.BottomRight {
13+
if toaster.Position != BottomRight {
1614
t.Errorf("expected default position to be BottomRight, got %v", toaster.Position)
1715
}
1816
if len(toaster.Icons) == 0 {
@@ -24,13 +22,13 @@ func TestToasterBuilder_Defaults(t *testing.T) {
2422
}
2523

2624
func TestToasterBuilder_CustomConfig(t *testing.T) {
27-
toaster := goaster.NewToasterBuilder().
28-
WithPosition(goaster.TopLeft).
25+
toaster := NewToasterBuilder().
26+
WithPosition(TopLeft).
2927
WithBorder(false).
3028
WithAutoDismiss(false).
3129
Build()
3230

33-
if toaster.Position != goaster.TopLeft {
31+
if toaster.Position != TopLeft {
3432
t.Errorf("expected position TopLeft, got %v", toaster.Position)
3533
}
3634
if toaster.Border != false {
@@ -42,20 +40,20 @@ func TestToasterBuilder_CustomConfig(t *testing.T) {
4240
}
4341

4442
func TestToasterBuilder_WithAllOptions(t *testing.T) {
45-
toaster := goaster.NewToasterBuilder().
46-
WithVariant(goaster.AccentLight).
43+
toaster := NewToasterBuilder().
44+
WithVariant(AccentLight).
4745
WithBorder(false).
4846
WithRounded(true).
4947
WithShowIcon(false).
5048
WithButton(false).
5149
WithAutoDismiss(false).
5250
WithAnimation(false).
5351
WithProgressBar(false).
54-
WithPosition(goaster.TopCenter).
55-
WithIcon(goaster.ErrorLevel, "<svg>Error</svg>").
52+
WithPosition(TopCenter).
53+
WithIcon(ErrorLevel, "<svg>Error</svg>").
5654
Build()
5755

58-
if toaster.Variant != goaster.AccentLight {
56+
if toaster.Variant != AccentLight {
5957
t.Errorf("expected Variant to be AccentLight, got %v", toaster.Variant)
6058
}
6159
if toaster.Border {
@@ -79,22 +77,22 @@ func TestToasterBuilder_WithAllOptions(t *testing.T) {
7977
if toaster.ProgressBar {
8078
t.Errorf("expected ProgressBar=false")
8179
}
82-
if toaster.Position != goaster.TopCenter {
80+
if toaster.Position != TopCenter {
8381
t.Errorf("expected Position=TopCenter, got %v", toaster.Position)
8482
}
85-
if icon, ok := toaster.Icons[goaster.ErrorLevel]; !ok || icon != "<svg>Error</svg>" {
83+
if icon, ok := toaster.Icons[ErrorLevel]; !ok || icon != "<svg>Error</svg>" {
8684
t.Errorf("expected custom ErrorLevel icon to be set, got %q", icon)
8785
}
8886
}
8987

9088
func TestToasterBuilder_WithOptionsMerge(t *testing.T) {
91-
builder := goaster.NewToasterBuilder().
89+
builder := NewToasterBuilder().
9290
WithBorder(false)
9391

9492
// Apply additional options
9593
toaster := builder.WithOptions(
96-
goaster.WithAutoDismiss(false),
97-
goaster.WithAnimation(false),
94+
WithAutoDismiss(false),
95+
WithAnimation(false),
9896
).Build()
9997

10098
if toaster.Border != false {
@@ -110,31 +108,31 @@ func TestToasterBuilder_WithOptionsMerge(t *testing.T) {
110108

111109
func TestToasterBuilder_WithIconNilMap(t *testing.T) {
112110
// Manually simulate icon map being nil
113-
builder := goaster.NewToasterBuilder()
111+
builder := NewToasterBuilder()
114112
builder.Build().Icons = nil // forcibly set to nil
115113

116114
// Set new icon — this should initialize the map
117-
toaster := builder.WithIcon(goaster.WarningLevel, "<svg>Warning</svg>").Build()
115+
toaster := builder.WithIcon(WarningLevel, "<svg>Warning</svg>").Build()
118116

119-
icon, ok := toaster.Icons[goaster.WarningLevel]
117+
icon, ok := toaster.Icons[WarningLevel]
120118
if !ok || icon != "<svg>Warning</svg>" {
121119
t.Errorf("expected WarningLevel icon to be set correctly, got %q", icon)
122120
}
123121
}
124122

125123
func TestToasterBuilder_Build_FallbackToDefaultPosition(t *testing.T) {
126124
// Simulate missing Position
127-
builder := goaster.NewToasterBuilder()
125+
builder := NewToasterBuilder()
128126
builder.Build().Position = "" // forcibly unset
129127
toaster := builder.Build()
130128

131-
if toaster.Position != goaster.BottomRight {
129+
if toaster.Position != BottomRight {
132130
t.Errorf("expected fallback Position to be BottomRight, got %v", toaster.Position)
133131
}
134132
}
135133

136134
func TestToasterBuilder_Build_FallbackToDefaultIcons(t *testing.T) {
137-
builder := goaster.NewToasterBuilder()
135+
builder := NewToasterBuilder()
138136
builder.Build().Icons = nil // forcibly unset
139137
toaster := builder.Build()
140138

@@ -144,36 +142,33 @@ func TestToasterBuilder_Build_FallbackToDefaultIcons(t *testing.T) {
144142
}
145143

146144
func TestToasterBuilder_Build_FallbackToNewQueue_Clean(t *testing.T) {
147-
builder := goaster.NewToasterBuilder()
148-
builder.Build().Queue().Dequeue() // trigger init
149-
builder.Build().Queue().Dequeue()
150-
builder.Build().Queue().Dequeue()
151-
152-
// forcibly nil the queue
153-
builder.Build().Queue().Dequeue()
145+
builder := NewToasterBuilder()
154146
toaster := builder.Build()
155-
toaster.Queue().Dequeue()
156147

157-
// ACTUAL REAL CLEAN VERSION BELOW:
158-
toaster = goaster.NewToasterBuilder().Build()
159-
toaster.Queue().Dequeue() // Should not panic
148+
// Attempt to dequeue from an empty queue
149+
_, err := toaster.Queue().Dequeue()
150+
if err == nil {
151+
t.Errorf("expected error when dequeuing from an empty queue, got nil")
152+
}
153+
154+
// Make sure the queue is initialized
160155
if toaster.Queue() == nil {
161156
t.Errorf("expected Queue to be initialized")
162157
}
163158
}
164159

165160
func TestToasterBuilder_Build_SetsDefaultPositionIfEmpty(t *testing.T) {
166-
builder := goaster.NewToasterBuilder()
161+
builder := NewToasterBuilder()
167162
builder.Build().Position = "" // simulate unset
168163

169164
toaster := builder.Build()
170-
if toaster.Position != goaster.BottomRight {
165+
if toaster.Position != BottomRight {
171166
t.Errorf("expected Position to default to BottomRight, got %v", toaster.Position)
172167
}
173168
}
174169

175170
func TestToasterBuilder_Build_SetsDefaultIconsIfNil(t *testing.T) {
176-
builder := goaster.NewToasterBuilder()
171+
builder := NewToasterBuilder()
177172
builder.Build().Icons = nil // simulate nil
178173

179174
toaster := builder.Build()
@@ -183,19 +178,21 @@ func TestToasterBuilder_Build_SetsDefaultIconsIfNil(t *testing.T) {
183178
}
184179

185180
func TestToasterBuilder_Build_InitializesQueueIfNil(t *testing.T) {
186-
builder := goaster.NewToasterBuilder()
187-
builder.Build().Queue().Dequeue() // init first
181+
builder := NewToasterBuilder()
188182

189-
builder.Build().Queue().Dequeue() // simulate use
190-
builder.Build().Queue().Dequeue()
183+
// Manually set queue to nil to simulate uninitialized queue
184+
builder.Build().queue = nil
191185

192-
// force nil
193-
builder.Build().Queue().Dequeue()
186+
// Call Build again to trigger fallback
187+
toaster := builder.Build()
194188

195-
// Actually clean version:
196-
toaster := goaster.NewToasterBuilder().Build()
197-
toaster.Queue().Dequeue()
198189
if toaster.Queue() == nil {
199-
t.Errorf("expected Queue to be initialized")
190+
t.Errorf("expected Queue to be initialized, got nil")
191+
}
192+
193+
// Ensure it functions properly
194+
_, err := toaster.Queue().Dequeue()
195+
if err == nil {
196+
t.Errorf("expected error when dequeuing from empty queue, got nil")
200197
}
201198
}

components/toaster.templ

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package components
2+
3+
import (
4+
"github.com/indaco/goaster/components/css"
5+
"github.com/indaco/goaster/components/js"
6+
"github.com/indaco/goaster/internal/viewmodel"
7+
"strconv"
8+
)
9+
10+
templ Container(t viewmodel.ToasterViewModel) {
11+
@css.GoasterCSS(t.Variant)
12+
<div class="gttContainer" data-position={ t.Position }>
13+
for _, toast := range t.Toasts {
14+
@component(toast, t)
15+
}
16+
</div>
17+
@js.GoasterJS()
18+
}
19+
20+
templ component(t viewmodel.ToastViewModel, toaster viewmodel.ToasterViewModel) {
21+
<div
22+
role="alert"
23+
class={ "gttToast", "gttShow", viewmodel.GetToastEntranceClass(toaster.Position) }
24+
data-bordered={ strconv.FormatBool(toaster.Border) }
25+
data-rounded={ strconv.FormatBool(toaster.Rounded) }
26+
data-level={ t.Level }
27+
data-variant={ toaster.Variant }
28+
if toaster.AutoDismiss {
29+
data-auto-dismiss="true"
30+
} else {
31+
data-auto-dismiss="false"
32+
}
33+
>
34+
if toaster.AutoDismiss && toaster.ProgressBar {
35+
<div class="gttProgressBar"></div>
36+
}
37+
if toaster.ShowIcon {
38+
<div class="gttIcon">
39+
@templ.Raw(t.Icon)
40+
</div>
41+
}
42+
<p class="gttMessage">{ t.Message }</p>
43+
if toaster.Button {
44+
<button class="gttCloseBtn">
45+
<span class="gttSrOnly">Dismiss</span>
46+
<svg style="stroke: currentColor; flex-shrink: 0; width: 1rem; height: 1rem;" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
47+
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"></path>
48+
</svg>
49+
</button>
50+
}
51+
</div>
52+
}

0 commit comments

Comments
 (0)