Extend the testcase widget to run tests

This commit is contained in:
Jeremy Ruston 2024-05-05 13:54:02 +01:00
parent 22ad43954e
commit 57e74a0ad5
5 changed files with 151 additions and 13 deletions

View File

@ -79,8 +79,52 @@ TestCaseWidget.prototype.render = function(parent,nextSibling) {
this.setVariable("transclusion",$tw.utils.hashString(jsonPayload));
// Generate a `payloadTiddlers` variable that contains the payload in JSON format
this.setVariable("payloadTiddlers",jsonPayload);
// Render the test rendering if required
if(this.testcaseTestOutput && this.testcaseTestExpectedResult) {
var testcaseOutputContainer = $tw.fakeDocument.createElement("div");
var testcaseOutputWidget = this.testcaseWiki.makeTranscludeWidget(this.testcaseTestOutput,{
document: $tw.fakeDocument,
parseAsInline: false,
parentWidget: this,
variables: {
currentTiddler: this.testcaseTestOutput
}
});
testcaseOutputWidget.render(testcaseOutputContainer);
}
// Clear changes queue
this.testcaseWiki.clearTiddlerEventQueue();
// Run the actions if provided
if(this.testcaseWiki.tiddlerExists(this.testcaseTestActions)) {
testcaseOutputWidget.invokeActionString(this.testcaseWiki.getTiddlerText(this.testcaseTestActions));
testcaseOutputWidget.refresh(this.testcaseWiki.changedTiddlers,testcaseOutputContainer);
}
// Set up the test result variables
var testResult = "",
outputHTML = "",
expectedHTML = "";
if(this.testcaseTestOutput && this.testcaseTestExpectedResult) {
outputHTML = testcaseOutputContainer.children[0].innerHTML;
expectedHTML = this.testcaseWiki.getTiddlerText(this.testcaseTestExpectedResult);
if(outputHTML === expectedHTML) {
testResult = "pass";
} else {
testResult = "fail";
}
this.setVariable("outputHTML",outputHTML);
this.setVariable("expectedHTML",expectedHTML);
this.setVariable("testResult",testResult);
}
// Don't display anything if testHideIfPass is "yes" and the tests have passed
if(this.testcaseHideIfPass === "yes" && testResult === "pass") {
return;
}
// Render the page root template of the subwiki
var rootWidget = this.testcaseWiki.makeTranscludeWidget(this.testcaseTemplate,{document: this.document, parseAsInline: false, parentWidget: this});
var rootWidget = this.testcaseWiki.makeTranscludeWidget(this.testcaseTemplate,{
document: this.document,
parseAsInline: false,
parentWidget: this
});
rootWidget.render(domNode);
// Trap changes in the wiki and refresh the rendering
this.testcaseWiki.addEventListener("change",function(changes) {
@ -93,6 +137,10 @@ Compute the internal state of the widget
*/
TestCaseWidget.prototype.execute = function() {
this.testcaseTemplate = this.getAttribute("template","$:/core/ui/testcases/DefaultTemplate");
this.testcaseTestOutput = this.getAttribute("testOutput");
this.testcaseTestActions = this.getAttribute("testActions");
this.testcaseTestExpectedResult = this.getAttribute("testExpectedResult");
this.testcaseHideIfPass = this.getAttribute("testHideIfPass");
};
/*

View File

@ -6,8 +6,27 @@ title: $:/core/ui/testcases/DefaultTemplate
>
<div class="tc-testcase-wrapper">
<div class="tc-testcase-header">
<h2><$transclude tiddler="Description" mode="inline"/></h2>
<h2>
<%if [<testResult>!match[]] %>
<span class={{{ tc-testcase-result-icon [<testResult>!match[fail]then[tc-testcase-result-icon-pass]] [<testResult>match[fail]then[tc-testcase-result-icon-fail]] +[join[ ]] }}}>
<%if [<testResult>!match[fail]] %>
{{$:/core/images/done-button}}
<%else%>
{{$:/core/images/close-button}}
<%endif%>
</span>
<%endif%>
<$view tiddler="Description" mode="inline"/>
</h2>
</div>
<%if [<testResult>match[fail]] %>
<div class="tc-testcase-result-fail">
<div>
TEST FAILED
</div>
<$diff-text source=<<expectedHTML>> dest=<<outputHTML>>/>
</div>
<%endif%>
<div class="tc-testcase-panes">
<div class="tc-testcase-source">
<$macrocall $name="tabs" tabsList="[all[tiddlers]sort[]] -[prefix<state>] -Description -ExpectedResult -Output Output +[putfirst[]] -[has[plugin-type]]" state=<<state>> default="Output" template="$:/core/ui/testcases/DefaultTemplate/Source"/>
@ -15,7 +34,7 @@ title: $:/core/ui/testcases/DefaultTemplate
<div class="tc-testcase-divider">
</div>
<div class="tc-testcase-output">
<$transclude tiddler="Output"/>
<$transclude $tiddler="Output" $mode="block"/>
</div>
</div>
</div>

View File

@ -1,6 +1,7 @@
title: $:/core/ui/testcases/DefaultTemplate/Source
\whitespace trim
\procedure body()
<$list filter="[<currentTab>fields[]] -text +[limit[1]]" variable="ignore">
<table class="tc-field-table">
<tbody>
@ -18,3 +19,6 @@ title: $:/core/ui/testcases/DefaultTemplate/Source
</table>
</$list>
<$edit class="tc-edit-texteditor" tiddler=<<currentTab>>/>
\end
<$transclude $variable="body" $mode="inline"/>

View File

@ -9,24 +9,46 @@ type: text/vnd.tiddlywiki
The testcase widget creates an independent subwiki loaded with the specified payload tiddlers and then renders a specified template from within the subwiki.The default template displays a split view with the source tiddlers on the left and the rendered tiddler titled `Output` on the right. It also displays the tiddler titled `Description` as the heading. This makes it possible to run independent tests that also serve as documentation examples.
The testcase widget can optionally also be used to run and verify test results within the subwiki.
The testcase widget creates a lightweight TiddlyWiki environment with the following important limitations:
* Output is rendered into a DIV, and so cannot be styled independently of the host wiki
* Any changes to the wiki made interactively by the user are volatile, and are lost when the testcase widget is refreshed
* Startup actions are not supported
* Only plugins available in the host wiki can be included in the testcase
The [[Innerwiki Plugin]] offers the ability to embed a fully independent subwiki via an `<iframe>` element, but without the testing related features of the testcase widget.
! Content and Attributes
The content of the `<$testcase>` widget is not displayed but instead is scanned for <<.wlink DataWidget>> widgets that define the payload tiddlers to be included in the test case. The `$:/core` plugin is automatically included in the payload.
The content of the `<$testcase>` widget is not displayed but instead is scanned for <<.wlink DataWidget>> widgets that define the payload tiddlers to be included in the testcase.
|!Attribute |!Description |
|<<.attr template>> |Optional title of the template used to display the testcase (defaults to $:/core/ui/testcases/DefaultTemplate). Note that custom templates will need to be explicitly added to the payload |
|<<.attr testOutput>> |Optional title of the tiddler whose output should be subject to testing (note that both <<.attr testOutput>> and <<.attr testExpectedResult>> must be provided in order for testing to occur) |
|<<.attr testExpectedResult>> |Optional title of the tiddler whose content is the expected result of rendering the output tiddler (note that both <<.attr testOutput>> and <<.attr testExpectedResult>> must be provided in order for testing to occur) |
|<<.attr testActions>> |Optional title of the tiddler containing actions that should be executed before the test occurs |
|<<.attr testHideIfPass>> |If set to "yes", hides the testcase widget if the test passes |
! Payload Tiddlers
The payload tiddlers are the tiddler values that are loaded into the subwiki that is created to run the tests. They are created via <<.wlink DataWidget>> widgets within the body of the `<$testcase>` widget.
The payload tiddlers are the tiddler values that are loaded into the subwiki that is created to run the tests. They are created via <<.wlink DataWidget>> widgets within the body of the `<$testcase>` widget. The `$:/core` plugin is automatically included in the payload.
! State Handling
! Testcase Template Variables
The `<$testcase>` widget sets the variable `transclusion` to a hash that reflects the names and values of all the payload tiddlers. This makes it easier for test case templates to create unique state tiddler titles using the [[qualify Macro]] or QualifyWidget.
The testcase widget makes the following variables available within the rendered template:
! Test Case Conventions
|!Variable |!Description |
|<<.var transclusion>> |A hash that reflects the names and values of all the payload tiddlers. This makes it easier for testcase templates to create unique state tiddler titles using the [[qualify Macro]] or QualifyWidget |
|<<.var payloadTiddlers>> |JSON array of payload tiddler fields |
|<<.var outputHTML>> |The actual output HTML if running tests |
|<<.var expectedHTML>> |The expected output HTML if running tests |
|<<.var testResult>> |The tests result if running tests (may be "pass" or "fail") |
The following conventions are used for test case tiddlers:
! Testcase Conventions
The following conventions are used for testcase tiddlers:
* `Description` contains a brief description of the test (rendered in inline mode)
* `Output` contains the tiddler text to be rendered. It can also reference other tiddlers
@ -35,9 +57,7 @@ The following conventions are used for test case tiddlers:
! Example
<$testcase>
<$data $tiddler="$:/core/ui/testcases/DefaultTemplate"/>
<$data $tiddler="$:/core/ui/testcases/DefaultTemplate/Source"/>
<$data title="Description" text="Simple example of a test case"/>
<$data title="Description" text="Simple example of a testcase"/>
<$data title="Output" text="""<$testcase>
<$data title="Description" text="How to calculate 2 plus 2"/>
<$data title="Output" text="<$text text={{{ [[2]add[2]] }}}/>"/>

View File

@ -3250,6 +3250,40 @@ span.tc-translink > a:first-child {
padding: 0 0.5em;
}
.tc-testcase-divider {
x-background-color: <<colour muted-foreground>>;
}
.tc-testcase-result-icon {
fill: #fff;
padding: 0.25em;
display: inline-block;
line-height: 0;
border-radius: 1em;
vertical-align: bottom;
margin-right: 0.25em;
}
.tc-testcase-result-icon-pass {
background-color: green;
}
.tc-testcase-result-icon-fail {
background-color: red;
}
.tc-testcase-result-icon svg {
width: 0.5em;
height: 0.5em;
}
.tc-testcase-result-fail {
border: 1px solid <<colour foreground>>;
background-color: <<colour background>>;
margin: 1em;
padding: 1em;
}
.tc-testcase-header > h2,
.tc-testcase-source > pre {
margin: 0;
@ -3260,7 +3294,6 @@ span.tc-translink > a:first-child {
}
.tc-testcase-panes {
background: <<colour background>>;
display: flex;
align-items: stretch;
flex-wrap: wrap;
@ -3274,10 +3307,23 @@ span.tc-translink > a:first-child {
min-width: 250px;
}
.tc-testcase-source .tc-tab-content {
background: <<colour background>>;
margin: 0;
}
.tc-testcase-source .tc-field-table {
width: 100%;
}
.tc-testcase-source table.tc-field-table {
margin: 0;
}
.tc-tiddler-frame .tc-edit-texteditor {
margin: 0;
}
.tc-testcase-divider {
flex: 0 0 2%;
}
@ -3287,6 +3333,7 @@ span.tc-translink > a:first-child {
}
.tc-testcase-output {
background: <<colour background>>;
border-radius: 3px;
border: 1px solid <<colour muted-foreground>>;
flex: 1 0 49%;