Sfoglia il codice sorgente

Add basic unit tests (#7)

* Unit tests for components, state, and utils
Nikhil Rao 2 anni fa
parent
commit
29e37350e5

+ 4 - 3
pynecone/.templates/app/tutorial.py

@@ -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()
-

+ 3 - 3
pynecone/app.py

@@ -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:

+ 2 - 2
pynecone/compiler/templates.py

@@ -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.

+ 1 - 1
pynecone/components/component.py

@@ -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:

+ 8 - 5
pynecone/state.py

@@ -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())

+ 2 - 2
pynecone/var.py

@@ -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 - 1
pyproject.toml

@@ -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 - 0
tests/__init__.py

@@ -0,0 +1 @@
+"""Root directory for tests."""

+ 1 - 0
tests/compiler/__init__.py

@@ -0,0 +1 @@
+"""Compiler tests."""

+ 72 - 0
tests/compiler/test_compiler.py

@@ -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 - 0
tests/components/__init__.py

@@ -0,0 +1 @@
+"""Component tests."""

+ 152 - 0
tests/components/test_component.py

@@ -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')"
+    )

+ 35 - 73
tests/components/test_tag.py

@@ -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)}'

+ 79 - 10
tests/test_app.py

@@ -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.
 
-    Args:
-        app: The app to test.
+    Returns:
+        A default state.
     """
-    assert app.state() == DefaultState()
+
+    class TestState(State):
+        var: int
+
+    return TestState
 
 
-def test_default_middleware(app: App) -> None:
-    """Test creating an app with no middleware.
+def test_default_app(app: App):
+    """Test creating an app with no args.
 
     Args:
         app: The app to test.
     """
+    assert app.state() == DefaultState()
     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 - 0
tests/test_base.py

@@ -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"}'

+ 29 - 9
tests/test_event.py

@@ -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"))

+ 0 - 0
tests/test_pynecone.py


+ 18 - 11
tests/test_state.py

@@ -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": ""}}

+ 12 - 0
tests/test_utils.py

@@ -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 - 0
tests/test_var.py

@@ -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}"
+    )