Initial commit: img-infinite-canvas AI design workbench MVP
Moteva-style AI design workbench replica. Home prompt creates a project; the project page provides an infinite canvas with node drag, zoom/pan, a design chat panel, history replay, image asset upload, and project save/regenerate. - Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage; sky-valley/pi agent runtime adapter. - Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n, shadcn/ui components, auth (OTP/Turnstile/Google/WeChat). - Deploy: Docker Compose and k3s manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package textextraction
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseTextExtractionAcceptsItems(t *testing.T) {
|
||||
extraction, err := parseTextExtraction(`{
|
||||
"items": [
|
||||
{
|
||||
"text": "新品上市",
|
||||
"x": 0.2,
|
||||
"y": 0.3,
|
||||
"width": 0.4,
|
||||
"height": 0.08,
|
||||
"fontSize": 38,
|
||||
"fontFamily": "Noto Sans SC",
|
||||
"fontWeight": "700",
|
||||
"color": "#111111",
|
||||
"textAlign": "center",
|
||||
"lineHeight": 1.05,
|
||||
"letterSpacing": 2,
|
||||
"strokeColor": "#ffffff",
|
||||
"strokeWidth": 1.5,
|
||||
"opacity": 0.8,
|
||||
"confidence": 0.95
|
||||
}
|
||||
]
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(extraction.Layers) != 1 {
|
||||
t.Fatalf("expected one layer, got %d", len(extraction.Layers))
|
||||
}
|
||||
layer := extraction.Layers[0]
|
||||
if layer.Text != "新品上市" || layer.X != 0.2 || layer.Y != 0.3 || layer.Width != 0.4 || layer.Height != 0.08 {
|
||||
t.Fatalf("unexpected layer: %#v", layer)
|
||||
}
|
||||
if layer.Color != "#111111" || layer.TextAlign != "center" {
|
||||
t.Fatalf("unexpected style: %#v", layer)
|
||||
}
|
||||
if layer.LineHeight != 1.05 || layer.LetterSpacing != 2 || layer.StrokeColor != "#ffffff" || layer.StrokeWidth != 1.5 || layer.Opacity != 0.8 {
|
||||
t.Fatalf("unexpected rendering style: %#v", layer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTextExtractionSkipsEmptyText(t *testing.T) {
|
||||
extraction, err := parseTextExtraction(`{"items":[{"text":"","x":0,"y":0,"width":0.2,"height":0.1}]}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(extraction.Layers) != 0 {
|
||||
t.Fatalf("expected no layers, got %d", len(extraction.Layers))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTextExtractionSkipsDuplicateLayers(t *testing.T) {
|
||||
extraction, err := parseTextExtraction(`{
|
||||
"items": [
|
||||
{"text":"SALE","x":0.1,"y":0.2,"width":0.3,"height":0.1},
|
||||
{"text":"SALE","x":0.105,"y":0.205,"width":0.29,"height":0.095},
|
||||
{"text":"NEW","x":0.1,"y":0.4,"width":0.2,"height":0.08}
|
||||
]
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(extraction.Layers) != 2 {
|
||||
t.Fatalf("expected duplicate layer to be skipped, got %d", len(extraction.Layers))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user