From 4d128817ed7f4ce95a2faefeedf270f94bebea9d Mon Sep 17 00:00:00 2001 From: harumaki4649 Date: Sat, 23 May 2026 21:11:13 +0900 Subject: [PATCH 1/4] Fix ActionRow add_item type validation --- discord/ui/action_row.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/ui/action_row.py b/discord/ui/action_row.py index 863564973..3e9f58913 100644 --- a/discord/ui/action_row.py +++ b/discord/ui/action_row.py @@ -260,15 +260,15 @@ class ActionRow(Item[V]): or (40) for the entire view. """ + if not isinstance(item, Item): + raise TypeError(f'expected Item not {item.__class__.__name__}') + if (self._weight + item.width) > 5: raise ValueError('maximum number of children exceeded') if len(self._children) >= 5: raise ValueError('maximum number of children exceeded') - if not isinstance(item, Item): - raise TypeError(f'expected Item not {item.__class__.__name__}') - if self._view: self._view._add_count(1) From 36b0421f21554e4e80ff5721ec04665e7e547e77 Mon Sep 17 00:00:00 2001 From: harumaki4649 Date: Sat, 23 May 2026 21:18:13 +0900 Subject: [PATCH 2/4] Fix View item state tracking --- discord/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/view.py b/discord/ui/view.py index 582c619d7..3672c373d 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -468,8 +468,8 @@ class BaseView: if not isinstance(item, Item): raise TypeError(f'expected Item not {item.__class__.__name__}') - item._update_view(self) self._add_count(item._total_count) + item._update_view(self) self._children.append(item) return self From 93b62d781d3d825bbd9af7a0462c5ccb94a50d2b Mon Sep 17 00:00:00 2001 From: harumaki4649 Date: Sat, 23 May 2026 21:21:08 +0900 Subject: [PATCH 3/4] Fix View add and remove item state --- discord/ui/view.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/discord/ui/view.py b/discord/ui/view.py index 3672c373d..e82c8fe9e 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -783,20 +783,17 @@ class View(BaseView): return components def add_item(self, item: Item[Any]) -> Self: + if not isinstance(item, Item): + raise TypeError(f'expected Item not {item.__class__.__name__}') + if len(self._children) >= 25: raise ValueError('maximum number of children exceeded') if item._is_v2(): raise ValueError('v2 items cannot be added to this view') + self.__weights.add_item(item) super().add_item(item) - try: - self.__weights.add_item(item) - except ValueError as e: - # if the item has no space left then remove it from _children - self._children.remove(item) - raise e - return self def remove_item(self, item: Item[Any]) -> Self: @@ -806,6 +803,7 @@ class View(BaseView): pass else: self.__weights.remove_item(item) + self._add_count(-item._total_count) item._update_view(None) return self From 727103312252a23f15f743cd3316c3cbb6961071 Mon Sep 17 00:00:00 2001 From: harumaki4649 Date: Sat, 23 May 2026 21:21:22 +0900 Subject: [PATCH 4/4] Add UI view item state tests --- tests/test_ui_view.py | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/test_ui_view.py diff --git a/tests/test_ui_view.py b/tests/test_ui_view.py new file mode 100644 index 000000000..57b17a6dc --- /dev/null +++ b/tests/test_ui_view.py @@ -0,0 +1,88 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import discord +import pytest + + +def test_add_item_with_full_row(): + view = discord.ui.View() + + for i in range(5): + view.add_item(discord.ui.Button(label=str(i), row=0)) + + with pytest.raises(ValueError): + view.add_item(discord.ui.Button(label="6", row=0)) + + assert len(view.children) == 5 + assert view.total_children_count == 5 + + +def test_add_item_invalid(): + view = discord.ui.View() + + with pytest.raises(TypeError): + view.add_item(object()) # type: ignore + + +def test_remove_item(): + view = discord.ui.View() + item = discord.ui.Button(label="Test") + view.add_item(item) + + view.remove_item(item) + + assert view.children == [] + assert view.total_children_count == 0 + assert item.view is None + + +def test_action_row_add_item_invalid(): + row = discord.ui.ActionRow() + + with pytest.raises(TypeError): + row.add_item(object()) # type: ignore + + +def test_layout_view_add_item_with_too_many_children(): + view = discord.ui.LayoutView() + max_item_limit = 40 + + for i in range(max_item_limit - 1): + view.add_item(discord.ui.TextDisplay(str(i))) + + row = discord.ui.ActionRow( + discord.ui.Button(label="A"), + discord.ui.Button(label="B"), + ) + + with pytest.raises(ValueError): + view.add_item(row) + + assert len(view.children) == max_item_limit - 1 + 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)