File: test_binding_inheritance.py

package info (click to toggle)
textual 2.1.2-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 55,080 kB
  • sloc: python: 85,423; lisp: 1,669; makefile: 101
file content (642 lines) | stat: -rw-r--r-- 24,202 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
"""Tests relating to key binding inheritance.

In here you'll find some tests for general key binding inheritance, but
there is an emphasis on the inheriting of movement key bindings as they (as
of the time of writing) hold a special place in the Widget hierarchy of
Textual.

<URL:https://github.com/Textualize/textual/issues/1343> holds much of the
background relating to this.
"""

from __future__ import annotations

from textual.actions import SkipAction
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import Static

##############################################################################
# These are the movement keys within Textual; they kind of have a special
# status in that they will get bound to movement-related methods.
MOVEMENT_KEYS = ["up", "down", "left", "right", "home", "end", "pageup", "pagedown"]

##############################################################################
# An application with no bindings anywhere.
#
# The idea of this first little test is that an application that has no
# bindings set anywhere, and uses a default screen, should only its
# hard-coded bindings in place.


class NoBindings(App[None]):
    """An app with zero bindings."""


async def test_just_app_no_bindings() -> None:
    """An app with no bindings should have no bindings, other than the app's hard-coded ones."""
    async with NoBindings().run_test() as pilot:
        assert list(pilot.app._bindings.key_to_bindings.keys()) == [
            "ctrl+q",
            "ctrl+c",
            "ctrl+p",
        ]
        assert pilot.app._bindings.get_bindings_for_key("ctrl+q")[0].priority is True


##############################################################################
# An application with a single alpha binding.
#
# Sticking with just an app and the default screen: this configuration has a
# BINDINGS on the app itself, and simply binds the letter a. The result
# should be that we see the letter a, the app's default bindings, and
# nothing else.


class AlphaBinding(App[None]):
    """An app with a simple alpha key binding."""

    BINDINGS = [Binding("a", "a", "a", priority=True)]


async def test_just_app_alpha_binding() -> None:
    """An app with a single binding should have just the one binding."""
    async with AlphaBinding().run_test() as pilot:
        assert sorted(pilot.app._bindings.key_to_bindings.keys()) == sorted(
            ["ctrl+c", "ctrl+p", "ctrl+q", "a"]
        )
        assert pilot.app._bindings.get_bindings_for_key("ctrl+q")[0].priority is True
        assert pilot.app._bindings.get_bindings_for_key("a")[0].priority is True


##############################################################################
# An application with a single low-priority alpha binding.
#
# The same as the above, but in this case we're going to, on purpose, lower
# the priority of our own bindings, while any define by App itself should
# remain the same.


class LowAlphaBinding(App[None]):
    """An app with a simple low-priority alpha key binding."""

    BINDINGS = [Binding("a", "a", "a", priority=False)]


async def test_just_app_low_priority_alpha_binding() -> None:
    """An app with a single low-priority binding should have just the one binding."""
    async with LowAlphaBinding().run_test() as pilot:
        assert sorted(pilot.app._bindings.key_to_bindings.keys()) == sorted(
            ["ctrl+c", "ctrl+p", "ctrl+q", "a"]
        )
        assert pilot.app._bindings.get_bindings_for_key("ctrl+q")[0].priority is True
        assert pilot.app._bindings.get_bindings_for_key("a")[0].priority is False


##############################################################################
# A non-default screen with a single alpha key binding.
#
# There's little point in testing a screen with no bindings added as that's
# pretty much the same as an app with a default screen (for the purposes of
# these tests). So, let's test a screen with a single alpha-key binding.


class ScreenWithBindings(Screen):
    """A screen with a simple alpha key binding."""

    BINDINGS = [Binding("a", "a", "a", priority=True)]


class AppWithScreenThatHasABinding(App[None]):
    """An app with no extra bindings but with a custom screen with a binding."""

    SCREENS = {"main": ScreenWithBindings}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_app_screen_with_bindings() -> None:
    """Test a screen with a single key binding defined."""
    async with AppWithScreenThatHasABinding().run_test() as pilot:
        assert pilot.app.screen._bindings.get_bindings_for_key("a")[0].priority is True


##############################################################################
# A non-default screen with a single low-priority alpha key binding.
#
# As above, but because Screen sets all keys as high priority by default, we
# want to be sure that if we set our keys in our subclass as low priority as
# default, they come through as such.


class ScreenWithLowBindings(Screen):
    """A screen with a simple low-priority alpha key binding."""

    BINDINGS = [Binding("a", "a", "a", priority=False)]


class AppWithScreenThatHasALowBinding(App[None]):
    """An app with no extra bindings but with a custom screen with a low-priority binding."""

    SCREENS = {"main": ScreenWithLowBindings}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_app_screen_with_low_bindings() -> None:
    """Test a screen with a single low-priority key binding defined."""
    async with AppWithScreenThatHasALowBinding().run_test() as pilot:
        assert pilot.app.screen._bindings.get_bindings_for_key("a")[0].priority is False


##############################################################################
# From here on in we're going to start simulating keystrokes to ensure that
# any bindings that are in place actually fire the correct actions. To help
# with this let's build a simple key/binding/action recorder base app.


class AppKeyRecorder(App[None]):
    """Base application class that can be used to record keystrokes."""

    ALPHAS = "abcxyz"
    """str: The alpha keys to test against."""

    ALL_KEYS = [*ALPHAS, *MOVEMENT_KEYS]
    """list[str]: All the test keys."""

    @staticmethod
    def make_bindings(action_prefix: str = "") -> list[Binding]:
        """Make the binding list for testing an app.

        Args:
            action_prefix (str, optional): An optional prefix for the action name.

        Returns:
            list[Binding]: The resulting list of bindings.
        """
        return [
            Binding(key, f"{action_prefix}record('{key}')", key)
            for key in [*AppKeyRecorder.ALPHAS, *MOVEMENT_KEYS]
        ]

    def __init__(self) -> None:
        """Initialise the recording app."""
        super().__init__()
        self.pressed_keys: list[str] = []

    async def action_record(self, key: str) -> None:
        """Record a key, as used from a binding.

        Args:
            key (str): The name of the key to record.
        """
        self.pressed_keys.append(key)

    def all_recorded(self, marker_prefix: str = "") -> None:
        """Were all the bindings recorded from the presses?

        Args:
            marker_prefix (str, optional): An optional prefix for the result markers.
        """
        assert self.pressed_keys == [f"{marker_prefix}{key}" for key in self.ALL_KEYS]


##############################################################################
# An app with bindings for movement keys.
#
# Having gone through various permutations of testing for what bindings are
# seen to be in place, we now move on to adding bindings, invoking them and
# seeing what happens. First off let's start with an application that has
# bindings, both for an alpha key, and also for all of the movement keys.


class AppWithMovementKeysBound(AppKeyRecorder):
    """An application with bindings."""

    BINDINGS = AppKeyRecorder.make_bindings()


async def test_pressing_alpha_on_app() -> None:
    """Test that pressing the alpha key, when it's bound on the app, results in an action fire."""
    async with AppWithMovementKeysBound().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALPHAS)
        await pilot.pause()
        assert pilot.app.pressed_keys == [*AppKeyRecorder.ALPHAS]


async def test_pressing_movement_keys_app() -> None:
    """Test that pressing the movement keys, when they're bound on the app, results in an action fire."""
    async with AppWithMovementKeysBound().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)
        await pilot.pause()
        pilot.app.all_recorded()


##############################################################################
# An app with a focused child widget with bindings.
#
# Now let's spin up an application, using the default screen, where the app
# itself is composing in a widget that can have, and has, focus. The widget
# also has bindings for all of the test keys. That child widget should be
# able to handle all of the test keys on its own and nothing else should
# grab them.


class FocusableWidgetWithBindings(Static, can_focus=True):
    """A widget that has its own bindings for the movement keys."""

    BINDINGS = AppKeyRecorder.make_bindings("local_")

    async def action_local_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"locally_{key}")


class AppWithWidgetWithBindings(AppKeyRecorder):
    """A test app that composes with a widget that has movement bindings."""

    def compose(self) -> ComposeResult:
        yield FocusableWidgetWithBindings()

    def on_mount(self) -> None:
        self.query_one(FocusableWidgetWithBindings).focus()


async def test_focused_child_widget_with_movement_bindings() -> None:
    """A focused child widget with movement bindings should handle its own actions."""
    async with AppWithWidgetWithBindings().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)

        pilot.app.all_recorded("locally_")


##############################################################################
# A focused widget within a screen that handles bindings.
#
# Similar to the previous test, here we're wrapping an app around a
# non-default screen, which in turn wraps a widget that can has, and will
# have, focus. The difference here however is that the screen has the
# bindings. What we should expect to see is that the bindings don't fire on
# the widget (it has none) and instead get caught by the screen.


class FocusableWidgetWithNoBindings(Static, can_focus=True):
    """A widget that can receive focus but has no bindings."""


class ScreenWithMovementBindings(Screen):
    """A screen that binds keys, including movement keys."""

    BINDINGS = AppKeyRecorder.make_bindings("screen_")

    async def action_screen_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"screenly_{key}")

    def compose(self) -> ComposeResult:
        yield FocusableWidgetWithNoBindings()

    def on_mount(self) -> None:
        self.query_one(FocusableWidgetWithNoBindings).focus()


class AppWithScreenWithBindingsWidgetNoBindings(AppKeyRecorder):
    """An app with a non-default screen that handles movement key bindings."""

    SCREENS = {"main": ScreenWithMovementBindings}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_focused_child_widget_with_movement_bindings_on_screen() -> None:
    """A focused child widget, with movement bindings in the screen, should trigger screen actions."""
    async with AppWithScreenWithBindingsWidgetNoBindings().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)

        pilot.app.all_recorded("screenly_")


##############################################################################
# A focused widget within a container within a screen that handles bindings.
#
# Similar again to the previous test, here we're wrapping an app around a
# non-default screen, which in turn wraps a container which wraps a widget
# that can have, and will have, focus. The issue here is that if the
# container isn't scrolling, especially if it's set up to just wrap a widget
# and do nothing else, it should not rob the screen of the binding hits.


class ScreenWithMovementBindingsAndContainerAroundWidget(Screen):
    """A screen that binds keys, including movement keys."""

    BINDINGS = AppKeyRecorder.make_bindings("screen_")

    async def action_screen_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"screenly_{key}")

    def compose(self) -> ComposeResult:
        yield Container(FocusableWidgetWithNoBindings())

    def on_mount(self) -> None:
        self.query_one(FocusableWidgetWithNoBindings).focus()


class AppWithScreenWithBindingsWrappedWidgetNoBindings(AppKeyRecorder):
    """An app with a non-default screen that handles movement key bindings."""

    SCREENS = {"main": ScreenWithMovementBindings}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_contained_focused_child_widget_with_movement_bindings_on_screen() -> (
    None
):
    """A contained focused child widget, with movement bindings in the screen, should trigger screen actions."""
    async with AppWithScreenWithBindingsWrappedWidgetNoBindings().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)

        pilot.app.all_recorded("screenly_")


##############################################################################
# A focused widget with bindings but no inheriting of bindings, on app.
#
# Now we move on to testing inherit_bindings. To start with we go back to an
# app with a default screen, with the app itself composing in a widget that
# can and will have focus, which has bindings for all the test keys, and
# crucially has inherit_bindings set to False.
#
# We should expect to see all of the test keys recorded post-press.


class WidgetWithBindingsNoInherit(Static, can_focus=True, inherit_bindings=False):
    """A widget that has its own bindings for the movement keys, no binding inheritance."""

    BINDINGS = AppKeyRecorder.make_bindings("local_")

    async def action_local_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"locally_{key}")


class AppWithWidgetWithBindingsNoInherit(AppKeyRecorder):
    """A test app that composes with a widget that has movement bindings without binding inheritance."""

    def compose(self) -> ComposeResult:
        yield WidgetWithBindingsNoInherit()

    def on_mount(self) -> None:
        self.query_one(WidgetWithBindingsNoInherit).focus()


async def test_focused_child_widget_with_movement_bindings_no_inherit() -> None:
    """A focused child widget with movement bindings and inherit_bindings=False should handle its own actions."""
    async with AppWithWidgetWithBindingsNoInherit().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)

        pilot.app.all_recorded("locally_")


##############################################################################
# A focused widget with no bindings and no inheriting of bindings, on screen.
#
# Now let's test with a widget that can and will have focus, which has no
# bindings, and which won't inherit bindings either. The bindings we're
# going to test are moved up to the screen. We should expect to see all of
# the test keys not be consumed by the focused widget, but instead they
# should make it up to the screen.
#
# NOTE: no bindings are declared for the widget, which is different from
# zero bindings declared.


class FocusableWidgetWithNoBindingsNoInherit(
    Static, can_focus=True, inherit_bindings=False
):
    """A widget that can receive focus but has no bindings and doesn't inherit bindings."""


class ScreenWithMovementBindingsNoInheritChild(Screen):
    """A screen that binds keys, including movement keys."""

    BINDINGS = AppKeyRecorder.make_bindings("screen_")

    async def action_screen_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"screenly_{key}")

    def compose(self) -> ComposeResult:
        yield FocusableWidgetWithNoBindingsNoInherit()

    def on_mount(self) -> None:
        self.query_one(FocusableWidgetWithNoBindingsNoInherit).focus()


class AppWithScreenWithBindingsWidgetNoBindingsNoInherit(AppKeyRecorder):
    """An app with a non-default screen that handles movement key bindings, child no-inherit."""

    SCREENS = {"main": ScreenWithMovementBindingsNoInheritChild}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_focused_child_widget_no_inherit_with_movement_bindings_on_screen() -> (
    None
):
    """A focused child widget, that doesn't inherit bindings, with movement bindings in the screen, should trigger screen actions."""
    async with AppWithScreenWithBindingsWidgetNoBindingsNoInherit().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)

        pilot.app.all_recorded("screenly_")


##############################################################################
# A focused widget with zero bindings declared, but no inheriting of
# bindings, on screen.
#
# Now let's test with a widget that can and will have focus, which has zero
# (an empty collection of) bindings, and which won't inherit bindings
# either. The bindings we're going to test are moved up to the screen. We
# should expect to see all of the test keys not be consumed by the focused
# widget, but instead they should make it up to the screen.
#
# NOTE: zero bindings are declared for the widget, which is different from
# no bindings declared.


class FocusableWidgetWithEmptyBindingsNoInherit(
    Static, can_focus=True, inherit_bindings=False
):
    """A widget that can receive focus but has empty bindings and doesn't inherit bindings."""

    BINDINGS = []


class ScreenWithMovementBindingsNoInheritEmptyChild(Screen):
    """A screen that binds keys, including movement keys."""

    BINDINGS = AppKeyRecorder.make_bindings("screen_")

    async def action_screen_record(self, key: str) -> None:
        # Sneaky forward reference. Just for the purposes of testing.
        await self.app.action_record(f"screenly_{key}")

    def compose(self) -> ComposeResult:
        yield FocusableWidgetWithEmptyBindingsNoInherit()

    def on_mount(self) -> None:
        self.query_one(FocusableWidgetWithEmptyBindingsNoInherit).focus()


class AppWithScreenWithBindingsWidgetEmptyBindingsNoInherit(AppKeyRecorder):
    """An app with a non-default screen that handles movement key bindings, child no-inherit."""

    SCREENS = {"main": ScreenWithMovementBindingsNoInheritEmptyChild}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_focused_child_widget_no_inherit_empty_bindings_with_movement_bindings_on_screen() -> (
    None
):
    """A focused child widget, that doesn't inherit bindings and sets BINDINGS empty, with movement bindings in the screen, should trigger screen actions."""
    async with AppWithScreenWithBindingsWidgetEmptyBindingsNoInherit().run_test() as pilot:
        await pilot.press(*AppKeyRecorder.ALL_KEYS)
        pilot.app.all_recorded("screenly_")


##############################################################################
# Testing priority of overlapping bindings.
#
# Here we we'll have an app, screen, and a focused widget, along with a
# combination of overlapping bindings, each with different forms of
# priority, so we can check who wins where.
#
# Here are the permutations tested, with the expected winner:
#
# |-----|----------|----------|----------|--------|
# | Key | App      | Screen   | Widget   | Winner |
# |-----|----------|----------|----------|--------|
# | 0   |          |          |          | Widget |
# | A   | Priority |          |          | App    |
# | B   |          | Priority |          | Screen |
# | C   |          |          | Priority | Widget |
# | D   | Priority | Priority |          | App    |
# | E   | Priority |          | Priority | App    |
# | F   |          | Priority | Priority | Screen |


class PriorityOverlapWidget(Static, can_focus=True):
    """A focusable widget with a priority binding."""

    BINDINGS = [
        Binding("0", "app.record('widget_0')", "0", priority=False),
        Binding("a", "app.record('widget_a')", "a", priority=False),
        Binding("b", "app.record('widget_b')", "b", priority=False),
        Binding("c", "app.record('widget_c')", "c", priority=True),
        Binding("d", "app.record('widget_d')", "d", priority=False),
        Binding("e", "app.record('widget_e')", "e", priority=True),
        Binding("f", "app.record('widget_f')", "f", priority=True),
    ]


class PriorityOverlapScreen(Screen):
    """A screen with a priority binding."""

    BINDINGS = [
        Binding("0", "app.record('screen_0')", "0", priority=False),
        Binding("a", "app.record('screen_a')", "a", priority=False),
        Binding("b", "app.record('screen_b')", "b", priority=True),
        Binding("c", "app.record('screen_c')", "c", priority=False),
        Binding("d", "app.record('screen_d')", "c", priority=True),
        Binding("e", "app.record('screen_e')", "e", priority=False),
        Binding("f", "app.record('screen_f')", "f", priority=True),
    ]

    def compose(self) -> ComposeResult:
        yield PriorityOverlapWidget()

    def on_mount(self) -> None:
        self.query_one(PriorityOverlapWidget).focus()


class PriorityOverlapApp(AppKeyRecorder):
    """An application with a priority binding."""

    BINDINGS = [
        Binding("0", "record('app_0')", "0", priority=False),
        Binding("a", "record('app_a')", "a", priority=True),
        Binding("b", "record('app_b')", "b", priority=False),
        Binding("c", "record('app_c')", "c", priority=False),
        Binding("d", "record('app_d')", "c", priority=True),
        Binding("e", "record('app_e')", "e", priority=True),
        Binding("f", "record('app_f')", "f", priority=False),
    ]

    SCREENS = {"main": PriorityOverlapScreen}

    def on_mount(self) -> None:
        self.push_screen("main")


async def test_overlapping_priority_bindings() -> None:
    """Test an app stack with overlapping bindings."""
    async with PriorityOverlapApp().run_test() as pilot:
        await pilot.press(*"0abcdef")
        assert pilot.app.pressed_keys == [
            "widget_0",
            "app_a",
            "screen_b",
            "widget_c",
            "app_d",
            "app_e",
            "screen_f",
        ]


async def test_skip_action() -> None:
    """Test that a binding may be skipped by an action raising SkipAction"""

    class Handle(Widget, can_focus=True):
        BINDINGS = [("t", "test('foo')", "Test")]

        def action_test(self, text: str) -> None:
            self.app.exit(text)

    no_handle_invoked = False

    class NoHandle(Widget, can_focus=True):
        BINDINGS = [("t", "test('bar')", "Test")]

        def action_test(self, text: str) -> bool:
            nonlocal no_handle_invoked
            no_handle_invoked = True
            raise SkipAction()

    class SkipApp(App):
        def compose(self) -> ComposeResult:
            yield Handle(NoHandle())

        def on_mount(self) -> None:
            self.query_one(NoHandle).focus()

    async with SkipApp().run_test() as pilot:
        # Check the NoHandle widget has focus
        assert pilot.app.query_one(NoHandle).has_focus
        # Press the "t" key
        await pilot.press("t")
        # Check the action on the no handle widget was called
        assert no_handle_invoked
        # Check the return value, confirming that the action on Handle was called
        assert pilot.app.return_value == "foo"