Skip to content

Commit 0f0849f

Browse files
authored
Merge pull request #6412 from Textualize/on-complete-fix
fix on complete
2 parents 6939565 + 6163ba1 commit 0f0849f

File tree

5 files changed

+83
-18
lines changed

5 files changed

+83
-18
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ docs/build
88
docs/source/_build
99
tools/*.txt
1010
playground/
11+
.mypy_cache/
12+
.screenshot_cache/
1113

1214
# Byte-compiled / optimized / DLL files
1315
__pycache__/
@@ -129,3 +131,6 @@ sandbox/
129131

130132
# Used by mkdocs-material social plugin
131133
.cache
134+
135+
# Snapshot tests output
136+
__snapshots__/

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [8.1.1] - 2026-03-10
9+
10+
### Fixed
11+
12+
- Hotfix for animation on complete https://github.com/Textualize/textual/pull/6412
13+
814
## [8.1.0] - 2026-03-10
915

1016
### Changed
@@ -3381,6 +3387,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
33813387
- New handler system for messages that doesn't require inheritance
33823388
- Improved traceback handling
33833389

3390+
[8.1.1]: https://github.com/Textualize/textual/compare/v8.1.0...v8.1.1
33843391
[8.1.0]: https://github.com/Textualize/textual/compare/v8.0.2...v8.1.0
33853392
[8.0.2]: https://github.com/Textualize/textual/compare/v8.0.1...v8.0.2
33863393
[8.0.1]: https://github.com/Textualize/textual/compare/v8.0.0...v8.0.1

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "textual"
3-
version = "8.1.0"
3+
version = "8.1.1"
44
homepage = "https://github.com/Textualize/textual"
55
repository = "https://github.com/Textualize/textual"
66
documentation = "https://textual.textualize.io/"
@@ -36,6 +36,7 @@ include = [
3636
# that works around that.
3737
{ path = "docs-offline/**/*", format = "sdist" },
3838
]
39+
exclude = ["tests/snapshot_tests/__snapshots__"]
3940

4041
[tool.poetry.urls]
4142
"Bug Tracker" = "https://github.com/Textualize/textual/issues"

src/textual/_animator.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,7 @@ def _animate(
458458

459459
if (current_animation := self._animations.get(animation_key)) is not None:
460460
if (on_complete := current_animation.on_complete) is not None:
461-
on_complete()
462-
self._animations.pop(animation_key)
463-
if current_animation == animation:
464-
return
461+
self.app.call_later(on_complete)
465462

466463
self._animations[animation_key] = animation
467464
self._timer.resume()

tests/test_animation.py

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -213,34 +213,89 @@ async def test_cancel_widget_non_animation() -> None:
213213
assert not pilot.app.animator.is_being_animated(widget, "counter")
214214

215215

216-
async def test_double_animation_on_complete() -> None:
217-
"""Test that animating an attribute a second time, fires its `on_complete` callback."""
216+
async def test_double_animation_same_value_on_complete() -> None:
217+
"""Test that animating an attribute a second time to the same value, fires its `on_complete` callback."""
218218

219-
complete_count = 0
219+
completed: list[str] = []
220220

221221
class AnimApp(App):
222222
x = var(0)
223223

224-
def on_key(self) -> None:
224+
async def animate_a(self) -> None:
225225

226226
def on_complete() -> None:
227-
nonlocal complete_count
228-
complete_count += 1
227+
completed.append("a")
229228

230229
self.animator.animate(
231230
self,
232231
"x",
233-
100 + complete_count,
232+
100,
233+
duration=0.1,
234+
on_complete=on_complete,
235+
)
236+
237+
async def animate_b(self) -> None:
238+
239+
def on_complete() -> None:
240+
completed.append("b")
241+
242+
self.animator.animate(
243+
self,
244+
"x",
245+
100,
246+
duration=0.1,
247+
on_complete=on_complete,
248+
)
249+
250+
app = AnimApp()
251+
async with app.run_test() as pilot:
252+
await app.animate_a()
253+
assert app.x != 100
254+
await app.animate_b()
255+
await pilot.wait_for_animation()
256+
assert completed == ["a", "b"]
257+
assert app.x == 100
258+
259+
260+
async def test_double_animation_different_value_on_complete() -> None:
261+
"""Test that animating an attribute a second time to a different value, fires its `on_complete` callback."""
262+
263+
completed: list[str] = []
264+
265+
class AnimApp(App):
266+
x = var(0)
267+
268+
async def animate_a(self) -> None:
269+
270+
def on_complete() -> None:
271+
completed.append("a")
272+
273+
self.animator.animate(
274+
self,
275+
"x",
276+
100,
277+
duration=0.1,
278+
on_complete=on_complete,
279+
)
280+
281+
async def animate_b(self) -> None:
282+
283+
def on_complete() -> None:
284+
completed.append("b")
285+
286+
self.animator.animate(
287+
self,
288+
"x",
289+
101,
234290
duration=0.1,
235291
on_complete=on_complete,
236292
)
237293

238294
app = AnimApp()
239295
async with app.run_test() as pilot:
240-
# Press space twice to initiate 2 animations
241-
await pilot.press("space")
242-
await pilot.press("space")
243-
# Wait for animations to complete
296+
await app.animate_a()
297+
assert app.x != 101
298+
await app.animate_b() # animate to different value
244299
await pilot.wait_for_animation()
245-
# Check that on_complete callback was invoked twice
246-
assert complete_count == 2
300+
assert completed == ["a", "b"]
301+
assert app.x == 101

0 commit comments

Comments
 (0)