@@ -12,6 +12,10 @@ interface MockConfig {
1212 heartbeatDefaultIntervalMs ?: number ;
1313}
1414
15+ interface MockOptions {
16+ getConfigError ?: Error ;
17+ }
18+
1519interface MockAPIClient {
1620 config : {
1721 getConfig : ( ) => Promise < MockConfig > ;
@@ -34,7 +38,7 @@ void mock.module("@/browser/contexts/API", () => ({
3438
3539import { HeartbeatSection } from "./HeartbeatSection" ;
3640
37- function createMockAPI ( configOverrides : Partial < MockConfig > = { } ) {
41+ function createMockAPI ( configOverrides : Partial < MockConfig > = { } , options : MockOptions = { } ) {
3842 const config : MockConfig = {
3943 ...configOverrides ,
4044 } ;
@@ -55,7 +59,11 @@ function createMockAPI(configOverrides: Partial<MockConfig> = {}) {
5559 return {
5660 api : {
5761 config : {
58- getConfig : mock ( ( ) => Promise . resolve ( { ...config } ) ) ,
62+ getConfig : mock ( ( ) =>
63+ options . getConfigError
64+ ? Promise . reject ( options . getConfigError )
65+ : Promise . resolve ( { ...config } )
66+ ) ,
5967 updateHeartbeatDefaultPrompt : updateHeartbeatDefaultPromptMock ,
6068 updateHeartbeatDefaultIntervalMs : updateHeartbeatDefaultIntervalMsMock ,
6169 } ,
@@ -65,9 +73,12 @@ function createMockAPI(configOverrides: Partial<MockConfig> = {}) {
6573 } ;
6674}
6775
68- function renderHeartbeatSection ( configOverrides : Partial < MockConfig > = { } ) {
76+ function renderHeartbeatSection (
77+ configOverrides : Partial < MockConfig > = { } ,
78+ options : MockOptions = { }
79+ ) {
6980 const { api, updateHeartbeatDefaultPromptMock, updateHeartbeatDefaultIntervalMsMock } =
70- createMockAPI ( configOverrides ) ;
81+ createMockAPI ( configOverrides , options ) ;
7182 mockApi = api ;
7283
7384 const view = render (
@@ -126,6 +137,42 @@ describe("HeartbeatSection", () => {
126137 } ) ;
127138 } ) ;
128139
140+ test ( "skips saving a stale prompt after config reload fails until the user edits" , async ( ) => {
141+ const initialPrompt = "Review pending work before acting." ;
142+ const { view } = renderHeartbeatSection ( {
143+ heartbeatDefaultPrompt : initialPrompt ,
144+ } ) ;
145+
146+ const promptField = ( await waitFor ( ( ) =>
147+ view . getByLabelText ( "Default heartbeat prompt" )
148+ ) ) as HTMLTextAreaElement ;
149+
150+ await waitFor ( ( ) => {
151+ expect ( promptField . value ) . toBe ( initialPrompt ) ;
152+ } ) ;
153+
154+ const failedReload = createMockAPI ( { } , { getConfigError : new Error ( "load failed" ) } ) ;
155+ mockApi = failedReload . api ;
156+ view . rerender (
157+ < ThemeProvider forcedTheme = "dark" >
158+ < HeartbeatSection />
159+ </ ThemeProvider >
160+ ) ;
161+
162+ await waitFor ( ( ) => {
163+ expect ( promptField . value ) . toBe ( initialPrompt ) ;
164+ } ) ;
165+
166+ const failedReloadPromptField = view . getByLabelText (
167+ "Default heartbeat prompt"
168+ ) as HTMLTextAreaElement ;
169+
170+ fireEvent . blur ( failedReloadPromptField ) ;
171+ await Promise . resolve ( ) ;
172+ await Promise . resolve ( ) ;
173+ expect ( failedReload . updateHeartbeatDefaultPromptMock ) . not . toHaveBeenCalled ( ) ;
174+ } ) ;
175+
129176 test ( "loads and saves the default heartbeat threshold" , async ( ) => {
130177 const initialIntervalMs = 45 * 60_000 ;
131178 const { view, updateHeartbeatDefaultIntervalMsMock } = renderHeartbeatSection ( {
0 commit comments