DevHarness Module
Driving Issie from outside it, for development and for automated checking.
Published as `window.issieDev` in a debug build, and reached from a terminal through
`scripts/drive.js`. It exists because the alternative - synthesising DOM events and reading
rendered text back - is both slow and wrong often enough to mislead: a click has to find the
right element, a wait has to guess how long a render takes, and text scraped from the DOM can
be a frame out of date while looking authoritative.
Four things, which is what that experience says are needed:
onNextRender when the update has been applied AND the view has run, so a caller can wait
for the app rather than for a stopwatch
send a named command, dispatched as the message the UI would have sent
state what the app currently is, as data rather than as rendered text
simRefs what is holding a simulation, which is the question the DOM cannot answer
`send` takes a NAME from a fixed table rather than a serialised Msg. A Msg is an F# union
carrying models, canvases and functions; nothing useful survives a round trip through JSON, and
a general dispatch-anything surface in a debug build is a hazard for the sake of messages
nobody wanted to send. Add a row to `commands` when a new one is needed - that is the point.
Functions and values
| Function or value | Description |
Full Usage:
forceException kind dispatch
Parameters:
string
dispatch : Msg -> unit
Returns: string
|
Fire the renderer's exception boundary on purpose, so that what it records - and what Info ->
Bug Reports then shows - can be checked rather than assumed. One kind per way an exception can
get out of Issie's own code; see the boundary in Renderer.fs. Reached from the Development
menu and from `drive.js send forceException
|
Full Usage:
forceViewException
Returns: bool
|
Make the view throw, so that the renderer's exception boundary can be fired on purpose - read by `Renderer.view'` and set by the Development menu and the `forceException` command below. Never cleared. A view is a pure function of the model, so a view that throws once throws on every render after it; a flag that cleared itself would be testing something that cannot happen, and would not leave the crash page up long enough to read. Restarting is the way out, as it is for the real thing. Not model state: it exists to break the model's rendering (docs/mutableState.md).
|
|
|
|
|
Full Usage:
renderDone ()
Parameters:
unit
|
Called from the view wrapper, after the view has produced its elements. The callbacks run on the next animation frame rather than immediately: the view returning means React has been given the new elements, not that the DOM holds them, and a caller waiting for a render wants the state it can then read to be the state it was waiting for.
|
A long simulation run ON THE SIDECAR, reported through the same progress bar as a local run. The loop is chunked SimRun requests with a time budget each; after every reply it updates the bar and re-checks ownership on the model, so Cancel takes effect within one chunk. The wave-cursor nudge in CancelWaveSimulation touches only the LOCAL simulation and is a no-op when none exists. |
|
|
|
Full Usage:
setSimulateInRenderer inRenderer dispatch
Parameters:
bool
dispatch : Msg -> unit
|
Choose which simulator runs, throwing away anything currently simulating. The two backends share nothing - not a step array, not a built simulation - so a switch cannot leave one running and hand it to the other. Everything that holds a simulation is therefore ended first: the step simulator, the waveform simulator and the truth table, which between them are what `ModelHelpers.simulationIsOpen` reports. The flag is then set, and the next simulation the user starts is built by whichever backend is now chosen. Here rather than in the menu because both the Development menu and the `simulateIn` harness command need it, and a switch that stopped simulations in one path and not the other would be a good way to end up with a waveform viewer reading a simulation nobody is running.
|