Skip to content

Commit

Permalink
Merge branch 'main' into html-elements
Browse files Browse the repository at this point in the history
  • Loading branch information
falkoschindler committed Oct 24, 2024
2 parents e5cf645 + 3b9293d commit a996c0a
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ If you would like to support this project and have your avatar or company logo a

<p align="center">
<a href="https://github.com/lechler-gmbh"><img src="https://github.com/lechler-gmbh.png" width="50px" alt="Lechler GmbH" /></a>
<a href="https://github.com/daviborges666"><img src="https://github.com/daviborges666.png" width="50px"alt="daviborges666" /></a>
</p>

Consider this low-barrier form of contribution yourself.
Expand Down
5 changes: 2 additions & 3 deletions examples/single_page_app/router_frame.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
export default {
template: "<div><slot></slot></div>",
mounted() {
const initial_path = window.location.pathname;
window.addEventListener("popstate", (event) => {
if (event.state?.page) {
this.$emit("open", event.state.page);
}
this.$emit("open", event.state?.page || initial_path);
});
const connectInterval = setInterval(async () => {
if (window.socket.id === undefined) return;
Expand Down
2 changes: 2 additions & 0 deletions nicegui/air.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def _handle_handshake(data: Dict[str, Any]) -> bool:
return False
client = Client.instances[client_id]
client.environ = data['environ']
if data.get('old_tab_id'):
core.app.storage.copy_tab(data['old_tab_id'], data['tab_id'])
client.tab_id = data['tab_id']
client.on_air = True
client.handle_handshake()
Expand Down
21 changes: 16 additions & 5 deletions nicegui/elements/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ def __init__(self,
self._props['node-key'] = node_key
self._props['label-key'] = label_key
self._props['children-key'] = children_key
self._props['selected'] = None
self._props['expanded'] = []
self._props['ticked'] = []
if tick_strategy is not None:
self._props['tick-strategy'] = tick_strategy
if on_select:
self._props['selected'] = None
if on_expand:
self._props['expanded'] = []
if on_tick or tick_strategy:
self._props['ticked'] = []
self._props['tick-strategy'] = tick_strategy or 'leaf'
self._select_handlers = [on_select] if on_select else []
self._expand_handlers = [on_expand] if on_expand else []
self._tick_handlers = [on_tick] if on_tick else []
Expand Down Expand Up @@ -79,6 +81,7 @@ def handle_ticked(e: GenericEventArguments) -> None:

def on_select(self, callback: Handler[ValueChangeEventArguments]) -> Self:
"""Add a callback to be invoked when the selection changes."""
self._props.setdefault('selected', None)
self._select_handlers.append(callback)
return self

Expand All @@ -87,6 +90,7 @@ def select(self, node_key: Optional[str]) -> Self:
:param node_key: node key to select
"""
self._props.setdefault('selected', None)
if self._props['selected'] != node_key:
self._props['selected'] = node_key
self.update()
Expand All @@ -98,11 +102,14 @@ def deselect(self) -> Self:

def on_expand(self, callback: Handler[ValueChangeEventArguments]) -> Self:
"""Add a callback to be invoked when the expansion changes."""
self._props.setdefault('expanded', [])
self._expand_handlers.append(callback)
return self

def on_tick(self, callback: Handler[ValueChangeEventArguments]) -> Self:
"""Add a callback to be invoked when a node is ticked or unticked."""
self._props.setdefault('ticked', [])
self._props.setdefault('tick-strategy', 'leaf')
self._tick_handlers.append(callback)
return self

Expand All @@ -111,6 +118,7 @@ def tick(self, node_keys: Optional[List[str]] = None) -> Self:
:param node_keys: list of node keys to tick or ``None`` to tick all nodes (default: ``None``)
"""
self._props.setdefault('ticked', [])
self._props['ticked'][:] = self._find_node_keys(node_keys).union(self._props['ticked'])
self.update()
return self
Expand All @@ -120,6 +128,7 @@ def untick(self, node_keys: Optional[List[str]] = None) -> Self:
:param node_keys: list of node keys to untick or ``None`` to untick all nodes (default: ``None``)
"""
self._props.setdefault('ticked', [])
self._props['ticked'][:] = set(self._props['ticked']).difference(self._find_node_keys(node_keys))
self.update()
return self
Expand All @@ -129,6 +138,7 @@ def expand(self, node_keys: Optional[List[str]] = None) -> Self:
:param node_keys: list of node keys to expand (default: all nodes)
"""
self._props.setdefault('expanded', [])
self._props['expanded'][:] = self._find_node_keys(node_keys).union(self._props['expanded'])
self.update()
return self
Expand All @@ -138,6 +148,7 @@ def collapse(self, node_keys: Optional[List[str]] = None) -> Self:
:param node_keys: list of node keys to collapse (default: all nodes)
"""
self._props.setdefault('expanded', [])
self._props['expanded'][:] = set(self._props['expanded']).difference(self._find_node_keys(node_keys))
self.update()
return self
Expand Down
2 changes: 2 additions & 0 deletions nicegui/nicegui.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ async def _on_handshake(sid: str, data: Dict[str, str]) -> bool:
client = Client.instances.get(data['client_id'])
if not client:
return False
if data.get('old_tab_id'):
app.storage.copy_tab(data['old_tab_id'], data['tab_id'])
client.tab_id = data['tab_id']
if sid[:5].startswith('test-'):
client.environ = {'asgi.scope': {'description': 'test client', 'type': 'test'}}
Expand Down
22 changes: 16 additions & 6 deletions nicegui/static/nicegui.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ function createRandomUUID() {
}
}

const OLD_TAB_ID = sessionStorage.__nicegui_tab_closed === "false" ? sessionStorage.__nicegui_tab_id : null;
const TAB_ID =
!sessionStorage.__nicegui_tab_id || sessionStorage.__nicegui_tab_closed === "false"
? (sessionStorage.__nicegui_tab_id = createRandomUUID())
: sessionStorage.__nicegui_tab_id;
sessionStorage.__nicegui_tab_closed = "false";
window.onbeforeunload = function () {
sessionStorage.__nicegui_tab_closed = "true";
};

function createApp(elements, options) {
replaceUndefinedAttributes(elements, 0);
return (app = Vue.createApp({
Expand All @@ -319,12 +329,12 @@ function createApp(elements, options) {
window.did_handshake = false;
const messageHandlers = {
connect: () => {
let tabId = sessionStorage.getItem("__nicegui_tab_id");
if (!tabId) {
tabId = createRandomUUID();
sessionStorage.setItem("__nicegui_tab_id", tabId);
}
window.socket.emit("handshake", { client_id: window.clientId, tab_id: tabId }, (ok) => {
const args = {
client_id: window.clientId,
tab_id: TAB_ID,
old_tab_id: OLD_TAB_ID,
};
window.socket.emit("handshake", args, (ok) => {
if (!ok) {
console.log("reloading because handshake failed for clientId " + window.clientId);
window.location.reload();
Expand Down
5 changes: 5 additions & 0 deletions nicegui/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ def tab(self) -> observables.ObservableDict:
self._tabs[client.tab_id] = observables.ObservableDict()
return self._tabs[client.tab_id]

def copy_tab(self, old_tab_id: str, tab_id: str) -> None:
"""Copy the tab storage to a new tab. (For internal use only.)"""
if old_tab_id in self._tabs:
self._tabs[tab_id] = observables.ObservableDict(self._tabs[old_tab_id].copy())

async def prune_tab_storage(self) -> None:
"""Regularly prune tab storage that is older than the configured `max_tab_storage_age`."""
while True:
Expand Down
57 changes: 50 additions & 7 deletions tests/test_radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,57 @@
from nicegui.testing import Screen


def test_changing_options(screen: Screen):
r = ui.radio([10, 20, 30], value=10)
ui.label().bind_text_from(r, 'value', lambda v: f'value = {v}')
ui.button('reverse', on_click=lambda: (r.options.reverse(), r.update()))
ui.button('clear', on_click=lambda: (r.options.clear(), r.update()))
def test_radio_click(screen: Screen):
radio = ui.radio(['A', 'B', 'C'])
ui.label().bind_text_from(radio, 'value', lambda x: f'Value: {x}')

screen.open('/')
screen.click('A')
screen.should_contain('Value: A')
screen.click('B')
screen.should_contain('Value: B')

screen.click('B') # already selected, should not change
screen.wait(0.5)
screen.should_contain('Value: B')


def test_radio_set_value(screen: Screen):
radio = ui.radio(['A', 'B', 'C'])
ui.label().bind_text_from(radio, 'value', lambda x: f'Value: {x}')

screen.open('/')
radio.set_value('B')
screen.should_contain('Value: B')


def test_radio_set_options(screen: Screen):
radio = ui.radio(['A', 'B', 'C'], value='C', on_change=lambda e: ui.notify(f'Event: {e.value}'))
ui.label().bind_text_from(radio, 'value', lambda x: f'Value: {x}')

ui.button('reverse', on_click=lambda: (radio.options.reverse(), radio.update())) # type: ignore
ui.button('clear', on_click=lambda: (radio.options.clear(), radio.update())) # type: ignore

screen.open('/')
radio.set_options(['C', 'D', 'E'])
screen.should_contain('D')
screen.should_contain('E')
screen.should_contain('Value: C')

radio.set_options(['X', 'Y', 'Z'])
screen.should_contain('X')
screen.should_contain('Y')
screen.should_contain('Z')
screen.should_contain('Value: None')
screen.should_contain('Event: None')

radio.set_options(['1', '2', '3'], value='3')
screen.should_contain('Value: 3')
screen.should_contain('Event: 3')

screen.click('reverse')
screen.should_contain('value = 10')
screen.should_contain('Value: 3')
screen.should_contain('Event: 3')

screen.click('clear')
screen.should_contain('value = None')
screen.should_contain('Value: None')
76 changes: 0 additions & 76 deletions tests/test_radio_element.py

This file was deleted.

4 changes: 2 additions & 2 deletions website/documentation/content/user_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ def user_fixture():
ui.markdown('''
We recommend utilizing the `user` fixture instead of the [`screen` fixture](/documentation/screen) wherever possible
because execution is as fast as unit tests and it does not need Selenium as a dependency
when loaded via `pytest_plugins = ['nicegui.testing.user_plugin']`
(see [project structure](/documentation/project_structure)).
when loaded via `pytest_plugins = ['nicegui.testing.user_plugin']`.
The `user` fixture cuts away the browser and replaces it by a lightweight simulation entirely in Python.
See [project structure](/documentation/project_structure) for a description of the setup.
You can assert to "see" specific elements or content, click buttons, type into inputs and trigger events.
We aimed for a nice API to write acceptance tests which read like a story and are easy to understand.
Expand Down

0 comments on commit a996c0a

Please sign in to comment.