@@ -130,6 +130,122 @@ describe('encodeAttributes', () => {
130130 expect ( result ) . toEqual ( { valid : 'ok' } ) ;
131131 } ) ;
132132
133+ it ( 'does not modify original object when dropping values' , ( ) => {
134+ const input = { valid : 'ok' , bad : ( ) => { } } ;
135+ const result = encodeAttributes ( input ) ;
136+ expect ( result ) . toEqual ( { valid : 'ok' } ) ;
137+ expect ( input ) . toHaveProperty ( 'bad' ) ;
138+ } ) ;
139+
140+ it ( 'does not modify original object when dropping nested invalid values' , ( ) => {
141+ const input = { user : { profile : { bad : ( ) => { } , good : 'ok' } } } ;
142+ const userBefore = { ...input . user } ;
143+ const profileBefore = { ...input . user . profile } ;
144+
145+ const result = encodeAttributes ( input ) ;
146+
147+ // Encoder should flatten and drop invalid function
148+ expect ( result ) . toEqual ( { 'user.profile.good' : 'ok' } ) ;
149+
150+ // Verify that the original objects were not mutated or replaced
151+ expect ( input . user ) . toEqual ( userBefore ) ;
152+ expect ( input . user . profile ) . toEqual ( profileBefore ) ;
153+ } ) ;
154+
155+ it ( 'does not modify original array inside object' , ( ) => {
156+ const arr = [ 1 , 2 , ( ) => { } ] ;
157+ const input = { data : arr } ;
158+ const arrBefore = [ ...arr ] ;
159+
160+ const result = encodeAttributes ( input ) ;
161+ expect ( result ) . toEqual ( { data : [ 1 , 2 ] } ) ; // dropped the function
162+ expect ( input . data ) . toEqual ( arrBefore ) ; // original array untouched
163+ } ) ;
164+
165+ it ( 'does not modify original nested arrays of objects' , ( ) => {
166+ const objA = { val : 1 } ;
167+ const objB = { bad : ( ) => { } } ;
168+ const objC = { val : 2 } ;
169+
170+ const input = {
171+ matrix : [ [ objA , objB ] , [ objC ] ]
172+ } ;
173+
174+ // capture snapshots
175+ const matrixBefore = input . matrix ;
176+ const row0Before = input . matrix [ 0 ] ;
177+ const row1Before = input . matrix [ 1 ] ;
178+ const objA_before = { ...objA } ;
179+ const objB_before = { ...objB } ;
180+ const objC_before = { ...objC } ;
181+
182+ const result = encodeAttributes ( input ) ;
183+
184+ expect ( result ) . toEqual ( {
185+ matrix : [
186+ [ { val : 1 } , { } ] , // objB sanitized
187+ [ { val : 2 } ]
188+ ]
189+ } ) ;
190+
191+ // check original references untouched
192+ expect ( input . matrix ) . toBe ( matrixBefore ) ; // same outer array reference
193+ expect ( input . matrix [ 0 ] ) . toBe ( row0Before ) ; // same row0 reference
194+ expect ( input . matrix [ 1 ] ) . toBe ( row1Before ) ; // same row1 reference
195+ expect ( objA ) . toEqual ( objA_before ) ; // object A unchanged
196+ expect ( objB ) . toEqual ( objB_before ) ; // object B unchanged
197+ expect ( objC ) . toEqual ( objC_before ) ; // object C unchanged
198+ } ) ;
199+
200+ it ( 'does not modify original Map when encoding' , ( ) => {
201+ const innerMap = new Map ( [ [ 'x' , 1 ] ] ) ;
202+ const outerMap = new Map < any , any > ( [ [ 'inner' , innerMap ] ] ) ;
203+ const input = { outer : outerMap } ;
204+
205+ const snapshot = new Map ( outerMap ) ;
206+ const innerSnapshot = new Map ( innerMap ) ;
207+
208+ const result = encodeAttributes ( input ) ;
209+ expect ( result . outer ) . toBeInstanceOf ( Array ) ;
210+ expect ( input . outer ) . toBe ( outerMap ) ; // same reference
211+ expect ( Array . from ( input . outer . entries ( ) ) ) . toEqual (
212+ Array . from ( snapshot . entries ( ) )
213+ ) ;
214+ expect ( Array . from ( innerMap . entries ( ) ) ) . toEqual (
215+ Array . from ( innerSnapshot . entries ( ) )
216+ ) ;
217+ } ) ;
218+
219+ it ( 'does not modify original object when attribute limit is reached' , ( ) => {
220+ const input : Record < string , string > = { } ;
221+ for ( let i = 0 ; i < 200 ; i ++ ) {
222+ input [ `k${ i } ` ] = `v${ i } ` ;
223+ }
224+ const snapshot = { ...input } ;
225+
226+ const result = encodeAttributes ( input ) ;
227+ expect ( Object . keys ( result ) ) . toHaveLength ( 128 ) ;
228+ expect ( input ) . toEqual ( snapshot ) ; // original still has 200 keys
229+ } ) ;
230+
231+ it ( 'does not modify original when sanitizing arrays of objects' , ( ) => {
232+ const obj1 = { ok : true } ;
233+ const obj2 = { bad : ( ) => { } } ;
234+ const input = [ obj1 , obj2 ] ;
235+
236+ // Capture pre-encode snapshots manually
237+ const obj1Before = { ...obj1 } ;
238+ const obj2Before = { ...obj2 } ;
239+ const arrayBefore = [ ...input ] ;
240+
241+ const result = encodeAttributes ( input ) ;
242+
243+ expect ( result ) . toEqual ( { context : [ { ok : true } , { } ] } ) ;
244+ expect ( input ) . toEqual ( arrayBefore ) ;
245+ expect ( input [ 0 ] ) . toEqual ( obj1Before ) ;
246+ expect ( input [ 1 ] ) . toEqual ( obj2Before ) ;
247+ } ) ;
248+
133249 it ( 'handles deeply nested objects' , ( ) => {
134250 const deep = { level1 : { level2 : { level3 : { value : 42 } } } } ;
135251 const result = encodeAttributes ( deep ) ;
0 commit comments