Skip to content

add basic test of IDOM #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ jobs:
run: |
npm install -g npm@latest
npm --version
nox -s test
nox -s test -- --headless
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the double dash (-- --headless)intentional here?

Copy link
Author

@rmorshea rmorshea Jul 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was intentional - it delimits CLI args for nox vs args that end up a Session.posargs in noxfile.py (see here)

11 changes: 7 additions & 4 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ def format(session: Session) -> None:
def test(session: Session) -> None:
"""Run the complete test suite"""
session.install("--upgrade", "pip", "setuptools", "wheel")
session.notify("test_suite")
session.notify("test_suite", posargs=session.posargs)
session.notify("test_style")


@nox.session
def test_suite(session: Session) -> None:
"""Run the Python-based test suite"""
session.env["IDOM_DEBUG_MODE"] = "1"
install_requirements_file(session, "test-env")
session.install(".[all]")
session.chdir("tests")
session.run("figure-it-out")

session.chdir(HERE / "tests")
session.env["IDOM_DEBUG_MODE"] = "1"
session.env["SELENIUM_HEADLESS"] = str(int("--headless" in session.posargs))
session.run("python", "manage.py", "build_js")
session.run("python", "manage.py", "test")


@nox.session
Expand Down
5 changes: 5 additions & 0 deletions requirements/test-env.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
django
selenium

# required due issue with channels:
# https://github.com/django/channels/issues/1639#issuecomment-817994671
twisted<21
32 changes: 18 additions & 14 deletions src/django_idom/websocket_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from typing import Any

from channels.generic.websocket import AsyncJsonWebsocketConsumer
from idom.core.component import ComponentConstructor
from idom.core.dispatcher import dispatch_single_view
from idom.core.layout import Layout
from idom.core.layout import Layout, LayoutEvent
from idom.core.proto import ComponentConstructor


class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer):
Expand All @@ -19,22 +19,26 @@ def __init__(

async def connect(self) -> None:
await super().connect()
self._idom_recv_queue = recv_queue = asyncio.Queue()
self._idom_dispatcher_future = asyncio.ensure_future(
dispatch_single_view(
Layout(self._idom_component_constructor()),
self.send_json,
recv_queue.get,
)
)
self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop())

async def close(self, *args: Any, **kwargs: Any) -> None:
async def disconnect(self, code: int) -> None:
if self._idom_dispatcher_future.done():
await self._idom_dispatcher_future
else:
self._idom_dispatcher_future.cancel()
await asyncio.wait([self._idom_dispatcher_future])
await super().close(*args, **kwargs)
await super().disconnect(code)

async def receive_json(self, content: Any, **kwargs: Any) -> None:
await self._idom_recv_queue.put(content)
await self._idom_recv_queue.put(LayoutEvent(**content))

async def _run_dispatch_loop(self):
self._idom_recv_queue = recv_queue = asyncio.Queue()
try:
await dispatch_single_view(
Layout(self._idom_component_constructor()),
self.send_json,
recv_queue.get,
)
except Exception:
await self.close()
raise
11 changes: 11 additions & 0 deletions tests/js/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,15 @@ export default {
),
}),
],
onwarn: function (warning) {
// Skip certain warnings

// should intercept ... but doesn't in some rollup versions
if (warning.code === "THIS_IS_UNDEFINED") {
return;
}

// console.warn everything else
console.warn(warning.message);
},
};
4 changes: 2 additions & 2 deletions tests/test_app/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from django_idom import IdomAsyncWebSocketConsumer # noqa: E402

from .views import HelloWorld
from .views import Root


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings")
Expand All @@ -29,7 +29,7 @@
{
"http": http_asgi_app,
"websocket": URLRouter(
[url("", IdomAsyncWebSocketConsumer.as_asgi(component=HelloWorld))]
[url("", IdomAsyncWebSocketConsumer.as_asgi(component=Root))]
),
}
)
7 changes: 5 additions & 2 deletions tests/test_app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
"TEST": {
"NAME": os.path.join(BASE_DIR, "db_test.sqlite3"),
},
},
}

# Password validation
Expand Down
38 changes: 35 additions & 3 deletions tests/test_app/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
from django.test import TestCase
import os

from channels.testing import ChannelsLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

class Temp(TestCase):
pass

class TestIdomCapabilities(ChannelsLiveServerTestCase):
def setUp(self):
self.driver = make_driver(5, 5)
self.driver.get(self.live_server_url)

def tearDown(self) -> None:
self.driver.quit()

def wait_until(self, condition, timeout=5):
WebDriverWait(self.driver, timeout).until(lambda driver: condition())

def test_hello_world(self):
self.driver.find_element_by_id("hello-world")

def test_counter(self):
button = self.driver.find_element_by_id("counter-inc")
count = self.driver.find_element_by_id("counter-num")

for i in range(5):
self.wait_until(lambda: count.get_attribute("data-count") == str(i))
button.click()


def make_driver(page_load_timeout, implicit_wait_timeout):
options = webdriver.ChromeOptions()
options.headless = bool(int(os.environ.get("SELENIUM_HEADLESS", 0)))
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(page_load_timeout)
driver.implicitly_wait(implicit_wait_timeout)
return driver
20 changes: 20 additions & 0 deletions tests/test_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ def base_template(request):
return HttpResponse(template.render(context, request))


@idom.component
def Root():
return idom.html.div(HelloWorld(), Counter())


@idom.component
def HelloWorld():
return idom.html.h1({"id": "hello-world"}, "Hello World!")


@idom.component
def Counter():
count, set_count = idom.hooks.use_state(0)
return idom.html.div(
idom.html.button(
{"id": "counter-inc", "onClick": lambda event: set_count(count + 1)},
"Click me!",
),
idom.html.p(
{"id": "counter-num", "data-count": count},
f"Current count is: {count}",
),
)