1111t = TypeScriptComponent({0})
1212"""
1313
14+ basic_app_template = """
15+ from dash import Dash, html, dcc, callback, Input, Output
1416
15- def run_pyright (codefile : str ):
17+ app = Dash()
18+
19+ {0}
20+ app.layout = {1}
21+
22+ @callback(Output("out", "children"), Input("btn", "n_clicks"))
23+ def on_click() -> html.Div:
24+ return {2}
25+ """
26+
27+ valid_layout = """html.Div([
28+ html.H2('Valid'),
29+ 'String in middle',
30+ 123,
31+ 404.4,
32+ dcc.Input(value='', id='in')
33+ ])
34+ """
35+ valid_layout_list = """[
36+ html.H2('Valid'),
37+ 'String in middle',
38+ 123,
39+ 404.4,
40+ dcc.Input(value='', id='in')
41+ ]
42+ """
43+ valid_layout_function = """
44+ def layout() -> html.Div:
45+ return html.Div(["hello layout"])
46+
47+ """
48+
49+ invalid_layout = """html.Div([
50+ {"invalid": "dictionary in children"}
51+ ])
52+ """
53+ # There is not invalid layout for function & list as explicitly typed as Any to avoid special cases.
54+
55+ valid_callback = "html.Div('Valid')"
56+ invalid_callback = "[]"
57+
58+
59+ def run_module (codefile : str , module : str , extra : str = "" ):
1660
1761 cmd = shlex .split (
18- f"pyright { codefile } " ,
62+ f"{ sys . executable } -m { module } { codefile } { extra } " ,
1963 posix = sys .platform != "win32" ,
2064 comments = True ,
2165 )
@@ -32,17 +76,51 @@ def run_pyright(codefile: str):
3276 return out .decode (), err .decode (), proc .poll ()
3377
3478
35- def assert_pyright_output (
36- codefile : str , expected_outputs = tuple (), expected_errors = tuple (), expected_status = 0
79+ def assert_output (
80+ codefile : str ,
81+ code : str ,
82+ expected_outputs = tuple (),
83+ expected_errors = tuple (),
84+ expected_status = 0 ,
85+ module = "pyright" ,
3786):
38- output , error , status = run_pyright (codefile )
87+ output , error , status = run_module (codefile , module )
3988 assert (
4089 status == expected_status
41- ), f"Status: { status } \n Output: { output } \n Error: { error } "
90+ ), f"Status: { status } \n Output: { output } \n Error: { error } \n Code: { code } "
4291 for ex_out in expected_outputs :
43- assert ex_out in output , f"Invalid output:\n { output } "
44- for ex_err in expected_errors :
45- assert ex_err in error
92+ assert ex_out in output , f"Invalid output:\n { output } \n \n Code: { code } "
93+
94+
95+ def format_template_and_save (template , filename , * args ):
96+ formatted = template .format (* args )
97+ with open (filename , "w" ) as f :
98+ f .write (formatted )
99+ return formatted
100+
101+
102+ def expect (status = None , outputs = None , modular = False ):
103+ data = {}
104+ if status is not None :
105+ data ["expected_status" ] = status
106+ if outputs is not None :
107+ data ["expected_outputs" ] = outputs
108+ if modular :
109+ # The expectations are per module.
110+ data ["modular" ] = modular
111+ return data
112+
113+
114+ @pytest .fixture ()
115+ def change_dir ():
116+ original_dir = os .getcwd ()
117+
118+ def change (dirname ):
119+ os .chdir (dirname )
120+
121+ yield change
122+
123+ os .chdir (original_dir )
46124
47125
48126@pytest .mark .parametrize (
@@ -205,7 +283,7 @@ def assert_pyright_output(
205283 "expected_status" : 1 ,
206284 "expected_outputs" : [
207285 'Argument of type "tuple[Literal[1], Literal[2]]" cannot be assigned '
208- 'to parameter "a_tuple" of type "Tuple[SupportsFloat | SupportsInt | SupportsComplex , str] | None'
286+ 'to parameter "a_tuple" of type "Tuple[NumberType , str] | None'
209287 ],
210288 },
211289 ),
@@ -247,9 +325,35 @@ def assert_pyright_output(
247325 ),
248326 ],
249327)
250- def test_component_typing (arguments , assertions , tmp_path ):
328+ def test_typi001_component_typing (arguments , assertions , tmp_path ):
251329 codefile = os .path .join (tmp_path , "code.py" )
252- with open (codefile , "w" ) as f :
253- f .write (component_template .format (arguments ))
330+ code = format_template_and_save (component_template , codefile , arguments )
331+ assert_output (codefile , code , module = "pyright" , ** assertions )
332+
333+
334+ typing_modules = ["pyright" ]
335+
336+ if sys .version_info .minor >= 10 :
337+ typing_modules .append ("mypy" )
254338
255- assert_pyright_output (codefile , ** assertions )
339+
340+ @pytest .mark .parametrize ("typing_module" , typing_modules )
341+ @pytest .mark .parametrize (
342+ "prelayout, layout, callback_return, assertions" ,
343+ [
344+ ("" , valid_layout , valid_callback , expect (status = 0 )),
345+ ("" , valid_layout_list , valid_callback , expect (status = 0 )),
346+ (valid_layout_function , "layout" , valid_callback , expect (status = 0 )),
347+ ("" , valid_layout , invalid_callback , expect (status = 1 )),
348+ ("" , invalid_layout , valid_callback , expect (status = 1 )),
349+ ],
350+ )
351+ def test_typi002_typing_compliance (
352+ typing_module , prelayout , layout , callback_return , assertions , tmp_path , change_dir
353+ ):
354+ codefile = os .path .join (tmp_path , "code.py" )
355+ os .chdir (tmp_path )
356+ code = format_template_and_save (
357+ basic_app_template , codefile , prelayout , layout , callback_return
358+ )
359+ assert_output (codefile , code , module = typing_module , ** assertions )
0 commit comments