PR #448 stripped a leading H1 matching --name on the markdown path of `dws doc create`, but the JSONML path (--content-format jsonml) was never covered. Rich documents — tables, callouts, styled blocks — go through create_document + update_document(jsonml=...), which writes the body verbatim, so a leading h1 whose text equals the document name renders the title twice (the "two headings" effect the doc platform shows because it already renders --name as the page title). Add stripLeadingDuplicateTitleJSONML: parse the marshaled JSONML body, skip an optional ["root", {}, ...] wrapper, and drop the first node when it is an h1 whose concatenated leaf text equals --name (trimmed, case-insensitive). Any parse failure or non-match leaves the body untouched, so a valid write is never blocked. A stderr note mirrors the markdown path so agents learn the convention.
269 lines
8.0 KiB
Go
269 lines
8.0 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package helpers
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestStripLeadingDuplicateTitleHeading(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
content string
|
|
docName string
|
|
want string
|
|
stripped bool
|
|
}{
|
|
{
|
|
name: "exact match stripped",
|
|
content: "# 2026-06-10 Ari晚会简报\n\n聚焦今日变化。\n",
|
|
docName: "2026-06-10 Ari晚会简报",
|
|
want: "聚焦今日变化。\n",
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "leading blank lines tolerated",
|
|
content: "\n\n# Title\nbody",
|
|
docName: "Title",
|
|
want: "body",
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "case insensitive match",
|
|
content: "# weekly REPORT\nbody",
|
|
docName: "Weekly Report",
|
|
want: "body",
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "atx closing hashes",
|
|
content: "# Title #\nbody",
|
|
docName: "Title",
|
|
want: "body",
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "title-only content becomes empty",
|
|
content: "# Title",
|
|
docName: "Title",
|
|
want: "",
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "different heading kept",
|
|
content: "# 背景\nbody",
|
|
docName: "2026-06-10 Ari晚会简报",
|
|
want: "# 背景\nbody",
|
|
stripped: false,
|
|
},
|
|
{
|
|
name: "h2 not touched",
|
|
content: "## Title\nbody",
|
|
docName: "Title",
|
|
want: "## Title\nbody",
|
|
stripped: false,
|
|
},
|
|
{
|
|
name: "no heading kept",
|
|
content: "plain text\n# Title later",
|
|
docName: "Title",
|
|
want: "plain text\n# Title later",
|
|
stripped: false,
|
|
},
|
|
{
|
|
name: "name ending with hash not over-trimmed",
|
|
content: "# C#\nbody",
|
|
docName: "C",
|
|
want: "# C#\nbody",
|
|
stripped: false,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, stripped := stripLeadingDuplicateTitleHeading(tc.content, tc.docName)
|
|
if stripped != tc.stripped {
|
|
t.Fatalf("stripped = %v, want %v", stripped, tc.stripped)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("content = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDocCreateStripsDuplicateTitleHeading verifies the end-to-end behavior:
|
|
// `doc create --name X --content "# X\n..."` must not forward the duplicate
|
|
// H1 to the MCP tool — the platform renders the document name as the page
|
|
// title, so keeping it would display two headings.
|
|
func TestDocCreateStripsDuplicateTitleHeading(t *testing.T) {
|
|
runner := &docCommandRunner{}
|
|
root := newDocTestRoot(runner)
|
|
|
|
_, errOut, err := executeDocCommand(t, root,
|
|
"create", "--name", "2026-06-10 Ari晚会简报",
|
|
"--content", "# 2026-06-10 Ari晚会简报\n\n聚焦今日变化。")
|
|
if err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
got, _ := runner.last.Params["markdown"].(string)
|
|
if got != "聚焦今日变化。" {
|
|
t.Errorf("markdown param = %q, want duplicate H1 stripped", got)
|
|
}
|
|
if !strings.Contains(errOut, "已自动移除") {
|
|
t.Errorf("stderr = %q, want a note about the removed heading", errOut)
|
|
}
|
|
}
|
|
|
|
// TestDocCreateKeepsDistinctHeading ensures the guard never eats an H1 that
|
|
// differs from the document name.
|
|
func TestDocCreateKeepsDistinctHeading(t *testing.T) {
|
|
runner := &docCommandRunner{}
|
|
root := newDocTestRoot(runner)
|
|
|
|
_, _, err := executeDocCommand(t, root,
|
|
"create", "--name", "晚会简报", "--content", "# 背景\n正文")
|
|
if err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
got, _ := runner.last.Params["markdown"].(string)
|
|
if got != "# 背景\n正文" {
|
|
t.Errorf("markdown param = %q, want content untouched", got)
|
|
}
|
|
}
|
|
|
|
// TestDocCreateTitleOnlyContentOmitsMarkdown: when the body is nothing but
|
|
// the duplicate H1, the markdown param should be omitted entirely instead of
|
|
// sending an empty string.
|
|
func TestDocCreateTitleOnlyContentOmitsMarkdown(t *testing.T) {
|
|
runner := &docCommandRunner{}
|
|
root := newDocTestRoot(runner)
|
|
|
|
_, _, err := executeDocCommand(t, root,
|
|
"create", "--name", "晚会简报", "--content", "# 晚会简报")
|
|
if err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
if _, ok := runner.last.Params["markdown"]; ok {
|
|
t.Errorf("markdown param = %v, want omitted", runner.last.Params["markdown"])
|
|
}
|
|
}
|
|
|
|
// TestDocCreateStripsDuplicateTitleJSONML verifies the end-to-end JSONML path:
|
|
// `doc create --content-format jsonml` must drop a leading h1 whose text equals
|
|
// --name before forwarding the body to update_document, otherwise the rich
|
|
// document shows the page title twice.
|
|
func TestDocCreateStripsDuplicateTitleJSONML(t *testing.T) {
|
|
runner := &docCommandRunner{responses: []map[string]any{{"nodeId": "NODE_X"}}}
|
|
root := newDocTestRoot(runner)
|
|
|
|
body := `{"jsonml":[["h1",{},"命令树参考"],["p",{},"正文"]]}`
|
|
_, errOut, err := executeDocCommand(t, root,
|
|
"create", "--name", "命令树参考",
|
|
"--content-format", "jsonml", "--content", body)
|
|
if err != nil {
|
|
t.Fatalf("execute: %v\nstderr:\n%s", err, errOut)
|
|
}
|
|
if len(runner.all) != 2 {
|
|
t.Fatalf("calls = %d, want 2 (create + update)", len(runner.all))
|
|
}
|
|
if runner.all[1].Tool != "update_document" {
|
|
t.Fatalf("second tool = %q, want update_document", runner.all[1].Tool)
|
|
}
|
|
got, _ := runner.all[1].Params["jsonml"].(string)
|
|
if strings.Contains(got, "命令树参考") {
|
|
t.Errorf("jsonml = %q, want duplicate h1 stripped", got)
|
|
}
|
|
if !strings.Contains(got, "正文") {
|
|
t.Errorf("jsonml = %q, want body content kept", got)
|
|
}
|
|
if !strings.Contains(errOut, "已自动移除") {
|
|
t.Errorf("stderr = %q, want a note about the removed heading", errOut)
|
|
}
|
|
}
|
|
|
|
// TestStripLeadingDuplicateTitleJSONML covers the JSONML-path counterpart: a
|
|
// leading h1 node whose text equals the document name is removed; everything
|
|
// else is left untouched.
|
|
func TestStripLeadingDuplicateTitleJSONML(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
body string
|
|
docName string
|
|
want string
|
|
stripped bool
|
|
}{
|
|
{
|
|
name: "root-wrapped duplicate h1 stripped",
|
|
body: `["root",{},["h1",{},"晚会简报"],["p",{},"正文"]]`,
|
|
docName: "晚会简报",
|
|
want: `["root",{},["p",{},"正文"]]`,
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "nested leaf text matches and stripped",
|
|
body: `["root",{},["h1",{"uuid":"x"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"晚会简报"]]],["p",{},"正文"]]`,
|
|
docName: "晚会简报",
|
|
want: `["root",{},["p",{},"正文"]]`,
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "bare body without root wrapper",
|
|
body: `[["h1",{},"标题"],["p",{},"正文"]]`,
|
|
docName: "标题",
|
|
want: `[["p",{},"正文"]]`,
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "case insensitive match",
|
|
body: `["root",{},["h1",{},"Weekly REPORT"],["p",{},"x"]]`,
|
|
docName: "weekly report",
|
|
want: `["root",{},["p",{},"x"]]`,
|
|
stripped: true,
|
|
},
|
|
{
|
|
name: "distinct heading kept",
|
|
body: `["root",{},["h1",{},"背景"],["p",{},"正文"]]`,
|
|
docName: "晚会简报",
|
|
want: `["root",{},["h1",{},"背景"],["p",{},"正文"]]`,
|
|
stripped: false,
|
|
},
|
|
{
|
|
name: "non-h1 leading node kept",
|
|
body: `["root",{},["h2",{},"晚会简报"],["p",{},"正文"]]`,
|
|
docName: "晚会简报",
|
|
want: `["root",{},["h2",{},"晚会简报"],["p",{},"正文"]]`,
|
|
stripped: false,
|
|
},
|
|
{
|
|
name: "invalid json untouched",
|
|
body: `not json`,
|
|
docName: "x",
|
|
want: `not json`,
|
|
stripped: false,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, ok := stripLeadingDuplicateTitleJSONML(tc.body, tc.docName)
|
|
if ok != tc.stripped {
|
|
t.Fatalf("stripped = %v, want %v", ok, tc.stripped)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("body = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|