Skip to content

Commit 8d520b1

Browse files
committed
Add TypeScript components generation support.
1 parent 58bec06 commit 8d520b1

19 files changed

+20092
-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,233 @@
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+
});
99+
100+
describe('Test prop attributes', () => {
101+
test('Required props', () => {
102+
expect(
103+
R.path(
104+
propPath('TypeScriptComponent', 'required_string').concat(
105+
'required'
106+
),
107+
metadata
108+
)
109+
).toBeTruthy();
110+
expect(
111+
R.path(
112+
propPath('TypeScriptComponent', 'a_string').concat(
113+
'required'
114+
),
115+
metadata
116+
)
117+
).toBeFalsy();
118+
});
119+
test('Component prop has comment', () => {
120+
// Comments with `@` in them will not work due the way the typescript compiler handle them with jsdoc.
121+
// To fix & add test once they add back the ability to get raw comments.
122+
expect(
123+
R.path(
124+
propPath('TypeScriptComponent', 'required_string').concat(
125+
'description'
126+
),
127+
metadata
128+
)
129+
).toBe('A string');
130+
});
131+
test('Enum options', () => {
132+
expect(
133+
R.path(
134+
propPath('TypeScriptComponent', 'enum_string').concat(
135+
'type',
136+
'value'
137+
),
138+
metadata
139+
)
140+
).toStrictEqual([
141+
{value: "'one'", computed: false},
142+
{value: "'two'", computed: false}
143+
]);
144+
});
145+
test('Union of number and string', () => {
146+
const propType = R.path(
147+
propPath('TypeScriptComponent', 'union').concat('type'),
148+
metadata
149+
);
150+
expect(propType.value.map(R.prop('name'))).toStrictEqual([
151+
'string',
152+
'number'
153+
]);
154+
});
155+
test('Union of shape and string', () => {
156+
const propType = R.path(
157+
propPath('TypeScriptComponent', 'union_shape').concat(
158+
'type',
159+
'value'
160+
),
161+
metadata
162+
);
163+
const types = propType.map(R.prop('name'));
164+
expect(types).toHaveLength(2);
165+
expect(types).toContainEqual('shape');
166+
expect(types).toContainEqual('string');
167+
});
168+
test('Array of union of shapes and string', () => {
169+
const propType = R.path(
170+
propPath('TypeScriptComponent', 'array_union_shape').concat(
171+
'type'
172+
),
173+
metadata
174+
);
175+
expect(propType.value.name).toBe('union');
176+
expect(propType.value.value.length).toBe(2);
177+
expect(propType.value.value[0].name).toBe('string');
178+
expect(propType.value.value[1].name).toBe('shape');
179+
});
180+
test('Obj properties', () => {
181+
const propType = R.path(
182+
propPath('TypeScriptComponent', 'obj').concat('type', 'value'),
183+
metadata
184+
);
185+
expect(propType.value.name).toBe('any');
186+
expect(propType.label.name).toBe('string');
187+
});
188+
test.each(['TypeScriptComponent', 'TypeScriptClassComponent'])(
189+
'Default props',
190+
(componentName: string) => {
191+
const defaultValue = (field: string) =>
192+
R.path(
193+
propPath(componentName, field).concat(
194+
'defaultValue',
195+
'value'
196+
),
197+
metadata
198+
);
199+
expect(defaultValue('string_default')).toBe("'default'");
200+
expect(defaultValue('number_default')).toBe('42');
201+
expect(defaultValue('bool_default')).toBe(
202+
componentName === 'TypeScriptComponent' ? 'true' : 'false'
203+
);
204+
expect(defaultValue('null_default')).toBe('null');
205+
expect(eval(`(${defaultValue('obj_default')})`)).toStrictEqual({
206+
a: 'a',
207+
b: 3
208+
});
209+
}
210+
);
211+
});
212+
213+
describe('Test component comments', () => {
214+
test('Component has docstring', () => {
215+
expect(
216+
R.path(['TypeScriptComponent', 'description'], metadata)
217+
).toBe('Component docstring');
218+
});
219+
test.each(['TypeScriptClassComponent', 'MemoTypeScriptComponent'])(
220+
'Component with `@` in docstring',
221+
componentName => {
222+
expect(R.path([componentName, 'description'], metadata)).toBe(
223+
'Description\n' +
224+
'Example:\n```\n' +
225+
'@app.callback("clicks@btn")\n' +
226+
'def on_click(*args):\n' +
227+
' return 1\n' +
228+
'```'
229+
);
230+
}
231+
);
232+
});
233+
});
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)