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
83 changes: 83 additions & 0 deletions cmd/mdl/svg_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package main
import (
"context"
"encoding/xml"
"fmt"
"io"
"net"
"net/url"
"os"
"os/exec"
"path/filepath"
"sync/atomic"
"testing"
"time"

"github.com/chromedp/chromedp"
"github.com/jaschaephraim/lrserver"
)

type nodeOverflow struct {
Expand Down Expand Up @@ -104,6 +108,69 @@ func TestSVGEndToEnd(t *testing.T) {
}
}

// TestSVGHeadlessDoesNotConnectToLiveReload verifies automated rendering is
// isolated from interactive editor reloads sent on the process-wide port.
func TestSVGHeadlessDoesNotConnectToLiveReload(t *testing.T) {
if !hasChrome() {
t.Skip("skipping: Chrome/Chromium not available in PATH")
}

reloadServer := lrserver.New(lrserver.DefaultName, lrserver.DefaultPort)
serverDone := make(chan error, 1)
go func() {
serverDone <- reloadServer.ListenAndServe()
}()
if err := waitForTCP("127.0.0.1:35729", 5*time.Second); err != nil {
select {
case serverErr := <-serverDone:
t.Skipf("live-reload port unavailable: %v", serverErr)
default:
t.Fatalf("start live-reload server: %v", err)
}
}
t.Cleanup(func() {
if err := reloadServer.Close(); err != nil {
t.Errorf("close live-reload server: %v", err)
}
})

var connected atomic.Bool
stopObserving := make(chan struct{})
observerDone := make(chan struct{})
go func() {
defer close(observerDone)
ticker := time.NewTicker(time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if reloadServer.IsConnected() {
connected.Store(true)
}
case <-stopObserving:
return
}
}
}()

outDir := t.TempDir()
cfg := config{
dir: outDir,
port: 0,
timeout: 30 * time.Second,
views: SliceFlag{"SystemContext"},
}
err := runSVG("goa.design/model/examples/basic/model", cfg)
close(stopObserving)
<-observerDone
if err != nil {
t.Fatalf("runSVG failed: %v", err)
}
if connected.Load() {
t.Fatal("headless renderer connected to the interactive LiveReload server")
}
}

func TestSVGNodeTextFits(t *testing.T) {
if !hasChrome() {
t.Skip("skipping: Chrome/Chromium not available in PATH")
Expand Down Expand Up @@ -353,6 +420,22 @@ func inspectVerticalEdgeLabelOverlaps(t *testing.T, path string) []verticalEdgeL
return overlaps
}

// waitForTCP waits until a local test server accepts connections.
func waitForTCP(address string, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
connection, err := net.DialTimeout("tcp", address, 100*time.Millisecond)
if err == nil {
if closeErr := connection.Close(); closeErr != nil {
return closeErr
}
return nil
}
time.Sleep(10 * time.Millisecond)
}
return fmt.Errorf("timeout waiting for %s", address)
}

func newChromeContext(t *testing.T) (context.Context, func()) {
t.Helper()

Expand Down
2 changes: 1 addition & 1 deletion cmd/mdl/webapp/dist/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cmd/mdl/webapp/dist/main.js.map

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions cmd/mdl/webapp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,20 @@ const App: React.FC = () => {
};

useEffect(() => {
// Setup refresh connector
const refreshConnector = new RefreshConnector(handleFileChange);
refreshConnector.connect();
const params = new URLSearchParams(document.location.search);
// Headless rendering has no watcher and must not receive reloads from an
// unrelated interactive editor using the shared LiveReload port.
const auto = params.get('auto');
const save = params.get('save');
const automated = auto === '1' || auto === 'true' || save === '1' || save === 'true';
const refreshConnector = automated ? null : new RefreshConnector(handleFileChange);
refreshConnector?.connect();

// Initial data load
loadData();

// Cleanup function
return () => {
// RefreshConnector cleanup would go here if it had a disconnect method
refreshConnector?.disconnect();
};
}, []);

Expand Down
Loading