Skip to content

Commit 7b1cb2b

Browse files
committed
Add TypeScript components generation support.
1 parent 0fffb14 commit 7b1cb2b

19 files changed

+20100
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Dash generator test component typescript
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import json
2+
import os as _os
3+
4+
_basepath = _os.path.dirname(__file__)
5+
_filepath = _os.path.abspath(_os.path.join(_basepath, 'package.json'))
6+
with open(_filepath) as f:
7+
package = json.load(f)
8+
9+
package_name = package['name'].replace(' ', '_').replace('-', '_')
10+
__version__ = package['version']
11+
12+
from ._imports_ import * # noqa: F401, F403
13+
from ._imports_ import __all__ # noqa: E402
14+
15+
_js_dist = [
16+
dict(
17+
relative_package_path='dash_generator_test_component_typescript.js',
18+
namespace='dash_generator_test_component_typescript'
19+
)
20+
]
21+
22+
for _component in __all__:
23+
setattr(locals()[_component], '_js_dist', _js_dist)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import child_process from 'child_process';
2+
import path from 'path';
3+
import R from 'ramda';
4+
5+
function getMetadata() {
6+
return new Promise((resolve, reject) => {
7+
const cp = child_process.spawn(
8+
process.execPath,
9+
[
10+
path.resolve(__dirname, '..', '..', 'dash', 'meta-ts.js'),
11+
'""', // ignore pattern
12+
'""', // reserved keywords
13+
path.join(__dirname, 'src', 'components')
14+
],
15+
// To debug `meta-ts.js` using pycharm debugger:
16+
// comment `env` and add `MODULES_PATH=./node_modules`
17+
// in the run config environment variables.
18+
{
19+
env: {MODULES_PATH: path.resolve(__dirname, './node_modules')},
20+
cwd: __dirname
21+
}
22+
);
23+
const meta = [];
24+
const err = [];
25+
cp.stdout.on('data', data => {
26+
meta.push(data);
27+
});
28+
cp.stderr.on('data', data => {
29+
err.push(data);
30+
});
31+
cp.on('close', code => {
32+
if (code === 0) {
33+
resolve(JSON.parse(meta.join('')));
34+
} else {
35+
reject(err.join(''));
36+
}
37+
});
38+
cp.on('error', error => {
39+
console.error(error);
40+
reject(error);
41+
});
42+
});
43+
}
44+
45+
describe('Test Typescript component metadata generation', () => {
46+
let metadata;
47+
48+
beforeAll(async () => {
49+
metadata = await getMetadata();
50+
});
51+
52+
const propPath = (componentName, propName) => [
53+
componentName,
54+
'props',
55+
propName
56+
];
57+
58+
describe.each([
59+
'TypeScriptComponent',
60+
'TypeScriptClassComponent',
61+
'MemoTypeScriptComponent'
62+
])('Test prop type names', componentName => {
63+
const getPropTypeName = (name, data) =>
64+
R.path(propPath(componentName, name).concat('type', 'name'), data);
65+
const testTypeFactory = (name, expectedType) => () =>
66+
expect(getPropTypeName(name, metadata)).toBe(expectedType);
67+
68+
test(
69+
`${componentName} string type`,
70+
testTypeFactory('a_string', 'string')
71+
);
72+
test(
73+
`${componentName} number type`,
74+
testTypeFactory('a_number', 'number')
75+
);
76+
test(
77+
`${componentName} array type`,
78+
testTypeFactory('array_string', 'arrayOf')
79+
);
80+
test(`${componentName} object type`, testTypeFactory('obj', 'shape'));
81+
test(`${componentName} union type`, testTypeFactory('union', 'union'));
82+
test(
83+
`${componentName} enum type`,
84+
testTypeFactory('enum_string', 'enum')
85+
);
86+
test(
87+
`${componentName} children React.Node`,
88+
testTypeFactory('children', 'node')
89+
);
90+
test(
91+
`${componentName} element JSX.Element`,
92+
testTypeFactory('element', 'node')
93+
);
94+
test(
95+
`${componentName} boolean type`,
96+
testTypeFactory('a_bool', 'bool')
97+
);
98+
test(
99+
`${componentName} setProps func`,
100+
testTypeFactory('setProps', 'func')
101+
)
102+
});
103+
104+
describe('Test prop attributes', () => {
105+
test('Required props', () => {
106+
expect(
107+
R.path(
108+
propPath('TypeScriptComponent', 'required_string').concat(
109+
'required'
110+
),
111+
metadata
112+
)
113+
).toBeTruthy();
114+
expect(
115+
R.path(
116+
propPath('TypeScriptComponent', 'a_string').concat(
117+
'required'
118+
),
119+
metadata
120+
)
121+
).toBeFalsy();
122+
});
123+
test('Component prop has comment', () => {
124+
// Comments with `@` in them will not work due the way the typescript compiler handle them with jsdoc.
125+
// To fix & add test once they add back the ability to get raw comments.
126+
expect(
127+
R.path(
128+
propPath('TypeScriptComponent', 'required_string').concat(
129+
'description'
130+
),
131+
metadata
132+
)
133+
).toBe('A string');
134+
});
135+
test('Enum options', () => {
136+
expect(
137+
R.path(
138+
propPath('TypeScriptComponent', 'enum_string').concat(
139+
'type',
140+
'value'
141+
),
142+
metadata
143+
)
144+
).toStrictEqual([
145+
{value: "'one'", computed: false},
146+
{value: "'two'", computed: false}
147+
]);
148+
});
149+
test('Union of number and string', () => {
150+
const propType = R.path(
151+
propPath('TypeScriptComponent', 'union').concat('type'),
152+
metadata
153+
);
154+
expect(propType.value.map(R.prop('name'))).toStrictEqual([
155+
'string',
156+
'number'
157+
]);
158+
});
159+
test('Union of shape and string', () => {
160+
const propType = R.path(
161+
propPath('TypeScriptComponent', 'union_shape').concat(
162+
'type',
163+
'value'
164+
),
165+
metadata
166+
);
167+
const types = propType.map(R.prop('name'));
168+
expect(types).toHaveLength(2);
169+
expect(types).toContainEqual('shape');
170+
expect(types).toContainEqual('string');
171+
});
172+
test('Array of union of shapes and string', () => {
173+
const propType = R.path(
174+
propPath('TypeScriptComponent', 'array_union_shape').concat(
175+
'type'
176+
),
177+
metadata
178+
);
179+
expect(propType.value.name).toBe('union');
180+
expect(propType.value.value.length).toBe(2);
181+
expect(propType.value.value[0].name).toBe('string');
182+
expect(propType.value.value[1].name).toBe('shape');
183+
});
184+
test('Obj properties', () => {
185+
const propType = R.path(
186+
propPath('TypeScriptComponent', 'obj').concat('type', 'value'),
187+
metadata
188+
);
189+
expect(propType.value.name).toBe('any');
190+
expect(propType.label.name).toBe('string');
191+
});
192+
test.each(['TypeScriptComponent', 'TypeScriptClassComponent'])(
193+
'Default props',
194+
(componentName: string) => {
195+
const defaultValue = (field: string) =>
196+
R.path(
197+
propPath(componentName, field).concat(
198+
'defaultValue',
199+
'value'
200+
),
201+
metadata
202+
);
203+
expect(defaultValue('string_default')).toBe("'default'");
204+
expect(defaultValue('number_default')).toBe('42');
205+
expect(defaultValue('bool_default')).toBe(
206+
componentName === 'TypeScriptComponent' ? 'true' : 'false'
207+
);
208+
expect(defaultValue('null_default')).toBe('null');
209+
expect(eval(`(${defaultValue('obj_default')})`)).toStrictEqual({
210+
a: 'a',
211+
b: 3
212+
});
213+
}
214+
);
215+
});
216+
217+
describe('Test component comments', () => {
218+
test('Component has docstring', () => {
219+
expect(
220+
R.path(['TypeScriptComponent', 'description'], metadata)
221+
).toBe('Component docstring');
222+
});
223+
test.each(['TypeScriptClassComponent', 'MemoTypeScriptComponent'])(
224+
'Component with `@` in docstring',
225+
componentName => {
226+
expect(R.path([componentName, 'description'], metadata)).toBe(
227+
'Description\n' +
228+
'Example:\n```\n' +
229+
'@app.callback("clicks@btn")\n' +
230+
'def on_click(*args):\n' +
231+
' return 1\n' +
232+
'```'
233+
);
234+
}
235+
);
236+
});
237+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
coverageProvider: "v8",
3+
transform: {
4+
'^.+\\.(ts|tsx)?$': 'ts-jest',
5+
}
6+
};

0 commit comments

Comments
 (0)