@@ -11,32 +11,77 @@ import { theme } from "constants/DefaultTheme";
1111import { EditorTheme } from "./EditorConfig" ;
1212import { LOCAL_STORAGE_KEYS } from "utils/localStorage" ;
1313
14- function renderPopup ( props ?: {
14+ function popupJsx ( props ?: {
1515 hasError ?: boolean ;
1616 hideEvaluatedValue ?: boolean ;
17+ isOpen ?: boolean ;
1718} ) {
18- return render (
19+ return (
1920 < Provider store = { store } >
2021 < ThemeProvider theme = { theme } >
2122 < EvaluatedValuePopup
2223 errors = { [ ] }
2324 hasError = { props ?. hasError || false }
2425 hideEvaluatedValue = { props ?. hideEvaluatedValue || false }
25- isOpen
26+ isOpen = { props ?. isOpen ?? true }
2627 theme = { EditorTheme . LIGHT }
2728 >
2829 < div > children</ div >
2930 </ EvaluatedValuePopup >
3031 </ ThemeProvider >
31- </ Provider > ,
32+ </ Provider >
3233 ) ;
3334}
3435
36+ function renderPopup ( props ?: {
37+ hasError ?: boolean ;
38+ hideEvaluatedValue ?: boolean ;
39+ isOpen ?: boolean ;
40+ } ) {
41+ return render ( popupJsx ( props ) ) ;
42+ }
43+
44+ // Drags the popup by its handle so its internal dragged-position state is
45+ // set, exactly as a user drag would. jsdom elements measure 0x0, and a
46+ // zero-size popup is treated as closed on mouseup, so give the popper
47+ // element a real box first.
48+ function dragPopupTo ( clientX : number , clientY : number ) {
49+ const popperElement = document . querySelector ( ".t--CodeEditor-evaluatedValue" )
50+ ?. parentElement as HTMLElement ;
51+
52+ popperElement . getBoundingClientRect = ( ) =>
53+ ( {
54+ left : 100 ,
55+ top : 100 ,
56+ width : 300 ,
57+ height : 118 ,
58+ right : 400 ,
59+ bottom : 218 ,
60+ x : 100 ,
61+ y : 100 ,
62+ toJSON : ( ) => ( { } ) ,
63+ } ) as DOMRect ;
64+
65+ const handle = document . querySelector (
66+ "[id$='-popper-draghandler']" ,
67+ ) as HTMLElement ;
68+
69+ fireEvent . mouseDown ( handle , { clientX : 10 , clientY : 10 } ) ;
70+ fireEvent . mouseMove ( document , { clientX, clientY } ) ;
71+ fireEvent . mouseUp ( document , { clientX, clientY } ) ;
72+
73+ return popperElement ;
74+ }
75+
3576describe ( "EvaluatedValuePopup" , ( ) => {
3677 beforeEach ( ( ) => {
3778 localStorage . removeItem ( LOCAL_STORAGE_KEYS . EVALUATED_VALUE_POPUP_COLLAPSED ) ;
3879 } ) ;
3980
81+ afterEach ( ( ) => {
82+ jest . restoreAllMocks ( ) ;
83+ } ) ;
84+
4085 it ( "should render evaluated popup when hideEvaluatedValue is false" , ( ) => {
4186 renderPopup ( ) ;
4287 const input = screen . queryByTestId ( "evaluated-value-popup-title" ) ;
@@ -125,4 +170,47 @@ describe("EvaluatedValuePopup", () => {
125170 screen . getByTestId ( "t--evaluated-popup-error-indicator" ) ,
126171 ) . toBeTruthy ( ) ;
127172 } ) ;
173+
174+ it ( "should keep the dragged position across a window-level blur (minimize / app switch)" , ( ) => {
175+ jest . spyOn ( document , "hasFocus" ) . mockReturnValue ( false ) ;
176+
177+ const { rerender } = renderPopup ( ) ;
178+
179+ // the anchor ref is null on the very first render; a second render lets
180+ // the popper attach and create its drag handle
181+ rerender ( popupJsx ( { isOpen : true } ) ) ;
182+ dragPopupTo ( 60 , 60 ) ;
183+
184+ // window loses focus -> editor blurs -> popup closes
185+ rerender ( popupJsx ( { isOpen : false } ) ) ;
186+ // window restored, editor refocuses -> popup reopens
187+ rerender ( popupJsx ( { isOpen : true } ) ) ;
188+
189+ const popperElement = document . querySelector (
190+ ".t--CodeEditor-evaluatedValue" ,
191+ ) ?. parentElement as HTMLElement ;
192+
193+ expect ( popperElement . style . top ) . toBe ( "100px" ) ;
194+ expect ( popperElement . style . left ) . toBe ( "100px" ) ;
195+ } ) ;
196+
197+ it ( "should re-anchor after an in-app blur discards the dragged position" , ( ) => {
198+ jest . spyOn ( document , "hasFocus" ) . mockReturnValue ( true ) ;
199+
200+ const { rerender } = renderPopup ( ) ;
201+
202+ rerender ( popupJsx ( { isOpen : true } ) ) ;
203+ dragPopupTo ( 60 , 60 ) ;
204+
205+ // the user moves on inside the app: close and reopen
206+ rerender ( popupJsx ( { isOpen : false } ) ) ;
207+ rerender ( popupJsx ( { isOpen : true } ) ) ;
208+
209+ const popperElement = document . querySelector (
210+ ".t--CodeEditor-evaluatedValue" ,
211+ ) ?. parentElement as HTMLElement ;
212+
213+ expect ( popperElement . style . top ) . not . toBe ( "100px" ) ;
214+ expect ( popperElement . style . left ) . not . toBe ( "100px" ) ;
215+ } ) ;
128216} ) ;
0 commit comments