Skip to content

ShoesSpecDSL.md

Noah Gibbs edited this page Nov 2, 2023 · 1 revision

ShoesSpec DSL

You can find the current Scarpe-Webview implementation of the ShoesSpec testing APIs in the appropriate GitHub repo.

Design Notes: App Code and Test Code

You can test a Shoes app by writing a Shoes app that calls normal Shoes APIs and verifies the results. For instance, if you wanted to check that the .text=() setter for a Shoes Button works, you could just set the text and check the result directly in a normal Shoes application.

But most commonly, for Shoes Spec, you'll want to run a certain amount of test code. You'll want assertions to make sure that values are what you expect them to be. You'll want to check display information about drawables: what actual size in pixels were they assigned? Are they currently visible? Do they have focus? You'll want to send events such as button clicks, focus changes and so on.

A normal Shoes app wouldn't commonly do any of those things. In fact, the old Shoes API doesn't really have a way to do any of that.

Instead, Shoes-Spec provides a way to specify test code alongside an application. In that test code you can specify assertions, create artificial events and so on.

Design Notes: Assertions

We need assertions for the Shoes-Spec DSL. We have import/export Scarpe components for Minitest, allowing us to use Minitest as our assertion DSL. It's also easy for somebody else to rebuild the Minitest API as needed -- there's not that much to it. But it's nice to use original Minitest where we can, which should be most places.

Assertion methods will normally only be available from test code. A normal Shoes application can't normally "fail" in quite the same way.

Design Notes: Querying Drawables

We need to be able to look up specific drawables in the tree. Our current API for that looks like this:

button("@b").text()

That first method call is finding the button object -- or a test proxy for it -- to call methods on it. This is the same method name as button() that creates a new button in a Shoes::Slot or Shoes::App, so that means the test's "self" object can't be the Shoes App or a slot.

I'll refer to the object returned by button() and other finder methods as a "proxy" or "drawable proxy" object where I need to refer to it at all. Technically you could just return a drawable (would that 100% work? Or no?), but often a proxy object that forwards method calls to the drawable could make more sense, since it's a test-only object and normal app code can't easily create one.

It's not 100% clear to me if these should have to be proxies in every Shoes application. Could we return the drawable object itself and have it handle all the test APIs? Perhaps test code could be nearly the same as non-test code, with the exception of assertions and querying drawables. Even querying drawables is a perfectly reasonable thing for some applications to want to do.

If so, we'd need different method names. I like the button(:@b).text() syntax, but it doesn't work inside Shoes::App, where "button" is used to create a button, not query an existing one.

Design Notes: Querying Display Information

We'll need methods on the drawable proxies to query display information. It could make a lot of sense to have tests that assert things about the internal HTML of an HTML-based drawable, or whether the parent window is minimised for a drawable in a native window. These things are not part of the ordinary Shoes API, and probably shouldn't be. But it would be reasonable to make assertions only in cases where certain information (e.g. window-is-minimised or innerHTML) is available. I'm not sure how we want to handle these extensions, though respond_to? on known method names is a traditional Ruby answer.

Design Notes: Sending Artificial Events

This is an odd category. Shoes-Spec test code definitely needs to send artificial events. But also, that's a perfectly legitimate thing for a non-test Shoes application to want to do. The most common reasons are accessibility and automation -- one application may want to control another for either of those reasons, or for a number of others.

As such, an artificial event API is probably a good thing to expose to regular applications, not just test code.

Design Notes: Making it Rubyish

I feel like Capybara's DSL isn't amazing for feeling natural and Ruby-like for a GUI. For instance:

# Capybara code
fill_in('First Name', with: 'John')

That checks for a label matching 'First Name' and then fills it with the text. But for Shoes it seems far more natural to hold a reference to a queried object, something more like this-or-similar:

# Possible ShoesSpec API code
edit_line(text: 'First Name').fill_with('John')

It's a bit more verbose. But I think in a good way. It would also allow using Shoes accessors that already exist:

# Possible ShoesSpec API code
edit_line(text: 'First Name').text = 'John'

As well, the query syntax allows for checking in more different ways. Capybara does have some methods that work more like that:

# Capybara code
find_field(id: 'my_field').value

It might make sense to allow an implicit query type on find/fill, but I suspect fuzzy text match (Capybara's default) isn't it.

Design Notes: Influences

We're using Minitest directly. Capybara and its DSL is another significant influence on this DSL work.

Clone this wiki locally