Add basic unit tests (#7)
* Unit tests for components, state, and utils
This commit is contained in:
parent
7d91a9db68
commit
29e37350e5
@ -1,5 +1,6 @@
|
||||
"""Welcome to Pynecone! This file outlines the steps to create a basic app."""
|
||||
import pcconfig
|
||||
|
||||
import pynecone as pc
|
||||
|
||||
docs_url = "https://pynecone.io/docs/getting-started/introduction"
|
||||
@ -8,6 +9,7 @@ filename = f"{pcconfig.APP_NAME}/{pcconfig.APP_NAME}.py"
|
||||
|
||||
class State(pc.State):
|
||||
"""The app state."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -21,10 +23,10 @@ def index():
|
||||
href=docs_url,
|
||||
border="0.1em solid",
|
||||
padding="0.5em",
|
||||
border_radius="0.5em"
|
||||
border_radius="0.5em",
|
||||
),
|
||||
),
|
||||
padding="5em"
|
||||
padding="5em",
|
||||
)
|
||||
|
||||
|
||||
@ -32,4 +34,3 @@ def index():
|
||||
app = pc.App(state=State)
|
||||
app.add_page(index)
|
||||
app.compile()
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""The main Pynecone app."""
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
@ -12,7 +13,7 @@ from pynecone.compiler import compiler
|
||||
from pynecone.compiler import utils as compiler_utils
|
||||
from pynecone.components.component import Component, ComponentStyle
|
||||
from pynecone.event import Event
|
||||
from pynecone.middleware import HydrateMiddleware, LoggingMiddleware, Middleware
|
||||
from pynecone.middleware import HydrateMiddleware, Middleware
|
||||
from pynecone.model import Model
|
||||
from pynecone.state import DefaultState, Delta, State, StateManager, StateUpdate
|
||||
|
||||
@ -56,7 +57,6 @@ class App(Base):
|
||||
|
||||
# Add middleware.
|
||||
self.middleware.append(HydrateMiddleware())
|
||||
self.middleware.append(LoggingMiddleware())
|
||||
|
||||
# Set up the state manager.
|
||||
self.state_manager.set(state=self.state)
|
||||
@ -187,7 +187,7 @@ class App(Base):
|
||||
|
||||
from pynecone.var import BaseVar
|
||||
|
||||
parts = path.split("/")
|
||||
parts = os.path.split(path)
|
||||
check = re.compile(r"^\[(.+)\]$")
|
||||
args = []
|
||||
for part in parts:
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""Templates to use in the pynecone compiler."""
|
||||
|
||||
from typing import Callable, Optional, Set
|
||||
from typing import Optional, Set
|
||||
|
||||
from pynecone import constants, utils
|
||||
from pynecone import constants
|
||||
from pynecone.utils import join
|
||||
|
||||
# Template for the Pynecone config file.
|
||||
|
@ -327,7 +327,7 @@ class Component(Base, ABC):
|
||||
for child in self.children:
|
||||
child_code = child.get_custom_code()
|
||||
if child_code != "" and child_code not in code:
|
||||
code += child_code
|
||||
code += "\n" + child_code
|
||||
return code
|
||||
|
||||
def _get_imports(self) -> ImportDict:
|
||||
|
@ -368,13 +368,16 @@ class State(Base, ABC):
|
||||
Returns:
|
||||
The delta for the state.
|
||||
"""
|
||||
delta = {}
|
||||
|
||||
# Return the dirty vars, as well as all computed vars.
|
||||
delta = {
|
||||
self.get_full_name(): {
|
||||
prop: getattr(self, prop)
|
||||
for prop in self.dirty_vars | set(self.computed_vars.keys())
|
||||
}
|
||||
subdelta = {
|
||||
prop: getattr(self, prop)
|
||||
for prop in self.dirty_vars | set(self.computed_vars.keys())
|
||||
}
|
||||
if len(subdelta) > 0:
|
||||
delta[self.get_full_name()] = subdelta
|
||||
|
||||
# Recursively find the substate deltas.
|
||||
for substate in self.dirty_substates:
|
||||
delta.update(self.substates[substate].get_delta())
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from abc import ABC
|
||||
from typing import _GenericAlias # type: ignore
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type, Union
|
||||
|
||||
from plotly.graph_objects import Figure
|
||||
from plotly.io import to_json
|
||||
@ -153,7 +153,7 @@ class Var(ABC):
|
||||
type_ = utils.get_args(self.type_)[0]
|
||||
else:
|
||||
type_ = Any
|
||||
elif utils.is_dataframe(self.type_):
|
||||
elif utils._issubclass(self.type_, Dict) or utils.is_dataframe(self.type_):
|
||||
if isinstance(i, str):
|
||||
i = utils.wrap(i, '"')
|
||||
if isinstance(self.type_, _GenericAlias):
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pynecone-io"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = ""
|
||||
authors = [
|
||||
"Nikhil Rao <nikhil@pynecone.io>",
|
||||
|
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Root directory for tests."""
|
1
tests/compiler/__init__.py
Normal file
1
tests/compiler/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Compiler tests."""
|
72
tests/compiler/test_compiler.py
Normal file
72
tests/compiler/test_compiler.py
Normal file
@ -0,0 +1,72 @@
|
||||
from typing import Set
|
||||
|
||||
import pytest
|
||||
|
||||
from pynecone.compiler import utils
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"lib,fields,output",
|
||||
[
|
||||
("axios", {"axios"}, 'import axios from "axios"'),
|
||||
("axios", {"foo", "bar"}, 'import {bar, foo} from "axios"'),
|
||||
("axios", {"axios", "foo", "bar"}, 'import axios, {bar, foo} from "axios"'),
|
||||
],
|
||||
)
|
||||
def test_compile_import_statement(lib: str, fields: Set[str], output: str):
|
||||
"""Test the compile_import_statement function.
|
||||
|
||||
Args:
|
||||
lib: The library name.
|
||||
fields: The fields to import.
|
||||
output: The expected output.
|
||||
"""
|
||||
assert utils.compile_import_statement(lib, fields) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"import_dict,output",
|
||||
[
|
||||
({}, ""),
|
||||
({"axios": {"axios"}}, 'import axios from "axios"'),
|
||||
({"axios": {"foo", "bar"}}, 'import {bar, foo} from "axios"'),
|
||||
(
|
||||
{"axios": {"axios", "foo", "bar"}, "react": {"react"}},
|
||||
'import axios, {bar, foo} from "axios"\nimport react from "react"',
|
||||
),
|
||||
({"": {"lib1.js", "lib2.js"}}, 'import "lib1.js"\nimport "lib2.js"'),
|
||||
(
|
||||
{"": {"lib1.js", "lib2.js"}, "axios": {"axios"}},
|
||||
'import "lib1.js"\nimport "lib2.js"\nimport axios from "axios"',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_compile_imports(import_dict: utils.ImportDict, output: str):
|
||||
"""Test the compile_imports function.
|
||||
|
||||
Args:
|
||||
import_dict: The import dictionary.
|
||||
output: The expected output.
|
||||
"""
|
||||
assert utils.compile_imports(import_dict) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,value,output",
|
||||
[
|
||||
("foo", "bar", 'const foo = "bar"'),
|
||||
("num", 1, "const num = 1"),
|
||||
("check", False, "const check = false"),
|
||||
("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
|
||||
("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
|
||||
],
|
||||
)
|
||||
def test_compile_constant_declaration(name: str, value: str, output: str):
|
||||
"""Test the compile_constant_declaration function.
|
||||
|
||||
Args:
|
||||
name: The name of the constant.
|
||||
value: The value of the constant.
|
||||
output: The expected output.
|
||||
"""
|
||||
assert utils.compile_constant_declaration(name, value) == output
|
1
tests/components/__init__.py
Normal file
1
tests/components/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Component tests."""
|
152
tests/components/test_component.py
Normal file
152
tests/components/test_component.py
Normal file
@ -0,0 +1,152 @@
|
||||
from typing import Type
|
||||
|
||||
import pytest
|
||||
|
||||
from pynecone.components.component import Component, ImportDict
|
||||
from pynecone.event import EventHandler
|
||||
from pynecone.style import Style
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def component1() -> Type[Component]:
|
||||
"""A test component.
|
||||
|
||||
Returns:
|
||||
A test component.
|
||||
"""
|
||||
|
||||
class TestComponent1(Component):
|
||||
def _get_imports(self) -> ImportDict:
|
||||
return {"react": {"Component"}}
|
||||
|
||||
def _get_custom_code(self) -> str:
|
||||
return "console.log('component1')"
|
||||
|
||||
return TestComponent1
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def component2() -> Type[Component]:
|
||||
"""A test component.
|
||||
|
||||
Returns:
|
||||
A test component.
|
||||
"""
|
||||
|
||||
class TestComponent2(Component):
|
||||
def _get_imports(self) -> ImportDict:
|
||||
return {"react-redux": {"connect"}}
|
||||
|
||||
def _get_custom_code(self) -> str:
|
||||
return "console.log('component2')"
|
||||
|
||||
return TestComponent2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def on_click1() -> EventHandler:
|
||||
"""A sample on click function.
|
||||
|
||||
Returns:
|
||||
A sample on click function.
|
||||
"""
|
||||
|
||||
def on_click1():
|
||||
pass
|
||||
|
||||
return EventHandler(fn=on_click1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def on_click2() -> EventHandler:
|
||||
"""A sample on click function.
|
||||
|
||||
Returns:
|
||||
A sample on click function.
|
||||
"""
|
||||
|
||||
def on_click2():
|
||||
pass
|
||||
|
||||
return EventHandler(fn=on_click2)
|
||||
|
||||
|
||||
def test_set_style_attrs(component1: Type[Component]):
|
||||
"""Test that style attributes are set in the dict.
|
||||
|
||||
Args:
|
||||
component1: A test component.
|
||||
"""
|
||||
component = component1(color="white", text_align="center")
|
||||
assert component.style["color"] == "white"
|
||||
assert component.style["textAlign"] == "center"
|
||||
|
||||
|
||||
def test_create_component(component1: Type[Component]):
|
||||
"""Test that the component is created correctly.
|
||||
|
||||
Args:
|
||||
component1: A test component.
|
||||
"""
|
||||
children = [component1() for _ in range(3)]
|
||||
attrs = {"color": "white", "text_align": "center"}
|
||||
c = component1.create(*children, **attrs)
|
||||
assert isinstance(c, component1)
|
||||
assert c.children == children
|
||||
assert c.style == {"color": "white", "textAlign": "center"}
|
||||
|
||||
|
||||
def test_add_style(component1: Type[Component], component2: Type[Component]):
|
||||
"""Test adding a style to a component.
|
||||
|
||||
Args:
|
||||
component1: A test component.
|
||||
component2: A test component.
|
||||
"""
|
||||
style = {
|
||||
component1: Style({"color": "white"}),
|
||||
component2: Style({"color": "black"}),
|
||||
}
|
||||
c1 = component1().add_style(style) # type: ignore
|
||||
c2 = component2().add_style(style) # type: ignore
|
||||
assert c1.style["color"] == "white"
|
||||
assert c2.style["color"] == "black"
|
||||
|
||||
|
||||
def test_get_imports(component1: Type[Component], component2: Type[Component]):
|
||||
"""Test getting the imports of a component.
|
||||
|
||||
Args:
|
||||
component1: A test component.
|
||||
component2: A test component.
|
||||
"""
|
||||
c1 = component1.create()
|
||||
c2 = component2.create(c1)
|
||||
assert c1.get_imports() == {"react": {"Component"}}
|
||||
assert c2.get_imports() == {"react-redux": {"connect"}, "react": {"Component"}}
|
||||
|
||||
|
||||
def test_get_custom_code(component1: Type[Component], component2: Type[Component]):
|
||||
"""Test getting the custom code of a component.
|
||||
|
||||
Args:
|
||||
component1: A test component.
|
||||
component2: A test component.
|
||||
"""
|
||||
# Check that the code gets compiled correctly.
|
||||
c1 = component1.create()
|
||||
c2 = component2.create()
|
||||
assert c1.get_custom_code() == "console.log('component1')"
|
||||
assert c2.get_custom_code() == "console.log('component2')"
|
||||
|
||||
# Check that nesting components compiles both codes.
|
||||
c1 = component1.create(c2)
|
||||
assert (
|
||||
c1.get_custom_code() == "console.log('component1')\nconsole.log('component2')"
|
||||
)
|
||||
|
||||
# Check that code is not duplicated.
|
||||
c1 = component1.create(c2, c2, c1, c1)
|
||||
assert (
|
||||
c1.get_custom_code() == "console.log('component1')\nconsole.log('component2')"
|
||||
)
|
@ -1,9 +1,9 @@
|
||||
from typing import Dict
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from pynecone.components.tags import CondTag, Tag
|
||||
from pynecone.components import Box
|
||||
from pynecone.components.tags import CondTag, IterTag, Tag
|
||||
from pynecone.event import EventHandler, EventSpec, EventChain
|
||||
from pynecone.var import BaseVar, Var
|
||||
|
||||
@ -13,29 +13,7 @@ def mock_event(arg):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cond,valid",
|
||||
[
|
||||
(BaseVar(name="p", type_=bool), True),
|
||||
(BaseVar(name="p", type_=int), False),
|
||||
(BaseVar(name="p", type_=str), False),
|
||||
],
|
||||
)
|
||||
def test_validate_cond(cond: BaseVar, valid: bool):
|
||||
"""Test that the cond is a boolean.
|
||||
|
||||
Args:
|
||||
cond: The cond to test.
|
||||
valid: The expected validity of the cond.
|
||||
"""
|
||||
if not valid:
|
||||
with pytest.raises(pydantic.ValidationError):
|
||||
Tag(cond=cond)
|
||||
else:
|
||||
assert cond == Tag(cond=cond).cond
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr,formatted",
|
||||
"prop,formatted",
|
||||
[
|
||||
("string", '"string"'),
|
||||
("{wrapped_string}", "{wrapped_string}"),
|
||||
@ -47,31 +25,35 @@ def test_validate_cond(cond: BaseVar, valid: bool):
|
||||
(["a", "b", "c"], '{["a", "b", "c"]}'),
|
||||
({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
|
||||
(
|
||||
EventSpec(handler=EventHandler(fn=mock_event)),
|
||||
EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
|
||||
'{() => Event([E("mock_event", {})])}',
|
||||
),
|
||||
(
|
||||
EventSpec(
|
||||
handler=EventHandler(fn=mock_event),
|
||||
local_args=("e",),
|
||||
args=(("arg", "e.target.value"),),
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(
|
||||
handler=EventHandler(fn=mock_event),
|
||||
local_args=("e",),
|
||||
args=(("arg", "e.target.value"),),
|
||||
)
|
||||
]
|
||||
),
|
||||
'{(e) => Event([E("mock_event", {arg:e.target.value})])}',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_value(attr: Var, formatted: str):
|
||||
"""Test that the formatted value of an attribute is correct.
|
||||
def test_format_value(prop: Var, formatted: str):
|
||||
"""Test that the formatted value of an prop is correct.
|
||||
|
||||
Args:
|
||||
attr: The attribute to test.
|
||||
prop: The prop to test.
|
||||
formatted: The expected formatted value.
|
||||
"""
|
||||
assert Tag.format_attr_value(attr) == formatted
|
||||
assert Tag.format_prop(prop) == formatted
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attrs,formatted",
|
||||
"props,formatted",
|
||||
[
|
||||
({}, ""),
|
||||
({"key": 1}, "key={1}"),
|
||||
@ -79,18 +61,18 @@ def test_format_value(attr: Var, formatted: str):
|
||||
({"key": True, "key2": "value2"}, 'key={true}\nkey2="value2"'),
|
||||
],
|
||||
)
|
||||
def test_format_attrs(attrs: Dict[str, Var], formatted: str):
|
||||
"""Test that the formatted attributes are correct.
|
||||
def test_format_props(props: Dict[str, Var], formatted: str):
|
||||
"""Test that the formatted props are correct.
|
||||
|
||||
Args:
|
||||
attrs: The attributes to test.
|
||||
formatted: The expected formatted attributes.
|
||||
props: The props to test.
|
||||
formatted: The expected formatted props.
|
||||
"""
|
||||
assert Tag(attrs=attrs).format_attrs() == formatted
|
||||
assert Tag(props=props).format_props() == formatted
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr,valid",
|
||||
"prop,valid",
|
||||
[
|
||||
(1, True),
|
||||
(3.14, True),
|
||||
@ -101,23 +83,23 @@ def test_format_attrs(attrs: Dict[str, Var], formatted: str):
|
||||
(None, False),
|
||||
],
|
||||
)
|
||||
def test_is_valid_attr(attr: Var, valid: bool):
|
||||
"""Test that the attribute is valid.
|
||||
def test_is_valid_prop(prop: Var, valid: bool):
|
||||
"""Test that the prop is valid.
|
||||
|
||||
Args:
|
||||
attr: The attribute to test.
|
||||
valid: The expected validity of the attribute.
|
||||
prop: The prop to test.
|
||||
valid: The expected validity of the prop.
|
||||
"""
|
||||
assert Tag.is_valid_attr(attr) == valid
|
||||
assert Tag.is_valid_prop(prop) == valid
|
||||
|
||||
|
||||
def test_add_props():
|
||||
"""Test that the attributes are added."""
|
||||
"""Test that the props are added."""
|
||||
tag = Tag().add_props(key="value", key2=42, invalid=None, invalid2={})
|
||||
assert tag.attrs["key"] == Var.create("value")
|
||||
assert tag.attrs["key2"] == Var.create(42)
|
||||
assert "invalid" not in tag.attrs
|
||||
assert "invalid2" not in tag.attrs
|
||||
assert tag.props["key"] == Var.create("value")
|
||||
assert tag.props["key2"] == Var.create(42)
|
||||
assert "invalid" not in tag.props
|
||||
assert "invalid2" not in tag.props
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -128,25 +110,17 @@ def test_add_props():
|
||||
(Tag(contents="hello"), "<>hello</>"),
|
||||
(Tag(name="h1", contents="hello"), "<h1>hello</h1>"),
|
||||
(
|
||||
Tag(name="box", attrs={"color": "red", "textAlign": "center"}),
|
||||
Tag(name="box", props={"color": "red", "textAlign": "center"}),
|
||||
'<box color="red"\ntextAlign="center"/>',
|
||||
),
|
||||
(
|
||||
Tag(
|
||||
name="box",
|
||||
attrs={"color": "red", "textAlign": "center"},
|
||||
props={"color": "red", "textAlign": "center"},
|
||||
contents="text",
|
||||
),
|
||||
'<box color="red"\ntextAlign="center">text</box>',
|
||||
),
|
||||
(
|
||||
Tag(
|
||||
name="h1",
|
||||
contents="hello",
|
||||
cond=BaseVar(name="logged_in", type_=bool),
|
||||
),
|
||||
'{logged_in ? <h1>hello</h1> : ""}',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_tag(tag: Tag, expected: str):
|
||||
@ -167,15 +141,3 @@ def test_format_cond_tag():
|
||||
cond=BaseVar(name="logged_in", type_=bool),
|
||||
)
|
||||
assert str(tag) == "{logged_in ? <h1>True content</h1> : <h2>False content</h2>}"
|
||||
|
||||
|
||||
def test_format_iter_tag():
|
||||
"""Test that the formatted iter tag is correct."""
|
||||
# def render_todo(todo: str):
|
||||
# return Tag(name="Text", contents=todo)
|
||||
|
||||
# tag = IterTag(
|
||||
# iterable=BaseVar(name="todos", type_=list),
|
||||
# render_fn=render_todo
|
||||
# )
|
||||
# assert str(tag) == '{state.todos.map(render_todo)}'
|
||||
|
@ -1,9 +1,12 @@
|
||||
from typing import Type
|
||||
|
||||
import pytest
|
||||
|
||||
from pynecone.base import Base
|
||||
from pynecone.app import App, DefaultState
|
||||
from pynecone.middleware import HydrateMiddleware
|
||||
from pynecone.components import Box
|
||||
from pynecone.state import State
|
||||
from pynecone.style import Style
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -36,25 +39,32 @@ def about_page():
|
||||
return about
|
||||
|
||||
|
||||
def test_default_state(app: App) -> None:
|
||||
"""Test creating an app with no state.
|
||||
@pytest.fixture()
|
||||
def TestState() -> Type[State]:
|
||||
"""A default state.
|
||||
|
||||
Returns:
|
||||
A default state.
|
||||
"""
|
||||
|
||||
class TestState(State):
|
||||
var: int
|
||||
|
||||
return TestState
|
||||
|
||||
|
||||
def test_default_app(app: App):
|
||||
"""Test creating an app with no args.
|
||||
|
||||
Args:
|
||||
app: The app to test.
|
||||
"""
|
||||
assert app.state() == DefaultState()
|
||||
|
||||
|
||||
def test_default_middleware(app: App) -> None:
|
||||
"""Test creating an app with no middleware.
|
||||
|
||||
Args:
|
||||
app: The app to test.
|
||||
"""
|
||||
assert app.middleware == [HydrateMiddleware()]
|
||||
assert app.style == Style()
|
||||
|
||||
|
||||
def test_add_page_default_route(app: App, index_page, about_page) -> None:
|
||||
def test_add_page_default_route(app: App, index_page, about_page):
|
||||
"""Test adding a page to an app.
|
||||
|
||||
Args:
|
||||
@ -69,7 +79,7 @@ def test_add_page_default_route(app: App, index_page, about_page) -> None:
|
||||
assert set(app.pages.keys()) == {"index", "about"}
|
||||
|
||||
|
||||
def test_add_page_set_route(app: App, index_page) -> None:
|
||||
def test_add_page_set_route(app: App, index_page):
|
||||
"""Test adding a page to an app.
|
||||
|
||||
Args:
|
||||
@ -79,3 +89,62 @@ def test_add_page_set_route(app: App, index_page) -> None:
|
||||
assert app.pages == {}
|
||||
app.add_page(index_page, path="/test")
|
||||
assert set(app.pages.keys()) == {"test"}
|
||||
|
||||
|
||||
def test_add_page_set_route_nested(app: App, index_page):
|
||||
"""Test adding a page to an app.
|
||||
|
||||
Args:
|
||||
app: The app to test.
|
||||
index_page: The index page.
|
||||
"""
|
||||
assert app.pages == {}
|
||||
app.add_page(index_page, path="/test/nested")
|
||||
assert set(app.pages.keys()) == {"test/nested"}
|
||||
|
||||
|
||||
def test_initialize_with_state(TestState: Type[State]):
|
||||
"""Test setting the state of an app.
|
||||
|
||||
Args:
|
||||
DefaultState: The default state.
|
||||
"""
|
||||
app = App(state=TestState)
|
||||
assert app.state == TestState
|
||||
|
||||
# Get a state for a given token.
|
||||
token = "token"
|
||||
state = app.get_state(token)
|
||||
assert isinstance(state, TestState)
|
||||
assert state.var == 0
|
||||
|
||||
|
||||
def test_set_and_get_state(TestState: Type[State]):
|
||||
"""Test setting and getting the state of an app with different tokens.
|
||||
|
||||
Args:
|
||||
DefaultState: The default state.
|
||||
"""
|
||||
app = App(state=TestState)
|
||||
|
||||
# Create two tokens.
|
||||
token1 = "token1"
|
||||
token2 = "token2"
|
||||
|
||||
# Get the default state for each token.
|
||||
state1 = app.get_state(token1)
|
||||
state2 = app.get_state(token2)
|
||||
assert state1.var == 0
|
||||
assert state2.var == 0
|
||||
|
||||
# Set the vars to different values.
|
||||
state1.var = 1
|
||||
state2.var = 2
|
||||
app.set_state(token1, state1)
|
||||
app.set_state(token2, state2)
|
||||
|
||||
# Get the states again and check the values.
|
||||
state1 = app.get_state(token1)
|
||||
state2 = app.get_state(token2)
|
||||
assert state1.var == 1
|
||||
assert state2.var == 2
|
||||
|
47
tests/test_base.py
Normal file
47
tests/test_base.py
Normal file
@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
|
||||
from pynecone.base import Base
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def child() -> Base:
|
||||
"""A child class.
|
||||
|
||||
Returns:
|
||||
A child class.
|
||||
"""
|
||||
|
||||
class Child(Base):
|
||||
num: float
|
||||
key: str
|
||||
|
||||
return Child(num=3.14, key="pi")
|
||||
|
||||
|
||||
def test_get_fields(child):
|
||||
"""Test that the fields are set correctly.
|
||||
|
||||
Args:
|
||||
child: A child class.
|
||||
"""
|
||||
assert child.get_fields().keys() == {"num", "key"}
|
||||
|
||||
|
||||
def test_set(child):
|
||||
"""Test setting fields.
|
||||
|
||||
Args:
|
||||
child: A child class.
|
||||
"""
|
||||
child.set(num=1, key="a")
|
||||
assert child.num == 1
|
||||
assert child.key == "a"
|
||||
|
||||
|
||||
def test_json(child):
|
||||
"""Test converting to json.
|
||||
|
||||
Args:
|
||||
child: A child class.
|
||||
"""
|
||||
assert child.json().replace(" ", "") == '{"num":3.14,"key":"pi"}'
|
@ -1,15 +1,35 @@
|
||||
from datetime import datetime
|
||||
import pytest
|
||||
|
||||
from pynecone.event import Event
|
||||
from pynecone.event import Event, EventHandler, EventSpec
|
||||
|
||||
|
||||
# def test_event_default_date():
|
||||
# """Test that that the default date is set."""
|
||||
# t1 = datetime.now()
|
||||
def test_create_event():
|
||||
"""Test creating an event."""
|
||||
event = Event(token="token", name="state.do_thing", payload={"arg": "value"})
|
||||
assert event.token == "token"
|
||||
assert event.name == "state.do_thing"
|
||||
assert event.payload == {"arg": "value"}
|
||||
|
||||
# e1 = Event(token="t", name="e1")
|
||||
# e2 = Event(token="t", name="e2")
|
||||
|
||||
# t2 = datetime.now()
|
||||
def test_call_event_handler():
|
||||
"""Test that calling an event handler creates an event spec."""
|
||||
|
||||
# assert t1 < e1.date < e2.date < t2
|
||||
def test_fn():
|
||||
pass
|
||||
|
||||
def test_fn_with_args(_, arg1, arg2):
|
||||
pass
|
||||
|
||||
handler = EventHandler(fn=test_fn)
|
||||
event_spec = handler()
|
||||
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == ()
|
||||
|
||||
handler = EventHandler(fn=test_fn_with_args)
|
||||
event_spec = handler("first", "second")
|
||||
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == (("arg1", "first"), ("arg2", "second"))
|
||||
|
@ -643,14 +643,15 @@ async def test_process_event_simple(TestState):
|
||||
test_state = TestState()
|
||||
assert test_state.num1 == 0
|
||||
|
||||
event = Event(token="t", name="set_num1", payload={"num1": 69})
|
||||
delta = await test_state.process(event)
|
||||
event = Event(token="t", name="set_num1", payload={"value": 69})
|
||||
update = await test_state.process(event)
|
||||
|
||||
# The event should update the value.
|
||||
assert test_state.num1 == 69
|
||||
|
||||
# The delta should contain the changes, including computed vars.
|
||||
assert delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}
|
||||
assert update.delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}
|
||||
assert update.events == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -672,10 +673,13 @@ async def test_process_event_substate(TestState, ChildState, GrandchildState):
|
||||
event = Event(
|
||||
token="t", name="child_state.change_both", payload={"value": "hi", "count": 12}
|
||||
)
|
||||
delta = await test_state.process(event)
|
||||
update = await test_state.process(event)
|
||||
assert child_state.value == "HI"
|
||||
assert child_state.count == 24
|
||||
assert delta == {"test_state.child_state": {"value": "HI", "count": 24}}
|
||||
assert update.delta == {
|
||||
"test_state.child_state": {"value": "HI", "count": 24},
|
||||
"test_state": {"sum": 3.14, "upper": ""},
|
||||
}
|
||||
test_state.clean()
|
||||
|
||||
# Test with the granchild state.
|
||||
@ -683,11 +687,14 @@ async def test_process_event_substate(TestState, ChildState, GrandchildState):
|
||||
event = Event(
|
||||
token="t",
|
||||
name="child_state.grandchild_state.set_value2",
|
||||
payload={"value2": "new"},
|
||||
payload={"value": "new"},
|
||||
)
|
||||
delta = await test_state.process(event)
|
||||
update = await test_state.process(event)
|
||||
assert grandchild_state.value2 == "new"
|
||||
assert delta == {"test_state.child_state.grandchild_state": {"value2": "new"}}
|
||||
assert update.delta == {
|
||||
"test_state.child_state.grandchild_state": {"value2": "new"},
|
||||
"test_state": {"sum": 3.14, "upper": ""},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -699,7 +706,7 @@ async def test_process_event_substate_set_parent_state(TestState, ChildState):
|
||||
ChildState: The child state class.
|
||||
"""
|
||||
test_state = TestState()
|
||||
event = Event(token="t", name="child_state.set_num1", payload={"num1": 69})
|
||||
delta = await test_state.process(event)
|
||||
event = Event(token="t", name="child_state.set_num1", payload={"value": 69})
|
||||
update = await test_state.process(event)
|
||||
assert test_state.num1 == 69
|
||||
assert delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}
|
||||
assert update.delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}
|
||||
|
@ -11,6 +11,8 @@ from pynecone import utils
|
||||
("Hello", "hello"),
|
||||
("camelCase", "camel_case"),
|
||||
("camelTwoHumps", "camel_two_humps"),
|
||||
("_start_with_underscore", "_start_with_underscore"),
|
||||
("__start_with_double_underscore", "__start_with_double_underscore"),
|
||||
],
|
||||
)
|
||||
def test_to_snake_case(input: str, output: str):
|
||||
@ -169,3 +171,13 @@ def test_format_cond(condition: str, true_value: str, false_value: str, expected
|
||||
expected: The expected output string.
|
||||
"""
|
||||
assert utils.format_cond(condition, true_value, false_value) == expected
|
||||
|
||||
|
||||
def test_merge_imports():
|
||||
"""Test that imports are merged correctly."""
|
||||
d1 = {"react": {"Component"}}
|
||||
d2 = {"react": {"Component"}, "react-dom": {"render"}}
|
||||
d = utils.merge_imports(d1, d2)
|
||||
assert set(d.keys()) == {"react", "react-dom"}
|
||||
assert set(d["react"]) == {"Component"}
|
||||
assert set(d["react-dom"]) == {"render"}
|
||||
|
169
tests/test_var.py
Normal file
169
tests/test_var.py
Normal file
@ -0,0 +1,169 @@
|
||||
import pytest
|
||||
|
||||
from pynecone.base import Base
|
||||
from pynecone.var import BaseVar, Var
|
||||
|
||||
test_vars = [
|
||||
BaseVar(name="prop1", type_=int),
|
||||
BaseVar(name="key", type_=str),
|
||||
BaseVar(name="value", type_=str, state="state"),
|
||||
BaseVar(name="local", type_=str, state="state", is_local=True),
|
||||
BaseVar(name="local2", type_=str, is_local=True),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def TestObj():
|
||||
class TestObj(Base):
|
||||
foo: int
|
||||
bar: str
|
||||
|
||||
return TestObj
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,expected",
|
||||
zip(
|
||||
test_vars,
|
||||
[
|
||||
"prop1",
|
||||
"key",
|
||||
"state.value",
|
||||
"state.local",
|
||||
"local2",
|
||||
],
|
||||
),
|
||||
)
|
||||
def test_full_name(prop, expected):
|
||||
"""Test that the full name of a var is correct.
|
||||
|
||||
Args:
|
||||
prop: The var to test.
|
||||
expected: The expected full name.
|
||||
"""
|
||||
assert prop.full_name == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,expected",
|
||||
zip(
|
||||
test_vars,
|
||||
["{prop1}", "{key}", "{state.value}", "state.local", "local2"],
|
||||
),
|
||||
)
|
||||
def test_str(prop, expected):
|
||||
"""Test that the string representation of a var is correct.
|
||||
|
||||
Args:
|
||||
prop: The var to test.
|
||||
expected: The expected string representation.
|
||||
"""
|
||||
assert str(prop) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,expected",
|
||||
[
|
||||
(BaseVar(name="p", type_=int), 0),
|
||||
(BaseVar(name="p", type_=float), 0.0),
|
||||
(BaseVar(name="p", type_=str), ""),
|
||||
(BaseVar(name="p", type_=bool), False),
|
||||
(BaseVar(name="p", type_=list), []),
|
||||
(BaseVar(name="p", type_=dict), {}),
|
||||
(BaseVar(name="p", type_=tuple), ()),
|
||||
(BaseVar(name="p", type_=set), set()),
|
||||
],
|
||||
)
|
||||
def test_default_value(prop, expected):
|
||||
"""Test that the default value of a var is correct.
|
||||
|
||||
Args:
|
||||
prop: The var to test.
|
||||
expected: The expected default value.
|
||||
"""
|
||||
assert prop.get_default_value() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,expected",
|
||||
zip(
|
||||
test_vars,
|
||||
[
|
||||
"set_prop1",
|
||||
"set_key",
|
||||
"state.set_value",
|
||||
"state.set_local",
|
||||
"set_local2",
|
||||
],
|
||||
),
|
||||
)
|
||||
def test_get_setter(prop, expected):
|
||||
"""Test that the name of the setter function of a var is correct.
|
||||
|
||||
Args:
|
||||
prop: The var to test.
|
||||
expected: The expected name of the setter function.
|
||||
"""
|
||||
assert prop.get_setter_name() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
(None, None),
|
||||
(1, BaseVar(name="1", type_=int, is_local=True)),
|
||||
("key", BaseVar(name="key", type_=str, is_local=True)),
|
||||
(3.14, BaseVar(name="3.14", type_=float, is_local=True)),
|
||||
([1, 2, 3], BaseVar(name="[1, 2, 3]", type_=list, is_local=True)),
|
||||
(
|
||||
{"a": 1, "b": 2},
|
||||
BaseVar(name='{"a": 1, "b": 2}', type_=dict, is_local=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_create(value, expected):
|
||||
"""Test the var create function.
|
||||
|
||||
Args:
|
||||
value: The value to create a var from.
|
||||
expected: The expected name of the setter function.
|
||||
"""
|
||||
prop = Var.create(value)
|
||||
if value is None:
|
||||
assert prop == expected
|
||||
else:
|
||||
assert prop.equals(expected) # type: ignore
|
||||
|
||||
|
||||
def test_basic_operations(TestObj):
|
||||
"""Test the var operations.
|
||||
|
||||
Args:
|
||||
TestObj: The test object.
|
||||
"""
|
||||
|
||||
def v(value) -> Var:
|
||||
val = Var.create(value)
|
||||
assert val is not None
|
||||
return val
|
||||
|
||||
assert str(v(1) == v(2)) == "{(1 == 2)}"
|
||||
assert str(v(1) != v(2)) == "{(1 != 2)}"
|
||||
assert str(v(1) < v(2)) == "{(1 < 2)}"
|
||||
assert str(v(1) <= v(2)) == "{(1 <= 2)}"
|
||||
assert str(v(1) > v(2)) == "{(1 > 2)}"
|
||||
assert str(v(1) >= v(2)) == "{(1 >= 2)}"
|
||||
assert str(v(1) + v(2)) == "{(1 + 2)}"
|
||||
assert str(v(1) - v(2)) == "{(1 - 2)}"
|
||||
assert str(v(1) * v(2)) == "{(1 * 2)}"
|
||||
assert str(v(1) / v(2)) == "{(1 / 2)}"
|
||||
assert str(v(1) // v(2)) == "{Math.floor(1 / 2)}"
|
||||
assert str(v(1) % v(2)) == "{(1 % 2)}"
|
||||
assert str(v(1) ** v(2)) == "{Math.pow(1 , 2)}"
|
||||
assert str(v(1) & v(2)) == "{(1 && 2)}"
|
||||
assert str(v(1) | v(2)) == "{(1 || 2)}"
|
||||
assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3][0]}"
|
||||
assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}'
|
||||
assert (
|
||||
str(BaseVar(name="foo", state="state", type_=TestObj).bar) == "{state.foo.bar}"
|
||||
)
|
Loading…
Reference in New Issue
Block a user