108 lines
5.9 KiB
Go
108 lines
5.9 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const canvasFixture = `{"schema_version":1,"nodes":[{"id":"a","type":"material","position":{"x":100,"y":200},"width":320,"height":220,"selected":true,"data":{"kind":"text","title":"灵感笔记","text":"正文","rotation":30,"fontSize":24,"textStyle":{"color":"#37352f","background":"transparent","bold":true,"italic":false,"underline":false,"align":"left","fontFamily":"sans","lineHeight":1.5}}},{"id":"b","type":"material","position":{"x":0,"y":0},"data":{"kind":"script","title":"Script","text":""}},{"id":"icon","type":"icon","position":{"x":500,"y":200},"width":80,"height":80,"data":{"kind":"icon","title":"火箭","text":"","icon":"Rocket","rotation":0}}],"edges":[{"id":"e","source":"a","target":"b","sourceHandle":"top","targetHandle":"bottom"}],"viewport":{"x":0,"y":0,"zoom":1}}`
|
|
|
|
func TestCanvasValidation(t *testing.T) {
|
|
out, ok := normalizeCanvas(json.RawMessage(canvasFixture))
|
|
if !ok || strings.Contains(string(out), "selected") {
|
|
t.Fatal("valid snapshot rejected or UI state retained")
|
|
}
|
|
cases := [][2]string{{`"rotation":30`, `"rotation":360`}, {`"width":320`, `"width":19`}, {`"color":"#37352f"`, `"color":"red"`}, {`"bold":true`, `"bold":"true"`}, {`"target":"b"`, `"target":"missing"`}, {`"target":"b"`, `"target":"icon"`}, {`"zoom":1`, `"zoom":0`}, {`"sourceHandle":"top"`, `"sourceHandle":"unknown"`}, {`"fontSize":24`, `"fontSize":161`}, {`"lineHeight":1.5`, `"lineHeight":4`}, {`"id":"b"`, `"id":"a"`}, {`"schema_version":1`, `"schema_version":3`}}
|
|
for _, c := range cases {
|
|
t.Run(c[1], func(t *testing.T) {
|
|
if _, ok := normalizeCanvas(json.RawMessage(strings.Replace(canvasFixture, c[0], c[1], 1))); ok {
|
|
t.Fatal("accepted invalid snapshot")
|
|
}
|
|
})
|
|
}
|
|
for _, bad := range []string{`null`, `{}`, `{"schema_version":1,"nodes":null,"edges":[],"viewport":{"x":0,"y":0,"zoom":1}}`} {
|
|
if _, ok := normalizeCanvas(json.RawMessage(bad)); ok {
|
|
t.Fatal("accepted missing document fields")
|
|
}
|
|
}
|
|
}
|
|
func TestSlugValidation(t *testing.T) {
|
|
for _, v := range []string{"my-board", "workspace-2026"} {
|
|
if !validSlug(&v) {
|
|
t.Fatal(v)
|
|
}
|
|
}
|
|
for _, v := range []string{"", "Bad Slug", "../board", strings.Repeat("a", 101)} {
|
|
if validSlug(&v) {
|
|
t.Fatal(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Frame nodes follow the frontend contract: membership is data, not containment.
|
|
const frameFixture = `{"schema_version":1,"nodes":[{"id":"text","type":"material","position":{"x":100,"y":100},"data":{"kind":"text","title":"成员","text":"正文","frameId":"frame"}},{"id":"plain","type":"plainText","position":{"x":0,"y":0},"data":{"kind":"plain-text","title":"纯文本","text":"文字","frameId":"frame"}},{"id":"icon","type":"icon","position":{"x":10,"y":10},"data":{"kind":"icon","title":"图标","text":"","icon":"Star","frameId":"frame"}},{"id":"frame","type":"frame","position":{"x":0,"y":0},"width":600,"height":400,"zIndex":-1,"style":{"width":600,"height":400,"pointerEvents":"none"},"data":{"kind":"frame","title":"框选区","text":"","rotation":0,"labelMode":"fixed"}},{"id":"outside","type":"material","position":{"x":100,"y":100},"data":{"kind":"script","title":"重叠但未加入","text":""}}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}}`
|
|
|
|
func TestCanvasFrames(t *testing.T) {
|
|
for _, mode := range []string{"hidden", "fixed", "scaled"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
out, ok := normalizeCanvas(json.RawMessage(strings.Replace(frameFixture, `"labelMode":"fixed"`, `"labelMode":"`+mode+`"`, 1)))
|
|
if !ok {
|
|
t.Fatal("valid frame rejected")
|
|
}
|
|
var doc map[string]any
|
|
json.Unmarshal(out, &doc)
|
|
nodes := doc["nodes"].([]any)
|
|
if nodes[0].(map[string]any)["data"].(map[string]any)["frameId"] != "frame" || nodes[3].(map[string]any)["data"].(map[string]any)["labelMode"] != mode {
|
|
t.Fatal("frame data lost")
|
|
}
|
|
if _, exists := nodes[4].(map[string]any)["data"].(map[string]any)["frameId"]; exists {
|
|
t.Fatal("position inferred membership")
|
|
}
|
|
if _, exists := nodes[3].(map[string]any)["zIndex"]; exists {
|
|
t.Fatal("rendering state persisted")
|
|
}
|
|
})
|
|
}
|
|
cases := [][2]string{
|
|
{`"labelMode":"fixed"`, `"labelMode":"invalid"`},
|
|
{`"labelMode":"fixed"`, `"labelMode":null`},
|
|
{`"rotation":0`, `"rotation":1`},
|
|
{`"frameId":"frame"`, `"frameId":"missing"`},
|
|
{`"frameId":"frame"`, `"frameId":"text"`},
|
|
{`"frameId":"frame"`, `"frameId":null`},
|
|
{`"frameId":"frame"`, `"frameId":""`},
|
|
{`"labelMode":"fixed"`, `"labelMode":"fixed","frameId":"frame"`},
|
|
{`"type":"frame"`, `"type":"material"`},
|
|
{`"edges":[]`, `"edges":[{"id":"e","source":"text","target":"frame"}]`},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c[1], func(t *testing.T) {
|
|
if _, ok := normalizeCanvas(json.RawMessage(strings.Replace(frameFixture, c[0], c[1], 1))); ok {
|
|
t.Fatal("invalid frame accepted")
|
|
}
|
|
})
|
|
}
|
|
withoutMode := strings.Replace(frameFixture, `,"labelMode":"fixed"`, "", 1)
|
|
if _, ok := normalizeCanvas(json.RawMessage(withoutMode)); !ok {
|
|
t.Fatal("optional labelMode rejected")
|
|
}
|
|
}
|
|
|
|
func TestCanvasPlainImages(t *testing.T) {
|
|
fixture := `{"schema_version":1,"nodes":[{"id":"photo","type":"plainImage","position":{"x":20,"y":30},"width":320,"height":240,"data":{"kind":"plain-image","title":"Photo","text":"","src":"https://example.com/photo.png","rotation":30,"frameId":"frame"}},{"id":"frame","type":"frame","position":{"x":0,"y":0},"width":640,"height":400,"data":{"kind":"frame","title":"Frame","text":"","labelMode":"fixed"}}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}}`
|
|
if _, ok := normalizeCanvas(json.RawMessage(fixture)); !ok {
|
|
t.Fatal("plain image rejected")
|
|
}
|
|
for _, change := range [][2]string{
|
|
{`"type":"plainImage"`, `"type":"material"`},
|
|
{`,"src":"https://example.com/photo.png"`, ``},
|
|
{`"src":"https://example.com/photo.png"`, `"src":"javascript:bad"`},
|
|
{`"edges":[]`, `"edges":[{"id":"e","source":"photo","target":"photo"}]`},
|
|
} {
|
|
if _, ok := normalizeCanvas(json.RawMessage(strings.Replace(fixture, change[0], change[1], 1))); ok {
|
|
t.Fatal("invalid pure image accepted")
|
|
}
|
|
}
|
|
}
|