@@ -6,6 +6,7 @@ import * as Common from '../../../core/common/common.js';
66import * as Host from '../../../core/host/host.js' ;
77import type * as Platform from '../../../core/platform/platform.js' ;
88import {
9+ assertScreenshot ,
910 dispatchClickEvent ,
1011 getCleanTextContentFromElements ,
1112 renderElementIntoDOM ,
@@ -16,6 +17,11 @@ import * as Explain from '../explain.js';
1617describeWithEnvironment ( 'ConsoleInsight' , ( ) => {
1718 let component : Explain . ConsoleInsight | undefined ;
1819
20+ const containerCss = `
21+ box-sizing: border-box;
22+ background-color: aqua;
23+ ` ;
24+
1925 afterEach ( ( ) => {
2026 component ?. remove ( ) ;
2127 Common . Settings . settingForTest ( 'console-insights-enabled' ) . set ( true ) ;
@@ -580,4 +586,352 @@ after
580586 assert . strictEqual ( xLinks [ 3 ] . textContent ?. trim ( ) , 'https://www.factuality.test/' ) ;
581587 assert . strictEqual ( xLinks [ 3 ] . getAttribute ( 'href' ) , 'https://www.factuality.test/' ) ;
582588 } ) ;
589+
590+ function animatedPromise ( component : Explain . ConsoleInsight ) : Promise < unknown > {
591+ component . disableAnimations = true ;
592+
593+ // Unfortunately, disabling animations is not enough, as some animations are
594+ // not controlled by that flag.
595+ const animated = new Promise ( resolve => {
596+ component . shadowRoot ! . querySelector ( '.wrapper' ) ! . addEventListener ( 'animationend' , resolve , {
597+ once : true ,
598+ } ) ;
599+ } ) ;
600+
601+ return animated ;
602+ }
603+
604+ it ( 'renders the opt-in teaser' , async ( ) => {
605+ Common . Settings . settingForTest ( 'console-insights-enabled' ) . set ( false ) ;
606+
607+ const component = new Explain . ConsoleInsight (
608+ getTestPromptBuilder ( ) , getTestAidaClient ( ) , Host . AidaClient . AidaAccessPreconditions . AVAILABLE ) ;
609+
610+ const container = document . createElement ( 'div' ) ;
611+ container . style . cssText = containerCss ;
612+ component . style . width = '574px' ;
613+ component . style . height = '64px' ;
614+ container . appendChild ( component ) ;
615+ const animated = animatedPromise ( component ) ;
616+ renderElementIntoDOM ( container ) ;
617+ await animated ;
618+
619+ await drainMicroTasks ( ) ;
620+ await assertScreenshot ( 'explain/console_insight_optin.png' ) ;
621+ } ) ;
622+
623+ it ( 'renders the consent reminder' , async ( ) => {
624+ function getPromptBuilderForConsentReminder ( ) {
625+ return {
626+ getSearchQuery ( ) {
627+ return '' ;
628+ } ,
629+ async buildPrompt ( ) {
630+ return {
631+ prompt : '' ,
632+ isPageReloadRecommended : false ,
633+ sources : [
634+ {
635+ type : Explain . SourceType . MESSAGE ,
636+ value : 'Something went wrong\n\nSomething went wrong' ,
637+ } ,
638+ {
639+ type : Explain . SourceType . NETWORK_REQUEST ,
640+ value : `Request: https://example.com/data.html
641+
642+ Request headers:
643+ :authority: example.com
644+ :method: GET
645+ :path: https://example.com/data.json
646+ :scheme: https
647+ accept: */*
648+ accept-encoding: gzip, deflate, br
649+ accept-language: en-DE,en;q=0.9,de-DE;q=0.8,de;q=0.7,en-US;q=0.6
650+ referer: https://example.com/demo.html
651+ sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"
652+ sec-ch-ua-arch: "arm"
653+ sec-ch-ua-bitness: "64"
654+ sec-ch-ua-full-version: "121.0.6116.0"
655+ sec-ch-ua-full-version-list: "Not A(Brand";v="99.0.0.0", "Google Chrome";v="121.0.6116.0", "Chromium";v="121.0.6116.0"
656+ sec-ch-ua-mobile: ?0
657+ sec-ch-ua-model: ""
658+ sec-ch-ua-platform: "macOS"
659+ sec-ch-ua-platform-version: "14.1.0"
660+ sec-ch-ua-wow64: ?0
661+ sec-fetch-dest: empty
662+ sec-fetch-mode: cors
663+ sec-fetch-site: same-origin
664+ user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
665+
666+ Response headers:
667+ accept-ch: Sec-CH-UA, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-WoW64
668+ content-length: 1646
669+ content-type: text/html; charset=UTF-8
670+ cross-origin-opener-policy-report-only: same-origin; report-to="gfe-static-content-corp"
671+ date: Fri, 10 Nov 2023 13:46:47 GMT
672+ permissions-policy: ch-ua=*, ch-ua-arch=*, ch-ua-bitness=*, ch-ua-full-version=*, ch-ua-full-version-list=*, ch-ua-mobile=*, ch-ua-model=*, ch-ua-platform=*, ch-ua-platform-version=*, ch-ua-wow64=*
673+ server: sffe
674+ strict-transport-security: max-age=31536000; includeSubdomains
675+ vary: Origin
676+
677+ Response status: 404` ,
678+ } ,
679+ ]
680+ } ;
681+ }
682+ } ;
683+ }
684+
685+ function getAidaClientForConsentReminder ( ) {
686+ return {
687+ async *
688+ fetch ( ) {
689+ await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ;
690+ yield {
691+ explanation : `Some text with \`code\`. Some code:
692+ \`\`\`ts
693+ console.log('test');
694+ document.querySelector('test').style = 'black';
695+ \`\`\`
696+ Some text with \`code\`. Some code:
697+ \`\`\`ts
698+ console.log('test');
699+ document.querySelector('test').style = 'black';
700+ \`\`\`
701+ Some text with \`code\`. Some code:
702+ \`\`\`ts
703+ console.log('test');
704+ document.querySelector('test').style = 'black';
705+ \`\`\`
706+ ` ,
707+ metadata : { } ,
708+ completed : true ,
709+ } ;
710+ } ,
711+ registerClientEvent : ( ) => Promise . resolve ( { } ) ,
712+ } ;
713+ }
714+
715+ Common . Settings . Settings . instance ( ) . createLocalSetting ( 'console-insights-onboarding-finished' , false ) . set ( false ) ;
716+
717+ const component = new Explain . ConsoleInsight (
718+ getPromptBuilderForConsentReminder ( ) , getAidaClientForConsentReminder ( ) ,
719+ Host . AidaClient . AidaAccessPreconditions . AVAILABLE ) ;
720+
721+ const container = document . createElement ( 'div' ) ;
722+ container . style . cssText = containerCss ;
723+ component . style . width = '574px' ;
724+ component . style . height = '271px' ;
725+ container . appendChild ( component ) ;
726+ const animated = animatedPromise ( component ) ;
727+ renderElementIntoDOM ( container ) ;
728+ await animated ;
729+
730+ await drainMicroTasks ( ) ;
731+ await assertScreenshot ( 'explain/console_insight_reminder.png' ) ;
732+ } ) ;
733+
734+ it ( 'renders the insight' , async ( ) => {
735+ function getPromptBuilderForInsight ( ) {
736+ return {
737+ getSearchQuery ( ) {
738+ return '' ;
739+ } ,
740+ async buildPrompt ( ) {
741+ return {
742+ prompt : '' ,
743+ isPageReloadRecommended : false ,
744+ sources : [
745+ {
746+ type : Explain . SourceType . MESSAGE ,
747+ value : 'Something went wrong\n\nSomething went wrong' ,
748+ } ,
749+ {
750+ type : Explain . SourceType . STACKTRACE ,
751+ value : 'Stacktrace line1\nStacketrace line2' ,
752+ } ,
753+ {
754+ type : Explain . SourceType . RELATED_CODE ,
755+ value : 'RelatedCode' ,
756+ } ,
757+ {
758+ type : Explain . SourceType . NETWORK_REQUEST ,
759+ value : `Request: https://example.com/data.html
760+
761+ Request headers:
762+ :authority: example.com
763+ user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
764+
765+ Response headers:
766+ Response status: 404` ,
767+ } ,
768+ ] ,
769+ } ;
770+ } ,
771+ } ;
772+ }
773+
774+ function getAidaClientForInsight ( ) {
775+ return {
776+ async *
777+ fetch ( ) {
778+ yield {
779+ explanation : `## Result
780+
781+ Some text with \`code\`. Some code:
782+ \`\`\`ts
783+ console.log('test');
784+ document.querySelector('test').style = 'black';
785+ \`\`\`
786+
787+ \`\`\`
788+ <!DOCTYPE html>
789+ <div>Hello world</div>
790+ <script>
791+ console.log('Hello World');
792+ </script>
793+ \`\`\`
794+
795+ Links: [https://example.com](https://example.com)
796+ Images: 
797+ ` ,
798+ metadata : { } ,
799+ completed : true ,
800+ } ;
801+ } ,
802+ registerClientEvent : ( ) => Promise . resolve ( { } ) ,
803+ } ;
804+ }
805+
806+ const component = new Explain . ConsoleInsight (
807+ getPromptBuilderForInsight ( ) , getAidaClientForInsight ( ) , Host . AidaClient . AidaAccessPreconditions . AVAILABLE ) ;
808+
809+ const container = document . createElement ( 'div' ) ;
810+ container . style . cssText = containerCss ;
811+ component . style . width = '574px' ;
812+ component . style . height = '530px' ;
813+ container . appendChild ( component ) ;
814+ const animated = animatedPromise ( component ) ;
815+ renderElementIntoDOM ( container ) ;
816+ await animated ;
817+
818+ await drainMicroTasks ( ) ;
819+ await assertScreenshot ( 'explain/console_insight.png' ) ;
820+ } ) ;
821+
822+ it ( 'renders insights with references' , async ( ) => {
823+ function getPromptBuilderForInsight ( ) {
824+ return {
825+ getSearchQuery ( ) {
826+ return '' ;
827+ } ,
828+ async buildPrompt ( ) {
829+ return {
830+ prompt : '' ,
831+ isPageReloadRecommended : false ,
832+ sources : [
833+ {
834+ type : Explain . SourceType . MESSAGE ,
835+ value : 'Something went wrong\n\nSomething went wrong' ,
836+ } ,
837+ {
838+ type : Explain . SourceType . STACKTRACE ,
839+ value : 'Stacktrace line1\nStacketrace line2' ,
840+ } ,
841+ {
842+ type : Explain . SourceType . RELATED_CODE ,
843+ value : 'RelatedCode' ,
844+ } ,
845+ {
846+ type : Explain . SourceType . NETWORK_REQUEST ,
847+ value : `Request: https://example.com/data.html
848+
849+ Request headers:
850+ :authority: example.com
851+ user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
852+
853+ Response headers:
854+ Response status: 404` ,
855+ } ,
856+ ] ,
857+ } ;
858+ } ,
859+ } ;
860+ }
861+
862+ function getAidaClientForInsight ( ) {
863+ return {
864+ async *
865+ fetch ( ) {
866+ yield {
867+ explanation : `## Result
868+
869+ Here is a text which contains both direct and indirect citations.
870+
871+ An indirect citation is a link to a reference which applies to the whole response.
872+
873+ A direct citation is a link to a reference, but it only applies to a specific part of the response. Direct citations are numbered and are shown as a number within square brackets in the response text.
874+ ` ,
875+ metadata : {
876+ attributionMetadata : {
877+ attributionAction : Host . AidaClient . RecitationAction . CITE ,
878+ citations : [
879+ {
880+ startIndex : 20 ,
881+ endIndex : 50 ,
882+ uri : 'https://www.direct-citation.dev' ,
883+ sourceType : Host . AidaClient . CitationSourceType . WORLD_FACTS ,
884+ } ,
885+ {
886+ startIndex : 170 ,
887+ endIndex : 176 ,
888+ uri : 'https://www.another-direct-citation.dev' ,
889+ sourceType : Host . AidaClient . CitationSourceType . WORLD_FACTS ,
890+ } ,
891+ ] ,
892+ } ,
893+ factualityMetadata : {
894+ facts : [
895+ {
896+ sourceUri : 'https://www.indirect-citation.dev' ,
897+ } ,
898+ {
899+ sourceUri : 'https://www.the-whole-world.dev' ,
900+ } ,
901+ {
902+ sourceUri : 'https://www.even-more-content.dev' ,
903+ } ,
904+ ]
905+ }
906+ } ,
907+ completed : true ,
908+ } ;
909+ } ,
910+ registerClientEvent : ( ) => Promise . resolve ( { } ) ,
911+ } ;
912+ }
913+
914+ const component = new Explain . ConsoleInsight (
915+ getPromptBuilderForInsight ( ) , getAidaClientForInsight ( ) , Host . AidaClient . AidaAccessPreconditions . AVAILABLE ) ;
916+
917+ const container = document . createElement ( 'div' ) ;
918+ container . style . cssText = containerCss ;
919+ component . style . width = '576px' ;
920+ component . style . height = '463px' ;
921+ container . appendChild ( component ) ;
922+ const animated = animatedPromise ( component ) ;
923+ renderElementIntoDOM ( container ) ;
924+ await animated ;
925+
926+ const detailsElement = component . shadowRoot ! . querySelector ( 'details.references' ) ;
927+ const transitioned = new Promise < void > ( resolve => {
928+ detailsElement ?. addEventListener ( 'transitionend' , ( ) => {
929+ resolve ( ) ;
930+ } ) ;
931+ } ) ;
932+ detailsElement ?. querySelector ( 'summary' ) ?. click ( ) ;
933+ await transitioned ;
934+
935+ await assertScreenshot ( 'explain/console_insight_references.png' ) ;
936+ } ) ;
583937} ) ;
0 commit comments