Exploratory Interfaces alongside unit tests
In this, we'll take some example code – something small-enough that it has unit tests rather than integration tests, and see what it gives us to play with . Then we'll play with it.
We'll take CadenceKeeper as our subject – but we're not going to play with it directly very much.
Instead, we'll play with code : Unit tests : config.js : Exploratory Interface
Exercise 1: look at the code
5 mins
Take a look at the code – identify its public interface (the ways that other code can interact with it – and the information that other code needs to provide and can expect). Have a swift look at config.js to see its programatic context and at CadenceKeeper to see how it's used.
using an LLM to do some work
I asked Claude Haiku 3 Tell me about the public methods that this exposes, and it replied...
The Timer_v2 class exposes the following public methods:
- constructor(name, startTime, uniqueId, interval): This is the constructor method that initializes a new
Timer_v2instance. It takes the following parameters: name: A string representing the name of the timer.startTime: An optionalDateobject representing the start time of the timer (defaults to the current time).uniqueId: An optional unique identifier for the timer (defaults to the current timestamp).interval: An optional interval value from theUPDATE_INTERVALobject, which determines the update interval for the timer (defaults toUPDATE_INTERVAL.SECONDS).
- get elapsedTime(): This is a getter method that returns the elapsed time in milliseconds since the timer was started.
- setupTick(newInterval): This method sets up the timer's tick functionality. It takes an optional
newIntervalparameter to specify a new update interval. If nonewIntervalis provided, it will use thecurrentIntervalproperty. - setupChangeovers(): This method sets up the timer's changeovers from seconds to minutes and from minutes to hours.
- stopUpdates(): This method stops the timer's updates by clearing the interval and timeout IDs.
- static fromObject(obj): This is a static method that creates a new
Timer_v2instance from an object. If the object has anidproperty but nouniqueId, theidproperty is used as theuniqueId.
These public methods allow you to create, manage, and interact with the Timer_v2 instances in your application.
Exercise 2 – have a look at the tests
5 mins
Have a look at the unit tests – is it clear what is being exercised? Not really, but the code of the page might tell you...
another LLM shortcut
I asked `If the following are the unit tests, what public methods might match up with the tests?`, and got back...
Based on the unit tests provided, the following public methods from the Timer_v2 class are likely to be covered:
- constructor(name, startTime, uniqueId, interval): The constructor is tested in the "Construction" suite, where it checks the creation of timers with default and custom values.
- get elapsedTime(): The "It should keep track of elapsed time" suite tests the
elapsedTimegetter method, ensuring that it returns the correct elapsed time. - stopUpdates(): The "Update Control" suite tests the
stopUpdates()method, ensuring that it stops the timer's updates by clearing the interval and timeout IDs. - setupTick(newInterval): The "Interval Changes" suite (though currently marked as "xit") tests the behavior of the
setupTick()method when the timer's age changes, ensuring that the update interval is adjusted accordingly. - static fromObject(obj): The "fromObject" suite tests the static
fromObject()method, ensuring that it can create aTimer_v2instance from a stored object, including handling the legacyidproperty.
Additionally, the "Event Handling" suite tests the timer's response to the timerReset event, which likely involves the setupTick() and startTime properties.
The other public methods, such as setupChangeovers(), are not explicitly tested in the provided unit tests, but they may be covered implicitly or in other test suites not shown here.
Exercise 3 – explore the unit
10 minutes
I had an irritating problem that I saw in CadenceKeeper and couldn't reproduce with the tests. So I asked for an exploratory interface, and played with the code that way...
Go play with the Exploratory Interface. Think about how it helps, and how it complements confirmatory unit tests and code analysis.
Using an LLM to build an exploratory interface
Surely it is ridiculous to build a complex UI and then throw it away. It certainly was ridiculous.
Not any more. Here's the prompt that got me close to this interface...
I want an HTML page, using components in ../components to let me explore Timer_v2.js.js. I want it to have sections containing a mockup UI for
- elements that the code interacts with,
- test UI elements
** to let me trigger all events that it listens to,
** to interface (input and output) with all functions that it exposes,
** to listen to and log any event that it may send
** to show the internal data and state of the object(s) – including any newly-made objects.
I envisage a set of buttons for listened-to events and input areas if those events need a payload, and a set of collapsible sections for each and every exposed function, with any inputs needed for their payload, and the outputs processed to make them readable.
Where an input has default values, I need an alternate interface to pass in specific values.
Offer drop-downs if an input has a limted choice, and also offer an alternate to allow free entry of any data. If an input takes a range, offer an alterntive with a parametric slider between reasonable limits.
Pay attention to the config file config.js , using values from that file – and also put a section containing a list of the values used into the interface, allowing them to be changed.
In terms of look and feel:
- Output data should be in a monospaced font.
- Sections should be collapsible and re-orderable by dragging.
- Show the data exposed by the objects and functions in real time, updated every 100ms – and add a pause+countdown button to stop that refresh for 5s to allow copy.
Conclusion...
Let's share and talk as we play.