Skip to content

Commit 111b308

Browse files
committed
minor 4.5.0: adds tests for moveComments
1 parent e2e3a94 commit 111b308

File tree

4 files changed

+288
-4
lines changed

4 files changed

+288
-4
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "comment-json",
3-
"version": "4.4.1",
3+
"version": "4.5.0",
44
"description": "Parse and stringify JSON with comments. It will retain comments even after saved!",
55
"main": "src/index.js",
66
"types": "index.d.ts",

src/array.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ class CommentArray extends Array {
107107
// as well as:
108108
// - slice
109109
/**
110-
* Changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
110+
* Changes the contents of an array by removing or replacing existing
111+
* elements and/or adding new elements in place.
111112
* Comments are automatically preserved and repositioned during the operation.
112113
*
113114
* @param {...*} args Arguments passed to Array.prototype.splice
@@ -163,7 +164,8 @@ class CommentArray extends Array {
163164
* Comments are copied to the appropriate positions in the new array.
164165
*
165166
* @param {...*} args Arguments passed to Array.prototype.slice
166-
* @returns {CommentArray} A new CommentArray containing the extracted elements with their comments.
167+
* @returns {CommentArray} A new CommentArray containing the extracted
168+
* elements with their comments.
167169
*/
168170
slice (...args) {
169171
const {length} = this

src/common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const UNDEFINED = undefined
4343
const symbol = (prefix, key) => Symbol.for(prefix + COLON + key)
4444
const symbol_checked = (prefix, key) => key
4545
? symbol(prefix, key)
46-
: prefix
46+
: Symbol.for(prefix)
4747

4848
const define = (target, key, value) => Object.defineProperty(target, key, {
4949
value,

test/move-comments.test.js

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// eslint-disable-next-line import/no-unresolved
2+
const test = require('ava')
3+
4+
const {parse, stringify, moveComments} = require('..')
5+
6+
test('moveComments: should throw TypeError if source is not an object', t => {
7+
const error = t.throws(() => {
8+
moveComments('not an object', {}, {kind: 'before'}, {kind: 'after'})
9+
}, {instanceOf: TypeError})
10+
11+
t.is(error.message, 'source must be an object')
12+
})
13+
14+
test('moveComments: should throw TypeError if source is null', t => {
15+
const error = t.throws(() => {
16+
moveComments(null, {}, {kind: 'before'}, {kind: 'after'})
17+
}, {instanceOf: TypeError})
18+
19+
t.is(error.message, 'source must be an object')
20+
})
21+
22+
test('moveComments: should throw TypeError if source is primitive', t => {
23+
const error = t.throws(() => {
24+
moveComments(123, {}, {kind: 'before'}, {kind: 'after'})
25+
}, {instanceOf: TypeError})
26+
27+
t.is(error.message, 'source must be an object')
28+
})
29+
30+
test('moveComments: should use source as target when target is not provided', t => {
31+
const source = parse(`{
32+
"foo": 1 // comment
33+
}`)
34+
35+
// Call moveComments without target parameter
36+
moveComments(source, null,
37+
{kind: 'after', key: 'foo'},
38+
{kind: 'before', key: 'foo'}
39+
)
40+
41+
const result = stringify(source, null, 2)
42+
t.true(result.includes('// comment'))
43+
t.true(result.includes('"foo": 1'))
44+
})
45+
46+
test('moveComments: should use source as target when target is undefined', t => {
47+
const source = parse(`{
48+
"foo": 1 // comment
49+
}`)
50+
51+
// Call moveComments with undefined target
52+
moveComments(source, undefined,
53+
{kind: 'after', key: 'foo'},
54+
{kind: 'before', key: 'foo'}
55+
)
56+
57+
const result = stringify(source, null, 2)
58+
t.true(result.includes('// comment'))
59+
t.true(result.includes('"foo": 1'))
60+
})
61+
62+
test('moveComments: should return early if target is not an object after assignment', t => {
63+
const source = parse(`{
64+
"foo": 1 // comment
65+
}`)
66+
67+
// This should return early and not throw
68+
moveComments(source, 'not an object',
69+
{kind: 'after-value', key: 'foo'},
70+
{kind: 'before', key: 'foo'}
71+
)
72+
73+
// Source should be unchanged
74+
const result = stringify(source, null, 2)
75+
t.true(result.includes('// comment'))
76+
})
77+
78+
test('moveComments: should return early if target is null', t => {
79+
const source = parse(`{
80+
"foo": 1 // comment
81+
}`)
82+
83+
// This should return early and not throw
84+
moveComments(source, null,
85+
{kind: 'after', key: 'foo'},
86+
{kind: 'before', key: 'foo'}
87+
)
88+
89+
// Source should have moved comments (because target defaults to source)
90+
const result = stringify(source, null, 2)
91+
t.true(result.includes('// comment'))
92+
})
93+
94+
test('moveComments: should return early if source does not have the from property', t => {
95+
const source = {foo: 1}
96+
const target = {bar: 2}
97+
98+
// Try to move non-existent comment
99+
moveComments(source, target,
100+
{kind: 'after', key: 'foo'},
101+
{kind: 'before', key: 'bar'}
102+
)
103+
104+
// Target should be unchanged
105+
t.deepEqual(target, {bar: 2})
106+
})
107+
108+
test('moveComments: should move comments and override existing ones when override is true', t => {
109+
const source = parse(`{
110+
"foo": 1 // source comment
111+
}`)
112+
113+
const target = parse(`{
114+
// existing comment
115+
"bar": 2
116+
}`)
117+
118+
moveComments(source, target,
119+
{kind: 'after', key: 'foo'},
120+
{kind: 'before', key: 'bar'},
121+
true // override = true
122+
)
123+
124+
const result = stringify(target, null, 2)
125+
t.true(result.includes('// source comment'))
126+
t.false(result.includes('// existing comment'))
127+
})
128+
129+
test('moveComments: should move comments to empty target location', t => {
130+
const source = parse(`{
131+
"foo": 1 // source comment
132+
}`)
133+
134+
const target = {bar: 2}
135+
136+
moveComments(source, target,
137+
{kind: 'after', key: 'foo'},
138+
{kind: 'before', key: 'bar'}
139+
)
140+
141+
const result = stringify(target, null, 2)
142+
t.true(result.includes('// source comment'))
143+
})
144+
145+
test('moveComments: should append comments when target has existing comments and override is false', t => {
146+
const source = parse(`{
147+
"foo": 1 // source comment
148+
}`)
149+
150+
const target = parse(`{
151+
// existing comment
152+
"bar": 2
153+
}`)
154+
155+
moveComments(source, target,
156+
{kind: 'after', key: 'foo'},
157+
{kind: 'before', key: 'bar'},
158+
false // override = false (default)
159+
)
160+
161+
const result = stringify(target, null, 2)
162+
t.true(result.includes('// existing comment'))
163+
t.true(result.includes('// source comment'))
164+
})
165+
166+
test('moveComments: should handle non-property comments (before-all)', t => {
167+
const source = parse(`// top comment
168+
{
169+
"foo": 1
170+
}`)
171+
172+
const target = {bar: 2}
173+
174+
moveComments(source, target,
175+
{kind: 'before-all'},
176+
{kind: 'after-all'}
177+
)
178+
179+
const result = stringify(target, null, 2)
180+
t.true(result.includes('// top comment'))
181+
})
182+
183+
test('moveComments: should handle property-specific comments with keys', t => {
184+
const source = parse(`{
185+
"foo" /* after-prop */: 1
186+
}`)
187+
188+
moveComments(source, source,
189+
{kind: 'after-prop', key: 'foo'},
190+
{kind: 'before', key: 'foo'}
191+
)
192+
193+
const result = stringify(source, null, 2)
194+
t.true(result.includes('/* after-prop */'))
195+
})
196+
197+
test('moveComments: should handle case where target_comments is null/undefined', t => {
198+
const source = parse(`{
199+
"foo": 1 // comment
200+
}`)
201+
202+
const target = {}
203+
// Manually create a symbol property with null value to test line 304
204+
const targetSymbol = Symbol.for('before:bar')
205+
Object.defineProperty(target, targetSymbol, {
206+
value: null,
207+
writable: true,
208+
configurable: true
209+
})
210+
211+
moveComments(source, target,
212+
{kind: 'after', key: 'foo'},
213+
{kind: 'before', key: 'bar'}
214+
)
215+
216+
// Should not throw error even if target_comments is null
217+
// This tests the condition on line 304: if (target_comments)
218+
t.pass()
219+
})
220+
221+
test('moveComments: should delete source comment after moving', t => {
222+
const source = parse(`{
223+
"foo": 1 // comment to move
224+
}`)
225+
226+
const target = {bar: 2}
227+
228+
moveComments(source, target,
229+
{kind: 'after', key: 'foo'},
230+
{kind: 'before', key: 'bar'}
231+
)
232+
233+
// Source should no longer have the comment
234+
const sourceResult = stringify(source, null, 2)
235+
t.false(sourceResult.includes('// comment to move'))
236+
237+
// Target should have the comment
238+
const targetResult = stringify(target, null, 2)
239+
t.true(targetResult.includes('// comment to move'))
240+
})
241+
242+
test('moveComments: should work with different comment types', t => {
243+
const source = parse(`{
244+
"foo": /* block comment */ 1
245+
}`)
246+
247+
moveComments(source, source,
248+
{kind: 'after-prop', key: 'foo'},
249+
{kind: 'after', key: 'foo'}
250+
)
251+
252+
const result = stringify(source, null, 2)
253+
t.true(result.includes('/* block comment */'))
254+
})
255+
256+
test('moveComments: integration test with multiple moves', t => {
257+
const obj = parse(`{
258+
// before foo
259+
"foo": 1, // after foo
260+
// before bar
261+
"bar": 2 // after bar
262+
}`)
263+
264+
// Move all comments to after-all
265+
moveComments(obj, obj, {kind: 'before', key: 'foo'}, {kind: 'after-all'})
266+
moveComments(obj, obj, {kind: 'after', key: 'foo'}, {kind: 'after-all'})
267+
moveComments(obj, obj, {kind: 'before', key: 'bar'}, {kind: 'after-all'})
268+
moveComments(obj, obj, {kind: 'after', key: 'bar'}, {kind: 'after-all'})
269+
270+
const result = stringify(obj, null, 2)
271+
272+
// Check that comments were moved (should appear at the end with space formatting)
273+
t.true(result.includes('before foo'))
274+
t.true(result.includes('after foo'))
275+
t.true(result.includes('before bar'))
276+
t.true(result.includes('after bar'))
277+
278+
// Verify that the after-all symbol exists and has comments
279+
const afterAllSymbol = Symbol.for('after-all')
280+
t.true(Object.hasOwn(obj, afterAllSymbol))
281+
t.is(obj[afterAllSymbol].length, 4)
282+
})

0 commit comments

Comments
 (0)