package cef import ( "context" "testing" "github.com/bnema/dumber/internal/application/port" purecef "github.com/bnema/purego-cef/cef" cefmocks "github.com/bnema/purego-cef/cef/mocks" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestFaviconDiscoveryResolvesLinksAndFallback(t *testing.T) { pageURL := "https://example.com/docs/page.html" html := `
` got := DiscoverFaviconCandidates(pageURL, html) require.Equal(t, []string{ "https://example.com/favicon.png", "https://example.com/docs/icons/shortcut.ico", "https://cdn.example.com/apple.png", "https://example.com/favicon.ico", }, got) } func TestFaviconDiscoveryFallbackOnly(t *testing.T) { require.Equal(t, []string{"https://example.com/favicon.ico"}, DiscoverFaviconCandidates("https://example.com/path", "")) require.Nil(t, DiscoverFaviconCandidates("about:blank", "")) } func TestFaviconDiscoveryKeepsAbsoluteIconsForOpaquePageURL(t *testing.T) { got := DiscoverFaviconCandidates("about:blank", ``) require.Equal(t, []string{"https://cdn.example.com/icon.png"}, got) } func TestFaviconDiscoveryIgnoresUnsafeIconURLs(t *testing.T) { got := DiscoverFaviconCandidates("https://example.com/path", ``) require.Equal(t, []string{"https://cdn.example.com/icon.png", "https://example.com/favicon.ico"}, got) } func TestFaviconDiscoveryIgnoresWhitespaceOnlyHrefs(t *testing.T) { got := DiscoverFaviconCandidates("https://example.com/path", ``) require.Equal(t, []string{"https://example.com/favicon.ico"}, got) } func TestCEFOnFaviconUrlchangeForwardsOrderedCandidates(t *testing.T) { prevDecode := decodeCEFStringList decodeCEFStringList = func(list purecef.StringList) []string { require.Equal(t, purecef.StringList(42), list) return []string{"/favicon.png", "https://cdn.example.com/icon.ico", "javascript:alert(1)"} } defer func() { decodeCEFStringList = prevDecode }() var page string var icons []string browser := cefmocks.NewMockBrowser(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() wv := &WebView{ctx: context.Background(), uri: "https://example.com/docs/page", browser: browser, documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(p string, urls []string) { page, icons = p, urls }, }} (&handlerSet{wv: wv}).OnFaviconUrlchange(browser, purecef.StringList(42)) require.Equal(t, "https://example.com/docs/page", page) require.Equal(t, []string{"https://example.com/favicon.png", "https://cdn.example.com/icon.ico"}, icons) } func TestCEFOnFaviconUrlchangeResolvesInternalCandidatesAgainstCommittedURL(t *testing.T) { prevDecode := decodeCEFStringList decodeCEFStringList = func(purecef.StringList) []string { return []string{"/favicon.png"} } defer func() { decodeCEFStringList = prevDecode }() var page string var icons []string browser := cefmocks.NewMockBrowser(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() wv := &WebView{ctx: context.Background(), browser: browser, documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(p string, urls []string) { page, icons = p, urls }, }} wv.setCommittedURLsLocked("https://dumber.invalid/history", "dumb://history") (&handlerSet{wv: wv}).OnFaviconUrlchange(browser, purecef.StringList(1)) require.Equal(t, "dumb://history", page) require.Equal(t, []string{"https://dumber.invalid/favicon.png"}, icons) } func TestCEFLoadingFinishedDiscoversMetadataBeforeFallback(t *testing.T) { prevNewStringVisitor := cefNewStringVisitor cefNewStringVisitor = func(visitor purecef.StringVisitor) purecef.StringVisitor { return visitor } defer func() { cefNewStringVisitor = prevNewStringVisitor }() browser := cefmocks.NewMockBrowser(t) frame := cefmocks.NewMockFrame(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() browser.EXPECT().GetMainFrame().Return(frame) frame.EXPECT().GetSource(mock.Anything).Run(func(visitor purecef.StringVisitor) { visitor.Visit(``) }).Return() var page string var icons []string wv := &WebView{ctx: context.Background(), uri: "https://example.com/docs/page", browser: browser, documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(p string, urls []string) { page, icons = p, urls }, }} (&handlerSet{wv: wv}).OnLoadingStateChange(browser, 0, 1, 0) require.Equal(t, "https://example.com/docs/page", page) require.Equal(t, []string{"https://example.com/meta.png", "https://example.com/favicon.ico"}, icons) } func TestCEFLoadingFinishedResolvesInternalMetadataAgainstCommittedURL(t *testing.T) { prevNewStringVisitor := cefNewStringVisitor cefNewStringVisitor = func(visitor purecef.StringVisitor) purecef.StringVisitor { return visitor } defer func() { cefNewStringVisitor = prevNewStringVisitor }() browser := cefmocks.NewMockBrowser(t) frame := cefmocks.NewMockFrame(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() browser.EXPECT().GetMainFrame().Return(frame) frame.EXPECT().GetSource(mock.Anything).Run(func(visitor purecef.StringVisitor) { visitor.Visit(``) }).Return() var page string var icons []string wv := &WebView{ctx: context.Background(), browser: browser, documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(p string, urls []string) { page, icons = p, urls }, }} wv.setCommittedURLsLocked("https://dumber.invalid/history", "dumb://history") (&handlerSet{wv: wv}).OnLoadingStateChange(browser, 0, 1, 0) require.Equal(t, "dumb://history", page) require.Equal(t, []string{"https://dumber.invalid/meta.png", "https://dumber.invalid/favicon.ico"}, icons) } func TestCEFLoadingFinishedForwardsFallbackCandidate(t *testing.T) { var page string var icons []string wv := &WebView{ctx: context.Background(), uri: "https://example.com/docs/page", documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(p string, urls []string) { page, icons = p, urls }, }} (&handlerSet{wv: wv}).OnLoadingStateChange(nil, 0, 1, 0) require.Equal(t, "https://example.com/docs/page", page) require.Equal(t, []string{"https://example.com/favicon.ico"}, icons) } func TestCEFLoadingFinishedSkipsSourceWhenEngineProvidedCandidates(t *testing.T) { prevDecode := decodeCEFStringList decodeCEFStringList = func(purecef.StringList) []string { return []string{"/engine.png"} } defer func() { decodeCEFStringList = prevDecode }() browser := cefmocks.NewMockBrowser(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() calls := 0 wv := &WebView{ctx: context.Background(), uri: "https://example.com/page", browser: browser, documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(string, []string) { calls++ }, }} h := &handlerSet{wv: wv} h.OnFaviconUrlchange(browser, purecef.StringList(1)) h.OnLoadingStateChange(browser, 0, 0, 0) require.Equal(t, 1, calls) } func TestCEFLoadingFinishedCoalescesPendingSourceDiscovery(t *testing.T) { prevNewStringVisitor := cefNewStringVisitor cefNewStringVisitor = func(visitor purecef.StringVisitor) purecef.StringVisitor { return visitor } defer func() { cefNewStringVisitor = prevNewStringVisitor }() browser := cefmocks.NewMockBrowser(t) frame := cefmocks.NewMockFrame(t) browser.EXPECT().GetMainFrame().Return(frame).Once() frame.EXPECT().GetSource(mock.Anything).Return().Once() wv := &WebView{ctx: context.Background(), uri: "https://example.com/page", documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(string, []string) {}, }} h := &handlerSet{wv: wv} h.OnLoadingStateChange(browser, 0, 0, 0) h.OnLoadingStateChange(browser, 0, 0, 0) } func TestCEFLoadingFinishedDoesNotRepeatCompletedSourceDiscovery(t *testing.T) { prevNewStringVisitor := cefNewStringVisitor var visitor purecef.StringVisitor cefNewStringVisitor = func(v purecef.StringVisitor) purecef.StringVisitor { visitor = v return v } defer func() { cefNewStringVisitor = prevNewStringVisitor }() browser := cefmocks.NewMockBrowser(t) frame := cefmocks.NewMockFrame(t) browser.EXPECT().GetIdentifier().Return(int32(7)).Maybe() browser.EXPECT().GetMainFrame().Return(frame).Once() frame.EXPECT().GetSource(mock.Anything).Return().Once() wv := &WebView{ctx: context.Background(), uri: "https://example.com/page", documentSeq: 1, callbacks: &port.WebViewCallbacks{ OnFaviconURLChanged: func(string, []string) {}, }} h := &handlerSet{wv: wv} h.OnLoadingStateChange(browser, 0, 0, 0) require.NotNil(t, visitor, "loading-finished must start source discovery") visitor.Visit(``) // The same document finishing again must not repeat the full-document // transfer; the GetMainFrame and GetSource expectations are Once. h.OnLoadingStateChange(browser, 0, 0, 0) }