ref: 069daee6347a509dab3c109291cfd35956229b91
dir: /browser/browser_test.go/
package browser import ( "9fans.net/go/draw" "fmt" "github.com/mjl-/duit" "github.com/psilva261/opossum/browser/duitx" "github.com/psilva261/opossum/logger" "github.com/psilva261/opossum/nodes" "github.com/psilva261/opossum/style" "golang.org/x/net/html" "image" "net/http" "net/url" "strings" "testing" ) var ( _ duitx.Boxable = &Element{} ) func init() { debugPrintHtml = false log.Debug = true style.Init(nil) } type item struct { orig string href string expect string } func TestElementClick(t *testing.T) { el := Element{} for _, b := range []bool{true, false} { el.Click = func() (e duit.Event) { e.Consumed = b return } if el.click() != b { t.Fail() } } } func TestArrange(t *testing.T) { htm := ` <div> <h1>title 1</h1> <h2>title 2</h2> <h3>title 3</h3> </div> ` for _, d := range []string{"inline", "block"} { doc, err := html.ParseWithOptions( strings.NewReader(string(htm)), html.ParseOptionEnableScripting(false), ) if err != nil { t.Fatalf(err.Error()) } nodeMap := make(map[*html.Node]style.Map) nt := nodes.NewNodeTree(doc, style.Map{}, nodeMap, nil) h1 := nt.Find("h1") h2 := nt.Find("h2") h3 := nt.Find("h3") m := style.Map{ Declarations: make(map[string]style.Declaration), } m.Declarations["display"] = style.Declaration{ Prop: "display", Val: d, } h1.Map = m h2.Map = m h3.Map = m es := []*Element{ &Element{n: h1}, &Element{n: h2}, &Element{n: h3}, } v := Arrange(nt, es...) for _, e := range es { if e.n.IsInline() != (d == "inline") { t.Fatalf("%+v", e) } } PrintTree(v) b := v.UI.(*duitx.Box) if len(b.Kids) != 3 { t.Fatalf("%v %+v", len(b.Kids), b) } for _, k := range b.Kids { disp := k.UI.(duitx.Boxable).Display() if d == "inline" { if disp != duitx.Inline { t.Fail() } } else { if disp != duitx.Block { t.Fail() } } } } } func TestLinkedUrl(t *testing.T) { items := []item{ item{ orig: "https://news.ycombinator.com/item?id=24777268", href: "news", expect: "https://news.ycombinator.com/news", }, item{ orig: "https://golang.org/pkg/", href: "net/http", expect: "https://golang.org/pkg/net/http", }, item{ orig: "https://example.com/", href: "info", expect: "https://example.com/info", }, item{ orig: "https://example.com", href: "/path/info", expect: "https://example.com/path/info", }, } for _, i := range items { b := Browser{} origin, err := url.Parse(i.orig) if err != nil { panic(err.Error()) } b.History.Push(origin, 0) res, err := b.LinkedUrl(i.href) if err != nil { panic(err.Error()) } if res.String() != i.expect { t.Fatalf("got %v but expected %v", res, i.expect) } t.Logf("res=%v, i.expect=%v", res, i.expect) } } func TestNodeToBoxNoscript(t *testing.T) { enable := true EnableNoScriptTag = enable htm := ` <body> <noscript> <a href="https://example.com">Link</a> </noscript> <a>Other</a> <input value=123> </body> ` doc, err := html.ParseWithOptions( strings.NewReader(string(htm)), html.ParseOptionEnableScripting(false), ) if err != nil { t.Fatalf(err.Error()) } nodeMap := make(map[*html.Node]style.Map) body := grep(doc, "body") b := &Browser{} b.client = &http.Client{} browser = b u, err := url.Parse("https://example.com") if err != nil { log.Fatalf("parse: %v", err) } b.History.Push(u, 0) nt := nodes.NewNodeTree(body, style.Map{}, nodeMap, nil) boxed := NodeToBox(0, b, nt) numInputs := 0 TraverseTree(boxed, func(ui duit.UI) { if _, ok := ui.(*duit.Field); ok { numInputs++ } }) if numInputs != 1 { t.Fail() } } func digestHtm(htm string) (nt *nodes.Node, boxed *Element, err error) { doc, err := html.ParseWithOptions( strings.NewReader(string(htm)), html.ParseOptionEnableScripting(false), ) if err != nil { return nil, nil, fmt.Errorf("parse html: %w", err) } body := grep(doc, "body") b := &Browser{} b.client = &http.Client{} browser = b u, err := url.Parse("https://example.com") if err != nil { return nil, nil, fmt.Errorf("parse url: %w", err) } b.History.Push(u, 0) nm, err := style.FetchNodeMap(doc, style.AddOnCSS, 1280) if err != nil { return nil, nil, fmt.Errorf("FetchNodeMap: %w", err) } nt = nodes.NewNodeTree(body, style.Map{}, nm, nil) boxed = NodeToBox(0, b, nt) return } func explodeRow(e *Element) (cols []*duit.Kid, ok bool) { for { el, ok := e.UI.(*Element) if ok { e = el } else { break } } el := e.UI.(*duitx.Box) return el.Kids, true } func TestInlining(t *testing.T) { htm := ` <body> <span id="outer">(<a href="http://example.com"><span>example.com</span></a></span> </body> ` nt, boxed, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } // 1. nodes are row-like outerSpan := nt.Find("span") if outerSpan.Attr("id") != "outer" || len(outerSpan.Children) != 2 || outerSpan.IsFlex() { t.Fatalf(" node") } bracket := outerSpan.Children[0] if bracket.Data() != "(" || !bracket.IsInline() { t.Errorf("bracket, is inline: %v", bracket.IsInline()) } a := outerSpan.Children[1] if a.Data() != "a" || !a.IsInline() { t.Errorf("a, is inline: %v, %+v %+v", a.IsInline(), a, nil) } // 2. Elements are row-like kids, ok := explodeRow(boxed) if !ok || len(kids) != 1 { t.Errorf("boxed: %+v", boxed) } kids, ok = explodeRow(kids[0].UI.(*Element)) if !ok || len(kids) != 2 { t.Errorf("boxed: %+v", boxed) } bel := kids[0].UI.(*Element) ael := kids[1].UI.(*Element) if bel.n.Data() != "(" { t.Errorf("bel: %+v", bel) } if ael.n.Data() != "a" { ael.n.PrintTree() t.Errorf("ael: %+v %+v '%v'", ael, ael.n, ael.n.Data()) } if !ael.IsLink || ael.Click == nil { t.Errorf("ael: %+v %+v '%v'", ael, ael.n, ael.n.Data()) } } func TestInlining2(t *testing.T) { htm := ` <body> <span id="outer"> <span id="sp1">[</span> <a href="http://example.com">edit</a> <span id="sp2">]</span> </span> </body> ` nt, boxed, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } // 1. nodes are row-like outerSpan := nt.Find("span") if outerSpan.Attr("id") != "outer" || len(outerSpan.Children) != 3 || outerSpan.IsFlex() { t.Errorf("node: %+v", outerSpan) } bracket := outerSpan.Children[0] if bracket.Data() != "span" || !bracket.IsInline() { t.Errorf("bracket, is inline: %v %+v %+v", bracket.IsInline(), bracket, bracket.Data()) } a := outerSpan.Children[1] if a.Data() != "a" || !a.IsInline() { t.Errorf("sp1, is inline: %v, %+v %+v", a.IsInline(), a, a.Data()) } // 2. Elements are row-like kids, ok := explodeRow(boxed) if !ok || len(kids) != 1 { t.Errorf("boxed: %+v, kids: %+v", boxed, kids) } kids, ok = explodeRow(kids[0].UI.(*Element)) if !ok || len(kids) != 3 { t.Errorf("boxed: %+v, kids: %+v", boxed, kids) } sel := kids[0].UI.(*Element) ael := kids[1].UI.(*Element) if sel.n.Data() != "span" { t.Errorf("sel: %+v", sel) } if ael.n.Data() != "a" { ael.n.PrintTree() t.Errorf("ael: %+v %+v", ael, ael.n) } if !ael.IsLink || ael.Click == nil { t.Errorf("ael: %+v %+v", ael, ael.n) } } func TestInlining3(t *testing.T) { htm := ` <body> <p> <span> <tt>bind -ac /dist/plan9front /</tt> </span> </p> <p> <span> <tt>git/pull -u gits://git.9front.org/plan9front/plan9front</tt> </span> </p> </body> ` nt, boxed, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } // 1. nodes are 2 rows ps := nt.FindAll("p") if len(ps) != 2 || ps[0].IsInline() || ps[1].IsInline() { t.Fail() } // 1a. nodes' children are inline for i := 0; i < 2; i++ { p := ps[i] span := p.Find("span") tt := span.Find("tt") if !span.IsInline() || !tt.IsInline() { t.Fail() } } PrintTree(boxed) // 2. Elements are 2 rows kids, ok := explodeRow(boxed) if !ok || len(kids) != 2 { t.Errorf("boxed: %+v, kids: %+v", boxed, kids) } for _, k := range kids { if k.UI.(duitx.Boxable).Display() != duitx.Block { t.Fail() } } } func TestSpansCanBeWrapped(t *testing.T) { htm := ` <body> <span> A text with multiple words. </span> </body> ` _, boxed, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } n := 0 TraverseTree(boxed, func(ui duit.UI) { if el, ok := ui.(*Label); ok { n++ fmt.Printf("n data=%v\n", el.n.Data()) fmt.Printf("n cls=%v\n", el.n.Attr("class")) } }) if n != 5 { t.Errorf("%v", n) } } func TestAlwaysOneElement(t *testing.T) { h := ` <!DOCTYPE html> <html> <body> <div class="wrapper"> <main>main content</main> <footer>footer</footer> </div> </body> </html> ` _, boxed, err := digestHtm(h) if err != nil { t.Fatalf("digest: %v", err) } n := 0 TraverseTree(boxed, func(ui duit.UI) { if el, ok := ui.(*Element); ok && el.n.Attr("class") == "wrapper" { n++ fmt.Printf("n data=%v\n", el.n.Data()) fmt.Printf("n cls=%v\n", el.n.Attr("class")) } }) if n != 1 { t.Errorf("%v", n) } } func TestTextArea(t *testing.T) { htm := ` <textarea height="100"> </textarea> ` nt, _, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } ta := nt.Find("textarea") el := NewTextArea(ta) // Trigger key press to trigger call to changed el.Key(nil, nil, 'a', draw.Mouse{}, image.Point{}) } func TestNewPicture(t *testing.T) { htm := ` <picture itemprop="contentUrl"> <source srcset="https://example.com/2040 2040w,https://example.com/1880 1880w,https://example.com/1400 1400w" media="(-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi)"> <source srcset="https://example.com/1020 1020w,https://example.com/940 940w,https://example.com/700 700w"> <img src="https://example.com/465" height="5000" width="7000" loading="lazy"> </picture> ` nt, _, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } p := nt.Find("picture") src := newPicture(p) if src != "https://example.com/700" { t.Error() } } func TestSrcSet(t *testing.T) { htm := ` <img width="800" height="429" style="width: 600px" src="/t.jpg" srcset="/t.jpg 800w, /t-300x165.jpg 300w, /t-768x421.jpg 768w, /t-561x308.jpg 561w, /t-364x200.jpg 364w, /t-728x399.jpg 728w, /t-608x334.jpg 608w, /t-758x416.jpg 758w, /t-87x48.jpg 87w, /t-175x96.jpg 175w, /t-313x172.jpg 313w" sizes="(max-width: 800px) 100vw, 800px"> ` nt, _, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } img := nt.Find("img") w, src := srcSet(img) t.Logf("%v, %v, %v", nt.Data(), w, src) if w != 608 || src != "/t-608x334.jpg" { t.Error() } } func TestWidths(t *testing.T) { htm := ` <html> <body style="width: 100%"> <h1> Info </h1> <main style="width: 50%"> <nav style="width: 33%"> </nav> <article id="lo"> <h2> General information </h2> <p style="width: 90%"> Supplementary information </p> </article> </main> </body> </html> ` nt, _, err := digestHtm(htm) if err != nil { t.Fatalf("digest: %v", err) } if nt.Data() != "body" || nt.Width() != 1280 { t.Fail() } if main := nt.Find("main"); main.Width() != 640 { t.Fail() } if p := nt.Find("p"); p.Width() != 576 { t.Fail() } }