Files
iris/host/watcher_test.go
Hugo Nijhuis 62f085e8e6
All checks were successful
CI / build (pull_request) Successful in 32s
Add hot reload for development
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>
2026-01-09 17:03:28 +01:00

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")
}
}