@@ -106,6 +106,26 @@ describe("BuiltInStrategies", () => {
106106 expect ( BuiltInStrategies . update ( { ...args , ours : undefined , theirs : "y" } ) . value ) . toBe ( DROP ) ;
107107 } ) ;
108108
109+ it ( "concat arrays" , ( ) => {
110+ const r = BuiltInStrategies . concat ( { ...args , ours : [ 1 ] , theirs : [ 2 ] } ) ;
111+ expect ( r ) . toEqual ( { status : StrategyStatus_OK , value : [ 1 , 2 ] } ) ;
112+ } ) ;
113+
114+ it ( "concat non-arrays → CONTINUE" , ( ) => {
115+ const r = BuiltInStrategies . concat ( { ...args , ours : "a" , theirs : "b" } ) ;
116+ expect ( r . status ) . toBe ( StrategyStatus_CONTINUE ) ;
117+ } ) ;
118+
119+ it ( "unique arrays" , ( ) => {
120+ const r = BuiltInStrategies . unique ( { ...args , ours : [ 1 , 2 ] , theirs : [ 2 , 3 ] } ) ;
121+ expect ( r ) . toEqual ( { status : StrategyStatus_OK , value : [ 1 , 2 , 3 ] } ) ;
122+ } ) ;
123+
124+ it ( "unique non-arrays → CONTINUE" , ( ) => {
125+ const r = BuiltInStrategies . unique ( { ...args , ours : "a" , theirs : "b" } ) ;
126+ expect ( r . status ) . toBe ( StrategyStatus_CONTINUE ) ;
127+ } ) ;
128+
109129 it ( "merge plain objects recurses" , async ( ) => {
110130 const objArgs = {
111131 ...args ,
@@ -175,13 +195,13 @@ describe("mergeObject", () => {
175195 expect ( conflicts [ 0 ] . reason ) . toMatch ( / S k i p / ) ;
176196 } ) ;
177197
178- it . skip ( "adds conflict if all CONTINUE" , async ( ) => {
179- ( resolveStrategies as any ) . mockReturnValueOnce ( [ "non-empty " ] ) ;
198+ it ( "adds conflict if all CONTINUE" , async ( ) => {
199+ ( resolveStrategies as any ) . mockReturnValueOnce ( [ "concat " ] ) ;
180200 const ctx = makeCtx ( ) ;
181201 const conflicts : Conflict [ ] = [ ] ;
182202 const v = await mergeObject ( {
183- ours : "" ,
184- theirs : "" ,
203+ ours : "a " ,
204+ theirs : "b " ,
185205 base : "" ,
186206 path : "p" ,
187207 ctx,
@@ -194,4 +214,64 @@ describe("mergeObject", () => {
194214 reason : expect . stringContaining ( "All strategies failed" ) ,
195215 } ) ;
196216 } ) ;
197- } ) ;
217+
218+ it ( "uses custom strategy from ctx.strategies" , async ( ) => {
219+ ( resolveStrategies as any ) . mockReturnValueOnce ( [ "custom" ] ) ;
220+ const ctx = makeCtx ( ) ;
221+ ctx . strategies . custom = vi . fn ( ( ) => ( { status : StrategyStatus_OK , value : "custom-result" } ) ) ;
222+ const conflicts : Conflict [ ] = [ ] ;
223+ const v = await mergeObject ( {
224+ ours : "a" ,
225+ theirs : "b" ,
226+ path : "p" ,
227+ ctx,
228+ conflicts,
229+ logger : mockLogger ,
230+ } ) ;
231+ expect ( v ) . toBe ( "custom-result" ) ;
232+ expect ( ctx . strategies . custom ) . toHaveBeenCalled ( ) ;
233+ } ) ;
234+
235+ it ( "throws on FAIL status" , async ( ) => {
236+ ( resolveStrategies as any ) . mockReturnValueOnce ( [ "fail-strategy" ] ) ;
237+ const ctx = makeCtx ( ) ;
238+ ctx . strategies [ "fail-strategy" ] = vi . fn ( ( ) => ( {
239+ status : StrategyStatus_FAIL ,
240+ reason : "test fail" ,
241+ } ) ) ;
242+ const conflicts : Conflict [ ] = [ ] ;
243+ await expect (
244+ mergeObject ( {
245+ ours : "a" ,
246+ theirs : "b" ,
247+ path : "p" ,
248+ ctx,
249+ conflicts,
250+ logger : mockLogger ,
251+ } ) ,
252+ ) . rejects . toThrow ( "Merge failed at p: test fail" ) ;
253+ expect ( conflicts [ 0 ] . reason ) . toBe ( "test fail" ) ;
254+ } ) ;
255+
256+ it ( "enriches conflict with debug info when debug enabled" , async ( ) => {
257+ ( resolveStrategies as any ) . mockReturnValueOnce ( [ "concat" ] ) ;
258+ const ctx = makeCtx ( ) ;
259+ ctx . config . debug = true ;
260+ const conflicts : Conflict [ ] = [ ] ;
261+ await mergeObject ( {
262+ ours : "a" ,
263+ theirs : "b" ,
264+ base : "c" ,
265+ path : "p" ,
266+ ctx,
267+ conflicts,
268+ logger : mockLogger ,
269+ } ) ;
270+ expect ( conflicts [ 0 ] ) . toMatchObject ( {
271+ path : "p" ,
272+ ours : "a" ,
273+ theirs : "b" ,
274+ base : "c" ,
275+ } ) ;
276+ } ) ;
277+ } ) ;
0 commit comments