Bluesky linkifies nothing by itself. A URL in the text of a record is plain text unless a richtext facet says which bytes of the post are a link and where they point, so our posts carried URLs no one could press. Each link now travels on the Post as an output.Link with the offsets of the text it occupies, and the bluesky output turns those into app.bsky.richtext.facet#link. The offsets are UTF-8 byte offsets, not character counts: a facet measured in characters slides off the URL as soon as any Japanese text precedes it, and underlines the wrong words. Scanning moved from preview.go to findLinks in main.go, which now reports every link with its offsets rather than just the first one; the preview card still goes to the first, which is the one a reader meets first. X is unaffected, as it linkifies URLs itself. The facet feature only gains its lexicon type when marshalled, so the test checks the encoded record rather than the struct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
package output
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bluesky-social/indigo/api/bsky"
|
|
"github.com/bluesky-social/indigo/util"
|
|
)
|
|
|
|
func TestLinkFacets(t *testing.T) {
|
|
if facets := linkFacets(nil); facets != nil {
|
|
t.Errorf("a post with no links got %d facets, want none", len(facets))
|
|
}
|
|
|
|
text := "みてね https://example.com/x"
|
|
post := &bsky.FeedPost{
|
|
Text: text,
|
|
CreatedAt: time.Now().Format(util.ISO8601),
|
|
Facets: linkFacets([]Link{{URL: "https://example.com/x", ByteStart: 10, ByteEnd: 31}}),
|
|
}
|
|
|
|
// The feature carries its lexicon type only once marshalled, so the
|
|
// record as it goes over the wire is what has to be checked.
|
|
encoded, err := json.Marshal(post)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, want := range []string{
|
|
`"$type":"app.bsky.richtext.facet#link"`,
|
|
`"uri":"https://example.com/x"`,
|
|
`"byteStart":10`,
|
|
`"byteEnd":31`,
|
|
} {
|
|
if !strings.Contains(string(encoded), want) {
|
|
t.Errorf("record does not contain %s\ngot: %s", want, encoded)
|
|
}
|
|
}
|
|
|
|
// A facet that points at the wrong bytes underlines the wrong text.
|
|
index := post.Facets[0].Index
|
|
if slice := text[index.ByteStart:index.ByteEnd]; slice != "https://example.com/x" {
|
|
t.Errorf("facet covers %q, want the URL", slice)
|
|
}
|
|
}
|
|
|
|
func TestRecordKey(t *testing.T) {
|
|
rkey, err := recordKey("at://did:plc:abc123/app.bsky.feed.post/3kqz7xyz")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rkey != "3kqz7xyz" {
|
|
t.Errorf("recordKey = %q, want 3kqz7xyz", rkey)
|
|
}
|
|
|
|
for _, uri := range []string{"", "at://did:plc:abc123/app.bsky.feed.post/", "nonsense"} {
|
|
if _, err := recordKey(uri); err == nil {
|
|
t.Errorf("recordKey(%q) returned no error", uri)
|
|
}
|
|
}
|
|
}
|