ENB: a virtual user for the Qt gui

29 views
Skip to first unread message

Edward K. Ream

unread,
Apr 4, 2026, 8:01:21 AM (3 days ago) Apr 4
to leo-editor
​This Engineering Notebook post shows a simple Leonine script that simulates a user typing at the keyboard. It will turn out to be useful when testing concrete mypy annotations. See PR #4576.

I'll say much more about annotations in another ENB, but I wanted to share the prototype now. Here it is:

g.cls()
for command_or_key in [
    'full-command',  # Activate minibuffer.
    's',
    'h',
    '\t',  # Tab completion!
    'keyboard-quit',  # Deactivate minibuffer.
]:
    stroke = c.k.getStrokeForCommandName(command_or_key)
    if stroke:  # The name of a command.
        print(f"{stroke.s}: {command_or_key}")
        g.app.gui.event_generate(c, char=None, shortcut=stroke.s, w=None)
    else:  # A single character.
        char = binding = command_or_key
        event = g.app.gui.create_key_event(c, char=char, binding=binding)
        print(f"{event.char!r}")
        c.k.masterKeyHandler(event)
        g.app.gui.qtApp.processEvents()
        g.sleep(1.0)  # Testing only.


Try it out. It's nifty.

This script's eventual purpose will be to call every g.check* function throughout Leo's code. New traces will ensure that every check function does, in fact, get called.

This prototype will likely become a unit test that runs with Qt gui instead of the null gui that Leo's unit tests usually use. The PR will create the infrastructure for running unit tests with the Qt gui. Initial experiments show that this task is straightforward.

Summary

I've written the first draft of a longer ENB that discusses PR #4576, but I wanted to share this prototype right away.

Edward

Edward K. Ream

unread,
Apr 4, 2026, 8:09:47 AM (3 days ago) Apr 4
to leo-editor
On Saturday, April 4, 2026 at 7:01:21 AM UTC-5 Edward K. Ream wrote:

>This Engineering Notebook post shows a simple Leonine script that simulates a user typing at the keyboard.
...

>This script's eventual purpose will be to call every g.check* function throughout Leo's code.

So, for example, the script should alter the body pane, create and delete tree nodes, etc., etc.

It would likely be unsafe to do those things in a Leo itself, which is why they should be done in a Qt unit test. Stay tuned.

Edward

Edward K. Ream

unread,
Apr 5, 2026, 5:26:53 AM (2 days ago) Apr 5
to leo-editor
On Saturday, April 4, 2026 at 7:09:47 AM UTC-5 Edward K. Ream wrote:

>>This [virtual user] script's eventual purpose will be to call every g.check* function throughout Leo's code.

>So, for example, the script should alter the body pane, create and delete tree nodes, etc., etc.

The virtual user was a useful starting point (thought experiment), but its code was too clever by half. I'll omit the details.

Instead, the corresponding unit test can just test the ground truth of annotations directly. This is a much simpler task!

The unit test will ensure that the actual wrapper and widget classes have the expected values (obj.__class__.__name__). The unit test will "cheat": it will know that some wrappers have a "widget" ivar, others have a "bodyCtrl" ivar, etc.

And there should be (perhaps already is) a similar unit test that runs with the Null gui.

Edward

P.S. I have finally  gotten around to reading info item #1585: Notes re Leo's codebase. It's basically useless :-) I'll update it as part of the concrete annotations PR.

EKR

Edward K. Ream

unread,
Apr 5, 2026, 7:13:20 AM (2 days ago) Apr 5
to leo-editor
On Sunday, April 5, 2026 at 4:26:53 AM UTC-5 Edward K. Ream wrote:

> The unit test will ensure that the actual wrapper and widget classes have the expected values (obj.__class__.__name__).
> The unit test will "cheat": it will know that some wrappers have a "widget" ivar, others have a "bodyCtrl" ivar, etc.

Wow. It's amazing how useful a little cheating can be :-)

I revised unit test by peeking into the LeoQtFrame ctor to find its ivars.

Imagine my surprise when the unit test failed because the c.frame.bar1 and c.frame.bar2 were None.

At first, I thought that the TestQtGui.setUpClass method wasn't doing its job. That was a lengthy diversion.

At last, it became clear that the bar1 and bar2 ivars are never set, which means that three LeoQtFrame methods are never called and can be deleted. Rev a7cdaa4 removes these ivars and methods.

Edward

Edward K. Ream

unread,
Apr 5, 2026, 9:10:10 AM (2 days ago) Apr 5
to leo-editor
On Sunday, April 5, 2026 at 4:26:53 AM UTC-5 Edward K. Ream wrote:

> The...unit test can just test the ground truth of annotations directly. This is a much simpler task!

> The unit test will ensure that the actual wrapper and widget classes have the expected values (obj.__class__.__name__).

And here it is. The table will be the starting point for concrete Qt gui annotations:

def test_annotations(self):
    c = self.c
    table = (
        # LeoQtFrame ivars...
        (c.frame, 'LeoQtFrame'),
        (c.frame.body, 'LeoQtBody'),
        (c.frame.iconBar, 'QtIconBarClass'),
        (c.frame.log, 'LeoQtLog'),
        (c.frame.statusLine, 'QtStatusLineClass'),
        (c.frame.tree, 'LeoQtTree'),
        (c.frame.top, 'DynamicWindow'),
        # LeoQtBody ivars...
        (c.frame.body.wrapper, 'QTextEditWrapper'),  # QScintillaWrapper | QTextEditWrapper
        (c.frame.body.widget, 'LeoQTextBrowser'),  # not QWidget!
        # LeoQtLog ivars...
        (c.frame.log.qtLogCtrl, 'QTextEditWrapper'),
        (c.frame.log.logWidget, 'LeoQTextBrowser'),
        (c.frame.log.qtTabWidget, 'QTabWidget'),  # Not QWidget!
        # LeoQtTree ivars...
        (c.frame.tree.treeWidget, 'LeoQTreeWidget'),
    )
    for obj, class_name in table:
        if class_name.startswith('?'):
            print(class_name, obj.__class__.__name__)
        else:
            assert obj.__class__.__name__ == class_name, (repr(obj), class_name


Edward

Edward K. Ream

unread,
Apr 5, 2026, 10:13:48 AM (2 days ago) Apr 5
to leo-editor
On Sunday, April 5, 2026 at 4:26:53 AM UTC-5 Edward K. Ream wrote:

>  And there should be a...unit test [like TestQtGui.test_annotations] that runs with the Null gui.

Now there is: TestNullGui.test_annotations.

It found that there no need for the NullLog.wrapper ivar. Removing such cruft removes a lot of confusion.

Edward
Reply all
Reply to author
Forward
0 new messages