Skip to content
quarry Artifact [9e445be10f]
Login

Artifact [9e445be10f]

Artifact 9e445be10f7a652b7a8d8f5a2cf8efa8a14d6d7a565958436df94f1242a57e59:


package web

import (
	"strings"
	"testing"
)

// The composer offers two views of one draft: Rich, reading its blocks
// from this repository's markup-blocks and its pickers' lists from
// ref.suggest, and Markdown, the textarea the form posts. There is no third Preview: Rich renders through the same
// pipeline. Without script the tab bar stays hidden and the textarea is
// the composer.
func TestTheComposerOffersRichAndMarkdown(t *testing.T) {
	var b strings.Builder
	if err := noHooks.template(forumAnnouncePage).ExecuteTemplate(&b, "content", pageData{Repo: "alpha", Composer: true}); err != nil {
		t.Fatal(err)
	}
	s := b.String()
	if !strings.Contains(s, `data-hook="composer-rich"`) || !strings.Contains(s, `data-blocks="/r/alpha/markup-blocks"`) {
		t.Error("FAIL: the composer has no Rich view reading this repository's blocks")
	}
	if !strings.Contains(s, `data-suggest="/r/alpha/ref.suggest"`) {
		t.Error("FAIL: the Rich view's pickers do not ask this repository")
	}
	if !strings.Contains(s, `data-hook="composer-write"`) || strings.Contains(s, `data-tab="preview"`) {
		t.Error("FAIL: the composer's tabs are not Rich and Markdown")
	}
	if !strings.Contains(s, `<div class="f-tabs" hidden>`) || !strings.Contains(s, `name="body"`) {
		t.Error("FAIL: without script the composer is not the plain textarea")
	}
}

// "/" opens the search everywhere except where somebody is typing, and
// the Rich composer is a place where somebody is typing: it is an
// editable element, not an input, so the shortcut has to ask.
func TestSlashSearchLeavesTheRichComposerAlone(t *testing.T) {
	shortcuts := strings.Count(baseHTML, `evt.key == '/'`)
	guarded := strings.Count(baseHTML, `evt.key == '/' &amp;&amp; !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName) &amp;&amp; !document.activeElement.isContentEditable`)
	if shortcuts == 0 || guarded != shortcuts {
		t.Errorf("FAIL: %d of %d search shortcuts skip an editable element", guarded, shortcuts)
	}
}

// Whoever may edit a post (forum.get hands them its source: the author,
// whoever answers for an agent author, a moderator) sees Edit beside
// Reply, and the edit page is the composer holding the post's text.
func TestAnAuthorEditsTheirPostInTheComposer(t *testing.T) {
	data := pageData{Repo: "alpha", ForumRoot: "abc123", ForumViewer: "lou", CanWrite: true,
		ForumPosts: []forumPostRow{
			{Hash: "aaaaaaaaaaaa1111", User: "lou-bot", HTML: "<p>my agent's</p>", Source: "my agent's", Date: "2026-09-16T09:00:00Z",
				Edits: 1, EditedBy: "lou"},
			{Hash: "bbbbbbbbbbbb2222", User: "mia", HTML: "<p>hers</p>", Date: "2026-09-16T09:05:00Z"},
		}}
	var b strings.Builder
	if err := noHooks.template(forumThreadPage).ExecuteTemplate(&b, "forum-posts", data); err != nil {
		t.Fatal(err)
	}
	if n := strings.Count(b.String(), `data-hook="post-edit"`); n != 1 {
		t.Errorf("FAIL: %d Edit buttons, want one, on the post the viewer may edit", n)
	}
	if !strings.Contains(b.String(), `/r/alpha/forum/abc123/edit/aaaaaaaaaaaa1111`) {
		t.Error("FAIL: Edit does not lead to the post the viewer may edit")
	}
	if !strings.Contains(b.String(), "edited by lou") {
		t.Error("FAIL: an edit by someone other than the author does not say who made it")
	}
	var e strings.Builder
	edit := pageData{Repo: "alpha", ForumRoot: "abc123",
		ForumPosts: []forumPostRow{{Hash: "aaaaaaaaaaaa1111", User: "lou", Source: "the **text**"}}}
	if err := noHooks.template(forumEditPage).ExecuteTemplate(&e, "content", edit); err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(e.String(), `data-hook="composer-rich"`) || !strings.Contains(e.String(), `name="body"`) ||
		!strings.Contains(e.String(), "the **text**") {
		t.Error("FAIL: the edit page is not the composer holding the post's text")
	}
}

// A repository's channel box is Rich: a host for the editor reading
// this repository's blocks and pickers, and the Aa button that docks
// its bar. The textarea stays the field the form posts, and the box
// without script. A surface without the channel's machinery (a team
// room) keeps the plain box.
func TestTheChannelBoxIsRich(t *testing.T) {
	s := chatContent(t, pageData{Repo: "alpha", ChatViewer: "dan"})
	for _, want := range []string{`class="f-rich chat-rich"`, `data-blocks="/r/alpha/markup-blocks"`,
		`data-suggest="/r/alpha/ref.suggest"`, `data-hook="chat-aa"`, `class="composer-in" name="text"`} {
		if !strings.Contains(s, want) {
			t.Errorf("FAIL: the channel box is missing %s", want)
		}
	}
	if s := chatContent(t, pageData{Repo: "alpha", ChatViewer: "dan", ChatOn: "/t/crew"}); strings.Contains(s, "chat-rich") {
		t.Error("FAIL: a surface without the channel's machinery draws the Rich box")
	}
}

// A markdown wiki page is edited in the composer, Rich by default; a
// page in fossil's wiki markup keeps the plain box, because Rich writes
// markdown and that page is not.
func TestAMarkdownWikiPageIsEditedInRich(t *testing.T) {
	edit := func(rich bool) string {
		var b strings.Builder
		d := pageData{Repo: "alpha", Wiki: wikiDetail{Title: "Home"}, EditSource: "# Home\n\nwords\n", WikiRich: rich}
		if err := noHooks.template(wikiEditPage).ExecuteTemplate(&b, "content", d); err != nil {
			t.Fatal(err)
		}
		return b.String()
	}
	md := edit(true)
	if !strings.Contains(md, `data-hook="composer-rich"`) || !strings.Contains(md, `name="body"`) || !strings.Contains(md, "words") {
		t.Error("FAIL: a markdown page is not edited in the composer holding its text")
	}
	if fw := edit(false); strings.Contains(fw, `data-hook="composer-rich"`) || !strings.Contains(fw, `<textarea name="body"`) {
		t.Error("FAIL: a fossil-wiki page is offered Rich, which would write markdown into it")
	}
}