-
Notifications
You must be signed in to change notification settings - Fork 29
ShoesSpecDSL.md
You can find the current Scarpe-Webview implementation of the ShoesSpec testing APIs in the appropriate GitHub repo.
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.
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.
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.
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.
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.
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.
We're using Minitest directly. Capybara and its DSL is another significant influence on this DSL work.