Skip to content

Commit f276033

Browse files
authored
Merge pull request #84 from headwirecom/bug/58-recursive-add-exception
Bug/58 recursive add exception
2 parents 4cffab0 + 919fdcc commit f276033

File tree

4 files changed

+208
-6
lines changed

4 files changed

+208
-6
lines changed

packages/example/package.json

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@
2424
"scripts": {
2525
"validate": "../../node_modules/.bin/tsc --noEmit",
2626
"build": "echo 'Nothing to do'",
27-
"test": "echo 'Nothing to do'"
27+
"test": "jest --no-cache"
28+
},
29+
"devDependencies": {
30+
"jest": "^24.9.0",
31+
"ts-jest": "^24.2.0"
32+
},
33+
"jest": {
34+
"moduleFileExtensions": [
35+
"ts",
36+
"tsx",
37+
"js"
38+
],
39+
"transform": {
40+
"^.+\\.(ts|tsx)$": "ts-jest"
41+
},
42+
"transformIgnorePatterns": [
43+
"node_modules/(?!(@react-spectrum)/)"
44+
],
45+
"testMatch": [
46+
"**/test/**/*.test.tsx"
47+
],
48+
"testPathIgnorePatterns": [
49+
"/node_modules/",
50+
"/dist/"
51+
]
2852
}
29-
}
53+
}

packages/example/src/App.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
ExampleDispatchProps,
3939
} from './reduxUtil';
4040
import { TextArea } from './TextArea';
41-
import { ReactExampleDescription } from './util';
41+
import { circularReferenceReplacer, ReactExampleDescription } from './util';
4242
import {
4343
getExamplesFromLocalStorage,
4444
setExampleInLocalStorage,
@@ -131,7 +131,7 @@ function App(props: AppProps & { selectedExample: ReactExampleDescription }) {
131131
value={
132132
JSON.stringify(
133133
props.selectedExample.uischema,
134-
null,
134+
circularReferenceReplacer(),
135135
2
136136
) || ''
137137
}
@@ -143,8 +143,11 @@ function App(props: AppProps & { selectedExample: ReactExampleDescription }) {
143143
<Content margin='size-100'>
144144
<TextArea
145145
value={
146-
JSON.stringify(props.selectedExample.schema, null, 2) ||
147-
''
146+
JSON.stringify(
147+
props.selectedExample.schema,
148+
circularReferenceReplacer(),
149+
2
150+
) || ''
148151
}
149152
onChange={updateCurrentSchema}
150153
/>

packages/example/src/util.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,33 @@ export const enhanceExample: (
305305
return e;
306306
}
307307
});
308+
309+
/**
310+
* Replacer to allow circular references in JSON.stringify
311+
*/
312+
export function circularReferenceReplacer() {
313+
const paths = new Map();
314+
const finalPaths = new Map();
315+
let root: any = null;
316+
317+
return function (this: Object, field: string, value: any) {
318+
const p = paths.get(this) + '/' + field;
319+
const isComplex = value === Object(value);
320+
321+
if (isComplex) paths.set(value, p);
322+
323+
const existingPath = finalPaths.get(value) || '';
324+
const path = p.replace(/undefined\/\/?/, '');
325+
let val = existingPath ? { $ref: `#/${existingPath}` } : value;
326+
327+
if (!root) {
328+
root = value;
329+
} else if (val === root) {
330+
val = { $ref: '#/' };
331+
}
332+
333+
if (!existingPath && isComplex) finalPaths.set(value, path);
334+
335+
return val;
336+
};
337+
}

packages/example/test/util.test.tsx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { circularReferenceReplacer } from '../src/util';
2+
3+
const stringify = (obj: Object): any =>
4+
JSON.parse(JSON.stringify(obj, circularReferenceReplacer()));
5+
6+
describe('circularReferenceReplacer', () => {
7+
test('with emtpy values', () => {
8+
// given
9+
const undefObj: any = {
10+
val: undefined,
11+
};
12+
13+
// when
14+
const undefResult = stringify(undefObj);
15+
16+
// expect
17+
expect(undefResult).toEqual({});
18+
19+
// given
20+
const nullObj: any = {
21+
val: null,
22+
};
23+
24+
// when
25+
const nullResult = stringify(nullObj);
26+
27+
// expect
28+
expect(nullResult).toEqual(nullObj);
29+
30+
// given
31+
const emptyStringObj: any = {
32+
val: '',
33+
};
34+
35+
// when
36+
const emptyStringRes = stringify(emptyStringObj);
37+
38+
// expect
39+
expect(emptyStringRes).toEqual(emptyStringObj);
40+
41+
// given
42+
const emptyObj: any = {};
43+
44+
// when
45+
const emptyObjRes = stringify(emptyObj);
46+
47+
// then
48+
expect(emptyObjRes).toEqual(emptyObj);
49+
});
50+
51+
test('with non-circular objects', () => {
52+
// given
53+
const obj: any = {
54+
a: 'foo',
55+
b: 'bar',
56+
nested: {
57+
c: 'baz',
58+
},
59+
};
60+
61+
// when
62+
const result = stringify(obj);
63+
64+
// then
65+
expect(result).toEqual(obj);
66+
});
67+
68+
test('with circular object', () => {
69+
// given
70+
const obj: any = {
71+
a: {
72+
prop: 'foo',
73+
nested: {
74+
prop: 'bar',
75+
},
76+
},
77+
};
78+
79+
obj.a.prop2 = obj.a;
80+
81+
obj.b = {
82+
foo: obj.a,
83+
bar: obj.a.nested,
84+
};
85+
86+
// when
87+
const result = stringify(obj);
88+
89+
// then
90+
expect(result.a).toEqual({
91+
prop: 'foo',
92+
prop2: {
93+
$ref: '#/a',
94+
},
95+
nested: {
96+
prop: 'bar',
97+
},
98+
});
99+
expect(result.b).toEqual({
100+
foo: {
101+
$ref: '#/a',
102+
},
103+
bar: {
104+
$ref: '#/a/nested',
105+
},
106+
});
107+
});
108+
109+
test('with root reference', () => {
110+
// given
111+
const obj: any = {
112+
a: {
113+
prop: 'foo',
114+
},
115+
};
116+
obj.b = obj;
117+
118+
// when
119+
const result = stringify(obj);
120+
121+
// then
122+
expect(result.a).toEqual({
123+
prop: 'foo',
124+
});
125+
expect(result.b).toEqual({
126+
$ref: '#/',
127+
});
128+
});
129+
130+
test('with equal object', () => {
131+
// given
132+
const obj: any = {
133+
a: {
134+
prop: 'foo',
135+
},
136+
};
137+
obj.b = { ...obj.a };
138+
139+
// when
140+
const result = stringify(obj);
141+
142+
// then
143+
expect(result).toEqual(obj);
144+
});
145+
});

0 commit comments

Comments
 (0)