瀏覽代碼

Merge branch 'loopback'

Rodja Trappe 2 年之前
父節點
當前提交
1d8928dc0d

+ 4 - 1
nicegui/elements/mixins/value_element.py

@@ -16,10 +16,13 @@ class ValueElement(Element):
         self.set_value(value)
         self._props[self.VALUE_PROP] = self._value_to_model_value(value)
         self._props['loopback'] = self.LOOPBACK
+        self._send_update_on_value_change = True
         self.change_handler = on_value_change
 
         def handle_change(msg: Dict) -> None:
+            self._send_update_on_value_change = self.LOOPBACK
             self.set_value(self._msg_to_value(msg))
+            self._send_update_on_value_change = True
         self.on(f'update:{self.VALUE_PROP}', handle_change, self.EVENT_ARGS, throttle=throttle)
 
     def bind_value_to(self, target_object: Any, target_name: str = 'value', forward: Callable = lambda x: x):
@@ -40,7 +43,7 @@ class ValueElement(Element):
 
     def on_value_change(self, value: Any) -> None:
         self._props[self.VALUE_PROP] = self._value_to_model_value(value)
-        if self.LOOPBACK:
+        if self._send_update_on_value_change:
             self.update()
         args = ValueChangeEventArguments(sender=self, client=self.client, value=self._value_to_event_value(value))
         handle_event(self.change_handler, args)

+ 1 - 1
nicegui/elements/number.py

@@ -29,7 +29,7 @@ class Number(ValueElement):
             self._props['placeholder'] = placeholder
 
     def _msg_to_value(self, msg: Dict) -> Any:
-        return float(msg['args'])
+        return float(msg['args']) if msg['args'] else None
 
     def _value_to_model_value(self, value: Any) -> Any:
         if value is None:

+ 1 - 1
tests/conftest.py

@@ -30,7 +30,7 @@ def capabilities(capabilities: Dict) -> Dict:
 
 @pytest.fixture
 def selenium(selenium: webdriver.Chrome) -> webdriver.Chrome:
-    selenium.implicitly_wait(4)
+    selenium.implicitly_wait(Screen.IMPLICIT_WAIT)
     selenium.set_page_load_timeout(4)
     return selenium
 

+ 18 - 1
tests/screen.py

@@ -17,6 +17,7 @@ IGNORED_CLASSES = ['row', 'column', 'q-card', 'q-field', 'q-field__label', 'q-in
 
 
 class Screen:
+    IMPLICIT_WAIT = 4
     SCREENSHOT_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'screenshots')
     UI_RUN_KWARGS = {'port': PORT, 'show': False, 'reload': False}
 
@@ -75,7 +76,9 @@ class Screen:
             self.selenium.switch_to.window(self.selenium.window_handles[tab_id])
 
     def should_contain(self, text: str) -> None:
-        assert self.selenium.title == text or self.find(text), f'could not find "{text}"'
+        if self.selenium.title == text:
+            return
+        self.find(text)
 
     def should_not_contain(self, text: str) -> None:
         assert self.selenium.title != text
@@ -84,6 +87,15 @@ class Screen:
             self.find(text)
             self.selenium.implicitly_wait(2)
 
+    def should_contain_input(self, text: str) -> None:
+        deadline = time.time() + self.IMPLICIT_WAIT
+        while time.time() < deadline:
+            for input in self.selenium.find_elements(By.TAG_NAME, 'input'):
+                if input.get_attribute('value') == text:
+                    return
+            self.wait(0.1)
+        raise AssertionError(f'Could not find input with value "{text}"')
+
     def click(self, target_text: str) -> WebElement:
         element = self.find(target_text)
         try:
@@ -96,6 +108,11 @@ class Screen:
         action = ActionChains(self.selenium)
         action.move_to_element_with_offset(element, x, y).click().perform()
 
+    def type(self, text: str) -> None:
+        self.selenium.execute_script("window.focus();")
+        self.wait(0.2)
+        self.selenium.switch_to.active_element.send_keys(text)
+
     def find(self, text: str) -> WebElement:
         try:
             query = f'//*[not(self::script) and not(self::style) and contains(text(), "{text}")]'

+ 24 - 0
tests/test_binding.py

@@ -1,4 +1,6 @@
 
+from selenium.webdriver.common.keys import Keys
+
 from nicegui import ui
 
 from .screen import Screen
@@ -59,3 +61,25 @@ def test_ui_select_with_list_of_lists(screen: Screen):
     screen.should_contain('2,2')
     screen.should_not_contain('1,1')
     assert data.selection == [2, 2]
+
+
+def test_binding_to_input(screen: Screen):
+    class Model:
+        text = 'one'
+    data = Model()
+    element = ui.input().bind_value(data, 'text')
+
+    screen.open('/')
+    screen.should_contain_input('one')
+    screen.type(Keys.TAB)
+    screen.type('two')
+    screen.should_contain_input('two')
+    assert data.text == 'two'
+    data.text = 'three'
+    screen.should_contain_input('three')
+    element.set_value('four')
+    screen.should_contain_input('four')
+    assert data.text == 'four'
+    element.value = 'five'
+    screen.should_contain_input('five')
+    assert data.text == 'five'

+ 23 - 0
tests/test_color_input.py

@@ -0,0 +1,23 @@
+from selenium.webdriver.common.keys import Keys
+
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_entering_color(screen: Screen):
+    ui.color_input(label='Color', on_change=lambda e: ui.label(f'content: {e.value}'))
+
+    screen.open('/')
+    screen.type(Keys.TAB)
+    screen.type('#001100')
+    screen.should_contain('content: #001100')
+
+
+def test_picking_color(screen: Screen):
+    ui.color_input(label='Color', on_change=lambda e: ui.label(f'content: {e.value}'))
+
+    screen.open('/')
+    picker = screen.click('colorize')
+    screen.click_at_position(picker, x=40, y=120)
+    screen.should_contain('content: #de8383')

+ 1 - 2
tests/test_dialog.py

@@ -1,6 +1,5 @@
 from typing import List
 
-from selenium.webdriver.common.action_chains import ActionChains
 from selenium.webdriver.common.keys import Keys
 
 from nicegui import ui
@@ -42,6 +41,6 @@ def test_await_dialog(screen: Screen):
     screen.click('Open')
     screen.click('No')
     screen.click('Open')
-    ActionChains(screen.selenium).send_keys(Keys.ESCAPE).perform()
+    screen.type(Keys.ESCAPE)
     screen.wait(0.5)
     assert results == ['Yes', 'No', None]

+ 1 - 4
tests/test_keyboard.py

@@ -1,4 +1,3 @@
-from selenium.webdriver.common.action_chains import ActionChains
 from selenium.webdriver.common.by import By
 
 from nicegui import ui
@@ -13,7 +12,5 @@ def test_keyboard(screen: Screen):
     screen.open('/')
     assert screen.selenium.find_element(By.ID, keyboard.id)
     screen.wait(1.0)
-    ActionChains(screen.selenium) \
-        .send_keys('t') \
-        .perform()
+    screen.type('t')
     screen.should_contain('t, KeyboardAction(keydown=False, keyup=True, repeat=False)')