@@ -2,6 +2,7 @@ import { getDomainNameValidationError } from 'common/lib/domains';
22import { arePathsEqual } from 'common/lib/fs-utils' ;
33import {
44 getSiteByFolder ,
5+ isXdebugBetaEnabled ,
56 unlockAppdata ,
67 readAppdata ,
78 saveAppdata ,
@@ -26,6 +27,7 @@ jest.mock( 'common/lib/fs-utils', () => ( {
2627jest . mock ( 'cli/lib/appdata' , ( ) => ( {
2728 ...jest . requireActual ( 'cli/lib/appdata' ) ,
2829 getSiteByFolder : jest . fn ( ) ,
30+ isXdebugBetaEnabled : jest . fn ( ) ,
2931 lockAppdata : jest . fn ( ) . mockResolvedValue ( undefined ) ,
3032 unlockAppdata : jest . fn ( ) . mockResolvedValue ( undefined ) ,
3133 readAppdata : jest . fn ( ) ,
@@ -70,6 +72,7 @@ describe( 'CLI: studio site set', () => {
7072
7173 ( arePathsEqual as jest . Mock ) . mockReturnValue ( true ) ;
7274 ( getSiteByFolder as jest . Mock ) . mockResolvedValue ( getTestSite ( ) ) ;
75+ ( isXdebugBetaEnabled as jest . Mock ) . mockResolvedValue ( true ) ;
7376 ( readAppdata as jest . Mock ) . mockResolvedValue ( testAppdata ) ;
7477 ( connect as jest . Mock ) . mockResolvedValue ( undefined ) ;
7578 ( disconnect as jest . Mock ) . mockResolvedValue ( undefined ) ;
@@ -88,7 +91,7 @@ describe( 'CLI: studio site set', () => {
8891 describe ( 'Validation' , ( ) => {
8992 it ( 'should throw when no options provided' , async ( ) => {
9093 await expect ( runCommand ( testSitePath , { } ) ) . rejects . toThrow (
91- 'At least one option (--name, --domain, --https, --php, --wp) is required.'
94+ 'At least one option (--name, --domain, --https, --php, --wp, --xdebug ) is required.'
9295 ) ;
9396 } ) ;
9497
@@ -329,6 +332,94 @@ describe( 'CLI: studio site set', () => {
329332 } ) ;
330333 } ) ;
331334
335+ describe ( 'Xdebug changes' , ( ) => {
336+ it ( 'should throw when beta feature is not enabled' , async ( ) => {
337+ ( isXdebugBetaEnabled as jest . Mock ) . mockResolvedValue ( false ) ;
338+
339+ await expect ( runCommand ( testSitePath , { xdebug : true } ) ) . rejects . toThrow (
340+ 'Xdebug support is a beta feature. Enable it in Studio settings first.'
341+ ) ;
342+ } ) ;
343+
344+ it ( 'should throw when another site already has xdebug enabled' , async ( ) => {
345+ const testSite = getTestSite ( ) ;
346+ const otherSite = {
347+ ...getTestSite ( ) ,
348+ id : 'site-2' ,
349+ name : 'Other Site' ,
350+ path : '/other/site' ,
351+ enableXdebug : true ,
352+ } ;
353+ ( getSiteByFolder as jest . Mock ) . mockResolvedValue ( testSite ) ;
354+ ( readAppdata as jest . Mock ) . mockResolvedValue ( {
355+ sites : [ testSite , otherSite ] ,
356+ snapshots : [ ] ,
357+ } ) ;
358+
359+ await expect ( runCommand ( testSitePath , { xdebug : true } ) ) . rejects . toThrow (
360+ 'Only one site can have Xdebug enabled at a time. Disable Xdebug on "Other Site" first.'
361+ ) ;
362+ } ) ;
363+
364+ it ( 'should update xdebug setting without restart when site is stopped' , async ( ) => {
365+ await runCommand ( testSitePath , { xdebug : true } ) ;
366+
367+ const savedAppdata = ( saveAppdata as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
368+ expect ( savedAppdata . sites [ 0 ] . enableXdebug ) . toBe ( true ) ;
369+ expect ( stopWordPressServer ) . not . toHaveBeenCalled ( ) ;
370+ expect ( startWordPressServer ) . not . toHaveBeenCalled ( ) ;
371+ } ) ;
372+
373+ it ( 'should restart running site when xdebug changes' , async ( ) => {
374+ ( isServerRunning as jest . Mock ) . mockResolvedValue ( testProcessDescription ) ;
375+
376+ await runCommand ( testSitePath , { xdebug : true } ) ;
377+
378+ expect ( stopWordPressServer ) . toHaveBeenCalledWith ( 'site-1' ) ;
379+ expect ( startWordPressServer ) . toHaveBeenCalled ( ) ;
380+ } ) ;
381+
382+ it ( 'should disable xdebug' , async ( ) => {
383+ const siteWithXdebug = { ...getTestSite ( ) , enableXdebug : true } ;
384+ ( getSiteByFolder as jest . Mock ) . mockResolvedValue ( siteWithXdebug ) ;
385+ ( readAppdata as jest . Mock ) . mockResolvedValue ( {
386+ sites : [ siteWithXdebug ] ,
387+ snapshots : [ ] ,
388+ } ) ;
389+
390+ await runCommand ( testSitePath , { xdebug : false } ) ;
391+
392+ const savedAppdata = ( saveAppdata as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
393+ expect ( savedAppdata . sites [ 0 ] . enableXdebug ) . toBe ( false ) ;
394+ } ) ;
395+
396+ it ( 'should throw when xdebug is already enabled' , async ( ) => {
397+ const siteWithXdebug = { ...getTestSite ( ) , enableXdebug : true } ;
398+ ( getSiteByFolder as jest . Mock ) . mockResolvedValue ( siteWithXdebug ) ;
399+ ( readAppdata as jest . Mock ) . mockResolvedValue ( {
400+ sites : [ siteWithXdebug ] ,
401+ snapshots : [ ] ,
402+ } ) ;
403+
404+ await expect ( runCommand ( testSitePath , { xdebug : true } ) ) . rejects . toThrow (
405+ 'No changes to apply. The site already has the specified settings.'
406+ ) ;
407+ } ) ;
408+
409+ it ( 'should throw when xdebug is already disabled' , async ( ) => {
410+ const siteWithXdebugDisabled = { ...getTestSite ( ) , enableXdebug : false } ;
411+ ( getSiteByFolder as jest . Mock ) . mockResolvedValue ( siteWithXdebugDisabled ) ;
412+ ( readAppdata as jest . Mock ) . mockResolvedValue ( {
413+ sites : [ siteWithXdebugDisabled ] ,
414+ snapshots : [ ] ,
415+ } ) ;
416+
417+ await expect ( runCommand ( testSitePath , { xdebug : false } ) ) . rejects . toThrow (
418+ 'No changes to apply. The site already has the specified settings.'
419+ ) ;
420+ } ) ;
421+ } ) ;
422+
332423 describe ( 'Error handling' , ( ) => {
333424 it ( 'should throw when site not found' , async ( ) => {
334425 ( getSiteByFolder as jest . Mock ) . mockRejectedValue ( new Error ( 'Site not found' ) ) ;
0 commit comments