Browse Source

Fix nested UI item parent state

pull/10473/merge
harumaki4649 2 days ago
committed by GitHub
parent
commit
d2996be144
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      discord/ui/action_row.py
  2. 6
      discord/ui/container.py
  3. 4
      discord/ui/item.py
  4. 3
      discord/ui/section.py
  5. 94
      tests/test_ui_view.py

6
discord/ui/action_row.py

@ -203,6 +203,9 @@ class ActionRow(Item[V]):
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
child_index = self._children.index(base)
self._children[child_index] = new # type: ignore
base._detach_view()
new._update_view(self.view)
new._parent = self
@property
def width(self):
@ -299,6 +302,7 @@ class ActionRow(Item[V]):
if self._view:
self._view._add_count(-1)
self._weight -= item.width
item._detach_view()
return self
@ -330,6 +334,8 @@ class ActionRow(Item[V]):
"""
if self._view:
self._view._add_count(-len(self._children))
for item in self._children:
item._detach_view()
self._children.clear()
self._weight = 0
return self

6
discord/ui/container.py

@ -197,6 +197,9 @@ class Container(Item[V]):
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
child_index = self._children.index(base)
self._children[child_index] = new # type: ignore
base._detach_view()
new._update_view(self.view)
new._parent = self
@property
def children(self) -> List[Item[V]]:
@ -336,6 +339,7 @@ class Container(Item[V]):
else:
if self._view:
self._view._add_count(-item._total_count)
item._detach_view()
return self
def find_item(self, id: int, /) -> Optional[Item[V]]:
@ -367,5 +371,7 @@ class Container(Item[V]):
if self._view:
self._view._add_count(-len(tuple(self.walk_children())))
for item in self._children:
item._detach_view()
self._children.clear()
return self

4
discord/ui/item.py

@ -206,6 +206,10 @@ class Item(Generic[V]):
def _update_view(self, view) -> None:
self._view = view
def _detach_view(self) -> None:
self._update_view(None)
self._parent = None
def copy(self) -> Self:
return copy.deepcopy(self)

3
discord/ui/section.py

@ -212,6 +212,7 @@ class Section(Item[V]):
else:
if self._view:
self._view._add_count(-1)
item._detach_view()
return self
@ -244,6 +245,8 @@ class Section(Item[V]):
if self._view:
self._view._add_count(-len(self._children)) # we don't count the accessory because it is required
for item in self._children:
item._detach_view()
self._children.clear()
return self

94
tests/test_ui_view.py

@ -28,6 +28,12 @@ import discord
import pytest
class DynamicButton(discord.ui.DynamicItem[discord.ui.Button], template=r'dynamic:(?P<number>[0-9]+)'):
@classmethod
async def from_custom_id(cls, interaction, item, match):
return cls(discord.ui.Button(label='Dynamic', custom_id=match.group(0)))
def test_add_item_with_full_row():
view = discord.ui.View()
@ -86,3 +92,91 @@ def test_layout_view_add_item_with_too_many_children():
assert view.total_children_count == max_item_limit - 1
assert row.view is None
assert all(item.view is None for item in row.children)
def test_section_remove_item_clears_view_and_parent():
view = discord.ui.LayoutView()
section = discord.ui.Section('test', accessory=discord.ui.Button(label='Test'))
view.add_item(section)
item = section.children[0]
section.remove_item(item)
assert item.view is None
assert item.parent is None
def test_action_row_remove_item_clears_view_and_parent():
view = discord.ui.LayoutView()
row = discord.ui.ActionRow()
item = discord.ui.Button(label='Test')
row.add_item(item)
view.add_item(row)
row.remove_item(item)
assert item.view is None
assert item.parent is None
def test_container_remove_item_clears_view_and_parent():
view = discord.ui.LayoutView()
container = discord.ui.Container()
item = discord.ui.TextDisplay('test')
container.add_item(item)
view.add_item(container)
container.remove_item(item)
assert item.view is None
assert item.parent is None
@pytest.mark.asyncio
async def test_action_row_dynamic_item_swap_preserves_parent_checks():
calls = []
class Row(discord.ui.ActionRow):
async def interaction_check(self, interaction):
calls.append('row')
return True
base = discord.ui.Button(label='Dynamic', custom_id='dynamic:1')
row = Row(base)
view = discord.ui.LayoutView()
view.add_item(row)
item = DynamicButton(discord.ui.Button(label='Dynamic', custom_id='dynamic:1'))
row._swap_item(base, item, 'dynamic:1')
await item._run_checks(None)
assert item.view is view
assert item.parent is row
assert base.view is None
assert base.parent is None
assert calls == ['row']
@pytest.mark.asyncio
async def test_container_dynamic_item_swap_preserves_parent_checks():
calls = []
class Container(discord.ui.Container):
async def interaction_check(self, interaction):
calls.append('container')
return True
base = discord.ui.ActionRow(discord.ui.Button(label='Dynamic', custom_id='dynamic:1'))
container = Container(base)
view = discord.ui.LayoutView()
view.add_item(container)
item = DynamicButton(discord.ui.Button(label='Dynamic', custom_id='dynamic:1'))
container._swap_item(base, item, 'dynamic:1')
await item._run_checks(None)
assert item.view is view
assert item.parent is container
assert base.view is None
assert base.parent is None
assert calls == ['container']

Loading…
Cancel
Save