1- import type { JsonValue , ObjectHashContext , Patch } from './index' ;
2- import { generateJSONPatch , pathInfo } from './index' ;
3- import { applyPatch , deepClone } from 'fast-json-patch' ;
4- import { assert , expect } from 'chai' ;
1+ import type { JsonValue , ObjectHashContext , Patch } from './index' ;
2+ import { generateJSONPatch , pathInfo } from './index' ;
3+ import { applyPatch , deepClone } from 'fast-json-patch' ;
4+ import { assert , expect } from 'chai' ;
55
66type Title = string ;
77type Before = JsonValue ;
@@ -18,9 +18,9 @@ const jsonValues = {
1818 primitiveNumberZero : 0 ,
1919 primitiveBooleanTrue : true ,
2020 primitiveBooleanFalse : false ,
21- jsonObjectWithFlatPropertiesAndStringValues : { a : 'a' , b : 'b' , c : 'c' } ,
22- jsonObjectWithFlatPropertiesAndNumberValues : { a : 3 , b : 2 , c : 1 } ,
23- jsonObjectWithFlatPropertiesAndMixedValues : { a : true , b : 'b' , c : 12 } ,
21+ jsonObjectWithFlatPropertiesAndStringValues : { a : 'a' , b : 'b' , c : 'c' } ,
22+ jsonObjectWithFlatPropertiesAndNumberValues : { a : 3 , b : 2 , c : 1 } ,
23+ jsonObjectWithFlatPropertiesAndMixedValues : { a : true , b : 'b' , c : 12 } ,
2424} as const ;
2525
2626describe ( 'a generate json patch function' , ( ) => {
@@ -47,12 +47,12 @@ describe('a generate json patch function', () => {
4747 'adds root array elements' ,
4848 [ 1 , 2 , 3 ] ,
4949 [ 1 , 2 , 3 , 4 ] ,
50- [ { op : 'add' , path : '/3' , value : 4 } ] ,
50+ [ { op : 'add' , path : '/3' , value : 4 } ] ,
5151 ] ,
5252 [
5353 'adds root object property' ,
54- { a : 'a' , b : 'b' } ,
55- { a : 'a' , b : 'b' , c : 'c' } ,
54+ { a : 'a' , b : 'b' } ,
55+ { a : 'a' , b : 'b' , c : 'c' } ,
5656 [
5757 {
5858 op : 'add' ,
@@ -65,38 +65,38 @@ describe('a generate json patch function', () => {
6565 'removes root array elements' ,
6666 [ 1 , 2 , 3 , 4 ] ,
6767 [ 1 , 2 , 3 ] ,
68- [ { op : 'remove' , path : '/3' } ] ,
68+ [ { op : 'remove' , path : '/3' } ] ,
6969 ] ,
7070 [
7171 'removes root object property' ,
72- { a : 'a' , b : 'b' , c : 'c' } ,
73- { a : 'a' , b : 'b' } ,
74- [ { op : 'remove' , path : '/c' } ] ,
72+ { a : 'a' , b : 'b' , c : 'c' } ,
73+ { a : 'a' , b : 'b' } ,
74+ [ { op : 'remove' , path : '/c' } ] ,
7575 ] ,
7676 [
7777 'replaces root number values' ,
7878 1 ,
7979 2 ,
80- [ { op : 'replace' , path : '' , value : 2 } ] ,
80+ [ { op : 'replace' , path : '' , value : 2 } ] ,
8181 ] ,
8282 [
8383 'replaces root string values' ,
8484 'hello' ,
8585 'world' ,
86- [ { op : 'replace' , path : '' , value : 'world' } ] ,
86+ [ { op : 'replace' , path : '' , value : 'world' } ] ,
8787 ] ,
8888 [
8989 'replaces root boolean values' ,
9090 true ,
9191 false ,
92- [ { op : 'replace' , path : '' , value : false } ] ,
92+ [ { op : 'replace' , path : '' , value : false } ] ,
9393 ] ,
9494
9595 [ 'replaces root empty arrays' , [ ] , [ ] , [ ] ] ,
9696 [
9797 'replaces root object property' ,
98- { a : 'a' , b : 'b' } ,
99- { a : 'a' , b : 'c' } ,
98+ { a : 'a' , b : 'b' } ,
99+ { a : 'a' , b : 'c' } ,
100100 [
101101 {
102102 op : 'replace' ,
@@ -109,12 +109,12 @@ describe('a generate json patch function', () => {
109109 'replaces root array elements' ,
110110 [ 1 , 2 , 3 ] ,
111111 [ 1 , 2 , 4 ] ,
112- [ { op : 'replace' , path : '/2' , value : 4 } ] ,
112+ [ { op : 'replace' , path : '/2' , value : 4 } ] ,
113113 ] ,
114114 [
115115 'replaces an obj prop with an array property' ,
116- { prop : { hello : 'world' } } ,
117- { prop : [ 'hello' , 'world' ] } ,
116+ { prop : { hello : 'world' } } ,
117+ { prop : [ 'hello' , 'world' ] } ,
118118 [
119119 {
120120 op : 'replace' ,
@@ -125,20 +125,20 @@ describe('a generate json patch function', () => {
125125 ] ,
126126 [
127127 'replaces an array prop with an obj property' ,
128- { prop : [ 'hello' , 'world' ] } ,
129- { prop : { hello : 'world' } } ,
128+ { prop : [ 'hello' , 'world' ] } ,
129+ { prop : { hello : 'world' } } ,
130130 [
131131 {
132132 op : 'replace' ,
133133 path : '/prop' ,
134- value : { hello : 'world' } ,
134+ value : { hello : 'world' } ,
135135 } ,
136136 ] ,
137137 ] ,
138138 [
139139 'replaces a deep nested object property' ,
140- { root : { first : { second : { third : 'before' } } } } ,
141- { root : { first : { second : { third : 'after' } } } } ,
140+ { root : { first : { second : { third : 'before' } } } } ,
141+ { root : { first : { second : { third : 'after' } } } } ,
142142 [
143143 {
144144 op : 'replace' ,
@@ -153,13 +153,13 @@ describe('a generate json patch function', () => {
153153 root : {
154154 first : [
155155 { } ,
156- { second : { third : 'before' , list : [ 'hello' , 'world' ] } } ,
156+ { second : { third : 'before' , list : [ 'hello' , 'world' ] } } ,
157157 ] ,
158158 } ,
159159 } ,
160160 {
161161 root : {
162- first : [ { } , { second : { third : 'after' , list : [ 'hello' , 'world' ] } } ] ,
162+ first : [ { } , { second : { third : 'after' , list : [ 'hello' , 'world' ] } } ] ,
163163 } ,
164164 } ,
165165 [
@@ -172,8 +172,8 @@ describe('a generate json patch function', () => {
172172 ] ,
173173 [
174174 'detects several changes on arrays by reference' ,
175- { root : [ { id : 1 } , { id : 2 } , { id : 3 } , { id : 4 } ] } ,
176- { root : [ { id : 4 } , { id : 3 } , { id : 2 } ] } ,
175+ { root : [ { id : 1 } , { id : 2 } , { id : 3 } , { id : 4 } ] } ,
176+ { root : [ { id : 4 } , { id : 3 } , { id : 2 } ] } ,
177177 [
178178 {
179179 op : 'remove' ,
@@ -210,8 +210,8 @@ describe('a generate json patch function', () => {
210210
211211 describe ( 'with an array value hash function' , ( ) => {
212212 it ( 'throws when objectHash is not a function' , ( ) => {
213- const before = [ { id : 1 , paramOne : 'before' } ] ;
214- const after = [ { id : 2 , paramOne : 'after' } ] ;
213+ const before = [ { id : 1 , paramOne : 'before' } ] ;
214+ const after = [ { id : 2 , paramOne : 'after' } ] ;
215215
216216 assert . throws ( ( ) =>
217217 generateJSONPatch ( before , after , {
@@ -223,12 +223,12 @@ describe('a generate json patch function', () => {
223223
224224 it ( 'handles changes with change and move on the same property' , ( ) => {
225225 const before = [
226- { id : 1 , paramOne : 'future' , paramTwo : 'past' } ,
227- { id : 2 , paramOne : 'current' } ,
226+ { id : 1 , paramOne : 'future' , paramTwo : 'past' } ,
227+ { id : 2 , paramOne : 'current' } ,
228228 ] ;
229229 const after = [
230- { id : 2 , paramOne : 'current' } ,
231- { id : 1 , paramOne : 'current' } ,
230+ { id : 2 , paramOne : 'current' } ,
231+ { id : 1 , paramOne : 'current' } ,
232232 ] ;
233233
234234 const patch = generateJSONPatch ( before , after , {
@@ -239,19 +239,19 @@ describe('a generate json patch function', () => {
239239
240240 const patched = doPatch ( before , patch ) ;
241241 expect ( patched ) . to . be . eql ( [
242- { id : 2 , paramOne : 'current' } ,
243- { id : 1 , paramOne : 'current' } ,
242+ { id : 2 , paramOne : 'current' } ,
243+ { id : 1 , paramOne : 'current' } ,
244244 ] ) ;
245245 } ) ;
246246
247247 it ( 'handles changes on array objects with different shape' , ( ) => {
248- const before = [ { id : 1 , paramOne : 'current' } ] ;
248+ const before = [ { id : 1 , paramOne : 'current' } ] ;
249249 const after = [
250250 {
251251 id : 1 ,
252252 paramOne : 'future' ,
253253 paramTwo : 'past' ,
254- paramThree : { nested : 'some text' } ,
254+ paramThree : { nested : 'some text' } ,
255255 } ,
256256 ] ;
257257
@@ -268,7 +268,7 @@ describe('a generate json patch function', () => {
268268 id : 1 ,
269269 paramOne : 'future' ,
270270 paramTwo : 'past' ,
271- paramThree : { nested : 'some text' } ,
271+ paramThree : { nested : 'some text' } ,
272272 } ,
273273 ] ) ;
274274 } ) ;
@@ -487,7 +487,7 @@ describe('a generate json patch function', () => {
487487 objectHash : function ( obj : any ) {
488488 return `${ obj . id } ` ;
489489 } ,
490- array : { ignoreMove : true } ,
490+ array : { ignoreMove : true } ,
491491 } ) ;
492492
493493 const patched = doPatch ( before , patch ) ;
@@ -514,10 +514,10 @@ describe('a generate json patch function', () => {
514514 type : 'Granada' ,
515515 colors : [ 'red' , 'silver' , 'yellow' ] ,
516516 engine : [
517- { name : 'Cologne V6 2.6' , hp : 125 } ,
518- { name : 'Cologne V6 2.0' , hp : 90 } ,
519- { name : 'Cologne V6 2.3' , hp : 108 } ,
520- { name : 'Essex V6 3.0' , hp : 150 } ,
517+ { name : 'Cologne V6 2.6' , hp : 125 } ,
518+ { name : 'Cologne V6 2.0' , hp : 90 } ,
519+ { name : 'Cologne V6 2.3' , hp : 108 } ,
520+ { name : 'Essex V6 3.0' , hp : 150 } ,
521521 ] ,
522522 } ;
523523
@@ -526,16 +526,16 @@ describe('a generate json patch function', () => {
526526 type : 'Granada' ,
527527 colors : [ 'red' , 'silver' , 'yellow' ] ,
528528 engine : [
529- { name : 'Essex V6 3.0' , hp : 138 } ,
530- { name : 'Cologne V6 2.6' , hp : 125 } ,
531- { name : 'Cologne V6 2.0' , hp : 90 } ,
532- { name : 'Cologne V6 2.3' , hp : 108 } ,
529+ { name : 'Essex V6 3.0' , hp : 138 } ,
530+ { name : 'Cologne V6 2.6' , hp : 125 } ,
531+ { name : 'Cologne V6 2.0' , hp : 90 } ,
532+ { name : 'Cologne V6 2.3' , hp : 108 } ,
533533 ] ,
534534 } ;
535535
536536 const patch = generateJSONPatch ( before , after , {
537537 objectHash : function ( value : JsonValue , context : ObjectHashContext ) {
538- const { length, last } = pathInfo ( context . path ) ;
538+ const { length, last} = pathInfo ( context . path ) ;
539539 if ( length === 2 && last === 'engine' ) {
540540 // @ts -ignore
541541 return value ?. name ;
@@ -548,8 +548,8 @@ describe('a generate json patch function', () => {
548548 expect ( patched ) . to . be . eql ( after ) ;
549549
550550 expect ( patch ) . to . be . eql ( [
551- { op : 'replace' , path : '/engine/3/hp' , value : 138 } ,
552- { op : 'move' , from : '/engine/3' , path : '/engine/0' } ,
551+ { op : 'replace' , path : '/engine/3/hp' , value : 138 } ,
552+ { op : 'move' , from : '/engine/3' , path : '/engine/0' } ,
553553 ] ) ;
554554 } ) ;
555555 } ) ;
@@ -584,7 +584,7 @@ describe('a generate json patch function', () => {
584584 expect ( patched ) . to . be . eql ( {
585585 id : 1 ,
586586 paramOne : 'after' ,
587- paramTwo : { ignoreMe : 'before' , doNotIgnoreMe : 'after' } ,
587+ paramTwo : { ignoreMe : 'before' , doNotIgnoreMe : 'after' } ,
588588 } ) ;
589589 } ) ;
590590
@@ -627,14 +627,14 @@ describe('a generate json patch function', () => {
627627 paramTwo : {
628628 ignoreMe : 'before' ,
629629 doNotIgnoreMe : 'after' ,
630- two : { ignoreMe : 'after' } ,
630+ two : { ignoreMe : 'after' } ,
631631 } ,
632632 } ) ;
633633
634634 expect ( patch ) . to . eql ( [
635- { op : 'replace' , path : '/paramOne' , value : 'after' } ,
636- { op : 'replace' , path : '/paramTwo/doNotIgnoreMe' , value : 'after' } ,
637- { op : 'replace' , path : '/paramTwo/two/ignoreMe' , value : 'after' } ,
635+ { op : 'replace' , path : '/paramOne' , value : 'after' } ,
636+ { op : 'replace' , path : '/paramTwo/doNotIgnoreMe' , value : 'after' } ,
637+ { op : 'replace' , path : '/paramTwo/two/ignoreMe' , value : 'after' } ,
638638 ] ) ;
639639 } ) ;
640640
@@ -648,8 +648,8 @@ describe('a generate json patch function', () => {
648648 expect ( patched ) . to . be . eql ( [ 1 ] ) ;
649649
650650 expect ( patch ) . to . eql ( [
651- { op : 'remove' , path : '/2' } ,
652- { op : 'remove' , path : '/1' } ,
651+ { op : 'remove' , path : '/2' } ,
652+ { op : 'remove' , path : '/1' } ,
653653 ] ) ;
654654 } ) ;
655655 } ) ;
@@ -680,7 +680,7 @@ describe('a generate json patch function', () => {
680680 } ;
681681
682682 it ( 'detects changes as a given depth of 3' , ( ) => {
683- const patch = generateJSONPatch ( before , after , { maxDepth : 3 } ) ;
683+ const patch = generateJSONPatch ( before , after , { maxDepth : 3 } ) ;
684684 expect ( patch ) . to . eql ( [
685685 {
686686 op : 'replace' ,
@@ -696,10 +696,45 @@ describe('a generate json patch function', () => {
696696 ] ) ;
697697 } ) ;
698698
699+ it ( 'creates empty patch for arrays with object hash' , ( ) => {
700+ const before = {
701+ obj : {
702+ arrayField : [ { nested : { id : 'one' , value : 'hello' } } , {
703+ nested : {
704+ id : 'two' ,
705+ value : 'world'
706+ }
707+ } ]
708+ }
709+ } ;
710+ const after = {
711+ obj : {
712+ arrayField : [ { nested : { value : 'hello' , id : 'one' , } } , {
713+ nested : {
714+ id : 'two' ,
715+ value : 'world'
716+ }
717+ } ]
718+ }
719+ } ;
720+
721+ const patch = generateJSONPatch ( before , after , {
722+ maxDepth : 3 ,
723+ objectHash : function ( obj , context ) {
724+ if ( context . path === '/obj/arrayField' ) {
725+ // @ts -ignore
726+ return obj . nested . id ;
727+ }
728+ return context . index . toString ( ) ;
729+ } ,
730+ } ) ;
731+ expect ( patch ) . to . eql ( [ ] ) ;
732+ } ) ;
733+
699734 it ( 'detects changes as a given depth of 4' , ( ) => {
700735 const afterModified = structuredClone ( after ) ;
701736 afterModified . firstLevel . secondLevel . thirdLevelTwo = 'hello-world' ;
702- const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
737+ const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
703738 expect ( patch ) . to . eql ( [
704739 {
705740 op : 'replace' ,
@@ -719,7 +754,7 @@ describe('a generate json patch function', () => {
719754 it ( 'detects changes as a given depth of 4 for an array value' , ( ) => {
720755 const afterModified = structuredClone ( before ) ;
721756 afterModified . firstLevel . secondLevel . thirdLevelThree = [ 'test' ] ;
722- const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
757+ const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
723758 expect ( patch ) . to . eql ( [
724759 {
725760 op : 'replace' ,
@@ -733,7 +768,7 @@ describe('a generate json patch function', () => {
733768 const afterModified = structuredClone ( before ) ;
734769 // @ts -ignore
735770 delete afterModified . firstLevel . secondLevel . thirdLevelThree ;
736- const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
771+ const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
737772 expect ( patch ) . to . eql ( [
738773 {
739774 op : 'remove' ,
@@ -746,7 +781,7 @@ describe('a generate json patch function', () => {
746781 const afterModified = structuredClone ( before ) ;
747782 // @ts -ignore
748783 afterModified . firstLevel . secondLevel . thirdLevelThree = null ;
749- const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
784+ const patch = generateJSONPatch ( before , afterModified , { maxDepth : 4 } ) ;
750785 expect ( patch ) . to . eql ( [
751786 {
752787 op : 'replace' ,
0 commit comments