Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2119a7fe5 |
@@ -268,12 +268,15 @@ rules). Case-sensitive comparison is unambiguous.
|
||||
A single user session can have multiple clients (phone, laptop, terminal).
|
||||
|
||||
- Each client gets a **separate server-to-client (S2C) message queue**.
|
||||
- The server fans out messages and events from other users, and those the user
|
||||
sends from any of its clients, to every client queue for that user session.
|
||||
The one exception is an IRC listener connection, which gets nothing queued for
|
||||
a command it sent itself: it has already sent its user the echo, or, for
|
||||
`PRIVMSG` and `NOTICE`, IRC sends none. Replies to a command (numerics) go
|
||||
only to the client that sent it.
|
||||
- The server fans out messages and events from other users to every client queue
|
||||
for that user session.
|
||||
- When the user sends a `PRIVMSG`, `NOTICE`, `JOIN`, `PART`, `NICK`, `TOPIC`,
|
||||
`MODE` or `KICK` from one of its clients, the resulting event goes to every
|
||||
client queue of the user's own session, as well as to the other users it is
|
||||
meant for. The one exception is an IRC listener connection that sent the
|
||||
command, which gets nothing queued for it: it has already sent its user the
|
||||
echo, or, for `PRIVMSG` and `NOTICE`, IRC sends none.
|
||||
- Replies to a command (numerics) go only to the client that sent it.
|
||||
- `GET /api/v1/messages` delivers from the calling client's specific queue,
|
||||
identified by the auth cookie.
|
||||
- Client queues have **independent expiry/pruning** — one client going offline
|
||||
@@ -746,7 +749,7 @@ Leave a channel.
|
||||
{"command": "PART", "to": "#general", "body": ["goodbye"]}
|
||||
```
|
||||
|
||||
**S2C (broadcast to the other channel members):**
|
||||
**S2C (broadcast to all channel members, including the departing user):**
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
+7
-3
@@ -757,7 +757,7 @@ func (a *App) handleServerMessage(msg *api.Message) {
|
||||
case irc.CmdJoin:
|
||||
a.handleJoinEvent(msg, timestamp)
|
||||
case irc.CmdPart:
|
||||
a.handlePartEvent(msg, timestamp)
|
||||
a.handlePartEvent(msg, timestamp, myNick)
|
||||
case irc.CmdQuit:
|
||||
a.handleQuitEvent(msg, timestamp)
|
||||
case irc.CmdNick:
|
||||
@@ -813,10 +813,14 @@ func (a *App) handleJoinEvent(
|
||||
))
|
||||
}
|
||||
|
||||
// handlePartEvent shows another user's part. The server
|
||||
// also sends the user's own part back to it, which cmdPart
|
||||
// has already shown; showing it again would add a second
|
||||
// line and mark the channel just left as unread.
|
||||
func (a *App) handlePartEvent(
|
||||
msg *api.Message, timestamp string,
|
||||
msg *api.Message, timestamp, myNick string,
|
||||
) {
|
||||
if msg.To == "" {
|
||||
if msg.To == "" || msg.From == myNick {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package cli
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
api "sneak.berlin/go/neoirc/internal/cli/api"
|
||||
"sneak.berlin/go/neoirc/pkg/irc"
|
||||
)
|
||||
|
||||
func TestMemberPrefix(t *testing.T) {
|
||||
@@ -49,3 +51,66 @@ func TestMemberPrefix(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPartEvent checks that a PART event adds a line to the
|
||||
// channel's buffer and marks it unread, except for the
|
||||
// user's own part, which cmdPart has already shown.
|
||||
func TestPartEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
from string
|
||||
wantLines int
|
||||
wantUnread int
|
||||
}{
|
||||
{name: "own part", from: "alice", wantLines: 0, wantUnread: 0},
|
||||
{name: "other member", from: "bob", wantLines: 1, wantUnread: 1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := &App{ui: NewUI(), nick: "alice"}
|
||||
runUI(t, app.ui)
|
||||
|
||||
app.handleServerMessage(&api.Message{
|
||||
Command: irc.CmdPart, From: test.from, To: "#chan",
|
||||
})
|
||||
|
||||
var lines, unread int
|
||||
|
||||
app.ui.app.QueueUpdate(func() {
|
||||
buf := app.ui.getOrCreateBuffer("#chan")
|
||||
lines = len(buf.Lines)
|
||||
unread = buf.Unread
|
||||
})
|
||||
|
||||
if lines != test.wantLines || unread != test.wantUnread {
|
||||
t.Fatalf("got %d lines and %d unread, want %d and %d",
|
||||
lines, unread, test.wantLines, test.wantUnread)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// runUI runs ui on a simulated screen until the test ends.
|
||||
func runUI(t *testing.T, ui *UI) {
|
||||
t.Helper()
|
||||
|
||||
ui.app.SetScreen(tcell.NewSimulationScreen(""))
|
||||
|
||||
done := make(chan error)
|
||||
|
||||
go func() { done <- ui.Run() }()
|
||||
|
||||
t.Cleanup(func() {
|
||||
ui.Stop()
|
||||
|
||||
err := <-done
|
||||
if err != nil {
|
||||
t.Errorf("run ui: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user