Skip to content

Commit b1b35f6

Browse files
Merge pull request #88 from cgreeno/feat/seq-actor
feat(sequence): actor declarations
2 parents 91cfd36 + 4925d24 commit b1b35f6

7 files changed

Lines changed: 152 additions & 5 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sequenceDiagram
2+
actor A as Alice
3+
actor B as Bob
4+
A->>B: Hello Bob, how are you?
5+
B-->>A: I am good thanks!
6+
---
7+
+-------+ +-----+
8+
| Alice | | Bob |
9+
+---+---+ +--+--+
10+
| |
11+
| Hello Bob, how are you?
12+
+----------->|
13+
| |
14+
| I am good thanks!
15+
|<...........+
16+
| |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sequenceDiagram
2+
actor Alice as Alice2
3+
participant John as John2
4+
Alice->>John: Hi John
5+
John->>Mandy: Hi Mandy
6+
---
7+
+--------+ +-------+ +-------+
8+
| Alice2 | | John2 | | Mandy |
9+
+----+---+ +---+---+ +---+---+
10+
| | |
11+
| Hi John | |
12+
+------------>| |
13+
| | |
14+
| | Hi Mandy |
15+
| +------------>|
16+
| | |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sequenceDiagram
2+
actor A as Alice
3+
actor B as Bob
4+
A->>B: Hello Bob, how are you?
5+
B-->>A: I am good thanks!
6+
---
7+
┌───────┐ ┌─────┐
8+
│ Alice │ │ Bob │
9+
└───┬───┘ └──┬──┘
10+
│ │
11+
│ Hello Bob, how are you?
12+
├───────────►│
13+
│ │
14+
│ I am good thanks!
15+
│◄┈┈┈┈┈┈┈┈┈┈┈┤
16+
│ │
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sequenceDiagram
2+
actor Alice as Alice2
3+
participant John as John2
4+
Alice->>John: Hi John
5+
John->>Mandy: Hi Mandy
6+
---
7+
┌────────┐ ┌───────┐ ┌───────┐
8+
│ Alice2 │ │ John2 │ │ Mandy │
9+
└────┬───┘ └───┬───┘ └───┬───┘
10+
│ │ │
11+
│ Hi John │ │
12+
├────────────►│ │
13+
│ │ │
14+
│ │ Hi Mandy │
15+
│ ├────────────►│
16+
│ │ │

pkg/sequence/actor_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package sequence
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/AlexanderGrooff/mermaid-ascii/pkg/diagram"
8+
)
9+
10+
// TestActorDeclarations mirrors mermaid's actor cases (sequenceDiagram.spec.js
11+
// apa13): `actor` declares a participant, with or without an `as` alias.
12+
// mermaid draws actors as stick figures; ASCII renders the same labelled box.
13+
func TestActorDeclarations(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
input string
17+
wantID string
18+
wantLabel string
19+
}{
20+
{"actor with alias", "sequenceDiagram\nactor A as Alice\nactor B as Bob\nA->B: Hello Bob, how are you?\nB-->A: I am good thanks!", "A", "Alice"},
21+
{"actor without alias", "sequenceDiagram\nactor Alice\nAlice->>Alice: Hi", "Alice", "Alice"},
22+
{"uppercase AS", "sequenceDiagram\nactor A AS Database Server\nA->>A: q", "A", "Database Server"},
23+
{"case-insensitive keyword", "sequenceDiagram\nActor A as Alice\nA->>A: hi", "A", "Alice"},
24+
{"actor name with spaces", "sequenceDiagram\nactor cron job as Cron\ncron job->>cron job: tick", "cron job", "Cron"},
25+
}
26+
27+
for _, tt := range tests {
28+
t.Run(tt.name, func(t *testing.T) {
29+
d, err := Parse(tt.input)
30+
if err != nil {
31+
t.Fatalf("unexpected error: %v", err)
32+
}
33+
if len(d.Participants) == 0 {
34+
t.Fatal("expected at least one participant")
35+
}
36+
p := d.Participants[0]
37+
if p.ID != tt.wantID || p.Label != tt.wantLabel {
38+
t.Errorf("got %q/%q, want %q/%q", p.ID, p.Label, tt.wantID, tt.wantLabel)
39+
}
40+
output, err := Render(d, diagram.DefaultConfig())
41+
if err != nil {
42+
t.Fatalf("render error: %v", err)
43+
}
44+
if !strings.Contains(output, tt.wantLabel) {
45+
t.Errorf("output should contain label %q:\n%s", tt.wantLabel, output)
46+
}
47+
})
48+
}
49+
}
50+
51+
// TestActorParticipantMix mirrors mermaid's apa12: actors and participants
52+
// coexist, keep declaration order, and undeclared names in messages are still
53+
// auto-created after them.
54+
func TestActorParticipantMix(t *testing.T) {
55+
d, err := Parse("sequenceDiagram\nactor Alice as Alice2\nactor Bob\nparticipant John as John2\nparticipant Mandy\nAlice->>Bob: Hi Bob\nBob->>Alice: Hi Alice\nAlice->>John: Hi John\nJohn->>Mandy: Hi Mandy\nMandy ->>Joan: Hi Joan")
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
wantOrder := []struct{ id, label string }{
60+
{"Alice", "Alice2"}, {"Bob", "Bob"}, {"John", "John2"}, {"Mandy", "Mandy"}, {"Joan", "Joan"},
61+
}
62+
if len(d.Participants) != len(wantOrder) {
63+
t.Fatalf("want %d participants, got %d", len(wantOrder), len(d.Participants))
64+
}
65+
for i, w := range wantOrder {
66+
if p := d.Participants[i]; p.ID != w.id || p.Label != w.label {
67+
t.Errorf("participant %d = %q/%q, want %q/%q", i, p.ID, p.Label, w.id, w.label)
68+
}
69+
}
70+
}
71+
72+
// TestActorDuplicateRejected: redeclaring a name (whether via actor or
73+
// participant) is a duplicate, as for participant+participant.
74+
func TestActorDuplicateRejected(t *testing.T) {
75+
if _, err := Parse("sequenceDiagram\nactor A as Alice\nparticipant A as Al\nA->>A: hi"); err == nil {
76+
t.Error("expected duplicate participant error")
77+
}
78+
}

pkg/sequence/parser.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ const (
1515
)
1616

1717
var (
18-
// participantRegex matches participant declarations: participant [rest].
19-
// The rest ([ID] [as Label]) is split by parseParticipant, because IDs may
20-
// contain spaces (mermaid's ID lexer state allows them: "participant cron
21-
// job as Cron").
22-
participantRegex = regexp.MustCompile(`(?i)^\s*participant\s+(.+)$`)
18+
// participantRegex matches participant declarations: participant [rest] or
19+
// actor [rest]. mermaid renders an actor as a stick figure; in ASCII both
20+
// forms draw the same labelled box. The rest ([ID] [as Label]) is split by
21+
// parseParticipant, because IDs may contain spaces (mermaid's ID lexer
22+
// state allows them: "participant cron job as Cron").
23+
participantRegex = regexp.MustCompile(`(?i)^\s*(?:participant|actor)\s+(.+)$`)
2324

2425
// participantAsRegex splits "ID as Label" at the FIRST " as ", matching
2526
// mermaid's lexer for whitespace-free IDs. For IDs containing spaces this

pkg/sequence/renderer_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ func TestSequenceDiagramRendering(t *testing.T) {
4040
"participant_names_spaces.txt",
4141
"participant_names_dashes.txt",
4242
"participant_names_alias_equals.txt",
43+
"actor_alias.txt",
44+
"actor_participant_mix.txt",
4345
"central_connections.txt",
4446
"central_connection_self.txt",
4547
"cross_arrows.txt",
@@ -134,6 +136,8 @@ func TestSequenceDiagramRendering_ASCIISmokeTest(t *testing.T) {
134136
"participant_names_spaces.txt",
135137
"participant_names_dashes.txt",
136138
"participant_names_alias_equals.txt",
139+
"actor_alias.txt",
140+
"actor_participant_mix.txt",
137141
"central_connections.txt",
138142
"central_connection_self.txt",
139143
"cross_arrows.txt",

0 commit comments

Comments
 (0)