@@ -4,6 +4,8 @@ import useAiAnswersSettings, { DEFAULT_PERSONALITY } from '../use-ai-answers-set
44
55jest . mock ( '@wordpress/api-fetch' , ( ) => jest . fn ( ) ) ;
66
7+ const SETTINGS_KEY = 'jetpack_search_ai_behavior_instructions' ;
8+
79const makePost = ( overrides = { } ) => ( {
810 id : 42 ,
911 guideline_categories : {
@@ -19,7 +21,7 @@ describe( 'useAiAnswersSettings', () => {
1921 jest . resetAllMocks ( ) ;
2022 } ) ;
2123
22- describe ( 'initial load' , ( ) => {
24+ describe ( 'initial load — guidelines endpoint available ' , ( ) => {
2325 it ( 'starts in loading state' , ( ) => {
2426 apiFetch . mockReturnValue ( new Promise ( ( ) => { } ) ) ;
2527 const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
@@ -62,31 +64,55 @@ describe( 'useAiAnswersSettings', () => {
6264 expect ( result . current . postId ) . toBeNull ( ) ;
6365 } ) ;
6466
65- it ( 'sets isUnavailable on rest_no_route error ' , async ( ) => {
66- apiFetch . mockRejectedValue ( { code : 'rest_no_route ' } ) ;
67+ it ( 'sets error on generic fetch failure ' , async ( ) => {
68+ apiFetch . mockRejectedValue ( { message : 'Network failure ' } ) ;
6769 const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
6870 await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
69- expect ( result . current . isUnavailable ) . toBe ( true ) ;
70- expect ( result . current . error ) . toBeNull ( ) ;
71+ expect ( result . current . error ) . toBe ( 'Network failure' ) ;
72+ expect ( result . current . isUnavailable ) . toBe ( false ) ;
7173 } ) ;
74+ } ) ;
7275
73- it ( 'sets isUnavailable on 404 status error' , async ( ) => {
74- apiFetch . mockRejectedValue ( { data : { status : 404 } } ) ;
76+ describe ( 'initial load — guidelines endpoint absent, settings fallback' , ( ) => {
77+ it ( 'falls back to /wp/v2/settings on rest_no_route' , async ( ) => {
78+ apiFetch
79+ . mockRejectedValueOnce ( { code : 'rest_no_route' } )
80+ . mockResolvedValueOnce ( { [ SETTINGS_KEY ] : 'Answer in French.' } ) ;
7581 const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
7682 await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
77- expect ( result . current . isUnavailable ) . toBe ( true ) ;
83+ expect ( result . current . content ) . toBe ( 'Answer in French.' ) ;
84+ expect ( result . current . isUnavailable ) . toBe ( false ) ;
7885 } ) ;
7986
80- it ( 'sets error on generic fetch failure' , async ( ) => {
81- apiFetch . mockRejectedValue ( { message : 'Network failure' } ) ;
87+ it ( 'falls back to /wp/v2/settings on 404 status error' , async ( ) => {
88+ apiFetch
89+ . mockRejectedValueOnce ( { data : { status : 404 } } )
90+ . mockResolvedValueOnce ( { [ SETTINGS_KEY ] : 'Be brief.' } ) ;
8291 const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
8392 await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
84- expect ( result . current . error ) . toBe ( 'Network failure ' ) ;
93+ expect ( result . current . content ) . toBe ( 'Be brief. ' ) ;
8594 expect ( result . current . isUnavailable ) . toBe ( false ) ;
8695 } ) ;
96+
97+ it ( 'leaves content empty when option is not set' , async ( ) => {
98+ apiFetch . mockRejectedValueOnce ( { code : 'rest_no_route' } ) . mockResolvedValueOnce ( { } ) ;
99+ const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
100+ await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
101+ expect ( result . current . content ) . toBe ( '' ) ;
102+ } ) ;
103+
104+ it ( 'sets isUnavailable when both endpoints fail' , async ( ) => {
105+ apiFetch
106+ . mockRejectedValueOnce ( { code : 'rest_no_route' } )
107+ . mockRejectedValueOnce ( { message : 'Settings unavailable' } ) ;
108+ const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
109+ await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
110+ expect ( result . current . isUnavailable ) . toBe ( true ) ;
111+ expect ( result . current . error ) . toBeNull ( ) ;
112+ } ) ;
87113 } ) ;
88114
89- describe ( 'savePersonality' , ( ) => {
115+ describe ( 'savePersonality — guidelines path ' , ( ) => {
90116 it ( 'POSTs when postId is null' , async ( ) => {
91117 apiFetch . mockResolvedValueOnce ( [ ] ) . mockResolvedValueOnce ( { id : 99 } ) ;
92118 const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
@@ -162,4 +188,61 @@ describe( 'useAiAnswersSettings', () => {
162188 ) . toBe ( 'Custom instructions.' ) ;
163189 } ) ;
164190 } ) ;
191+
192+ describe ( 'savePersonality — settings fallback path' , ( ) => {
193+ const setupSettingsFallback = async ( ) => {
194+ apiFetch
195+ . mockRejectedValueOnce ( { code : 'rest_no_route' } )
196+ . mockResolvedValueOnce ( { [ SETTINGS_KEY ] : 'Be concise.' } ) ;
197+ const utils = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
198+ await waitFor ( ( ) => expect ( utils . result . current . isLoading ) . toBe ( false ) ) ;
199+ return utils ;
200+ } ;
201+
202+ it ( 'POSTs to /wp/v2/settings' , async ( ) => {
203+ const { result } = await setupSettingsFallback ( ) ;
204+ apiFetch . mockResolvedValueOnce ( { } ) ;
205+
206+ await act ( async ( ) => result . current . savePersonality ( ) ) ;
207+
208+ const saveCall = apiFetch . mock . calls [ 2 ] [ 0 ] ;
209+ expect ( saveCall . method ) . toBe ( 'POST' ) ;
210+ expect ( saveCall . path ) . toBe ( '/wp/v2/settings' ) ;
211+ expect ( saveCall . data [ SETTINGS_KEY ] ) . toBe ( 'Be concise.' ) ;
212+ } ) ;
213+
214+ it ( 'sends DEFAULT_PERSONALITY when content is empty' , async ( ) => {
215+ apiFetch
216+ . mockRejectedValueOnce ( { code : 'rest_no_route' } )
217+ . mockResolvedValueOnce ( { } )
218+ . mockResolvedValueOnce ( { } ) ;
219+ const { result } = renderHook ( ( ) => useAiAnswersSettings ( ) ) ;
220+ await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
221+
222+ await act ( async ( ) => result . current . savePersonality ( ) ) ;
223+
224+ const saveCall = apiFetch . mock . calls [ 2 ] [ 0 ] ;
225+ expect ( saveCall . data [ SETTINGS_KEY ] ) . toBe ( DEFAULT_PERSONALITY ) ;
226+ } ) ;
227+
228+ it ( 'sets saved on success' , async ( ) => {
229+ const { result } = await setupSettingsFallback ( ) ;
230+ apiFetch . mockResolvedValueOnce ( { } ) ;
231+
232+ await act ( async ( ) => result . current . savePersonality ( ) ) ;
233+
234+ expect ( result . current . saved ) . toBe ( true ) ;
235+ expect ( result . current . isSaving ) . toBe ( false ) ;
236+ } ) ;
237+
238+ it ( 'sets error on save failure' , async ( ) => {
239+ const { result } = await setupSettingsFallback ( ) ;
240+ apiFetch . mockRejectedValueOnce ( { message : 'Settings save failed' } ) ;
241+
242+ await act ( async ( ) => result . current . savePersonality ( ) ) ;
243+
244+ expect ( result . current . error ) . toBe ( 'Settings save failed' ) ;
245+ expect ( result . current . saved ) . toBe ( false ) ;
246+ } ) ;
247+ } ) ;
165248} ) ;
0 commit comments