Implement automatic rebuild and browser reload during development:
- File watcher monitors .go files for changes with configurable extensions
- Builder compiles Go source to WASM on file changes
- LiveReload WebSocket server notifies connected browsers to reload
- DevServer combines all components for easy development setup
- HTML injection adds reload script automatically
Usage:
dev := host.NewDevServer("public", "index.html", ".", "public/app.wasm")
dev.ListenAndServe(":8080")
Closes #9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
217 lines
4.9 KiB
Go
217 lines
4.9 KiB
Go
package host
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestWatcher_DetectsNewFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create initial file
|
|
initialFile := filepath.Join(dir, "initial.go")
|
|
if err := os.WriteFile(initialFile, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Create new file
|
|
newFile := filepath.Join(dir, "new.go")
|
|
if err := os.WriteFile(newFile, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait for detection
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count == 0 {
|
|
t.Error("Expected change to be detected for new file")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_DetectsModifiedFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create initial file
|
|
file := filepath.Join(dir, "test.go")
|
|
if err := os.WriteFile(file, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Ensure mod time will be different
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Modify file
|
|
if err := os.WriteFile(file, []byte("package main\n// updated"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait for detection
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count == 0 {
|
|
t.Error("Expected change to be detected for modified file")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_DetectsDeletedFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create initial file
|
|
file := filepath.Join(dir, "test.go")
|
|
if err := os.WriteFile(file, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Delete file
|
|
if err := os.Remove(file); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait for detection
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count == 0 {
|
|
t.Error("Expected change to be detected for deleted file")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_IgnoresNonMatchingExtensions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create initial go file
|
|
goFile := filepath.Join(dir, "main.go")
|
|
if err := os.WriteFile(goFile, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond), WithExtensions(".go"))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Create non-matching file
|
|
txtFile := filepath.Join(dir, "readme.txt")
|
|
if err := os.WriteFile(txtFile, []byte("readme"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count != 0 {
|
|
t.Error("Expected no change detected for non-matching extension")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_CustomExtensions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create initial file
|
|
file := filepath.Join(dir, "style.css")
|
|
if err := os.WriteFile(file, []byte("body {}"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond), WithExtensions(".css", ".html"))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Ensure mod time will be different
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Modify css file
|
|
if err := os.WriteFile(file, []byte("body { color: red; }"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait for detection
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count == 0 {
|
|
t.Error("Expected change to be detected for custom extension")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_SkipsHiddenDirs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Create hidden directory with go file
|
|
hiddenDir := filepath.Join(dir, ".hidden")
|
|
if err := os.MkdirAll(hiddenDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a visible go file first
|
|
visibleFile := filepath.Join(dir, "visible.go")
|
|
if err := os.WriteFile(visibleFile, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var changeCount int32
|
|
w := NewWatcher(dir, func() {
|
|
atomic.AddInt32(&changeCount, 1)
|
|
}, WithInterval(50*time.Millisecond))
|
|
|
|
if err := w.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Stop()
|
|
|
|
// Create file in hidden directory
|
|
hiddenFile := filepath.Join(hiddenDir, "hidden.go")
|
|
if err := os.WriteFile(hiddenFile, []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Wait
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if count := atomic.LoadInt32(&changeCount); count != 0 {
|
|
t.Error("Expected no change detected for hidden directory")
|
|
}
|
|
}
|