test.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. const test = require('tap').test
  2. const fss = require('./')
  3. const clone = require('clone')
  4. const s = JSON.stringify
  5. const stream = require('stream')
  6. test('circular reference to root', function (assert) {
  7. const fixture = { name: 'Tywin Lannister' }
  8. fixture.circle = fixture
  9. const expected = s({ name: 'Tywin Lannister', circle: '[Circular]' })
  10. const actual = fss(fixture)
  11. assert.equal(actual, expected)
  12. assert.end()
  13. })
  14. test('circular getter reference to root', function (assert) {
  15. const fixture = {
  16. name: 'Tywin Lannister',
  17. get circle () {
  18. return fixture
  19. }
  20. }
  21. const expected = s({ name: 'Tywin Lannister', circle: '[Circular]' })
  22. const actual = fss(fixture)
  23. assert.equal(actual, expected)
  24. assert.end()
  25. })
  26. test('nested circular reference to root', function (assert) {
  27. const fixture = { name: 'Tywin Lannister' }
  28. fixture.id = { circle: fixture }
  29. const expected = s({ name: 'Tywin Lannister', id: { circle: '[Circular]' } })
  30. const actual = fss(fixture)
  31. assert.equal(actual, expected)
  32. assert.end()
  33. })
  34. test('child circular reference', function (assert) {
  35. const fixture = {
  36. name: 'Tywin Lannister',
  37. child: { name: 'Tyrion Lannister' }
  38. }
  39. fixture.child.dinklage = fixture.child
  40. const expected = s({
  41. name: 'Tywin Lannister',
  42. child: {
  43. name: 'Tyrion Lannister',
  44. dinklage: '[Circular]'
  45. }
  46. })
  47. const actual = fss(fixture)
  48. assert.equal(actual, expected)
  49. assert.end()
  50. })
  51. test('nested child circular reference', function (assert) {
  52. const fixture = {
  53. name: 'Tywin Lannister',
  54. child: { name: 'Tyrion Lannister' }
  55. }
  56. fixture.child.actor = { dinklage: fixture.child }
  57. const expected = s({
  58. name: 'Tywin Lannister',
  59. child: {
  60. name: 'Tyrion Lannister',
  61. actor: { dinklage: '[Circular]' }
  62. }
  63. })
  64. const actual = fss(fixture)
  65. assert.equal(actual, expected)
  66. assert.end()
  67. })
  68. test('circular objects in an array', function (assert) {
  69. const fixture = { name: 'Tywin Lannister' }
  70. fixture.hand = [fixture, fixture]
  71. const expected = s({
  72. name: 'Tywin Lannister',
  73. hand: ['[Circular]', '[Circular]']
  74. })
  75. const actual = fss(fixture)
  76. assert.equal(actual, expected)
  77. assert.end()
  78. })
  79. test('nested circular references in an array', function (assert) {
  80. const fixture = {
  81. name: 'Tywin Lannister',
  82. offspring: [{ name: 'Tyrion Lannister' }, { name: 'Cersei Lannister' }]
  83. }
  84. fixture.offspring[0].dinklage = fixture.offspring[0]
  85. fixture.offspring[1].headey = fixture.offspring[1]
  86. const expected = s({
  87. name: 'Tywin Lannister',
  88. offspring: [
  89. { name: 'Tyrion Lannister', dinklage: '[Circular]' },
  90. { name: 'Cersei Lannister', headey: '[Circular]' }
  91. ]
  92. })
  93. const actual = fss(fixture)
  94. assert.equal(actual, expected)
  95. assert.end()
  96. })
  97. test('circular arrays', function (assert) {
  98. const fixture = []
  99. fixture.push(fixture, fixture)
  100. const expected = s(['[Circular]', '[Circular]'])
  101. const actual = fss(fixture)
  102. assert.equal(actual, expected)
  103. assert.end()
  104. })
  105. test('nested circular arrays', function (assert) {
  106. const fixture = []
  107. fixture.push(
  108. { name: 'Jon Snow', bastards: fixture },
  109. { name: 'Ramsay Bolton', bastards: fixture }
  110. )
  111. const expected = s([
  112. { name: 'Jon Snow', bastards: '[Circular]' },
  113. { name: 'Ramsay Bolton', bastards: '[Circular]' }
  114. ])
  115. const actual = fss(fixture)
  116. assert.equal(actual, expected)
  117. assert.end()
  118. })
  119. test('repeated non-circular references in objects', function (assert) {
  120. const daenerys = { name: 'Daenerys Targaryen' }
  121. const fixture = {
  122. motherOfDragons: daenerys,
  123. queenOfMeereen: daenerys
  124. }
  125. const expected = s(fixture)
  126. const actual = fss(fixture)
  127. assert.equal(actual, expected)
  128. assert.end()
  129. })
  130. test('repeated non-circular references in arrays', function (assert) {
  131. const daenerys = { name: 'Daenerys Targaryen' }
  132. const fixture = [daenerys, daenerys]
  133. const expected = s(fixture)
  134. const actual = fss(fixture)
  135. assert.equal(actual, expected)
  136. assert.end()
  137. })
  138. test('double child circular reference', function (assert) {
  139. // create circular reference
  140. const child = { name: 'Tyrion Lannister' }
  141. child.dinklage = child
  142. // include it twice in the fixture
  143. const fixture = { name: 'Tywin Lannister', childA: child, childB: child }
  144. const cloned = clone(fixture)
  145. const expected = s({
  146. name: 'Tywin Lannister',
  147. childA: {
  148. name: 'Tyrion Lannister',
  149. dinklage: '[Circular]'
  150. },
  151. childB: {
  152. name: 'Tyrion Lannister',
  153. dinklage: '[Circular]'
  154. }
  155. })
  156. const actual = fss(fixture)
  157. assert.equal(actual, expected)
  158. // check if the fixture has not been modified
  159. assert.same(fixture, cloned)
  160. assert.end()
  161. })
  162. test('child circular reference with toJSON', function (assert) {
  163. // Create a test object that has an overridden `toJSON` property
  164. TestObject.prototype.toJSON = function () {
  165. return { special: 'case' }
  166. }
  167. function TestObject (content) {}
  168. // Creating a simple circular object structure
  169. const parentObject = {}
  170. parentObject.childObject = new TestObject()
  171. parentObject.childObject.parentObject = parentObject
  172. // Creating a simple circular object structure
  173. const otherParentObject = new TestObject()
  174. otherParentObject.otherChildObject = {}
  175. otherParentObject.otherChildObject.otherParentObject = otherParentObject
  176. // Making sure our original tests work
  177. assert.same(parentObject.childObject.parentObject, parentObject)
  178. assert.same(
  179. otherParentObject.otherChildObject.otherParentObject,
  180. otherParentObject
  181. )
  182. // Should both be idempotent
  183. assert.equal(fss(parentObject), '{"childObject":{"special":"case"}}')
  184. assert.equal(fss(otherParentObject), '{"special":"case"}')
  185. // Therefore the following assertion should be `true`
  186. assert.same(parentObject.childObject.parentObject, parentObject)
  187. assert.same(
  188. otherParentObject.otherChildObject.otherParentObject,
  189. otherParentObject
  190. )
  191. assert.end()
  192. })
  193. test('null object', function (assert) {
  194. const expected = s(null)
  195. const actual = fss(null)
  196. assert.equal(actual, expected)
  197. assert.end()
  198. })
  199. test('null property', function (assert) {
  200. const expected = s({ f: null })
  201. const actual = fss({ f: null })
  202. assert.equal(actual, expected)
  203. assert.end()
  204. })
  205. test('nested child circular reference in toJSON', function (assert) {
  206. const circle = { some: 'data' }
  207. circle.circle = circle
  208. const a = {
  209. b: {
  210. toJSON: function () {
  211. a.b = 2
  212. return '[Redacted]'
  213. }
  214. },
  215. baz: {
  216. circle,
  217. toJSON: function () {
  218. a.baz = circle
  219. return '[Redacted]'
  220. }
  221. }
  222. }
  223. const o = {
  224. a,
  225. bar: a
  226. }
  227. const expected = s({
  228. a: {
  229. b: '[Redacted]',
  230. baz: '[Redacted]'
  231. },
  232. bar: {
  233. b: 2,
  234. baz: {
  235. some: 'data',
  236. circle: '[Circular]'
  237. }
  238. }
  239. })
  240. const actual = fss(o)
  241. assert.equal(actual, expected)
  242. assert.end()
  243. })
  244. test('circular getters are restored when stringified', function (assert) {
  245. const fixture = {
  246. name: 'Tywin Lannister',
  247. get circle () {
  248. return fixture
  249. }
  250. }
  251. fss(fixture)
  252. assert.equal(fixture.circle, fixture)
  253. assert.end()
  254. })
  255. test('non-configurable circular getters use a replacer instead of markers', function (assert) {
  256. const fixture = { name: 'Tywin Lannister' }
  257. Object.defineProperty(fixture, 'circle', {
  258. configurable: false,
  259. get: function () {
  260. return fixture
  261. },
  262. enumerable: true
  263. })
  264. fss(fixture)
  265. assert.equal(fixture.circle, fixture)
  266. assert.end()
  267. })
  268. test('getter child circular reference are replaced instead of marked', function (assert) {
  269. const fixture = {
  270. name: 'Tywin Lannister',
  271. child: {
  272. name: 'Tyrion Lannister',
  273. get dinklage () {
  274. return fixture.child
  275. }
  276. },
  277. get self () {
  278. return fixture
  279. }
  280. }
  281. const expected = s({
  282. name: 'Tywin Lannister',
  283. child: {
  284. name: 'Tyrion Lannister',
  285. dinklage: '[Circular]'
  286. },
  287. self: '[Circular]'
  288. })
  289. const actual = fss(fixture)
  290. assert.equal(actual, expected)
  291. assert.end()
  292. })
  293. test('Proxy throwing', function (assert) {
  294. assert.plan(1)
  295. const s = new stream.PassThrough()
  296. s.resume()
  297. s.write('', () => {
  298. assert.end()
  299. })
  300. const actual = fss({ s, p: new Proxy({}, { get () { throw new Error('kaboom') } }) })
  301. assert.equal(actual, '"[unable to serialize, circular reference is too complex to analyze]"')
  302. })
  303. test('depthLimit option - will replace deep objects', function (assert) {
  304. const fixture = {
  305. name: 'Tywin Lannister',
  306. child: {
  307. name: 'Tyrion Lannister'
  308. },
  309. get self () {
  310. return fixture
  311. }
  312. }
  313. const expected = s({
  314. name: 'Tywin Lannister',
  315. child: '[...]',
  316. self: '[Circular]'
  317. })
  318. const actual = fss(fixture, undefined, undefined, {
  319. depthLimit: 1,
  320. edgesLimit: 1
  321. })
  322. assert.equal(actual, expected)
  323. assert.end()
  324. })
  325. test('edgesLimit option - will replace deep objects', function (assert) {
  326. const fixture = {
  327. object: {
  328. 1: { test: 'test' },
  329. 2: { test: 'test' },
  330. 3: { test: 'test' },
  331. 4: { test: 'test' }
  332. },
  333. array: [
  334. { test: 'test' },
  335. { test: 'test' },
  336. { test: 'test' },
  337. { test: 'test' }
  338. ],
  339. get self () {
  340. return fixture
  341. }
  342. }
  343. const expected = s({
  344. object: {
  345. 1: { test: 'test' },
  346. 2: { test: 'test' },
  347. 3: { test: 'test' },
  348. 4: '[...]'
  349. },
  350. array: [{ test: 'test' }, { test: 'test' }, { test: 'test' }, '[...]'],
  351. self: '[Circular]'
  352. })
  353. const actual = fss(fixture, undefined, undefined, {
  354. depthLimit: 3,
  355. edgesLimit: 3
  356. })
  357. assert.equal(actual, expected)
  358. assert.end()
  359. })