Removed legacy testing frameworks

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@40905 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2007-08-27 05:04:54 +00:00
parent e6da10b30b
commit d01bd2ab3b
73 changed files with 0 additions and 29838 deletions

View File

@ -1,7 +0,0 @@
<?php
class IntegrationTesting extends Controller {
function index() {
Director::redirect("sapphire/selenium/TestRunner.html?test=../../SeleniumTestSuite/&auto=true");
}
}

View File

@ -1,106 +0,0 @@
<!--
Copyright 2004 ThoughtWorks, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>Selenium Functional Test Runner</title>
<link rel="stylesheet" type="text/css" href="selenium.css" />
<script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
<script type="text/javascript" src="scripts/xmlextras.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/html-xpath-patched.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-api.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-commandhandlers.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-executionloop.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-seleneserunner.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/xpath.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
<script language="JavaScript" type="text/javascript">
function openDomViewer() {
var autFrame = document.getElementById('myiframe');
var autFrameDocument = getIframeDocument(autFrame);
var domViewer = window.open('domviewer/domviewer.html');
domViewer.rootDocument = autFrameDocument;
return false;
}
function cleanUp() {
if (LOG != null) {
LOG.close();
}
}
</script>
<script>
</script>
</head>
<body onLoad="runTest()" onUnload="cleanUp()">
<table border="1" style="height: 100%;">
<tr>
<td width="50%" height="30%">
<table>
<tr>
<td>
<img src="selenium-logo.png">
</td>
<td>
<h1><a href="http://selenium.thoughtworks.com" >Selenium</a> Functional Testing for Web Apps</h1>
Open Source From <a href="http://www.thoughtworks.com">ThoughtWorks, Inc</a> and Friends
<form action="">
<br/>Slow Mode:<INPUT TYPE="CHECKBOX" NAME="FASTMODE" VALUE="YES" onmouseup="slowClicked()">
<fieldset>
<legend>Tools</legend>
<button type="button" id="domViewer1" onclick="openDomViewer();">
View DOM
</button>
<button type="button" onclick="LOG.show();">
Show Log
</button>
</fieldset>
</form>
</td>
</tr>
</table>
<form action="">
<label id="context" name="context"></label>
</form>
</td>
<td width="50%" height="30%">
<b>Last Four Test Commands:</b><br/>
<div id="commandList"></div>
</td>
</tr>
<tr>
<td colspan="2" height="70%">
<iframe name="myiframe" id="myiframe" src="" height="100%" width="100%"></iframe>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,79 +0,0 @@
<html>
<head>
<title>Selenium Log Console</title>
<link id="cssLink" rel="stylesheet" href="selenium.css" />
</head>
<body id="logging-console">
<script language="JavaScript">
var logLevels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
var logLevelThreshold = null;
function getThresholdLevel() {
var buttons = document.getElementById('logLevelChooser').level;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].checked) {
return buttons[i].value;
}
}
}
function setThresholdLevel(logLevel) {
logLevelThreshold = logLevel;
var buttons = document.getElementById('logLevelChooser').level;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].value==logLevel) {
buttons[i].checked = true;
}
else {
buttons[i].checked = false;
}
}
}
function append(message, logLevel) {
if (logLevelThreshold==null) {
logLevelThreshold = getThresholdLevel();
}
if (logLevels[logLevel] < logLevels[logLevelThreshold]) {
return;
}
var log = document.getElementById('log');
var newEntry = document.createElement('li');
newEntry.className = logLevel;
newEntry.appendChild(document.createTextNode(message));
log.appendChild(newEntry);
if (newEntry.scrollIntoView) {
newEntry.scrollIntoView();
}
}
</script>
<div id="banner">
<form id="logLevelChooser">
<input id="level-error" type="radio" name="level"
value="error" /><label for="level-error">Error</label>
<input id="level-warn" type="radio" name="level"
value="warn" /><label for="level-warn">Warn</label>
<input id="level-info" type="radio" name="level" checked="yes"
value="info" /><label for="level-info">Info</label>
<input id="level-debug" type="radio" name="level"
value="debug" /><label for="level-debug">Debug</label>
</form>
<h1>Selenium Log Console</h1>
</div>
<ul id="log"></ul>
</body>
</html>

View File

@ -1,35 +0,0 @@
<?php
class SeleniumTestSuite extends Controller {
function index() {
echo <<<HTML
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>Test Suite</title>
</head>
<body>
<table id="suiteTable" cellpadding="1"
cellspacing="1"
border="1">
<tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td><a href="../cms/tests/LoginTest.html">LoginTest</a></td></tr>
<tr><td><a href="../cms/tests/SaveAndLoadTest.html">SaveAndLoadTest</a></td></tr>
</tbody>
</table>
</body>
</html>
HTML;
}
}
?>

View File

@ -1,93 +0,0 @@
<html>
<!--
Copyright 2004 ThoughtWorks, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>Select a Test Suite</title>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
<script>
function load() {
if (browserVersion.isHTA) {
document.getElementById("save-div").style.display = "inline";
}
}
function autoCheck() {
var auto = document.getElementById("auto");
var autoDiv = document.getElementById("auto-div");
if (auto.checked) {
autoDiv.style.display = "inline";
} else {
autoDiv.style.display = "none";
}
}
function saveCheck() {
var results = document.getElementById("results");
var check = document.getElementById("save").checked;
if (check) {
results.firstChild.nodeValue = "Results file ";
document.getElementById("resultsUrl").value = "results.html";
} else {
results.firstChild.nodeValue = "Results URL ";
document.getElementById("resultsUrl").value = "../postResults";
}
}
function go() {
var inputs = document.getElementsByTagName("input");
var queryString = "";
for (var i = 0; i < inputs.length; i++) {
var elem = inputs[i];
var name = elem.name;
var value = elem.value;
if (elem.checked) {
value = "true";
}
queryString += escape(name) + "=" + escape(value);
if (i < (inputs.length - 1)) {
queryString += "&";
}
}
window.parent.queryString = queryString;
window.parent.loadSuiteFrame();
return false;
}
</script>
</head>
<body onload="load()" style="font-size: x-small">
<form id="prompt" target="_top" method="GET" onsubmit="return go();" action="TestRunner.html">
<p>Select an HTML test suite:</p>
<input id="test" name="test" size="30" value="../../SeleniumTestSuite/"/>
<div><label for="auto">Auto-run </label><input id="auto" type="checkbox" name="auto" value="false" onclick="autoCheck();"/>
<div id="auto-div" style="display: none">
<label for="close" >Close afterwards </label><input id="close" type="checkbox" name="close" />
<div id="save-div" style="display: none">
<br/><label for="save">Save to file </label><input id="save" type="checkbox" name="save" onclick="saveCheck();"/>
</div>
</div>
<p id="results" >Results URL <input id="resultsUrl" name="resultsUrl" value="../postResults"/></p>
</div>
<p><input type="submit" value="go"/></p>
</form>
</body>
</html>

View File

@ -1,55 +0,0 @@
<!--
Copyright 2005 ThoughtWorks, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<link rel="stylesheet" type="text/css" href="selenium.css" />
<body>
<table width="100%">
<tr>
<th>&uarr;</th>
<th>&uarr;</th>
<th>&uarr;</th>
</tr>
<tr>
<th width="25%">Test Suite</th>
<th width="50%">Current Test</th>
<th width="25%">Control Panel</th>
</tr>
<tr><td>&nbsp;</td></tr>
<tr>
<td></td>
<td class="selenium splash">
<img src="selenium-logo.png" align="right">
<h1>Selenium</h1>
<h2>by <a href="http://www.thoughtworks.com">ThoughtWorks</a> and friends</h2>
<p>
For more information on Selenium, visit
<pre>
<a href="http://selenium.openqa.org" target="_blank">http://selenium.openqa.org</a>
</pre>
</td>
<tr>
</table>
</body>
</html>

View File

@ -1,148 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<HTA:APPLICATION ID="SeleniumHTARunner" APPLICATIONNAME="Selenium" >
<!-- the previous line is only relevant if you rename this
file to "TestRunner.hta" -->
<!-- The copyright notice and other comments have been moved to after the HTA declaration,
to work-around a bug in IE on Win2K whereby the HTA application doesn't function correctly -->
<!--
Copyright 2004 ThoughtWorks, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
<title>Selenium Functional Test Runner</title>
<link rel="stylesheet" type="text/css" href="selenium.css" />
<script language="JavaScript" type="text/javascript" src="scripts/html-xpath-patched.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-api.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-commandhandlers.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-executionloop.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-testrunner.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/xpath.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
<script language="JavaScript" type="text/javascript">
function openDomViewer() {
var autFrame = document.getElementById('myiframe');
var autFrameDocument = getIframeDocument(autFrame);
this.rootDocument = autFrameDocument;
var domViewer = window.open('domviewer/domviewer.html');
return false;
}
</script>
</head>
<body onload="start();">
<table class="layout">
<form action="" name="controlPanel">
<!-- Suite, Test, Control Panel -->
<tr class="selenium">
<td width="25%" height="30%" rowspan="2"><iframe name="testSuiteFrame" id="testSuiteFrame" src="./TestPrompt.html" application="yes"></iframe></td>
<td width="50%" height="30%" rowspan="2"><iframe name="testFrame" id="testFrame" application="yes"></iframe></td>
<th width="25%" height="1" class="header">
<h1><a href="http://selenium.thoughtworks.com" title="The Selenium Project">Selenium</a> TestRunner</h1>
</th>
</tr>
<tr class="selenium">
<td width="25%" height="30%" id="controlPanel">
<fieldset>
<legend>Execute Tests</legend>
<div>
<input id="modeRun" type="radio" name="runMode" value="0" checked="checked"/><label for="modeRun">Run</label>
<input id="modeWalk" type="radio" name="runMode" value="500" /><label for="modeWalk">Walk</label>
<input id="modeStep" type="radio" name="runMode" value="-1" /><label for="modeStep">Step</label>
</div>
<div>
<button type="button" id="runSuite" onclick="startTestSuite();"
title="Run the entire Test-Suite">
<strong>All</strong>
</button>
<button type="button" id="runTest" onclick="runSingleTest();"
title="Run the current Test">
<em>Selected</em>
</button>
<button type="button" id="continueTest" disabled="disabled"
title="Continue the Test">
Continue
</button>
</div>
</fieldset>
<table id="stats" align="center">
<tr>
<td colspan="2" align="right">Elapsed:</td>
<td id="elapsedTime" colspan="2">00.00</td>
</tr>
<tr>
<th colspan="2">Tests</th>
<th colspan="2">Commands</th>
</tr>
<tr>
<td class="count" id="testRuns">0</td>
<td>run</td>
<td class="count" id="commandPasses">0</td>
<td>passed</td>
</tr>
<tr>
<td class="count" id="testFailures">0</td>
<td>failed</td>
<td class="count" id="commandFailures">0</td>
<td>failed</td>
</tr>
<tr>
<td colspan="2"></td>
<td class="count" id="commandErrors">0</td>
<td>incomplete</td>
</tr>
</table>
<fieldset>
<legend>Tools</legend>
<button type="button" id="domViewer1" onclick="openDomViewer();">
View DOM
</button>
<button type="button" onclick="LOG.show();">
Show Log
</button>
</fieldset>
</td>
</tr>
<!-- AUT -->
<tr>
<td colspan="3" height="70%"><iframe name="myiframe" id="myiframe" src="TestRunner-splash.html"></iframe></td>
</tr>
</form>
</table>
</body>
</html>

View File

@ -1,148 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<HTA:APPLICATION ID="SeleniumHTARunner" APPLICATIONNAME="Selenium" >
<!-- the previous line is only relevant if you rename this
file to "TestRunner.hta" -->
<!-- The copyright notice and other comments have been moved to after the HTA declaration,
to work-around a bug in IE on Win2K whereby the HTA application doesn't function correctly -->
<!--
Copyright 2004 ThoughtWorks, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
<title>Selenium Functional Test Runner</title>
<link rel="stylesheet" type="text/css" href="selenium.css" />
<script language="JavaScript" type="text/javascript" src="scripts/html-xpath-patched.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-api.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-commandhandlers.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-executionloop.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-testrunner.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/xpath.js"></script>
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
<script language="JavaScript" type="text/javascript">
function openDomViewer() {
var autFrame = document.getElementById('myiframe');
var autFrameDocument = getIframeDocument(autFrame);
this.rootDocument = autFrameDocument;
var domViewer = window.open('domviewer/domviewer.html');
return false;
}
</script>
</head>
<body onload="start();">
<form action="" name="controlPanel">
<table class="layout">
<!-- Suite, Test, Control Panel -->
<tr class="selenium">
<td width="25%" height="30%" rowspan="2"><iframe name="testSuiteFrame" id="testSuiteFrame" src="./TestPrompt.html" application="yes"></iframe></td>
<td width="50%" height="30%" rowspan="2"><iframe name="testFrame" id="testFrame" application="yes"></iframe></td>
<th width="25%" height="1" class="header">
<h1><a href="http://selenium.thoughtworks.com" title="The Selenium Project">Selenium</a> TestRunner</h1>
</th>
</tr>
<tr class="selenium">
<td width="25%" height="30%" id="controlPanel">
<fieldset>
<legend>Execute Tests</legend>
<div>
<input id="modeRun" type="radio" name="runMode" value="0" checked="checked"/><label for="modeRun">Run</label>
<input id="modeWalk" type="radio" name="runMode" value="500" /><label for="modeWalk">Walk</label>
<input id="modeStep" type="radio" name="runMode" value="-1" /><label for="modeStep">Step</label>
</div>
<div>
<button type="button" id="runSuite" onclick="startTestSuite();"
title="Run the entire Test-Suite">
<strong>All</strong>
</button>
<button type="button" id="runTest" onclick="runSingleTest();"
title="Run the current Test">
<em>Selected</em>
</button>
<button type="button" id="continueTest" disabled="disabled"
title="Continue the Test">
Continue
</button>
</div>
</fieldset>
<table id="stats" align="center">
<tr>
<td colspan="2" align="right">Elapsed:</td>
<td id="elapsedTime" colspan="2">00.00</td>
</tr>
<tr>
<th colspan="2">Tests</th>
<th colspan="2">Commands</th>
</tr>
<tr>
<td class="count" id="testRuns">0</td>
<td>run</td>
<td class="count" id="commandPasses">0</td>
<td>passed</td>
</tr>
<tr>
<td class="count" id="testFailures">0</td>
<td>failed</td>
<td class="count" id="commandFailures">0</td>
<td>failed</td>
</tr>
<tr>
<td colspan="2"></td>
<td class="count" id="commandErrors">0</td>
<td>incomplete</td>
</tr>
</table>
<fieldset>
<legend>Tools</legend>
<button type="button" id="domViewer1" onclick="openDomViewer();">
View DOM
</button>
<button type="button" onclick="LOG.show();">
Show Log
</button>
</fieldset>
</td>
</tr>
<!-- AUT -->
<tr>
<td colspan="3" height="70%"><iframe name="myiframe" id="myiframe" src="TestRunner-splash.html"></iframe></td>
</tr>
</table>
</form>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

View File

@ -1,298 +0,0 @@
/******************************************************************************
* Defines default styles for site pages. *
******************************************************************************/
.hidden {
display: none;
}
img{
display: inline;
border: none;
}
.box{
background: #fcfcfc;
border: 1px solid #000;
border-color: blue;
color: #000000;
margin: 10px auto;
padding: 3px;
vertical-align: bottom;
}
a {
text-decoration: none;
}
body {
background-color: #ffffff;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 10pt;
}
h2 {
font-size: 140%;
}
h3 {
font-size: 120%;
}
h4 {
font-size: 100%;
}
pre {
font-family: Courier New, Courier, monospace;
font-size: 80%;
}
td, th {
font-family: Arial, Helvetica, sans-serif;
font-size: 10pt;
text-align: left;
vertical-align: top;
}
th {
font-weight: bold;
vertical-align: bottom;
}
ul {
list-style-type: square;
}
#demoBox {
border-color: #000000;
border-style: solid;
border-width: 1px;
padding: 8px;
width: 24em;
}
.footer {
margin-bottom: 0px;
text-align: center;
}
/* Boxed table styles */
table.boxed {
border-spacing: 2px;
empty-cells: hide;
}
td.boxed, th.boxed, th.boxedHeader {
background-color: #ffffff;
border-color: #000000;
border-style: solid;
border-width: 1px;
color: #000000;
padding: 2px;
padding-left: 8px;
padding-right: 8px;
}
th.boxed {
background-color: #c0c0c0;
}
th.boxedHeader {
background-color: #808080;
color: #ffffff;
}
a.object {
color: #0000ff;
}
li {
white-space: nowrap;
}
ul {
list-style-type: square;
margin-left: 0px;
padding-left: 1em;
}
.boxlevel1{
background: #FFD700;
}
.boxlevel2{
background: #D2691E;
}
.boxlevel3{
background: #DCDCDC;
}
.boxlevel4{
background: #F5F5F5;
}
.boxlevel5{
background: #BEBEBE;
}
.boxlevel6{
background: #D3D3D3;
}
.boxlevel7{
background: #A9A9A9;
}
.boxlevel8{
background: #191970;
}
.boxlevel9{
background: #000080;
}
.boxlevel10{
background: #6495ED;
}
.boxlevel11{
background: #483D8B;
}
.boxlevel12{
background: #6A5ACD;
}
.boxlevel13{
background: #7B68EE;
}
.boxlevel14{
background: #8470FF;
}
.boxlevel15{
background: #0000CD;
}
.boxlevel16{
background: #4169E1;
}
.boxlevel17{
background: #0000FF;
}
.boxlevel18{
background: #1E90FF;
}
.boxlevel19{
background: #00BFFF;
}
.boxlevel20{
background: #87CEEB;
}
.boxlevel21{
background: #B0C4DE;
}
.boxlevel22{
background: #ADD8E6;
}
.boxlevel23{
background: #00CED1;
}
.boxlevel24{
background: #48D1CC;
}
.boxlevel25{
background: #40E0D0;
}
.boxlevel26{
background: #008B8B;
}
.boxlevel27{
background: #00FFFF;
}
.boxlevel28{
background: #E0FFFF;
}
.boxlevel29{
background: #5F9EA0;
}
.boxlevel30{
background: #66CDAA;
}
.boxlevel31{
background: #7FFFD4;
}
.boxlevel32{
background: #006400;
}
.boxlevel33{
background: #556B2F;
}
.boxlevel34{
background: #8FBC8F;
}
.boxlevel35{
background: #2E8B57;
}
.boxlevel36{
background: #3CB371;
}
.boxlevel37{
background: #20B2AA;
}
.boxlevel38{
background: #00FF7F;
}
.boxlevel39{
background: #7CFC00;
}
.boxlevel40{
background: #90EE90;
}
.boxlevel41{
background: #00FF00;
}
.boxlevel41{
background: #7FFF00;
}
.boxlevel42{
background: #00FA9A;
}
.boxlevel43{
background: #ADFF2F;
}
.boxlevel44{
background: #32CD32;
}

View File

@ -1,16 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>DOM Viewer</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link rel="stylesheet" type="text/css" href="domviewer.css"/>
<script type="text/javascript" src="selenium-domviewer.js"></script>
</head>
<body onload="loadDomViewer();">
<h3>DOM Viewer</h3>
<p> This page is generated using JavaScript. If you see this text, your
browser doesn't support JavaScript.</p>
</body>
</html>

View File

@ -1,188 +0,0 @@
var HIDDEN="hidden";
var LEVEL = "level";
var PLUS_SRC="butplus.gif";
var MIN_SRC="butmin.gif";
var newRoot;
var maxColumns=1;
function loadDomViewer() {
// See if the rootDocument variable has been set on this window.
var rootDocument = window.rootDocument;
// If not look to the opener for an explicity rootDocument variable, otherwise, use the opener document
if (!rootDocument && window.opener) {
rootDocument = window.opener.rootDocument || window.opener.document;
}
if (rootDocument) {
document.body.innerHTML = displayDOM(rootDocument);
}
else {
document.body.innerHTML = "<b>Must specify rootDocument for window. This can be done by setting the rootDocument variable on this window, or on the opener window for a popup window.</b>";
}
}
function displayDOM(root){
var str = "";
str+="<table>";
str += treeTraversal(root,0);
// to make table columns work well.
str += "<tr>";
for (var i=0; i < maxColumns; i++) {
str+= "<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>";
}
str += "</tr>";
str += "</table>";
return str;
}
function checkForChildren(element){
if(!element.hasChildNodes())
return false;
var nodes = element.childNodes;
var size = nodes.length;
var count=0;
for(var i=0; i< size; i++){
var node = nodes.item(i);
//if(node.toString()=="[object Text]"){
//this is equalent to the above
//but will work with more browsers
if(node.nodeType!=1){
count++;
}
}
if(count == size)
return false;
else
return true;
}
function treeTraversal(root, level){
var str = "";
var nodes= null;
var size = null;
//it is supposed to show the last node,
//but the last node is always nodeText type
//and we don't show it
if(!root.hasChildNodes())
return "";//displayNode(root,level,false);
nodes = root.childNodes;
size = nodes.length;
for(var i=0; i< size; i++){
var element = nodes.item(i);
//if the node is textNode, don't display
if(element.nodeType==1){
str+= displayNode(element,level,checkForChildren(element));
str+=treeTraversal(element, level+1);
}
}
return str;
}
function displayNode(element, level, isLink){
nodeContent = getNodeContent(element);
columns = Math.round((nodeContent.length / 12) + 0.5);
if (columns + level > maxColumns) {
maxColumns = columns + level;
}
var str ="<tr class='"+LEVEL+level+"'>";
for (var i=0; i < level; i++)
str+= "<td> </td>";
str+="<td colspan='"+ columns +"' class='box"+" boxlevel"+level+"' >";
if(isLink){
str+='<a onclick="hide(this);return false;" href="javascript:void();">';
str+='<img src="'+MIN_SRC+'" />';
}
str += nodeContent;
if(isLink)
str+="</a></td></tr>";
return str;
}
function getNodeContent(element) {
str = "";
id ="";
if (element.id != null && element.id != "") {
id = " ID(" + element.id +")";
}
name ="";
if (element.name != null && element.name != "") {
name = " NAME(" + element.name + ")";
}
value ="";
if (element.value != null && element.value != "") {
value = " VALUE(" + element.value + ")";
}
href ="";
if (element.href != null && element.href != "") {
href = " HREF(" + element.href + ")";
}
text ="";
if (element.text != null && element.text != "" && element.text != "undefined") {
text = " #TEXT(" + trim(element.text) +")";
}
str+=" <b>"+ element.nodeName + id + name + value + href + text + "</b>";
return str;
}
function trim(val) {
val2 = val.substring(0,20) + " ";
var spaceChr = String.fromCharCode(32);
var length = val2.length;
var retVal = "";
var ix = length -1;
while(ix > -1){
if(val2.charAt(ix) == spaceChr) {
} else {
retVal = val2.substring(0, ix +1);
break;
}
ix = ix-1;
}
if (val.length > 20) {
retVal += "...";
}
return retVal;
}
function hide(hlink){
var isHidden = false;
var image = hlink.firstChild;
if(image.src.toString().indexOf(MIN_SRC)!=-1){
image.src=PLUS_SRC;
isHidden=true;
}else{
image.src=MIN_SRC;
}
var rowObj= hlink.parentNode.parentNode;
var rowLevel = parseInt(rowObj.className.substring(LEVEL.length));
var sibling = rowObj.nextSibling;
var siblingLevel = sibling.className.substring(LEVEL.length);
if(siblingLevel.indexOf(HIDDEN)!=-1){
siblingLevel = siblingLevel.substring(0,siblingLevel.length - HIDDEN.length-1);
}
siblingLevel=parseInt(siblingLevel);
while(sibling!=null && rowLevel<siblingLevel){
if(isHidden){
sibling.className += " "+ HIDDEN;
}else if(!isHidden && sibling.className.indexOf(HIDDEN)!=-1){
var str = sibling.className;
sibling.className=str.substring(0, str.length - HIDDEN.length-1);
}
sibling = sibling.nextSibling;
siblingLevel = parseInt(sibling.className.substring(LEVEL.length));
}
}
function LOG(message) {
window.opener.LOG.warn(message);
}

View File

@ -1,548 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<apidoc>
<top>Defines an object that runs Selenium commands.
<h3><a name="locators"></a>Element Locators</h3>
<p>
Element Locators tell Selenium which HTML element a command refers to.
The format of a locator is:</p>
<blockquote>
<em>locatorType</em><strong>=</strong><em>argument</em>
</blockquote>
<p>
We support the following strategies for locating elements:
</p>
<blockquote>
<dl>
<dt><strong>identifier</strong>=<em>id</em></dt>
<dd>Select the element with the specified &#64;id attribute. If no match is
found, select the first element whose &#64;name attribute is <em>id</em>.
(This is normally the default; see below.)</dd>
<dt><strong>id</strong>=<em>id</em></dt>
<dd>Select the element with the specified &#64;id attribute.</dd>
<dt><strong>name</strong>=<em>name</em></dt>
<dd>Select the first element with the specified &#64;name attribute.</dd>
<dd><ul class="first last simple">
<li>username</li>
<li>name=username</li>
</ul>
</dd>
<dd>The name may optionally be followed by one or more <em>element-filters</em>, separated from the name by whitespace. If the <em>filterType</em> is not specified, <strong>value</strong> is assumed.</dd>
<dd><ul class="first last simple">
<li>name=flavour value=chocolate</li>
</ul>
</dd>
<dt><strong>dom</strong>=<em>javascriptExpression</em></dt>
<dd>
<dd>Find an element using JavaScript traversal of the HTML Document Object
Model. DOM locators <em>must</em> begin with &quot;document.&quot;.
<ul class="first last simple">
<li>dom=document.forms['myForm'].myDropdown</li>
<li>dom=document.images[56]</li>
</ul>
</dd>
</dd>
<dt><strong>xpath</strong>=<em>xpathExpression</em></dt>
<dd>Locate an element using an XPath expression.
<ul class="first last simple">
<li>xpath=//img[&#64;alt='The image alt text']</li>
<li>xpath=//table[&#64;id='table1']//tr[4]/td[2]</li>
</ul>
</dd>
<dt><strong>link</strong>=<em>textPattern</em></dt>
<dd>Select the link (anchor) element which contains text matching the
specified <em>pattern</em>.
<ul class="first last simple">
<li>link=The link text</li>
</ul>
</dd>
</dl>
</blockquote>
<p>
Without an explicit locator prefix, Selenium uses the following default
strategies:
</p>
<ul class="simple">
<li><strong>dom</strong>, for locators starting with &quot;document.&quot;</li>
<li><strong>xpath</strong>, for locators starting with &quot;//&quot;</li>
<li><strong>identifier</strong>, otherwise</li>
</ul>
<h3><a name="element-filters">Element Filters</a></h3>
<blockquote>
<p>Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator.</p>
<p>Filters look much like locators, ie.</p>
<blockquote>
<em>filterType</em><strong>=</strong><em>argument</em></blockquote>
<p>Supported element-filters are:</p>
<p><strong>value=</strong><em>valuePattern</em></p>
<blockquote>
Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons.</blockquote>
<p><strong>index=</strong><em>index</em></p>
<blockquote>
Selects a single element based on its position in the list (offset from zero).</blockquote>
</blockquote>
<h3><a name="patterns"></a>String-match Patterns</h3>
<p>
Various Pattern syntaxes are available for matching string values:
</p>
<blockquote>
<dl>
<dt><strong>glob:</strong><em>pattern</em></dt>
<dd>Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a
kind of limited regular-expression syntax typically used in command-line
shells. In a glob pattern, "*" represents any sequence of characters, and "?"
represents any single character. Glob patterns match against the entire
string.</dd>
<dt><strong>regexp:</strong><em>regexp</em></dt>
<dd>Match a string using a regular-expression. The full power of JavaScript
regular-expressions is available.</dd>
<dt><strong>exact:</strong><em>string</em></dt>
<dd>Match a string exactly, verbatim, without any of that fancy wildcard
stuff.</dd>
</dl>
</blockquote>
<p>
If no pattern prefix is specified, Selenium assumes that it's a "glob"
pattern.
</p></top>
<function name="click">
<param name="locator">an element locator</param>
<comment>Clicks on a link, button, checkbox or radio button. If the click action
causes a new page to load (like a link usually does), call
waitForPageToLoad.</comment>
</function>
<function name="fireEvent">
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="eventName">the event name, e.g. "focus" or "blur"</param>
<comment>Explicitly simulate an event, to trigger the corresponding &quot;on<em>event</em>&quot;
handler.</comment>
</function>
<function name="keyPress">
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="keycode">the numeric keycode of the key to be pressed, normally the
ASCII value of that key.</param>
<comment>Simulates a user pressing and releasing a key.</comment>
</function>
<function name="keyDown">
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="keycode">the numeric keycode of the key to be pressed, normally the
ASCII value of that key.</param>
<comment>Simulates a user pressing a key (without releasing it yet).</comment>
</function>
<function name="keyUp">
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="keycode">the numeric keycode of the key to be released, normally the
ASCII value of that key.</param>
<comment>Simulates a user releasing a key.</comment>
</function>
<function name="mouseOver">
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Simulates a user hovering a mouse over the specified element.</comment>
</function>
<function name="mouseDown">
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
the specified element.</comment>
</function>
<function name="type">
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="value">the value to type</param>
<comment>Sets the value of an input field, as though you typed it in.
<p>Can also be used to set the value of combo boxes, check boxes, etc. In these cases,
value should be the value of the option selected, not the visible text.</p></comment>
</function>
<function name="check">
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Check a toggle-button (checkbox/radio)</comment>
</function>
<function name="uncheck">
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Uncheck a toggle-button (checkbox/radio)</comment>
</function>
<function name="select">
<param name="locator">an <a href="#locators">element locator</a> identifying a drop-down menu</param>
<param name="optionLocator">an option locator (a label by default)</param>
<comment>Select an option from a drop-down using an option locator.
<p>
Option locators provide different ways of specifying options of an HTML
Select element (e.g. for selecting a specific option, or for asserting
that the selected option satisfies a specification). There are several
forms of Select Option Locator.
</p>
<dl>
<dt><strong>label</strong>=<em>labelPattern</em></dt>
<dd>matches options based on their labels, i.e. the visible text. (This
is the default.)
<ul class="first last simple">
<li>label=regexp:^[Oo]ther</li>
</ul>
</dd>
<dt><strong>value</strong>=<em>valuePattern</em></dt>
<dd>matches options based on their values.
<ul class="first last simple">
<li>value=other</li>
</ul>
</dd>
<dt><strong>id</strong>=<em>id</em></dt>
<dd>matches options based on their ids.
<ul class="first last simple">
<li>id=option1</li>
</ul>
</dd>
<dt><strong>index</strong>=<em>index</em></dt>
<dd>matches an option based on its index (offset from zero).
<ul class="first last simple">
<li>index=2</li>
</ul>
</dd>
</dl>
<p>
If no option locator prefix is provided, the default behaviour is to match on <strong>label</strong>.
</p></comment>
</function>
<function name="addSelection">
<param name="locator">an <a href="#locators">element locator</a> identifying a multi-select box</param>
<param name="optionLocator">an option locator (a label by default)</param>
<comment>Add a selection to the set of selected options in a multi-select element using an option locator.
@see #doSelect for details of option locators</comment>
</function>
<function name="removeSelection">
<param name="locator">an <a href="#locators">element locator</a> identifying a multi-select box</param>
<param name="optionLocator">an option locator (a label by default)</param>
<comment>Remove a selection from the set of selected options in a multi-select element using an option locator.
@see #doSelect for details of option locators</comment>
</function>
<function name="submit">
<param name="formLocator">an <a href="#locators">element locator</a> for the form you want to submit</param>
<comment>Submit the specified form. This is particularly useful for forms without
submit buttons, e.g. single-input "Search" forms.</comment>
</function>
<function name="open">
<param name="url">the URL to open; may be relative or absolute</param>
<comment>Opens an URL in the test frame. This accepts both relative and absolute
URLs.
The &quot;open&quot; command waits for the page to load before proceeding,
ie. the &quot;AndWait&quot; suffix is implicit.
<em>Note</em>: The URL must be on the same domain as the runner HTML
due to security restrictions in the browser (Same Origin Policy). If you
need to open an URL on another domain, use the Selenium Server to start a
new browser session on that domain.</comment>
</function>
<function name="selectWindow">
<param name="windowID">the JavaScript window ID of the window to select</param>
<comment>Selects a popup window; once a popup window has been selected, all
commands go to that window. To select the main window again, use "null"
as the target.</comment>
</function>
<function name="waitForPopUp">
<param name="windowID">the JavaScript window ID of the window that will appear</param>
<param name="timeout">a timeout in milliseconds, after which the action will return with an error</param>
<comment>Waits for a popup window to appear and load up.</comment>
</function>
<function name="chooseCancelOnNextConfirmation">
<comment>By default, Selenium's overridden window.confirm() function will
return true, as if the user had manually clicked OK. After running
this command, the next call to confirm() will return false, as if
the user had clicked Cancel.</comment>
</function>
<function name="answerOnNextPrompt">
<param name="answer">the answer to give in response to the prompt pop-up</param>
<comment>Instructs Selenium to return the specified answer string in response to
the next JavaScript prompt [window.prompt()].</comment>
</function>
<function name="goBack">
<comment>Simulates the user clicking the "back" button on their browser.</comment>
</function>
<function name="refresh">
<comment>Simulates the user clicking the "Refresh" button on their browser.</comment>
</function>
<function name="close">
<comment>Simulates the user clicking the "close" button in the titlebar of a popup
window or tab.</comment>
</function>
<function name="isAlertPresent">
<return type="boolean">true if there is an alert</return>
<comment>Has an alert occurred?
<p>
This function never throws an exception
</p></comment>
</function>
<function name="isPromptPresent">
<return type="boolean">true if there is a pending prompt</return>
<comment>Has a prompt occurred?
<p>
This function never throws an exception
</p></comment>
</function>
<function name="isConfirmationPresent">
<return type="boolean">true if there is a pending confirmation</return>
<comment>Has confirm() been called?
<p>
This function never throws an exception
</p></comment>
</function>
<function name="getAlert">
<return type="string">The message of the most recent JavaScript alert</return>
<comment>Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
<p>Getting an alert has the same effect as manually clicking OK. If an
alert is generated but you do not get/verify it, the next Selenium action
will fail.</p>
<p>NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
dialog.</p>
<p>NOTE: Selenium does NOT support JavaScript alerts that are generated in a
page's onload() event handler. In this case a visible dialog WILL be
generated and Selenium will hang until someone manually clicks OK.</p></comment>
</function>
<function name="getConfirmation">
<return type="string">the message of the most recent JavaScript confirmation dialog</return>
<comment>Retrieves the message of a JavaScript confirmation dialog generated during
the previous action.
<p>
By default, the confirm function will return true, having the same effect
as manually clicking OK. This can be changed by prior execution of the
chooseCancelOnNextConfirmation command. If an confirmation is generated
but you do not get/verify it, the next Selenium action will fail.
</p>
<p>
NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible
dialog.
</p>
<p>
NOTE: Selenium does NOT support JavaScript confirmations that are
generated in a page's onload() event handler. In this case a visible
dialog WILL be generated and Selenium will hang until you manually click
OK.
</p></comment>
</function>
<function name="getPrompt">
<return type="string">the message of the most recent JavaScript question prompt</return>
<comment>Retrieves the message of a JavaScript question prompt dialog generated during
the previous action.
<p>Successful handling of the prompt requires prior execution of the
answerOnNextPrompt command. If a prompt is generated but you
do not get/verify it, the next Selenium action will fail.</p>
<p>NOTE: under Selenium, JavaScript prompts will NOT pop up a visible
dialog.</p>
<p>NOTE: Selenium does NOT support JavaScript prompts that are generated in a
page's onload() event handler. In this case a visible dialog WILL be
generated and Selenium will hang until someone manually clicks OK.</p></comment>
</function>
<function name="getAbsoluteLocation">
<return type="string">the absolute URL of the current page</return>
<comment>Gets the absolute URL of the current page.</comment>
</function>
<function name="isLocation">
<return type="boolean">true if the location matches, false otherwise</return>
<param name="expectedLocation">the location to match</param>
<comment>Verify the location of the current page ends with the expected location.
If an URL querystring is provided, this is checked as well.</comment>
</function>
<function name="getTitle">
<return type="string">the title of the current page</return>
<comment>Gets the title of the current page.</comment>
</function>
<function name="getBodyText">
<return type="string">the entire text of the page</return>
<comment>Gets the entire text of the page.</comment>
</function>
<function name="getValue">
<return type="string">the element value, or "on/off" for checkbox/radio elements</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter).
For checkbox/radio elements, the value will be "on" or "off" depending on
whether the element is checked or not.</comment>
</function>
<function name="getText">
<return type="string">the text of the element</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Gets the text of an element. This works for any element that contains
text. This command uses either the textContent (Mozilla-like browsers) or
the innerText (IE-like browsers) of the element, which is the rendered
text shown to the user.</comment>
</function>
<function name="getEval">
<return type="string">the results of evaluating the snippet</return>
<param name="script">the JavaScript snippet to run</param>
<comment>Gets the result of evaluating the specified JavaScript snippet. The snippet may
have multiple lines, but only the result of the last line will be returned.
<p>Note that, by default, the snippet will run in the context of the "selenium"
object itself, so <code>this</code> will refer to the Selenium object, and <code>window</code> will
refer to the top-level runner test window, not the window of your application.</p>
<p>If you need a reference to the window of your application, you can refer
to <code>this.browserbot.getCurrentWindow()</code> and if you need to use
a locator to refer to a single element in your application page, you can
use <code>this.page().findElement("foo")</code> where "foo" is your locator.</p></comment>
</function>
<function name="getChecked">
<return type="string">either "true" or "false" depending on whether the checkbox is checked</return>
<param name="locator">an <a href="#locators">element locator</a> pointing to a checkbox or radio button</param>
<comment>Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button.</comment>
</function>
<function name="getTable">
<return type="string">the text from the specified cell</return>
<param name="tableCellAddress">a cell address, e.g. "foo.1.4"</param>
<comment>Gets the text from a cell of a table. The cellAddress syntax
tableLocator.row.column, where row and column start at 0.</comment>
</function>
<function name="isSelected">
<return type="boolean">true if the selected option matches the locator, false otherwise</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<param name="optionLocator">an option locator, typically just an option label (e.g. "John Smith")</param>
<comment>Verifies that the selected option of a drop-down satisfies the optionSpecifier.
<p>See the select command for more information about option locators.</p></comment>
</function>
<function name="getSelectedOptions">
<return type="string[]">an array of all option labels in the specified select drop-down</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Gets all option labels for selected options in the specified select or multi-select element.</comment>
</function>
<function name="getSelectOptions">
<return type="string[]">an array of all option labels in the specified select drop-down</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Gets all option labels in the specified select drop-down.</comment>
</function>
<function name="getAttribute">
<return type="string">the value of the specified attribute</return>
<param name="attributeLocator">an element locator followed by an</param>
<comment>Gets the value of an element attribute.</comment>
</function>
<function name="isTextPresent">
<return type="boolean">true if the pattern matches the text, false otherwise</return>
<param name="pattern">a <a href="#patterns">pattern</a> to match with the text of the page</param>
<comment>Verifies that the specified text pattern appears somewhere on the rendered page shown to the user.</comment>
</function>
<function name="isElementPresent">
<return type="boolean">true if the element is present, false otherwise</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Verifies that the specified element is somewhere on the page.</comment>
</function>
<function name="isVisible">
<return type="boolean">true if the specified element is visible, false otherwise</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Determines if the specified element is visible. An
element can be rendered invisible by setting the CSS "visibility"
property to "hidden", or the "display" property to "none", either for the
element itself or one if its ancestors. This method will fail if
the element is not present.</comment>
</function>
<function name="isEditable">
<return type="boolean">true if the input element is editable, false otherwise</return>
<param name="locator">an <a href="#locators">element locator</a></param>
<comment>Determines whether the specified input element is editable, ie hasn't been disabled.
This method will fail if the specified element isn't an input element.</comment>
</function>
<function name="getAllButtons">
<return type="string[]">the IDs of all buttons on the page</return>
<comment>Returns the IDs of all buttons on the page.
<p>If a given button has no ID, it will appear as "" in this array.</p></comment>
</function>
<function name="getAllLinks">
<return type="string[]">the IDs of all links on the page</return>
<comment>Returns the IDs of all links on the page.
<p>If a given link has no ID, it will appear as "" in this array.</p></comment>
</function>
<function name="getAllFields">
<return type="string[]">the IDs of all field on the page</return>
<comment>Returns the IDs of all input fields on the page.
<p>If a given field has no ID, it will appear as "" in this array.</p></comment>
</function>
<function name="getHtmlSource">
<return type="string">the entire HTML source</return>
<comment>Returns the entire HTML source between the opening and
closing "html" tags.</comment>
</function>
<function name="setContext">
<param name="context">the message to be sent to the browser</param>
<param name="logLevelThreshold">one of "debug", "info", "warn", "error", sets the threshold for browser-side logging</param>
<comment>Writes a message to the status bar and adds a note to the browser-side
log.
<p>If logLevelThreshold is specified, set the threshold for logging
to that level (debug, info, warn, error).</p>
<p>(Note that the browser-side logs will <i>not</i> be sent back to the
server, and are invisible to the Client Driver.)</p></comment>
</function>
<function name="getExpression">
<return type="string">the value passed in</return>
<param name="expression">the value to return</param>
<comment>Returns the specified expression.
<p>This is useful because of JavaScript preprocessing.
It is used to generate commands like assertExpression and storeExpression.</p></comment>
</function>
<function name="waitForCondition">
<param name="script">the JavaScript snippet to run</param>
<param name="timeout">a timeout in milliseconds, after which this command will return with an error</param>
<comment>Runs the specified JavaScript snippet repeatedly until it evaluates to "true".
The snippet may have multiple lines, but only the result of the last line
will be considered.
<p>Note that, by default, the snippet will be run in the runner's test window, not in the window
of your application. To get the window of your application, you can use
the JavaScript snippet <code>selenium.browserbot.getCurrentWindow()</code>, and then
run your JavaScript in there</p></comment>
</function>
<function name="setTimeout">
<param name="timeout">a timeout in milliseconds, after which the action will return with an error</param>
<comment>Specifies the amount of time that Selenium will wait for actions to complete.
<p>Actions that require waiting include "open" and the "waitFor*" actions.</p>
The default timeout is 30 seconds.</comment>
</function>
<function name="waitForPageToLoad">
<param name="timeout">a timeout in milliseconds, after which this command will return with an error</param>
<comment>Waits for a new page to load.
<p>You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc.
(which are only available in the JS API).</p>
<p>Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded"
flag when it first notices a page load. Running any other Selenium command after
turns the flag to false. Hence, if you want to wait for a page to load, you must
wait immediately after a Selenium command that caused a page-load.</p></comment>
</function>
</apidoc>

View File

@ -1,660 +0,0 @@
/*
html-xpath, an implementation of DOM Level 3 XPath for Internet Explorer 5+
Copyright (C) 2004 Dimitri Glazkov
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
*/
/** SELENIUM:PATCH TO ALLOW USE WITH DOCUMENTS FROM OTHER WINDOWS: 2004-11-24
TODO resubmit this to http://sf.net/projects/html-xpath */
function addXPathSupport(document) {
/** END SELENIUM:PATCH */
var isIe = /MSIE [56789]/.test(navigator.userAgent) && (navigator.platform == "Win32");
// Mozilla has support by default, we don't have an implementation for the rest
if (isIe)
{
// release number
document.DomL3XPathRelease = "0.0.3.0";
// XPathException
// An Error object will be thrown, this is just a handler to instantiate that object
var XPathException = new _XPathExceptionHandler();
function _XPathExceptionHandler()
{
this.INVALID_EXPRESSION_ERR = 51;
this.TYPE_ERR = 52;
this.NOT_IMPLEMENTED_ERR = -1;
this.RUNTIME_ERR = -2;
this.ThrowNotImplemented = function(message)
{
ThrowError(this.NOT_IMPLEMENTED_ERR, "This functionality is not implemented.", message);
}
this.ThrowInvalidExpression = function(message)
{
ThrowError(this.INVALID_EXPRESSION_ERR, "Invalid expression", message);
}
this.ThrowType = function(message)
{
ThrowError(this.TYPE_ERR, "Type error", message);
}
this.Throw = function(message)
{
ThrowError(this.RUNTIME_ERR, "Run-time error", message);
}
function ThrowError(code, description, message)
{
var error = new Error(code, "DOM-L3-XPath " + document.DomL3XPathRelease + ": " + description + (message ? ", \"" + message + "\"": ""));
error.code = code;
error.name = "XPathException";
throw error;
}
}
// DOMException
// An Error object will be thrown, this is just a handler to instantiate that object
var DOMException = new _DOMExceptionHandler();
function _DOMExceptionHandler()
{
this.ThrowInvalidState = function(message)
{
ThrowError(13, "The state of the object is no longer valid", message);
}
function ThrowError(code, description, message)
{
var error = new Error(code, "DOM : " + description + (message ? ", \"" + message + "\"": ""));
error.code = code;
error.name = "DOMException";
throw error;
}
}
// XPathEvaluator
// implemented as document object methods
// XPathExpression createExpression(String expression, XPathNSResolver resolver)
document.createExpression = function
(
expression, // String
resolver // XPathNSResolver
)
{
// returns XPathExpression object
return new XPathExpression(expression, resolver);
}
// XPathNSResolver createNSResolver(nodeResolver)
document.createNSResolver = function
(
nodeResolver // Node
)
{
// returns XPathNSResolver
return new XPathNSResolver(nodeResolver);
}
// XPathResult evaluate(String expresison, Node contextNode, XPathNSResolver resolver, Number type, XPathResult result)
document.evaluate = function
(
expression, // String
contextNode, // Node
resolver, // XPathNSResolver
type, // Number
result // XPathResult
)
// can raise XPathException, DOMException
{
// return XPathResult
return document.createExpression(expression, resolver).evaluate(contextNode, type, result);
}
// XPathExpression
function XPathExpression
(
expression, // String
resolver // XPathNSResolver
)
{
this.expressionString = expression;
this.resolver = resolver;
// XPathResult evaluate(Node contextNode, Number type, XPathResult result)
this.evaluate = function
(
contextNode, // Node
type, // Number
result // XPathResult
)
// raises XPathException, DOMException
{
// return XPathResult
return (result && result.constructor == XPathResult ? result.initialize(this, contextNode, resolver, type) : new XPathResult(this, contextNode, resolver, type));
}
this.toString = function()
{
return "[XPathExpression]";
}
}
// XPathNSResolver
function XPathNSResolver(node)
{
this.node = node;
// String lookupNamespaceURI(String prefix)
this.lookupNamespaceURI = function
(
prefix // String
)
{
XPathException.ThrowNotImplemented();
// return String
return null;
}
this.toString = function()
{
return "[XPathNSResolver]";
}
}
// XPathResult
XPathResult.ANY_TYPE = 0;
XPathResult.NUMBER_TYPE = 1;
XPathResult.STRING_TYPE = 2;
XPathResult.BOOLEAN_TYPE = 3;
XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
XPathResult.UNORDERED_SNAPSHOT_TYPE = 6;
XPathResult.ORDERED_SNAPSHOT_TYPE = 7;
XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
function XPathResult
(
expression, // XPathExpression
contextNode, // Node
resolver, // XPathNSResolver
type // Number
)
{
this.initialize = function(expression, contextNode, resolver, type)
{
this._domResult = null;
this._expression = expression;
this._contextNode = contextNode;
this._resolver = resolver;
if (type)
{
this.resultType = type;
this._isIterator = (type == XPathResult.UNORDERED_NODE_ITERATOR_TYPE ||
type == XPathResult.ORDERED_NODE_ITERATOR_TYPE ||
type == XPathResult.ANY_TYPE);
this._isSnapshot = (type == XPathResult.UNORDERED_SNAPSHOT_TYPE || type == XPathResult.ORDERED_SNAPSHOT_TYPE);
this._isNodeSet = type > XPathResult.BOOLEAN_TYPE;
}
else
{
this.resultType = XPathResult.ANY_TYPE;
this._isIterator = true;
this._isSnapshot = false;
this._isNodeSet = true;
}
return this;
}
this.initialize(expression, contextNode, resolver, type);
this.getInvalidIteratorState = function()
{
return documentChangeDetected() || !this._isIterator;
}
this.getSnapshotLength = function()
// raises XPathException
{
if (!this._isSnapshot)
{
XPathException.ThrowType("Snapshot is not an expected result type");
}
activateResult(this);
// return Number
return this._domResult.length;
}
// Node iterateNext()
this.iterateNext = function()
// raises XPathException, DOMException
{
if (!this._isIterator)
{
XPathException.ThrowType("Iterator is not an expected result type");
}
activateResult(this);
if (documentChangeDetected())
{
DOMException.ThrowInvalidState("iterateNext");
}
// return Node
return getNextNode(this);
}
// Node snapshotItem(Number index)
this.snapshotItem = function(index)
// raises XPathException
{
if (!this._isSnapshot)
{
XPathException.ThrowType("Snapshot is not an expected result type");
}
// return Node
return getItemNode(this, index);
}
this.toString = function()
{
return "[XPathResult]";
}
// returns string value of the result, if result type is STRING_TYPE
// otherwise throws an XPathException
this.getStringValue = function()
{
if (this.resultType != XPathResult.STRING_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return String");
}
return getNodeText(this);
}
// returns number value of the result, if the result is NUMBER_TYPE
// otherwise throws an XPathException
this.getNumberValue = function()
{
if (this.resultType != XPathResult.NUMBER_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return Number");
}
var number = parseInt(getNodeText(this));
if (isNaN(number))
{
XPathException.ThrowType("The result can not be converted to Number");
}
return number;
}
// returns boolean value of the result, if the result is BOOLEAN_TYPE
// otherwise throws an XPathException
this.getBooleanValue = function()
{
if (this.resultType != XPathResult.BOOLEAN_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return Boolean");
}
var
text = getNodeText(this);
bool = (text ? text.toLowerCase() : null);
if (bool == "false" || bool == "true")
{
return bool;
}
XPathException.ThrowType("The result can not be converted to Boolean");
}
// returns single node, if the result is ANY_UNORDERED_NODE_TYPE or FIRST_ORDERED_NODE_TYPE
// otherwise throws an XPathException
this.getSingleNodeValue = function()
{
if (this.resultType != XPathResult.ANY_UNORDERED_NODE_TYPE &&
this.resultType != XPathResult.FIRST_ORDERED_NODE_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return single Node value");
}
return getSingleNode(this);
}
function documentChangeDetected()
{
return document._XPathMsxmlDocumentHelper.documentChangeDetected();
}
function getNodeText(result)
{
activateResult(result);
return result._textResult;
// return ((node = getSingleNode(result)) ? (node.nodeType == 1 ? node.innerText : node.nodeValue) : null);
}
function findNode(result, current)
{
switch(current.nodeType)
{
case 1: // NODE_ELEMENT
var id = current.attributes.getNamedItem("id");
if (id)
{
return document.getElementById(id.value);
}
XPathException.Throw("unable to locate element in XML tree");
case 2: // NODE_ATTRIBUTE
var id = current.selectSingleNode("..").attributes.getNamedItem("id");
if (id)
{
var node = document.getElementById(id.text);
if (node)
{
return node.attributes.getNamedItem(current.nodeName);
}
}
XPathException.Throw("unable to locate attribute in XML tree");
case 3: // NODE_TEXT
var id = current.selectSingleNode("..").attributes.getNamedItem("id");
if (id)
{
var node = document.getElementById(id.value);
if (node)
{
for(child in node.childNodes)
{
if (child.nodeType == 3 && child.nodeValue == current.nodeValue)
{
return child;
}
}
}
}
XPathException.Throw("unable to locate text in XML tree");
}
XPathException.Throw("unknown node type");
}
function activateResult(result)
{
if (!result._domResult)
{
try
{
var expression = result._expression.expressionString;
// adjust expression if contextNode is not a document
if (result._contextNode != document && expression.indexOf("//") != 0)
{
expression = "//*[@id = '" + result._contextNode.id + "']" +
(expression.indexOf("/") == 0 ? "" : "/") + expression;
}
if (result._isNodeSet)
{
result._domResult = document._XPathMsxmlDocumentHelper.getDom().selectNodes(expression);
}
else
{
result._domResult = true;
result._textResult = document._XPathMsxmlDocumentHelper.getTextResult(expression);
}
}
catch(error)
{
alert(error.description);
XPathException.ThrowInvalidExpression(error.description);
}
}
}
function getSingleNode(result)
{
var node = getItemNode(result, 0);
result._domResult = null;
return node;
}
function getItemNode(result, index)
{
activateResult(result);
var current = result._domResult.item(index);
return (current ? findNode(result, current) : null);
}
function getNextNode(result)
{
var current = result._domResult.nextNode;
if (current)
{
return findNode(result, current);
}
result._domResult = null;
return null;
}
}
document.reloadDom = function()
{
document._XPathMsxmlDocumentHelper.reset();
}
document._XPathMsxmlDocumentHelper = new _XPathMsxmlDocumentHelper();
function _XPathMsxmlDocumentHelper()
{
this.getDom = function()
{
activateDom(this);
return this.dom;
}
this.getXml = function()
{
activateDom(this);
return this.dom.xml;
}
this.getTextResult = function(expression)
{
expression = expression.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "\"");
var xslText = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
"<xsl:output method=\"text\"/><xsl:template match=\"*\"><xsl:value-of select=\"" + expression + "\"/>" +
"</xsl:template></xsl:stylesheet>";
var xsl = new ActiveXObject("Msxml2.DOMDocument");
xsl.loadXML(xslText);
try
{
var result = this.getDom().transformNode(xsl);
}
catch(error)
{
alert("Error: " + error.description);
}
return result;
}
this.reset = function()
{
this.dom = null;
}
function onPropertyChangeEventHandler()
{
document._propertyChangeDetected = true;
}
this.documentChangeDetected = function()
{
return (document.ignoreDocumentChanges ? false : this._currentElementCount != document.all.length || document._propertyChangeDetected);
}
function activateDom(helper)
{
if (!helper.dom)
{
var dom = new ActiveXObject("Msxml2.DOMDocument");
/** SELENIUM:PATCH TO ALLOW PROVIDE FULL XPATH SUPPORT */
dom.setProperty("SelectionLanguage", "XPath");
/** END SELENIUM:PATCH */
dom.async = false;
dom.resolveExternals = false;
loadDocument(dom, helper);
helper.dom = dom;
helper._currentElementCount = document.all.length;
document._propertyChangeDetected = false;
}
else
{
if (helper.documentChangeDetected())
{
var dom = helper.dom;
dom.load("");
loadDocument(dom, helper);
helper._currentElementCount = document.all.length;
document._propertyChangeDetected = false;
}
}
}
function loadDocument(dom, helper)
{
return loadNode(dom, dom, document.body, helper);
}
/** SELENIUM:PATCH for loadNode() - see SEL-68 */
function loadNode(dom, domParentNode, node, helper)
{
// Bad node scenarios
// 1. If the node contains a /, it's broken HTML
// 2. If the node doesn't have a name (typically from broken HTML), the node can't be loaded
// 3. Node types we can't deal with
//
// In all scenarios, we just skip the node. We won't be able to
// query on these nodes, but they're broken anyway.
if (node.nodeName.indexOf("/") > -1
|| node.nodeName == ""
|| node.nodeName == "#document"
|| node.nodeName == "#document-fragment"
|| node.nodeName == "#cdata-section"
|| node.nodeName == "#xml-declaration"
|| node.nodeName == "#whitespace"
|| node.nodeName == "#significat-whitespace"
)
{
return;
}
// #comment is a <!-- comment -->, which must be created with createComment()
if (node.nodeName == "#comment")
{
try
{
domParentNode.appendChild(dom.createComment(node.nodeValue));
}
catch (ex)
{
// it's just a comment, we don't care
}
}
else if (node.nodeType == 3)
{
domParentNode.appendChild(dom.createTextNode(node.nodeValue));
}
else
{
var domNode = dom.createElement(node.nodeName.toLowerCase());
if (!node.id)
{
node.id = node.uniqueID;
}
domParentNode.appendChild(domNode);
loadAttributes(dom, domNode, node);
var length = node.childNodes.length;
for(var i = 0; i < length; i ++ )
{
loadNode(dom, domNode, node.childNodes[i], helper);
}
node.attachEvent("onpropertychange", onPropertyChangeEventHandler);
}
}
/** END SELENIUM:PATCH */
function loadAttributes(dom, domParentNode, node)
{
for (var i = 0; i < node.attributes.length; i ++ )
{
var attribute = node.attributes[i];
var attributeValue = attribute.nodeValue;
/** SELENIUM:PATCH for loadAttributes() - see SEL-176 */
if (attributeValue && (attribute.specified || attribute.nodeName == 'value'))
/** END SELENIUM:PATCH */
{
var domAttribute = dom.createAttribute(attribute.nodeName);
domAttribute.value = attributeValue;
domParentNode.setAttributeNode(domAttribute);
}
}
}
}
}
else
{
document.reloadDom = function() {}
XPathResult.prototype.getStringValue = function()
{
return this.stringValue;
}
XPathResult.prototype.getNumberValue = function()
{
return this.numberValue;
}
XPathResult.prototype.getBooleanValue = function()
{
return this.booleanValue;
}
XPathResult.prototype.getSingleNodeValue = function()
{
return this.singleNodeValue;
}
XPathResult.prototype.getInvalidIteratorState = function()
{
return this.invalidIteratorState;
}
XPathResult.prototype.getSnapshotLength = function()
{
return this.snapshotLength;
}
XPathResult.prototype.getResultType = function()
{
return this.resultType;
}
}
/** SELENIUM:PATCH TO ALLOW USE WITH CONTAINED DOCUMENTS */
}
/** END SELENIUM:PATCH */

View File

@ -1,426 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// This script contains some HTML utility functions that
// make it possible to handle elements in a way that is
// compatible with both IE-like and Mozilla-like browsers
String.prototype.trim = function() {
var result = this.replace( /^\s+/g, "" );// strip leading
return result.replace( /\s+$/g, "" );// strip trailing
};
String.prototype.lcfirst = function() {
return this.charAt(0).toLowerCase() + this.substr(1);
};
String.prototype.ucfirst = function() {
return this.charAt(0).toUpperCase() + this.substr(1);
};
String.prototype.startsWith = function(str) {
return this.indexOf(str) == 0;
};
// Returns the text in this element
function getText(element) {
text = "";
if(browserVersion.isFirefox)
{
var dummyElement = element.cloneNode(true);
renderWhitespaceInTextContent(dummyElement);
text = dummyElement.textContent;
}
else if(element.textContent)
{
text = element.textContent;
}
else if(element.innerText)
{
text = element.innerText;
}
text = normalizeNewlines(text);
text = normalizeSpaces(text);
return text.trim();
}
function renderWhitespaceInTextContent(element) {
// Remove non-visible newlines in text nodes
if (element.nodeType == Node.TEXT_NODE)
{
element.data = element.data.replace(/\n|\r/g, " ");
return;
}
// Don't modify PRE elements
if (element.tagName == "PRE")
{
return;
}
// Handle inline element that force newlines
if (tagIs(element, ["BR", "HR"]))
{
// Replace this element with a newline text element
element.parentNode.replaceChild(document.createTextNode("\n"), element)
}
for (var i = 0; i < element.childNodes.length; i++)
{
var child = element.childNodes.item(i)
renderWhitespaceInTextContent(child);
}
// Handle block elements that introduce newlines
// -- From HTML spec:
//<!ENTITY % block
// "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
// BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
if (tagIs(element, ["P", "DIV"]))
{
element.appendChild(document.createTextNode("\n"), element)
}
}
function tagIs(element, tags)
{
var tag = element.tagName;
for (var i = 0; i < tags.length; i++)
{
if (tags[i] == tag)
{
return true;
}
}
return false;
}
/**
* Convert all newlines to \m
*/
function normalizeNewlines(text)
{
return text.replace(/\r\n|\r/g, "\n");
}
/**
* Replace multiple sequential spaces with a single space, and then convert &nbsp; to space.
*/
function normalizeSpaces(text)
{
// IE has already done this conversion, so doing it again will remove multiple nbsp
if (browserVersion.isIE)
{
return text;
}
// Replace multiple spaces with a single space
// TODO - this shouldn't occur inside PRE elements
text = text.replace(/\ +/g, " ");
// Replace &nbsp; with a space
var pat = String.fromCharCode(160); // Opera doesn't like /\240/g
var re = new RegExp(pat, "g");
return text.replace(re, " ");
}
// Sets the text in this element
function setText(element, text) {
if(element.textContent) {
element.textContent = text;
} else if(element.innerText) {
element.innerText = text;
}
}
// Get the value of an <input> element
function getInputValue(inputElement) {
if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
inputElement.type.toUpperCase() == 'RADIO')
{
return (inputElement.checked ? 'on' : 'off');
}
return inputElement.value;
}
/* Fire an event in a browser-compatible manner */
function triggerEvent(element, eventType, canBubble) {
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
element.fireEvent('on' + eventType);
}
else {
var evt = document.createEvent('HTMLEvents');
evt.initEvent(eventType, canBubble, true);
element.dispatchEvent(evt);
}
}
function triggerKeyEvent(element, eventType, keycode, canBubble) {
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
keyEvent = parent.frames['myiframe'].document.createEventObject();
keyEvent.keyCode=keycode;
element.fireEvent('on' + eventType, keyEvent);
}
else {
var evt = document.createEvent('KeyEvents');
evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
element.dispatchEvent(evt);
}
}
/* Fire a mouse event in a browser-compatible manner */
function triggerMouseEvent(element, eventType, canBubble) {
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
if (element.fireEvent) {
element.fireEvent('on' + eventType);
}
else {
var evt = document.createEvent('MouseEvents');
if (evt.initMouseEvent)
{
evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
}
else
{
// Safari
// TODO we should be initialising other mouse-event related attributes here
evt.initEvent(eventType, canBubble, true);
}
element.dispatchEvent(evt);
}
}
function removeLoadListener(element, command) {
if (window.removeEventListener)
element.removeEventListener("load", command, true);
else if (window.detachEvent)
element.detachEvent("onload", command);
}
function addLoadListener(element, command) {
if (window.addEventListener && !browserVersion.isOpera)
element.addEventListener("load",command, true);
else if (window.attachEvent)
element.attachEvent("onload",command);
}
function addUnloadListener(element, command) {
if (window.addEventListener)
element.addEventListener("unload",command, true);
else if (window.attachEvent)
element.attachEvent("onunload",command);
}
/**
* Override the broken getFunctionName() method from JsUnit
* This file must be loaded _after_ the jsunitCore.js
*/
function getFunctionName(aFunction) {
var regexpResult = aFunction.toString().match(/function (\w*)/);
if (regexpResult && regexpResult[1]) {
return regexpResult[1];
}
return 'anonymous';
}
function describe(object, delimiter) {
var props = new Array();
for (var prop in object) {
props.push(prop + " -> " + object[prop]);
}
return props.join(delimiter || '\n');
}
PatternMatcher = function(pattern) {
this.selectStrategy(pattern);
};
PatternMatcher.prototype = {
selectStrategy: function(pattern) {
this.pattern = pattern;
var strategyName = 'glob'; // by default
if (/^([a-z-]+):(.*)/.test(pattern)) {
strategyName = RegExp.$1;
pattern = RegExp.$2;
}
var matchStrategy = PatternMatcher.strategies[strategyName];
if (!matchStrategy) {
throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
}
this.strategy = matchStrategy;
this.matcher = new matchStrategy(pattern);
},
matches: function(actual) {
return this.matcher.matches(actual + '');
// Note: appending an empty string avoids a Konqueror bug
}
};
/**
* A "static" convenience method for easy matching
*/
PatternMatcher.matches = function(pattern, actual) {
return new PatternMatcher(pattern).matches(actual);
};
PatternMatcher.strategies = {
/**
* Exact matching, e.g. "exact:***"
*/
exact: function(expected) {
this.expected = expected;
this.matches = function(actual) {
return actual == this.expected;
};
},
/**
* Match by regular expression, e.g. "regexp:^[0-9]+$"
*/
regexp: function(regexpString) {
this.regexp = new RegExp(regexpString);
this.matches = function(actual) {
return this.regexp.test(actual);
};
},
/**
* "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
* but don't require a perfect match; instead succeed if actual
* contains something that matches globString.
* Making this distinction is motivated by a bug in IE6 which
* leads to the browser hanging if we implement *TextPresent tests
* by just matching against a regular expression beginning and
* ending with ".*". The globcontains strategy allows us to satisfy
* the functional needs of the *TextPresent ops more efficiently
* and so avoid running into this IE6 freeze.
*/
globContains: function(globString) {
this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
this.matches = function(actual) {
return this.regexp.test(actual);
};
},
/**
* "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
*/
glob: function(globString) {
this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
this.matches = function(actual) {
return this.regexp.test(actual);
};
}
};
PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
var re = glob;
re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
re = re.replace(/\?/g, "(.|[\r\n])");
re = re.replace(/\*/g, "(.|[\r\n])*");
return re;
};
PatternMatcher.regexpFromGlobContains = function(globContains) {
return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
};
PatternMatcher.regexpFromGlob = function(glob) {
return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
};
var Assert = {
fail: function(message) {
throw new AssertionFailedError(message);
},
/*
* Assert.equals(comment?, expected, actual)
*/
equals: function() {
var args = new AssertionArguments(arguments);
if (args.expected === args.actual) {
return;
}
Assert.fail(args.comment +
"Expected '" + args.expected +
"' but was '" + args.actual + "'");
},
/*
* Assert.matches(comment?, pattern, actual)
*/
matches: function() {
var args = new AssertionArguments(arguments);
if (PatternMatcher.matches(args.expected, args.actual)) {
return;
}
Assert.fail(args.comment +
"Actual value '" + args.actual +
"' did not match '" + args.expected + "'");
},
/*
* Assert.notMtches(comment?, pattern, actual)
*/
notMatches: function() {
var args = new AssertionArguments(arguments);
if (!PatternMatcher.matches(args.expected, args.actual)) {
return;
}
Assert.fail(args.comment +
"Actual value '" + args.actual +
"' did match '" + args.expected + "'");
}
};
// Preprocess the arguments to allow for an optional comment.
function AssertionArguments(args) {
if (args.length == 2) {
this.comment = "";
this.expected = args[0];
this.actual = args[1];
} else {
this.comment = args[0] + "; ";
this.expected = args[1];
this.actual = args[2];
}
}
function AssertionFailedError(message) {
this.isAssertionFailedError = true;
this.isSeleniumError = true;
this.message = message;
this.failureMessage = message;
}
function SeleniumError(message) {
var error = new Error(message);
error.isSeleniumError = true;
return error;
};

View File

@ -1,1239 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
storedVars = new Object();
function Selenium(browserbot) {
/**
* Defines an object that runs Selenium commands.
*
* <h3><a name="locators"></a>Element Locators</h3>
* <p>
* Element Locators tell Selenium which HTML element a command refers to.
* The format of a locator is:</p>
* <blockquote>
* <em>locatorType</em><strong>=</strong><em>argument</em>
* </blockquote>
*
* <p>
* We support the following strategies for locating elements:
* </p>
* <blockquote>
* <dl>
* <dt><strong>identifier</strong>=<em>id</em></dt>
* <dd>Select the element with the specified &#64;id attribute. If no match is
* found, select the first element whose &#64;name attribute is <em>id</em>.
* (This is normally the default; see below.)</dd>
* <dt><strong>id</strong>=<em>id</em></dt>
* <dd>Select the element with the specified &#64;id attribute.</dd>
*
* <dt><strong>name</strong>=<em>name</em></dt>
* <dd>Select the first element with the specified &#64;name attribute.</dd>
* <dd><ul class="first last simple">
* <li>username</li>
* <li>name=username</li>
* </ul>
* </dd>
* <dd>The name may optionally be followed by one or more <em>element-filters</em>, separated from the name by whitespace. If the <em>filterType</em> is not specified, <strong>value</strong> is assumed.</dd>
*
* <dd><ul class="first last simple">
* <li>name=flavour value=chocolate</li>
* </ul>
* </dd>
* <dt><strong>dom</strong>=<em>javascriptExpression</em></dt>
*
* <dd>
*
* <dd>Find an element using JavaScript traversal of the HTML Document Object
* Model. DOM locators <em>must</em> begin with &quot;document.&quot;.
* <ul class="first last simple">
* <li>dom=document.forms['myForm'].myDropdown</li>
* <li>dom=document.images[56]</li>
* </ul>
* </dd>
*
* </dd>
*
* <dt><strong>xpath</strong>=<em>xpathExpression</em></dt>
* <dd>Locate an element using an XPath expression.
* <ul class="first last simple">
* <li>xpath=//img[&#64;alt='The image alt text']</li>
* <li>xpath=//table[&#64;id='table1']//tr[4]/td[2]</li>
*
* </ul>
* </dd>
* <dt><strong>link</strong>=<em>textPattern</em></dt>
* <dd>Select the link (anchor) element which contains text matching the
* specified <em>pattern</em>.
* <ul class="first last simple">
* <li>link=The link text</li>
* </ul>
*
* </dd>
* </dl>
* </blockquote>
* <p>
* Without an explicit locator prefix, Selenium uses the following default
* strategies:
* </p>
*
* <ul class="simple">
* <li><strong>dom</strong>, for locators starting with &quot;document.&quot;</li>
* <li><strong>xpath</strong>, for locators starting with &quot;//&quot;</li>
* <li><strong>identifier</strong>, otherwise</li>
* </ul>
*
* <h3><a name="element-filters">Element Filters</a></h3>
* <blockquote>
* <p>Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator.</p>
* <p>Filters look much like locators, ie.</p>
* <blockquote>
* <em>filterType</em><strong>=</strong><em>argument</em></blockquote>
*
* <p>Supported element-filters are:</p>
* <p><strong>value=</strong><em>valuePattern</em></p>
* <blockquote>
* Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons.</blockquote>
* <p><strong>index=</strong><em>index</em></p>
* <blockquote>
* Selects a single element based on its position in the list (offset from zero).</blockquote>
* </blockquote>
*
* <h3><a name="patterns"></a>String-match Patterns</h3>
*
* <p>
* Various Pattern syntaxes are available for matching string values:
* </p>
* <blockquote>
* <dl>
* <dt><strong>glob:</strong><em>pattern</em></dt>
* <dd>Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a
* kind of limited regular-expression syntax typically used in command-line
* shells. In a glob pattern, "*" represents any sequence of characters, and "?"
* represents any single character. Glob patterns match against the entire
* string.</dd>
* <dt><strong>regexp:</strong><em>regexp</em></dt>
* <dd>Match a string using a regular-expression. The full power of JavaScript
* regular-expressions is available.</dd>
* <dt><strong>exact:</strong><em>string</em></dt>
*
* <dd>Match a string exactly, verbatim, without any of that fancy wildcard
* stuff.</dd>
* </dl>
* </blockquote>
* <p>
* If no pattern prefix is specified, Selenium assumes that it's a "glob"
* pattern.
* </p>
*/
this.browserbot = browserbot;
this.optionLocatorFactory = new OptionLocatorFactory();
this.page = function() {
return browserbot.getCurrentPage();
};
}
Selenium.createForFrame = function(frame) {
return new Selenium(BrowserBot.createForFrame(frame));
};
Selenium.prototype.reset = function() {
/**
* Clear out all stored variables and select the null (starting) window
*/
storedVars = new Object();
this.browserbot.selectWindow("null");
};
Selenium.prototype.doClick = function(locator) {
/**
* Clicks on a link, button, checkbox or radio button. If the click action
* causes a new page to load (like a link usually does), call
* waitForPageToLoad.
*
* @param locator an element locator
*
*/
var element = this.page().findElement(locator);
this.page().clickElement(element);
};
Selenium.prototype.doFireEvent = function(locator, eventName) {
/**
* Explicitly simulate an event, to trigger the corresponding &quot;on<em>event</em>&quot;
* handler.
*
* @param locator an <a href="#locators">element locator</a>
* @param eventName the event name, e.g. "focus" or "blur"
*/
var element = this.page().findElement(locator);
triggerEvent(element, eventName, false);
};
Selenium.prototype.doKeyPress = function(locator, keycode) {
/**
* Simulates a user pressing and releasing a key.
*
* @param locator an <a href="#locators">element locator</a>
* @param keycode the numeric keycode of the key to be pressed, normally the
* ASCII value of that key.
*/
var element = this.page().findElement(locator);
triggerKeyEvent(element, 'keypress', keycode, true);
};
Selenium.prototype.doKeyDown = function(locator, keycode) {
/**
* Simulates a user pressing a key (without releasing it yet).
*
* @param locator an <a href="#locators">element locator</a>
* @param keycode the numeric keycode of the key to be pressed, normally the
* ASCII value of that key.
*/
var element = this.page().findElement(locator);
triggerKeyEvent(element, 'keydown', keycode, true);
};
Selenium.prototype.doKeyUp = function(locator, keycode) {
/**
* Simulates a user releasing a key.
*
* @param locator an <a href="#locators">element locator</a>
* @param keycode the numeric keycode of the key to be released, normally the
* ASCII value of that key.
*/
var element = this.page().findElement(locator);
triggerKeyEvent(element, 'keyup', keycode, true);
};
Selenium.prototype.doMouseOver = function(locator) {
/**
* Simulates a user hovering a mouse over the specified element.
*
* @param locator an <a href="#locators">element locator</a>
*/
var element = this.page().findElement(locator);
triggerMouseEvent(element, 'mouseover', true);
};
Selenium.prototype.doMouseDown = function(locator) {
/**
* Simulates a user pressing the mouse button (without releasing it yet) on
* the specified element.
*
* @param locator an <a href="#locators">element locator</a>
*/
var element = this.page().findElement(locator);
triggerMouseEvent(element, 'mousedown', true);
};
Selenium.prototype.doType = function(locator, value) {
/**
* Sets the value of an input field, as though you typed it in.
*
* <p>Can also be used to set the value of combo boxes, check boxes, etc. In these cases,
* value should be the value of the option selected, not the visible text.</p>
*
* @param locator an <a href="#locators">element locator</a>
* @param value the value to type
*/
// TODO fail if it can't be typed into.
var element = this.page().findElement(locator);
this.page().replaceText(element, value);
};
Selenium.prototype.findToggleButton = function(locator) {
var element = this.page().findElement(locator);
if (element.checked == null) {
Assert.fail("Element " + locator + " is not a toggle-button.");
}
return element;
}
Selenium.prototype.doCheck = function(locator) {
/**
* Check a toggle-button (checkbox/radio)
*
* @param locator an <a href="#locators">element locator</a>
*/
this.findToggleButton(locator).checked = true;
};
Selenium.prototype.doUncheck = function(locator) {
/**
* Uncheck a toggle-button (checkbox/radio)
*
* @param locator an <a href="#locators">element locator</a>
*/
this.findToggleButton(locator).checked = false;
};
Selenium.prototype.doSelect = function(locator, optionLocator) {
/**
* Select an option from a drop-down using an option locator.
*
* <p>
* Option locators provide different ways of specifying options of an HTML
* Select element (e.g. for selecting a specific option, or for asserting
* that the selected option satisfies a specification). There are several
* forms of Select Option Locator.
* </p>
* <dl>
* <dt><strong>label</strong>=<em>labelPattern</em></dt>
* <dd>matches options based on their labels, i.e. the visible text. (This
* is the default.)
* <ul class="first last simple">
* <li>label=regexp:^[Oo]ther</li>
* </ul>
* </dd>
* <dt><strong>value</strong>=<em>valuePattern</em></dt>
* <dd>matches options based on their values.
* <ul class="first last simple">
* <li>value=other</li>
* </ul>
*
*
* </dd>
* <dt><strong>id</strong>=<em>id</em></dt>
*
* <dd>matches options based on their ids.
* <ul class="first last simple">
* <li>id=option1</li>
* </ul>
* </dd>
* <dt><strong>index</strong>=<em>index</em></dt>
* <dd>matches an option based on its index (offset from zero).
* <ul class="first last simple">
*
* <li>index=2</li>
* </ul>
* </dd>
* </dl>
* <p>
* If no option locator prefix is provided, the default behaviour is to match on <strong>label</strong>.
* </p>
*
*
* @param locator an <a href="#locators">element locator</a> identifying a drop-down menu
* @param optionLocator an option locator (a label by default)
*/
var element = this.page().findElement(locator);
if (!("options" in element)) {
throw new SeleniumError("Specified element is not a Select (has no options)");
}
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
var option = locator.findOption(element);
this.page().selectOption(element, option);
};
Selenium.prototype.doAddSelection = function(locator, optionLocator) {
/**
* Add a selection to the set of selected options in a multi-select element using an option locator.
*
* @see #doSelect for details of option locators
*
* @param locator an <a href="#locators">element locator</a> identifying a multi-select box
* @param optionLocator an option locator (a label by default)
*/
var element = this.page().findElement(locator);
if (!("options" in element)) {
throw new SeleniumError("Specified element is not a Select (has no options)");
}
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
var option = locator.findOption(element);
this.page().addSelection(element, option);
};
Selenium.prototype.doRemoveSelection = function(locator, optionLocator) {
/**
* Remove a selection from the set of selected options in a multi-select element using an option locator.
*
* @see #doSelect for details of option locators
*
* @param locator an <a href="#locators">element locator</a> identifying a multi-select box
* @param optionLocator an option locator (a label by default)
*/
var element = this.page().findElement(locator);
if (!("options" in element)) {
throw new SeleniumError("Specified element is not a Select (has no options)");
}
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
var option = locator.findOption(element);
this.page().removeSelection(element, option);
};
Selenium.prototype.doSubmit = function(formLocator) {
/**
* Submit the specified form. This is particularly useful for forms without
* submit buttons, e.g. single-input "Search" forms.
*
* @param formLocator an <a href="#locators">element locator</a> for the form you want to submit
*/
var form = this.page().findElement(formLocator);
var actuallySubmit = true;
if (form.onsubmit) {
// apply this to the correct window so alerts are properly handled, even in IE HTA mode
actuallySubmit = form.onsubmit.apply(this.browserbot.getContentWindow());
}
if (actuallySubmit) {
form.submit();
}
};
Selenium.prototype.doOpen = function(url) {
/**
* Opens an URL in the test frame. This accepts both relative and absolute
* URLs.
*
* The &quot;open&quot; command waits for the page to load before proceeding,
* ie. the &quot;AndWait&quot; suffix is implicit.
*
* <em>Note</em>: The URL must be on the same domain as the runner HTML
* due to security restrictions in the browser (Same Origin Policy). If you
* need to open an URL on another domain, use the Selenium Server to start a
* new browser session on that domain.
*
* @param url the URL to open; may be relative or absolute
*/
this.browserbot.openLocation(url);
return SELENIUM_PROCESS_WAIT;
};
Selenium.prototype.doSelectWindow = function(windowID) {
/**
* Selects a popup window; once a popup window has been selected, all
* commands go to that window. To select the main window again, use "null"
* as the target.
*
* @param windowID the JavaScript window ID of the window to select
*/
this.browserbot.selectWindow(windowID);
};
Selenium.prototype.doWaitForPopUp = function(windowID, timeout) {
/**
* Waits for a popup window to appear and load up.
*
* @param windowID the JavaScript window ID of the window that will appear
* @param timeout a timeout in milliseconds, after which the action will return with an error
*/
if (isNaN(timeout)) {
throw new SeleniumError("Timeout is not a number: " + timeout);
}
testLoop.waitForCondition = function () {
var targetWindow = selenium.browserbot.getTargetWindow(windowID);
if (!targetWindow) return false;
if (!targetWindow.location) return false;
if ("about:blank" == targetWindow.location) return false;
if (!targetWindow.document) return false;
if (!targetWindow.document.readyState) return true;
if ('complete' != targetWindow.document.readyState) return false;
return true;
};
testLoop.waitForConditionStart = new Date().getTime();
testLoop.waitForConditionTimeout = timeout;
}
Selenium.prototype.doChooseCancelOnNextConfirmation = function() {
/**
* By default, Selenium's overridden window.confirm() function will
* return true, as if the user had manually clicked OK. After running
* this command, the next call to confirm() will return false, as if
* the user had clicked Cancel.
*
*/
this.browserbot.cancelNextConfirmation();
};
Selenium.prototype.doAnswerOnNextPrompt = function(answer) {
/**
* Instructs Selenium to return the specified answer string in response to
* the next JavaScript prompt [window.prompt()].
*
*
* @param answer the answer to give in response to the prompt pop-up
*/
this.browserbot.setNextPromptResult(answer);
};
Selenium.prototype.doGoBack = function() {
/**
* Simulates the user clicking the "back" button on their browser.
*
*/
this.page().goBack();
};
Selenium.prototype.doRefresh = function() {
/**
* Simulates the user clicking the "Refresh" button on their browser.
*
*/
this.page().refresh();
};
Selenium.prototype.doClose = function() {
/**
* Simulates the user clicking the "close" button in the titlebar of a popup
* window or tab.
*/
this.page().close();
};
Selenium.prototype.isAlertPresent = function() {
/**
* Has an alert occurred?
*
* <p>
* This function never throws an exception
* </p>
* @return boolean true if there is an alert
*/
return this.browserbot.hasAlerts();
};
Selenium.prototype.isPromptPresent = function() {
/**
* Has a prompt occurred?
*
* <p>
* This function never throws an exception
* </p>
* @return boolean true if there is a pending prompt
*/
return this.browserbot.hasPrompts();
};
Selenium.prototype.isConfirmationPresent = function() {
/**
* Has confirm() been called?
*
* <p>
* This function never throws an exception
* </p>
* @return boolean true if there is a pending confirmation
*/
return this.browserbot.hasConfirmations();
};
Selenium.prototype.getAlert = function() {
/**
* Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
*
* <p>Getting an alert has the same effect as manually clicking OK. If an
* alert is generated but you do not get/verify it, the next Selenium action
* will fail.</p>
*
* <p>NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
* dialog.</p>
*
* <p>NOTE: Selenium does NOT support JavaScript alerts that are generated in a
* page's onload() event handler. In this case a visible dialog WILL be
* generated and Selenium will hang until someone manually clicks OK.</p>
* @return string The message of the most recent JavaScript alert
*/
if (!this.browserbot.hasAlerts()) {
Assert.fail("There were no alerts");
}
return this.browserbot.getNextAlert();
};
Selenium.prototype.getAlert.dontCheckAlertsAndConfirms = true;
Selenium.prototype.getConfirmation = function() {
/**
* Retrieves the message of a JavaScript confirmation dialog generated during
* the previous action.
*
* <p>
* By default, the confirm function will return true, having the same effect
* as manually clicking OK. This can be changed by prior execution of the
* chooseCancelOnNextConfirmation command. If an confirmation is generated
* but you do not get/verify it, the next Selenium action will fail.
* </p>
*
* <p>
* NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible
* dialog.
* </p>
*
* <p>
* NOTE: Selenium does NOT support JavaScript confirmations that are
* generated in a page's onload() event handler. In this case a visible
* dialog WILL be generated and Selenium will hang until you manually click
* OK.
* </p>
*
* @return string the message of the most recent JavaScript confirmation dialog
*/
if (!this.browserbot.hasConfirmations()) {
Assert.fail("There were no confirmations");
}
return this.browserbot.getNextConfirmation();
};
Selenium.prototype.getConfirmation.dontCheckAlertsAndConfirms = true;
Selenium.prototype.getPrompt = function() {
/**
* Retrieves the message of a JavaScript question prompt dialog generated during
* the previous action.
*
* <p>Successful handling of the prompt requires prior execution of the
* answerOnNextPrompt command. If a prompt is generated but you
* do not get/verify it, the next Selenium action will fail.</p>
*
* <p>NOTE: under Selenium, JavaScript prompts will NOT pop up a visible
* dialog.</p>
*
* <p>NOTE: Selenium does NOT support JavaScript prompts that are generated in a
* page's onload() event handler. In this case a visible dialog WILL be
* generated and Selenium will hang until someone manually clicks OK.</p>
* @return string the message of the most recent JavaScript question prompt
*/
if (! this.browserbot.hasPrompts()) {
Assert.fail("There were no prompts");
}
return this.browserbot.getNextPrompt();
};
Selenium.prototype.getAbsoluteLocation = function() {
/** Gets the absolute URL of the current page.
*
* @return string the absolute URL of the current page
*/
return this.page().location;
};
Selenium.prototype.isLocation = function(expectedLocation) {
/**
* Verify the location of the current page ends with the expected location.
* If an URL querystring is provided, this is checked as well.
* @param expectedLocation the location to match
* @return boolean true if the location matches, false otherwise
*/
var docLocation = this.page().location;
var actualPath = docLocation.pathname;
if (docLocation.protocol == "file:") {
// replace backslashes with forward slashes, so IE can run off the file system
var actualPath = docLocation.pathname.replace(/\\/g, "/");
}
var searchPos = expectedLocation.lastIndexOf('?');
if (searchPos == -1) {
return PatternMatcher.matches('*' + expectedLocation, actualPath)
}
else {
var expectedPath = expectedLocation.substring(0, searchPos);
return PatternMatcher.matches('*' + expectedPath, actualPath)
var expectedQueryString = expectedLocation.substring(searchPos);
return PatternMatcher.matches(expectedQueryString, docLocation.search)
}
};
Selenium.prototype.getTitle = function() {
/** Gets the title of the current page.
*
* @return string the title of the current page
*/
return this.page().title();
};
Selenium.prototype.getBodyText = function() {
/**
* Gets the entire text of the page.
* @return string the entire text of the page
*/
return this.page().bodyText();
};
Selenium.prototype.getValue = function(locator) {
/**
* Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter).
* For checkbox/radio elements, the value will be "on" or "off" depending on
* whether the element is checked or not.
*
* @param locator an <a href="#locators">element locator</a>
* @return string the element value, or "on/off" for checkbox/radio elements
*/
var element = this.page().findElement(locator)
return getInputValue(element).trim();
}
Selenium.prototype.getText = function(locator) {
/**
* Gets the text of an element. This works for any element that contains
* text. This command uses either the textContent (Mozilla-like browsers) or
* the innerText (IE-like browsers) of the element, which is the rendered
* text shown to the user.
*
* @param locator an <a href="#locators">element locator</a>
* @return string the text of the element
*/
var element = this.page().findElement(locator);
return getText(element).trim();
};
Selenium.prototype.getEval = function(script) {
/** Gets the result of evaluating the specified JavaScript snippet. The snippet may
* have multiple lines, but only the result of the last line will be returned.
*
* <p>Note that, by default, the snippet will run in the context of the "selenium"
* object itself, so <code>this</code> will refer to the Selenium object, and <code>window</code> will
* refer to the top-level runner test window, not the window of your application.</p>
*
* <p>If you need a reference to the window of your application, you can refer
* to <code>this.browserbot.getCurrentWindow()</code> and if you need to use
* a locator to refer to a single element in your application page, you can
* use <code>this.page().findElement("foo")</code> where "foo" is your locator.</p>
*
* @param script the JavaScript snippet to run
* @return string the results of evaluating the snippet
*/
try {
return eval(script);
} catch (e) {
throw new SeleniumError("Threw an exception: " + e.message);
}
};
Selenium.prototype.getChecked = function(locator) {
/**
* Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button.
* @param locator an <a href="#locators">element locator</a> pointing to a checkbox or radio button
* @return string either "true" or "false" depending on whether the checkbox is checked
*/
var element = this.page().findElement(locator);
if (element.checked == null) {
throw new SeleniumError("Element " + locator + " is not a toggle-button.");
}
return element.checked;
};
Selenium.prototype.getTable = function(tableCellAddress) {
/**
* Gets the text from a cell of a table. The cellAddress syntax
* tableLocator.row.column, where row and column start at 0.
*
* @param tableCellAddress a cell address, e.g. "foo.1.4"
* @return string the text from the specified cell
*/
// This regular expression matches "tableName.row.column"
// For example, "mytable.3.4"
pattern = /(.*)\.(\d+)\.(\d+)/;
if(!pattern.test(tableCellAddress)) {
throw new SeleniumError("Invalid target format. Correct format is tableName.rowNum.columnNum");
}
pieces = tableCellAddress.match(pattern);
tableName = pieces[1];
row = pieces[2];
col = pieces[3];
var table = this.page().findElement(tableName);
if (row > table.rows.length) {
Assert.fail("Cannot access row " + row + " - table has " + table.rows.length + " rows");
}
else if (col > table.rows[row].cells.length) {
Assert.fail("Cannot access column " + col + " - table row has " + table.rows[row].cells.length + " columns");
}
else {
actualContent = getText(table.rows[row].cells[col]);
return actualContent.trim();
}
};
Selenium.prototype.isSelected = function(locator, optionLocator) {
/**
* Verifies that the selected option of a drop-down satisfies the optionSpecifier.
*
* <p>See the select command for more information about option locators.</p>
*
* @param locator an <a href="#locators">element locator</a>
* @param optionLocator an option locator, typically just an option label (e.g. "John Smith")
* @return boolean true if the selected option matches the locator, false otherwise
*/
var element = this.page().findElement(locator);
var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
if (element.selectedIndex == -1)
{
return false;
}
return locator.isSelected(element);
};
Selenium.prototype.getSelectedOptions = function(locator) {
/** Gets all option labels for selected options in the specified select or multi-select element.
*
* @param locator an <a href="#locators">element locator</a>
* @return string[] an array of all option labels in the specified select drop-down
*/
var element = this.page().findElement(locator);
var selectedOptions = [];
for (var i = 0; i < element.options.length; i++) {
if (element.options[i].selected)
{
var option = element.options[i].text.replace(/,/g, "\\,");
selectedOptions.push(option);
}
}
return selectedOptions.join(",");
}
Selenium.prototype.getSelectOptions = function(locator) {
/** Gets all option labels in the specified select drop-down.
*
* @param locator an <a href="#locators">element locator</a>
* @return string[] an array of all option labels in the specified select drop-down
*/
var element = this.page().findElement(locator);
var selectOptions = [];
for (var i = 0; i < element.options.length; i++) {
var option = element.options[i].text.replace(/,/g, "\\,");
selectOptions.push(option);
}
return selectOptions.join(",");
};
Selenium.prototype.getAttribute = function(attributeLocator) {
/**
* Gets the value of an element attribute.
* @param attributeLocator an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar"
* @return string the value of the specified attribute
*/
var result = this.page().findAttribute(attributeLocator);
if (result == null) {
throw new SeleniumError("Could not find element attribute: " + attributeLocator);
}
return result;
};
Selenium.prototype.isTextPresent = function(pattern) {
/**
* Verifies that the specified text pattern appears somewhere on the rendered page shown to the user.
* @param pattern a <a href="#patterns">pattern</a> to match with the text of the page
* @return boolean true if the pattern matches the text, false otherwise
*/
var allText = this.page().bodyText();
if(allText == "") {
Assert.fail("Page text not found");
} else {
var patternMatcher = new PatternMatcher(pattern);
if (patternMatcher.strategy == PatternMatcher.strategies.glob) {
patternMatcher.matcher = new PatternMatcher.strategies.globContains(pattern);
}
return patternMatcher.matches(allText);
}
};
Selenium.prototype.isElementPresent = function(locator) {
/**
* Verifies that the specified element is somewhere on the page.
* @param locator an <a href="#locators">element locator</a>
* @return boolean true if the element is present, false otherwise
*/
try {
this.page().findElement(locator);
} catch (e) {
return false;
}
return true;
};
Selenium.prototype.isVisible = function(locator) {
/**
* Determines if the specified element is visible. An
* element can be rendered invisible by setting the CSS "visibility"
* property to "hidden", or the "display" property to "none", either for the
* element itself or one if its ancestors. This method will fail if
* the element is not present.
*
* @param locator an <a href="#locators">element locator</a>
* @return boolean true if the specified element is visible, false otherwise
*/
var element;
element = this.page().findElement(locator);
var visibility = this.findEffectiveStyleProperty(element, "visibility");
var _isDisplayed = this._isDisplayed(element);
return (visibility != "hidden" && _isDisplayed);
};
Selenium.prototype.findEffectiveStyleProperty = function(element, property) {
var effectiveStyle = this.findEffectiveStyle(element);
var propertyValue = effectiveStyle[property];
if (propertyValue == 'inherit' && element.parentNode.style) {
return this.findEffectiveStyleProperty(element.parentNode, property);
}
return propertyValue;
};
Selenium.prototype._isDisplayed = function(element) {
var display = this.findEffectiveStyleProperty(element, "display");
if (display == "none") return false;
if (element.parentNode.style) {
return this._isDisplayed(element.parentNode);
}
return true;
};
Selenium.prototype.findEffectiveStyle = function(element) {
if (element.style == undefined) {
return undefined; // not a styled element
}
var window = this.browserbot.getContentWindow();
if (window.getComputedStyle) {
// DOM-Level-2-CSS
return window.getComputedStyle(element, null);
}
if (element.currentStyle) {
// non-standard IE alternative
return element.currentStyle;
// TODO: this won't really work in a general sense, as
// currentStyle is not identical to getComputedStyle()
// ... but it's good enough for "visibility"
}
throw new SeleniumError("cannot determine effective stylesheet in this browser");
};
Selenium.prototype.isEditable = function(locator) {
/**
* Determines whether the specified input element is editable, ie hasn't been disabled.
* This method will fail if the specified element isn't an input element.
*
* @param locator an <a href="#locators">element locator</a>
* @return boolean true if the input element is editable, false otherwise
*/
var element = this.page().findElement(locator);
if (element.value == undefined) {
Assert.fail("Element " + locator + " is not an input.");
}
return !element.disabled;
};
Selenium.prototype.getAllButtons = function() {
/** Returns the IDs of all buttons on the page.
*
* <p>If a given button has no ID, it will appear as "" in this array.</p>
*
* @return string[] the IDs of all buttons on the page
*/
return this.page().getAllButtons();
};
Selenium.prototype.getAllLinks = function() {
/** Returns the IDs of all links on the page.
*
* <p>If a given link has no ID, it will appear as "" in this array.</p>
*
* @return string[] the IDs of all links on the page
*/
return this.page().getAllLinks();
};
Selenium.prototype.getAllFields = function() {
/** Returns the IDs of all input fields on the page.
*
* <p>If a given field has no ID, it will appear as "" in this array.</p>
*
* @return string[] the IDs of all field on the page
*/
return this.page().getAllFields();
};
Selenium.prototype.getHtmlSource = function() {
/** Returns the entire HTML source between the opening and
* closing "html" tags.
*
* @return string the entire HTML source
*/
return this.page().currentDocument.getElementsByTagName("html")[0].innerHTML;
};
Selenium.prototype.doSetContext = function(context, logLevelThreshold) {
/**
* Writes a message to the status bar and adds a note to the browser-side
* log.
*
* <p>If logLevelThreshold is specified, set the threshold for logging
* to that level (debug, info, warn, error).</p>
*
* <p>(Note that the browser-side logs will <i>not</i> be sent back to the
* server, and are invisible to the Client Driver.)</p>
*
* @param context
* the message to be sent to the browser
* @param logLevelThreshold one of "debug", "info", "warn", "error", sets the threshold for browser-side logging
*/
if (logLevelThreshold==null || logLevelThreshold=="") {
return this.page().setContext(context);
}
return this.page().setContext(context, logLevelThreshold);
};
Selenium.prototype.getExpression = function(expression) {
/**
* Returns the specified expression.
*
* <p>This is useful because of JavaScript preprocessing.
* It is used to generate commands like assertExpression and storeExpression.</p>
*
* @param expression the value to return
* @return string the value passed in
*/
return expression;
}
Selenium.prototype.doWaitForCondition = function(script, timeout) {
/**
* Runs the specified JavaScript snippet repeatedly until it evaluates to "true".
* The snippet may have multiple lines, but only the result of the last line
* will be considered.
*
* <p>Note that, by default, the snippet will be run in the runner's test window, not in the window
* of your application. To get the window of your application, you can use
* the JavaScript snippet <code>selenium.browserbot.getCurrentWindow()</code>, and then
* run your JavaScript in there</p>
* @param script the JavaScript snippet to run
* @param timeout a timeout in milliseconds, after which this command will return with an error
*/
if (isNaN(timeout)) {
throw new SeleniumError("Timeout is not a number: " + timeout);
}
testLoop.waitForCondition = function () {
return eval(script);
};
testLoop.waitForConditionStart = new Date().getTime();
testLoop.waitForConditionTimeout = timeout;
};
Selenium.prototype.doSetTimeout = function(timeout) {
/**
* Specifies the amount of time that Selenium will wait for actions to complete.
*
* <p>Actions that require waiting include "open" and the "waitFor*" actions.</p>
* The default timeout is 30 seconds.
* @param timeout a timeout in milliseconds, after which the action will return with an error
*/
testLoop.waitForConditionTimeout = timeout;
}
Selenium.prototype.doWaitForPageToLoad = function(timeout) {
/**
* Waits for a new page to load.
*
* <p>You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc.
* (which are only available in the JS API).</p>
*
* <p>Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded"
* flag when it first notices a page load. Running any other Selenium command after
* turns the flag to false. Hence, if you want to wait for a page to load, you must
* wait immediately after a Selenium command that caused a page-load.</p>
* @param timeout a timeout in milliseconds, after which this command will return with an error
*/
this.doWaitForCondition("selenium.browserbot.isNewPageLoaded()", timeout);
};
/**
* Evaluate a parameter, performing JavaScript evaluation and variable substitution.
* If the string matches the pattern "javascript{ ... }", evaluate the string between the braces.
*/
Selenium.prototype.preprocessParameter = function(value) {
var match = value.match(/^javascript\{((.|\r?\n)+)\}$/);
if (match && match[1]) {
return eval(match[1]).toString();
}
return this.replaceVariables(value);
};
/*
* Search through str and replace all variable references ${varName} with their
* value in storedVars.
*/
Selenium.prototype.replaceVariables = function(str) {
var stringResult = str;
// Find all of the matching variable references
var match = stringResult.match(/\$\{\w+\}/g);
if (!match) {
return stringResult;
}
// For each match, lookup the variable value, and replace if found
for (var i = 0; match && i < match.length; i++) {
var variable = match[i]; // The replacement variable, with ${}
var name = variable.substring(2, variable.length - 1); // The replacement variable without ${}
var replacement = storedVars[name];
if (replacement != undefined) {
stringResult = stringResult.replace(variable, replacement);
}
}
return stringResult;
};
/**
* Factory for creating "Option Locators".
* An OptionLocator is an object for dealing with Select options (e.g. for
* finding a specified option, or asserting that the selected option of
* Select element matches some condition.
* The type of locator returned by the factory depends on the locator string:
* label=<exp> (OptionLocatorByLabel)
* value=<exp> (OptionLocatorByValue)
* index=<exp> (OptionLocatorByIndex)
* id=<exp> (OptionLocatorById)
* <exp> (default is OptionLocatorByLabel).
*/
function OptionLocatorFactory() {
}
OptionLocatorFactory.prototype.fromLocatorString = function(locatorString) {
var locatorType = 'label';
var locatorValue = locatorString;
// If there is a locator prefix, use the specified strategy
var result = locatorString.match(/^([a-zA-Z]+)=(.*)/);
if (result) {
locatorType = result[1];
locatorValue = result[2];
}
if (this.optionLocators == undefined) {
this.registerOptionLocators();
}
if (this.optionLocators[locatorType]) {
return new this.optionLocators[locatorType](locatorValue);
}
throw new SeleniumError("Unkown option locator type: " + locatorType);
};
/**
* To allow for easy extension, all of the option locators are found by
* searching for all methods of OptionLocatorFactory.prototype that start
* with "OptionLocatorBy".
* TODO: Consider using the term "Option Specifier" instead of "Option Locator".
*/
OptionLocatorFactory.prototype.registerOptionLocators = function() {
this.optionLocators={};
for (var functionName in this) {
var result = /OptionLocatorBy([A-Z].+)$/.exec(functionName);
if (result != null) {
var locatorName = result[1].lcfirst();
this.optionLocators[locatorName] = this[functionName];
}
}
};
/**
* OptionLocator for options identified by their labels.
*/
OptionLocatorFactory.prototype.OptionLocatorByLabel = function(label) {
this.label = label;
this.labelMatcher = new PatternMatcher(this.label);
this.findOption = function(element) {
for (var i = 0; i < element.options.length; i++) {
if (this.labelMatcher.matches(element.options[i].text)) {
return element.options[i];
}
}
throw new SeleniumError("Option with label '" + this.label + "' not found");
};
this.isSelected = function(element) {
var selectedLabel = element.options[element.selectedIndex].text;
return PatternMatcher.matches(this.label, selectedLabel)
};
};
/**
* OptionLocator for options identified by their values.
*/
OptionLocatorFactory.prototype.OptionLocatorByValue = function(value) {
this.value = value;
this.valueMatcher = new PatternMatcher(this.value);
this.findOption = function(element) {
for (var i = 0; i < element.options.length; i++) {
if (this.valueMatcher.matches(element.options[i].value)) {
return element.options[i];
}
}
throw new SeleniumError("Option with value '" + this.value + "' not found");
};
this.isSelected = function(element) {
var selectedValue = element.options[element.selectedIndex].value;
return PatternMatcher.matches(this.value, selectedValue)
};
};
/**
* OptionLocator for options identified by their index.
*/
OptionLocatorFactory.prototype.OptionLocatorByIndex = function(index) {
this.index = Number(index);
if (isNaN(this.index) || this.index < 0) {
throw new SeleniumError("Illegal Index: " + index);
}
this.findOption = function(element) {
if (element.options.length <= this.index) {
throw new SeleniumError("Index out of range. Only " + element.options.length + " options available");
}
return element.options[this.index];
};
this.isSelected = function(element) {
return this.index == element.selectedIndex;
};
};
/**
* OptionLocator for options identified by their id.
*/
OptionLocatorFactory.prototype.OptionLocatorById = function(id) {
this.id = id;
this.idMatcher = new PatternMatcher(this.id);
this.findOption = function(element) {
for (var i = 0; i < element.options.length; i++) {
if (this.idMatcher.matches(element.options[i].id)) {
return element.options[i];
}
}
throw new SeleniumError("Option with id '" + this.id + "' not found");
};
this.isSelected = function(element) {
var selectedId = element.options[element.selectedIndex].id;
return PatternMatcher.matches(this.id, selectedId)
};
};

View File

@ -1,1047 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* This script provides the Javascript API to drive the test application contained within
* a Browser Window.
* TODO:
* Add support for more events (keyboard and mouse)
* Allow to switch "user-entry" mode from mouse-based to keyboard-based, firing different
* events in different modes.
*/
// The window to which the commands will be sent. For example, to click on a
// popup window, first select that window, and then do a normal click command.
BrowserBot = function(frame) {
this.frame = frame;
this.currentPage = null;
this.currentWindowName = null;
this.modalDialogTest = null;
this.recordedAlerts = new Array();
this.recordedConfirmations = new Array();
this.recordedPrompts = new Array();
this.openedWindows = {};
this.nextConfirmResult = true;
this.nextPromptResult = '';
this.newPageLoaded = false;
this.pageLoadError = null;
var self = this;
this.recordPageLoad = function() {
LOG.debug("Page load detected");
try {
LOG.debug("Page load location=" + self.getCurrentWindow().location);
} catch (e) {
self.pageLoadError = e;
return;
}
self.currentPage = null;
self.newPageLoaded = true;
};
this.isNewPageLoaded = function() {
if (this.pageLoadError) throw this.pageLoadError;
return self.newPageLoaded;
};
};
BrowserBot.createForFrame = function(frame) {
var browserbot;
LOG.debug("browserName: " + browserVersion.name);
LOG.debug("userAgent: " + navigator.userAgent);
if (browserVersion.isIE) {
browserbot = new IEBrowserBot(frame);
}
else if (browserVersion.isKonqueror) {
browserbot = new KonquerorBrowserBot(frame);
}
else if (browserVersion.isSafari) {
browserbot = new SafariBrowserBot(frame);
}
else {
LOG.info("Using MozillaBrowserBot")
// Use mozilla by default
browserbot = new MozillaBrowserBot(frame);
}
// Modify the test IFrame so that page loads are detected.
addLoadListener(browserbot.getFrame(), browserbot.recordPageLoad);
return browserbot;
};
BrowserBot.prototype.doModalDialogTest = function(test) {
this.modalDialogTest = test;
};
BrowserBot.prototype.cancelNextConfirmation = function() {
this.nextConfirmResult = false;
};
BrowserBot.prototype.setNextPromptResult = function(result) {
this.nextPromptResult = result;
};
BrowserBot.prototype.hasAlerts = function() {
return (this.recordedAlerts.length > 0) ;
};
BrowserBot.prototype.getNextAlert = function() {
return this.recordedAlerts.shift();
};
BrowserBot.prototype.hasConfirmations = function() {
return (this.recordedConfirmations.length > 0) ;
};
BrowserBot.prototype.getNextConfirmation = function() {
return this.recordedConfirmations.shift();
};
BrowserBot.prototype.hasPrompts = function() {
return (this.recordedPrompts.length > 0) ;
};
BrowserBot.prototype.getNextPrompt = function() {
return this.recordedPrompts.shift();
};
BrowserBot.prototype.getFrame = function() {
return this.frame;
};
BrowserBot.prototype.selectWindow = function(target) {
// we've moved to a new page - clear the current one
this.currentPage = null;
this.currentWindowName = null;
if (target && target != "null") {
// If window exists
if (this.getTargetWindow(target)) {
this.currentWindowName = target;
}
}
};
BrowserBot.prototype.openLocation = function(target) {
// We're moving to a new page - clear the current one
this.currentPage = null;
this.newPageLoaded = false;
this.setOpenLocation(target);
};
BrowserBot.prototype.setIFrameLocation = function(iframe, location) {
iframe.src = location;
};
BrowserBot.prototype.setOpenLocation = function(location) {
this.getCurrentWindow().location.href = location;
};
BrowserBot.prototype.getCurrentPage = function() {
if (this.currentPage == null) {
var testWindow = this.getCurrentWindow();
this.modifyWindowToRecordPopUpDialogs(testWindow, this);
this.modifySeparateTestWindowToDetectPageLoads(testWindow);
this.currentPage = PageBot.createForWindow(testWindow);
this.newPageLoaded = false;
}
return this.currentPage;
};
BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) {
windowToModify.alert = function(alert) {
browserBot.recordedAlerts.push(alert);
};
windowToModify.confirm = function(message) {
browserBot.recordedConfirmations.push(message);
var result = browserBot.nextConfirmResult;
browserBot.nextConfirmResult = true;
return result;
};
windowToModify.prompt = function(message) {
browserBot.recordedPrompts.push(message);
var result = !browserBot.nextConfirmResult ? null : browserBot.nextPromptResult;
browserBot.nextConfirmResult = true;
browserBot.nextPromptResult = '';
return result;
};
// Keep a reference to all popup windows by name
// note that in IE the "windowName" argument must be a valid javascript identifier, it seems.
var originalOpen = windowToModify.open;
windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) {
var openedWindow = originalOpen(url, windowName, windowFeatures, replaceFlag);
selenium.browserbot.openedWindows[windowName] = openedWindow;
return openedWindow;
};
};
/**
* The main IFrame has a single, long-lived onload handler that clears
* Browserbot.currentPage and sets the "newPageLoaded" flag. For separate
* windows, we need to attach a handler each time. This uses the
* "callOnWindowPageTransition" mechanism, which is implemented differently
* for different browsers.
*/
BrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads = function(windowToModify) {
if (this.currentWindowName != null) {
this.callOnWindowPageTransition(this.recordPageLoad, windowToModify);
}
};
/**
* Call the supplied function when a the current page unloads and a new one loads.
* This is done by polling continuously until the document changes and is fully loaded.
*/
BrowserBot.prototype.callOnWindowPageTransition = function(loadFunction, windowObject) {
// Since the unload event doesn't fire in Safari 1.3, we start polling immediately
if (windowObject && !windowObject.closed) {
LOG.debug("Starting pollForLoad: " + windowObject.document.location);
this.pollForLoad(loadFunction, windowObject, windowObject.document.location, windowObject.document.location.href);
}
};
/**
* Set up a polling timer that will keep checking the readyState of the document until it's complete.
* Since we might call this before the original page is unloaded, we first check to see that the current location
* or href is different from the original one.
*/
BrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, originalLocation, originalHref) {
var windowClosed = true;
try {
windowClosed = windowObject.closed;
} catch (e) {
LOG.debug("exception detecting closed window (I guess it must be closed)");
LOG.exception(e);
// swallow exceptions which may occur in HTA mode when the window is closed
}
if (windowClosed) {
return;
}
LOG.debug("pollForLoad original: " + originalHref);
try {
var currentLocation = windowObject.document.location;
var currentHref = currentLocation.href
var sameLoc = (originalLocation === currentLocation);
var sameHref = (originalHref === currentHref);
var rs = windowObject.document.readyState;
if (rs == null) rs = 'complete';
if (!(sameLoc && sameHref) && rs == 'complete') {
LOG.debug("pollForLoad complete: " + rs + " (" + currentHref + ")");
loadFunction();
return;
}
var self = this;
LOG.debug("pollForLoad continue: " + currentHref);
window.setTimeout(function() {self.pollForLoad(loadFunction, windowObject, originalLocation, originalHref);}, 500);
} catch (e) {
this.pageLoadError = e;
}
};
BrowserBot.prototype.getContentWindow = function() {
return this.getFrame().contentWindow || frames[this.getFrame().id];
};
BrowserBot.prototype.getTargetWindow = function(windowName) {
LOG.debug("getTargetWindow(" + windowName + ")");
// First look in the map of opened windows
var targetWindow = this.openedWindows[windowName];
if (!targetWindow) {
var evalString = "this.getContentWindow().window." + windowName;
targetWindow = eval(evalString);
}
if (!targetWindow) {
throw new SeleniumError("Window does not exist");
}
return targetWindow;
};
BrowserBot.prototype.getCurrentWindow = function() {
var testWindow = this.getContentWindow().window;
if (this.currentWindowName != null) {
testWindow = this.getTargetWindow(this.currentWindowName);
}
return testWindow;
};
function MozillaBrowserBot(frame) {
BrowserBot.call(this, frame);
}
MozillaBrowserBot.prototype = new BrowserBot;
function KonquerorBrowserBot(frame) {
BrowserBot.call(this, frame);
}
KonquerorBrowserBot.prototype = new BrowserBot;
KonquerorBrowserBot.prototype.setIFrameLocation = function(iframe, location) {
// Window doesn't fire onload event when setting src to the current value,
// so we set it to blank first.
iframe.src = "about:blank";
iframe.src = location;
};
KonquerorBrowserBot.prototype.setOpenLocation = function(location) {
// Window doesn't fire onload event when setting src to the current value,
// so we set it to blank first.
this.getCurrentWindow().location.href = "about:blank";
this.getCurrentWindow().location.href = location;
};
function SafariBrowserBot(frame) {
BrowserBot.call(this, frame);
}
SafariBrowserBot.prototype = new BrowserBot;
SafariBrowserBot.prototype.setIFrameLocation = KonquerorBrowserBot.prototype.setIFrameLocation;
function IEBrowserBot(frame) {
BrowserBot.call(this, frame);
}
IEBrowserBot.prototype = new BrowserBot;
IEBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) {
BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot);
// we will call the previous version of this method from within our own interception
oldShowModalDialog = windowToModify.showModalDialog;
windowToModify.showModalDialog = function(url, args, features) {
// Get relative directory to where TestRunner.html lives
// A risky assumption is that the user's TestRunner is named TestRunner.html
var doc_location = document.location.toString();
var end_of_base_ref = doc_location.indexOf('TestRunner.html');
var base_ref = doc_location.substring(0, end_of_base_ref);
var fullURL = base_ref + "TestRunner.html?singletest=" + escape(browserBot.modalDialogTest) + "&autoURL=" + escape(url) + "&runInterval=" + runInterval;
browserBot.modalDialogTest = null;
var returnValue = oldShowModalDialog(fullURL, args, features);
return returnValue;
};
};
SafariBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) {
BrowserBot.prototype.modifyWindowToRecordPopUpDialogs(windowToModify, browserBot);
var originalOpen = windowToModify.open;
/*
* Safari seems to be broken, so that when we manually trigger the onclick method
* of a button/href, any window.open calls aren't resolved relative to the app location.
* So here we replace the open() method with one that does resolve the url correctly.
*/
windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) {
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")) {
return originalOpen(url, windowName, windowFeatures, replaceFlag);
}
// Reduce the current path to the directory
var currentPath = windowToModify.location.pathname || "/";
currentPath = currentPath.replace(/\/[^\/]*$/, "/");
// Remove any leading "./" from the new url.
url = url.replace(/^\.\//, "");
newUrl = currentPath + url;
return originalOpen(newUrl, windowName, windowFeatures, replaceFlag);
};
};
PageBot = function(pageWindow) {
if (pageWindow) {
this.currentWindow = pageWindow;
this.currentDocument = pageWindow.document;
this.location = pageWindow.location;
this.title = function() {return this.currentDocument.title;};
}
// Register all locateElementBy* functions
// TODO - don't do this in the constructor - only needed once ever
this.locationStrategies = {};
for (var functionName in this) {
var result = /^locateElementBy([A-Z].+)$/.exec(functionName);
if (result != null) {
var locatorFunction = this[functionName];
if (typeof(locatorFunction) != 'function') {
continue;
}
// Use a specified prefix in preference to one generated from
// the function name
var locatorPrefix = locatorFunction.prefix || result[1].toLowerCase();
this.locationStrategies[locatorPrefix] = locatorFunction;
}
}
/**
* Find a locator based on a prefix.
*/
this.findElementBy = function(locatorType, locator, inDocument) {
var locatorFunction = this.locationStrategies[locatorType];
if (! locatorFunction) {
throw new SeleniumError("Unrecognised locator type: '" + locatorType + "'");
}
return locatorFunction.call(this, locator, inDocument);
};
/**
* The implicit locator, that is used when no prefix is supplied.
*/
this.locationStrategies['implicit'] = function(locator, inDocument) {
if (locator.startsWith('//')) {
return this.locateElementByXPath(locator, inDocument);
}
if (locator.startsWith('document.')) {
return this.locateElementByDomTraversal(locator, inDocument);
}
return this.locateElementByIdentifier(locator, inDocument);
};
};
PageBot.createForWindow = function(windowObject) {
if (browserVersion.isIE) {
return new IEPageBot(windowObject);
}
else if (browserVersion.isKonqueror) {
return new KonquerorPageBot(windowObject);
}
else if (browserVersion.isSafari) {
return new SafariPageBot(windowObject);
}
else {
LOG.info("Using MozillaPageBot")
// Use mozilla by default
return new MozillaPageBot(windowObject);
}
};
MozillaPageBot = function(pageWindow) {
PageBot.call(this, pageWindow);
};
MozillaPageBot.prototype = new PageBot();
KonquerorPageBot = function(pageWindow) {
PageBot.call(this, pageWindow);
};
KonquerorPageBot.prototype = new PageBot();
SafariPageBot = function(pageWindow) {
PageBot.call(this, pageWindow);
};
SafariPageBot.prototype = new PageBot();
IEPageBot = function(pageWindow) {
PageBot.call(this, pageWindow);
};
IEPageBot.prototype = new PageBot();
/*
* Finds an element on the current page, using various lookup protocols
*/
PageBot.prototype.findElement = function(locator) {
var locatorType = 'implicit';
var locatorString = locator;
// If there is a locator prefix, use the specified strategy
var result = locator.match(/^([A-Za-z]+)=(.+)/);
if (result) {
locatorType = result[1].toLowerCase();
locatorString = result[2];
}
var element = this.findElementBy(locatorType, locatorString, this.currentDocument);
if (element != null) {
return element;
}
for (var i = 0; i < this.currentWindow.frames.length; i++) {
element = this.findElementBy(locatorType, locatorString, this.currentWindow.frames[i].document);
if (element != null) {
return element;
}
}
// Element was not found by any locator function.
throw new SeleniumError("Element " + locator + " not found");
};
/**
* In non-IE browsers, getElementById() does not search by name. Instead, we
* we search separately by id and name.
*/
PageBot.prototype.locateElementByIdentifier = function(identifier, inDocument) {
return PageBot.prototype.locateElementById(identifier, inDocument)
|| PageBot.prototype.locateElementByName(identifier, inDocument)
|| null;
};
/**
* In IE, getElementById() also searches by name - this is an optimisation for IE.
*/
IEPageBot.prototype.locateElementByIdentifer = function(identifier, inDocument) {
return inDocument.getElementById(identifier);
};
/**
* Find the element with id - can't rely on getElementById, coz it returns by name as well in IE..
*/
PageBot.prototype.locateElementById = function(identifier, inDocument) {
var element = inDocument.getElementById(identifier);
if (element && element.id === identifier) {
return element;
}
else {
return null;
}
};
/**
* Find an element by name, refined by (optional) element-filter
* expressions.
*/
PageBot.prototype.locateElementByName = function(locator, document) {
var elements = document.getElementsByTagName("*");
var filters = locator.split(' ');
filters[0] = 'name=' + filters[0];
while (filters.length) {
var filter = filters.shift();
elements = this.selectElements(filter, elements, 'value');
}
if (elements.length > 0) {
return elements[0];
}
return null;
};
/**
* Finds an element using by evaluating the "document.*" string against the
* current document object. Dom expressions must begin with "document."
*/
PageBot.prototype.locateElementByDomTraversal = function(domTraversal, inDocument) {
if (domTraversal.indexOf("document.") != 0) {
return null;
}
// Trim the leading 'document'
domTraversal = domTraversal.substr(9);
var locatorScript = "inDocument." + domTraversal;
var element = eval(locatorScript);
if (!element) {
return null;
}
return element;
};
PageBot.prototype.locateElementByDomTraversal.prefix = "dom";
/**
* Finds an element identified by the xpath expression. Expressions _must_
* begin with "//".
*/
PageBot.prototype.locateElementByXPath = function(xpath, inDocument) {
// Trim any trailing "/": not valid xpath, and remains from attribute
// locator.
if (xpath.charAt(xpath.length - 1) == '/') {
xpath = xpath.slice(0, -1);
}
// Handle //tag
var match = xpath.match(/^\/\/(\w+|\*)$/);
if (match) {
var elements = inDocument.getElementsByTagName(match[1].toUpperCase());
if (elements == null) return null;
return elements[0];
}
// Handle //tag[@attr='value']
var match = xpath.match(/^\/\/(\w+|\*)\[@(\w+)=('([^\']+)'|"([^\"]+)")\]$/);
if (match) {
return this.findElementByTagNameAndAttributeValue(
inDocument,
match[1].toUpperCase(),
match[2].toLowerCase(),
match[3].slice(1, -1)
);
}
// Handle //tag[text()='value']
var match = xpath.match(/^\/\/(\w+|\*)\[text\(\)=('([^\']+)'|"([^\"]+)")\]$/);
if (match) {
return this.findElementByTagNameAndText(
inDocument,
match[1].toUpperCase(),
match[2].slice(1, -1)
);
}
return this.findElementUsingFullXPath(xpath, inDocument);
};
PageBot.prototype.findElementByTagNameAndAttributeValue = function(
inDocument, tagName, attributeName, attributeValue
) {
if (browserVersion.isIE && attributeName == "class") {
attributeName = "className";
}
var elements = inDocument.getElementsByTagName(tagName);
for (var i = 0; i < elements.length; i++) {
var elementAttr = elements[i].getAttribute(attributeName);
if (elementAttr == attributeValue) {
return elements[i];
}
}
return null;
};
PageBot.prototype.findElementByTagNameAndText = function(
inDocument, tagName, text
) {
var elements = inDocument.getElementsByTagName(tagName);
for (var i = 0; i < elements.length; i++) {
if (getText(elements[i]) == text) {
return elements[i];
}
}
return null;
};
PageBot.prototype.findElementUsingFullXPath = function(xpath, inDocument) {
if (browserVersion.isIE && !inDocument.evaluate) {
addXPathSupport(inDocument);
}
// Use document.evaluate() if it's available
if (inDocument.evaluate) {
return inDocument.evaluate(xpath, inDocument, null, 0, null).iterateNext();
}
// If not, fall back to slower JavaScript implementation
var context = new XPathContext();
context.expressionContextNode = inDocument;
var xpathResult = new XPathParser().parse(xpath).evaluate(context);
if (xpathResult && xpathResult.toArray) {
return xpathResult.toArray()[0];
}
return null;
};
/**
* Finds a link element with text matching the expression supplied. Expressions must
* begin with "link:".
*/
PageBot.prototype.locateElementByLinkText = function(linkText, inDocument) {
var links = inDocument.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var element = links[i];
if (PatternMatcher.matches(linkText, getText(element))) {
return element;
}
}
return null;
};
PageBot.prototype.locateElementByLinkText.prefix = "link";
/**
* Returns an attribute based on an attribute locator. This is made up of an element locator
* suffixed with @attribute-name.
*/
PageBot.prototype.findAttribute = function(locator) {
// Split into locator + attributeName
var attributePos = locator.lastIndexOf("@");
var elementLocator = locator.slice(0, attributePos);
var attributeName = locator.slice(attributePos + 1);
// Find the element.
var element = this.findElement(elementLocator);
// Handle missing "class" attribute in IE.
if (browserVersion.isIE && attributeName == "class") {
attributeName = "className";
}
// Get the attribute value.
var attributeValue = element.getAttribute(attributeName);
return attributeValue ? attributeValue.toString() : null;
};
/*
* Select the specified option and trigger the relevant events of the element.
*/
PageBot.prototype.selectOption = function(element, optionToSelect) {
triggerEvent(element, 'focus', false);
var changed = false;
for (var i = 0; i < element.options.length; i++) {
var option = element.options[i];
if (option.selected && option != optionToSelect) {
option.selected = false;
changed = true;
}
else if (!option.selected && option == optionToSelect) {
option.selected = true;
changed = true;
}
}
if (changed) {
triggerEvent(element, 'change', true);
}
triggerEvent(element, 'blur', false);
};
/*
* Select the specified option and trigger the relevant events of the element.
*/
PageBot.prototype.addSelection = function(element, option) {
this.checkMultiselect(element);
triggerEvent(element, 'focus', false);
if (!option.selected) {
option.selected = true;
triggerEvent(element, 'change', true);
}
triggerEvent(element, 'blur', false);
};
/*
* Select the specified option and trigger the relevant events of the element.
*/
PageBot.prototype.removeSelection = function(element, option) {
this.checkMultiselect(element);
triggerEvent(element, 'focus', false);
if (option.selected) {
option.selected = false;
triggerEvent(element, 'change', true);
}
triggerEvent(element, 'blur', false);
};
PageBot.prototype.checkMultiselect = function(element) {
if (!element.multiple)
{
throw new SeleniumError("Not a multi-select");
}
};
PageBot.prototype.replaceText = function(element, stringValue) {
triggerEvent(element, 'focus', false);
triggerEvent(element, 'select', true);
element.value=stringValue;
triggerEvent(element, 'change', true);
triggerEvent(element, 'blur', false);
};
// TODO Opera uses this too - split out an Opera version so we don't need the isGecko check here
MozillaPageBot.prototype.clickElement = function(element) {
triggerEvent(element, 'focus', false);
// Add an event listener that detects if the default action has been prevented.
// (This is caused by a javascript onclick handler returning false)
var preventDefault = false;
if (browserVersion.isGecko) {
element.addEventListener("click", function(evt) {preventDefault = evt.getPreventDefault();}, false);
}
// Trigger the click event.
triggerMouseEvent(element, 'click', true);
// Perform the link action if preventDefault was set.
if (browserVersion.isGecko && !preventDefault) {
// Try the element itself, as well as it's parent - this handles clicking images inside links.
if (element.href) {
this.currentWindow.location.href = element.href;
}
else if (element.parentNode && element.parentNode.href) {
this.currentWindow.location.href = element.parentNode.href;
}
}
if (this.windowClosed()) {
return;
}
triggerEvent(element, 'blur', false);
};
KonquerorPageBot.prototype.clickElement = function(element) {
triggerEvent(element, 'focus', false);
if (element.click) {
element.click();
}
else {
triggerMouseEvent(element, 'click', true);
}
if (this.windowClosed()) {
return;
}
triggerEvent(element, 'blur', false);
};
SafariPageBot.prototype.clickElement = function(element) {
triggerEvent(element, 'focus', false);
var wasChecked = element.checked;
// For form element it is simple.
if (element.click) {
element.click();
}
// For links and other elements, event emulation is required.
else {
triggerMouseEvent(element, 'click', true);
// Unfortunately, triggering the event doesn't seem to activate onclick handlers.
// We currently call onclick for the link, but I'm guessing that the onclick for containing
// elements is not being called.
var success = true;
if (element.onclick) {
var evt = document.createEvent('HTMLEvents');
evt.initEvent('click', true, true);
var onclickResult = element.onclick(evt);
if (onclickResult === false) {
success = false;
}
}
if (success) {
// Try the element itself, as well as it's parent - this handles clicking images inside links.
if (element.href) {
this.currentWindow.location.href = element.href;
}
else if (element.parentNode.href) {
this.currentWindow.location.href = element.parentNode.href;
} else {
// This is true for buttons outside of forms, and maybe others.
LOG.warn("Ignoring 'click' call for button outside form, or link without href."
+ "Using buttons without an enclosing form can cause wierd problems with URL resolution in Safari." );
// I implemented special handling for window.open, but unfortunately this behaviour is also displayed
// when we have a button without an enclosing form that sets document.location in the onclick handler.
// The solution is to always use an enclosing form for a button.
}
}
}
if (this.windowClosed()) {
return;
}
triggerEvent(element, 'blur', false);
};
IEPageBot.prototype.clickElement = function(element) {
triggerEvent(element, 'focus', false);
var wasChecked = element.checked;
// Set a flag that records if the page will unload - this isn't always accurate, because
// <a href="javascript:alert('foo'):"> triggers the onbeforeunload event, even thought the page won't unload
var pageUnloading = false;
var pageUnloadDetector = function() {pageUnloading = true;};
this.currentWindow.attachEvent("onbeforeunload", pageUnloadDetector);
element.click();
// If the page is going to unload - still attempt to fire any subsequent events.
// However, we can't guarantee that the page won't unload half way through, so we need to handle exceptions.
try {
this.currentWindow.detachEvent("onbeforeunload", pageUnloadDetector);
if (this.windowClosed()) {
return;
}
// Onchange event is not triggered automatically in IE.
if (isDefined(element.checked) && wasChecked != element.checked) {
triggerEvent(element, 'change', true);
}
triggerEvent(element, 'blur', false);
}
catch (e) {
// If the page is unloading, we may get a "Permission denied" or "Unspecified error".
// Just ignore it, because the document may have unloaded.
if (pageUnloading) {
LOG.warn("Caught exception when firing events on unloading page: " + e.message);
return;
}
throw e;
}
};
PageBot.prototype.windowClosed = function(element) {
return this.currentWindow.closed;
};
PageBot.prototype.bodyText = function() {
return getText(this.currentDocument.body);
};
PageBot.prototype.getAllButtons = function() {
var elements = this.currentDocument.getElementsByTagName('input');
var result = '';
for (var i = 0; i < elements.length; i++) {
if (elements[i].type == 'button' || elements[i].type == 'submit' || elements[i].type == 'reset') {
result += elements[i].id;
result += ',';
}
}
return result;
};
PageBot.prototype.getAllFields = function() {
var elements = this.currentDocument.getElementsByTagName('input');
var result = '';
for (var i = 0; i < elements.length; i++) {
if (elements[i].type == 'text') {
result += elements[i].id;
result += ',';
}
}
return result;
};
PageBot.prototype.getAllLinks = function() {
var elements = this.currentDocument.getElementsByTagName('a');
var result = '';
for (var i = 0; i < elements.length; i++) {
result += elements[i].id;
result += ',';
}
return result;
};
PageBot.prototype.setContext = function(strContext, logLevel) {
//set the current test title
document.getElementById("context").innerHTML=strContext;
if (logLevel!=null) {
LOG.setLogLevelThreshold(logLevel);
}
};
function isDefined(value) {
return typeof(value) != undefined;
}
PageBot.prototype.goBack = function() {
this.currentWindow.history.back();
};
PageBot.prototype.goForward = function() {
this.currentWindow.history.forward();
};
PageBot.prototype.close = function() {
this.currentWindow.eval("window.close();");
};
PageBot.prototype.refresh = function() {
this.currentWindow.location.reload(true);
};
/**
* Refine a list of elements using a filter.
*/
PageBot.prototype.selectElementsBy = function(filterType, filter, elements) {
var filterFunction = PageBot.filterFunctions[filterType];
if (! filterFunction) {
throw new SeleniumError("Unrecognised element-filter type: '" + filterType + "'");
}
return filterFunction(filter, elements);
};
PageBot.filterFunctions = {};
PageBot.filterFunctions.name = function(name, elements) {
var selectedElements = [];
for (var i = 0; i < elements.length; i++) {
if (elements[i].name === name) {
selectedElements.push(elements[i]);
}
}
return selectedElements;
};
PageBot.filterFunctions.value = function(value, elements) {
var selectedElements = [];
for (var i = 0; i < elements.length; i++) {
if (elements[i].value === value) {
selectedElements.push(elements[i]);
}
}
return selectedElements;
};
PageBot.filterFunctions.index = function(index, elements) {
index = Number(index);
if (isNaN(index) || index < 0) {
throw new SeleniumError("Illegal Index: " + index);
}
if (elements.length <= index) {
throw new SeleniumError("Index out of range: " + index);
}
return [elements[index]];
};
PageBot.prototype.selectElements = function(filterExpr, elements, defaultFilterType) {
var filterType = (defaultFilterType || 'value');
// If there is a filter prefix, use the specified strategy
var result = filterExpr.match(/^([A-Za-z]+)=(.+)/);
if (result) {
filterType = result[1].toLowerCase();
filterExpr = result[2];
}
return this.selectElementsBy(filterType, filterExpr, elements);
};

View File

@ -1,86 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Although it's generally better web development practice not to use browser-detection
// (feature detection is better), the subtle browser differences that Selenium has to
// work around seem to make it necessary. Maybe as we learn more about what we need,
// we can do this in a more "feature-centric" rather than "browser-centric" way.
BrowserVersion = function() {
this.name = navigator.appName;
if (window.opera != null)
{
this.browser = BrowserVersion.OPERA;
this.isOpera = true;
return;
}
if (this.name == "Microsoft Internet Explorer")
{
this.browser = BrowserVersion.IE;
this.isIE = true;
if (window.top.SeleniumHTARunner && window.top.document.location.pathname.match(/.hta$/i)) {
this.isHTA = true;
}
return;
}
if (navigator.userAgent.indexOf('Safari') != -1)
{
this.browser = BrowserVersion.SAFARI;
this.isSafari = true;
this.khtml = true;
return;
}
if (navigator.userAgent.indexOf('Konqueror') != -1)
{
this.browser = BrowserVersion.KONQUEROR;
this.isKonqueror = true;
this.khtml = true;
return;
}
if (navigator.userAgent.indexOf('Firefox') != -1)
{
this.browser = BrowserVersion.FIREFOX;
this.isFirefox = true;
this.isGecko = true;
return;
}
if (navigator.userAgent.indexOf('Gecko') != -1)
{
this.browser = BrowserVersion.MOZILLA;
this.isMozilla = true;
this.isGecko = true;
return;
}
this.browser = BrowserVersion.UNKNOWN;
}
BrowserVersion.OPERA = "Opera";
BrowserVersion.IE = "IE";
BrowserVersion.KONQUEROR = "Konqueror";
BrowserVersion.SAFARI = "Safari";
BrowserVersion.FIREFOX = "Firefox";
BrowserVersion.MOZILLA = "Mozilla";
BrowserVersion.UNKNOWN = "Unknown";
browserVersion = new BrowserVersion();

View File

@ -1,371 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
function CommandHandlerFactory() {
this.actions = {};
this.asserts = {};
this.accessors = {};
var self = this;
this.registerAction = function(name, action, wait, dontCheckAlertsAndConfirms) {
var handler = new ActionHandler(action, wait, dontCheckAlertsAndConfirms);
this.actions[name] = handler;
};
this.registerAccessor = function(name, accessor) {
var handler = new AccessorHandler(accessor);
this.accessors[name] = handler;
};
this.registerAssert = function(name, assertion, haltOnFailure) {
var handler = new AssertHandler(assertion, haltOnFailure);
this.asserts[name] = handler;
};
this.getCommandHandler = function(name) {
return this.actions[name] || this.accessors[name] || this.asserts[name] || null;
};
this.registerAll = function(commandObject) {
registerAllAccessors(commandObject);
registerAllActions(commandObject);
registerAllAsserts(commandObject);
};
var registerAllActions = function(commandObject) {
for (var functionName in commandObject) {
var result = /^do([A-Z].+)$/.exec(functionName);
if (result != null) {
var actionName = result[1].lcfirst();
// Register the action without the wait flag.
var action = commandObject[functionName];
self.registerAction(actionName, action, false, false);
// Register actionName + "AndWait" with the wait flag;
var waitActionName = actionName + "AndWait";
self.registerAction(waitActionName, action, true, false);
}
}
};
var registerAllAsserts = function(commandObject) {
for (var functionName in commandObject) {
var result = /^assert([A-Z].+)$/.exec(functionName);
if (result != null) {
var assert = commandObject[functionName];
// Register the assert with the "assert" prefix, and halt on failure.
var assertName = functionName;
self.registerAssert(assertName, assert, true);
// Register the assert with the "verify" prefix, and do not halt on failure.
var verifyName = "verify" + result[1];
self.registerAssert(verifyName, assert, false);
}
}
};
// Given an accessor function getBlah(target),
// return a "predicate" equivalient to isBlah(target, value) that
// is true when the value returned by the accessor matches the specified value.
this.createPredicateFromSingleArgAccessor = function(accessor) {
return function(target, value) {
var accessorResult = accessor.call(this, target);
if (PatternMatcher.matches(value, accessorResult)) {
return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
} else {
return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'");
}
};
};
// Given a (no-arg) accessor function getBlah(),
// return a "predicate" equivalient to isBlah(value) that
// is true when the value returned by the accessor matches the specified value.
this.createPredicateFromNoArgAccessor = function(accessor) {
return function(value) {
var accessorResult = accessor.call(this);
if (PatternMatcher.matches(value, accessorResult)) {
return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
} else {
return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'");
}
};
};
// Given a boolean accessor function isBlah(),
// return a "predicate" equivalient to isBlah() that
// returns an appropriate PredicateResult value.
this.createPredicateFromBooleanAccessor = function(accessor) {
return function() {
var accessorResult;
if (arguments.length > 2) throw new SeleniumError("Too many arguments! " + arguments.length);
if (arguments.length == 2) {
accessorResult = accessor.call(this, arguments[0], arguments[1]);
} else if (arguments.length == 1) {
accessorResult = accessor.call(this, arguments[0]);
} else {
accessorResult = accessor.call(this);
}
if (accessorResult) {
return new PredicateResult(true, "true");
} else {
return new PredicateResult(false, "false");
}
};
};
// Given an accessor fuction getBlah([target]) (target is optional)
// return a predicate equivalent to isBlah([target,] value) that
// is true when the value returned by the accessor matches the specified value.
this.createPredicateFromAccessor = function(accessor) {
if (accessor.length == 0) {
return self.createPredicateFromNoArgAccessor(accessor);
}
return self.createPredicateFromSingleArgAccessor(accessor);
};
// Given a predicate, return the negation of that predicate.
// Leaves the message unchanged.
// Used to create assertNot, verifyNot, and waitForNot commands.
this.invertPredicate = function(predicate) {
return function(target, value) {
var result = predicate.call(this, target, value);
result.isTrue = ! result.isTrue;
return result;
};
};
// Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function.
this.createAssertionFromPredicate = function(predicate) {
return function(target, value) {
var result = predicate.call(this, target, value);
if (!result.isTrue) {
Assert.fail(result.message);
}
};
};
// Register an assertion, a verification, a negative assertion,
// and a negative verification based on the specified accessor.
this.registerAssertionsBasedOnAccessor = function(accessor, baseName, predicate) {
if (predicate==null) {
predicate = self.createPredicateFromAccessor(accessor);
}
var assertion = self.createAssertionFromPredicate(predicate);
// Register an assert with the "assert" prefix, and halt on failure.
self.registerAssert("assert" + baseName, assertion, true);
// Register a verify with the "verify" prefix, and do not halt on failure.
self.registerAssert("verify" + baseName, assertion, false);
var invertedPredicate = self.invertPredicate(predicate);
var negativeAssertion = self.createAssertionFromPredicate(invertedPredicate);
var result = /^(.*)Present$/.exec(baseName);
if (result==null) {
// Register an assertNot with the "assertNot" prefix, and halt on failure.
self.registerAssert("assertNot"+baseName, negativeAssertion, true);
// Register a verifyNot with the "verifyNot" prefix, and do not halt on failure.
self.registerAssert("verifyNot"+baseName, negativeAssertion, false);
}
else {
var invertedBaseName = result[1] + "NotPresent";
// Register an assertNot ending w/ "NotPresent", and halt on failure.
self.registerAssert("assert"+invertedBaseName, negativeAssertion, true);
// Register an assertNot ending w/ "NotPresent", and do not halt on failure.
self.registerAssert("verify"+invertedBaseName, negativeAssertion, false);
}
};
// Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function.
this.createWaitForActionFromPredicate = function(predicate) {
var action = function(target, value) {
var seleniumApi = this;
testLoop.waitForCondition = function () {
try {
return predicate.call(seleniumApi, target, value).isTrue;
} catch (e) {
// Treat exceptions as meaning the condition is not yet met.
// Useful, for example, for waitForValue when the element has
// not even been created yet.
// TODO: possibly should rethrow some types of exception.
return false;
}
};
};
return action;
};
// Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor.
this.registerWaitForCommandsBasedOnAccessor = function(accessor, baseName, predicate) {
if (predicate==null) {
predicate = self.createPredicateFromAccessor(accessor);
}
var waitForAction = self.createWaitForActionFromPredicate(predicate);
self.registerAction("waitFor"+baseName, waitForAction, false, accessor.dontCheckAlertsAndConfirms);
var invertedPredicate = self.invertPredicate(predicate);
var waitForNotAction = self.createWaitForActionFromPredicate(invertedPredicate);
self.registerAction("waitForNot"+baseName, waitForNotAction, false, accessor.dontCheckAlertsAndConfirms);
}
// Register a storeBlahBlah based on the specified accessor.
this.registerStoreCommandBasedOnAccessor = function(accessor, baseName) {
var action;
if (accessor.length == 1) {
action = function(target, varName) {
storedVars[varName] = accessor.call(this, target);
};
} else {
action = function(varName) {
storedVars[varName] = accessor.call(this);
};
}
self.registerAction("store"+baseName, action, false, accessor.dontCheckAlertsAndConfirms);
};
// Methods of the form getFoo(target) result in commands:
// getFoo, assertFoo, verifyFoo, assertNotFoo, verifyNotFoo
// storeFoo, waitForFoo, and waitForNotFoo.
var registerAllAccessors = function(commandObject) {
for (var functionName in commandObject) {
var match = /^get([A-Z].+)$/.exec(functionName);
if (match != null) {
var accessor = commandObject[functionName];
var baseName = match[1];
self.registerAccessor(functionName, accessor);
self.registerAssertionsBasedOnAccessor(accessor, baseName);
self.registerStoreCommandBasedOnAccessor(accessor, baseName);
self.registerWaitForCommandsBasedOnAccessor(accessor, baseName);
}
var match = /^is([A-Z].+)$/.exec(functionName);
if (match != null) {
var accessor = commandObject[functionName];
var baseName = match[1];
var predicate = self.createPredicateFromBooleanAccessor(accessor);
self.registerAccessor(functionName, accessor);
self.registerAssertionsBasedOnAccessor(accessor, baseName, predicate);
self.registerStoreCommandBasedOnAccessor(accessor, baseName);
self.registerWaitForCommandsBasedOnAccessor(accessor, baseName, predicate);
}
}
};
}
function PredicateResult(isTrue, message) {
this.isTrue = isTrue;
this.message = message;
}
// NOTE: The CommandHandler is effectively an abstract base for
// various handlers including ActionHandler, AccessorHandler and AssertHandler.
// Subclasses need to implement an execute(seleniumApi, command) function,
// where seleniumApi is the Selenium object, and command a SeleniumCommand object.
function CommandHandler(type, haltOnFailure, executor) {
this.type = type;
this.haltOnFailure = haltOnFailure;
this.executor = executor;
}
// An ActionHandler is a command handler that executes the sepcified action,
// possibly checking for alerts and confirmations (if checkAlerts is set), and
// possibly waiting for a page load if wait is set.
function ActionHandler(action, wait, dontCheckAlerts) {
CommandHandler.call(this, "action", true, action);
if (wait) {
this.wait = true;
}
// note that dontCheckAlerts could be undefined!!!
this.checkAlerts = (dontCheckAlerts) ? false : true;
}
ActionHandler.prototype = new CommandHandler;
ActionHandler.prototype.execute = function(seleniumApi, command) {
if (this.checkAlerts && (null==/(Alert|Confirmation)(Not)?Present/.exec(command.command))) {
this.checkForAlerts(seleniumApi);
}
var processState = this.executor.call(seleniumApi, command.target, command.value);
// If the handler didn't return a wait flag, check to see if the
// handler was registered with the wait flag.
if (processState == undefined && this.wait) {
processState = SELENIUM_PROCESS_WAIT;
}
return new CommandResult(processState);
};
ActionHandler.prototype.checkForAlerts = function(seleniumApi) {
if ( seleniumApi.browserbot.hasAlerts() ) {
throw new SeleniumError("There was an unexpected Alert! [" + seleniumApi.browserbot.getNextAlert() + "]");
}
if ( seleniumApi.browserbot.hasConfirmations() ) {
throw new SeleniumError("There was an unexpected Confirmation! [" + seleniumApi.browserbot.getNextConfirmation() + "]");
}
};
function AccessorHandler(accessor) {
CommandHandler.call(this, "accessor", true, accessor);
}
AccessorHandler.prototype = new CommandHandler;
AccessorHandler.prototype.execute = function(seleniumApi, command) {
var returnValue = this.executor.call(seleniumApi, command.target, command.value);
var result = new CommandResult();
result.result = returnValue;
return result;
};
/**
* Handler for assertions and verifications.
*/
function AssertHandler(assertion, haltOnFailure) {
CommandHandler.call(this, "assert", haltOnFailure || false, assertion);
}
AssertHandler.prototype = new CommandHandler;
AssertHandler.prototype.execute = function(seleniumApi, command) {
var result = new CommandResult();
try {
this.executor.call(seleniumApi, command.target, command.value);
result.passed = true;
} catch (e) {
// If this is not a AssertionFailedError, or we should haltOnFailure, rethrow.
if (!e.isAssertionFailedError) {
throw e;
}
if (this.haltOnFailure) {
var error = new SeleniumError(e.failureMessage);
throw error;
}
result.failed = true;
result.failureMessage = e.failureMessage;
}
return result;
};
function CommandResult(processState) {
this.processState = processState;
this.result = null;
}
function SeleniumCommand(command, target, value) {
this.command = command;
this.target = target;
this.value = value;
}

View File

@ -1,266 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
SELENIUM_PROCESS_WAIT = "wait";
function TestLoop(commandFactory) {
this.commandFactory = commandFactory;
this.waitForConditionTimeout = 30 * 1000; // 30 seconds
this.start = function() {
selenium.reset();
LOG.debug("testLoop.start()");
this.continueTest();
};
/**
* Select the next command and continue the test.
*/
this.continueTest = function() {
LOG.debug("testLoop.continueTest() - acquire the next command");
if (! this.aborted) {
this.currentCommand = this.nextCommand();
}
if (! this.requiresCallBack) {
this.beginNextTest();
} // otherwise, just finish and let the callback invoke beginNextTest()
};
this.beginNextTest = function() {
LOG.debug("testLoop.beginNextTest()");
if (this.currentCommand) {
// TODO: rename commandStarted to commandSelected, OR roll it into nextCommand
this.commandStarted(this.currentCommand);
this.resumeAfterDelay();
} else {
this.testComplete();
}
}
/**
* Pause, then execute the current command.
*/
this.resumeAfterDelay = function() {
// Get the command delay. If a pauseInterval is set, use it once
// and reset it. Otherwise, use the defined command-interval.
var delay = this.pauseInterval || this.getCommandInterval();
this.pauseInterval = undefined;
if (delay < 0) {
// Pause: enable the "next/continue" button
this.pause();
} else {
window.setTimeout("testLoop.resume()", delay);
}
};
/**
* Select the next command and continue the test.
*/
this.resume = function() {
LOG.debug("testLoop.resume() - actually execute");
try {
this.executeCurrentCommand();
this.waitForConditionStart = new Date().getTime();
this.continueTestWhenConditionIsTrue();
} catch (e) {
this.handleCommandError(e);
this.testComplete();
return;
}
};
/**
* Execute the current command.
*
* The return value, if not null, should be a function which will be
* used to determine when execution can continue.
*/
this.executeCurrentCommand = function() {
var command = this.currentCommand;
LOG.info("Executing: |" + command.command + " | " + command.target + " | " + command.value + " |");
var handler = this.commandFactory.getCommandHandler(command.command);
if (handler == null) {
throw new SeleniumError("Unknown command: '" + command.command + "'");
}
command.target = selenium.preprocessParameter(command.target);
command.value = selenium.preprocessParameter(command.value);
LOG.debug("Command found, going to execute " + command.command);
var result = handler.execute(selenium, command);
LOG.debug("Command complete");
this.commandComplete(result);
if (result.processState == SELENIUM_PROCESS_WAIT) {
this.waitForCondition = function() {
LOG.debug("Checking condition: isNewPageLoaded?");
return selenium.browserbot.isNewPageLoaded();
};
}
};
this.handleCommandError = function(e) {
if (!e.isSeleniumError) {
LOG.exception(e);
var msg = "Selenium failure. Please report to selenium-dev@openqa.org, with error details from the log window.";
if (e.message) {
msg += " The error message is: " + e.message;
}
this.commandError(msg);
} else {
LOG.error(e.message);
this.commandError(e.message);
}
};
/**
* Busy wait for waitForCondition() to become true, and then carry on
* with test. Fail the current test if there's a timeout or an exception.
*/
this.continueTestWhenConditionIsTrue = function () {
LOG.debug("testLoop.continueTestWhenConditionIsTrue()");
try {
if (this.waitForCondition == null || this.waitForCondition()) {
LOG.debug("condition satisfied; let's continueTest()");
this.waitForCondition = null;
this.waitForConditionStart = null;
this.continueTest();
} else {
LOG.debug("waitForCondition was false; keep waiting!");
if (this.waitForConditionTimeout != null) {
var now = new Date();
if ((now - this.waitForConditionStart) > this.waitForConditionTimeout) {
throw new SeleniumError("Timed out after " + this.waitForConditionTimeout + "ms");
}
}
window.setTimeout("testLoop.continueTestWhenConditionIsTrue()", 10);
}
} catch (e) {
var lastResult = new CommandResult();
lastResult.failed = true;
lastResult.failureMessage = e.message;
this.commandComplete(lastResult);
this.testComplete();
}
};
}
/** The default is not to have any interval between commands. */
TestLoop.prototype.getCommandInterval = function() {
return 0;
};
TestLoop.prototype.nextCommand = noop;
TestLoop.prototype.commandStarted = noop;
TestLoop.prototype.commandError = noop;
TestLoop.prototype.commandComplete = noop;
TestLoop.prototype.testComplete = noop;
TestLoop.prototype.pause = noop;
function noop() {
};
/**
* Tell Selenium to expect a failure on the next command execution. This
* command temporarily installs a CommandFactory that generates
* CommandHandlers that expect a failure.
*/
Selenium.prototype.assertFailureOnNext = function(message) {
if (!message) {
throw new Error("Message must be provided");
}
var expectFailureCommandFactory =
new ExpectFailureCommandFactory(testLoop.commandFactory, message, "failure");
expectFailureCommandFactory.baseExecutor = executeCommandAndReturnFailureMessage;
testLoop.commandFactory = expectFailureCommandFactory;
};
/**
* Tell Selenium to expect an error on the next command execution. This
* command temporarily installs a CommandFactory that generates
* CommandHandlers that expect a failure.
*/
Selenium.prototype.assertErrorOnNext = function(message) {
if (!message) {
throw new Error("Message must be provided");
}
var expectFailureCommandFactory =
new ExpectFailureCommandFactory(testLoop.commandFactory, message, "error");
expectFailureCommandFactory.baseExecutor = executeCommandAndReturnErrorMessage;
testLoop.commandFactory = expectFailureCommandFactory;
};
function ExpectFailureCommandFactory(originalCommandFactory, expectedErrorMessage, errorType) {
this.getCommandHandler = function(name) {
var baseHandler = originalCommandFactory.getCommandHandler(name);
var baseExecutor = this.baseExecutor;
var expectFailureCommand = {};
expectFailureCommand.execute = function() {
var baseFailureMessage = baseExecutor(baseHandler, arguments);
var result = new CommandResult();
if (!baseFailureMessage) {
result.failed = true;
result.failureMessage = "Expected " + errorType + " did not occur.";
}
else {
if (! PatternMatcher.matches(expectedErrorMessage, baseFailureMessage)) {
result.failed = true;
result.failureMessage = "Expected " + errorType + " message '" + expectedErrorMessage
+ "' but was '" + baseFailureMessage + "'";
}
else {
result.passed = true;
result.result = baseFailureMessage;
}
}
testLoop.commandFactory = originalCommandFactory;
return result;
};
return expectFailureCommand;
};
};
function executeCommandAndReturnFailureMessage(baseHandler, originalArguments) {
var baseResult = baseHandler.execute.apply(baseHandler, originalArguments);
if (baseResult.passed) {
return null;
}
return baseResult.failureMessage;
};
function executeCommandAndReturnErrorMessage(baseHandler, originalArguments) {
try {
baseHandler.execute.apply(baseHandler, originalArguments);
return null;
}
catch (expected) {
return expected.message;
}
};

View File

@ -1,119 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Logger = function() {
this.logWindow = null;
}
Logger.prototype = {
setLogLevelThreshold: function(logLevel) {
this.pendingLogLevelThreshold = logLevel;
this.show();
//
// The following message does not show up in the log -- _unless_ I step along w/ the debugger
// down to the append call. I believe this is because the new log window has not yet loaded,
// and therefore the log msg is discarded; but if I step through the debugger, this changes
// the scheduling so as to load that window and make it ready.
// this.info("Log level programmatically set to " + logLevel + " (presumably by driven-mode test code)");
},
getLogWindow: function() {
if (this.logWindow && this.logWindow.closed) {
this.logWindow = null;
}
if (this.logWindow && this.pendingLogLevelThreshold && this.logWindow.setThresholdLevel) {
this.logWindow.setThresholdLevel(this.pendingLogLevelThreshold);
// can't just directly log because that action would loop back to this code infinitely
this.pendingInfoMessage = "Log level programmatically set to " + this.pendingLogLevelThreshold + " (presumably by driven-mode test code)";
this.pendingLogLevelThreshold = null; // let's only go this way one time
}
return this.logWindow;
},
openLogWindow: function() {
this.logWindow = window.open(
"SeleniumLog.html", "SeleniumLog",
"width=600,height=250,bottom=0,right=0,status,scrollbars,resizable"
);
return this.logWindow;
},
show: function() {
if (! this.getLogWindow()) {
this.openLogWindow();
}
},
log: function(message, className) {
var logWindow = this.getLogWindow();
if (logWindow) {
if (logWindow.append) {
if (this.pendingInfoMessage) {
logWindow.append(this.pendingInfoMessage, "info");
this.pendingInfoMessage = null;
}
logWindow.append(message, className);
}
}
},
close: function(message) {
if (this.logWindow != null) {
this.logWindow.close();
this.logWindow = null;
}
},
debug: function(message) {
this.log(message, "debug");
},
info: function(message) {
this.log(message, "info");
},
warn: function(message) {
this.log(message, "warn");
},
error: function(message) {
this.log(message, "error");
},
exception: function(exception) {
var msg = "Unexpected Exception: " + describe(exception, ', ');
this.error(msg);
}
};
var LOG = new Logger();
function noop() {};
var DummyLogger = function() {};
DummyLogger.prototype = {
show: noop,
log: noop,
debug: noop,
info: noop,
warn: noop,
error: noop
};

View File

@ -1,264 +0,0 @@
/*
* Copyright 2005 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
passColor = "#cfffcf";
failColor = "#ffcfcf";
errorColor = "#ffffff";
workingColor = "#DEE7EC";
doneColor = "#FFFFCC";
slowMode = false;
var cmd1 = document.createElement("div");
var cmd2 = document.createElement("div");
var cmd3 = document.createElement("div");
var cmd4 = document.createElement("div");
var postResult = "START";
function runTest() {
var testAppFrame = document.getElementById('myiframe');
selenium = Selenium.createForFrame(testAppFrame);
commandFactory = new CommandHandlerFactory();
commandFactory.registerAll(selenium);
testLoop = new TestLoop(commandFactory);
testLoop.nextCommand = nextCommand;
testLoop.commandStarted = commandStarted;
testLoop.commandComplete = commandComplete;
testLoop.commandError = commandError;
testLoop.requiresCallBack = true;
testLoop.testComplete = function() {
window.status = "Selenium Tests Complete, for this Test"
// Continue checking for new results
testLoop.continueTest();
postResult = "START";
};
document.getElementById("commandList").appendChild(cmd4);
document.getElementById("commandList").appendChild(cmd3);
document.getElementById("commandList").appendChild(cmd2);
document.getElementById("commandList").appendChild(cmd1);
var doContinue = getQueryVariable("continue");
if (doContinue != null) postResult = "OK";
testLoop.start();
}
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
}
function buildBaseUrl() {
var lastSlash = window.location.href.lastIndexOf('/');
var baseUrl = window.location.href.substring(0, lastSlash+1);
return baseUrl;
}
function buildDriverParams() {
var params = "";
var host = getQueryVariable("driverhost");
var port = getQueryVariable("driverport");
if (host != undefined && port != undefined) {
params = params + "&driverhost=" + host + "&driverport=" + port;
}
var sessionId = getQueryVariable("sessionId");
if (sessionId != undefined) {
params = params + "&sessionId=" + sessionId;
}
return params;
}
function preventBrowserCaching() {
var t = (new Date()).getTime();
return "&counterToMakeURsUniqueAndSoStopPageCachingInTheBrowser=" + t;
}
function nextCommand() {
xmlHttp = XmlHttp.create();
try {
var url = buildBaseUrl();
if (postResult == "START") {
url = url + "driver/?seleniumStart=true" + buildDriverParams() + preventBrowserCaching();
} else {
url = url + "driver/?" + buildDriverParams() + preventBrowserCaching();
}
LOG.debug("XMLHTTPRequesting " + url);
xmlHttp.open("POST", url, true);
xmlHttp.onreadystatechange=handleHttpResponse;
xmlHttp.send(postResult);
} catch(e) {
var s = 'xmlHttp returned:\n'
for (key in e) {
s += "\t" + key + " -> " + e[key] + "\n"
}
LOG.error(s);
return null;
}
return null;
}
function handleHttpResponse() {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status == 200) {
var command = extractCommand(xmlHttp);
testLoop.currentCommand = command;
testLoop.beginNextTest();
} else {
var s = 'xmlHttp returned: ' + xmlHttp.status + ": " + xmlHttp.statusText;
LOG.error(s);
testLoop.currentCommand = null;
setTimeout("testLoop.beginNextTest();", 2000);
}
}
}
function extractCommand(xmlHttp) {
if (slowMode) {
delay(2000);
}
var command;
try {
command = xmlHttp.responseText;
} catch (e) {
alert('could not get responseText: ' + e.message);
}
if (command.substr(0,'|testComplete'.length)=='|testComplete') {
return null;
}
return createCommandFromRequest(command);
}
function commandStarted(command) {
commandNode = document.createElement("div");
innerHTML = command.command + '(';
if (command.target != null && command.target != "") {
innerHTML += command.target;
if (command.value != null && command.value != "") {
innerHTML += ', ' + command.value;
}
}
innerHTML += ")";
commandNode.innerHTML = innerHTML;
commandNode.style.backgroundColor = workingColor;
document.getElementById("commandList").removeChild(cmd1);
document.getElementById("commandList").removeChild(cmd2);
document.getElementById("commandList").removeChild(cmd3);
document.getElementById("commandList").removeChild(cmd4);
cmd4 = cmd3;
cmd3 = cmd2;
cmd2 = cmd1;
cmd1 = commandNode;
document.getElementById("commandList").appendChild(cmd4);
document.getElementById("commandList").appendChild(cmd3);
document.getElementById("commandList").appendChild(cmd2);
document.getElementById("commandList").appendChild(cmd1);
}
function commandComplete(result) {
if (result.failed) {
if (postResult == "CONTINUATION") {
testLoop.aborted = true;
}
postResult = result.failureMessage;
commandNode.title = result.failureMessage;
commandNode.style.backgroundColor = failColor;
} else if (result.passed) {
postResult = "OK";
commandNode.style.backgroundColor = passColor;
} else {
if (result.result == null) {
postResult = "OK";
} else {
postResult = "OK," + result.result;
}
commandNode.style.backgroundColor = doneColor;
}
}
function commandError(message) {
postResult = "ERROR: " + message;
commandNode.style.backgroundColor = errorColor;
commandNode.title = message;
}
function slowClicked() {
slowMode = !slowMode;
}
function delay(millis) {
startMillis = new Date();
while (true) {
milli = new Date();
if (milli-startMillis > millis) {
break;
}
}
}
function getIframeDocument(iframe) {
if (iframe.contentDocument) {
return iframe.contentDocument;
}
else {
return iframe.contentWindow.document;
}
}
// Parses a URI query string into a SeleniumCommand object
function createCommandFromRequest(commandRequest) {
//decodeURIComponent doesn't strip plus signs
var processed = commandRequest.replace(/\+/g, "%20");
// strip trailing spaces
var processed = processed.replace(/\s+$/, "");
var vars = processed.split("&");
var cmdArgs = new Object();
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
cmdArgs[pair[0]] = pair[1];
}
var cmd = cmdArgs['cmd'];
var arg1 = cmdArgs['1'];
if (null == arg1) arg1 = "";
arg1 = decodeURIComponent(arg1);
var arg2 = cmdArgs['2'];
if (null == arg2) arg2 = "";
arg2 = decodeURIComponent(arg2);
if (cmd == null) {
throw new Error("Bad command request: " + commandRequest);
}
return new SeleniumCommand(cmd, arg1, arg2);
}

View File

@ -1,742 +0,0 @@
/*
* Copyright 2004 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// The current row in the list of tests (test suite)
currentRowInSuite = 0;
// An object representing the current test
currentTest = null;
// Whether or not the jsFT should run all tests in the suite
runAllTests = false;
// Whether or not the current test has any errors;
testFailed = false;
suiteFailed = false;
// Colors used to provide feedback
passColor = "#ccffcc";
doneColor = "#eeffee";
failColor = "#ffcccc";
workingColor = "#ffffcc";
// Holds the handlers for each command.
commandHandlers = null;
// The number of tests run
numTestPasses = 0;
// The number of tests that have failed
numTestFailures = 0;
// The number of commands which have passed
numCommandPasses = 0;
// The number of commands which have failed
numCommandFailures = 0;
// The number of commands which have caused errors (element not found)
numCommandErrors = 0;
// The time that the test was started.
startTime = null;
// The current time.
currentTime = null;
// An simple enum for failureType
ERROR = 0;
FAILURE = 1;
runInterval = 0;
queryString = null;
function setRunInterval() {
// Get the value of the checked runMode option.
// There should be a way of getting the value of the "group", but I don't know how.
var runModeOptions = document.forms['controlPanel'].runMode;
for (var i = 0; i < runModeOptions.length; i++) {
if (runModeOptions[i].checked) {
runInterval = runModeOptions[i].value;
break;
}
}
}
function continueCurrentTest() {
document.getElementById('continueTest').disabled = true;
testLoop.resume();
}
function getApplicationFrame() {
return document.getElementById('myiframe');
}
function getSuiteFrame() {
return document.getElementById('testSuiteFrame');
}
function getTestFrame(){
return document.getElementById('testFrame');
}
function loadAndRunIfAuto() {
loadSuiteFrame();
}
function start() {
queryString = null;
setRunInterval();
loadSuiteFrame();
}
function loadSuiteFrame() {
var testAppFrame = document.getElementById('myiframe');
selenium = Selenium.createForFrame(testAppFrame);
registerCommandHandlers();
//set the runInterval if there is a queryParameter for it
var tempRunInterval = getQueryParameter("runInterval");
if (tempRunInterval) {
runInterval = tempRunInterval;
}
document.getElementById("modeRun").onclick = setRunInterval;
document.getElementById('modeWalk').onclick = setRunInterval;
document.getElementById('modeStep').onclick = setRunInterval;
document.getElementById('continueTest').onclick = continueCurrentTest;
var testSuiteName = getQueryParameter("test");
if (testSuiteName) {
addLoadListener(getSuiteFrame(), onloadTestSuite);
getSuiteFrame().src = testSuiteName;
} else {
onloadTestSuite();
}
}
function startSingleTest() {
removeLoadListener(getApplicationFrame(), startSingleTest);
var singleTestName = getQueryParameter("singletest");
addLoadListener(getTestFrame(), startTest);
getTestFrame().src = singleTestName;
}
function getIframeDocument(iframe)
{
if (iframe.contentDocument) {
return iframe.contentDocument;
}
else {
return iframe.contentWindow.document;
}
}
function onloadTestSuite() {
removeLoadListener(getSuiteFrame(), onloadTestSuite);
// Add an onclick function to each link in all suite tables
var allTables = getIframeDocument(getSuiteFrame()).getElementsByTagName("table");
for (var tableNum = 0; tableNum < allTables.length; tableNum++)
{
var skippedTable = allTables[tableNum];
for(rowNum = 1;rowNum < skippedTable.rows.length; rowNum++) {
addOnclick(skippedTable, rowNum);
}
}
suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0];
if (suiteTable!=null) {
if (isAutomatedRun()) {
startTestSuite();
} else if (getQueryParameter("autoURL")) {
addLoadListener(getApplicationFrame(), startSingleTest);
getApplicationFrame().src = getQueryParameter("autoURL");
} else {
testLink = suiteTable.rows[currentRowInSuite+1].cells[0].getElementsByTagName("a")[0];
getTestFrame().src = testLink.href;
}
}
}
// Adds an onclick function to the link in the given row in suite table.
// This function checks whether the test has already been run and the data is
// stored. If the data is stored, it sets the test frame to be the stored data.
// Otherwise, it loads the fresh page.
function addOnclick(suiteTable, rowNum) {
aLink = suiteTable.rows[rowNum].cells[0].getElementsByTagName("a")[0];
aLink.onclick = function(eventObj) {
srcObj = null;
// For mozilla-like browsers
if(eventObj)
srcObj = eventObj.target;
// For IE-like browsers
else if (getSuiteFrame().contentWindow.event)
srcObj = getSuiteFrame().contentWindow.event.srcElement;
// The target row (the event source is not consistently reported by browsers)
row = srcObj.parentNode.parentNode.rowIndex || srcObj.parentNode.parentNode.parentNode.rowIndex;
// If the row has a stored results table, use that
if(suiteTable.rows[row].cells[1]) {
var bodyElement = getIframeDocument(getTestFrame()).body;
// Create a div element to hold the results table.
var tableNode = getIframeDocument(getTestFrame()).createElement("div");
var resultsCell = suiteTable.rows[row].cells[1];
tableNode.innerHTML = resultsCell.innerHTML;
// Append this text node, and remove all the preceding nodes.
bodyElement.appendChild(tableNode);
while (bodyElement.firstChild != bodyElement.lastChild) {
bodyElement.removeChild(bodyElement.firstChild);
}
}
// Otherwise, just open up the fresh page.
else {
getTestFrame().src = suiteTable.rows[row].cells[0].getElementsByTagName("a")[0].href;
}
return false;
};
}
function isQueryParameterTrue(name) {
parameterValue = getQueryParameter(name);
return (parameterValue != null && parameterValue.toLowerCase() == "true");
}
function getQueryString() {
if (queryString != null) return queryString;
if (browserVersion.isHTA) {
var args = extractArgs();
if (args.length < 2) return null;
queryString = args[1];
return queryString;
} else {
return location.search.substr(1);
}
}
function extractArgs() {
var str = SeleniumHTARunner.commandLine;
if (str == null || str == "") return new Array();
var matches = str.match(/(?:"([^"]+)"|(?!"([^"]+)")\b(\S+)\b)/g);
// We either want non quote stuff ([^"]+) surrounded by quotes
// or we want to look-ahead, see that the next character isn't
// a quoted argument, and then grab all the non-space stuff
// this will return for the line: "foo" bar
// the results "\"foo\"" and "bar"
// So, let's unquote the quoted arguments:
var args = new Array;
for (var i = 0; i < matches.length; i++) {
args[i] = matches[i];
args[i] = args[i].replace(/^"(.*)"$/, "$1");
}
return args;
}
function getQueryParameter(searchKey) {
var str = getQueryString();
if (str == null) return null;
var clauses = str.split('&');
for (var i = 0; i < clauses.length; i++) {
var keyValuePair = clauses[i].split('=',2);
var key = unescape(keyValuePair[0]);
if (key == searchKey) {
return unescape(keyValuePair[1]);
}
}
return null;
}
function isNewWindow() {
return isQueryParameterTrue("newWindow");
}
function isAutomatedRun() {
return isQueryParameterTrue("auto");
}
function resetMetrics() {
numTestPasses = 0;
numTestFailures = 0;
numCommandPasses = 0;
numCommandFailures = 0;
numCommandErrors = 0;
startTime = new Date().getTime();
}
function runSingleTest() {
runAllTests = false;
resetMetrics();
startTest();
}
function startTest() {
removeLoadListener(getTestFrame(), startTest);
// Scroll to the top of the test frame
if (getTestFrame().contentWindow) {
getTestFrame().contentWindow.scrollTo(0,0);
}
else {
frames['testFrame'].scrollTo(0,0);
}
currentTest = new HtmlTest(getIframeDocument(getTestFrame()));
testFailed = false;
storedVars = new Object();
testLoop = initialiseTestLoop();
testLoop.start();
}
function HtmlTest(testDocument) {
this.init(testDocument);
}
HtmlTest.prototype = {
init: function(testDocument) {
this.document = testDocument;
this.document.bgColor = "";
this.currentRow = null;
this.commandRows = new Array();
this.headerRow = null;
var tables = this.document.getElementsByTagName("table");
for (var i = 0; i < tables.length; i++) {
var candidateRows = tables[i].rows;
for (var j = 0; j < candidateRows.length; j++) {
if (!this.headerRow) {
this.headerRow = candidateRows[j];
}
if (candidateRows[j].cells.length >= 3) {
this.addCommandRow(candidateRows[j]);
}
}
}
},
addCommandRow: function(row) {
if (row.cells[2] && row.cells[2].originalHTML) {
row.cells[2].innerHTML = row.cells[2].originalHTML;
}
row.bgColor = "";
this.commandRows.push(row);
},
nextCommand: function() {
if (this.commandRows.length > 0) {
this.currentRow = this.commandRows.shift();
} else {
this.currentRow = null;
}
return this.currentRow;
}
};
function startTestSuite() {
resetMetrics();
currentRowInSuite = 0;
runAllTests = true;
suiteFailed = false;
runNextTest();
}
function runNextTest() {
if (!runAllTests)
return;
suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0];
// Do not change the row color of the first row
if (currentRowInSuite > 0) {
// Provide test-status feedback
if (testFailed) {
setCellColor(suiteTable.rows, currentRowInSuite, 0, failColor);
} else {
setCellColor(suiteTable.rows, currentRowInSuite, 0, passColor);
}
// Set the results from the previous test run
setResultsData(suiteTable, currentRowInSuite);
}
currentRowInSuite++;
// If we are done with all of the tests, set the title bar as pass or fail
if (currentRowInSuite >= suiteTable.rows.length) {
if (suiteFailed) {
setCellColor(suiteTable.rows, 0, 0, failColor);
} else {
setCellColor(suiteTable.rows, 0, 0, passColor);
}
// If this is an automated run (i.e., build script), then submit
// the test results by posting to a form
if (isAutomatedRun())
postTestResults(suiteFailed, suiteTable);
}
else {
// Make the current row blue
setCellColor(suiteTable.rows, currentRowInSuite, 0, workingColor);
testLink = suiteTable.rows[currentRowInSuite].cells[0].getElementsByTagName("a")[0];
testLink.focus();
var testFrame = getTestFrame();
addLoadListener(testFrame, startTest);
selenium.browserbot.setIFrameLocation(testFrame, testLink.href);
}
}
function setCellColor(tableRows, row, col, colorStr) {
tableRows[row].cells[col].bgColor = colorStr;
}
// Sets the results from a test into a hidden column on the suite table. So,
// for each tests, the second column is set to the HTML from the test table.
function setResultsData(suiteTable, row) {
// Create a text node of the test table
var resultTable = getIframeDocument(getTestFrame()).body.innerHTML;
if (!resultTable) return;
var tableNode = suiteTable.ownerDocument.createElement("div");
tableNode.innerHTML = resultTable;
var new_column = suiteTable.ownerDocument.createElement("td");
new_column.appendChild(tableNode);
// Set the column to be invisible
new_column.style.cssText = "display: none;";
// Add the invisible column
suiteTable.rows[row].appendChild(new_column);
}
// Post the results to a servlet, CGI-script, etc. The URL of the
// results-handler defaults to "/postResults", but an alternative location
// can be specified by providing a "resultsUrl" query parameter.
//
// Parameters passed to the results-handler are:
// result: passed/failed depending on whether the suite passed or failed
// totalTime: the total running time in seconds for the suite.
//
// numTestPasses: the total number of tests which passed.
// numTestFailures: the total number of tests which failed.
//
// numCommandPasses: the total number of commands which passed.
// numCommandFailures: the total number of commands which failed.
// numCommandErrors: the total number of commands which errored.
//
// suite: the suite table, including the hidden column of test results
// testTable.1 to testTable.N: the individual test tables
//
function postTestResults(suiteFailed, suiteTable) {
form = document.createElement("form");
document.body.appendChild(form);
form.id = "resultsForm";
form.method="post";
form.target="myiframe";
var resultsUrl = getQueryParameter("resultsUrl");
if (!resultsUrl) {
resultsUrl = "./postResults";
}
var actionAndParameters = resultsUrl.split('?',2);
form.action = actionAndParameters[0];
var resultsUrlQueryString = actionAndParameters[1];
form.createHiddenField = function(name, value) {
input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = value;
this.appendChild(input);
};
if (resultsUrlQueryString) {
var clauses = resultsUrlQueryString.split('&');
for (var i = 0; i < clauses.length; i++) {
var keyValuePair = clauses[i].split('=',2);
var key = unescape(keyValuePair[0]);
var value = unescape(keyValuePair[1]);
form.createHiddenField(key, value);
}
}
form.createHiddenField("selenium.version", Selenium.version);
form.createHiddenField("selenium.revision", Selenium.revision);
form.createHiddenField("result", suiteFailed == true ? "failed" : "passed");
form.createHiddenField("totalTime", Math.floor((currentTime - startTime) / 1000));
form.createHiddenField("numTestPasses", numTestPasses);
form.createHiddenField("numTestFailures", numTestFailures);
form.createHiddenField("numCommandPasses", numCommandPasses);
form.createHiddenField("numCommandFailures", numCommandFailures);
form.createHiddenField("numCommandErrors", numCommandErrors);
// Create an input for each test table. The inputs are named
// testTable.1, testTable.2, etc.
for (rowNum = 1; rowNum < suiteTable.rows.length;rowNum++) {
// If there is a second column, then add a new input
if (suiteTable.rows[rowNum].cells.length > 1) {
var resultCell = suiteTable.rows[rowNum].cells[1];
form.createHiddenField("testTable." + rowNum, resultCell.innerHTML);
// remove the resultCell, so it's not included in the suite HTML
resultCell.parentNode.removeChild(resultCell);
}
}
form.createHiddenField("numTestTotal", rowNum);
// Add HTML for the suite itself
form.createHiddenField("suite", suiteTable.parentNode.innerHTML);
if (isQueryParameterTrue("save")) {
saveToFile(resultsUrl, form);
} else {
form.submit();
}
document.body.removeChild(form);
if (isQueryParameterTrue("close")) {
window.top.close();
}
}
function saveToFile(fileName, form) {
// This only works when run as an IE HTA
var inputs = new Object();
for (var i = 0; i < form.elements.length; i++) {
inputs[form.elements[i].name] = form.elements[i].value;
}
var objFSO = new ActiveXObject("Scripting.FileSystemObject")
var scriptFile = objFSO.CreateTextFile(fileName);
scriptFile.WriteLine("<html><body>\n<h1>Test suite results </h1>" +
"\n\n<table>\n<tr>\n<td>result:</td>\n<td>" + inputs["result"] + "</td>\n" +
"</tr>\n<tr>\n<td>totalTime:</td>\n<td>" + inputs["totalTime"] + "</td>\n</tr>\n" +
"<tr>\n<td>numTestPasses:</td>\n<td>" + inputs["numTestPasses"] + "</td>\n</tr>\n" +
"<tr>\n<td>numTestFailures:</td>\n<td>" + inputs["numTestFailures"] + "</td>\n</tr>\n" +
"<tr>\n<td>numCommandPasses:</td>\n<td>" + inputs["numCommandPasses"] + "</td>\n</tr>\n" +
"<tr>\n<td>numCommandFailures:</td>\n<td>" + inputs["numCommandFailures"] + "</td>\n</tr>\n" +
"<tr>\n<td>numCommandErrors:</td>\n<td>" + inputs["numCommandErrors"] + "</td>\n</tr>\n" +
"<tr>\n<td>" + inputs["suite"] + "</td>\n<td>&nbsp;</td>\n</tr>");
var testNum = inputs["numTestTotal"];
for (var rowNum = 1; rowNum < testNum; rowNum++) {
scriptFile.WriteLine("<tr>\n<td>" + inputs["testTable." + rowNum] + "</td>\n<td>&nbsp;</td>\n</tr>");
}
scriptFile.WriteLine("</table></body></html>");
scriptFile.Close();
}
function printMetrics() {
setText(document.getElementById("commandPasses"), numCommandPasses);
setText(document.getElementById("commandFailures"), numCommandFailures);
setText(document.getElementById("commandErrors"), numCommandErrors);
setText(document.getElementById("testRuns"), numTestPasses + numTestFailures);
setText(document.getElementById("testFailures"), numTestFailures);
currentTime = new Date().getTime();
timeDiff = currentTime - startTime;
totalSecs = Math.floor(timeDiff / 1000);
minutes = Math.floor(totalSecs / 60);
seconds = totalSecs % 60;
setText(document.getElementById("elapsedTime"), pad(minutes)+":"+pad(seconds));
}
// Puts a leading 0 on num if it is less than 10
function pad (num) {
return (num > 9) ? num : "0" + num;
}
/*
* Register all of the built-in command handlers with the CommandHandlerFactory.
* TODO work out an easy way for people to register handlers without modifying the Selenium sources.
*/
function registerCommandHandlers() {
commandFactory = new CommandHandlerFactory();
commandFactory.registerAll(selenium);
}
function initialiseTestLoop() {
testLoop = new TestLoop(commandFactory);
testLoop.getCommandInterval = function() { return runInterval; };
testLoop.nextCommand = nextCommand;
testLoop.commandStarted = commandStarted;
testLoop.commandComplete = commandComplete;
testLoop.commandError = commandError;
testLoop.testComplete = testComplete;
testLoop.pause = function() {
document.getElementById('continueTest').disabled = false;
};
return testLoop;
}
function nextCommand() {
var row = currentTest.nextCommand();
if (row == null) {
return null;
}
row.cells[2].originalHTML = row.cells[2].innerHTML;
return new SeleniumCommand(getText(row.cells[0]),
getText(row.cells[1]),
getText(row.cells[2]));
}
function removeNbsp(value) {
return value.replace(/\240/g, "");
}
function scrollIntoView(element) {
if (element.scrollIntoView) {
element.scrollIntoView(false);
return;
}
// TODO: work out how to scroll browsers that don't support
// scrollIntoView (like Konqueror)
}
function commandStarted() {
currentTest.currentRow.bgColor = workingColor;
scrollIntoView(currentTest.currentRow.cells[0]);
printMetrics();
}
function commandComplete(result) {
if (result.failed) {
numCommandFailures += 1;
recordFailure(result.failureMessage);
} else if (result.passed) {
numCommandPasses += 1;
currentTest.currentRow.bgColor = passColor;
} else {
currentTest.currentRow.bgColor = doneColor;
}
}
function commandError(errorMessage) {
numCommandErrors += 1;
recordFailure(errorMessage);
}
function recordFailure(errorMsg) {
LOG.warn("recordFailure: " + errorMsg);
// Set cell background to red
currentTest.currentRow.bgColor = failColor;
// Set error message
currentTest.currentRow.cells[2].innerHTML = errorMsg;
currentTest.currentRow.title = errorMsg;
testFailed = true;
suiteFailed = true;
}
function testComplete() {
var resultColor = passColor;
if (testFailed) {
resultColor = failColor;
numTestFailures += 1;
} else {
numTestPasses += 1;
}
if (currentTest.headerRow) {
currentTest.headerRow.bgColor = resultColor;
}
printMetrics();
window.setTimeout("runNextTest()", 1);
}
Selenium.prototype.doPause = function(waitTime) {
testLoop.pauseInterval = waitTime;
};
Selenium.prototype.doBreak = function() {
document.getElementById('modeStep').checked = true;
runInterval = -1;
};
/*
* Click on the located element, and attach a callback to notify
* when the page is reloaded.
*/
Selenium.prototype.doModalDialogTest = function(returnValue) {
this.browserbot.doModalDialogTest(returnValue);
};
/*
* Store the value of a form input in a variable
*/
Selenium.prototype.doStoreValue = function(target, varName) {
if (!varName) {
// Backward compatibility mode: read the ENTIRE text of the page
// and stores it in a variable with the name of the target
value = this.page().bodyText();
storedVars[target] = value;
return;
}
var element = this.page().findElement(target);
storedVars[varName] = getInputValue(element);
};
/*
* Store the text of an element in a variable
*/
Selenium.prototype.doStoreText = function(target, varName) {
var element = this.page().findElement(target);
storedVars[varName] = getText(element);
};
/*
* Store the value of an element attribute in a variable
*/
Selenium.prototype.doStoreAttribute = function(target, varName) {
storedVars[varName] = this.page().findAttribute(target);
};
/*
* Store the result of a literal value
*/
Selenium.prototype.doStore = function(value, varName) {
storedVars[varName] = value;
};

View File

@ -1,5 +0,0 @@
Selenium.version = "0.7.0";
Selenium.revision = "1007M";
window.top.document.title += " v" + Selenium.version + " [" + Selenium.revision + "]";

View File

@ -1,114 +0,0 @@
// Waits for the condition to be "true"
Selenium.prototype.doWaitForCondition = function(script, timeout) {
if (isNaN(timeout)) {
throw new SeleniumError("Timeout is not a number: " + timeout);
}
testLoop.waitForCondition = function () {
try {
return eval(script);
} catch(er) {
alert("Error evaluation condition:" + er.message);
throw new SeleniumError("Error evaluation condition:" + er.message);
}
};
testLoop.waitForConditionStart = new Date().getTime();
testLoop.waitForConditionTimeout = timeout;
testLoop.window = this.browserbot.getCurrentWindow();
testLoop.firstTime = true;
testLoop.pollUntilConditionIsTrue = function () {
try {
if (!this.firstTime && this.waitForCondition()) {
this.waitForCondition = null;
this.waitForConditionStart = null;
this.waitForConditionTimeout = null;
this.continueCommandExecutionWithDelay();
} else {
if (this.waitForConditionTimeout != null) {
var now = new Date();
if ((now - this.waitForConditionStart) > this.waitForConditionTimeout) {
throw new SeleniumError("Timed out after " + this.waitForConditionTimeout + "ms");
}
}
window.setTimeout("testLoop.pollUntilConditionIsTrue()", testLoop.firstTime ? 1000 : 10);
testLoop.firstTime = false;
}
} catch (e) {
var lastResult = new CommandResult();
lastResult.failed = true;
lastResult.failureMessage = e.message;
this.commandComplete(lastResult);
this.testComplete();
}
};
};
Selenium.prototype.doAjaxWait = function(script, timeout) {
return this.doWaitForCondition('this.window._AJAX_LOADING == false', 10000);
}
Selenium.prototype.doSleep = function(time) {
if (isNaN(time)) {
throw new SeleniumError("Timeout is not a number: " + time);
}
window.setTimeout("testLoop.testComplete()", time);
};
Selenium.prototype.doAssertModalPresent = function() {
if(this.browserbot.recordedModals.length == 0)
Assert.fail("No modal was present");
this.browserbot.recordedModals.shift();
};
Selenium.prototype.doAssertNotModalPresent = function() {
return (this.browserbot.recordedModals.length > 0)
Assert.fail("A modal was present when it shouldn't be.");
};
Selenium.prototype.doAnswerOnNextModal = function(answer) {
this.browserbot.nextModalResult = answer;
};
BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) {
windowToModify.alert = function(alert) {
browserBot.recordedAlerts.push(alert);
};
windowToModify.confirm = function(message) {
browserBot.recordedConfirmations.push(message);
var result = browserBot.nextConfirmResult;
browserBot.nextConfirmResult = true;
return result;
};
windowToModify.prompt = function(message) {
browserBot.recordedPrompts.push(message);
var result = !browserBot.nextConfirmResult ? null : browserBot.nextPromptResult;
browserBot.nextConfirmResult = true;
browserBot.nextPromptResult = '';
return result;
};
if(!browserBot.recordedModals) browserBot.recordedModals = Array();
windowToModify.modalDialog = function(url, handlers) {
browserBot.recordedModals.push(url);
if(browserBot.nextModalResult) handlers[browserBot.nextModalResult]();
browserBot.nextModalResult = null;
};
// Keep a reference to all popup windows by name
// note that in IE the "windowName" argument must be a valid javascript identifier, it seems.
var originalOpen = windowToModify.open;
windowToModify.open = function(url, windowName, windowFeatures, replaceFlag) {
var openedWindow = originalOpen(url, windowName, windowFeatures, replaceFlag);
selenium.browserbot.openedWindows[windowName] = openedWindow;
return openedWindow;
};
};

View File

@ -1,75 +0,0 @@
/*
* By default, Selenium looks for a file called "user-extensions.js", and loads and javascript
* code found in that file. This file is a sample of what that file could look like.
*
* user-extensions.js provides a convenient location for adding extensions to Selenium, like
* new actions, checks and locator-strategies.
* By default, this file does not exist. Users can create this file and place their extension code
* in this common location, removing the need to modify the Selenium sources, and hopefully assisting
* with the upgrade process.
*
* You can find contributed extensions at http://wiki.openqa.org/display/SEL/Contributed%20User-Extensions
*/
// The following examples try to give an indication of how Selenium can be extended with javascript.
// All do* methods on the Selenium prototype are added as actions.
// Eg add a typeRepeated action to Selenium, which types the text twice into a text box.
// The typeTwiceAndWait command will be available automatically
Selenium.prototype.doTypeRepeated = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Create the text to type
var valueToType = text + text;
// Replace the element text with the new text
this.page().replaceText(element, valueToType);
};
// All assert* methods on the Selenium prototype are added as checks.
// Eg add a assertValueRepeated check, that makes sure that the element value
// consists of the supplied text repeated.
// The verify version will be available automatically.
Selenium.prototype.assertValueRepeated = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Create the text to verify
var expectedValue = text + text;
// Get the actual element value
var actualValue = element.value;
// Make sure the actual value matches the expected
Assert.matches(expectedValue, actualValue);
};
// All get* methods on the Selenium prototype result in
// store, assert, assertNot, verify, verifyNot, waitFor, and waitForNot commands.
// E.g. add a getTextLength method that returns the length of the text
// of a specified element.
// Will result in support for storeTextLength, assertTextLength, etc.
Selenium.prototype.getTextLength = function(locator) {
return this.getText(locator).length;
};
// All locateElementBy* methods are added as locator-strategies.
// Eg add a "valuerepeated=" locator, that finds the first element with the supplied value, repeated.
// The "inDocument" is a the document you are searching.
PageBot.prototype.locateElementByValueRepeated = function(text, inDocument) {
// Create the text to search for
var expectedValue = text + text;
// Loop through all elements, looking for ones that have a value === our expected value
var allElements = inDocument.getElementsByTagName("*");
for (var i = 0; i < allElements.length; i++) {
var testElement = allElements[i];
if (testElement.value && testElement.value === expectedValue) {
return testElement;
}
}
return null;
};

View File

@ -1,153 +0,0 @@
// This is a third party JavaScript library from
// http://webfx.eae.net/dhtml/xmlextras/xmlextras.html
// i.e. This has not been written by ThoughtWorks.
//<script>
//////////////////
// Helper Stuff //
//////////////////
// used to find the Automation server name
function getDomDocumentPrefix() {
if (getDomDocumentPrefix.prefix)
return getDomDocumentPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".DomDocument");
return getDomDocumentPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
function getXmlHttpPrefix() {
if (getXmlHttpPrefix.prefix)
return getXmlHttpPrefix.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".XmlHttp");
return getXmlHttpPrefix.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
}
//////////////////////////
// Start the Real stuff //
//////////////////////////
// XmlHttp factory
function XmlHttp() {}
XmlHttp.create = function () {
try {
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (req.readyState == null) {
req.readyState = 1;
req.addEventListener("load", function () {
req.readyState = 4;
if (typeof req.onreadystatechange == "function")
req.onreadystatechange();
}, false);
}
return req;
}
if (window.ActiveXObject) {
return new ActiveXObject(getXmlHttpPrefix() + ".XmlHttp");
}
}
catch (ex) {}
// fell through
throw new Error("Your browser does not support XmlHttp objects");
};
// XmlDocument factory
function XmlDocument() {}
XmlDocument.create = function () {
try {
// DOM2
if (document.implementation && document.implementation.createDocument) {
var doc = document.implementation.createDocument("", "", null);
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (doc.readyState == null) {
doc.readyState = 1;
doc.addEventListener("load", function () {
doc.readyState = 4;
if (typeof doc.onreadystatechange == "function")
doc.onreadystatechange();
}, false);
}
return doc;
}
if (window.ActiveXObject)
return new ActiveXObject(getDomDocumentPrefix() + ".DomDocument");
}
catch (ex) {}
throw new Error("Your browser does not support XmlDocument objects");
};
// Create the loadXML method and xml getter for Mozilla
if (window.DOMParser &&
window.XMLSerializer &&
window.Node && Node.prototype && Node.prototype.__defineGetter__) {
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
//XMLDocument.prototype.loadXML =
Document.prototype.loadXML = function (s) {
// parse the string to a new doc
var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
// remove all initial children
while (this.hasChildNodes())
this.removeChild(this.lastChild);
// insert and import nodes
for (var i = 0; i < doc2.childNodes.length; i++) {
this.appendChild(this.importNode(doc2.childNodes[i], true));
}
};
/*
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
*
*/
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
/*
XMLDocument.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
*/
Document.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
}

View File

@ -1,4169 +0,0 @@
/*
* xpath.js
*
* An XPath 1.0 library for JavaScript.
*
* Cameron McCormack <cam (at) mcc.id.au>
*
* This work is licensed under the Creative Commons Attribution-ShareAlike
* License. To view a copy of this license, visit
*
* http://creativecommons.org/licenses/by-sa/2.0/
*
* or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford,
* California 94305, USA.
*
* Revision 18: October 27, 2005
* DOM 3 XPath support. Caveats:
* - namespace prefixes aren't resolved in XPathEvaluator.createExpression,
* but in XPathExpression.evaluate.
* - XPathResult.invalidIteratorState is not implemented.
*
* Revision 17: October 25, 2005
* Some core XPath function fixes and a patch to avoid crashing certain
* versions of MSXML in PathExpr.prototype.getOwnerElement, thanks to
* Sébastien Cramatte <contact (at) zeninteractif.com>.
*
* Revision 16: September 22, 2005
* Workarounds for some IE 5.5 deficiencies.
* Fixed problem with prefix node tests on attribute nodes.
*
* Revision 15: May 21, 2005
* Fixed problem with QName node tests on elements with an xmlns="...".
*
* Revision 14: May 19, 2005
* Fixed QName node tests on attribute node regression.
*
* Revision 13: May 3, 2005
* Node tests are case insensitive now if working in an HTML DOM.
*
* Revision 12: April 26, 2005
* Updated licence. Slight code changes to enable use of Dean
* Edwards' script compression, http://dean.edwards.name/packer/ .
*
* Revision 11: April 23, 2005
* Fixed bug with 'and' and 'or' operators, fix thanks to
* Sandy McArthur <sandy (at) mcarthur.org>.
*
* Revision 10: April 15, 2005
* Added support for a virtual root node, supposedly helpful for
* implementing XForms. Fixed problem with QName node tests and
* the parent axis.
*
* Revision 9: March 17, 2005
* Namespace resolver tweaked so using the document node as the context
* for namespace lookups is equivalent to using the document element.
*
* Revision 8: February 13, 2005
* Handle implicit declaration of 'xmlns' namespace prefix.
* Fixed bug when comparing nodesets.
* Instance data can now be associated with a FunctionResolver, and
* workaround for MSXML not supporting 'localName' and 'getElementById',
* thanks to Grant Gongaware.
* Fix a few problems when the context node is the root node.
*
* Revision 7: February 11, 2005
* Default namespace resolver fix from Grant Gongaware
* <grant (at) gongaware.com>.
*
* Revision 6: February 10, 2005
* Fixed bug in 'number' function.
*
* Revision 5: February 9, 2005
* Fixed bug where text nodes not getting converted to string values.
*
* Revision 4: January 21, 2005
* Bug in 'name' function, fix thanks to Bill Edney.
* Fixed incorrect processing of namespace nodes.
* Fixed NamespaceResolver to resolve 'xml' namespace.
* Implemented union '|' operator.
*
* Revision 3: January 14, 2005
* Fixed bug with nodeset comparisons, bug lexing < and >.
*
* Revision 2: October 26, 2004
* QName node test namespace handling fixed. Few other bug fixes.
*
* Revision 1: August 13, 2004
* Bug fixes from William J. Edney <bedney (at) technicalpursuit.com>.
* Added minimal licence.
*
* Initial version: June 14, 2004
*/
// XPathParser ///////////////////////////////////////////////////////////////
XPathParser.prototype = new Object();
XPathParser.prototype.constructor = XPathParser;
XPathParser.superclass = Object.prototype;
function XPathParser() {
this.init();
}
XPathParser.prototype.init = function() {
this.reduceActions = [];
this.reduceActions[3] = function(rhs) {
return new OrOperation(rhs[0], rhs[2]);
};
this.reduceActions[5] = function(rhs) {
return new AndOperation(rhs[0], rhs[2]);
};
this.reduceActions[7] = function(rhs) {
return new EqualsOperation(rhs[0], rhs[2]);
};
this.reduceActions[8] = function(rhs) {
return new NotEqualOperation(rhs[0], rhs[2]);
};
this.reduceActions[10] = function(rhs) {
return new LessThanOperation(rhs[0], rhs[2]);
};
this.reduceActions[11] = function(rhs) {
return new GreaterThanOperation(rhs[0], rhs[2]);
};
this.reduceActions[12] = function(rhs) {
return new LessThanOrEqualOperation(rhs[0], rhs[2]);
};
this.reduceActions[13] = function(rhs) {
return new GreaterThanOrEqualOperation(rhs[0], rhs[2]);
};
this.reduceActions[15] = function(rhs) {
return new PlusOperation(rhs[0], rhs[2]);
};
this.reduceActions[16] = function(rhs) {
return new MinusOperation(rhs[0], rhs[2]);
};
this.reduceActions[18] = function(rhs) {
return new MultiplyOperation(rhs[0], rhs[2]);
};
this.reduceActions[19] = function(rhs) {
return new DivOperation(rhs[0], rhs[2]);
};
this.reduceActions[20] = function(rhs) {
return new ModOperation(rhs[0], rhs[2]);
};
this.reduceActions[22] = function(rhs) {
return new UnaryMinusOperation(rhs[1]);
};
this.reduceActions[24] = function(rhs) {
return new BarOperation(rhs[0], rhs[2]);
};
this.reduceActions[25] = function(rhs) {
return new PathExpr(undefined, undefined, rhs[0]);
};
this.reduceActions[27] = function(rhs) {
rhs[0].locationPath = rhs[2];
return rhs[0];
};
this.reduceActions[28] = function(rhs) {
rhs[0].locationPath = rhs[2];
rhs[0].locationPath.steps.unshift(new Step(Step.DESCENDANTORSELF, new NodeTest(NodeTest.NODE, undefined), []));
return rhs[0];
};
this.reduceActions[29] = function(rhs) {
return new PathExpr(rhs[0], [], undefined);
};
this.reduceActions[30] = function(rhs) {
if (Utilities.instance_of(rhs[0], PathExpr)) {
if (rhs[0].filterPredicates == undefined) {
rhs[0].filterPredicates = [];
}
rhs[0].filterPredicates.push(rhs[1]);
return rhs[0];
} else {
return new PathExpr(rhs[0], [rhs[1]], undefined);
}
};
this.reduceActions[32] = function(rhs) {
return rhs[1];
};
this.reduceActions[33] = function(rhs) {
return new XString(rhs[0]);
};
this.reduceActions[34] = function(rhs) {
return new XNumber(rhs[0]);
};
this.reduceActions[36] = function(rhs) {
return new FunctionCall(rhs[0], []);
};
this.reduceActions[37] = function(rhs) {
return new FunctionCall(rhs[0], rhs[2]);
};
this.reduceActions[38] = function(rhs) {
return [ rhs[0] ];
};
this.reduceActions[39] = function(rhs) {
rhs[2].unshift(rhs[0]);
return rhs[2];
};
this.reduceActions[43] = function(rhs) {
return new LocationPath(true, []);
};
this.reduceActions[44] = function(rhs) {
rhs[1].absolute = true;
return rhs[1];
};
this.reduceActions[46] = function(rhs) {
return new LocationPath(false, [ rhs[0] ]);
};
this.reduceActions[47] = function(rhs) {
rhs[0].steps.push(rhs[2]);
return rhs[0];
};
this.reduceActions[49] = function(rhs) {
return new Step(rhs[0], rhs[1], []);
};
this.reduceActions[50] = function(rhs) {
return new Step(Step.CHILD, rhs[0], []);
};
this.reduceActions[51] = function(rhs) {
return new Step(rhs[0], rhs[1], rhs[2]);
};
this.reduceActions[52] = function(rhs) {
return new Step(Step.CHILD, rhs[0], rhs[1]);
};
this.reduceActions[54] = function(rhs) {
return [ rhs[0] ];
};
this.reduceActions[55] = function(rhs) {
rhs[1].unshift(rhs[0]);
return rhs[1];
};
this.reduceActions[56] = function(rhs) {
if (rhs[0] == "ancestor") {
return Step.ANCESTOR;
} else if (rhs[0] == "ancestor-or-self") {
return Step.ANCESTORORSELF;
} else if (rhs[0] == "attribute") {
return Step.ATTRIBUTE;
} else if (rhs[0] == "child") {
return Step.CHILD;
} else if (rhs[0] == "descendant") {
return Step.DESCENDANT;
} else if (rhs[0] == "descendant-or-self") {
return Step.DESCENDANTORSELF;
} else if (rhs[0] == "following") {
return Step.FOLLOWING;
} else if (rhs[0] == "following-sibling") {
return Step.FOLLOWINGSIBLING;
} else if (rhs[0] == "namespace") {
return Step.NAMESPACE;
} else if (rhs[0] == "parent") {
return Step.PARENT;
} else if (rhs[0] == "preceding") {
return Step.PRECEDING;
} else if (rhs[0] == "preceding-sibling") {
return Step.PRECEDINGSIBLING;
} else if (rhs[0] == "self") {
return Step.SELF;
}
return -1;
};
this.reduceActions[57] = function(rhs) {
return Step.ATTRIBUTE;
};
this.reduceActions[59] = function(rhs) {
if (rhs[0] == "comment") {
return new NodeTest(NodeTest.COMMENT, undefined);
} else if (rhs[0] == "text") {
return new NodeTest(NodeTest.TEXT, undefined);
} else if (rhs[0] == "processing-instruction") {
return new NodeTest(NodeTest.PI, undefined);
} else if (rhs[0] == "node") {
return new NodeTest(NodeTest.NODE, undefined);
}
return new NodeTest(-1, undefined);
};
this.reduceActions[60] = function(rhs) {
return new NodeTest(NodeTest.PI, rhs[2]);
};
this.reduceActions[61] = function(rhs) {
return rhs[1];
};
this.reduceActions[63] = function(rhs) {
rhs[1].absolute = true;
rhs[1].steps.unshift(new Step(Step.DESCENDANTORSELF, new NodeTest(NodeTest.NODE, undefined), []));
return rhs[1];
};
this.reduceActions[64] = function(rhs) {
rhs[0].steps.push(new Step(Step.DESCENDANTORSELF, new NodeTest(NodeTest.NODE, undefined), []));
rhs[0].steps.push(rhs[2]);
return rhs[0];
};
this.reduceActions[65] = function(rhs) {
return new Step(Step.SELF, new NodeTest(NodeTest.NODE, undefined), []);
};
this.reduceActions[66] = function(rhs) {
return new Step(Step.PARENT, new NodeTest(NodeTest.NODE, undefined), []);
};
this.reduceActions[67] = function(rhs) {
return new VariableReference(rhs[1]);
};
this.reduceActions[68] = function(rhs) {
return new NodeTest(NodeTest.NAMETESTANY, undefined);
};
this.reduceActions[69] = function(rhs) {
var prefix = rhs[0].substring(0, rhs[0].indexOf(":"));
return new NodeTest(NodeTest.NAMETESTPREFIXANY, prefix);
};
this.reduceActions[70] = function(rhs) {
return new NodeTest(NodeTest.NAMETESTQNAME, rhs[0]);
};
};
XPathParser.actionTable = [
" s s sssssssss s ss s ss",
" s ",
"r rrrrrrrrr rrrrrrr rr r ",
" rrrrr ",
" s s sssssssss s ss s ss",
"rs rrrrrrrr s sssssrrrrrr rrs rs ",
" s s sssssssss s ss s ss",
" s ",
" s ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
" s ",
" s ",
" s s sssss s s ",
"r rrrrrrrrr rrrrrrr rr r ",
"a ",
"r s rr r ",
"r sr rr r ",
"r s rr s rr r ",
"r rssrr rss rr r ",
"r rrrrr rrrss rr r ",
"r rrrrrsss rrrrr rr r ",
"r rrrrrrrr rrrrr rr r ",
"r rrrrrrrr rrrrrs rr r ",
"r rrrrrrrr rrrrrr rr r ",
"r rrrrrrrr rrrrrr rr r ",
"r srrrrrrrr rrrrrrs rr sr ",
"r srrrrrrrr rrrrrrs rr r ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrr rrrrrr rr r ",
"r rrrrrrrr rrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
" sssss ",
"r rrrrrrrrr rrrrrrr rr sr ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
" s ",
"r srrrrrrrr rrrrrrs rr r ",
"r rrrrrrrr rrrrr rr r ",
" s ",
" s ",
" rrrrr ",
" s s sssssssss s sss s ss",
"r srrrrrrrr rrrrrrs rr r ",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss s ss s ss",
" s s sssssssss ss s ss",
" s s sssssssss s ss s ss",
" s s sssss s s ",
" s s sssss s s ",
"r rrrrrrrrr rrrrrrr rr rr ",
" s s sssss s s ",
" s s sssss s s ",
"r rrrrrrrrr rrrrrrr rr sr ",
"r rrrrrrrrr rrrrrrr rr sr ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr rr ",
" s ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
" rr ",
" s ",
" rs ",
"r sr rr r ",
"r s rr s rr r ",
"r rssrr rss rr r ",
"r rssrr rss rr r ",
"r rrrrr rrrss rr r ",
"r rrrrr rrrss rr r ",
"r rrrrr rrrss rr r ",
"r rrrrr rrrss rr r ",
"r rrrrrsss rrrrr rr r ",
"r rrrrrsss rrrrr rr r ",
"r rrrrrrrr rrrrr rr r ",
"r rrrrrrrr rrrrr rr r ",
"r rrrrrrrr rrrrr rr r ",
"r rrrrrrrr rrrrrr rr r ",
" r ",
" s ",
"r srrrrrrrr rrrrrrs rr r ",
"r srrrrrrrr rrrrrrs rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr r ",
"r rrrrrrrrr rrrrrrr rr rr ",
"r rrrrrrrrr rrrrrrr rr rr ",
" s s sssssssss s ss s ss",
"r rrrrrrrrr rrrrrrr rr rr ",
" r "
];
XPathParser.actionTableNumber = [
" 1 0 /.-,+*)(' & %$ # \"!",
" J ",
"a aaaaaaaaa aaaaaaa aa a ",
" YYYYY ",
" 1 0 /.-,+*)(' & %$ # \"!",
"K1 KKKKKKKK . +*)('KKKKKK KK# K\" ",
" 1 0 /.-,+*)(' & %$ # \"!",
" N ",
" O ",
"e eeeeeeeee eeeeeee ee ee ",
"f fffffffff fffffff ff ff ",
"d ddddddddd ddddddd dd dd ",
"B BBBBBBBBB BBBBBBB BB BB ",
"A AAAAAAAAA AAAAAAA AA AA ",
" P ",
" Q ",
" 1 . +*)(' # \" ",
"b bbbbbbbbb bbbbbbb bb b ",
" ",
"! S !! ! ",
"\" T\" \"\" \" ",
"$ V $$ U $$ $ ",
"& &ZY&& &XW && & ",
") ))))) )))\\[ )) ) ",
". ....._^] ..... .. . ",
"1 11111111 11111 11 1 ",
"5 55555555 55555` 55 5 ",
"7 77777777 777777 77 7 ",
"9 99999999 999999 99 9 ",
": c:::::::: ::::::b :: a: ",
"I fIIIIIIII IIIIIIe II I ",
"= ========= ======= == == ",
"? ????????? ??????? ?? ?? ",
"C CCCCCCCCC CCCCCCC CC CC ",
"J JJJJJJJJ JJJJJJ JJ J ",
"M MMMMMMMM MMMMMM MM M ",
"N NNNNNNNNN NNNNNNN NN N ",
"P PPPPPPPPP PPPPPPP PP P ",
" +*)(' ",
"R RRRRRRRRR RRRRRRR RR aR ",
"U UUUUUUUUU UUUUUUU UU U ",
"Z ZZZZZZZZZ ZZZZZZZ ZZ ZZ ",
"c ccccccccc ccccccc cc cc ",
" j ",
"L fLLLLLLLL LLLLLLe LL L ",
"6 66666666 66666 66 6 ",
" k ",
" l ",
" XXXXX ",
" 1 0 /.-,+*)(' & %$m # \"!",
"_ f________ ______e __ _ ",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 0 /.-,+*)(' %$ # \"!",
" 1 0 /.-,+*)(' & %$ # \"!",
" 1 . +*)(' # \" ",
" 1 . +*)(' # \" ",
"> >>>>>>>>> >>>>>>> >> >> ",
" 1 . +*)(' # \" ",
" 1 . +*)(' # \" ",
"Q QQQQQQQQQ QQQQQQQ QQ aQ ",
"V VVVVVVVVV VVVVVVV VV aV ",
"T TTTTTTTTT TTTTTTT TT T ",
"@ @@@@@@@@@ @@@@@@@ @@ @@ ",
" \x87 ",
"[ [[[[[[[[[ [[[[[[[ [[ [[ ",
"D DDDDDDDDD DDDDDDD DD DD ",
" HH ",
" \x88 ",
" F\x89 ",
"# T# ## # ",
"% V %% U %% % ",
"' 'ZY'' 'XW '' ' ",
"( (ZY(( (XW (( ( ",
"+ +++++ +++\\[ ++ + ",
"* ***** ***\\[ ** * ",
"- ----- ---\\[ -- - ",
", ,,,,, ,,,\\[ ,, , ",
"0 00000_^] 00000 00 0 ",
"/ /////_^] ///// // / ",
"2 22222222 22222 22 2 ",
"3 33333333 33333 33 3 ",
"4 44444444 44444 44 4 ",
"8 88888888 888888 88 8 ",
" ^ ",
" \x8a ",
"; f;;;;;;;; ;;;;;;e ;; ; ",
"< f<<<<<<<< <<<<<<e << < ",
"O OOOOOOOOO OOOOOOO OO O ",
"` ````````` ``````` `` ` ",
"S SSSSSSSSS SSSSSSS SS S ",
"W WWWWWWWWW WWWWWWW WW W ",
"\\ \\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\ \\\\ \\\\ ",
"E EEEEEEEEE EEEEEEE EE EE ",
" 1 0 /.-,+*)(' & %$ # \"!",
"] ]]]]]]]]] ]]]]]]] ]] ]] ",
" G "
];
XPathParser.gotoTable = [
"3456789:;<=>?@ AB CDEFGH IJ ",
" ",
" ",
" ",
"L456789:;<=>?@ AB CDEFGH IJ ",
" M EFGH IJ ",
" N;<=>?@ AB CDEFGH IJ ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" S EFGH IJ ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" e ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" h J ",
" i j ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
"o456789:;<=>?@ ABpqCDEFGH IJ ",
" ",
" r6789:;<=>?@ AB CDEFGH IJ ",
" s789:;<=>?@ AB CDEFGH IJ ",
" t89:;<=>?@ AB CDEFGH IJ ",
" u89:;<=>?@ AB CDEFGH IJ ",
" v9:;<=>?@ AB CDEFGH IJ ",
" w9:;<=>?@ AB CDEFGH IJ ",
" x9:;<=>?@ AB CDEFGH IJ ",
" y9:;<=>?@ AB CDEFGH IJ ",
" z:;<=>?@ AB CDEFGH IJ ",
" {:;<=>?@ AB CDEFGH IJ ",
" |;<=>?@ AB CDEFGH IJ ",
" };<=>?@ AB CDEFGH IJ ",
" ~;<=>?@ AB CDEFGH IJ ",
" \x7f=>?@ AB CDEFGH IJ ",
"\x80456789:;<=>?@ AB CDEFGH IJ\x81",
" \x82 EFGH IJ ",
" \x83 EFGH IJ ",
" ",
" \x84 GH IJ ",
" \x85 GH IJ ",
" i \x86 ",
" i \x87 ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
"o456789:;<=>?@ AB\x8cqCDEFGH IJ ",
" ",
" "
];
XPathParser.productions = [
[1, 1, 2],
[2, 1, 3],
[3, 1, 4],
[3, 3, 3, -9, 4],
[4, 1, 5],
[4, 3, 4, -8, 5],
[5, 1, 6],
[5, 3, 5, -22, 6],
[5, 3, 5, -5, 6],
[6, 1, 7],
[6, 3, 6, -23, 7],
[6, 3, 6, -24, 7],
[6, 3, 6, -6, 7],
[6, 3, 6, -7, 7],
[7, 1, 8],
[7, 3, 7, -25, 8],
[7, 3, 7, -26, 8],
[8, 1, 9],
[8, 3, 8, -12, 9],
[8, 3, 8, -11, 9],
[8, 3, 8, -10, 9],
[9, 1, 10],
[9, 2, -26, 9],
[10, 1, 11],
[10, 3, 10, -27, 11],
[11, 1, 12],
[11, 1, 13],
[11, 3, 13, -28, 14],
[11, 3, 13, -4, 14],
[13, 1, 15],
[13, 2, 13, 16],
[15, 1, 17],
[15, 3, -29, 2, -30],
[15, 1, -15],
[15, 1, -16],
[15, 1, 18],
[18, 3, -13, -29, -30],
[18, 4, -13, -29, 19, -30],
[19, 1, 20],
[19, 3, 20, -31, 19],
[20, 1, 2],
[12, 1, 14],
[12, 1, 21],
[21, 1, -28],
[21, 2, -28, 14],
[21, 1, 22],
[14, 1, 23],
[14, 3, 14, -28, 23],
[14, 1, 24],
[23, 2, 25, 26],
[23, 1, 26],
[23, 3, 25, 26, 27],
[23, 2, 26, 27],
[23, 1, 28],
[27, 1, 16],
[27, 2, 16, 27],
[25, 2, -14, -3],
[25, 1, -32],
[26, 1, 29],
[26, 3, -20, -29, -30],
[26, 4, -21, -29, -15, -30],
[16, 3, -33, 30, -34],
[30, 1, 2],
[22, 2, -4, 14],
[24, 3, 14, -4, 23],
[28, 1, -35],
[28, 1, -2],
[17, 2, -36, -18],
[29, 1, -17],
[29, 1, -19],
[29, 1, -18]
];
XPathParser.DOUBLEDOT = 2;
XPathParser.DOUBLECOLON = 3;
XPathParser.DOUBLESLASH = 4;
XPathParser.NOTEQUAL = 5;
XPathParser.LESSTHANOREQUAL = 6;
XPathParser.GREATERTHANOREQUAL = 7;
XPathParser.AND = 8;
XPathParser.OR = 9;
XPathParser.MOD = 10;
XPathParser.DIV = 11;
XPathParser.MULTIPLYOPERATOR = 12;
XPathParser.FUNCTIONNAME = 13;
XPathParser.AXISNAME = 14;
XPathParser.LITERAL = 15;
XPathParser.NUMBER = 16;
XPathParser.ASTERISKNAMETEST = 17;
XPathParser.QNAME = 18;
XPathParser.NCNAMECOLONASTERISK = 19;
XPathParser.NODETYPE = 20;
XPathParser.PROCESSINGINSTRUCTIONWITHLITERAL = 21;
XPathParser.EQUALS = 22;
XPathParser.LESSTHAN = 23;
XPathParser.GREATERTHAN = 24;
XPathParser.PLUS = 25;
XPathParser.MINUS = 26;
XPathParser.BAR = 27;
XPathParser.SLASH = 28;
XPathParser.LEFTPARENTHESIS = 29;
XPathParser.RIGHTPARENTHESIS = 30;
XPathParser.COMMA = 31;
XPathParser.AT = 32;
XPathParser.LEFTBRACKET = 33;
XPathParser.RIGHTBRACKET = 34;
XPathParser.DOT = 35;
XPathParser.DOLLAR = 36;
XPathParser.prototype.tokenize = function(s1) {
var types = [];
var values = [];
var s = s1 + '\0';
var pos = 0;
var c = s.charAt(pos++);
while (1) {
while (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
c = s.charAt(pos++);
}
if (c == '\0' || pos >= s.length) {
break;
}
if (c == '(') {
types.push(XPathParser.LEFTPARENTHESIS);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == ')') {
types.push(XPathParser.RIGHTPARENTHESIS);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '[') {
types.push(XPathParser.LEFTBRACKET);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == ']') {
types.push(XPathParser.RIGHTBRACKET);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '@') {
types.push(XPathParser.AT);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == ',') {
types.push(XPathParser.COMMA);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '|') {
types.push(XPathParser.BAR);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '+') {
types.push(XPathParser.PLUS);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '-') {
types.push(XPathParser.MINUS);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '=') {
types.push(XPathParser.EQUALS);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '$') {
types.push(XPathParser.DOLLAR);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == '.') {
c = s.charAt(pos++);
if (c == '.') {
types.push(XPathParser.DOUBLEDOT);
values.push("..");
c = s.charAt(pos++);
continue;
}
if (c >= '0' && c <= '9') {
var number = "." + c;
c = s.charAt(pos++);
while (c >= '0' && c <= '9') {
number += c;
c = s.charAt(pos++);
}
types.push(XPathParser.NUMBER);
values.push(number);
continue;
}
types.push(XPathParser.DOT);
values.push('.');
continue;
}
if (c == '\'' || c == '"') {
var delimiter = c;
var literal = "";
while ((c = s.charAt(pos++)) != delimiter) {
literal += c;
}
types.push(XPathParser.LITERAL);
values.push(literal);
c = s.charAt(pos++);
continue;
}
if (c >= '0' && c <= '9') {
var number = c;
c = s.charAt(pos++);
while (c >= '0' && c <= '9') {
number += c;
c = s.charAt(pos++);
}
if (c == '.') {
if (s.charAt(pos) >= '0' && s.charAt(pos) <= '9') {
number += c;
number += s.charAt(pos++);
c = s.charAt(pos++);
while (c >= '0' && c <= '9') {
number += c;
c = s.charAt(pos++);
}
}
}
types.push(XPathParser.NUMBER);
values.push(number);
continue;
}
if (c == '*') {
if (types.length > 0) {
var last = types[types.length - 1];
if (last != XPathParser.AT
&& last != XPathParser.DOUBLECOLON
&& last != XPathParser.LEFTPARENTHESIS
&& last != XPathParser.LEFTBRACKET
&& last != XPathParser.AND
&& last != XPathParser.OR
&& last != XPathParser.MOD
&& last != XPathParser.DIV
&& last != XPathParser.MULTIPLYOPERATOR
&& last != XPathParser.SLASH
&& last != XPathParser.DOUBLESLASH
&& last != XPathParser.BAR
&& last != XPathParser.PLUS
&& last != XPathParser.MINUS
&& last != XPathParser.EQUALS
&& last != XPathParser.NOTEQUAL
&& last != XPathParser.LESSTHAN
&& last != XPathParser.LESSTHANOREQUAL
&& last != XPathParser.GREATERTHAN
&& last != XPathParser.GREATERTHANOREQUAL) {
types.push(XPathParser.MULTIPLYOPERATOR);
values.push(c);
c = s.charAt(pos++);
continue;
}
}
types.push(XPathParser.ASTERISKNAMETEST);
values.push(c);
c = s.charAt(pos++);
continue;
}
if (c == ':') {
if (s.charAt(pos) == ':') {
types.push(XPathParser.DOUBLECOLON);
values.push("::");
pos++;
c = s.charAt(pos++);
continue;
}
}
if (c == '/') {
c = s.charAt(pos++);
if (c == '/') {
types.push(XPathParser.DOUBLESLASH);
values.push("//");
c = s.charAt(pos++);
continue;
}
types.push(XPathParser.SLASH);
values.push('/');
continue;
}
if (c == '!') {
if (s.charAt(pos) == '=') {
types.push(XPathParser.NOTEQUAL);
values.push("!=");
pos++;
c = s.charAt(pos++);
continue;
}
}
if (c == '<') {
if (s.charAt(pos) == '=') {
types.push(XPathParser.LESSTHANOREQUAL);
values.push("<=");
pos++;
c = s.charAt(pos++);
continue;
}
types.push(XPathParser.LESSTHAN);
values.push('<');
c = s.charAt(pos++);
continue;
}
if (c == '>') {
if (s.charAt(pos) == '=') {
types.push(XPathParser.GREATERTHANOREQUAL);
values.push(">=");
pos++;
c = s.charAt(pos++);
continue;
}
types.push(XPathParser.GREATERTHAN);
values.push('>');
c = s.charAt(pos++);
continue;
}
if (c == '_' || Utilities.isLetter(c.charCodeAt(0))) {
var name = c;
c = s.charAt(pos++);
while (Utilities.isNCNameChar(c.charCodeAt(0))) {
name += c;
c = s.charAt(pos++);
}
if (types.length > 0) {
var last = types[types.length - 1];
if (last != XPathParser.AT
&& last != XPathParser.DOUBLECOLON
&& last != XPathParser.LEFTPARENTHESIS
&& last != XPathParser.LEFTBRACKET
&& last != XPathParser.AND
&& last != XPathParser.OR
&& last != XPathParser.MOD
&& last != XPathParser.DIV
&& last != XPathParser.MULTIPLYOPERATOR
&& last != XPathParser.SLASH
&& last != XPathParser.DOUBLESLASH
&& last != XPathParser.BAR
&& last != XPathParser.PLUS
&& last != XPathParser.MINUS
&& last != XPathParser.EQUALS
&& last != XPathParser.NOTEQUAL
&& last != XPathParser.LESSTHAN
&& last != XPathParser.LESSTHANOREQUAL
&& last != XPathParser.GREATERTHAN
&& last != XPathParser.GREATERTHANOREQUAL) {
if (name == "and") {
types.push(XPathParser.AND);
values.push(name);
continue;
}
if (name == "or") {
types.push(XPathParser.OR);
values.push(name);
continue;
}
if (name == "mod") {
types.push(XPathParser.MOD);
values.push(name);
continue;
}
if (name == "div") {
types.push(XPathParser.DIV);
values.push(name);
continue;
}
}
}
if (c == ':') {
if (s.charAt(pos) == '*') {
types.push(XPathParser.NCNAMECOLONASTERISK);
values.push(name + ":*");
pos++;
c = s.charAt(pos++);
continue;
}
if (s.charAt(pos) == '_' || Utilities.isLetter(s.charCodeAt(pos))) {
name += ':';
c = s.charAt(pos++);
while (Utilities.isNCNameChar(c.charCodeAt(0))) {
name += c;
c = s.charAt(pos++);
}
if (c == '(') {
types.push(XPathParser.FUNCTIONNAME);
values.push(name);
continue;
}
types.push(XPathParser.QNAME);
values.push(name);
continue;
}
if (s.charAt(pos) == ':') {
types.push(XPathParser.AXISNAME);
values.push(name);
continue;
}
}
if (c == '(') {
if (name == "comment" || name == "text" || name == "node") {
types.push(XPathParser.NODETYPE);
values.push(name);
continue;
}
if (name == "processing-instruction") {
if (s.charAt(pos) == ')') {
types.push(XPathParser.NODETYPE);
} else {
types.push(XPathParser.PROCESSINGINSTRUCTIONWITHLITERAL);
}
values.push(name);
continue;
}
types.push(XPathParser.FUNCTIONNAME);
values.push(name);
continue;
}
types.push(XPathParser.QNAME);
values.push(name);
continue;
}
throw new Error("Unexpected character " + c);
}
types.push(1);
values.push("[EOF]");
return [types, values];
};
XPathParser.SHIFT = 's';
XPathParser.REDUCE = 'r';
XPathParser.ACCEPT = 'a';
XPathParser.prototype.parse = function(s) {
var types;
var values;
var res = this.tokenize(s);
if (res == undefined) {
return undefined;
}
types = res[0];
values = res[1];
var tokenPos = 0;
var state = [];
var tokenType = [];
var tokenValue = [];
var s;
var a;
var t;
state.push(0);
tokenType.push(1);
tokenValue.push("_S");
a = types[tokenPos];
t = values[tokenPos++];
while (1) {
s = state[state.length - 1];
switch (XPathParser.actionTable[s].charAt(a - 1)) {
case XPathParser.SHIFT:
tokenType.push(-a);
tokenValue.push(t);
state.push(XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32);
a = types[tokenPos];
t = values[tokenPos++];
break;
case XPathParser.REDUCE:
var num = XPathParser.productions[XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32][1];
var rhs = [];
for (var i = 0; i < num; i++) {
tokenType.pop();
rhs.unshift(tokenValue.pop());
state.pop();
}
var s_ = state[state.length - 1];
tokenType.push(XPathParser.productions[XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32][0]);
if (this.reduceActions[XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32] == undefined) {
tokenValue.push(rhs[0]);
} else {
tokenValue.push(this.reduceActions[XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32](rhs));
}
state.push(XPathParser.gotoTable[s_].charCodeAt(XPathParser.productions[XPathParser.actionTableNumber[s].charCodeAt(a - 1) - 32][0] - 2) - 33);
break;
case XPathParser.ACCEPT:
return new XPath(tokenValue.pop());
default:
throw new Error("XPath parse error");
}
}
};
// XPath /////////////////////////////////////////////////////////////////////
XPath.prototype = new Object();
XPath.prototype.constructor = XPath;
XPath.superclass = Object.prototype;
function XPath(e) {
this.expression = e;
}
XPath.prototype.toString = function() {
return this.expression.toString();
};
XPath.prototype.evaluate = function(c) {
c.contextNode = c.expressionContextNode;
c.contextSize = 1;
c.contextPosition = 1;
c.caseInsensitive = false;
if (c.contextNode != null) {
var doc = c.contextNode;
if (doc.nodeType != 9 /*Node.DOCUMENT_NODE*/) {
doc = doc.ownerDocument;
}
try {
c.caseInsensitive = doc.implementation.hasFeature("HTML", "2.0");
} catch (e) {
c.caseInsensitive = true;
}
}
return this.expression.evaluate(c);
};
XPath.XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
XPath.XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
// Expression ////////////////////////////////////////////////////////////////
Expression.prototype = new Object();
Expression.prototype.constructor = Expression;
Expression.superclass = Object.prototype;
function Expression() {
}
Expression.prototype.init = function() {
};
Expression.prototype.toString = function() {
return "<Expression>";
};
Expression.prototype.evaluate = function(c) {
throw new Error("Could not evaluate expression.");
};
// UnaryOperation ////////////////////////////////////////////////////////////
UnaryOperation.prototype = new Expression();
UnaryOperation.prototype.constructor = UnaryOperation;
UnaryOperation.superclass = Expression.prototype;
function UnaryOperation(rhs) {
if (arguments.length > 0) {
this.init(rhs);
}
}
UnaryOperation.prototype.init = function(rhs) {
this.rhs = rhs;
};
// UnaryMinusOperation ///////////////////////////////////////////////////////
UnaryMinusOperation.prototype = new UnaryOperation();
UnaryMinusOperation.prototype.constructor = UnaryMinusOperation;
UnaryMinusOperation.superclass = UnaryOperation.prototype;
function UnaryMinusOperation(rhs) {
if (arguments.length > 0) {
this.init(rhs);
}
}
UnaryMinusOperation.prototype.init = function(rhs) {
UnaryMinusOperation.superclass.init.call(this, rhs);
};
UnaryMinusOperation.prototype.evaluate = function(c) {
return this.rhs.evaluate(c).number().negate();
};
UnaryMinusOperation.prototype.toString = function() {
return "-" + this.rhs.toString();
};
// BinaryOperation ///////////////////////////////////////////////////////////
BinaryOperation.prototype = new Expression();
BinaryOperation.prototype.constructor = BinaryOperation;
BinaryOperation.superclass = Expression.prototype;
function BinaryOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
BinaryOperation.prototype.init = function(lhs, rhs) {
this.lhs = lhs;
this.rhs = rhs;
};
// OrOperation ///////////////////////////////////////////////////////////////
OrOperation.prototype = new BinaryOperation();
OrOperation.prototype.constructor = OrOperation;
OrOperation.superclass = BinaryOperation.prototype;
function OrOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
OrOperation.prototype.init = function(lhs, rhs) {
OrOperation.superclass.init.call(this, lhs, rhs);
};
OrOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " or " + this.rhs.toString() + ")";
};
OrOperation.prototype.evaluate = function(c) {
var b = this.lhs.evaluate(c).bool();
if (b.booleanValue()) {
return b;
}
return this.rhs.evaluate(c).bool();
};
// AndOperation //////////////////////////////////////////////////////////////
AndOperation.prototype = new BinaryOperation();
AndOperation.prototype.constructor = AndOperation;
AndOperation.superclass = BinaryOperation.prototype;
function AndOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
AndOperation.prototype.init = function(lhs, rhs) {
AndOperation.superclass.init.call(this, lhs, rhs);
};
AndOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " and " + this.rhs.toString() + ")";
};
AndOperation.prototype.evaluate = function(c) {
var b = this.lhs.evaluate(c).bool();
if (!b.booleanValue()) {
return b;
}
return this.rhs.evaluate(c).bool();
};
// EqualsOperation ///////////////////////////////////////////////////////////
EqualsOperation.prototype = new BinaryOperation();
EqualsOperation.prototype.constructor = EqualsOperation;
EqualsOperation.superclass = BinaryOperation.prototype;
function EqualsOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
EqualsOperation.prototype.init = function(lhs, rhs) {
EqualsOperation.superclass.init.call(this, lhs, rhs);
};
EqualsOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " = " + this.rhs.toString() + ")";
};
EqualsOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).equals(this.rhs.evaluate(c));
};
// NotEqualOperation /////////////////////////////////////////////////////////
NotEqualOperation.prototype = new BinaryOperation();
NotEqualOperation.prototype.constructor = NotEqualOperation;
NotEqualOperation.superclass = BinaryOperation.prototype;
function NotEqualOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
NotEqualOperation.prototype.init = function(lhs, rhs) {
NotEqualOperation.superclass.init.call(this, lhs, rhs);
};
NotEqualOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " != " + this.rhs.toString() + ")";
};
NotEqualOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).notequal(this.rhs.evaluate(c));
};
// LessThanOperation /////////////////////////////////////////////////////////
LessThanOperation.prototype = new BinaryOperation();
LessThanOperation.prototype.constructor = LessThanOperation;
LessThanOperation.superclass = BinaryOperation.prototype;
function LessThanOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
LessThanOperation.prototype.init = function(lhs, rhs) {
LessThanOperation.superclass.init.call(this, lhs, rhs);
};
LessThanOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).lessthan(this.rhs.evaluate(c));
};
LessThanOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " < " + this.rhs.toString() + ")";
};
// GreaterThanOperation //////////////////////////////////////////////////////
GreaterThanOperation.prototype = new BinaryOperation();
GreaterThanOperation.prototype.constructor = GreaterThanOperation;
GreaterThanOperation.superclass = BinaryOperation.prototype;
function GreaterThanOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
GreaterThanOperation.prototype.init = function(lhs, rhs) {
GreaterThanOperation.superclass.init.call(this, lhs, rhs);
};
GreaterThanOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).greaterthan(this.rhs.evaluate(c));
};
GreaterThanOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " > " + this.rhs.toString() + ")";
};
// LessThanOrEqualOperation //////////////////////////////////////////////////
LessThanOrEqualOperation.prototype = new BinaryOperation();
LessThanOrEqualOperation.prototype.constructor = LessThanOrEqualOperation;
LessThanOrEqualOperation.superclass = BinaryOperation.prototype;
function LessThanOrEqualOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
LessThanOrEqualOperation.prototype.init = function(lhs, rhs) {
LessThanOrEqualOperation.superclass.init.call(this, lhs, rhs);
};
LessThanOrEqualOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).lessthanorequal(this.rhs.evaluate(c));
};
LessThanOrEqualOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " <= " + this.rhs.toString() + ")";
};
// GreaterThanOrEqualOperation ///////////////////////////////////////////////
GreaterThanOrEqualOperation.prototype = new BinaryOperation();
GreaterThanOrEqualOperation.prototype.constructor = GreaterThanOrEqualOperation;
GreaterThanOrEqualOperation.superclass = BinaryOperation.prototype;
function GreaterThanOrEqualOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
GreaterThanOrEqualOperation.prototype.init = function(lhs, rhs) {
GreaterThanOrEqualOperation.superclass.init.call(this, lhs, rhs);
};
GreaterThanOrEqualOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).greaterthanorequal(this.rhs.evaluate(c));
};
GreaterThanOrEqualOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " >= " + this.rhs.toString() + ")";
};
// PlusOperation /////////////////////////////////////////////////////////////
PlusOperation.prototype = new BinaryOperation();
PlusOperation.prototype.constructor = PlusOperation;
PlusOperation.superclass = BinaryOperation.prototype;
function PlusOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
PlusOperation.prototype.init = function(lhs, rhs) {
PlusOperation.superclass.init.call(this, lhs, rhs);
};
PlusOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).number().plus(this.rhs.evaluate(c).number());
};
PlusOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " + " + this.rhs.toString() + ")";
};
// MinusOperation ////////////////////////////////////////////////////////////
MinusOperation.prototype = new BinaryOperation();
MinusOperation.prototype.constructor = MinusOperation;
MinusOperation.superclass = BinaryOperation.prototype;
function MinusOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
MinusOperation.prototype.init = function(lhs, rhs) {
MinusOperation.superclass.init.call(this, lhs, rhs);
};
MinusOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).number().minus(this.rhs.evaluate(c).number());
};
MinusOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " - " + this.rhs.toString() + ")";
};
// MultiplyOperation /////////////////////////////////////////////////////////
MultiplyOperation.prototype = new BinaryOperation();
MultiplyOperation.prototype.constructor = MultiplyOperation;
MultiplyOperation.superclass = BinaryOperation.prototype;
function MultiplyOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
MultiplyOperation.prototype.init = function(lhs, rhs) {
MultiplyOperation.superclass.init.call(this, lhs, rhs);
};
MultiplyOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).number().multiply(this.rhs.evaluate(c).number());
};
MultiplyOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " * " + this.rhs.toString() + ")";
};
// DivOperation //////////////////////////////////////////////////////////////
DivOperation.prototype = new BinaryOperation();
DivOperation.prototype.constructor = DivOperation;
DivOperation.superclass = BinaryOperation.prototype;
function DivOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
DivOperation.prototype.init = function(lhs, rhs) {
DivOperation.superclass.init.call(this, lhs, rhs);
};
DivOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).number().div(this.rhs.evaluate(c).number());
};
DivOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " div " + this.rhs.toString() + ")";
};
// ModOperation //////////////////////////////////////////////////////////////
ModOperation.prototype = new BinaryOperation();
ModOperation.prototype.constructor = ModOperation;
ModOperation.superclass = BinaryOperation.prototype;
function ModOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
ModOperation.prototype.init = function(lhs, rhs) {
ModOperation.superclass.init.call(this, lhs, rhs);
};
ModOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).number().mod(this.rhs.evaluate(c).number());
};
ModOperation.prototype.toString = function() {
return "(" + this.lhs.toString() + " mod " + this.rhs.toString() + ")";
};
// BarOperation //////////////////////////////////////////////////////////////
BarOperation.prototype = new BinaryOperation();
BarOperation.prototype.constructor = BarOperation;
BarOperation.superclass = BinaryOperation.prototype;
function BarOperation(lhs, rhs) {
if (arguments.length > 0) {
this.init(lhs, rhs);
}
}
BarOperation.prototype.init = function(lhs, rhs) {
BarOperation.superclass.init.call(this, lhs, rhs);
};
BarOperation.prototype.evaluate = function(c) {
return this.lhs.evaluate(c).nodeset().union(this.rhs.evaluate(c).nodeset());
};
BarOperation.prototype.toString = function() {
return this.lhs.toString() + " | " + this.rhs.toString();
};
// PathExpr //////////////////////////////////////////////////////////////////
PathExpr.prototype = new Expression();
PathExpr.prototype.constructor = PathExpr;
PathExpr.superclass = Expression.prototype;
function PathExpr(filter, filterPreds, locpath) {
if (arguments.length > 0) {
this.init(filter, filterPreds, locpath);
}
}
PathExpr.prototype.init = function(filter, filterPreds, locpath) {
PathExpr.superclass.init.call(this);
this.filter = filter;
this.filterPredicates = filterPreds;
this.locationPath = locpath;
};
PathExpr.prototype.evaluate = function(c) {
var nodes;
var xpc = new XPathContext();
xpc.variableResolver = c.variableResolver;
xpc.functionResolver = c.functionResolver;
xpc.namespaceResolver = c.namespaceResolver;
xpc.expressionContextNode = c.expressionContextNode;
xpc.virtualRoot = c.virtualRoot;
xpc.caseInsensitive = c.caseInsensitive;
if (this.filter == null) {
nodes = [ c.contextNode ];
} else {
var ns = this.filter.evaluate(c);
if (!Utilities.instance_of(ns, XNodeSet)) {
if (this.filterPredicates != null && this.filterPredicates.length > 0 || this.locationPath != null) {
throw new Error("Path expression filter must evaluate to a nodset if predicates or location path are used");
}
return ns;
}
nodes = ns.toArray();
if (this.filterPredicates != null) {
// apply each of the predicates in turn
for (var j = 0; j < this.filterPredicates.length; j++) {
var pred = this.filterPredicates[j];
var newNodes = [];
xpc.contextSize = nodes.length;
for (xpc.contextPosition = 1; xpc.contextPosition <= xpc.contextSize; xpc.contextPosition++) {
xpc.contextNode = nodes[xpc.contextPosition - 1];
if (this.predicateMatches(pred, xpc)) {
newNodes.push(xpc.contextNode);
}
}
nodes = newNodes;
}
}
}
if (this.locationPath != null) {
if (this.locationPath.absolute) {
if (nodes[0].nodeType != 9 /*Node.DOCUMENT_NODE*/) {
if (xpc.virtualRoot != null) {
nodes = [ xpc.virtualRoot ];
} else {
if (nodes[0].ownerDocument == null) {
// IE 5.5 doesn't have ownerDocument?
var n = nodes[0];
while (n.parentNode != null) {
n = n.parentNode;
}
nodes = [ n ];
} else {
nodes = [ nodes[0].ownerDocument ];
}
}
} else {
nodes = [ nodes[0] ];
}
}
for (var i = 0; i < this.locationPath.steps.length; i++) {
var step = this.locationPath.steps[i];
var newNodes = [];
for (var j = 0; j < nodes.length; j++) {
xpc.contextNode = nodes[j];
switch (step.axis) {
case Step.ANCESTOR:
// look at all the ancestor nodes
if (xpc.contextNode === xpc.virtualRoot) {
break;
}
var m;
if (xpc.contextNode.nodeType == 2 /*Node.ATTRIBUTE_NODE*/) {
m = this.getOwnerElement(xpc.contextNode);
} else {
m = xpc.contextNode.parentNode;
}
while (m != null) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
if (m === xpc.virtualRoot) {
break;
}
m = m.parentNode;
}
break;
case Step.ANCESTORORSELF:
// look at all the ancestor nodes and the current node
for (var m = xpc.contextNode; m != null; m = m.nodeType == 2 /*Node.ATTRIBUTE_NODE*/ ? this.getOwnerElement(m) : m.parentNode) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
if (m === xpc.virtualRoot) {
break;
}
}
break;
case Step.ATTRIBUTE:
// look at the attributes
var nnm = xpc.contextNode.attributes;
if (nnm != null) {
for (var k = 0; k < nnm.length; k++) {
var m = nnm.item(k);
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
}
}
break;
case Step.CHILD:
// look at all child elements
for (var m = xpc.contextNode.firstChild; m != null; m = m.nextSibling) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
}
break;
case Step.DESCENDANT:
// look at all descendant nodes
var st = [ xpc.contextNode.firstChild ];
while (st.length > 0) {
for (var m = st.pop(); m != null; ) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
if (m.firstChild != null) {
st.push(m.nextSibling);
m = m.firstChild;
} else {
m = m.nextSibling;
}
}
}
break;
case Step.DESCENDANTORSELF:
// look at self
if (step.nodeTest.matches(xpc.contextNode, xpc)) {
newNodes.push(xpc.contextNode);
}
// look at all descendant nodes
var st = [ xpc.contextNode.firstChild ];
while (st.length > 0) {
for (var m = st.pop(); m != null; ) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
if (m.firstChild != null) {
st.push(m.nextSibling);
m = m.firstChild;
} else {
m = m.nextSibling;
}
}
}
break;
case Step.FOLLOWING:
if (xpc.contextNode === xpc.virtualRoot) {
break;
}
var st = [];
if (xpc.contextNode.firstChild != null) {
st.unshift(xpc.contextNode.firstChild);
} else {
st.unshift(xpc.contextNode.nextSibling);
}
for (var m = xpc.contextNode.parentNode; m != null && m.nodeType != 9 /*Node.DOCUMENT_NODE*/ && m !== xpc.virtualRoot; m = m.parentNode) {
st.unshift(m.nextSibling);
}
do {
for (var m = st.pop(); m != null; ) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
if (m.firstChild != null) {
st.push(m.nextSibling);
m = m.firstChild;
} else {
m = m.nextSibling;
}
}
} while (st.length > 0);
break;
case Step.FOLLOWINGSIBLING:
if (xpc.contextNode === xpc.virtualRoot) {
break;
}
for (var m = xpc.contextNode.nextSibling; m != null; m = m.nextSibling) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
}
break;
case Step.NAMESPACE:
var n = {};
if (xpc.contextNode.nodeType == 1 /*Node.ELEMENT_NODE*/) {
n["xml"] = XPath.XML_NAMESPACE_URI;
n["xmlns"] = XPath.XMLNS_NAMESPACE_URI;
for (var m = xpc.contextNode; m != null && m.nodeType == 1 /*Node.ELEMENT_NODE*/; m = m.parentNode) {
for (var k = 0; k < m.attributes.length; k++) {
var attr = m.attributes.item(k);
var nm = String(attr.name);
if (nm == "xmlns") {
if (n[""] == undefined) {
n[""] = attr.value;
}
} else if (nm.length > 6 && nm.substring(0, 6) == "xmlns:") {
var pre = nm.substring(6, nm.length);
if (n[pre] == undefined) {
n[pre] = attr.value;
}
}
}
}
for (var pre in n) {
var nsn = new NamespaceNode(pre, n[pre], xpc.contextNode);
if (step.nodeTest.matches(nsn, xpc)) {
newNodes.push(nsn);
}
}
}
break;
case Step.PARENT:
m = null;
if (xpc.contextNode !== xpc.virtualRoot) {
if (xpc.contextNode.nodeType == 2 /*Node.ATTRIBUTE_NODE*/) {
m = this.getOwnerElement(xpc.contextNode);
} else {
m = xpc.contextNode.parentNode;
}
}
if (m != null && step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
break;
case Step.PRECEDING:
var st;
if (xpc.virtualRoot != null) {
st = [ xpc.virtualRoot ];
} else {
st = xpc.contextNode.nodeType == 9 /*Node.DOCUMENT_NODE*/
? [ xpc.contextNode ]
: [ xpc.contextNode.ownerDocument ];
}
outer: while (st.length > 0) {
for (var m = st.pop(); m != null; ) {
if (m == xpc.contextNode) {
break outer;
}
if (step.nodeTest.matches(m, xpc)) {
newNodes.unshift(m);
}
if (m.firstChild != null) {
st.push(m.nextSibling);
m = m.firstChild;
} else {
m = m.nextSibling;
}
}
}
break;
case Step.PRECEDINGSIBLING:
if (xpc.contextNode === xpc.virtualRoot) {
break;
}
for (var m = xpc.contextNode.previousSibling; m != null; m = m.previousSibling) {
if (step.nodeTest.matches(m, xpc)) {
newNodes.push(m);
}
}
break;
case Step.SELF:
if (step.nodeTest.matches(xpc.contextNode, xpc)) {
newNodes.push(xpc.contextNode);
}
break;
default:
}
}
nodes = newNodes;
// apply each of the predicates in turn
for (var j = 0; j < step.predicates.length; j++) {
var pred = step.predicates[j];
var newNodes = [];
xpc.contextSize = nodes.length;
for (xpc.contextPosition = 1; xpc.contextPosition <= xpc.contextSize; xpc.contextPosition++) {
xpc.contextNode = nodes[xpc.contextPosition - 1];
if (this.predicateMatches(pred, xpc)) {
newNodes.push(xpc.contextNode);
} else {
}
}
nodes = newNodes;
}
}
}
var ns = new XNodeSet();
ns.addArray(nodes);
return ns;
};
PathExpr.prototype.predicateMatches = function(pred, c) {
var res = pred.evaluate(c);
if (Utilities.instance_of(res, XNumber)) {
return c.contextPosition == res.numberValue();
}
return res.booleanValue();
};
PathExpr.prototype.toString = function() {
if (this.filter != undefined) {
var s = this.filter.toString();
if (Utilities.instance_of(this.filter, XString)) {
s = "'" + s + "'";
}
if (this.filterPredicates != undefined) {
for (var i = 0; i < this.filterPredicates.length; i++) {
s = s + "[" + this.filterPredicates[i].toString() + "]";
}
}
if (this.locationPath != undefined) {
if (!this.locationPath.absolute) {
s += "/";
}
s += this.locationPath.toString();
}
return s;
}
return this.locationPath.toString();
};
PathExpr.prototype.getOwnerElement = function(n) {
// DOM 2 has ownerElement
if (n.ownerElement) {
return n.ownerElement;
}
// DOM 1 Internet Explorer can use selectSingleNode (ironically)
try {
if (n.selectSingleNode) {
return n.selectSingleNode("..");
}
} catch (e) {
}
// Other DOM 1 implementations must use this egregious search
var doc = n.nodeType == 9 /*Node.DOCUMENT_NODE*/
? n
: n.ownerDocument;
var elts = doc.getElementsByTagName("*");
for (var i = 0; i < elts.length; i++) {
var elt = elts.item(i);
var nnm = elt.attributes;
for (var j = 0; j < nnm.length; j++) {
var an = nnm.item(j);
if (an === n) {
return elt;
}
}
}
return null;
};
// LocationPath //////////////////////////////////////////////////////////////
LocationPath.prototype = new Object();
LocationPath.prototype.constructor = LocationPath;
LocationPath.superclass = Object.prototype;
function LocationPath(abs, steps) {
if (arguments.length > 0) {
this.init(abs, steps);
}
}
LocationPath.prototype.init = function(abs, steps) {
this.absolute = abs;
this.steps = steps;
};
LocationPath.prototype.toString = function() {
var s;
if (this.absolute) {
s = "/";
} else {
s = "";
}
for (var i = 0; i < this.steps.length; i++) {
if (i != 0) {
s += "/";
}
s += this.steps[i].toString();
}
return s;
};
// Step //////////////////////////////////////////////////////////////////////
Step.prototype = new Object();
Step.prototype.constructor = Step;
Step.superclass = Object.prototype;
function Step(axis, nodetest, preds) {
if (arguments.length > 0) {
this.init(axis, nodetest, preds);
}
}
Step.prototype.init = function(axis, nodetest, preds) {
this.axis = axis;
this.nodeTest = nodetest;
this.predicates = preds;
};
Step.prototype.toString = function() {
var s;
switch (this.axis) {
case Step.ANCESTOR:
s = "ancestor";
break;
case Step.ANCESTORORSELF:
s = "ancestor-or-self";
break;
case Step.ATTRIBUTE:
s = "attribute";
break;
case Step.CHILD:
s = "child";
break;
case Step.DESCENDANT:
s = "descendant";
break;
case Step.DESCENDANTORSELF:
s = "descendant-or-self";
break;
case Step.FOLLOWING:
s = "following";
break;
case Step.FOLLOWINGSIBLING:
s = "following-sibling";
break;
case Step.NAMESPACE:
s = "namespace";
break;
case Step.PARENT:
s = "parent";
break;
case Step.PRECEDING:
s = "preceding";
break;
case Step.PRECEDINGSIBLING:
s = "preceding-sibling";
break;
case Step.SELF:
s = "self";
break;
}
s += "::";
s += this.nodeTest.toString();
for (var i = 0; i < this.predicates.length; i++) {
s += "[" + this.predicates[i].toString() + "]";
}
return s;
};
Step.ANCESTOR = 0;
Step.ANCESTORORSELF = 1;
Step.ATTRIBUTE = 2;
Step.CHILD = 3;
Step.DESCENDANT = 4;
Step.DESCENDANTORSELF = 5;
Step.FOLLOWING = 6;
Step.FOLLOWINGSIBLING = 7;
Step.NAMESPACE = 8;
Step.PARENT = 9;
Step.PRECEDING = 10;
Step.PRECEDINGSIBLING = 11;
Step.SELF = 12;
// NodeTest //////////////////////////////////////////////////////////////////
NodeTest.prototype = new Object();
NodeTest.prototype.constructor = NodeTest;
NodeTest.superclass = Object.prototype;
function NodeTest(type, value) {
if (arguments.length > 0) {
this.init(type, value);
}
}
NodeTest.prototype.init = function(type, value) {
this.type = type;
this.value = value;
};
NodeTest.prototype.toString = function() {
switch (this.type) {
case NodeTest.NAMETESTANY:
return "*";
case NodeTest.NAMETESTPREFIXANY:
return this.value + ":*";
case NodeTest.NAMETESTRESOLVEDANY:
return "{" + this.value + "}*";
case NodeTest.NAMETESTQNAME:
return this.value;
case NodeTest.NAMETESTRESOLVEDNAME:
return "{" + this.namespaceURI + "}" + this.value;
case NodeTest.COMMENT:
return "comment()";
case NodeTest.TEXT:
return "text()";
case NodeTest.PI:
if (this.value != undefined) {
return "processing-instruction(\"" + this.value + "\")";
}
return "processing-instruction()";
case NodeTest.NODE:
return "node()";
}
return "<unknown nodetest type>";
};
NodeTest.prototype.matches = function(n, xpc) {
switch (this.type) {
case NodeTest.NAMETESTANY:
if (n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/
|| n.nodeType == 1 /*Node.ELEMENT_NODE*/
|| n.nodeType == XPathNamespace.XPATH_NAMESPACE_NODE) {
return true;
}
return false;
case NodeTest.NAMETESTPREFIXANY:
if ((n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/ || n.nodeType == 1 /*Node.ELEMENT_NODE*/)) {
var ns = xpc.namespaceResolver.getNamespace(this.value, xpc.expressionContextNode)
if (ns == null) {
throw new Error("Cannot resolve QName " + this.value);
}
return true;
}
return false;
case NodeTest.NAMETESTQNAME:
if (n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/
|| n.nodeType == 1 /*Node.ELEMENT_NODE*/
|| n.nodeType == XPathNamespace.XPATH_NAMESPACE_NODE) {
var test = Utilities.resolveQName(this.value, xpc.namespaceResolver, xpc.expressionContextNode, false);
if (test[0] == null) {
throw new Error("Cannot resolve QName " + this.value);
}
test[0] = String(test[0]);
test[1] = String(test[1]);
if (test[0] == "") {
test[0] = null;
}
var node = Utilities.resolveQName(n.nodeName, xpc.namespaceResolver, n, true);
node[0] = String(node[0]);
node[1] = String(node[1]);
if (node[0] == "") {
node[0] = null;
}
if (xpc.caseInsensitive) {
return test[0] == node[0] && String(test[1]).toLowerCase() == String(node[1]).toLowerCase();
}
return test[0] == node[0] && test[1] == node[1];
}
return false;
case NodeTest.COMMENT:
return n.nodeType == 8 /*Node.COMMENT_NODE*/;
case NodeTest.TEXT:
return n.nodeType == 3 /*Node.TEXT_NODE*/ || n.nodeType == 4 /*Node.CDATA_SECTION_NODE*/;
case NodeTest.PI:
return n.nodeType == 7 /*Node.PROCESSING_INSTRUCTION_NODE*/
&& (this.value == null || n.nodeName == this.value);
case NodeTest.NODE:
return n.nodeType == 9 /*Node.DOCUMENT_NODE*/
|| n.nodeType == 1 /*Node.ELEMENT_NODE*/
|| n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/
|| n.nodeType == 3 /*Node.TEXT_NODE*/
|| n.nodeType == 4 /*Node.CDATA_SECTION_NODE*/
|| n.nodeType == 8 /*Node.COMMENT_NODE*/
|| n.nodeType == 7 /*Node.PROCESSING_INSTRUCTION_NODE*/;
}
return false;
};
NodeTest.NAMETESTANY = 0;
NodeTest.NAMETESTPREFIXANY = 1;
NodeTest.NAMETESTQNAME = 2;
NodeTest.COMMENT = 3;
NodeTest.TEXT = 4;
NodeTest.PI = 5;
NodeTest.NODE = 6;
// VariableReference /////////////////////////////////////////////////////////
VariableReference.prototype = new Expression();
VariableReference.prototype.constructor = VariableReference;
VariableReference.superclass = Expression.prototype;
function VariableReference(v) {
if (arguments.length > 0) {
this.init(v);
}
}
VariableReference.prototype.init = function(v) {
this.variable = v;
};
VariableReference.prototype.toString = function() {
return "$" + this.variable;
};
VariableReference.prototype.evaluate = function(c) {
return c.variableResolver.getVariable(this.variable, c);
};
// FunctionCall //////////////////////////////////////////////////////////////
FunctionCall.prototype = new Expression();
FunctionCall.prototype.constructor = FunctionCall;
FunctionCall.superclass = Expression.prototype;
function FunctionCall(fn, args) {
if (arguments.length > 0) {
this.init(fn, args);
}
}
FunctionCall.prototype.init = function(fn, args) {
this.functionName = fn;
this.arguments = args;
};
FunctionCall.prototype.toString = function() {
var s = this.functionName + "(";
for (var i = 0; i < this.arguments.length; i++) {
if (i > 0) {
s += ", ";
}
s += this.arguments[i].toString();
}
return s + ")";
};
FunctionCall.prototype.evaluate = function(c) {
var f = c.functionResolver.getFunction(this.functionName, c);
if (f == undefined) {
throw new Error("Unknown function " + this.functionName);
}
var a = [c].concat(this.arguments);
return f.apply(c.functionResolver.thisArg, a);
};
// XString ///////////////////////////////////////////////////////////////////
XString.prototype = new Expression();
XString.prototype.constructor = XString;
XString.superclass = Expression.prototype;
function XString(s) {
if (arguments.length > 0) {
this.init(s);
}
}
XString.prototype.init = function(s) {
this.str = s;
};
XString.prototype.toString = function() {
return this.str;
};
XString.prototype.evaluate = function(c) {
return this;
};
XString.prototype.string = function() {
return this;
};
XString.prototype.number = function() {
return new XNumber(this.str);
};
XString.prototype.bool = function() {
return new XBoolean(this.str);
};
XString.prototype.nodeset = function() {
throw new Error("Cannot convert string to nodeset");
};
XString.prototype.stringValue = function() {
return this.str;
};
XString.prototype.numberValue = function() {
return this.number().numberValue();
};
XString.prototype.booleanValue = function() {
return this.bool().booleanValue();
};
XString.prototype.equals = function(r) {
if (Utilities.instance_of(r, XBoolean)) {
return this.bool().equals(r);
}
if (Utilities.instance_of(r, XNumber)) {
return this.number().equals(r);
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithString(this, Operators.equals);
}
return new XBoolean(this.str == r.str);
};
XString.prototype.notequal = function(r) {
if (Utilities.instance_of(r, XBoolean)) {
return this.bool().notequal(r);
}
if (Utilities.instance_of(r, XNumber)) {
return this.number().notequal(r);
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithString(this, Operators.notequal);
}
return new XBoolean(this.str != r.str);
};
XString.prototype.lessthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.greaterthanorequal);
}
return this.number().lessthan(r.number());
};
XString.prototype.greaterthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.lessthanorequal);
}
return this.number().greaterthan(r.number());
};
XString.prototype.lessthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.greaterthan);
}
return this.number().lessthanorequal(r.number());
};
XString.prototype.greaterthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.lessthan);
}
return this.number().greaterthanorequal(r.number());
};
// XNumber ///////////////////////////////////////////////////////////////////
XNumber.prototype = new Expression();
XNumber.prototype.constructor = XNumber;
XNumber.superclass = Expression.prototype;
function XNumber(n) {
if (arguments.length > 0) {
this.init(n);
}
}
XNumber.prototype.init = function(n) {
this.num = Number(n);
};
XNumber.prototype.toString = function() {
return this.num;
};
XNumber.prototype.evaluate = function(c) {
return this;
};
XNumber.prototype.string = function() {
return new XString(this.num);
};
XNumber.prototype.number = function() {
return this;
};
XNumber.prototype.bool = function() {
return new XBoolean(this.num);
};
XNumber.prototype.nodeset = function() {
throw new Error("Cannot convert number to nodeset");
};
XNumber.prototype.stringValue = function() {
return this.string().stringValue();
};
XNumber.prototype.numberValue = function() {
return this.num;
};
XNumber.prototype.booleanValue = function() {
return this.bool().booleanValue();
};
XNumber.prototype.negate = function() {
return new XNumber(-this.num);
};
XNumber.prototype.equals = function(r) {
if (Utilities.instance_of(r, XBoolean)) {
return this.bool().equals(r);
}
if (Utilities.instance_of(r, XString)) {
return this.equals(r.number());
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.equals);
}
return new XBoolean(this.num == r.num);
};
XNumber.prototype.notequal = function(r) {
if (Utilities.instance_of(r, XBoolean)) {
return this.bool().notequal(r);
}
if (Utilities.instance_of(r, XString)) {
return this.notequal(r.number());
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.notequal);
}
return new XBoolean(this.num != r.num);
};
XNumber.prototype.lessthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.greaterthanorequal);
}
if (Utilities.instance_of(r, XBoolean) || Utilities.instance_of(r, XString)) {
return this.lessthan(r.number());
}
return new XBoolean(this.num < r.num);
};
XNumber.prototype.greaterthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.lessthanorequal);
}
if (Utilities.instance_of(r, XBoolean) || Utilities.instance_of(r, XString)) {
return this.greaterthan(r.number());
}
return new XBoolean(this.num > r.num);
};
XNumber.prototype.lessthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.greaterthan);
}
if (Utilities.instance_of(r, XBoolean) || Utilities.instance_of(r, XString)) {
return this.lessthanorequal(r.number());
}
return new XBoolean(this.num <= r.num);
};
XNumber.prototype.greaterthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this, Operators.lessthan);
}
if (Utilities.instance_of(r, XBoolean) || Utilities.instance_of(r, XString)) {
return this.greaterthanorequal(r.number());
}
return new XBoolean(this.num >= r.num);
};
XNumber.prototype.plus = function(r) {
return new XNumber(this.num + r.num);
};
XNumber.prototype.minus = function(r) {
return new XNumber(this.num - r.num);
};
XNumber.prototype.multiply = function(r) {
return new XNumber(this.num * r.num);
};
XNumber.prototype.div = function(r) {
return new XNumber(this.num / r.num);
};
XNumber.prototype.mod = function(r) {
return new XNumber(this.num % r.num);
};
// XBoolean //////////////////////////////////////////////////////////////////
XBoolean.prototype = new Expression();
XBoolean.prototype.constructor = XBoolean;
XBoolean.superclass = Expression.prototype;
function XBoolean(b) {
if (arguments.length > 0) {
this.init(b);
}
}
XBoolean.prototype.init = function(b) {
this.b = Boolean(b);
};
XBoolean.prototype.toString = function() {
return this.b.toString();
};
XBoolean.prototype.evaluate = function(c) {
return this;
};
XBoolean.prototype.string = function() {
return new XString(this.b);
};
XBoolean.prototype.number = function() {
return new XNumber(this.b);
};
XBoolean.prototype.bool = function() {
return this;
};
XBoolean.prototype.nodeset = function() {
throw new Error("Cannot convert boolean to nodeset");
};
XBoolean.prototype.stringValue = function() {
return this.string().stringValue();
};
XBoolean.prototype.numberValue = function() {
return this.num().numberValue();
};
XBoolean.prototype.booleanValue = function() {
return this.b;
};
XBoolean.prototype.not = function() {
return new XBoolean(!this.b);
};
XBoolean.prototype.equals = function(r) {
if (Utilities.instance_of(r, XString) || Utilities.instance_of(r, XNumber)) {
return this.equals(r.bool());
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithBoolean(this, Operators.equals);
}
return new XBoolean(this.b == r.b);
};
XBoolean.prototype.notequal = function(r) {
if (Utilities.instance_of(r, XString) || Utilities.instance_of(r, XNumber)) {
return this.notequal(r.bool());
}
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithBoolean(this, Operators.notequal);
}
return new XBoolean(this.b != r.b);
};
XBoolean.prototype.lessthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.greaterthanorequal);
}
return this.number().lessthan(r.number());
};
XBoolean.prototype.greaterthan = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.lessthanorequal);
}
return this.number().greaterthan(r.number());
};
XBoolean.prototype.lessthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.greaterthan);
}
return this.number().lessthanorequal(r.number());
};
XBoolean.prototype.greaterthanorequal = function(r) {
if (Utilities.instance_of(r, XNodeSet)) {
return r.compareWithNumber(this.number(), Operators.lessthan);
}
return this.number().greaterthanorequal(r.number());
};
// XNodeSet //////////////////////////////////////////////////////////////////
XNodeSet.prototype = new Expression();
XNodeSet.prototype.constructor = XNodeSet;
XNodeSet.superclass = Expression.prototype;
function XNodeSet() {
this.init();
}
XNodeSet.prototype.init = function() {
this.tree = null;
this.size = 0;
};
XNodeSet.prototype.toString = function() {
var p = this.first();
if (p == null) {
return "";
}
return this.stringForNode(p);
};
XNodeSet.prototype.evaluate = function(c) {
return this;
};
XNodeSet.prototype.string = function() {
return new XString(this.toString());
};
XNodeSet.prototype.stringValue = function() {
return this.toString();
};
XNodeSet.prototype.number = function() {
return new XNumber(this.string());
};
XNodeSet.prototype.numberValue = function() {
return Number(this.string());
};
XNodeSet.prototype.bool = function() {
return new XBoolean(this.tree != null);
};
XNodeSet.prototype.booleanValue = function() {
return this.tree != null;
};
XNodeSet.prototype.nodeset = function() {
return this;
};
XNodeSet.prototype.stringForNode = function(n) {
if (n.nodeType == 9 /*Node.DOCUMENT_NODE*/) {
n = n.documentElement;
}
if (n.nodeType == 1 /*Node.ELEMENT_NODE*/) {
return this.stringForNodeRec(n);
}
if (n.isNamespaceNode) {
return n.namespace;
}
return n.nodeValue;
};
XNodeSet.prototype.stringForNodeRec = function(n) {
var s = "";
for (var n2 = n.firstChild; n2 != null; n2 = n2.nextSibling) {
if (n2.nodeType == 3 /*Node.TEXT_NODE*/) {
s += n2.nodeValue;
} else if (n2.nodeType == 1 /*Node.ELEMENT_NODE*/) {
s += this.stringForNodeRec(n2);
}
}
return s;
};
XNodeSet.prototype.first = function() {
var p = this.tree;
if (p == null) {
return null;
}
while (p.left != null) {
p = p.left;
}
return p.node;
};
XNodeSet.prototype.add = function(n) {
if (this.tree == null) {
this.tree = new Object();
this.tree.node = n;
this.tree.left = null;
this.tree.right = null;
this.size = 1;
return;
}
var p = this.tree;
while (1) {
var o = this.order(n, p.node);
if (o == 0) {
return;
}
if (o > 0) {
if (p.right == null) {
p.right = new Object();
p.right.node = n;
p.right.left = null;
p.right.right = null;
this.size++;
return;
}
p = p.right;
} else {
if (p.left == null) {
p.left = new Object();
p.left.node = n;
p.left.left = null;
p.left.right = null;
this.size++;
return;
}
p = p.left;
}
}
};
XNodeSet.prototype.addArray = function(ns) {
for (var i = 0; i < ns.length; i++) {
this.add(ns[i]);
}
};
XNodeSet.prototype.toArray = function() {
var a = [];
this.toArrayRec(this.tree, a);
return a;
};
XNodeSet.prototype.toArrayRec = function(t, a) {
if (t != null) {
this.toArrayRec(t.left, a);
a.push(t.node);
this.toArrayRec(t.right, a);
}
};
XNodeSet.prototype.order = function(n1, n2) {
if (n1 == n2) {
return 0;
}
var d1 = 0;
var d2 = 0;
for (var m1 = n1; m1 != null; m1 = m1.parentNode) {
d1++;
}
for (var m2 = n2; m2 != null; m2 = m2.parentNode) {
d2++;
}
if (d1 > d2) {
while (d1 > d2) {
n1 = n1.parentNode;
d1--;
}
if (n1 == n2) {
return 1;
}
} else if (d2 > d1) {
while (d2 > d1) {
n2 = n2.parentNode;
d2--;
}
if (n1 == n2) {
return -1;
}
}
while (n1.parentNode != n2.parentNode) {
n1 = n1.parentNode;
n2 = n2.parentNode;
}
while (n1.previousSibling != null && n2.previousSibling != null) {
n1 = n1.previousSibling;
n2 = n2.previousSibling;
}
if (n1.previousSibling == null) {
return -1;
}
return 1;
};
XNodeSet.prototype.compareWithString = function(r, o) {
var a = this.toArray();
for (var i = 0; i < a.length; i++) {
var n = a[i];
var l = new XString(this.stringForNode(n));
var res = o(l, r);
if (res.booleanValue()) {
return res;
}
}
return new XBoolean(false);
};
XNodeSet.prototype.compareWithNumber = function(r, o) {
var a = this.toArray();
for (var i = 0; i < a.length; i++) {
var n = a[i];
var l = new XNumber(this.stringForNode(n));
var res = o(l, r);
if (res.booleanValue()) {
return res;
}
}
return new XBoolean(false);
};
XNodeSet.prototype.compareWithBoolean = function(r, o) {
return o(this.bool(), r);
};
XNodeSet.prototype.compareWithNodeSet = function(r, o) {
var a = this.toArray();
for (var i = 0; i < a.length; i++) {
var n = a[i];
var l = new XString(this.stringForNode(n));
var b = r.toArray();
for (var j = 0; j < b.length; j++) {
var n2 = b[j];
var r = new XString(this.stringForNode(n2));
var res = o(l, r);
if (res.booleanValue()) {
return res;
}
}
}
return new XBoolean(false);
};
XNodeSet.prototype.equals = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithString(r, Operators.equals);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.equals);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.equals);
}
return this.compareWithNodeSet(r, Operators.equals);
};
XNodeSet.prototype.notequal = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithString(r, Operators.notequal);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.notequal);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.notequal);
}
return this.compareWithNodeSet(r, Operators.notequal);
};
XNodeSet.prototype.lessthan = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithNumber(r.number(), Operators.lessthan);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.lessthan);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.lessthan);
}
return this.compareWithNodeSet(r, Operators.lessthan);
};
XNodeSet.prototype.greaterthan = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithNumber(r.number(), Operators.greaterthan);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.greaterthan);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.greaterthan);
}
return this.compareWithNodeSet(r, Operators.greaterthan);
};
XNodeSet.prototype.lessthanorequal = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithNumber(r.number(), Operators.lessthanorequal);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.lessthanorequal);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.lessthanorequal);
}
return this.compareWithNodeSet(r, Operators.lessthanorequal);
};
XNodeSet.prototype.greaterthanorequal = function(r) {
if (Utilities.instance_of(r, XString)) {
return this.compareWithNumber(r.number(), Operators.greaterthanorequal);
}
if (Utilities.instance_of(r, XNumber)) {
return this.compareWithNumber(r, Operators.greaterthanorequal);
}
if (Utilities.instance_of(r, XBoolean)) {
return this.compareWithBoolean(r, Operators.greaterthanorequal);
}
return this.compareWithNodeSet(r, Operators.greaterthanorequal);
};
XNodeSet.prototype.union = function(r) {
var ns = new XNodeSet();
ns.tree = this.tree;
ns.size = this.size;
ns.addArray(r.toArray());
return ns;
};
// XPathNamespace ////////////////////////////////////////////////////////////
XPathNamespace.prototype = new Object();
XPathNamespace.prototype.constructor = XPathNamespace;
XPathNamespace.superclass = Object.prototype;
function XPathNamespace(pre, ns, p) {
this.isXPathNamespace = true;
this.ownerDocument = p.ownerDocument;
this.nodeName = "#namespace";
this.prefix = pre;
this.localName = pre;
this.namespaceURI = ns;
this.nodeValue = ns;
this.ownerElement = p;
this.nodeType = XPathNamespace.XPATH_NAMESPACE_NODE;
}
XPathNamespace.prototype.toString = function() {
return "{ \"" + this.prefix + "\", \"" + this.namespaceURI + "\" }";
};
// Operators /////////////////////////////////////////////////////////////////
var Operators = new Object();
Operators.equals = function(l, r) {
return l.equals(r);
};
Operators.notequal = function(l, r) {
return l.notequal(r);
};
Operators.lessthan = function(l, r) {
return l.lessthan(r);
};
Operators.greaterthan = function(l, r) {
return l.greaterthan(r);
};
Operators.lessthanorequal = function(l, r) {
return l.lessthanorequal(r);
};
Operators.greaterthanorequal = function(l, r) {
return l.greaterthanorequal(r);
};
// XPathContext //////////////////////////////////////////////////////////////
XPathContext.prototype = new Object();
XPathContext.prototype.constructor = XPathContext;
XPathContext.superclass = Object.prototype;
function XPathContext(vr, nr, fr) {
this.variableResolver = vr != null ? vr : new VariableResolver();
this.namespaceResolver = nr != null ? nr : new NamespaceResolver();
this.functionResolver = fr != null ? fr : new FunctionResolver();
}
// VariableResolver //////////////////////////////////////////////////////////
VariableResolver.prototype = new Object();
VariableResolver.prototype.constructor = VariableResolver;
VariableResolver.superclass = Object.prototype;
function VariableResolver() {
}
VariableResolver.prototype.getVariable = function(vn, c) {
var parts = Utilities.splitQName(vn);
if (parts[0] != null) {
parts[0] = c.namespaceResolver.getNamespace(parts[0], c.expressionContextNode);
if (parts[0] == null) {
throw new Error("Cannot resolve QName " + fn);
}
}
return this.getVariableWithName(parts[0], parts[1], c.expressionContextNode);
};
VariableResolver.prototype.getVariableWithName = function(ns, ln, c) {
return null;
};
// FunctionResolver //////////////////////////////////////////////////////////
FunctionResolver.prototype = new Object();
FunctionResolver.prototype.constructor = FunctionResolver;
FunctionResolver.superclass = Object.prototype;
function FunctionResolver(thisArg) {
this.thisArg = thisArg != null ? thisArg : Functions;
this.functions = new Object();
this.addStandardFunctions();
}
FunctionResolver.prototype.addStandardFunctions = function() {
this.functions["{}last"] = Functions.last;
this.functions["{}position"] = Functions.position;
this.functions["{}count"] = Functions.count;
this.functions["{}id"] = Functions.id;
this.functions["{}local-name"] = Functions.localName;
this.functions["{}namespace-uri"] = Functions.namespaceURI;
this.functions["{}name"] = Functions.name;
this.functions["{}string"] = Functions.string;
this.functions["{}concat"] = Functions.concat;
this.functions["{}starts-with"] = Functions.startsWith;
this.functions["{}contains"] = Functions.contains;
this.functions["{}substring-before"] = Functions.substringBefore;
this.functions["{}substring-after"] = Functions.substringAfter;
this.functions["{}substring"] = Functions.substring;
this.functions["{}string-length"] = Functions.stringLength;
this.functions["{}normalize-space"] = Functions.normalizeSpace;
this.functions["{}translate"] = Functions.translate;
this.functions["{}boolean"] = Functions.boolean_;
this.functions["{}not"] = Functions.not;
this.functions["{}true"] = Functions.true_;
this.functions["{}false"] = Functions.false_;
this.functions["{}lang"] = Functions.lang;
this.functions["{}number"] = Functions.number;
this.functions["{}sum"] = Functions.sum;
this.functions["{}floor"] = Functions.floor;
this.functions["{}ceiling"] = Functions.ceiling;
this.functions["{}round"] = Functions.round;
};
FunctionResolver.prototype.addFunction = function(ns, ln, f) {
this.functions["{" + ns + "}" + ln] = f;
};
FunctionResolver.prototype.getFunction = function(fn, c) {
var parts = Utilities.resolveQName(fn, c.namespaceResolver, c.contextNode, false);
if (parts[0] == null) {
throw new Error("Cannot resolve QName " + fn);
}
return this.getFunctionWithName(parts[0], parts[1], c.contextNode);
};
FunctionResolver.prototype.getFunctionWithName = function(ns, ln, c) {
return this.functions["{" + ns + "}" + ln];
};
// NamespaceResolver /////////////////////////////////////////////////////////
NamespaceResolver.prototype = new Object();
NamespaceResolver.prototype.constructor = NamespaceResolver;
NamespaceResolver.superclass = Object.prototype;
function NamespaceResolver() {
}
NamespaceResolver.prototype.getNamespace = function(prefix, n) {
if (prefix == "xml") {
return XPath.XML_NAMESPACE_URI;
} else if (prefix == "xmlns") {
return XPath.XMLNS_NAMESPACE_URI;
}
if (n.nodeType == 9 /*Node.DOCUMENT_NODE*/) {
n = n.documentElement;
} else if (n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/) {
n = PathExpr.prototype.getOwnerElement(n);
} else if (n.nodeType != 1 /*Node.ELEMENT_NODE*/) {
n = n.parentNode;
}
while (n != null && n.nodeType == 1 /*Node.ELEMENT_NODE*/) {
var nnm = n.attributes;
for (var i = 0; i < nnm.length; i++) {
var a = nnm.item(i);
var aname = a.nodeName;
if (aname == "xmlns" && prefix == ""
|| aname == "xmlns:" + prefix) {
return String(a.nodeValue);
}
}
n = n.parentNode;
}
return null;
};
// Functions /////////////////////////////////////////////////////////////////
Functions = new Object();
Functions.last = function() {
var c = arguments[0];
if (arguments.length != 1) {
throw new Error("Function last expects ()");
}
return new XNumber(c.contextSize);
};
Functions.position = function() {
var c = arguments[0];
if (arguments.length != 1) {
throw new Error("Function position expects ()");
}
return new XNumber(c.contextPosition);
};
Functions.count = function() {
var c = arguments[0];
var ns;
if (arguments.length != 2 || !Utilities.instance_of(ns = arguments[1].evaluate(c), XNodeSet)) {
throw new Error("Function count expects (node-set)");
}
return new XNumber(ns.size);
};
Functions.id = function() {
var c = arguments[0];
var id;
if (arguments.length != 2) {
throw new Error("Function id expects (object)");
}
id = arguments[1].evaluate(c);
if (Utilities.instance_of(id, XNodeSet)) {
id = id.toArray().join(" ");
} else {
id = id.stringValue();
}
var ids = id.split(/[\x0d\x0a\x09\x20]+/);
var count = 0;
var ns = new XNodeSet();
var doc = c.contextNode.nodeType == 9 /*Node.DOCUMENT_NODE*/
? c.contextNode
: c.contextNode.ownerDocument;
for (var i = 0; i < ids.length; i++) {
var n;
if (doc.getElementById) {
n = doc.getElementById(ids[i]);
} else {
n = Utilities.getElementById(doc, ids[i]);
}
if (n != null) {
ns.add(n);
count++;
}
}
return ns;
};
Functions.localName = function() {
var c = arguments[0];
var n;
if (arguments.length == 1) {
n = c.contextNode;
} else if (arguments.length == 2) {
n = arguments[1].evaluate(c).first();
} else {
throw new Error("Function local-name expects (node-set?)");
}
if (n == null) {
return new XString("");
}
return new XString(n.localName ? n.localName : n.baseName);
};
Functions.namespaceURI = function() {
var c = arguments[0];
var n;
if (arguments.length == 1) {
n = c.contextNode;
} else if (arguments.length == 2) {
n = arguments[1].evaluate(c).first();
} else {
throw new Error("Function namespace-uri expects (node-set?)");
}
if (n == null) {
return new XString("");
}
return new XString(n.namespaceURI);
};
Functions.name = function() {
var c = arguments[0];
var n;
if (arguments.length == 1) {
n = c.contextNode;
} else if (arguments.length == 2) {
n = arguments[1].evaluate(c).first();
} else {
throw new Error("Function name expects (node-set?)");
}
if (n == null) {
return new XString("");
}
if (n.nodeType == 1 /*Node.ELEMENT_NODE*/ || n.nodeType == 2 /*Node.ATTRIBUTE_NODE*/) {
return new XString(n.nodeName);
} else if (n.localName == null) {
return new XString("");
} else {
return new XString(n.localName);
}
};
Functions.string = function() {
var c = arguments[0];
if (arguments.length == 1) {
return XNodeSet.prototype.stringForNode(c.contextNode);
} else if (arguments.length == 2) {
return arguments[1].evaluate(c).string();
}
throw new Error("Function string expects (object?)");
};
Functions.concat = function() {
var c = arguments[0];
if (arguments.length < 3) {
throw new Error("Function concat expects (string, string, string*)");
}
var s = "";
for (var i = 1; i < arguments.length; i++) {
s += arguments[i].evaluate(c).stringValue();
}
return new XString(s);
};
Functions.startsWith = function() {
var c = arguments[0];
if (arguments.length != 3) {
throw new Error("Function startsWith expects (string, string)");
}
var s1 = arguments[1].evaluate(c).stringValue();
var s2 = arguments[2].evaluate(c).stringValue();
return new XBoolean(s1.substring(0, s2.length) == s2);
};
Functions.contains = function() {
var c = arguments[0];
if (arguments.length != 3) {
throw new Error("Function contains expects (string, string)");
}
var s1 = arguments[1].evaluate(c).stringValue();
var s2 = arguments[2].evaluate(c).stringValue();
return new XBoolean(s1.indexOf(s2) != -1);
};
Functions.substringBefore = function() {
var c = arguments[0];
if (arguments.length != 3) {
throw new Error("Function substring-before expects (string, string)");
}
var s1 = arguments[1].evaluate(c).stringValue();
var s2 = arguments[2].evaluate(c).stringValue();
return new XString(s1.substring(0, s1.indexOf(s2)));
};
Functions.substringAfter = function() {
var c = arguments[0];
if (arguments.length != 3) {
throw new Error("Function substring-after expects (string, string)");
}
var s1 = arguments[1].evaluate(c).stringValue();
var s2 = arguments[2].evaluate(c).stringValue();
if (s2.length == 0) {
return new XString(s1);
}
var i = s1.indexOf(s2);
if (i == -1) {
return new XString("");
}
return new XString(s1.substring(s1.indexOf(s2) + 1));
};
Functions.substring = function() {
var c = arguments[0];
if (!(arguments.length == 3 || arguments.length == 4)) {
throw new Error("Function substring expects (string, number, number?)");
}
var s = arguments[1].evaluate(c).stringValue();
var n1 = Math.round(arguments[2].evaluate(c).numberValue()) - 1;
var n2 = arguments.length == 4 ? n1 + Math.round(arguments[3].evaluate(c).numberValue()) : undefined;
return new XString(s.substring(n1, n2));
};
Functions.stringLength = function() {
var c = arguments[0];
var s;
if (arguments.length == 1) {
s = XNodeSet.prototype.stringForNode(c.contextNode);
} else if (arguments.length == 2) {
s = arguments[1].evaluate(c).stringValue();
} else {
throw new Error("Function string-length expects (string?)");
}
return new XNumber(s.length);
};
Functions.normalizeSpace = function() {
var c = arguments[0];
var s;
if (arguments.length == 1) {
s = XNodeSet.prototype.stringForNode(c.contextNode);
} else if (arguments.length == 2) {
s = arguments[1].evaluate(c).stringValue();
} else {
throw new Error("Function normalize-space expects (string?)");
}
var i = 0;
var j = s.length - 1;
while (Utilities.isSpace(s.charCodeAt(j))) {
j--;
}
var t = "";
while (i <= j && Utilities.isSpace(s.charCodeAt(i))) {
i++;
}
while (i <= j) {
if (Utilities.isSpace(s.charCodeAt(i))) {
t += " ";
while (i <= j && Utilities.isSpace(s.charCodeAt(i))) {
i++;
}
} else {
t += s.charAt(i);
i++;
}
}
return new XString(t);
};
Functions.translate = function() {
var c = arguments[0];
if (arguments.length != 4) {
throw new Error("Function translate expects (string, string, string)");
}
var s1 = arguments[1].evaluate(c).stringValue();
var s2 = arguments[2].evaluate(c).stringValue();
var s3 = arguments[3].evaluate(c).stringValue();
var map = [];
for (var i = 0; i < s2.length; i++) {
var j = s2.charCodeAt(i);
if (map[j] == undefined) {
var k = i > s3.length ? "" : s3.charAt(i);
map[j] = k;
}
}
var t = "";
for (var i = 0; i < s1.length; i++) {
var c = s1.charCodeAt(i);
var r = map[c];
if (r == undefined) {
t += s1.charAt(i);
} else {
t += r;
}
}
return new XString(t);
};
Functions.boolean_ = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function boolean expects (object)");
}
return arguments[1].evaluate(c).bool();
};
Functions.not = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function not expects (object)");
}
return arguments[1].evaluate(c).bool().not();
};
Functions.true_ = function() {
if (arguments.length != 1) {
throw new Error("Function true expects ()");
}
return new XBoolean(true);
};
Functions.false_ = function() {
if (arguments.length != 1) {
throw new Error("Function false expects ()");
}
return new XBoolean(false);
};
Functions.lang = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function lang expects (string)");
}
var lang;
for (var n = c.contextNode; n != null && n.nodeType != 9 /*Node.DOCUMENT_NODE*/; n = n.parentNode) {
var a = n.getAttributeNS(XPath.XML_NAMESPACE_URI, "lang");
if (a != null) {
lang = String(a);
break;
}
}
if (lang == null) {
return new XBoolean(false);
}
var s = arguments[1].evaluate(c).stringValue();
return new XBoolean(lang.substring(0, s.length) == s
&& (lang.length == s.length || lang.charAt(s.length) == '-'));
};
Functions.number = function() {
var c = arguments[0];
if (!(arguments.length == 1 || arguments.length == 2)) {
throw new Error("Function number expects (object?)");
}
if (arguments.length == 1) {
return new XNumber(XNodeSet.prototype.stringForNode(c.contextNode));
}
return arguments[1].evaluate(c).number();
};
Functions.sum = function() {
var c = arguments[0];
var ns;
if (arguments.length != 2 || !Utilities.instance_of((ns = arguments[1].evaluate(c)), XNodeSet)) {
throw new Error("Function sum expects (node-set)");
}
ns = ns.toArray();
var n = 0;
for (var i = 0; i < ns.length; i++) {
n += new XNumber(XNodeSet.prototype.stringForNode(ns[i])).numberValue();
}
return new XNumber(n);
};
Functions.floor = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function floor expects (number)");
}
return new XNumber(Math.floor(arguments[1].evaluate(c).numberValue()));
};
Functions.ceiling = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function ceiling expects (number)");
}
return new XNumber(Math.ceil(arguments[1].evaluate(c).numberValue()));
};
Functions.round = function() {
var c = arguments[0];
if (arguments.length != 2) {
throw new Error("Function round expects (number)");
}
return new XNumber(Math.round(arguments[1].evaluate(c).numberValue()));
};
// Utilities /////////////////////////////////////////////////////////////////
Utilities = new Object();
Utilities.splitQName = function(qn) {
var i = qn.indexOf(":");
if (i == -1) {
return [ null, qn ];
}
return [ qn.substring(0, i), qn.substring(i + 1) ];
};
Utilities.resolveQName = function(qn, nr, n, useDefault) {
var parts = Utilities.splitQName(qn);
if (parts[0] != null) {
parts[0] = nr.getNamespace(parts[0], n);
} else {
if (useDefault) {
parts[0] = nr.getNamespace("", n);
if (parts[0] == null) {
parts[0] = "";
}
} else {
parts[0] = "";
}
}
return parts;
};
Utilities.isSpace = function(c) {
return c == 0x9 || c == 0xd || c == 0xa || c == 0x20;
};
Utilities.isLetter = function(c) {
return c >= 0x0041 && c <= 0x005A ||
c >= 0x0061 && c <= 0x007A ||
c >= 0x00C0 && c <= 0x00D6 ||
c >= 0x00D8 && c <= 0x00F6 ||
c >= 0x00F8 && c <= 0x00FF ||
c >= 0x0100 && c <= 0x0131 ||
c >= 0x0134 && c <= 0x013E ||
c >= 0x0141 && c <= 0x0148 ||
c >= 0x014A && c <= 0x017E ||
c >= 0x0180 && c <= 0x01C3 ||
c >= 0x01CD && c <= 0x01F0 ||
c >= 0x01F4 && c <= 0x01F5 ||
c >= 0x01FA && c <= 0x0217 ||
c >= 0x0250 && c <= 0x02A8 ||
c >= 0x02BB && c <= 0x02C1 ||
c == 0x0386 ||
c >= 0x0388 && c <= 0x038A ||
c == 0x038C ||
c >= 0x038E && c <= 0x03A1 ||
c >= 0x03A3 && c <= 0x03CE ||
c >= 0x03D0 && c <= 0x03D6 ||
c == 0x03DA ||
c == 0x03DC ||
c == 0x03DE ||
c == 0x03E0 ||
c >= 0x03E2 && c <= 0x03F3 ||
c >= 0x0401 && c <= 0x040C ||
c >= 0x040E && c <= 0x044F ||
c >= 0x0451 && c <= 0x045C ||
c >= 0x045E && c <= 0x0481 ||
c >= 0x0490 && c <= 0x04C4 ||
c >= 0x04C7 && c <= 0x04C8 ||
c >= 0x04CB && c <= 0x04CC ||
c >= 0x04D0 && c <= 0x04EB ||
c >= 0x04EE && c <= 0x04F5 ||
c >= 0x04F8 && c <= 0x04F9 ||
c >= 0x0531 && c <= 0x0556 ||
c == 0x0559 ||
c >= 0x0561 && c <= 0x0586 ||
c >= 0x05D0 && c <= 0x05EA ||
c >= 0x05F0 && c <= 0x05F2 ||
c >= 0x0621 && c <= 0x063A ||
c >= 0x0641 && c <= 0x064A ||
c >= 0x0671 && c <= 0x06B7 ||
c >= 0x06BA && c <= 0x06BE ||
c >= 0x06C0 && c <= 0x06CE ||
c >= 0x06D0 && c <= 0x06D3 ||
c == 0x06D5 ||
c >= 0x06E5 && c <= 0x06E6 ||
c >= 0x0905 && c <= 0x0939 ||
c == 0x093D ||
c >= 0x0958 && c <= 0x0961 ||
c >= 0x0985 && c <= 0x098C ||
c >= 0x098F && c <= 0x0990 ||
c >= 0x0993 && c <= 0x09A8 ||
c >= 0x09AA && c <= 0x09B0 ||
c == 0x09B2 ||
c >= 0x09B6 && c <= 0x09B9 ||
c >= 0x09DC && c <= 0x09DD ||
c >= 0x09DF && c <= 0x09E1 ||
c >= 0x09F0 && c <= 0x09F1 ||
c >= 0x0A05 && c <= 0x0A0A ||
c >= 0x0A0F && c <= 0x0A10 ||
c >= 0x0A13 && c <= 0x0A28 ||
c >= 0x0A2A && c <= 0x0A30 ||
c >= 0x0A32 && c <= 0x0A33 ||
c >= 0x0A35 && c <= 0x0A36 ||
c >= 0x0A38 && c <= 0x0A39 ||
c >= 0x0A59 && c <= 0x0A5C ||
c == 0x0A5E ||
c >= 0x0A72 && c <= 0x0A74 ||
c >= 0x0A85 && c <= 0x0A8B ||
c == 0x0A8D ||
c >= 0x0A8F && c <= 0x0A91 ||
c >= 0x0A93 && c <= 0x0AA8 ||
c >= 0x0AAA && c <= 0x0AB0 ||
c >= 0x0AB2 && c <= 0x0AB3 ||
c >= 0x0AB5 && c <= 0x0AB9 ||
c == 0x0ABD ||
c == 0x0AE0 ||
c >= 0x0B05 && c <= 0x0B0C ||
c >= 0x0B0F && c <= 0x0B10 ||
c >= 0x0B13 && c <= 0x0B28 ||
c >= 0x0B2A && c <= 0x0B30 ||
c >= 0x0B32 && c <= 0x0B33 ||
c >= 0x0B36 && c <= 0x0B39 ||
c == 0x0B3D ||
c >= 0x0B5C && c <= 0x0B5D ||
c >= 0x0B5F && c <= 0x0B61 ||
c >= 0x0B85 && c <= 0x0B8A ||
c >= 0x0B8E && c <= 0x0B90 ||
c >= 0x0B92 && c <= 0x0B95 ||
c >= 0x0B99 && c <= 0x0B9A ||
c == 0x0B9C ||
c >= 0x0B9E && c <= 0x0B9F ||
c >= 0x0BA3 && c <= 0x0BA4 ||
c >= 0x0BA8 && c <= 0x0BAA ||
c >= 0x0BAE && c <= 0x0BB5 ||
c >= 0x0BB7 && c <= 0x0BB9 ||
c >= 0x0C05 && c <= 0x0C0C ||
c >= 0x0C0E && c <= 0x0C10 ||
c >= 0x0C12 && c <= 0x0C28 ||
c >= 0x0C2A && c <= 0x0C33 ||
c >= 0x0C35 && c <= 0x0C39 ||
c >= 0x0C60 && c <= 0x0C61 ||
c >= 0x0C85 && c <= 0x0C8C ||
c >= 0x0C8E && c <= 0x0C90 ||
c >= 0x0C92 && c <= 0x0CA8 ||
c >= 0x0CAA && c <= 0x0CB3 ||
c >= 0x0CB5 && c <= 0x0CB9 ||
c == 0x0CDE ||
c >= 0x0CE0 && c <= 0x0CE1 ||
c >= 0x0D05 && c <= 0x0D0C ||
c >= 0x0D0E && c <= 0x0D10 ||
c >= 0x0D12 && c <= 0x0D28 ||
c >= 0x0D2A && c <= 0x0D39 ||
c >= 0x0D60 && c <= 0x0D61 ||
c >= 0x0E01 && c <= 0x0E2E ||
c == 0x0E30 ||
c >= 0x0E32 && c <= 0x0E33 ||
c >= 0x0E40 && c <= 0x0E45 ||
c >= 0x0E81 && c <= 0x0E82 ||
c == 0x0E84 ||
c >= 0x0E87 && c <= 0x0E88 ||
c == 0x0E8A ||
c == 0x0E8D ||
c >= 0x0E94 && c <= 0x0E97 ||
c >= 0x0E99 && c <= 0x0E9F ||
c >= 0x0EA1 && c <= 0x0EA3 ||
c == 0x0EA5 ||
c == 0x0EA7 ||
c >= 0x0EAA && c <= 0x0EAB ||
c >= 0x0EAD && c <= 0x0EAE ||
c == 0x0EB0 ||
c >= 0x0EB2 && c <= 0x0EB3 ||
c == 0x0EBD ||
c >= 0x0EC0 && c <= 0x0EC4 ||
c >= 0x0F40 && c <= 0x0F47 ||
c >= 0x0F49 && c <= 0x0F69 ||
c >= 0x10A0 && c <= 0x10C5 ||
c >= 0x10D0 && c <= 0x10F6 ||
c == 0x1100 ||
c >= 0x1102 && c <= 0x1103 ||
c >= 0x1105 && c <= 0x1107 ||
c == 0x1109 ||
c >= 0x110B && c <= 0x110C ||
c >= 0x110E && c <= 0x1112 ||
c == 0x113C ||
c == 0x113E ||
c == 0x1140 ||
c == 0x114C ||
c == 0x114E ||
c == 0x1150 ||
c >= 0x1154 && c <= 0x1155 ||
c == 0x1159 ||
c >= 0x115F && c <= 0x1161 ||
c == 0x1163 ||
c == 0x1165 ||
c == 0x1167 ||
c == 0x1169 ||
c >= 0x116D && c <= 0x116E ||
c >= 0x1172 && c <= 0x1173 ||
c == 0x1175 ||
c == 0x119E ||
c == 0x11A8 ||
c == 0x11AB ||
c >= 0x11AE && c <= 0x11AF ||
c >= 0x11B7 && c <= 0x11B8 ||
c == 0x11BA ||
c >= 0x11BC && c <= 0x11C2 ||
c == 0x11EB ||
c == 0x11F0 ||
c == 0x11F9 ||
c >= 0x1E00 && c <= 0x1E9B ||
c >= 0x1EA0 && c <= 0x1EF9 ||
c >= 0x1F00 && c <= 0x1F15 ||
c >= 0x1F18 && c <= 0x1F1D ||
c >= 0x1F20 && c <= 0x1F45 ||
c >= 0x1F48 && c <= 0x1F4D ||
c >= 0x1F50 && c <= 0x1F57 ||
c == 0x1F59 ||
c == 0x1F5B ||
c == 0x1F5D ||
c >= 0x1F5F && c <= 0x1F7D ||
c >= 0x1F80 && c <= 0x1FB4 ||
c >= 0x1FB6 && c <= 0x1FBC ||
c == 0x1FBE ||
c >= 0x1FC2 && c <= 0x1FC4 ||
c >= 0x1FC6 && c <= 0x1FCC ||
c >= 0x1FD0 && c <= 0x1FD3 ||
c >= 0x1FD6 && c <= 0x1FDB ||
c >= 0x1FE0 && c <= 0x1FEC ||
c >= 0x1FF2 && c <= 0x1FF4 ||
c >= 0x1FF6 && c <= 0x1FFC ||
c == 0x2126 ||
c >= 0x212A && c <= 0x212B ||
c == 0x212E ||
c >= 0x2180 && c <= 0x2182 ||
c >= 0x3041 && c <= 0x3094 ||
c >= 0x30A1 && c <= 0x30FA ||
c >= 0x3105 && c <= 0x312C ||
c >= 0xAC00 && c <= 0xD7A3 ||
c >= 0x4E00 && c <= 0x9FA5 ||
c == 0x3007 ||
c >= 0x3021 && c <= 0x3029;
};
Utilities.isNCNameChar = function(c) {
return c >= 0x0030 && c <= 0x0039
|| c >= 0x0660 && c <= 0x0669
|| c >= 0x06F0 && c <= 0x06F9
|| c >= 0x0966 && c <= 0x096F
|| c >= 0x09E6 && c <= 0x09EF
|| c >= 0x0A66 && c <= 0x0A6F
|| c >= 0x0AE6 && c <= 0x0AEF
|| c >= 0x0B66 && c <= 0x0B6F
|| c >= 0x0BE7 && c <= 0x0BEF
|| c >= 0x0C66 && c <= 0x0C6F
|| c >= 0x0CE6 && c <= 0x0CEF
|| c >= 0x0D66 && c <= 0x0D6F
|| c >= 0x0E50 && c <= 0x0E59
|| c >= 0x0ED0 && c <= 0x0ED9
|| c >= 0x0F20 && c <= 0x0F29
|| c == 0x002E
|| c == 0x002D
|| c == 0x005F
|| Utilities.isLetter(c)
|| c >= 0x0300 && c <= 0x0345
|| c >= 0x0360 && c <= 0x0361
|| c >= 0x0483 && c <= 0x0486
|| c >= 0x0591 && c <= 0x05A1
|| c >= 0x05A3 && c <= 0x05B9
|| c >= 0x05BB && c <= 0x05BD
|| c == 0x05BF
|| c >= 0x05C1 && c <= 0x05C2
|| c == 0x05C4
|| c >= 0x064B && c <= 0x0652
|| c == 0x0670
|| c >= 0x06D6 && c <= 0x06DC
|| c >= 0x06DD && c <= 0x06DF
|| c >= 0x06E0 && c <= 0x06E4
|| c >= 0x06E7 && c <= 0x06E8
|| c >= 0x06EA && c <= 0x06ED
|| c >= 0x0901 && c <= 0x0903
|| c == 0x093C
|| c >= 0x093E && c <= 0x094C
|| c == 0x094D
|| c >= 0x0951 && c <= 0x0954
|| c >= 0x0962 && c <= 0x0963
|| c >= 0x0981 && c <= 0x0983
|| c == 0x09BC
|| c == 0x09BE
|| c == 0x09BF
|| c >= 0x09C0 && c <= 0x09C4
|| c >= 0x09C7 && c <= 0x09C8
|| c >= 0x09CB && c <= 0x09CD
|| c == 0x09D7
|| c >= 0x09E2 && c <= 0x09E3
|| c == 0x0A02
|| c == 0x0A3C
|| c == 0x0A3E
|| c == 0x0A3F
|| c >= 0x0A40 && c <= 0x0A42
|| c >= 0x0A47 && c <= 0x0A48
|| c >= 0x0A4B && c <= 0x0A4D
|| c >= 0x0A70 && c <= 0x0A71
|| c >= 0x0A81 && c <= 0x0A83
|| c == 0x0ABC
|| c >= 0x0ABE && c <= 0x0AC5
|| c >= 0x0AC7 && c <= 0x0AC9
|| c >= 0x0ACB && c <= 0x0ACD
|| c >= 0x0B01 && c <= 0x0B03
|| c == 0x0B3C
|| c >= 0x0B3E && c <= 0x0B43
|| c >= 0x0B47 && c <= 0x0B48
|| c >= 0x0B4B && c <= 0x0B4D
|| c >= 0x0B56 && c <= 0x0B57
|| c >= 0x0B82 && c <= 0x0B83
|| c >= 0x0BBE && c <= 0x0BC2
|| c >= 0x0BC6 && c <= 0x0BC8
|| c >= 0x0BCA && c <= 0x0BCD
|| c == 0x0BD7
|| c >= 0x0C01 && c <= 0x0C03
|| c >= 0x0C3E && c <= 0x0C44
|| c >= 0x0C46 && c <= 0x0C48
|| c >= 0x0C4A && c <= 0x0C4D
|| c >= 0x0C55 && c <= 0x0C56
|| c >= 0x0C82 && c <= 0x0C83
|| c >= 0x0CBE && c <= 0x0CC4
|| c >= 0x0CC6 && c <= 0x0CC8
|| c >= 0x0CCA && c <= 0x0CCD
|| c >= 0x0CD5 && c <= 0x0CD6
|| c >= 0x0D02 && c <= 0x0D03
|| c >= 0x0D3E && c <= 0x0D43
|| c >= 0x0D46 && c <= 0x0D48
|| c >= 0x0D4A && c <= 0x0D4D
|| c == 0x0D57
|| c == 0x0E31
|| c >= 0x0E34 && c <= 0x0E3A
|| c >= 0x0E47 && c <= 0x0E4E
|| c == 0x0EB1
|| c >= 0x0EB4 && c <= 0x0EB9
|| c >= 0x0EBB && c <= 0x0EBC
|| c >= 0x0EC8 && c <= 0x0ECD
|| c >= 0x0F18 && c <= 0x0F19
|| c == 0x0F35
|| c == 0x0F37
|| c == 0x0F39
|| c == 0x0F3E
|| c == 0x0F3F
|| c >= 0x0F71 && c <= 0x0F84
|| c >= 0x0F86 && c <= 0x0F8B
|| c >= 0x0F90 && c <= 0x0F95
|| c == 0x0F97
|| c >= 0x0F99 && c <= 0x0FAD
|| c >= 0x0FB1 && c <= 0x0FB7
|| c == 0x0FB9
|| c >= 0x20D0 && c <= 0x20DC
|| c == 0x20E1
|| c >= 0x302A && c <= 0x302F
|| c == 0x3099
|| c == 0x309A
|| c == 0x00B7
|| c == 0x02D0
|| c == 0x02D1
|| c == 0x0387
|| c == 0x0640
|| c == 0x0E46
|| c == 0x0EC6
|| c == 0x3005
|| c >= 0x3031 && c <= 0x3035
|| c >= 0x309D && c <= 0x309E
|| c >= 0x30FC && c <= 0x30FE;
};
Utilities.coalesceText = function(n) {
for (var m = n.firstChild; m != null; m = m.nextSibling) {
if (m.nodeType == 3 /*Node.TEXT_NODE*/ || m.nodeType == 4 /*Node.CDATA_SECTION_NODE*/) {
var s = m.nodeValue;
var first = m;
m = m.nextSibling;
while (m != null && (m.nodeType == 3 /*Node.TEXT_NODE*/ || m.nodeType == 4 /*Node.CDATA_SECTION_NODE*/)) {
s += m.nodeValue;
var del = m;
m = m.nextSibling;
del.parentNode.removeChild(del);
}
if (first.nodeType == 4 /*Node.CDATA_SECTION_NODE*/) {
var p = first.parentNode;
if (first.nextSibling == null) {
p.removeChild(first);
p.appendChild(p.ownerDocument.createTextNode(s));
} else {
var next = first.nextSibling;
p.removeChild(first);
p.insertBefore(p.ownerDocument.createTextNode(s), next);
}
} else {
first.nodeValue = s;
}
if (m == null) {
break;
}
} else if (m.nodeType == 1 /*Node.ELEMENT_NODE*/) {
Utilities.coalesceText(m);
}
}
};
Utilities.instance_of = function(o, c) {
while (o != null) {
if (o.constructor === c) {
return true;
}
if (o === Object) {
return false;
}
o = o.constructor.superclass;
}
return false;
};
Utilities.getElementById = function(n, id) {
// Note that this does not check the DTD to check for actual
// attributes of type ID, so this may be a bit wrong.
if (n.nodeType == 1 /*Node.ELEMENT_NODE*/) {
if (n.getAttribute("id") == id
|| n.getAttributeNS(null, "id") == id) {
return n;
}
}
for (var m = n.firstChild; m != null; m = m.nextSibling) {
var res = Utilities.getElementById(m, id);
if (res != null) {
return res;
}
}
return null;
};
// XPathException ////////////////////////////////////////////////////////////
XPathException.prototype = {};
XPathException.prototype.constructor = XPathException;
XPathException.superclass = Object.prototype;
function XPathException(c, e) {
this.code = c;
this.exception = e;
}
XPathException.prototype.toString = function() {
var msg = this.exception ? ": " + this.exception.toString() : "";
switch (this.code) {
case XPathException.INVALID_EXPRESSION_ERR:
return "Invalid expression" + msg;
case XPathException.TYPE_ERR:
return "Type error" + msg;
}
};
XPathException.INVALID_EXPRESSION_ERR = 51;
XPathException.TYPE_ERR = 52;
// XPathExpression ///////////////////////////////////////////////////////////
XPathExpression.prototype = {};
XPathExpression.prototype.constructor = XPathExpression;
XPathExpression.superclass = Object.prototype;
function XPathExpression(e, r, p) {
this.xpath = p.parse(e);
this.context = new XPathContext();
this.context.namespaceResolver = new XPathNSResolverWrapper(r);
}
XPathExpression.prototype.evaluate = function(n, t, res) {
this.context.expressionContextNode = n;
var result = this.xpath.evaluate(this.context);
return new XPathResult(result, t);
}
// XPathNSResolverWrapper ////////////////////////////////////////////////////
XPathNSResolverWrapper.prototype = {};
XPathNSResolverWrapper.prototype.constructor = XPathNSResolverWrapper;
XPathNSResolverWrapper.superclass = Object.prototype;
function XPathNSResolverWrapper(r) {
this.xpathNSResolver = r;
}
XPathNSResolverWrapper.prototype.getNamespace = function(prefix, n) {
if (this.xpathNSResolver == null) {
return null;
}
return this.xpathNSResolver.lookupNamespaceURI(prefix);
};
// NodeXPathNSResolver ///////////////////////////////////////////////////////
NodeXPathNSResolver.prototype = {};
NodeXPathNSResolver.prototype.constructor = NodeXPathNSResolver;
NodeXPathNSResolver.superclass = Object.prototype;
function NodeXPathNSResolver(n) {
this.node = n;
this.namespaceResolver = new NamespaceResolver();
}
NodeXPathNSResolver.prototype.lookupNamespaceURI = function(prefix) {
return this.namespaceResolver.getNamespace(prefix, this.node);
};
// XPathResult ///////////////////////////////////////////////////////////////
XPathResult.prototype = {};
XPathResult.prototype.constructor = XPathResult;
XPathResult.superclass = Object.prototype;
function XPathResult(v, t) {
if (t == XPathResult.ANY_TYPE) {
if (v.constructor === XString) {
t = XPathResult.STRING_TYPE;
} else if (v.constructor === XNumber) {
t = XPathResult.NUMBER_TYPE;
} else if (v.constructor === XBoolean) {
t = XPathResult.BOOLEAN_TYPE;
} else if (v.constructor === XNodeSet) {
t = XPathResult.UNORDERED_NODE_ITERATOR_TYPE;
}
}
this.resultType = t;
switch (t) {
case XPathResult.NUMBER_TYPE:
this.numberValue = v.numberValue();
return;
case XPathResult.STRING_TYPE:
this.stringValue = v.stringValue();
return;
case XPathResult.BOOLEAN_TYPE:
this.booleanValue = v.booleanValue();
return;
case XPathResult.ANY_UNORDERED_NODE_TYPE:
case XPathResult.FIRST_UNORDERED_NODE_TYPE:
if (v.constructor === XNodeSet) {
this.singleNodeValue = v.first();
return;
}
break;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
if (v.constructor === XNodeSet) {
this.invalidIteratorState = false;
this.nodes = v.toArray();
this.iteratorIndex = 0;
return;
}
break;
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
if (v.constructor === XNodeSet) {
this.nodes = v.toArray();
this.snapshotLength = this.nodes.length;
return;
}
break;
}
throw new XPathException(XPathException.TYPE_ERR);
};
XPathResult.prototype.iterateNext = function() {
if (this.resultType != XPathResult.UNORDERED_NODE_ITERATOR_TYPE
&& this.resultType != XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
throw new XPathException(XPathException.TYPE_ERR);
}
return this.nodes[this.iteratorIndex++];
};
XPathResult.prototype.snapshotItem = function(i) {
if (this.resultType != XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE
&& this.resultType != XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) {
throw new XPathException(XPathException.TYPE_ERR);
}
return this.nodes[i];
};
XPathResult.ANY_TYPE = 0;
XPathResult.NUMBER_TYPE = 1;
XPathResult.STRING_TYPE = 2;
XPathResult.BOOLEAN_TYPE = 3;
XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6;
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7;
XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
// DOM 3 XPath support ///////////////////////////////////////////////////////
function installDOM3XPathSupport(doc, p) {
doc.createExpression = function(e, r) {
try {
return new XPathExpression(e, r, p);
} catch (e) {
throw new XPathException(XPathException.INVALID_EXPRESSION_ERR, e);
}
};
doc.createNSResolver = function(n) {
return new NodeXPathNSResolver(n);
};
doc.evaluate = function(e, cn, r, t, res) {
if (t < 0 || t > 9) {
throw { code: 0, toString: function() { return "Request type not supported"; } };
}
return doc.createExpression(e, r, p).evaluate(cn, t, res);
};
};
// ---------------------------------------------------------------------------
// Install DOM 3 XPath support for the current document.
try {
var shouldInstall = true;
try {
if (document.implementation
&& document.implementation.hasFeature
&& document.implementation.hasFeature("XPath", null)) {
shouldInstall = false;
}
} catch (e) {
}
if (shouldInstall) {
installDOM3XPathSupport(document, new XPathParser());
}
} catch (e) {
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,211 +0,0 @@
/*
* Copyright 2005 ThoughtWorks, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*---( Layout )---*/
body {
margin: 0;
padding: 0;
overflow: auto;
}
td {
position: static;
}
tr {
vertical-align: top;
}
.layout {
width: 100%;
height: 100%;
border-collapse: collapse;
}
.layout td {
margin: 0;
padding: 0;
border: 0;
}
iframe {
width: 100%;
height: 100%;
border: 0;
background: white;
overflow: auto;
}
/*---( Style )---*/
body, html {
font-family: Verdana, Arial, sans-serif;
}
.selenium th, .selenium td {
border: 1px solid #999;
}
.header {
background: #ccc;
padding: 0;
font-size: 90%;
}
#controlPanel {
padding: 0.5ex;
background: #eee;
overflow: auto;
font-size: 75%;
text-align: center;
}
#controlPanel fieldset {
margin: 0.3ex;
padding: 0.3ex;
}
#controlPanel fieldset legend {
color: black;
}
#controlPanel button {
margin: 0.5ex;
}
#controlPanel table {
font-size: 100%;
}
#controlPanel th, #controlPanel td {
border: 0;
}
h1 {
margin: 0.2ex;
font-size: 130%;
font-weight: bold;
}
h2 {
margin: 0.2ex;
font-size: 80%;
font-weight: normal;
}
.selenium a {
color: black;
text-decoration: none;
}
.selenium a:hover {
text-decoration: underline;
}
button, label {
cursor: pointer;
}
#stats {
margin: 0.5em auto 0.5em auto;
}
#stats th, #stats td {
text-align: left;
padding-left: 2px;
}
#stats th {
text-decoration: underline;
}
#stats td.count {
font-weight: bold;
text-align: right;
}
#testRuns {
color: green;
}
#testFailures {
color: red;
}
#commandPasses {
color: green;
}
#commandFailures {
color: red;
}
#commandErrors {
color: #f90;
}
.splash {
border: 1px solid black;
padding: 20px;
background: #ccc;
}
/*---( Logging Console )---*/
#logging-console {
background: #fff;
font-size: 75%;
}
#logging-console #banner {
display: block;
width: 100%;
position: fixed;
top: 0;
background: #ddd;
border-bottom: 1px solid #666;
}
#logging-console #logLevelChooser {
float: right;
margin: 3px;
}
#logging-console ul {
list-style-type: none;
margin: 0px;
margin-top: 3em;
padding-left: 5px;
}
#logging-console li {
margin: 2px;
border-top: 1px solid #ccc;
}
#logging-console li.error {
font-weight: bold;
color: red;
}
#logging-console li.warn {
color: red;
}
#logging-console li.debug {
color: green;
}

View File

@ -1,312 +0,0 @@
Simple Test interface changes
=============================
Because the SimpleTest tool set is still evolving it is likely that tests
written with earlier versions will fail with the newest ones. The most
dramatic changes are in the alpha releases. Here is a list of possible
problems and their fixes...
Failure to connect now emits failures
-------------------------------------
It used to be that you would have to use the
getTransferError() call on the web tester to see if
there was a socket level error in a fetch. This check
is now always carried out by the WebTestCase unless
the fetch is prefaced with WebTestCase::ignoreErrors().
The ignore directive only lasts for test case fetching
action such as get() and click().
No method SimpleTestOptions::ignore()
-------------------------------------
This is deprecated in version 1.0.1beta and has been moved
to SimpleTest::ignore() as that is more readable. In
addition, parent classes are also ignored automatically.
If you are using PHP5 you can skip this directive simply
by marking your test case as abstract.
No method assertCopy()
----------------------
This is deprecated in 1.0.1 in favour of assertClone().
The assertClone() method is slightly different in that
the objects must be identical, but without being a
reference. It is thus not a strict inversion of
assertReference().
Constructor wildcard override has no effect in mocks
----------------------------------------------------
As of 1.0.1beta this is now set with setWildcard() instead
of in the constructor.
No methods setStubBaseClass()/getStubBaseClass()
------------------------------------------------
As mocks are now used instead of stubs, these methods
stopped working and are now removed as of the 1.0.1beta
release. The mock objects may be freely used instead.
No method addPartialMockCode()
------------------------------
The ability to insert arbitrary partial mock code
has been removed. This was a low value feature
causing needless complications.It was removed
in the 1.0.1beta release.
No method setMockBaseClass()
----------------------------
The ability to change the mock base class has been
scheduled for removal and is deprecated since the
1.0.1beta version. This was a rarely used feature
except as a workaround for PHP5 limitations. As
these limitations are being resolved it's hoped
that the bundled mocks can be used directly.
No class Stub
-------------
Server stubs are deprecated from 1.0.1 as the mocks now
have exactly the same interface. Just use mock objects
instead.
No class SimpleTestOptions
--------------------------
This was replced by the shorter SimpleTest in 1.0.1beta1
and is since deprecated.
No file simple_test.php
-----------------------
This was renamed test_case.php in 1.0.1beta to more accurately
reflect it's purpose. This file should never be directly
included in test suites though, as it's part of the
underlying mechanics and has a tendency to be refactored.
No class WantedPatternExpectation
---------------------------------
This was deprecated in 1.0.1alpha in favour of the simpler
name PatternExpectation.
No class NoUnwantedPatternExpectation
-------------------------------------
This was deprecated in 1.0.1alpha in favour of the simpler
name NoPatternExpectation.
No method assertNoUnwantedPattern()
-----------------------------------
This has been renamed to assertNoPattern() in 1.0.1alpha and
the old form is deprecated.
No method assertWantedPattern()
-------------------------------
This has been renamed to assertPattern() in 1.0.1alpha and
the old form is deprecated.
No method assertExpectation()
-----------------------------
This was renamed as assert() in 1.0.1alpha and the old form
has been deprecated.
No class WildcardExpectation
----------------------------
This was a mostly internal class for the mock objects. It was
renamed AnythingExpectation to bring it closer to JMock and
NMock in version 1.0.1alpha.
Missing UnitTestCase::assertErrorPattern()
------------------------------------------
This method is deprecated for version 1.0.1 onwards.
This method has been subsumed by assertError() that can now
take an expectation. Simply pass a PatternExpectation
into assertError() to simulate the old behaviour.
No HTML when matching page elements
-----------------------------------
This behaviour has been switched to using plain text as if it
were seen by the user of the browser. This means that HTML tags
are suppressed, entities are converted and whitespace is
normalised. This should make it easier to match items in forms.
Also images are replaced with their "alt" text so that they
can be matched as well.
No method SimpleRunner::_getTestCase()
--------------------------------------
This was made public as getTestCase() in 1.0RC2.
No method restartSession()
--------------------------
This was renamed to restart() in the WebTestCase, SimpleBrowser
and the underlying SimpleUserAgent in 1.0RC2. Because it was
undocumented anyway, no attempt was made at backward
compatibility.
My custom test case ignored by tally()
--------------------------------------
The _assertTrue method has had it's signature changed due to a bug
in the PHP 5.0.1 release. You must now use getTest() from within
that method to get the test case. Mock compatibility with other
unit testers is now deprecated as of 1.0.1alpha as PEAR::PHUnit2
should soon have mock support of it's own.
Broken code extending SimpleRunner
----------------------------------
This was replaced with SimpleScorer so that I could use the runner
name in another class. This happened in RC1 development and there
is no easy backward compatibility fix. The solution is simply to
extend SimpleScorer instead.
Missing method getBaseCookieValue()
-----------------------------------
This was renamed getCurrentCookieValue() in RC1.
Missing files from the SimpleTest suite
---------------------------------------
Versions of SimpleTest prior to Beta6 required a SIMPLE_TEST constant
to point at the SimpleTest folder location before any of the toolset
was loaded. This is no longer documented as it is now unnecessary
for later versions. If you are using an earlier version you may
need this constant. Consult the documentation that was bundled with
the release that you are using or upgrade to Beta6 or later.
No method SimpleBrowser::getCurrentUrl()
--------------------------------------
This is replaced with the more versatile showRequest() for
debugging. It only existed in this context for version Beta5.
Later versions will have SimpleBrowser::getHistory() for tracking
paths through pages. It is renamed as getUrl() since 1.0RC1.
No method Stub::setStubBaseClass()
----------------------------------
This method has finally been removed in 1.0RC1. Use
SimpleTestOptions::setStubBaseClass() instead.
No class CommandLineReporter
----------------------------
This was renamed to TextReporter in Beta3 and the deprecated version
was removed in 1.0RC1.
No method requireReturn()
-------------------------
This was deprecated in Beta3 and is now removed.
No method expectCookie()
------------------------
This method was abruptly removed in Beta4 so as to simplify the internals
until another mechanism can replace it. As a workaround it is necessary
to assert that the cookie has changed by setting it before the page
fetch and then assert the desired value.
No method clickSubmitByFormId()
-------------------------------
This method had an incorrect name as no button was involved. It was
renamed to submitByFormId() in Beta4 and the old version deprecated.
Now removed.
No method paintStart() or paintEnd()
------------------------------------
You should only get this error if you have subclassed the lower level
reporting and test runner machinery. These methods have been broken
down into events for test methods, events for test cases and events
for group tests. The new methods are...
paintStart() --> paintMethodStart(), paintCaseStart(), paintGroupStart()
paintEnd() --> paintMethodEnd(), paintCaseEnd(), paintGroupEnd()
This change was made in Beta3, ironically to make it easier to subclass
the inner machinery. Simply duplicating the code you had in the previous
methods should provide a temporary fix.
No class TestDisplay
--------------------
This has been folded into SimpleReporter in Beta3 and is now deprecated.
It was removed in RC1.
No method WebTestCase::fetch()
------------------------------
This was renamed get() in Alpha8. It is removed in Beta3.
No method submit()
------------------
This has been renamed clickSubmit() in Beta1. The old method was
removed in Beta2.
No method clearHistory()
------------------------
This method is deprecated in Beta2 and removed in RC1.
No method getCallCount()
------------------------
This method has been deprecated since Beta1 and has now been
removed. There are now more ways to set expectations on counts
and so this method should be unecessery. Removed in RC1.
Cannot find file *
------------------
The following public name changes have occoured...
simple_html_test.php --> reporter.php
simple_mock.php --> mock_objects.php
simple_unit.php --> unit_tester.php
simple_web.php --> web_tester.php
The old names were deprecated in Alpha8 and removed in Beta1.
No method attachObserver()
--------------------------
Prior to the Alpha8 release the old internal observer pattern was
gutted and replaced with a visitor. This is to trade flexibility of
test case expansion against the ease of writing user interfaces.
Code such as...
$test = &new MyTestCase();
$test->attachObserver(new TestHtmlDisplay());
$test->run();
...should be rewritten as...
$test = &new MyTestCase();
$test->run(new HtmlReporter());
If you previously attached multiple observers then the workaround
is to run the tests twice, once with each, until they can be combined.
For one observer the old method is simulated in Alpha 8, but is
removed in Beta1.
No class TestHtmlDisplay
------------------------
This class has been renamed to HtmlReporter in Alpha8. It is supported,
but deprecated in Beta1 and removed in Beta2. If you have subclassed
the display for your own design, then you will have to extend this
class (HtmlReporter) instead.
If you have accessed the event queue by overriding the notify() method
then I am afraid you are in big trouble :(. The reporter is now
carried around the test suite by the runner classes and the methods
called directly. In the unlikely event that this is a problem and
you don't want to upgrade the test tool then simplest is to write your
own runner class and invoke the tests with...
$test->accept(new MyRunner(new MyReporter()));
...rather than the run method. This should be easier to extend
anyway and gives much more control. Even this method is overhauled
in Beta3 where the runner class can be set within the test case. Really
the best thing to do is to upgrade to this version as whatever you were
trying to achieve before should now be very much easier.
Missing set options method
--------------------------
All test suite options are now in one class called SimpleTestOptions.
This means that options are set differently...
GroupTest::ignore() --> SimpleTestOptions::ignore()
Mock::setMockBaseClass() --> SimpleTestOptions::setMockBaseClass()
These changed in Alpha8 and the old versions are now removed in RC1.
No method setExpected*()
------------------------
The mock expectations changed their names in Alpha4 and the old names
ceased to be supported in Alpha8. The changes are...
setExpectedArguments() --> expectArguments()
setExpectedArgumentsSequence() --> expectArgumentsAt()
setExpectedCallCount() --> expectCallCount()
setMaximumCallCount() --> expectMaximumCallCount()
The parameters remained the same.

View File

@ -1,502 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -1,108 +0,0 @@
SimpleTest
==========
You probably got this package from...
http://sourceforge.net/projects/simpletest/
If there is no licence agreement with this package please download
a version from the location above. You must read and accept that
licence to use this software. The file is titled simply LICENSE.
What is it? It's a framework for unit testing, web site testing and
mock objects for PHP 4.2.0+.
If you have used JUnit you will find this PHP unit testing version very
similar. Also included is a mock objects and server stubs generator.
The stubs can have return values set for different arguments, can have
sequences set also by arguments and can return items by reference.
The mocks inherit all of this functionality and can also have
expectations set, again in sequences and for different arguments.
A web tester similar in concept to JWebUnit is also included. There is no
JavaScript or tables support, but forms, authentication, cookies and
frames are handled.
You can see a release schedule at http://www.lastcraft.com/overview.php
which is also copied to the documentation folder with this release.
A full PHPDocumenter API documentation exists at
http://simpletest.sourceforge.net/.
The user interface is minimal
in the extreme, but a lot of information flows from the test suite.
After version 1.0 we will release a better web UI, but we are leaving XUL
and GTk versions to volunteers as everybody has their own opinion
on a good GUI, and we don't want to discourage development by shipping
one with the toolkit.
You are looking at a first full release. The unit tests for SimpleTest
itself can be run here...
simpletest/test/unit_tests.php
And tests involving live network connections as well are here...
simpletest/test/all_tests.php
The full tests will typically overrun the 8Mb limit usually allowed
to a PHP process. A workaround is to run the tests on the command
with a custom php.ini file if you do not have access to your server
version.
You will have to edit the all_tests.php file if you are accesssing
the internet through a proxy server. See the comments in all_tests.php
for instructions.
The full tests read some test data from the LastCraft site. If the site
is down or has been modified for a later version then you will get
spurious errors. A unit_tests.php failure on the other hand would be
very serious. As far as we know we haven't yet managed to check in any
unit test failures so please correct us if you find one.
Even if all of the tests run please verify that your existing test suites
also function as expected. If they don't see the file...
HELP_MY_TESTS_DONT_WORK_ANYMORE
This contains information on interface changes. It also points out
deprecated interfaces so you should read this even if all of
your current tests appear to run.
There is a documentation folder which contains the core reference information
in English and French, although this information is fairly basic.
You can find a tutorial on...
http://www.lastcraft.com/first_test_tutorial.php
...to get you started and this material will eventually become included
with the project documentation. A French translation exists at...
http://www.onpk.net/index.php/2005/01/12/254-tutoriel-simpletest-decouvrir-les-tests-unitaires.
If you download and use and possibly even extend this tool, please let us
know. Any feedback, even bad, is always welcome and we will work to get
your suggestions into the next release. Ideally please send your
comments to...
simpletest-support@lists.sourceforge.net
...so that others can read them too. We usually try to respond within 48
hours.
There is no change log as yet except at Sourceforge. You can visit the
release notes to see the completed TODO list after each cycle and also the
status of any bugs, but if the bug is recent then it will be fixed in CVS only.
The CVS check-ins always have all the tests passing and so CVS snapshots should
be pretty usable, although the code may not look so good internally.
Oh, yes. It is called "Simple" because it should be simple to
use. We intend to add a complete set of tools for a test first
and "test as you code" type of development. "Simple" does not
mean "Lite" in this context.
Thanks to everyone who has sent comments and offered suggestions. They
really are invaluable, but sadly you are too many to mention in full.
Thanks to all on the advanced PHP forum on SitePoint, especially Harry
Feucks. Early adopters are always an inspiration.
yours Marcus Baker, Jason Sweat, Travis Swicegood and Perrick Penet.
--
marcus@lastcraft.com

View File

@ -1,5 +0,0 @@
<?php
require_once('unit_tester.php');
?>

View File

@ -1,33 +0,0 @@
<?php
require_once('unit_tester.php');
require_once('reporter.php');
/**
* UnitTesting is a wrapper around the "Simple Test" unit testing framework.
*/
class UnitTesting extends Controller {
function index() {
$test = &new GroupTest("SilverStripe Unit-testing Results for '" . project() . "'");
$builtins = array('UnitTestCase', 'ShellTestCase', 'WebTestCase','SimpleTestCase');
foreach(ClassInfo::subclassesFor("SimpleTestCase") as $testCase) {
if(!in_array($testCase, $builtins)) {
$test->addTestCase($cases[] = new $testCase());
}
}
$test->run(new HtmlReporter());
echo "<h2>What's being tested?</h2>";
foreach($cases as $testCase) {
echo "<li style=\"color: " . ($testCase->testComplete?$testCase->testComplete:'orange') . "\"><b>" . get_class($testCase) . ":</b> ";
echo ($testCase->whatsBeingTested?$testCase->whatsBeingTested:'unknown');
echo "</li>";
}
}
}
?>

View File

@ -1 +0,0 @@
1.0.1alpha3

View File

@ -1,238 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**
* include http class
*/
require_once(dirname(__FILE__) . '/http.php');
/**
* Represents a single security realm's identity.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleRealm {
var $_type;
var $_root;
var $_username;
var $_password;
/**
* Starts with the initial entry directory.
* @param string $type Authentication type for this
* realm. Only Basic authentication
* is currently supported.
* @param SimpleUrl $url Somewhere in realm.
* @access public
*/
function SimpleRealm($type, $url) {
$this->_type = $type;
$this->_root = $url->getBasePath();
$this->_username = false;
$this->_password = false;
}
/**
* Adds another location to the realm.
* @param SimpleUrl $url Somewhere in realm.
* @access public
*/
function stretch($url) {
$this->_root = $this->_getCommonPath($this->_root, $url->getPath());
}
/**
* Finds the common starting path.
* @param string $first Path to compare.
* @param string $second Path to compare.
* @return string Common directories.
* @access private
*/
function _getCommonPath($first, $second) {
$first = explode('/', $first);
$second = explode('/', $second);
for ($i = 0; $i < min(count($first), count($second)); $i++) {
if ($first[$i] != $second[$i]) {
return implode('/', array_slice($first, 0, $i)) . '/';
}
}
return implode('/', $first) . '/';
}
/**
* Sets the identity to try within this realm.
* @param string $username Username in authentication dialog.
* @param string $username Password in authentication dialog.
* @access public
*/
function setIdentity($username, $password) {
$this->_username = $username;
$this->_password = $password;
}
/**
* Accessor for current identity.
* @return string Last succesful username.
* @access public
*/
function getUsername() {
return $this->_username;
}
/**
* Accessor for current identity.
* @return string Last succesful password.
* @access public
*/
function getPassword() {
return $this->_password;
}
/**
* Test to see if the URL is within the directory
* tree of the realm.
* @param SimpleUrl $url URL to test.
* @return boolean True if subpath.
* @access public
*/
function isWithin($url) {
if ($this->_isIn($this->_root, $url->getBasePath())) {
return true;
}
if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
return true;
}
return false;
}
/**
* Tests to see if one string is a substring of
* another.
* @param string $part Small bit.
* @param string $whole Big bit.
* @return boolean True if the small bit is
* in the big bit.
* @access private
*/
function _isIn($part, $whole) {
return strpos($whole, $part) === 0;
}
}
/**
* Manages security realms.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleAuthenticator {
var $_realms;
/**
* Clears the realms.
* @access public
*/
function SimpleAuthenticator() {
$this->restartSession();
}
/**
* Starts with no realms set up.
* @access public
*/
function restartSession() {
$this->_realms = array();
}
/**
* Adds a new realm centered the current URL.
* Browsers vary wildly on their behaviour in this
* regard. Mozilla ignores the realm and presents
* only when challenged, wasting bandwidth. IE
* just carries on presenting until a new challenge
* occours. SimpleTest tries to follow the spirit of
* the original standards committee and treats the
* base URL as the root of a file tree shaped realm.
* @param SimpleUrl $url Base of realm.
* @param string $type Authentication type for this
* realm. Only Basic authentication
* is currently supported.
* @param string $realm Name of realm.
* @access public
*/
function addRealm($url, $type, $realm) {
$this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
}
/**
* Sets the current identity to be presented
* against that realm.
* @param string $host Server hosting realm.
* @param string $realm Name of realm.
* @param string $username Username for realm.
* @param string $password Password for realm.
* @access public
*/
function setIdentityForRealm($host, $realm, $username, $password) {
if (isset($this->_realms[$host][$realm])) {
$this->_realms[$host][$realm]->setIdentity($username, $password);
}
}
/**
* Finds the name of the realm by comparing URLs.
* @param SimpleUrl $url URL to test.
* @return SimpleRealm Name of realm.
* @access private
*/
function _findRealmFromUrl($url) {
if (! isset($this->_realms[$url->getHost()])) {
return false;
}
foreach ($this->_realms[$url->getHost()] as $name => $realm) {
if ($realm->isWithin($url)) {
return $realm;
}
}
return false;
}
/**
* Presents the appropriate headers for this location.
* @param SimpleHttpRequest $request Request to modify.
* @param SimpleUrl $url Base of realm.
* @access public
*/
function addHeaders(&$request, $url) {
if ($url->getUsername() && $url->getPassword()) {
$username = $url->getUsername();
$password = $url->getPassword();
} elseif ($realm = $this->_findRealmFromUrl($url)) {
$username = $realm->getUsername();
$password = $realm->getPassword();
} else {
return;
}
$this->addBasicHeaders($request, $username, $password);
}
/**
* Presents the appropriate headers for this
* location for basic authentication.
* @param SimpleHttpRequest $request Request to modify.
* @param string $username Username for realm.
* @param string $password Password for realm.
* @access public
* @static
*/
function addBasicHeaders(&$request, $username, $password) {
if ($username && $password) {
$request->addHeaderLine(
'Authorization: Basic ' . base64_encode("$username:$password"));
}
}
}
?>

View File

@ -1,1057 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/simpletest.php');
require_once(dirname(__FILE__) . '/http.php');
require_once(dirname(__FILE__) . '/encoding.php');
require_once(dirname(__FILE__) . '/page.php');
require_once(dirname(__FILE__) . '/selector.php');
require_once(dirname(__FILE__) . '/frames.php');
require_once(dirname(__FILE__) . '/user_agent.php');
/**#@-*/
if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
define('DEFAULT_MAX_NESTED_FRAMES', 3);
}
/**
* Browser history list.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleBrowserHistory {
var $_sequence;
var $_position;
/**
* Starts empty.
* @access public
*/
function SimpleBrowserHistory() {
$this->_sequence = array();
$this->_position = -1;
}
/**
* Test for no entries yet.
* @return boolean True if empty.
* @access private
*/
function _isEmpty() {
return ($this->_position == -1);
}
/**
* Test for being at the beginning.
* @return boolean True if first.
* @access private
*/
function _atBeginning() {
return ($this->_position == 0) && ! $this->_isEmpty();
}
/**
* Test for being at the last entry.
* @return boolean True if last.
* @access private
*/
function _atEnd() {
return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty();
}
/**
* Adds a successfully fetched page to the history.
* @param SimpleUrl $url URL of fetch.
* @param SimpleEncoding $parameters Any post data with the fetch.
* @access public
*/
function recordEntry($url, $parameters) {
$this->_dropFuture();
array_push(
$this->_sequence,
array('url' => $url, 'parameters' => $parameters));
$this->_position++;
}
/**
* Last fully qualified URL for current history
* position.
* @return SimpleUrl URL for this position.
* @access public
*/
function getUrl() {
if ($this->_isEmpty()) {
return false;
}
return $this->_sequence[$this->_position]['url'];
}
/**
* Parameters of last fetch from current history
* position.
* @return SimpleFormEncoding Post parameters.
* @access public
*/
function getParameters() {
if ($this->_isEmpty()) {
return false;
}
return $this->_sequence[$this->_position]['parameters'];
}
/**
* Step back one place in the history. Stops at
* the first page.
* @return boolean True if any previous entries.
* @access public
*/
function back() {
if ($this->_isEmpty() || $this->_atBeginning()) {
return false;
}
$this->_position--;
return true;
}
/**
* Step forward one place. If already at the
* latest entry then nothing will happen.
* @return boolean True if any future entries.
* @access public
*/
function forward() {
if ($this->_isEmpty() || $this->_atEnd()) {
return false;
}
$this->_position++;
return true;
}
/**
* Ditches all future entries beyond the current
* point.
* @access private
*/
function _dropFuture() {
if ($this->_isEmpty()) {
return;
}
while (! $this->_atEnd()) {
array_pop($this->_sequence);
}
}
}
/**
* Simulated web browser. This is an aggregate of
* the user agent, the HTML parsing, request history
* and the last header set.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleBrowser {
var $_user_agent;
var $_page;
var $_history;
var $_ignore_frames;
var $_maximum_nested_frames;
/**
* Starts with a fresh browser with no
* cookie or any other state information. The
* exception is that a default proxy will be
* set up if specified in the options.
* @access public
*/
function SimpleBrowser() {
$this->_user_agent = &$this->_createUserAgent();
$this->_user_agent->useProxy(
SimpleTest::getDefaultProxy(),
SimpleTest::getDefaultProxyUsername(),
SimpleTest::getDefaultProxyPassword());
$this->_page = &new SimplePage();
$this->_history = &$this->_createHistory();
$this->_ignore_frames = false;
$this->_maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES;
}
/**
* Creates the underlying user agent.
* @return SimpleFetcher Content fetcher.
* @access protected
*/
function &_createUserAgent() {
$user_agent = &new SimpleUserAgent();
return $user_agent;
}
/**
* Creates a new empty history list.
* @return SimpleBrowserHistory New list.
* @access protected
*/
function &_createHistory() {
$history = &new SimpleBrowserHistory();
return $history;
}
/**
* Disables frames support. Frames will not be fetched
* and the frameset page will be used instead.
* @access public
*/
function ignoreFrames() {
$this->_ignore_frames = true;
}
/**
* Enables frames support. Frames will be fetched from
* now on.
* @access public
*/
function useFrames() {
$this->_ignore_frames = false;
}
/**
* Switches off cookie sending and recieving.
* @access public
*/
function ignoreCookies() {
$this->_user_agent->ignoreCookies();
}
/**
* Switches back on the cookie sending and recieving.
* @access public
*/
function useCookies() {
$this->_user_agent->useCookies();
}
/**
* Parses the raw content into a page. Will load further
* frame pages unless frames are disabled.
* @param SimpleHttpResponse $response Response from fetch.
* @param integer $depth Nested frameset depth.
* @return SimplePage Parsed HTML.
* @access private
*/
function &_parse($response, $depth = 0) {
$page = &$this->_buildPage($response);
if ($this->_ignore_frames || ! $page->hasFrames() || ($depth > $this->_maximum_nested_frames)) {
return $page;
}
$frameset = &new SimpleFrameset($page);
foreach ($page->getFrameset() as $key => $url) {
$frame = &$this->_fetch($url, new SimpleGetEncoding(), $depth + 1);
$frameset->addFrame($frame, $key);
}
return $frameset;
}
/**
* Assembles the parsing machinery and actually parses
* a single page. Frees all of the builder memory and so
* unjams the PHP memory management.
* @param SimpleHttpResponse $response Response from fetch.
* @return SimplePage Parsed top level page.
* @access protected
*/
function &_buildPage($response) {
$builder = &new SimplePageBuilder();
$page = &$builder->parse($response);
$builder->free();
unset($builder);
return $page;
}
/**
* Fetches a page. Jointly recursive with the _parse()
* method as it descends a frameset.
* @param string/SimpleUrl $url Target to fetch.
* @param SimpleEncoding $encoding GET/POST parameters.
* @param integer $depth Nested frameset depth protection.
* @return SimplePage Parsed page.
* @access private
*/
function &_fetch($url, $encoding, $depth = 0) {
$response = &$this->_user_agent->fetchResponse($url, $encoding);
if ($response->isError()) {
$page = &new SimplePage($response);
} else {
$page = &$this->_parse($response, $depth);
}
return $page;
}
/**
* Fetches a page or a single frame if that is the current
* focus.
* @param SimpleUrl $url Target to fetch.
* @param SimpleEncoding $parameters GET/POST parameters.
* @return string Raw content of page.
* @access private
*/
function _load($url, $parameters) {
$frame = $url->getTarget();
if (! $frame || ! $this->_page->hasFrames() || (strtolower($frame) == '_top')) {
return $this->_loadPage($url, $parameters);
}
return $this->_loadFrame(array($frame), $url, $parameters);
}
/**
* Fetches a page and makes it the current page/frame.
* @param string/SimpleUrl $url Target to fetch as string.
* @param SimplePostEncoding $parameters POST parameters.
* @return string Raw content of page.
* @access private
*/
function _loadPage($url, $parameters) {
$this->_page = &$this->_fetch($url, $parameters);
$this->_history->recordEntry(
$this->_page->getUrl(),
$this->_page->getRequestData());
return $this->_page->getRaw();
}
/**
* Fetches a frame into the existing frameset replacing the
* original.
* @param array $frames List of names to drill down.
* @param string/SimpleUrl $url Target to fetch as string.
* @param SimpleFormEncoding $parameters POST parameters.
* @return string Raw content of page.
* @access private
*/
function _loadFrame($frames, $url, $parameters) {
$page = &$this->_fetch($url, $parameters);
$this->_page->setFrame($frames, $page);
}
/**
* Removes expired and temporary cookies as if
* the browser was closed and re-opened.
* @param string/integer $date Time when session restarted.
* If omitted then all persistent
* cookies are kept.
* @access public
*/
function restart($date = false) {
$this->_user_agent->restart($date);
}
/**
* Adds a header to every fetch.
* @param string $header Header line to add to every
* request until cleared.
* @access public
*/
function addHeader($header) {
$this->_user_agent->addHeader($header);
}
/**
* Ages the cookies by the specified time.
* @param integer $interval Amount in seconds.
* @access public
*/
function ageCookies($interval) {
$this->_user_agent->ageCookies($interval);
}
/**
* Sets an additional cookie. If a cookie has
* the same name and path it is replaced.
* @param string $name Cookie key.
* @param string $value Value of cookie.
* @param string $host Host upon which the cookie is valid.
* @param string $path Cookie path if not host wide.
* @param string $expiry Expiry date.
* @access public
*/
function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
$this->_user_agent->setCookie($name, $value, $host, $path, $expiry);
}
/**
* Reads the most specific cookie value from the
* browser cookies.
* @param string $host Host to search.
* @param string $path Applicable path.
* @param string $name Name of cookie to read.
* @return string False if not present, else the
* value as a string.
* @access public
*/
function getCookieValue($host, $path, $name) {
return $this->_user_agent->getCookieValue($host, $path, $name);
}
/**
* Reads the current cookies for the current URL.
* @param string $name Key of cookie to find.
* @return string Null if there is no current URL, false
* if the cookie is not set.
* @access public
*/
function getCurrentCookieValue($name) {
return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl());
}
/**
* Sets the maximum number of redirects before
* a page will be loaded anyway.
* @param integer $max Most hops allowed.
* @access public
*/
function setMaximumRedirects($max) {
$this->_user_agent->setMaximumRedirects($max);
}
/**
* Sets the maximum number of nesting of framed pages
* within a framed page to prevent loops.
* @param integer $max Highest depth allowed.
* @access public
*/
function setMaximumNestedFrames($max) {
$this->_maximum_nested_frames = $max;
}
/**
* Sets the socket timeout for opening a connection.
* @param integer $timeout Maximum time in seconds.
* @access public
*/
function setConnectionTimeout($timeout) {
$this->_user_agent->setConnectionTimeout($timeout);
}
/**
* Sets proxy to use on all requests for when
* testing from behind a firewall. Set URL
* to false to disable.
* @param string $proxy Proxy URL.
* @param string $username Proxy username for authentication.
* @param string $password Proxy password for authentication.
* @access public
*/
function useProxy($proxy, $username = false, $password = false) {
$this->_user_agent->useProxy($proxy, $username, $password);
}
/**
* Fetches the page content with a HEAD request.
* Will affect cookies, but will not change the base URL.
* @param string/SimpleUrl $url Target to fetch as string.
* @param hash/SimpleHeadEncoding $parameters Additional parameters for
* HEAD request.
* @return boolean True if successful.
* @access public
*/
function head($url, $parameters = false) {
if (! is_object($url)) {
$url = new SimpleUrl($url);
}
if ($this->getUrl()) {
$url = $url->makeAbsolute($this->getUrl());
}
$response = &$this->_user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
return ! $response->isError();
}
/**
* Fetches the page content with a simple GET request.
* @param string/SimpleUrl $url Target to fetch.
* @param hash/SimpleFormEncoding $parameters Additional parameters for
* GET request.
* @return string Content of page or false.
* @access public
*/
function get($url, $parameters = false) {
if (! is_object($url)) {
$url = new SimpleUrl($url);
}
if ($this->getUrl()) {
$url = $url->makeAbsolute($this->getUrl());
}
return $this->_load($url, new SimpleGetEncoding($parameters));
}
/**
* Fetches the page content with a POST request.
* @param string/SimpleUrl $url Target to fetch as string.
* @param hash/SimpleFormEncoding $parameters POST parameters.
* @return string Content of page.
* @access public
*/
function post($url, $parameters = false) {
if (! is_object($url)) {
$url = new SimpleUrl($url);
}
if ($this->getUrl()) {
$url = $url->makeAbsolute($this->getUrl());
}
return $this->_load($url, new SimplePostEncoding($parameters));
}
/**
* Equivalent to hitting the retry button on the
* browser. Will attempt to repeat the page fetch. If
* there is no history to repeat it will give false.
* @return string/boolean Content if fetch succeeded
* else false.
* @access public
*/
function retry() {
$frames = $this->_page->getFrameFocus();
if (count($frames) > 0) {
$this->_loadFrame(
$frames,
$this->_page->getUrl(),
$this->_page->getRequestData());
return $this->_page->getRaw();
}
if ($url = $this->_history->getUrl()) {
$this->_page = &$this->_fetch($url, $this->_history->getParameters());
return $this->_page->getRaw();
}
return false;
}
/**
* Equivalent to hitting the back button on the
* browser. The browser history is unchanged on
* failure. The page content is refetched as there
* is no concept of content caching in SimpleTest.
* @return boolean True if history entry and
* fetch succeeded
* @access public
*/
function back() {
if (! $this->_history->back()) {
return false;
}
$content = $this->retry();
if (! $content) {
$this->_history->forward();
}
return $content;
}
/**
* Equivalent to hitting the forward button on the
* browser. The browser history is unchanged on
* failure. The page content is refetched as there
* is no concept of content caching in SimpleTest.
* @return boolean True if history entry and
* fetch succeeded
* @access public
*/
function forward() {
if (! $this->_history->forward()) {
return false;
}
$content = $this->retry();
if (! $content) {
$this->_history->back();
}
return $content;
}
/**
* Retries a request after setting the authentication
* for the current realm.
* @param string $username Username for realm.
* @param string $password Password for realm.
* @return boolean True if successful fetch. Note
* that authentication may still have
* failed.
* @access public
*/
function authenticate($username, $password) {
if (! $this->_page->getRealm()) {
return false;
}
$url = $this->_page->getUrl();
if (! $url) {
return false;
}
$this->_user_agent->setIdentity(
$url->getHost(),
$this->_page->getRealm(),
$username,
$password);
return $this->retry();
}
/**
* Accessor for a breakdown of the frameset.
* @return array Hash tree of frames by name
* or index if no name.
* @access public
*/
function getFrames() {
return $this->_page->getFrames();
}
/**
* Accessor for current frame focus. Will be
* false if no frame has focus.
* @return integer/string/boolean Label if any, otherwise
* the position in the frameset
* or false if none.
* @access public
*/
function getFrameFocus() {
return $this->_page->getFrameFocus();
}
/**
* Sets the focus by index. The integer index starts from 1.
* @param integer $choice Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocusByIndex($choice) {
return $this->_page->setFrameFocusByIndex($choice);
}
/**
* Sets the focus by name.
* @param string $name Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocus($name) {
return $this->_page->setFrameFocus($name);
}
/**
* Clears the frame focus. All frames will be searched
* for content.
* @access public
*/
function clearFrameFocus() {
return $this->_page->clearFrameFocus();
}
/**
* Accessor for last error.
* @return string Error from last response.
* @access public
*/
function getTransportError() {
return $this->_page->getTransportError();
}
/**
* Accessor for current MIME type.
* @return string MIME type as string; e.g. 'text/html'
* @access public
*/
function getMimeType() {
return $this->_page->getMimeType();
}
/**
* Accessor for last response code.
* @return integer Last HTTP response code received.
* @access public
*/
function getResponseCode() {
return $this->_page->getResponseCode();
}
/**
* Accessor for last Authentication type. Only valid
* straight after a challenge (401).
* @return string Description of challenge type.
* @access public
*/
function getAuthentication() {
return $this->_page->getAuthentication();
}
/**
* Accessor for last Authentication realm. Only valid
* straight after a challenge (401).
* @return string Name of security realm.
* @access public
*/
function getRealm() {
return $this->_page->getRealm();
}
/**
* Accessor for current URL of page or frame if
* focused.
* @return string Location of current page or frame as
* a string.
*/
function getUrl() {
$url = $this->_page->getUrl();
return $url ? $url->asString() : false;
}
/**
* Accessor for raw bytes sent down the wire.
* @return string Original text sent.
* @access public
*/
function getRequest() {
return $this->_page->getRequest();
}
/**
* Accessor for raw header information.
* @return string Header block.
* @access public
*/
function getHeaders() {
return $this->_page->getHeaders();
}
/**
* Accessor for raw page information.
* @return string Original text content of web page.
* @access public
*/
function getContent() {
return $this->_page->getRaw();
}
/**
* Accessor for plain text version of the page.
* @return string Normalised text representation.
* @access public
*/
function getContentAsText() {
return $this->_page->getText();
}
/**
* Accessor for parsed title.
* @return string Title or false if no title is present.
* @access public
*/
function getTitle() {
return $this->_page->getTitle();
}
/**
* Accessor for a list of all fixed links in current page.
* @return array List of urls with scheme of
* http or https and hostname.
* @access public
*/
function getAbsoluteUrls() {
return $this->_page->getAbsoluteUrls();
}
/**
* Accessor for a list of all relative links.
* @return array List of urls without hostname.
* @access public
*/
function getRelativeUrls() {
return $this->_page->getRelativeUrls();
}
/**
* Sets all form fields with that name.
* @param string $label Name or label of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setField($label, $value) {
return $this->_page->setField(new SimpleByLabelOrName($label), $value);
}
/**
* Sets all form fields with that name. Will use label if
* one is available (not yet implemented).
* @param string $name Name of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setFieldByName($name, $value) {
return $this->_page->setField(new SimpleByName($name), $value);
}
/**
* Sets all form fields with that id attribute.
* @param string/integer $id Id of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setFieldById($id, $value) {
return $this->_page->setField(new SimpleById($id), $value);
}
/**
* Accessor for a form element value within the page.
* Finds the first match.
* @param string $label Field label.
* @return string/boolean A value if the field is
* present, false if unchecked
* and null if missing.
* @access public
*/
function getField($label) {
return $this->_page->getField(new SimpleByLabelOrName($label));
}
/**
* Accessor for a form element value within the page.
* Finds the first match.
* @param string $name Field name.
* @return string/boolean A string if the field is
* present, false if unchecked
* and null if missing.
* @access public
*/
function getFieldByName($name) {
return $this->_page->getField(new SimpleByName($name));
}
/**
* Accessor for a form element value within the page.
* @param string/integer $id Id of field in forms.
* @return string/boolean A string if the field is
* present, false if unchecked
* and null if missing.
* @access public
*/
function getFieldById($id) {
return $this->_page->getField(new SimpleById($id));
}
/**
* Clicks the submit button by label. The owning
* form will be submitted by this.
* @param string $label Button label. An unlabeled
* button can be triggered by 'Submit'.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickSubmit($label = 'Submit', $additional = false) {
if (! ($form = &$this->_page->getFormBySubmit(new SimpleByLabel($label)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitButton(new SimpleByLabel($label), $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Clicks the submit button by name attribute. The owning
* form will be submitted by this.
* @param string $name Button name.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickSubmitByName($name, $additional = false) {
if (! ($form = &$this->_page->getFormBySubmit(new SimpleByName($name)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitButton(new SimpleByName($name), $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Clicks the submit button by ID attribute of the button
* itself. The owning form will be submitted by this.
* @param string $id Button ID.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickSubmitById($id, $additional = false) {
if (! ($form = &$this->_page->getFormBySubmit(new SimpleById($id)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitButton(new SimpleById($id), $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Clicks the submit image by some kind of label. Usually
* the alt tag or the nearest equivalent. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param string $label ID attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickImage($label, $x = 1, $y = 1, $additional = false) {
if (! ($form = &$this->_page->getFormByImage(new SimpleByLabel($label)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Clicks the submit image by the name. Usually
* the alt tag or the nearest equivalent. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param string $name Name attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
if (! ($form = &$this->_page->getFormByImage(new SimpleByName($name)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitImage(new SimpleByName($name), $x, $y, $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Clicks the submit image by ID attribute. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param integer/string $id ID attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form data.
* @return string/boolean Page on success.
* @access public
*/
function clickImageById($id, $x = 1, $y = 1, $additional = false) {
if (! ($form = &$this->_page->getFormByImage(new SimpleById($id)))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submitImage(new SimpleById($id), $x, $y, $additional));
return ($success ? $this->getContent() : $success);
}
/**
* Submits a form by the ID.
* @param string $id The form ID. No submit button value
* will be sent.
* @return string/boolean Page on success.
* @access public
*/
function submitFormById($id) {
if (! ($form = &$this->_page->getFormById($id))) {
return false;
}
$success = $this->_load(
$form->getAction(),
$form->submit());
return ($success ? $this->getContent() : $success);
}
/**
* Follows a link by label. Will click the first link
* found with this link text by default, or a later
* one if an index is given. The match ignores case and
* white space issues.
* @param string $label Text between the anchor tags.
* @param integer $index Link position counting from zero.
* @return string/boolean Page on success.
* @access public
*/
function clickLink($label, $index = 0) {
$urls = $this->_page->getUrlsByLabel($label);
if (count($urls) == 0) {
return false;
}
if (count($urls) < $index + 1) {
return false;
}
$this->_load($urls[$index], new SimpleGetEncoding());
return $this->getContent();
}
/**
* Tests to see if a link is present by label.
* @param string $label Text of value attribute.
* @return boolean True if link present.
* @access public
*/
function isLink($label) {
return (count($this->_page->getUrlsByLabel($label)) > 0);
}
/**
* Follows a link by id attribute.
* @param string $id ID attribute value.
* @return string/boolean Page on success.
* @access public
*/
function clickLinkById($id) {
if (! ($url = $this->_page->getUrlById($id))) {
return false;
}
$this->_load($url, new SimpleGetEncoding());
return $this->getContent();
}
/**
* Tests to see if a link is present by ID attribute.
* @param string $id Text of id attribute.
* @return boolean True if link present.
* @access public
*/
function isLinkById($id) {
return (boolean)$this->_page->getUrlById($id);
}
/**
* Clicks a visible text item. Will first try buttons,
* then links and then images.
* @param string $label Visible text or alt text.
* @return string/boolean Raw page or false.
* @access public
*/
function click($label) {
$raw = $this->clickSubmit($label);
if (! $raw) {
$raw = $this->clickLink($label);
}
if (! $raw) {
$raw = $this->clickImage($label);
}
return $raw;
}
}
?>

View File

@ -1,115 +0,0 @@
<?php
/**
* This file contains the following classes: {@link SimpleCollector},
* {@link SimplePatternCollector}.
*
* @author Travis Swicegood <development@domain51.com>
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**
* The basic collector for {@link GroupTest}
*
* @see collect(), GroupTest::collect()
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleCollector {
/**
* Strips off any kind of slash at the end so as to normalise the path
*
* @param string $path Path to normalise.
*/
function _removeTrailingSlash($path) {
return preg_replace('|[\\/]$|', '', $path);
/**
* @internal
* Try benchmarking the following. It's more code, but by not using the
* regex, it may be faster? Also, shouldn't be looking for
* DIRECTORY_SEPERATOR instead of a manual "/"?
*/
if (substr($path, -1) == DIRECTORY_SEPERATOR) {
return substr($path, 0, -1);
} else {
return $path;
}
}
/**
* Scans the directory and adds what it can.
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
* @param string $path Directory to scan.
* @see _attemptToAdd()
*/
function collect(&$test, $path) {
$path = $this->_removeTrailingSlash($path);
if ($handle = opendir($path)) {
while (($entry = readdir($handle)) !== false) {
$this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry);
}
closedir($handle);
}
}
/**
* This method determines what should be done with a given file and adds
* it via {@link GroupTest::addTestFile()} if necessary.
*
* This method should be overriden to provide custom matching criteria,
* such as pattern matching, recursive matching, etc. For an example, see
* {@link SimplePatternCollector::_handle()}.
*
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
* @param string $filename A filename as generated by {@link collect()}
* @see collect()
* @access protected
*/
function _handle(&$test, $file) {
if (!is_dir($file)) {
$test->addTestFile($file);
}
}
}
/**
* An extension to {@link SimpleCollector} that only adds files matching a
* given pattern.
*
* @package SimpleTest
* @subpackage UnitTester
* @see SimpleCollector
*/
class SimplePatternCollector extends SimpleCollector {
var $_pattern;
/**
*
* @param string $pattern Perl compatible regex to test name against
* See {@link http://us4.php.net/manual/en/reference.pcre.pattern.syntax.php PHP's PCRE}
* for full documentation of valid pattern.s
*/
function SimplePatternCollector($pattern = '/php$/i') {
$this->_pattern = $pattern;
}
/**
* Attempts to add files that match a given pattern.
*
* @see SimpleCollector::_handle()
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
* @param string $path Directory to scan.
* @access protected
*/
function _handle(&$test, $filename) {
if (preg_match($this->_pattern, $filename)) {
parent::_handle($test, $filename);
}
}
}
?>

View File

@ -1,184 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @version $Id$
*/
/**
* Static methods for compatibility between different
* PHP versions.
* @package SimpleTest
*/
class SimpleTestCompatibility {
/**
* Creates a copy whether in PHP5 or PHP4.
* @param object $object Thing to copy.
* @return object A copy.
* @access public
* @static
*/
function copy($object) {
if (version_compare(phpversion(), '5') >= 0) {
eval('$copy = clone $object;');
return $copy;
}
return $object;
}
/**
* Identity test. Drops back to equality + types for PHP5
* objects as the === operator counts as the
* stronger reference constraint.
* @param mixed $first Test subject.
* @param mixed $second Comparison object.
* @return boolean True if identical.
* @access public
* @static
*/
function isIdentical($first, $second) {
if ($first != $second) {
return false;
}
if (version_compare(phpversion(), '5') >= 0) {
return SimpleTestCompatibility::_isIdenticalType($first, $second);
}
return ($first === $second);
}
/**
* Recursive type test.
* @param mixed $first Test subject.
* @param mixed $second Comparison object.
* @return boolean True if same type.
* @access private
* @static
*/
function _isIdenticalType($first, $second) {
if (gettype($first) != gettype($second)) {
return false;
}
if (is_object($first) && is_object($second)) {
if (get_class($first) != get_class($second)) {
return false;
}
return SimpleTestCompatibility::_isArrayOfIdenticalTypes(
get_object_vars($first),
get_object_vars($second));
}
if (is_array($first) && is_array($second)) {
return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second);
}
return true;
}
/**
* Recursive type test for each element of an array.
* @param mixed $first Test subject.
* @param mixed $second Comparison object.
* @return boolean True if identical.
* @access private
* @static
*/
function _isArrayOfIdenticalTypes($first, $second) {
if (array_keys($first) != array_keys($second)) {
return false;
}
foreach (array_keys($first) as $key) {
$is_identical = SimpleTestCompatibility::_isIdenticalType(
$first[$key],
$second[$key]);
if (! $is_identical) {
return false;
}
}
return true;
}
/**
* Test for two variables being aliases.
* @param mixed $first Test subject.
* @param mixed $second Comparison object.
* @return boolean True if same.
* @access public
* @static
*/
function isReference(&$first, &$second) {
if (version_compare(phpversion(), '5', '>=')
&& is_object($first)) {
return ($first === $second);
}
if (is_object($first) && is_object($second)) {
$id = uniqid("test");
$first->$id = true;
$is_ref = isset($second->$id);
unset($first->$id);
return $is_ref;
}
$temp = $first;
$first = uniqid("test");
$is_ref = ($first === $second);
$first = $temp;
return $is_ref;
}
/**
* Test to see if an object is a member of a
* class hiearchy.
* @param object $object Object to test.
* @param string $class Root name of hiearchy.
* @return boolean True if class in hiearchy.
* @access public
* @static
*/
function isA($object, $class) {
if (function_exists('is_a')) {
return is_a($object, $class);
}
if (version_compare(phpversion(), '5') >= 0) {
if (! class_exists($class, false)) {
if (function_exists('interface_exists')) {
if (! interface_exists($class, false)) {
return false;
}
}
}
eval("\$is_a = \$object instanceof $class;");
return $is_a;
}
return ((strtolower($class) == get_class($object))
or (is_subclass_of($object, $class)));
}
/**
* Sets a socket timeout for each chunk.
* @param resource $handle Socket handle.
* @param integer $timeout Limit in seconds.
* @access public
* @static
*/
function setTimeout($handle, $timeout) {
if (function_exists('stream_set_timeout')) {
stream_set_timeout($handle, $timeout, 0);
} elseif (function_exists('socket_set_timeout')) {
socket_set_timeout($handle, $timeout, 0);
} elseif (function_exists('set_socket_timeout')) {
set_socket_timeout($handle, $timeout, 0);
}
}
/**
* Gets the current stack trace topmost first.
* @return array List of stack frames.
* @access public
* @static
*/
function getStackTrace() {
if (function_exists('debug_backtrace')) {
return array_reverse(debug_backtrace());
}
return array();
}
}
?>

View File

@ -1,380 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/url.php');
/**#@-*/
/**
* Cookie data holder. Cookie rules are full of pretty
* arbitary stuff. I have used...
* http://wp.netscape.com/newsref/std/cookie_spec.html
* http://www.cookiecentral.com/faq/
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleCookie {
var $_host;
var $_name;
var $_value;
var $_path;
var $_expiry;
var $_is_secure;
/**
* Constructor. Sets the stored values.
* @param string $name Cookie key.
* @param string $value Value of cookie.
* @param string $path Cookie path if not host wide.
* @param string $expiry Expiry date as string.
* @param boolean $is_secure Currently ignored.
*/
function SimpleCookie($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
$this->_host = false;
$this->_name = $name;
$this->_value = $value;
$this->_path = ($path ? $this->_fixPath($path) : "/");
$this->_expiry = false;
if (is_string($expiry)) {
$this->_expiry = strtotime($expiry);
} elseif (is_integer($expiry)) {
$this->_expiry = $expiry;
}
$this->_is_secure = $is_secure;
}
/**
* Sets the host. The cookie rules determine
* that the first two parts are taken for
* certain TLDs and three for others. If the
* new host does not match these rules then the
* call will fail.
* @param string $host New hostname.
* @return boolean True if hostname is valid.
* @access public
*/
function setHost($host) {
if ($host = $this->_truncateHost($host)) {
$this->_host = $host;
return true;
}
return false;
}
/**
* Accessor for the truncated host to which this
* cookie applies.
* @return string Truncated hostname.
* @access public
*/
function getHost() {
return $this->_host;
}
/**
* Test for a cookie being valid for a host name.
* @param string $host Host to test against.
* @return boolean True if the cookie would be valid
* here.
*/
function isValidHost($host) {
return ($this->_truncateHost($host) === $this->getHost());
}
/**
* Extracts just the domain part that determines a
* cookie's host validity.
* @param string $host Host name to truncate.
* @return string Domain or false on a bad host.
* @access private
*/
function _truncateHost($host) {
$tlds = SimpleUrl::getAllTopLevelDomains();
if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
return $matches[0];
} elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
return $matches[0];
}
return false;
}
/**
* Accessor for name.
* @return string Cookie key.
* @access public
*/
function getName() {
return $this->_name;
}
/**
* Accessor for value. A deleted cookie will
* have an empty string for this.
* @return string Cookie value.
* @access public
*/
function getValue() {
return $this->_value;
}
/**
* Accessor for path.
* @return string Valid cookie path.
* @access public
*/
function getPath() {
return $this->_path;
}
/**
* Tests a path to see if the cookie applies
* there. The test path must be longer or
* equal to the cookie path.
* @param string $path Path to test against.
* @return boolean True if cookie valid here.
* @access public
*/
function isValidPath($path) {
return (strncmp(
$this->_fixPath($path),
$this->getPath(),
strlen($this->getPath())) == 0);
}
/**
* Accessor for expiry.
* @return string Expiry string.
* @access public
*/
function getExpiry() {
if (! $this->_expiry) {
return false;
}
return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT";
}
/**
* Test to see if cookie is expired against
* the cookie format time or timestamp.
* Will give true for a session cookie.
* @param integer/string $now Time to test against. Result
* will be false if this time
* is later than the cookie expiry.
* Can be either a timestamp integer
* or a cookie format date.
* @access public
*/
function isExpired($now) {
if (! $this->_expiry) {
return true;
}
if (is_string($now)) {
$now = strtotime($now);
}
return ($this->_expiry < $now);
}
/**
* Ages the cookie by the specified number of
* seconds.
* @param integer $interval In seconds.
* @public
*/
function agePrematurely($interval) {
if ($this->_expiry) {
$this->_expiry -= $interval;
}
}
/**
* Accessor for the secure flag.
* @return boolean True if cookie needs SSL.
* @access public
*/
function isSecure() {
return $this->_is_secure;
}
/**
* Adds a trailing and leading slash to the path
* if missing.
* @param string $path Path to fix.
* @access private
*/
function _fixPath($path) {
if (substr($path, 0, 1) != '/') {
$path = '/' . $path;
}
if (substr($path, -1, 1) != '/') {
$path .= '/';
}
return $path;
}
}
/**
* Repository for cookies. This stuff is a
* tiny bit browser dependent.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleCookieJar {
var $_cookies;
/**
* Constructor. Jar starts empty.
* @access public
*/
function SimpleCookieJar() {
$this->_cookies = array();
}
/**
* Removes expired and temporary cookies as if
* the browser was closed and re-opened.
* @param string/integer $now Time to test expiry against.
* @access public
*/
function restartSession($date = false) {
$surviving_cookies = array();
for ($i = 0; $i < count($this->_cookies); $i++) {
if (! $this->_cookies[$i]->getValue()) {
continue;
}
if (! $this->_cookies[$i]->getExpiry()) {
continue;
}
if ($date && $this->_cookies[$i]->isExpired($date)) {
continue;
}
$surviving_cookies[] = $this->_cookies[$i];
}
$this->_cookies = $surviving_cookies;
}
/**
* Ages all cookies in the cookie jar.
* @param integer $interval The old session is moved
* into the past by this number
* of seconds. Cookies now over
* age will be removed.
* @access public
*/
function agePrematurely($interval) {
for ($i = 0; $i < count($this->_cookies); $i++) {
$this->_cookies[$i]->agePrematurely($interval);
}
}
/**
* Sets an additional cookie. If a cookie has
* the same name and path it is replaced.
* @param string $name Cookie key.
* @param string $value Value of cookie.
* @param string $host Host upon which the cookie is valid.
* @param string $path Cookie path if not host wide.
* @param string $expiry Expiry date.
* @access public
*/
function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
$cookie = new SimpleCookie($name, $value, $path, $expiry);
if ($host) {
$cookie->setHost($host);
}
$this->_cookies[$this->_findFirstMatch($cookie)] = $cookie;
}
/**
* Finds a matching cookie to write over or the
* first empty slot if none.
* @param SimpleCookie $cookie Cookie to write into jar.
* @return integer Available slot.
* @access private
*/
function _findFirstMatch($cookie) {
for ($i = 0; $i < count($this->_cookies); $i++) {
$is_match = $this->_isMatch(
$cookie,
$this->_cookies[$i]->getHost(),
$this->_cookies[$i]->getPath(),
$this->_cookies[$i]->getName());
if ($is_match) {
return $i;
}
}
return count($this->_cookies);
}
/**
* Reads the most specific cookie value from the
* browser cookies. Looks for the longest path that
* matches.
* @param string $host Host to search.
* @param string $path Applicable path.
* @param string $name Name of cookie to read.
* @return string False if not present, else the
* value as a string.
* @access public
*/
function getCookieValue($host, $path, $name) {
$longest_path = '';
foreach ($this->_cookies as $cookie) {
if ($this->_isMatch($cookie, $host, $path, $name)) {
if (strlen($cookie->getPath()) > strlen($longest_path)) {
$value = $cookie->getValue();
$longest_path = $cookie->getPath();
}
}
}
return (isset($value) ? $value : false);
}
/**
* Tests cookie for matching against search
* criteria.
* @param SimpleTest $cookie Cookie to test.
* @param string $host Host must match.
* @param string $path Cookie path must be shorter than
* this path.
* @param string $name Name must match.
* @return boolean True if matched.
* @access private
*/
function _isMatch($cookie, $host, $path, $name) {
if ($cookie->getName() != $name) {
return false;
}
if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) {
return false;
}
if (! $cookie->isValidPath($path)) {
return false;
}
return true;
}
/**
* Uses a URL to sift relevant cookies by host and
* path. Results are list of strings of form "name=value".
* @param SimpleUrl $url Url to select by.
* @return array Valid name and value pairs.
* @access public
*/
function selectAsPairs($url) {
$pairs = array();
foreach ($this->_cookies as $cookie) {
if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
$pairs[] = $cookie->getName() . '=' . $cookie->getValue();
}
}
return $pairs;
}
}
?>

View File

@ -1,96 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/xml.php');
require_once(dirname(__FILE__) . '/shell_tester.php');
/**#@-*/
/**
* Runs an XML formated test in a separate process.
* @package SimpleTest
* @subpackage UnitTester
*/
class DetachedTestCase {
var $_command;
var $_dry_command;
var $_size;
/**
* Sets the location of the remote test.
* @param string $command Test script.
* @param string $dry_command Script for dry run.
* @access public
*/
function DetachedTestCase($command, $dry_command = false) {
$this->_command = $command;
$this->_dry_command = $dry_command ? $dry_command : $command;
$this->_size = false;
}
/**
* Accessor for the test name for subclasses.
* @return string Name of the test.
* @access public
*/
function getLabel() {
return $this->_command;
}
/**
* Runs the top level test for this class. Currently
* reads the data as a single chunk. I'll fix this
* once I have added iteration to the browser.
* @param SimpleReporter $reporter Target of test results.
* @returns boolean True if no failures.
* @access public
*/
function run(&$reporter) {
$shell = &new SimpleShell();
$shell->execute($this->_command);
$parser = &$this->_createParser($reporter);
if (! $parser->parse($shell->getOutput())) {
trigger_error('Cannot parse incoming XML from [' . $this->_command . ']');
return false;
}
return true;
}
/**
* Accessor for the number of subtests.
* @return integer Number of test cases.
* @access public
*/
function getSize() {
if ($this->_size === false) {
$shell = &new SimpleShell();
$shell->execute($this->_dry_command);
$reporter = &new SimpleReporter();
$parser = &$this->_createParser($reporter);
if (! $parser->parse($shell->getOutput())) {
trigger_error('Cannot parse incoming XML from [' . $this->_dry_command . ']');
return false;
}
$this->_size = $reporter->getTestCaseCount();
}
return $this->_size;
}
/**
* Creates the XML parser.
* @param SimpleReporter $reporter Target of test results.
* @return SimpleTestXmlListener XML reader.
* @access protected
*/
function &_createParser(&$reporter) {
return new SimpleTestXmlParser($reporter);
}
}
?>

View File

@ -1,402 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**
* does type matter
*/
if (! defined('TYPE_MATTERS')) {
define('TYPE_MATTERS', true);
}
/**
* Displays variables as text and does diffs.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleDumper {
/**
* Renders a variable in a shorter form than print_r().
* @param mixed $value Variable to render as a string.
* @return string Human readable string form.
* @access public
*/
function describeValue($value) {
$type = $this->getType($value);
switch($type) {
case "Null":
return "NULL";
case "Boolean":
return "Boolean: " . ($value ? "true" : "false");
case "Array":
return "Array: " . count($value) . " items";
case "Object":
return "Object: of " . get_class($value);
case "String":
return "String: " . $this->clipString($value, 200);
default:
return "$type: $value";
}
return "Unknown";
}
/**
* Gets the string representation of a type.
* @param mixed $value Variable to check against.
* @return string Type.
* @access public
*/
function getType($value) {
if (! isset($value)) {
return "Null";
} elseif (is_bool($value)) {
return "Boolean";
} elseif (is_string($value)) {
return "String";
} elseif (is_integer($value)) {
return "Integer";
} elseif (is_float($value)) {
return "Float";
} elseif (is_array($value)) {
return "Array";
} elseif (is_resource($value)) {
return "Resource";
} elseif (is_object($value)) {
return "Object";
}
return "Unknown";
}
/**
* Creates a human readable description of the
* difference between two variables. Uses a
* dynamic call.
* @param mixed $first First variable.
* @param mixed $second Value to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Description of difference.
* @access public
*/
function describeDifference($first, $second, $identical = false) {
if ($identical) {
if (! $this->_isTypeMatch($first, $second)) {
return "with type mismatch as [" . $this->describeValue($first) .
"] does not match [" . $this->describeValue($second) . "]";
}
}
$type = $this->getType($first);
if ($type == "Unknown") {
return "with unknown type";
}
$method = '_describe' . $type . 'Difference';
return $this->$method($first, $second, $identical);
}
/**
* Tests to see if types match.
* @param mixed $first First variable.
* @param mixed $second Value to compare with.
* @return boolean True if matches.
* @access private
*/
function _isTypeMatch($first, $second) {
return ($this->getType($first) == $this->getType($second));
}
/**
* Clips a string to a maximum length.
* @param string $value String to truncate.
* @param integer $size Minimum string size to show.
* @param integer $position Centre of string section.
* @return string Shortened version.
* @access public
*/
function clipString($value, $size, $position = 0) {
$length = strlen($value);
if ($length <= $size) {
return $value;
}
$position = min($position, $length);
$start = ($size/2 > $position ? 0 : $position - $size/2);
if ($start + $size > $length) {
$start = $length - $size;
}
$value = substr($value, $start, $size);
return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : "");
}
/**
* Creates a human readable description of the
* difference between two variables. The minimal
* version.
* @param null $first First value.
* @param mixed $second Value to compare with.
* @return string Human readable description.
* @access private
*/
function _describeGenericDifference($first, $second) {
return "as [" . $this->describeValue($first) .
"] does not match [" .
$this->describeValue($second) . "]";
}
/**
* Creates a human readable description of the
* difference between a null and another variable.
* @param null $first First null.
* @param mixed $second Null to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeNullDifference($first, $second, $identical) {
return $this->_describeGenericDifference($first, $second);
}
/**
* Creates a human readable description of the
* difference between a boolean and another variable.
* @param boolean $first First boolean.
* @param mixed $second Boolean to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeBooleanDifference($first, $second, $identical) {
return $this->_describeGenericDifference($first, $second);
}
/**
* Creates a human readable description of the
* difference between a string and another variable.
* @param string $first First string.
* @param mixed $second String to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeStringDifference($first, $second, $identical) {
if (is_object($second) || is_array($second)) {
return $this->_describeGenericDifference($first, $second);
}
$position = $this->_stringDiffersAt($first, $second);
$message = "at character $position";
$message .= " with [" .
$this->clipString($first, 200, $position) . "] and [" .
$this->clipString($second, 200, $position) . "]";
return $message;
}
/**
* Creates a human readable description of the
* difference between an integer and another variable.
* @param integer $first First number.
* @param mixed $second Number to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeIntegerDifference($first, $second, $identical) {
if (is_object($second) || is_array($second)) {
return $this->_describeGenericDifference($first, $second);
}
return "because [" . $this->describeValue($first) .
"] differs from [" .
$this->describeValue($second) . "] by " .
abs($first - $second);
}
/**
* Creates a human readable description of the
* difference between two floating point numbers.
* @param float $first First float.
* @param mixed $second Float to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeFloatDifference($first, $second, $identical) {
if (is_object($second) || is_array($second)) {
return $this->_describeGenericDifference($first, $second);
}
return "because [" . $this->describeValue($first) .
"] differs from [" .
$this->describeValue($second) . "] by " .
abs($first - $second);
}
/**
* Creates a human readable description of the
* difference between two arrays.
* @param array $first First array.
* @param mixed $second Array to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeArrayDifference($first, $second, $identical) {
if (! is_array($second)) {
return $this->_describeGenericDifference($first, $second);
}
if (! $this->_isMatchingKeys($first, $second, $identical)) {
return "as key list [" .
implode(", ", array_keys($first)) . "] does not match key list [" .
implode(", ", array_keys($second)) . "]";
}
foreach (array_keys($first) as $key) {
if ($identical && ($first[$key] === $second[$key])) {
continue;
}
if (! $identical && ($first[$key] == $second[$key])) {
continue;
}
return "with member [$key] " . $this->describeDifference(
$first[$key],
$second[$key],
$identical);
}
return "";
}
/**
* Compares two arrays to see if their key lists match.
* For an identical match, the ordering and types of the keys
* is significant.
* @param array $first First array.
* @param array $second Array to compare with.
* @param boolean $identical If true then type anomolies count.
* @return boolean True if matching.
* @access private
*/
function _isMatchingKeys($first, $second, $identical) {
$first_keys = array_keys($first);
$second_keys = array_keys($second);
if ($identical) {
return ($first_keys === $second_keys);
}
sort($first_keys);
sort($second_keys);
return ($first_keys == $second_keys);
}
/**
* Creates a human readable description of the
* difference between a resource and another variable.
* @param resource $first First resource.
* @param mixed $second Resource to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeResourceDifference($first, $second, $identical) {
return $this->_describeGenericDifference($first, $second);
}
/**
* Creates a human readable description of the
* difference between two objects.
* @param object $first First object.
* @param mixed $second Object to compare with.
* @param boolean $identical If true then type anomolies count.
* @return string Human readable description.
* @access private
*/
function _describeObjectDifference($first, $second, $identical) {
if (! is_object($second)) {
return $this->_describeGenericDifference($first, $second);
}
return $this->_describeArrayDifference(
get_object_vars($first),
get_object_vars($second),
$identical);
}
/**
* Find the first character position that differs
* in two strings by binary chop.
* @param string $first First string.
* @param string $second String to compare with.
* @return integer Position of first differing
* character.
* @access private
*/
function _stringDiffersAt($first, $second) {
if (! $first || ! $second) {
return 0;
}
if (strlen($first) < strlen($second)) {
list($first, $second) = array($second, $first);
}
$position = 0;
$step = strlen($first);
while ($step > 1) {
$step = (integer)(($step + 1) / 2);
if (strncmp($first, $second, $position + $step) == 0) {
$position += $step;
}
}
return $position;
}
/**
* Sends a formatted dump of a variable to a string.
* @param mixed $variable Variable to display.
* @return string Output from print_r().
* @access public
* @static
*/
function dump($variable) {
ob_start();
print_r($variable);
$formatted = ob_get_contents();
ob_end_clean();
return $formatted;
}
/**
* Extracts the last assertion that was not within
* Simpletest itself. The name must start with "assert".
* @param array $stack List of stack frames.
* @access public
* @static
*/
function getFormattedAssertionLine($stack) {
foreach ($stack as $frame) {
if (isset($frame['file'])) {
if (strpos($frame['file'], SIMPLE_TEST) !== false) {
if (dirname($frame['file']) . '/' == SIMPLE_TEST) {
continue;
}
}
}
if (SimpleDumper::_stackFrameIsAnAssertion($frame)) {
return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']';
}
}
return '';
}
/**
* Tries to determine if the method call is an assertion.
* @param array $frame PHP stack frame.
* @access private
* @static
*/
function _stackFrameIsAnAssertion($frame) {
if (($frame['function'] == 'fail') || ($frame['function'] == 'pass')) {
return true;
}
if (strncmp($frame['function'], 'assert', 6) == 0) {
return true;
}
if (strncmp($frame['function'], 'expect', 6) == 0) {
return true;
}
return false;
}
}
?>

View File

@ -1,521 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/socket.php');
/**#@-*/
/**
* Single post parameter.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleEncodedPair {
var $_key;
var $_value;
/**
* Stashes the data for rendering later.
* @param string $key Form element name.
* @param string $value Data to send.
*/
function SimpleEncodedPair($key, $value) {
$this->_key = $key;
$this->_value = $value;
}
/**
* The pair as a single string.
* @return string Encoded pair.
* @access public
*/
function asRequest() {
return $this->_key . '=' . urlencode($this->_value);
}
/**
* The MIME part as a string.
* @return string MIME part encoding.
* @access public
*/
function asMime() {
$part = 'Content-Disposition: form-data; ';
$part .= "name=\"" . $this->_key . "\"\r\n";
$part .= "\r\n" . $this->_value;
return $part;
}
/**
* Is this the value we are looking for?
* @param string $key Identifier.
* @return boolean True if matched.
* @access public
*/
function isKey($key) {
return $key == $this->_key;
}
/**
* Is this the value we are looking for?
* @return string Identifier.
* @access public
*/
function getKey() {
return $this->_key;
}
/**
* Is this the value we are looking for?
* @return string Content.
* @access public
*/
function getValue() {
return $this->_value;
}
}
/**
* Single post parameter.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleAttachment {
var $_key;
var $_content;
var $_filename;
/**
* Stashes the data for rendering later.
* @param string $key Key to add value to.
* @param string $content Raw data.
* @param hash $filename Original filename.
*/
function SimpleAttachment($key, $content, $filename) {
$this->_key = $key;
$this->_content = $content;
$this->_filename = $filename;
}
/**
* The pair as a single string.
* @return string Encoded pair.
* @access public
*/
function asRequest() {
return '';
}
/**
* The MIME part as a string.
* @return string MIME part encoding.
* @access public
*/
function asMime() {
$part = 'Content-Disposition: form-data; ';
$part .= 'name="' . $this->_key . '"; ';
$part .= 'filename="' . $this->_filename . '"';
$part .= "\r\nContent-Type: " . $this->_deduceMimeType();
$part .= "\r\n\r\n" . $this->_content;
return $part;
}
/**
* Attempts to figure out the MIME type from the
* file extension and the content.
* @return string MIME type.
* @access private
*/
function _deduceMimeType() {
if ($this->_isOnlyAscii($this->_content)) {
return 'text/plain';
}
return 'application/octet-stream';
}
/**
* Tests each character is in the range 0-127.
* @param string $ascii String to test.
* @access private
*/
function _isOnlyAscii($ascii) {
for ($i = 0, $length = strlen($ascii); $i < $length; $i++) {
if (ord($ascii[$i]) > 127) {
return false;
}
}
return true;
}
/**
* Is this the value we are looking for?
* @param string $key Identifier.
* @return boolean True if matched.
* @access public
*/
function isKey($key) {
return $key == $this->_key;
}
/**
* Is this the value we are looking for?
* @return string Identifier.
* @access public
*/
function getKey() {
return $this->_key;
}
/**
* Is this the value we are looking for?
* @return string Content.
* @access public
*/
function getValue() {
return $this->_filename;
}
}
/**
* Bundle of GET/POST parameters. Can include
* repeated parameters.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleEncoding {
var $_request;
/**
* Starts empty.
* @param array $query Hash of parameters.
* Multiple values are
* as lists on a single key.
* @access public
*/
function SimpleEncoding($query = false) {
if (! $query) {
$query = array();
}
$this->clear();
$this->merge($query);
}
/**
* Empties the request of parameters.
* @access public
*/
function clear() {
$this->_request = array();
}
/**
* Adds a parameter to the query.
* @param string $key Key to add value to.
* @param string/array $value New data.
* @access public
*/
function add($key, $value) {
if ($value === false) {
return;
}
if (is_array($value)) {
foreach ($value as $item) {
$this->_addPair($key, $item);
}
} else {
$this->_addPair($key, $value);
}
}
/**
* Adds a new value into the request.
* @param string $key Key to add value to.
* @param string/array $value New data.
* @access private
*/
function _addPair($key, $value) {
$this->_request[] = new SimpleEncodedPair($key, $value);
}
/**
* Adds a MIME part to the query. Does nothing for a
* form encoded packet.
* @param string $key Key to add value to.
* @param string $content Raw data.
* @param hash $filename Original filename.
* @access public
*/
function attach($key, $content, $filename) {
$this->_request[] = new SimpleAttachment($key, $content, $filename);
}
/**
* Adds a set of parameters to this query.
* @param array/SimpleQueryString $query Multiple values are
* as lists on a single key.
* @access public
*/
function merge($query) {
if (is_object($query)) {
$this->_request = array_merge($this->_request, $query->getAll());
} elseif (is_array($query)) {
foreach ($query as $key => $value) {
$this->add($key, $value);
}
}
}
/**
* Accessor for single value.
* @return string/array False if missing, string
* if present and array if
* multiple entries.
* @access public
*/
function getValue($key) {
$values = array();
foreach ($this->_request as $pair) {
if ($pair->isKey($key)) {
$values[] = $pair->getValue();
}
}
if (count($values) == 0) {
return false;
} elseif (count($values) == 1) {
return $values[0];
} else {
return $values;
}
}
/**
* Accessor for listing of pairs.
* @return array All pair objects.
* @access public
*/
function getAll() {
return $this->_request;
}
/**
* Renders the query string as a URL encoded
* request part.
* @return string Part of URL.
* @access protected
*/
function _encode() {
$statements = array();
foreach ($this->_request as $pair) {
if ($statement = $pair->asRequest()) {
$statements[] = $statement;
}
}
return implode('&', $statements);
}
}
/**
* Bundle of GET parameters. Can include
* repeated parameters.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleGetEncoding extends SimpleEncoding {
/**
* Starts empty.
* @param array $query Hash of parameters.
* Multiple values are
* as lists on a single key.
* @access public
*/
function SimpleGetEncoding($query = false) {
$this->SimpleEncoding($query);
}
/**
* HTTP request method.
* @return string Always GET.
* @access public
*/
function getMethod() {
return 'GET';
}
/**
* Writes no extra headers.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeHeadersTo(&$socket) {
}
/**
* No data is sent to the socket as the data is encoded into
* the URL.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeTo(&$socket) {
}
/**
* Renders the query string as a URL encoded
* request part for attaching to a URL.
* @return string Part of URL.
* @access public
*/
function asUrlRequest() {
return $this->_encode();
}
}
/**
* Bundle of URL parameters for a HEAD request.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHeadEncoding extends SimpleGetEncoding {
/**
* Starts empty.
* @param array $query Hash of parameters.
* Multiple values are
* as lists on a single key.
* @access public
*/
function SimpleHeadEncoding($query = false) {
$this->SimpleGetEncoding($query);
}
/**
* HTTP request method.
* @return string Always HEAD.
* @access public
*/
function getMethod() {
return 'HEAD';
}
}
/**
* Bundle of POST parameters. Can include
* repeated parameters.
* @package SimpleTest
* @subpackage WebTester
*/
class SimplePostEncoding extends SimpleEncoding {
/**
* Starts empty.
* @param array $query Hash of parameters.
* Multiple values are
* as lists on a single key.
* @access public
*/
function SimplePostEncoding($query = false) {
$this->SimpleEncoding($query);
}
/**
* HTTP request method.
* @return string Always POST.
* @access public
*/
function getMethod() {
return 'POST';
}
/**
* Dispatches the form headers down the socket.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeHeadersTo(&$socket) {
$socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
$socket->write("Content-Type: application/x-www-form-urlencoded\r\n");
}
/**
* Dispatches the form data down the socket.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeTo(&$socket) {
$socket->write($this->_encode());
}
/**
* Renders the query string as a URL encoded
* request part for attaching to a URL.
* @return string Part of URL.
* @access public
*/
function asUrlRequest() {
return '';
}
}
/**
* Bundle of POST parameters in the multipart
* format. Can include file uploads.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleMultipartEncoding extends SimplePostEncoding {
var $_boundary;
/**
* Starts empty.
* @param array $query Hash of parameters.
* Multiple values are
* as lists on a single key.
* @access public
*/
function SimpleMultipartEncoding($query = false, $boundary = false) {
$this->SimplePostEncoding($query);
$this->_boundary = ($boundary === false ? uniqid('st') : $boundary);
}
/**
* Dispatches the form headers down the socket.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeHeadersTo(&$socket) {
$socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
$socket->write("Content-Type: multipart/form-data, boundary=" . $this->_boundary . "\r\n");
}
/**
* Dispatches the form data down the socket.
* @param SimpleSocket $socket Socket to write to.
* @access public
*/
function writeTo(&$socket) {
$socket->write($this->_encode());
}
/**
* Renders the query string as a URL encoded
* request part.
* @return string Part of URL.
* @access public
*/
function _encode() {
$stream = '';
foreach ($this->_request as $pair) {
$stream .= "--" . $this->_boundary . "\r\n";
$stream .= $pair->asMime() . "\r\n";
}
$stream .= "--" . $this->_boundary . "--\r\n";
return $stream;
}
}
?>

View File

@ -1,182 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/** @ignore - PHP5 compatibility fix. */
if (! defined('E_STRICT')) {
define('E_STRICT', 2048);
}
/**#@+
* Includes SimpleTest files.
*/
require_once(dirname(__FILE__) . '/invoker.php');
/**
* Extension that traps errors into an error queue.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleErrorTrappingInvoker extends SimpleInvokerDecorator {
/**
* Stores the invoker to wrap.
* @param SimpleInvoker $invoker Test method runner.
*/
function SimpleErrorTrappingInvoker(&$invoker) {
$this->SimpleInvokerDecorator($invoker);
}
/**
* Invokes a test method and dispatches any
* untrapped errors. Called back from
* the visiting runner.
* @param string $method Test method to call.
* @access public
*/
function invoke($method) {
set_error_handler('simpleTestErrorHandler');
parent::invoke($method);
$queue = &SimpleErrorQueue::instance();
while (list($severity, $message, $file, $line, $globals) = $queue->extract()) {
$severity = SimpleErrorQueue::getSeverityAsString($severity);
$test_case = &$this->getTestCase();
$test_case->error($severity, $message, $file, $line);
}
restore_error_handler();
}
}
/**
* Singleton error queue used to record trapped
* errors.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleErrorQueue {
var $_queue;
/**
* Starts with an empty queue.
* @access public
*/
function SimpleErrorQueue() {
$this->clear();
}
/**
* Adds an error to the front of the queue.
* @param $severity PHP error code.
* @param $message Text of error.
* @param $filename File error occoured in.
* @param $line Line number of error.
* @param $super_globals Hash of PHP super global arrays.
* @access public
*/
function add($severity, $message, $filename, $line, $super_globals) {
array_push(
$this->_queue,
array($severity, $message, $filename, $line, $super_globals));
}
/**
* Pulls the earliest error from the queue.
* @return False if none, or a list of error
* information. Elements are: severity
* as the PHP error code, the error message,
* the file with the error, the line number
* and a list of PHP super global arrays.
* @access public
*/
function extract() {
if (count($this->_queue)) {
return array_shift($this->_queue);
}
return false;
}
/**
* Discards the contents of the error queue.
* @access public
*/
function clear() {
$this->_queue = array();
}
/**
* Tests to see if the queue is empty.
* @return True if empty.
*/
function isEmpty() {
return (count($this->_queue) == 0);
}
/**
* Global access to a single error queue.
* @return Global error queue object.
* @access public
* @static
*/
function &instance() {
static $queue = false;
if (! $queue) {
$queue = new SimpleErrorQueue();
}
return $queue;
}
/**
* Converst an error code into it's string
* representation.
* @param $severity PHP integer error code.
* @return String version of error code.
* @access public
* @static
*/
function getSeverityAsString($severity) {
static $map = array(
E_STRICT => 'E_STRICT',
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE');
return $map[$severity];
}
}
/**
* Error handler that simply stashes any errors into the global
* error queue. Simulates the existing behaviour with respect to
* logging errors, but this feature may be removed in future.
* @param $severity PHP error code.
* @param $message Text of error.
* @param $filename File error occoured in.
* @param $line Line number of error.
* @param $super_globals Hash of PHP super global arrays.
* @static
* @access public
*/
function simpleTestErrorHandler($severity, $message, $filename, $line, $super_globals) {
if ($severity = $severity & error_reporting()) {
restore_error_handler();
if (ini_get('log_errors')) {
$label = SimpleErrorQueue::getSeverityAsString($severity);
error_log("$label: $message in $filename on line $line");
}
$queue = &SimpleErrorQueue::instance();
$queue->add($severity, $message, $filename, $line, $super_globals);
set_error_handler('simpleTestErrorHandler');
}
}
?>

View File

@ -1,46 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* Includes SimpleTest files and defined the root constant
* for dependent libraries.
*/
require_once(dirname(__FILE__) . '/invoker.php');
/**
* Extension that traps exceptions and turns them into
* an error message.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleExceptionTrappingInvoker extends SimpleInvokerDecorator {
/**
* Stores the invoker to be wrapped.
* @param SimpleInvoker $invoker Test method runner.
*/
function SimpleExceptionTrappingInvoker($invoker) {
$this->SimpleInvokerDecorator($invoker);
}
/**
* Invokes a test method and dispatches any
* untrapped errors.
* @param string $method Test method to call.
* @access public
*/
function invoke($method) {
try {
parent::invoke($method);
} catch (Exception $exception) {
$test_case = &$this->getTestCase();
$test_case->exception($exception);
}
}
}
?>

View File

@ -1,720 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/dumper.php');
require_once(dirname(__FILE__) . '/compatibility.php');
/**#@-*/
/**
* Assertion that can display failure information.
* Also includes various helper methods.
* @package SimpleTest
* @subpackage UnitTester
* @abstract
*/
class SimpleExpectation {
var $_dumper;
var $_message;
/**
* Creates a dumper for displaying values and sets
* the test message.
* @param string $message Customised message on failure.
*/
function SimpleExpectation($message = '%s') {
$this->_dumper = &new SimpleDumper();
$this->_message = $message;
}
/**
* Tests the expectation. True if correct.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
* @abstract
*/
function test($compare) {
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
* @abstract
*/
function testMessage($compare) {
}
/**
* Overlays the generated message onto the stored user
* message. An additional message can be interjected.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function overlayMessage($compare) {
return sprintf($this->_message, $this->testMessage($compare));
}
/**
* Accessor for the dumper.
* @return SimpleDumper Current value dumper.
* @access protected
*/
function &_getDumper() {
return $this->_dumper;
}
/**
* Test to see if a value is an expectation object.
* A useful utility method.
* @param mixed $expectation Hopefully an Epectation
* class.
* @return boolean True if descended from
* this class.
* @access public
* @static
*/
function isExpectation($expectation) {
return is_object($expectation) &&
SimpleTestCompatibility::isA($expectation, 'SimpleExpectation');
}
}
/**
* Test for equality.
* @package SimpleTest
* @subpackage UnitTester
*/
class EqualExpectation extends SimpleExpectation {
var $_value;
/**
* Sets the value to compare against.
* @param mixed $value Test value to match.
* @param string $message Customised message on failure.
* @access public
*/
function EqualExpectation($value, $message = '%s') {
$this->SimpleExpectation($message);
$this->_value = $value;
}
/**
* Tests the expectation. True if it matches the
* held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return (($this->_value == $compare) && ($compare == $this->_value));
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return "Equal expectation [" . $this->_dumper->describeValue($this->_value) . "]";
} else {
return "Equal expectation fails " .
$this->_dumper->describeDifference($this->_value, $compare);
}
}
/**
* Accessor for comparison value.
* @return mixed Held value to compare with.
* @access protected
*/
function _getValue() {
return $this->_value;
}
}
/**
* Test for inequality.
* @package SimpleTest
* @subpackage UnitTester
*/
class NotEqualExpectation extends EqualExpectation {
/**
* Sets the value to compare against.
* @param mixed $value Test value to match.
* @param string $message Customised message on failure.
* @access public
*/
function NotEqualExpectation($value, $message = '%s') {
$this->EqualExpectation($value, $message);
}
/**
* Tests the expectation. True if it differs from the
* held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
if ($this->test($compare)) {
return "Not equal expectation passes " .
$dumper->describeDifference($this->_getValue(), $compare);
} else {
return "Not equal expectation fails [" .
$dumper->describeValue($this->_getValue()) .
"] matches";
}
}
}
/**
* Test for being within a range.
* @package SimpleTest
* @subpackage UnitTester
*/
class WithinMarginExpectation extends SimpleExpectation {
var $_upper;
var $_lower;
/**
* Sets the value to compare against and the fuzziness of
* the match. Used for comparing floating point values.
* @param mixed $value Test value to match.
* @param mixed $margin Fuzziness of match.
* @param string $message Customised message on failure.
* @access public
*/
function WithinMarginExpectation($value, $margin, $message = '%s') {
$this->SimpleExpectation($message);
$this->_upper = $value + $margin;
$this->_lower = $value - $margin;
}
/**
* Tests the expectation. True if it matches the
* held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return (($compare <= $this->_upper) && ($compare >= $this->_lower));
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_withinMessage($compare);
} else {
return $this->_outsideMessage($compare);
}
}
/**
* Creates a the message for being within the range.
* @param mixed $compare Value being tested.
* @access private
*/
function _withinMessage($compare) {
return "Within expectation [" . $this->_dumper->describeValue($this->_lower) . "] and [" .
$this->_dumper->describeValue($this->_upper) . "]";
}
/**
* Creates a the message for being within the range.
* @param mixed $compare Value being tested.
* @access private
*/
function _outsideMessage($compare) {
if ($compare > $this->_upper) {
return "Outside expectation " .
$this->_dumper->describeDifference($compare, $this->_upper);
} else {
return "Outside expectation " .
$this->_dumper->describeDifference($compare, $this->_lower);
}
}
}
/**
* Test for being outside of a range.
* @package SimpleTest
* @subpackage UnitTester
*/
class OutsideMarginExpectation extends WithinMarginExpectation {
/**
* Sets the value to compare against and the fuzziness of
* the match. Used for comparing floating point values.
* @param mixed $value Test value to not match.
* @param mixed $margin Fuzziness of match.
* @param string $message Customised message on failure.
* @access public
*/
function OutsideMarginExpectation($value, $margin, $message = '%s') {
$this->WithinMarginExpectation($value, $margin, $message);
}
/**
* Tests the expectation. True if it matches the
* held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if (! $this->test($compare)) {
return $this->_withinMessage($compare);
} else {
return $this->_outsideMessage($compare);
}
}
}
/**
* Test for identity.
* @package SimpleTest
* @subpackage UnitTester
*/
class IdenticalExpectation extends EqualExpectation {
/**
* Sets the value to compare against.
* @param mixed $value Test value to match.
* @param string $message Customised message on failure.
* @access public
*/
function IdenticalExpectation($value, $message = '%s') {
$this->EqualExpectation($value, $message);
}
/**
* Tests the expectation. True if it exactly
* matches the held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return SimpleTestCompatibility::isIdentical($this->_getValue(), $compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
if ($this->test($compare)) {
return "Identical expectation [" . $dumper->describeValue($this->_getValue()) . "]";
} else {
return "Identical expectation [" . $dumper->describeValue($this->_getValue()) .
"] fails with [" .
$dumper->describeValue($compare) . "] " .
$dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
}
}
}
/**
* Test for non-identity.
* @package SimpleTest
* @subpackage UnitTester
*/
class NotIdenticalExpectation extends IdenticalExpectation {
/**
* Sets the value to compare against.
* @param mixed $value Test value to match.
* @param string $message Customised message on failure.
* @access public
*/
function NotIdenticalExpectation($value, $message = '%s') {
$this->IdenticalExpectation($value, $message);
}
/**
* Tests the expectation. True if it differs from the
* held value.
* @param mixed $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
if ($this->test($compare)) {
return "Not identical expectation passes " .
$dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
} else {
return "Not identical expectation [" . $dumper->describeValue($this->_getValue()) . "] matches";
}
}
}
/**
* Test for a pattern using Perl regex rules.
* @package SimpleTest
* @subpackage UnitTester
*/
class PatternExpectation extends SimpleExpectation {
var $_pattern;
/**
* Sets the value to compare against.
* @param string $pattern Pattern to search for.
* @param string $message Customised message on failure.
* @access public
*/
function PatternExpectation($pattern, $message = '%s') {
$this->SimpleExpectation($message);
$this->_pattern = $pattern;
}
/**
* Accessor for the pattern.
* @return string Perl regex as string.
* @access protected
*/
function _getPattern() {
return $this->_pattern;
}
/**
* Tests the expectation. True if the Perl regex
* matches the comparison value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return (boolean)preg_match($this->_getPattern(), $compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_describePatternMatch($this->_getPattern(), $compare);
} else {
$dumper = &$this->_getDumper();
return "Pattern [" . $this->_getPattern() .
"] not detected in [" .
$dumper->describeValue($compare) . "]";
}
}
/**
* Describes a pattern match including the string
* found and it's position.
* @package SimpleTest
* @subpackage UnitTester
* @param string $pattern Regex to match against.
* @param string $subject Subject to search.
* @access protected
*/
function _describePatternMatch($pattern, $subject) {
preg_match($pattern, $subject, $matches);
$position = strpos($subject, $matches[0]);
$dumper = &$this->_getDumper();
return "Pattern [$pattern] detected at character [$position] in [" .
$dumper->describeValue($subject) . "] as [" .
$matches[0] . "] in region [" .
$dumper->clipString($subject, 100, $position) . "]";
}
}
/**
* @deprecated
*/
class WantedPatternExpectation extends PatternExpectation {
}
/**
* Fail if a pattern is detected within the
* comparison.
* @package SimpleTest
* @subpackage UnitTester
*/
class NoPatternExpectation extends PatternExpectation {
/**
* Sets the reject pattern
* @param string $pattern Pattern to search for.
* @param string $message Customised message on failure.
* @access public
*/
function NoPatternExpectation($pattern, $message = '%s') {
$this->PatternExpectation($pattern, $message);
}
/**
* Tests the expectation. False if the Perl regex
* matches the comparison value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param string $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
$dumper = &$this->_getDumper();
return "Pattern [" . $this->_getPattern() .
"] not detected in [" .
$dumper->describeValue($compare) . "]";
} else {
return $this->_describePatternMatch($this->_getPattern(), $compare);
}
}
}
/**
* @package SimpleTest
* @subpackage UnitTester
* @deprecated
*/
class UnwantedPatternExpectation extends NoPatternExpectation {
}
/**
* Tests either type or class name if it's an object.
* @package SimpleTest
* @subpackage UnitTester
*/
class IsAExpectation extends SimpleExpectation {
var $_type;
/**
* Sets the type to compare with.
* @param string $type Type or class name.
* @param string $message Customised message on failure.
* @access public
*/
function IsAExpectation($type, $message = '%s') {
$this->SimpleExpectation($message);
$this->_type = $type;
}
/**
* Accessor for type to check against.
* @return string Type or class name.
* @access protected
*/
function _getType() {
return $this->_type;
}
/**
* Tests the expectation. True if the type or
* class matches the string value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
if (is_object($compare)) {
return SimpleTestCompatibility::isA($compare, $this->_type);
} else {
return (strtolower(gettype($compare)) == $this->_canonicalType($this->_type));
}
}
/**
* Coerces type name into a gettype() match.
* @param string $type User type.
* @return string Simpler type.
* @access private
*/
function _canonicalType($type) {
$type = strtolower($type);
$map = array(
'bool' => 'boolean',
'float' => 'double',
'real' => 'double',
'int' => 'integer');
if (isset($map[$type])) {
$type = $map[$type];
}
return $type;
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
return "Value [" . $dumper->describeValue($compare) .
"] should be type [" . $this->_type . "]";
}
}
/**
* Tests either type or class name if it's an object.
* Will succeed if the type does not match.
* @package SimpleTest
* @subpackage UnitTester
*/
class NotAExpectation extends IsAExpectation {
var $_type;
/**
* Sets the type to compare with.
* @param string $type Type or class name.
* @param string $message Customised message on failure.
* @access public
*/
function NotAExpectation($type, $message = '%s') {
$this->IsAExpectation($type, $message);
}
/**
* Tests the expectation. False if the type or
* class matches the string value.
* @param string $compare Comparison value.
* @return boolean True if different.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
return "Value [" . $dumper->describeValue($compare) .
"] should not be type [" . $this->_getType() . "]";
}
}
/**
* Tests for existance of a method in an object
* @package SimpleTest
* @subpackage UnitTester
*/
class MethodExistsExpectation extends SimpleExpectation {
var $_method;
/**
* Sets the value to compare against.
* @param string $method Method to check.
* @param string $message Customised message on failure.
* @access public
* @return void
*/
function MethodExistsExpectation($method, $message = '%s') {
$this->SimpleExpectation($message);
$this->_method = &$method;
}
/**
* Tests the expectation. True if the method exists in the test object.
* @param string $compare Comparison method name.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return (boolean)(is_object($compare) && method_exists($compare, $this->_method));
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
if (! is_object($compare)) {
return 'No method on non-object [' . $dumper->describeValue($compare) . ']';
}
$method = $this->_method;
return "Object [" . $dumper->describeValue($compare) .
"] should contain method [$method]";
}
}
?>

View File

@ -1,352 +0,0 @@
<?php
/**
* Base include file for SimpleTest.
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
require_once(dirname(__FILE__) . '/tag.php');
require_once(dirname(__FILE__) . '/encoding.php');
require_once(dirname(__FILE__) . '/selector.php');
/**#@-*/
/**
* Form tag class to hold widget values.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleForm {
var $_method;
var $_action;
var $_encoding;
var $_default_target;
var $_id;
var $_buttons;
var $_images;
var $_widgets;
var $_radios;
var $_checkboxes;
/**
* Starts with no held controls/widgets.
* @param SimpleTag $tag Form tag to read.
* @param SimpleUrl $url Location of holding page.
*/
function SimpleForm($tag, $url) {
$this->_method = $tag->getAttribute('method');
$this->_action = $this->_createAction($tag->getAttribute('action'), $url);
$this->_encoding = $this->_setEncodingClass($tag);
$this->_default_target = false;
$this->_id = $tag->getAttribute('id');
$this->_buttons = array();
$this->_images = array();
$this->_widgets = array();
$this->_radios = array();
$this->_checkboxes = array();
}
/**
* Creates the request packet to be sent by the form.
* @param SimpleTag $tag Form tag to read.
* @return string Packet class.
* @access private
*/
function _setEncodingClass($tag) {
if (strtolower($tag->getAttribute('method')) == 'post') {
if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') {
return 'SimpleMultipartEncoding';
}
return 'SimplePostEncoding';
}
return 'SimpleGetEncoding';
}
/**
* Sets the frame target within a frameset.
* @param string $frame Name of frame.
* @access public
*/
function setDefaultTarget($frame) {
$this->_default_target = $frame;
}
/**
* Accessor for method of form submission.
* @return string Either get or post.
* @access public
*/
function getMethod() {
return ($this->_method ? strtolower($this->_method) : 'get');
}
/**
* Combined action attribute with current location
* to get an absolute form target.
* @param string $action Action attribute from form tag.
* @param SimpleUrl $base Page location.
* @return SimpleUrl Absolute form target.
*/
function _createAction($action, $base) {
if (($action === '') || ($action === false)) {
return $base;
}
$url = new SimpleUrl($action);
return $url->makeAbsolute($base);
}
/**
* Absolute URL of the target.
* @return SimpleUrl URL target.
* @access public
*/
function getAction() {
$url = $this->_action;
if ($this->_default_target && ! $url->getTarget()) {
$url->setTarget($this->_default_target);
}
return $url;
}
/**
* Creates the encoding for the current values in the
* form.
* @return SimpleFormEncoding Request to submit.
* @access private
*/
function _encode() {
$class = $this->_encoding;
$encoding = new $class();
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
$this->_widgets[$i]->write($encoding);
}
return $encoding;
}
/**
* ID field of form for unique identification.
* @return string Unique tag ID.
* @access public
*/
function getId() {
return $this->_id;
}
/**
* Adds a tag contents to the form.
* @param SimpleWidget $tag Input tag to add.
* @access public
*/
function addWidget(&$tag) {
if (strtolower($tag->getAttribute('type')) == 'submit') {
$this->_buttons[] = &$tag;
} elseif (strtolower($tag->getAttribute('type')) == 'image') {
$this->_images[] = &$tag;
} elseif ($tag->getName()) {
$this->_setWidget($tag);
}
}
/**
* Sets the widget into the form, grouping radio
* buttons if any.
* @param SimpleWidget $tag Incoming form control.
* @access private
*/
function _setWidget(&$tag) {
if (strtolower($tag->getAttribute('type')) == 'radio') {
$this->_addRadioButton($tag);
} elseif (strtolower($tag->getAttribute('type')) == 'checkbox') {
$this->_addCheckbox($tag);
} else {
$this->_widgets[] = &$tag;
}
}
/**
* Adds a radio button, building a group if necessary.
* @param SimpleRadioButtonTag $tag Incoming form control.
* @access private
*/
function _addRadioButton(&$tag) {
if (! isset($this->_radios[$tag->getName()])) {
$this->_widgets[] = &new SimpleRadioGroup();
$this->_radios[$tag->getName()] = count($this->_widgets) - 1;
}
$this->_widgets[$this->_radios[$tag->getName()]]->addWidget($tag);
}
/**
* Adds a checkbox, making it a group on a repeated name.
* @param SimpleCheckboxTag $tag Incoming form control.
* @access private
*/
function _addCheckbox(&$tag) {
if (! isset($this->_checkboxes[$tag->getName()])) {
$this->_widgets[] = &$tag;
$this->_checkboxes[$tag->getName()] = count($this->_widgets) - 1;
} else {
$index = $this->_checkboxes[$tag->getName()];
if (! SimpleTestCompatibility::isA($this->_widgets[$index], 'SimpleCheckboxGroup')) {
$previous = &$this->_widgets[$index];
$this->_widgets[$index] = &new SimpleCheckboxGroup();
$this->_widgets[$index]->addWidget($previous);
}
$this->_widgets[$index]->addWidget($tag);
}
}
/**
* Extracts current value from form.
* @param SimpleSelector $selector Criteria to apply.
* @return string/array Value(s) as string or null
* if not set.
* @access public
*/
function getValue($selector) {
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
if ($selector->isMatch($this->_widgets[$i])) {
return $this->_widgets[$i]->getValue();
}
}
foreach ($this->_buttons as $button) {
if ($selector->isMatch($button)) {
return $button->getValue();
}
}
return null;
}
/**
* Sets a widget value within the form.
* @param SimpleSelector $selector Criteria to apply.
* @param string $value Value to input into the widget.
* @return boolean True if value is legal, false
* otherwise. If the field is not
* present, nothing will be set.
* @access public
*/
function setField($selector, $value) {
$success = false;
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
if ($selector->isMatch($this->_widgets[$i])) {
if ($this->_widgets[$i]->setValue($value)) {
$success = true;
}
}
}
return $success;
}
/**
* Used by the page object to set widgets labels to
* external label tags.
* @param SimpleSelector $selector Criteria to apply.
* @access public
*/
function attachLabelBySelector($selector, $label) {
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
if ($selector->isMatch($this->_widgets[$i])) {
if (method_exists($this->_widgets[$i], 'setLabel')) {
$this->_widgets[$i]->setLabel($label);
return;
}
}
}
}
/**
* Test to see if a form has a submit button.
* @param SimpleSelector $selector Criteria to apply.
* @return boolean True if present.
* @access public
*/
function hasSubmit($selector) {
foreach ($this->_buttons as $button) {
if ($selector->isMatch($button)) {
return true;
}
}
return false;
}
/**
* Test to see if a form has an image control.
* @param SimpleSelector $selector Criteria to apply.
* @return boolean True if present.
* @access public
*/
function hasImage($selector) {
foreach ($this->_images as $image) {
if ($selector->isMatch($image)) {
return true;
}
}
return false;
}
/**
* Gets the submit values for a selected button.
* @param SimpleSelector $selector Criteria to apply.
* @param hash $additional Additional data for the form.
* @return SimpleEncoding Submitted values or false
* if there is no such button
* in the form.
* @access public
*/
function submitButton($selector, $additional = false) {
$additional = $additional ? $additional : array();
foreach ($this->_buttons as $button) {
if ($selector->isMatch($button)) {
$encoding = $this->_encode();
$button->write($encoding);
if ($additional) {
$encoding->merge($additional);
}
return $encoding;
}
}
return false;
}
/**
* Gets the submit values for an image.
* @param SimpleSelector $selector Criteria to apply.
* @param integer $x X-coordinate of click.
* @param integer $y Y-coordinate of click.
* @param hash $additional Additional data for the form.
* @return SimpleEncoding Submitted values or false
* if there is no such button in the
* form.
* @access public
*/
function submitImage($selector, $x, $y, $additional = false) {
$additional = $additional ? $additional : array();
foreach ($this->_images as $image) {
if ($selector->isMatch($image)) {
$encoding = $this->_encode();
$image->write($encoding, $x, $y);
if ($additional) {
$encoding->merge($additional);
}
return $encoding;
}
}
return false;
}
/**
* Simply submits the form without the submit button
* value. Used when there is only one button or it
* is unimportant.
* @return hash Submitted values.
* @access public
*/
function submit() {
return $this->_encode();
}
}
?>

View File

@ -1,588 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/page.php');
require_once(dirname(__FILE__) . '/user_agent.php');
/**#@-*/
/**
* A composite page. Wraps a frameset page and
* adds subframes. The original page will be
* mostly ignored. Implements the SimplePage
* interface so as to be interchangeable.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleFrameset {
var $_frameset;
var $_frames;
var $_focus;
var $_names;
/**
* Stashes the frameset page. Will make use of the
* browser to fetch the sub frames recursively.
* @param SimplePage $page Frameset page.
*/
function SimpleFrameset(&$page) {
$this->_frameset = &$page;
$this->_frames = array();
$this->_focus = false;
$this->_names = array();
}
/**
* Adds a parsed page to the frameset.
* @param SimplePage $page Frame page.
* @param string $name Name of frame in frameset.
* @access public
*/
function addFrame(&$page, $name = false) {
$this->_frames[] = &$page;
if ($name) {
$this->_names[$name] = count($this->_frames) - 1;
}
}
/**
* Replaces existing frame with another. If the
* frame is nested, then the call is passed down
* one level.
* @param array $path Path of frame in frameset.
* @param SimplePage $page Frame source.
* @access public
*/
function setFrame($path, &$page) {
$name = array_shift($path);
if (isset($this->_names[$name])) {
$index = $this->_names[$name];
} else {
$index = $name - 1;
}
if (count($path) == 0) {
$this->_frames[$index] = &$page;
return;
}
$this->_frames[$index]->setFrame($path, $page);
}
/**
* Accessor for current frame focus. Will be
* false if no frame has focus. Will have the nested
* frame focus if any.
* @return array Labels or indexes of nested frames.
* @access public
*/
function getFrameFocus() {
if ($this->_focus === false) {
return array();
}
return array_merge(
array($this->_getPublicNameFromIndex($this->_focus)),
$this->_frames[$this->_focus]->getFrameFocus());
}
/**
* Turns an internal array index into the frames list
* into a public name, or if none, then a one offset
* index.
* @param integer $subject Internal index.
* @return integer/string Public name.
* @access private
*/
function _getPublicNameFromIndex($subject) {
foreach ($this->_names as $name => $index) {
if ($subject == $index) {
return $name;
}
}
return $subject + 1;
}
/**
* Sets the focus by index. The integer index starts from 1.
* If already focused and the target frame also has frames,
* then the nested frame will be focused.
* @param integer $choice Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocusByIndex($choice) {
if (is_integer($this->_focus)) {
if ($this->_frames[$this->_focus]->hasFrames()) {
return $this->_frames[$this->_focus]->setFrameFocusByIndex($choice);
}
}
if (($choice < 1) || ($choice > count($this->_frames))) {
return false;
}
$this->_focus = $choice - 1;
return true;
}
/**
* Sets the focus by name. If already focused and the
* target frame also has frames, then the nested frame
* will be focused.
* @param string $name Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocus($name) {
if (is_integer($this->_focus)) {
if ($this->_frames[$this->_focus]->hasFrames()) {
return $this->_frames[$this->_focus]->setFrameFocus($name);
}
}
if (in_array($name, array_keys($this->_names))) {
$this->_focus = $this->_names[$name];
return true;
}
return false;
}
/**
* Clears the frame focus.
* @access public
*/
function clearFrameFocus() {
$this->_focus = false;
$this->_clearNestedFramesFocus();
}
/**
* Clears the frame focus for any nested frames.
* @access private
*/
function _clearNestedFramesFocus() {
for ($i = 0; $i < count($this->_frames); $i++) {
$this->_frames[$i]->clearFrameFocus();
}
}
/**
* Test for the presence of a frameset.
* @return boolean Always true.
* @access public
*/
function hasFrames() {
return true;
}
/**
* Accessor for frames information.
* @return array/string Recursive hash of frame URL strings.
* The key is either a numerical
* index or the name attribute.
* @access public
*/
function getFrames() {
$report = array();
for ($i = 0; $i < count($this->_frames); $i++) {
$report[$this->_getPublicNameFromIndex($i)] =
$this->_frames[$i]->getFrames();
}
return $report;
}
/**
* Accessor for raw text of either all the pages or
* the frame in focus.
* @return string Raw unparsed content.
* @access public
*/
function getRaw() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getRaw();
}
$raw = '';
for ($i = 0; $i < count($this->_frames); $i++) {
$raw .= $this->_frames[$i]->getRaw();
}
return $raw;
}
/**
* Accessor for plain text of either all the pages or
* the frame in focus.
* @return string Plain text content.
* @access public
*/
function getText() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getText();
}
$raw = '';
for ($i = 0; $i < count($this->_frames); $i++) {
$raw .= ' ' . $this->_frames[$i]->getText();
}
return trim($raw);
}
/**
* Accessor for last error.
* @return string Error from last response.
* @access public
*/
function getTransportError() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getTransportError();
}
return $this->_frameset->getTransportError();
}
/**
* Request method used to fetch this frame.
* @return string GET, POST or HEAD.
* @access public
*/
function getMethod() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getMethod();
}
return $this->_frameset->getMethod();
}
/**
* Original resource name.
* @return SimpleUrl Current url.
* @access public
*/
function getUrl() {
if (is_integer($this->_focus)) {
$url = $this->_frames[$this->_focus]->getUrl();
$url->setTarget($this->_getPublicNameFromIndex($this->_focus));
} else {
$url = $this->_frameset->getUrl();
}
return $url;
}
/**
* Original request data.
* @return mixed Sent content.
* @access public
*/
function getRequestData() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getRequestData();
}
return $this->_frameset->getRequestData();
}
/**
* Accessor for current MIME type.
* @return string MIME type as string; e.g. 'text/html'
* @access public
*/
function getMimeType() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getMimeType();
}
return $this->_frameset->getMimeType();
}
/**
* Accessor for last response code.
* @return integer Last HTTP response code received.
* @access public
*/
function getResponseCode() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getResponseCode();
}
return $this->_frameset->getResponseCode();
}
/**
* Accessor for last Authentication type. Only valid
* straight after a challenge (401).
* @return string Description of challenge type.
* @access public
*/
function getAuthentication() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getAuthentication();
}
return $this->_frameset->getAuthentication();
}
/**
* Accessor for last Authentication realm. Only valid
* straight after a challenge (401).
* @return string Name of security realm.
* @access public
*/
function getRealm() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getRealm();
}
return $this->_frameset->getRealm();
}
/**
* Accessor for outgoing header information.
* @return string Header block.
* @access public
*/
function getRequest() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getRequest();
}
return $this->_frameset->getRequest();
}
/**
* Accessor for raw header information.
* @return string Header block.
* @access public
*/
function getHeaders() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getHeaders();
}
return $this->_frameset->getHeaders();
}
/**
* Accessor for parsed title.
* @return string Title or false if no title is present.
* @access public
*/
function getTitle() {
return $this->_frameset->getTitle();
}
/**
* Accessor for a list of all fixed links.
* @return array List of urls with scheme of
* http or https and hostname.
* @access public
*/
function getAbsoluteUrls() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getAbsoluteUrls();
}
$urls = array();
foreach ($this->_frames as $frame) {
$urls = array_merge($urls, $frame->getAbsoluteUrls());
}
return array_values(array_unique($urls));
}
/**
* Accessor for a list of all relative links.
* @return array List of urls without hostname.
* @access public
*/
function getRelativeUrls() {
if (is_integer($this->_focus)) {
return $this->_frames[$this->_focus]->getRelativeUrls();
}
$urls = array();
foreach ($this->_frames as $frame) {
$urls = array_merge($urls, $frame->getRelativeUrls());
}
return array_values(array_unique($urls));
}
/**
* Accessor for URLs by the link label. Label will match
* regardess of whitespace issues and case.
* @param string $label Text of link.
* @return array List of links with that label.
* @access public
*/
function getUrlsByLabel($label) {
if (is_integer($this->_focus)) {
return $this->_tagUrlsWithFrame(
$this->_frames[$this->_focus]->getUrlsByLabel($label),
$this->_focus);
}
$urls = array();
foreach ($this->_frames as $index => $frame) {
$urls = array_merge(
$urls,
$this->_tagUrlsWithFrame(
$frame->getUrlsByLabel($label),
$index));
}
return $urls;
}
/**
* Accessor for a URL by the id attribute. If in a frameset
* then the first link found with that ID attribute is
* returned only. Focus on a frame if you want one from
* a specific part of the frameset.
* @param string $id Id attribute of link.
* @return string URL with that id.
* @access public
*/
function getUrlById($id) {
foreach ($this->_frames as $index => $frame) {
if ($url = $frame->getUrlById($id)) {
if (! $url->gettarget()) {
$url->setTarget($this->_getPublicNameFromIndex($index));
}
return $url;
}
}
return false;
}
/**
* Attaches the intended frame index to a list of URLs.
* @param array $urls List of SimpleUrls.
* @param string $frame Name of frame or index.
* @return array List of tagged URLs.
* @access private
*/
function _tagUrlsWithFrame($urls, $frame) {
$tagged = array();
foreach ($urls as $url) {
if (! $url->getTarget()) {
$url->setTarget($this->_getPublicNameFromIndex($frame));
}
$tagged[] = $url;
}
return $tagged;
}
/**
* Finds a held form by button label. Will only
* search correctly built forms.
* @param SimpleSelector $selector Button finder.
* @return SimpleForm Form object containing
* the button.
* @access public
*/
function &getFormBySubmit($selector) {
$form = &$this->_findForm('getFormBySubmit', $selector);
return $form;
}
/**
* Finds a held form by image using a selector.
* Will only search correctly built forms. The first
* form found either within the focused frame, or
* across frames, will be the one returned.
* @param SimpleSelector $selector Image finder.
* @return SimpleForm Form object containing
* the image.
* @access public
*/
function &getFormByImage($selector) {
$form = &$this->_findForm('getFormByImage', $selector);
return $form;
}
/**
* Finds a held form by the form ID. A way of
* identifying a specific form when we have control
* of the HTML code. The first form found
* either within the focused frame, or across frames,
* will be the one returned.
* @param string $id Form label.
* @return SimpleForm Form object containing the matching ID.
* @access public
*/
function &getFormById($id) {
$form = &$this->_findForm('getFormById', $id);
return $form;
}
/**
* General form finder. Will search all the frames or
* just the one in focus.
* @param string $method Method to use to find in a page.
* @param string $attribute Label, name or ID.
* @return SimpleForm Form object containing the matching ID.
* @access private
*/
function &_findForm($method, $attribute) {
if (is_integer($this->_focus)) {
$form = &$this->_findFormInFrame(
$this->_frames[$this->_focus],
$this->_focus,
$method,
$attribute);
return $form;
}
for ($i = 0; $i < count($this->_frames); $i++) {
$form = &$this->_findFormInFrame(
$this->_frames[$i],
$i,
$method,
$attribute);
if ($form) {
return $form;
}
}
$null = null;
return $null;
}
/**
* Finds a form in a page using a form finding method. Will
* also tag the form with the frame name it belongs in.
* @param SimplePage $page Page content of frame.
* @param integer $index Internal frame representation.
* @param string $method Method to use to find in a page.
* @param string $attribute Label, name or ID.
* @return SimpleForm Form object containing the matching ID.
* @access private
*/
function &_findFormInFrame(&$page, $index, $method, $attribute) {
$form = &$this->_frames[$index]->$method($attribute);
if (isset($form)) {
$form->setDefaultTarget($this->_getPublicNameFromIndex($index));
}
return $form;
}
/**
* Sets a field on each form in which the field is
* available.
* @param SimpleSelector $selector Field finder.
* @param string $value Value to set field to.
* @return boolean True if value is valid.
* @access public
*/
function setField($selector, $value) {
if (is_integer($this->_focus)) {
$this->_frames[$this->_focus]->setField($selector, $value);
} else {
for ($i = 0; $i < count($this->_frames); $i++) {
$this->_frames[$i]->setField($selector, $value);
}
}
}
/**
* Accessor for a form element value within a page.
* @param SimpleSelector $selector Field finder.
* @return string/boolean A string if the field is
* present, false if unchecked
* and null if missing.
* @access public
*/
function getField($selector) {
for ($i = 0; $i < count($this->_frames); $i++) {
$value = $this->_frames[$i]->getField($selector);
if (isset($value)) {
return $value;
}
}
return null;
}
}
?>

View File

@ -1,624 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/socket.php');
require_once(dirname(__FILE__) . '/cookies.php');
require_once(dirname(__FILE__) . '/url.php');
/**#@-*/
/**
* Creates HTTP headers for the end point of
* a HTTP request.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleRoute {
var $_url;
/**
* Sets the target URL.
* @param SimpleUrl $url URL as object.
* @access public
*/
function SimpleRoute($url) {
$this->_url = $url;
}
/**
* Resource name.
* @return SimpleUrl Current url.
* @access protected
*/
function getUrl() {
return $this->_url;
}
/**
* Creates the first line which is the actual request.
* @param string $method HTTP request method, usually GET.
* @return string Request line content.
* @access protected
*/
function _getRequestLine($method) {
return $method . ' ' . $this->_url->getPath() .
$this->_url->getEncodedRequest() . ' HTTP/1.0';
}
/**
* Creates the host part of the request.
* @return string Host line content.
* @access protected
*/
function _getHostLine() {
$line = 'Host: ' . $this->_url->getHost();
if ($this->_url->getPort()) {
$line .= ':' . $this->_url->getPort();
}
return $line;
}
/**
* Opens a socket to the route.
* @param string $method HTTP request method, usually GET.
* @param integer $timeout Connection timeout.
* @return SimpleSocket New socket.
* @access public
*/
function &createConnection($method, $timeout) {
$default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
$socket = &$this->_createSocket(
$this->_url->getScheme() ? $this->_url->getScheme() : 'http',
$this->_url->getHost(),
$this->_url->getPort() ? $this->_url->getPort() : $default_port,
$timeout);
if (! $socket->isError()) {
$socket->write($this->_getRequestLine($method) . "\r\n");
$socket->write($this->_getHostLine() . "\r\n");
$socket->write("Connection: close\r\n");
}
return $socket;
}
/**
* Factory for socket.
* @param string $scheme Protocol to use.
* @param string $host Hostname to connect to.
* @param integer $port Remote port.
* @param integer $timeout Connection timeout.
* @return SimpleSocket/SimpleSecureSocket New socket.
* @access protected
*/
function &_createSocket($scheme, $host, $port, $timeout) {
if (in_array($scheme, array('https'))) {
$socket = &new SimpleSecureSocket($host, $port, $timeout);
} else {
$socket = &new SimpleSocket($host, $port, $timeout);
}
return $socket;
}
}
/**
* Creates HTTP headers for the end point of
* a HTTP request via a proxy server.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleProxyRoute extends SimpleRoute {
var $_proxy;
var $_username;
var $_password;
/**
* Stashes the proxy address.
* @param SimpleUrl $url URL as object.
* @param string $proxy Proxy URL.
* @param string $username Username for autentication.
* @param string $password Password for autentication.
* @access public
*/
function SimpleProxyRoute($url, $proxy, $username = false, $password = false) {
$this->SimpleRoute($url);
$this->_proxy = $proxy;
$this->_username = $username;
$this->_password = $password;
}
/**
* Creates the first line which is the actual request.
* @param string $method HTTP request method, usually GET.
* @param SimpleUrl $url URL as object.
* @return string Request line content.
* @access protected
*/
function _getRequestLine($method) {
$url = $this->getUrl();
$scheme = $url->getScheme() ? $url->getScheme() : 'http';
$port = $url->getPort() ? ':' . $url->getPort() : '';
return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
$url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
}
/**
* Creates the host part of the request.
* @param SimpleUrl $url URL as object.
* @return string Host line content.
* @access protected
*/
function _getHostLine() {
$host = 'Host: ' . $this->_proxy->getHost();
$port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
return "$host:$port";
}
/**
* Opens a socket to the route.
* @param string $method HTTP request method, usually GET.
* @param integer $timeout Connection timeout.
* @return SimpleSocket New socket.
* @access public
*/
function &createConnection($method, $timeout) {
$socket = &$this->_createSocket(
$this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
$this->_proxy->getHost(),
$this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
$timeout);
if ($socket->isError()) {
return $socket;
}
$socket->write($this->_getRequestLine($method) . "\r\n");
$socket->write($this->_getHostLine() . "\r\n");
if ($this->_username && $this->_password) {
$socket->write('Proxy-Authorization: Basic ' .
base64_encode($this->_username . ':' . $this->_password) .
"\r\n");
}
$socket->write("Connection: close\r\n");
return $socket;
}
}
/**
* HTTP request for a web page. Factory for
* HttpResponse object.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHttpRequest {
var $_route;
var $_encoding;
var $_headers;
var $_cookies;
/**
* Builds the socket request from the different pieces.
* These include proxy information, URL, cookies, headers,
* request method and choice of encoding.
* @param SimpleRoute $route Request route.
* @param SimpleFormEncoding $encoding Content to send with
* request.
* @access public
*/
function SimpleHttpRequest(&$route, $encoding) {
$this->_route = &$route;
$this->_encoding = $encoding;
$this->_headers = array();
$this->_cookies = array();
}
/**
* Dispatches the content to the route's socket.
* @param integer $timeout Connection timeout.
* @return SimpleHttpResponse A response which may only have
* an error, but hopefully has a
* complete web page.
* @access public
*/
function &fetch($timeout) {
$socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
if (! $socket->isError()) {
$this->_dispatchRequest($socket, $this->_encoding);
}
$response = &$this->_createResponse($socket);
return $response;
}
/**
* Sends the headers.
* @param SimpleSocket $socket Open socket.
* @param string $method HTTP request method,
* usually GET.
* @param SimpleFormEncoding $encoding Content to send with request.
* @access private
*/
function _dispatchRequest(&$socket, $encoding) {
foreach ($this->_headers as $header_line) {
$socket->write($header_line . "\r\n");
}
if (count($this->_cookies) > 0) {
$socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
}
$encoding->writeHeadersTo($socket);
$socket->write("\r\n");
$encoding->writeTo($socket);
}
/**
* Adds a header line to the request.
* @param string $header_line Text of full header line.
* @access public
*/
function addHeaderLine($header_line) {
$this->_headers[] = $header_line;
}
/**
* Reads all the relevant cookies from the
* cookie jar.
* @param SimpleCookieJar $jar Jar to read
* @param SimpleUrl $url Url to use for scope.
* @access public
*/
function readCookiesFromJar($jar, $url) {
$this->_cookies = $jar->selectAsPairs($url);
}
/**
* Wraps the socket in a response parser.
* @param SimpleSocket $socket Responding socket.
* @return SimpleHttpResponse Parsed response object.
* @access protected
*/
function &_createResponse(&$socket) {
$response = &new SimpleHttpResponse(
$socket,
$this->_route->getUrl(),
$this->_encoding);
return $response;
}
}
/**
* Collection of header lines in the response.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHttpHeaders {
var $_raw_headers;
var $_response_code;
var $_http_version;
var $_mime_type;
var $_location;
var $_cookies;
var $_authentication;
var $_realm;
/**
* Parses the incoming header block.
* @param string $headers Header block.
* @access public
*/
function SimpleHttpHeaders($headers) {
$this->_raw_headers = $headers;
$this->_response_code = false;
$this->_http_version = false;
$this->_mime_type = '';
$this->_location = false;
$this->_cookies = array();
$this->_authentication = false;
$this->_realm = false;
foreach (split("\r\n", $headers) as $header_line) {
$this->_parseHeaderLine($header_line);
}
}
/**
* Accessor for parsed HTTP protocol version.
* @return integer HTTP error code.
* @access public
*/
function getHttpVersion() {
return $this->_http_version;
}
/**
* Accessor for raw header block.
* @return string All headers as raw string.
* @access public
*/
function getRaw() {
return $this->_raw_headers;
}
/**
* Accessor for parsed HTTP error code.
* @return integer HTTP error code.
* @access public
*/
function getResponseCode() {
return (integer)$this->_response_code;
}
/**
* Returns the redirected URL or false if
* no redirection.
* @return string URL or false for none.
* @access public
*/
function getLocation() {
return $this->_location;
}
/**
* Test to see if the response is a valid redirect.
* @return boolean True if valid redirect.
* @access public
*/
function isRedirect() {
return in_array($this->_response_code, array(301, 302, 303, 307)) &&
(boolean)$this->getLocation();
}
/**
* Test to see if the response is an authentication
* challenge.
* @return boolean True if challenge.
* @access public
*/
function isChallenge() {
return ($this->_response_code == 401) &&
(boolean)$this->_authentication &&
(boolean)$this->_realm;
}
/**
* Accessor for MIME type header information.
* @return string MIME type.
* @access public
*/
function getMimeType() {
return $this->_mime_type;
}
/**
* Accessor for authentication type.
* @return string Type.
* @access public
*/
function getAuthentication() {
return $this->_authentication;
}
/**
* Accessor for security realm.
* @return string Realm.
* @access public
*/
function getRealm() {
return $this->_realm;
}
/**
* Writes new cookies to the cookie jar.
* @param SimpleCookieJar $jar Jar to write to.
* @param SimpleUrl $url Host and path to write under.
* @access public
*/
function writeCookiesToJar(&$jar, $url) {
foreach ($this->_cookies as $cookie) {
$jar->setCookie(
$cookie->getName(),
$cookie->getValue(),
$url->getHost(),
$cookie->getPath(),
$cookie->getExpiry());
}
}
/**
* Called on each header line to accumulate the held
* data within the class.
* @param string $header_line One line of header.
* @access protected
*/
function _parseHeaderLine($header_line) {
if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
$this->_http_version = $matches[1];
$this->_response_code = $matches[2];
}
if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
$this->_mime_type = trim($matches[1]);
}
if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
$this->_location = trim($matches[1]);
}
if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
$this->_cookies[] = $this->_parseCookie($matches[1]);
}
if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
$this->_authentication = $matches[1];
$this->_realm = trim($matches[2]);
}
}
/**
* Parse the Set-cookie content.
* @param string $cookie_line Text after "Set-cookie:"
* @return SimpleCookie New cookie object.
* @access private
*/
function _parseCookie($cookie_line) {
$parts = split(";", $cookie_line);
$cookie = array();
preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
foreach ($parts as $part) {
if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
$cookie[$matches[1]] = trim($matches[2]);
}
}
return new SimpleCookie(
$cookie[1],
trim($cookie[2]),
isset($cookie["path"]) ? $cookie["path"] : "",
isset($cookie["expires"]) ? $cookie["expires"] : false);
}
}
/**
* Basic HTTP response.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHttpResponse extends SimpleStickyError {
var $_url;
var $_encoding;
var $_sent;
var $_content;
var $_headers;
/**
* Constructor. Reads and parses the incoming
* content and headers.
* @param SimpleSocket $socket Network connection to fetch
* response text from.
* @param SimpleUrl $url Resource name.
* @param mixed $encoding Record of content sent.
* @access public
*/
function SimpleHttpResponse(&$socket, $url, $encoding) {
$this->SimpleStickyError();
$this->_url = $url;
$this->_encoding = $encoding;
$this->_sent = $socket->getSent();
$this->_content = false;
$raw = $this->_readAll($socket);
if ($socket->isError()) {
$this->_setError('Error reading socket [' . $socket->getError() . ']');
return;
}
$this->_parse($raw);
}
/**
* Splits up the headers and the rest of the content.
* @param string $raw Content to parse.
* @access private
*/
function _parse($raw) {
if (! $raw) {
$this->_setError('Nothing fetched');
$this->_headers = &new SimpleHttpHeaders('');
} elseif (! strstr($raw, "\r\n\r\n")) {
$this->_setError('Could not split headers from content');
$this->_headers = &new SimpleHttpHeaders($raw);
} else {
list($headers, $this->_content) = split("\r\n\r\n", $raw, 2);
$this->_headers = &new SimpleHttpHeaders($headers);
}
}
/**
* Original request method.
* @return string GET, POST or HEAD.
* @access public
*/
function getMethod() {
return $this->_encoding->getMethod();
}
/**
* Resource name.
* @return SimpleUrl Current url.
* @access public
*/
function getUrl() {
return $this->_url;
}
/**
* Original request data.
* @return mixed Sent content.
* @access public
*/
function getRequestData() {
return $this->_encoding;
}
/**
* Raw request that was sent down the wire.
* @return string Bytes actually sent.
* @access public
*/
function getSent() {
return $this->_sent;
}
/**
* Accessor for the content after the last
* header line.
* @return string All content.
* @access public
*/
function getContent() {
return $this->_content;
}
/**
* Accessor for header block. The response is the
* combination of this and the content.
* @return SimpleHeaders Wrapped header block.
* @access public
*/
function getHeaders() {
return $this->_headers;
}
/**
* Accessor for any new cookies.
* @return array List of new cookies.
* @access public
*/
function getNewCookies() {
return $this->_headers->getNewCookies();
}
/**
* Reads the whole of the socket output into a
* single string.
* @param SimpleSocket $socket Unread socket.
* @return string Raw output if successful
* else false.
* @access private
*/
function _readAll(&$socket) {
$all = '';
while (! $this->_isLastPacket($next = $socket->read())) {
$all .= $next;
}
return $all;
}
/**
* Test to see if the packet from the socket is the
* last one.
* @param string $packet Chunk to interpret.
* @return boolean True if empty or EOF.
* @access private
*/
function _isLastPacket($packet) {
if (is_string($packet)) {
return $packet === '';
}
return ! $packet;
}
}
?>

View File

@ -1,139 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* Includes SimpleTest files and defined the root constant
* for dependent libraries.
*/
require_once(dirname(__FILE__) . '/errors.php');
require_once(dirname(__FILE__) . '/compatibility.php');
require_once(dirname(__FILE__) . '/scorer.php');
require_once(dirname(__FILE__) . '/expectation.php');
require_once(dirname(__FILE__) . '/dumper.php');
if (! defined('SIMPLE_TEST')) {
define('SIMPLE_TEST', dirname(__FILE__) . '/');
}
/**#@-*/
/**
* This is called by the class runner to run a
* single test method. Will also run the setUp()
* and tearDown() methods.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleInvoker {
var $_test_case;
/**
* Stashes the test case for later.
* @param SimpleTestCase $test_case Test case to run.
*/
function SimpleInvoker(&$test_case) {
$this->_test_case = &$test_case;
}
/**
* Accessor for test case being run.
* @return SimpleTestCase Test case.
* @access public
*/
function &getTestCase() {
return $this->_test_case;
}
/**
* Runs test level set up. Used for changing
* the mechanics of base test cases.
* @param string $method Test method to call.
* @access public
*/
function before($method) {
$this->_test_case->before($method);
}
/**
* Invokes a test method and buffered with setUp()
* and tearDown() calls.
* @param string $method Test method to call.
* @access public
*/
function invoke($method) {
$this->_test_case->setUp();
$this->_test_case->$method();
$this->_test_case->tearDown();
}
/**
* Runs test level clean up. Used for changing
* the mechanics of base test cases.
* @param string $method Test method to call.
* @access public
*/
function after($method) {
$this->_test_case->after($method);
}
}
/**
* Do nothing decorator. Just passes the invocation
* straight through.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleInvokerDecorator {
var $_invoker;
/**
* Stores the invoker to wrap.
* @param SimpleInvoker $invoker Test method runner.
*/
function SimpleInvokerDecorator(&$invoker) {
$this->_invoker = &$invoker;
}
/**
* Accessor for test case being run.
* @return SimpleTestCase Test case.
* @access public
*/
function &getTestCase() {
return $this->_invoker->getTestCase();
}
/**
* Runs test level set up. Used for changing
* the mechanics of base test cases.
* @param string $method Test method to call.
* @access public
*/
function before($method) {
$this->_invoker->before($method);
}
/**
* Invokes a test method and buffered with setUp()
* and tearDown() calls.
* @param string $method Test method to call.
* @access public
*/
function invoke($method) {
$this->_invoker->invoke($method);
}
/**
* Runs test level clean up. Used for changing
* the mechanics of base test cases.
* @param string $method Test method to call.
* @access public
*/
function after($method) {
$this->_invoker->after($method);
}
}
?>

View File

@ -1,1273 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage MockObjects
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
require_once(dirname(__FILE__) . '/expectation.php');
require_once(dirname(__FILE__) . '/simpletest.php');
require_once(dirname(__FILE__) . '/dumper.php');
if (version_compare(phpversion(), '5') >= 0) {
require_once(dirname(__FILE__) . '/reflection_php5.php');
} else {
require_once(dirname(__FILE__) . '/reflection_php4.php');
}
/**#@-*/
/**
* Default character simpletest will substitute for any value
*/
if (! defined('MOCK_ANYTHING')) {
define('MOCK_ANYTHING', '*');
}
/**
* A wildcard expectation always matches.
* @package SimpleTest
* @subpackage MockObjects
*/
class AnythingExpectation extends SimpleExpectation {
/**
* Tests the expectation. Always true.
* @param mixed $compare Ignored.
* @return boolean True.
* @access public
*/
function test($compare) {
return true;
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
return 'Anything always matches [' . $dumper->describeValue($compare) . ']';
}
}
/**
* Parameter comparison assertion.
* @package SimpleTest
* @subpackage MockObjects
*/
class ParametersExpectation extends SimpleExpectation {
var $_expected;
/**
* Sets the expected parameter list.
* @param array $parameters Array of parameters including
* those that are wildcarded.
* If the value is not an array
* then it is considered to match any.
* @param mixed $wildcard Any parameter matching this
* will always match.
* @param string $message Customised message on failure.
* @access public
*/
function ParametersExpectation($expected = false, $message = '%s') {
$this->SimpleExpectation($message);
$this->_expected = $expected;
}
/**
* Tests the assertion. True if correct.
* @param array $parameters Comparison values.
* @return boolean True if correct.
* @access public
*/
function test($parameters) {
if (! is_array($this->_expected)) {
return true;
}
if (count($this->_expected) != count($parameters)) {
return false;
}
for ($i = 0; $i < count($this->_expected); $i++) {
if (! $this->_testParameter($parameters[$i], $this->_expected[$i])) {
return false;
}
}
return true;
}
/**
* Tests an individual parameter.
* @param mixed $parameter Value to test.
* @param mixed $expected Comparison value.
* @return boolean True if expectation
* fulfilled.
* @access private
*/
function _testParameter($parameter, $expected) {
$comparison = $this->_coerceToExpectation($expected);
return $comparison->test($parameter);
}
/**
* Returns a human readable test message.
* @param array $comparison Incoming parameter list.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($parameters) {
if ($this->test($parameters)) {
return "Expectation of " . count($this->_expected) .
" arguments of [" . $this->_renderArguments($this->_expected) .
"] is correct";
} else {
return $this->_describeDifference($this->_expected, $parameters);
}
}
/**
* Message to display if expectation differs from
* the parameters actually received.
* @param array $expected Expected parameters as list.
* @param array $parameters Actual parameters received.
* @return string Description of difference.
* @access private
*/
function _describeDifference($expected, $parameters) {
if (count($expected) != count($parameters)) {
return "Expected " . count($expected) .
" arguments of [" . $this->_renderArguments($expected) .
"] but got " . count($parameters) .
" arguments of [" . $this->_renderArguments($parameters) . "]";
}
$messages = array();
for ($i = 0; $i < count($expected); $i++) {
$comparison = $this->_coerceToExpectation($expected[$i]);
if (! $comparison->test($parameters[$i])) {
$messages[] = "parameter " . ($i + 1) . " with [" .
$comparison->overlayMessage($parameters[$i]) . "]";
}
}
return "Parameter expectation differs at " . implode(" and ", $messages);
}
/**
* Creates an identical expectation if the
* object/value is not already some type
* of expectation.
* @param mixed $expected Expected value.
* @return SimpleExpectation Expectation object.
* @access private
*/
function _coerceToExpectation($expected) {
if (SimpleExpectation::isExpectation($expected)) {
return $expected;
}
return new IdenticalExpectation($expected);
}
/**
* Renders the argument list as a string for
* messages.
* @param array $args Incoming arguments.
* @return string Simple description of type and value.
* @access private
*/
function _renderArguments($args) {
$descriptions = array();
if (is_array($args)) {
foreach ($args as $arg) {
$dumper = &new SimpleDumper();
$descriptions[] = $dumper->describeValue($arg);
}
}
return implode(', ', $descriptions);
}
}
/**
* Confirms that the number of calls on a method is as expected.
*/
class CallCountExpectation extends SimpleExpectation {
var $_method;
var $_count;
/**
* Stashes the method and expected count for later
* reporting.
* @param string $method Name of method to confirm against.
* @param integer $count Expected number of calls.
* @param string $message Custom error message.
*/
function CallCountExpectation($method, $count, $message = '%s') {
$this->_method = $method;
$this->_count = $count;
$this->SimpleExpectation($message);
}
/**
* Tests the assertion. True if correct.
* @param integer $compare Measured call count.
* @return boolean True if expected.
* @access public
*/
function test($compare) {
return ($this->_count == $compare);
}
/**
* Reports the comparison.
* @param integer $compare Measured call count.
* @return string Message to show.
* @access public
*/
function testMessage($compare) {
return 'Expected call count for [' . $this->_method .
'] was [' . $this->_count .
'] got [' . $compare . ']';
}
}
/**
* Confirms that the number of calls on a method is as expected.
*/
class MinimumCallCountExpectation extends SimpleExpectation {
var $_method;
var $_count;
/**
* Stashes the method and expected count for later
* reporting.
* @param string $method Name of method to confirm against.
* @param integer $count Minimum number of calls.
* @param string $message Custom error message.
*/
function MinimumCallCountExpectation($method, $count, $message = '%s') {
$this->_method = $method;
$this->_count = $count;
$this->SimpleExpectation($message);
}
/**
* Tests the assertion. True if correct.
* @param integer $compare Measured call count.
* @return boolean True if enough.
* @access public
*/
function test($compare) {
return ($this->_count <= $compare);
}
/**
* Reports the comparison.
* @param integer $compare Measured call count.
* @return string Message to show.
* @access public
*/
function testMessage($compare) {
return 'Minimum call count for [' . $this->_method .
'] was [' . $this->_count .
'] got [' . $compare . ']';
}
}
/**
* Confirms that the number of calls on a method is as expected.
*/
class MaximumCallCountExpectation extends SimpleExpectation {
var $_method;
var $_count;
/**
* Stashes the method and expected count for later
* reporting.
* @param string $method Name of method to confirm against.
* @param integer $count Minimum number of calls.
* @param string $message Custom error message.
*/
function MaximumCallCountExpectation($method, $count, $message = '%s') {
$this->_method = $method;
$this->_count = $count;
$this->SimpleExpectation($message);
}
/**
* Tests the assertion. True if correct.
* @param integer $compare Measured call count.
* @return boolean True if not over.
* @access public
*/
function test($compare) {
return ($this->_count >= $compare);
}
/**
* Reports the comparison.
* @param integer $compare Measured call count.
* @return string Message to show.
* @access public
*/
function testMessage($compare) {
return 'Maximum call count for [' . $this->_method .
'] was [' . $this->_count .
'] got [' . $compare . ']';
}
}
/**
* Retrieves values and references by searching the
* parameter lists until a match is found.
* @package SimpleTest
* @subpackage MockObjects
*/
class CallMap {
var $_map;
/**
* Creates an empty call map.
* @access public
*/
function CallMap() {
$this->_map = array();
}
/**
* Stashes a value against a method call.
* @param array $parameters Arguments including wildcards.
* @param mixed $value Value copied into the map.
* @access public
*/
function addValue($parameters, $value) {
$this->addReference($parameters, $value);
}
/**
* Stashes a reference against a method call.
* @param array $parameters Array of arguments (including wildcards).
* @param mixed $reference Array reference placed in the map.
* @access public
*/
function addReference($parameters, &$reference) {
$place = count($this->_map);
$this->_map[$place] = array();
$this->_map[$place]["params"] = new ParametersExpectation($parameters);
$this->_map[$place]["content"] = &$reference;
}
/**
* Searches the call list for a matching parameter
* set. Returned by reference.
* @param array $parameters Parameters to search by
* without wildcards.
* @return object Object held in the first matching
* slot, otherwise null.
* @access public
*/
function &findFirstMatch($parameters) {
$slot = $this->_findFirstSlot($parameters);
if (!isset($slot)) {
$null = null;
return $null;
}
return $slot["content"];
}
/**
* Searches the call list for a matching parameter
* set. True if successful.
* @param array $parameters Parameters to search by
* without wildcards.
* @return boolean True if a match is present.
* @access public
*/
function isMatch($parameters) {
return ($this->_findFirstSlot($parameters) != null);
}
/**
* Searches the map for a matching item.
* @param array $parameters Parameters to search by
* without wildcards.
* @return array Reference to slot or null.
* @access private
*/
function &_findFirstSlot($parameters) {
$count = count($this->_map);
for ($i = 0; $i < $count; $i++) {
if ($this->_map[$i]["params"]->test($parameters)) {
return $this->_map[$i];
}
}
$null = null;
return $null;
}
}
/**
* An empty collection of methods that can have their
* return values set and expectations made of the
* calls upon them. The mock will assert the
* expectations against it's attached test case in
* addition to the server stub behaviour.
* @package SimpleTest
* @subpackage MockObjects
*/
class SimpleMock {
var $_wildcard = MOCK_ANYTHING;
var $_is_strict = true;
var $_returns;
var $_return_sequence;
var $_call_counts;
var $_expected_counts;
var $_max_counts;
var $_expected_args;
var $_expected_args_at;
/**
* Creates an empty return list and expectation list.
* All call counts are set to zero.
* @param SimpleTestCase $test Test case to test expectations in.
* @param mixed $wildcard Parameter matching wildcard.
* @param boolean $is_strict Enables method name checks on
* expectations.
*/
function SimpleMock() {
$this->_returns = array();
$this->_return_sequence = array();
$this->_call_counts = array();
$test = &$this->_getCurrentTestCase();
$test->tell($this);
$this->_expected_counts = array();
$this->_max_counts = array();
$this->_expected_args = array();
$this->_expected_args_at = array();
}
/**
* Disables a name check when setting expectations.
* This hack is needed for the partial mocks.
* @access public
*/
function disableExpectationNameChecks() {
$this->_is_strict = false;
}
/**
* Changes the default wildcard object.
* @param mixed $wildcard Parameter matching wildcard.
* @access public
*/
function setWildcard($wildcard) {
$this->_wildcard = $wildcard;
}
/**
* Finds currently running test.
* @return SimpeTestCase Current test case.
* @access protected
*/
function &_getCurrentTestCase() {
return SimpleTest::getCurrent();
}
/**
* Die if bad arguments array is passed
* @param mixed $args The arguments value to be checked.
* @param string $task Description of task attempt.
* @return boolean Valid arguments
* @access private
*/
function _checkArgumentsIsArray($args, $task) {
if (! is_array($args)) {
trigger_error(
"Cannot $task as \$args parameter is not an array",
E_USER_ERROR);
}
}
/**
* Triggers a PHP error if the method is not part
* of this object.
* @param string $method Name of method.
* @param string $task Description of task attempt.
* @access protected
*/
function _dieOnNoMethod($method, $task) {
if ($this->_is_strict && ! method_exists($this, $method)) {
trigger_error(
"Cannot $task as no ${method}() in class " . get_class($this),
E_USER_ERROR);
}
}
/**
* Replaces wildcard matches with wildcard
* expectations in the argument list.
* @param array $args Raw argument list.
* @return array Argument list with
* expectations.
* @access private
*/
function _replaceWildcards($args) {
if ($args === false) {
return false;
}
for ($i = 0; $i < count($args); $i++) {
if ($args[$i] === $this->_wildcard) {
$args[$i] = new AnythingExpectation();
}
}
return $args;
}
/**
* Adds one to the call count of a method.
* @param string $method Method called.
* @param array $args Arguments as an array.
* @access protected
*/
function _addCall($method, $args) {
if (!isset($this->_call_counts[$method])) {
$this->_call_counts[$method] = 0;
}
$this->_call_counts[$method]++;
}
/**
* Fetches the call count of a method so far.
* @param string $method Method name called.
* @return Number of calls so far.
* @access public
*/
function getCallCount($method) {
$this->_dieOnNoMethod($method, "get call count");
$method = strtolower($method);
if (! isset($this->_call_counts[$method])) {
return 0;
}
return $this->_call_counts[$method];
}
/**
* Sets a return for a parameter list that will
* be passed by value for all calls to this method.
* @param string $method Method name.
* @param mixed $value Result of call passed by value.
* @param array $args List of parameters to match
* including wildcards.
* @access public
*/
function setReturnValue($method, $value, $args = false) {
$this->_dieOnNoMethod($method, "set return value");
$args = $this->_replaceWildcards($args);
$method = strtolower($method);
if (! isset($this->_returns[$method])) {
$this->_returns[$method] = new CallMap();
}
$this->_returns[$method]->addValue($args, $value);
}
/**
* Sets a return for a parameter list that will
* be passed by value only when the required call count
* is reached.
* @param integer $timing Number of calls in the future
* to which the result applies. If
* not set then all calls will return
* the value.
* @param string $method Method name.
* @param mixed $value Result of call passed by value.
* @param array $args List of parameters to match
* including wildcards.
* @access public
*/
function setReturnValueAt($timing, $method, $value, $args = false) {
$this->_dieOnNoMethod($method, "set return value sequence");
$args = $this->_replaceWildcards($args);
$method = strtolower($method);
if (! isset($this->_return_sequence[$method])) {
$this->_return_sequence[$method] = array();
}
if (! isset($this->_return_sequence[$method][$timing])) {
$this->_return_sequence[$method][$timing] = new CallMap();
}
$this->_return_sequence[$method][$timing]->addValue($args, $value);
}
/**
* Sets a return for a parameter list that will
* be passed by reference for all calls.
* @param string $method Method name.
* @param mixed $reference Result of the call will be this object.
* @param array $args List of parameters to match
* including wildcards.
* @access public
*/
function setReturnReference($method, &$reference, $args = false) {
$this->_dieOnNoMethod($method, "set return reference");
$args = $this->_replaceWildcards($args);
$method = strtolower($method);
if (! isset($this->_returns[$method])) {
$this->_returns[$method] = new CallMap();
}
$this->_returns[$method]->addReference($args, $reference);
}
/**
* Sets a return for a parameter list that will
* be passed by value only when the required call count
* is reached.
* @param integer $timing Number of calls in the future
* to which the result applies. If
* not set then all calls will return
* the value.
* @param string $method Method name.
* @param mixed $reference Result of the call will be this object.
* @param array $args List of parameters to match
* including wildcards.
* @access public
*/
function setReturnReferenceAt($timing, $method, &$reference, $args = false) {
$this->_dieOnNoMethod($method, "set return reference sequence");
$args = $this->_replaceWildcards($args);
$method = strtolower($method);
if (! isset($this->_return_sequence[$method])) {
$this->_return_sequence[$method] = array();
}
if (! isset($this->_return_sequence[$method][$timing])) {
$this->_return_sequence[$method][$timing] = new CallMap();
}
$this->_return_sequence[$method][$timing]->addReference($args, $reference);
}
/**
* Sets up an expected call with a set of
* expected parameters in that call. All
* calls will be compared to these expectations
* regardless of when the call is made.
* @param string $method Method call to test.
* @param array $args Expected parameters for the call
* including wildcards.
* @param string $message Overridden message.
* @access public
*/
function expect($method, $args, $message = '%s') {
$this->_dieOnNoMethod($method, 'set expected arguments');
$this->_checkArgumentsIsArray($args, 'set expected arguments');
$args = $this->_replaceWildcards($args);
$message .= Mock::getExpectationLine();
$this->_expected_args[strtolower($method)] =
new ParametersExpectation($args, $message);
}
/**
* @deprecated
*/
function expectArguments($method, $args, $message = '%s') {
return $this->expect($method, $args, $message);
}
/**
* Sets up an expected call with a set of
* expected parameters in that call. The
* expected call count will be adjusted if it
* is set too low to reach this call.
* @param integer $timing Number of calls in the future at
* which to test. Next call is 0.
* @param string $method Method call to test.
* @param array $args Expected parameters for the call
* including wildcards.
* @param string $message Overridden message.
* @access public
*/
function expectAt($timing, $method, $args, $message = '%s') {
$this->_dieOnNoMethod($method, 'set expected arguments at time');
$this->_checkArgumentsIsArray($args, 'set expected arguments at time');
$args = $this->_replaceWildcards($args);
if (! isset($this->_expected_args_at[$timing])) {
$this->_expected_args_at[$timing] = array();
}
$method = strtolower($method);
$message .= Mock::getExpectationLine();
$this->_expected_args_at[$timing][$method] =
new ParametersExpectation($args, $message);
}
/**
* @deprecated
*/
function expectArgumentsAt($timing, $method, $args, $message = '%s') {
return $this->expectAt($timing, $method, $args, $message);
}
/**
* Sets an expectation for the number of times
* a method will be called. The tally method
* is used to check this.
* @param string $method Method call to test.
* @param integer $count Number of times it should
* have been called at tally.
* @param string $message Overridden message.
* @access public
*/
function expectCallCount($method, $count, $message = '%s') {
$this->_dieOnNoMethod($method, 'set expected call count');
$message .= Mock::getExpectationLine();
$this->_expected_counts[strtolower($method)] =
new CallCountExpectation($method, $count, $message);
}
/**
* Sets the number of times a method may be called
* before a test failure is triggered.
* @param string $method Method call to test.
* @param integer $count Most number of times it should
* have been called.
* @param string $message Overridden message.
* @access public
*/
function expectMaximumCallCount($method, $count, $message = '%s') {
$this->_dieOnNoMethod($method, 'set maximum call count');
$message .= Mock::getExpectationLine();
$this->_max_counts[strtolower($method)] =
new MaximumCallCountExpectation($method, $count, $message);
}
/**
* Sets the number of times to call a method to prevent
* a failure on the tally.
* @param string $method Method call to test.
* @param integer $count Least number of times it should
* have been called.
* @param string $message Overridden message.
* @access public
*/
function expectMinimumCallCount($method, $count, $message = '%s') {
$this->_dieOnNoMethod($method, 'set minimum call count');
$message .= Mock::getExpectationLine();
$this->_expected_counts[strtolower($method)] =
new MinimumCallCountExpectation($method, $count, $message);
}
/**
* Convenience method for barring a method
* call.
* @param string $method Method call to ban.
* @param string $message Overridden message.
* @access public
*/
function expectNever($method, $message = '%s') {
$this->expectMaximumCallCount($method, 0, $message);
}
/**
* Convenience method for a single method
* call.
* @param string $method Method call to track.
* @param array $args Expected argument list or
* false for any arguments.
* @param string $message Overridden message.
* @access public
*/
function expectOnce($method, $args = false, $message = '%s') {
$this->expectCallCount($method, 1, $message);
if ($args !== false) {
$this->expectArguments($method, $args, $message);
}
}
/**
* Convenience method for requiring a method
* call.
* @param string $method Method call to track.
* @param array $args Expected argument list or
* false for any arguments.
* @param string $message Overridden message.
* @access public
*/
function expectAtLeastOnce($method, $args = false, $message = '%s') {
$this->expectMinimumCallCount($method, 1, $message);
if ($args !== false) {
$this->expectArguments($method, $args, $message);
}
}
/**
* @deprecated
*/
function tally() {
}
/**
* Receives event from unit test that the current
* test method has finished. Totals up the call
* counts and triggers a test assertion if a test
* is present for expected call counts.
* @param string $method Current method name.
* @access public
*/
function atTestEnd($method) {
foreach ($this->_expected_counts as $method => $expectation) {
$this->_assertTrue(
$expectation->test($this->getCallCount($method)),
$expectation->overlayMessage($this->getCallCount($method)));
}
foreach ($this->_max_counts as $method => $expectation) {
if ($expectation->test($this->getCallCount($method))) {
$this->_assertTrue(
true,
$expectation->overlayMessage($this->getCallCount($method)));
}
}
}
/**
* Returns the expected value for the method name
* and checks expectations. Will generate any
* test assertions as a result of expectations
* if there is a test present.
* @param string $method Name of method to simulate.
* @param array $args Arguments as an array.
* @return mixed Stored return.
* @access private
*/
function &_invoke($method, $args) {
$method = strtolower($method);
$step = $this->getCallCount($method);
$this->_addCall($method, $args);
$this->_checkExpectations($method, $args, $step);
$result = &$this->_getReturn($method, $args, $step);
return $result;
}
/**
* Finds the return value matching the incoming
* arguments. If there is no matching value found
* then an error is triggered.
* @param string $method Method name.
* @param array $args Calling arguments.
* @param integer $step Current position in the
* call history.
* @return mixed Stored return.
* @access protected
*/
function &_getReturn($method, $args, $step) {
if (isset($this->_return_sequence[$method][$step])) {
if ($this->_return_sequence[$method][$step]->isMatch($args)) {
$result = &$this->_return_sequence[$method][$step]->findFirstMatch($args);
return $result;
}
}
if (isset($this->_returns[$method])) {
$result = &$this->_returns[$method]->findFirstMatch($args);
return $result;
}
$null = null;
return $null;
}
/**
* Tests the arguments against expectations.
* @param string $method Method to check.
* @param array $args Argument list to match.
* @param integer $timing The position of this call
* in the call history.
* @access private
*/
function _checkExpectations($method, $args, $timing) {
if (isset($this->_max_counts[$method])) {
if (! $this->_max_counts[$method]->test($timing + 1)) {
$this->_assertTrue(
false,
$this->_max_counts[$method]->overlayMessage($timing + 1));
}
}
if (isset($this->_expected_args_at[$timing][$method])) {
$this->_assertTrue(
$this->_expected_args_at[$timing][$method]->test($args),
"Mock method [$method] at [$timing] -> " .
$this->_expected_args_at[$timing][$method]->overlayMessage($args));
} elseif (isset($this->_expected_args[$method])) {
$this->_assertTrue(
$this->_expected_args[$method]->test($args),
"Mock method [$method] -> " . $this->_expected_args[$method]->overlayMessage($args));
}
}
/**
* Triggers an assertion on the held test case.
* Should be overridden when using another test
* framework other than the SimpleTest one if the
* assertion method has a different name.
* @param boolean $assertion True will pass.
* @param string $message Message that will go with
* the test event.
* @access protected
*/
function _assertTrue($assertion, $message) {
$test = &$this->_getCurrentTestCase();
$test->assertTrue($assertion, $message);
}
}
/**
* Static methods only service class for code generation of
* mock objects.
* @package SimpleTest
* @subpackage MockObjects
*/
class Mock {
/**
* Factory for mock object classes.
* @access public
*/
function Mock() {
trigger_error('Mock factory methods are class only.');
}
/**
* Clones a class' interface and creates a mock version
* that can have return values and expectations set.
* @param string $class Class to clone.
* @param string $mock_class New class name. Default is
* the old name with "Mock"
* prepended.
* @param array $methods Additional methods to add beyond
* those in th cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @static
* @access public
*/
function generate($class, $mock_class = false, $methods = false) {
$generator = new MockGenerator($class, $mock_class);
return $generator->generate($methods);
}
/**
* Generates a version of a class with selected
* methods mocked only. Inherits the old class
* and chains the mock methods of an aggregated
* mock object.
* @param string $class Class to clone.
* @param string $mock_class New class name.
* @param array $methods Methods to be overridden
* with mock versions.
* @static
* @access public
*/
function generatePartial($class, $mock_class, $methods) {
$generator = new MockGenerator($class, $mock_class);
return $generator->generatePartial($methods);
}
/**
* Uses a stack trace to find the line of an assertion.
* @param array $stack Stack frames top most first. Only
* needed if not using the PHP
* backtrace function.
* @return string Location of first expect*
* method embedded in format string.
* @access public
* @static
*/
function getExpectationLine($stack = false) {
if ($stack === false) {
$stack = SimpleTestCompatibility::getStackTrace();
}
return SimpleDumper::getFormattedAssertionLine($stack);
}
}
/**
* @deprecated
*/
class Stub extends Mock {
}
/**
* Service class for code generation of mock objects.
* @package SimpleTest
* @subpackage MockObjects
*/
class MockGenerator {
var $_class;
var $_mock_class;
var $_mock_base;
var $_reflection;
function MockGenerator($class, $mock_class) {
$this->_class = $class;
$this->_mock_class = $mock_class;
$this->_mock_base = SimpleTest::getMockBaseClass();
$this->_reflection = new SimpleReflection($this->_class);
}
/**
* Clones a class' interface and creates a mock version
* that can have return values and expectations set.
* @param array $methods Additional methods to add beyond
* those in th cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @access public
*/
function generate($methods) {
if (! $this->_reflection->classOrInterfaceExists()) {
return false;
}
if (! $this->_mock_class) {
$this->_mock_class = 'Mock' . $this->_class;
}
$mock_reflection = new SimpleReflection($this->_mock_class);
if ($mock_reflection->classExistsSansAutoload()) {
return false;
}
return eval(
$this->_createClassCode($methods ? $methods : array()) .
" return true;");
}
/**
* Generates a version of a class with selected
* methods mocked only. Inherits the old class
* and chains the mock methods of an aggregated
* mock object.
* @param array $methods Methods to be overridden
* with mock versions.
* @access public
*/
function generatePartial($methods) {
if (! $this->_reflection->classExists($this->_class)) {
return false;
}
$mock_reflection = new SimpleReflection($this->_mock_class);
if ($mock_reflection->classExistsSansAutoload()) {
trigger_error("Partial mock class [$mock_class] already exists");
return false;
}
return eval($this->_extendClassCode($methods));
}
/**
* The new mock class code as a string.
* @param array $methods Additional methods.
* @return string Code for new mock class.
* @access private
*/
function _createClassCode($methods) {
$implements = '';
$interfaces = $this->_reflection->getInterfaces();
if (function_exists('spl_classes')) {
$interfaces = array_diff($interfaces, array('Traversable'));
}
if (count($interfaces) > 0) {
$implements = 'implements ' . implode(', ', $interfaces);
}
$code = "class " . $this->_mock_class . " extends " . $this->_mock_base . " $implements {\n";
$code .= " function " . $this->_mock_class . "() {\n";
$code .= " \$this->" . $this->_mock_base . "();\n";
$code .= " }\n";
$code .= $this->_createHandlerCode($methods);
$code .= "}\n";
return $code;
}
/**
* The extension class code as a string. The class
* composites a mock object and chains mocked methods
* to it.
* @param array $methods Mocked methods.
* @return string Code for a new class.
* @access private
*/
function _extendClassCode($methods) {
$code = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
$code .= " var \$_mock;\n";
$code .= $this->_addMethodList($methods);
$code .= "\n";
$code .= " function " . $this->_mock_class . "() {\n";
$code .= " \$this->_mock = &new " . $this->_mock_base . "();\n";
$code .= " \$this->_mock->disableExpectationNameChecks();\n";
$code .= " }\n";
$code .= $this->_chainMockReturns();
$code .= $this->_chainMockExpectations();
$code .= $this->_overrideMethods($methods);
$code .= "}\n";
return $code;
}
/**
* Creates code within a class to generate replaced
* methods. All methods call the _invoke() handler
* with the method name and the arguments in an
* array.
* @param array $methods Additional methods.
* @access private
*/
function _createHandlerCode($methods) {
$code = '';
$methods = array_merge($methods, $this->_reflection->getMethods());
foreach ($methods as $method) {
if ($this->_isConstructor($method)) {
continue;
}
$mock_reflection = new SimpleReflection($this->_mock_base);
if (in_array($method, $mock_reflection->getMethods())) {
continue;
}
$code .= " " . $this->_reflection->getSignature($method) . " {\n";
$code .= " \$args = func_get_args();\n";
$code .= " \$result = &\$this->_invoke(\"$method\", \$args);\n";
$code .= " return \$result;\n";
$code .= " }\n";
}
return $code;
}
/**
* Tests to see if a special PHP method is about to
* be stubbed by mistake.
* @param string $method Method name.
* @return boolean True if special.
* @access private
*/
function _isConstructor($method) {
return in_array(
strtolower($method),
array('__construct', '__destruct', '__clone'));
}
/**
* Creates a list of mocked methods for error checking.
* @param array $methods Mocked methods.
* @return string Code for a method list.
* @access private
*/
function _addMethodList($methods) {
return " var \$_mocked_methods = array('" . implode("', '", $methods) . "');\n";
}
/**
* Creates code to abandon the expectation if not mocked.
* @param string $alias Parameter name of method name.
* @return string Code for bail out.
* @access private
*/
function _bailOutIfNotMocked($alias) {
$code = " if (! in_array($alias, \$this->_mocked_methods)) {\n";
$code .= " trigger_error(\"Method [$alias] is not mocked\");\n";
$code .= " \$null = null;\n";
$code .= " return \$null;\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code for chaining to the composited
* mock object.
* @return string Code for mock set up.
* @access private
*/
function _chainMockReturns() {
$code = " function setReturnValue(\$method, \$value, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnValue(\$method, \$value, \$args);\n";
$code .= " }\n";
$code .= " function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n";
$code .= " }\n";
$code .= " function setReturnReference(\$method, &\$ref, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n";
$code .= " }\n";
$code .= " function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code for chaining to an aggregated
* mock object.
* @return string Code for expectations.
* @access private
*/
function _chainMockExpectations() {
$code = " function expect(\$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expect(\$method, \$args);\n";
$code .= " }\n";
$code .= " function expectArguments(\$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArguments(\$method, \$args);\n";
$code .= " }\n";
$code .= " function expectAt(\$timing, \$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args);\n";
$code .= " }\n";
$code .= " function expectArgumentsAt(\$timing, \$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args);\n";
$code .= " }\n";
$code .= " function expectCallCount(\$method, \$count) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectCallCount(\$method, \$count);\n";
$code .= " }\n";
$code .= " function expectMaximumCallCount(\$method, \$count) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectMaximumCallCount(\$method, \$count);\n";
$code .= " }\n";
$code .= " function expectMinimumCallCount(\$method, \$count) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectMinimumCallCount(\$method, \$count);\n";
$code .= " }\n";
$code .= " function expectNever(\$method) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectNever(\$method);\n";
$code .= " }\n";
$code .= " function expectOnce(\$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectOnce(\$method, \$args);\n";
$code .= " }\n";
$code .= " function expectAtLeastOnce(\$method, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectAtLeastOnce(\$method, \$args);\n";
$code .= " }\n";
$code .= " function tally() {\n";
$code .= " \$this->_mock->tally();\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code to override a list of methods
* with mock versions.
* @param array $methods Methods to be overridden
* with mock versions.
* @return string Code for overridden chains.
* @access private
*/
function _overrideMethods($methods) {
$code = "";
foreach ($methods as $method) {
$code .= " " . $this->_reflection->getSignature($method) . " {\n";
$code .= " \$args = func_get_args();\n";
$code .= " \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
$code .= " return \$result;\n";
$code .= " }\n";
}
return $code;
}
}
?>

View File

@ -1,975 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/http.php');
require_once(dirname(__FILE__) . '/parser.php');
require_once(dirname(__FILE__) . '/tag.php');
require_once(dirname(__FILE__) . '/form.php');
require_once(dirname(__FILE__) . '/selector.php');
/**#@-*/
/**
* Creates tags and widgets given HTML tag
* attributes.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTagBuilder {
/**
* Factory for the tag objects. Creates the
* appropriate tag object for the incoming tag name
* and attributes.
* @param string $name HTML tag name.
* @param hash $attributes Element attributes.
* @return SimpleTag Tag object.
* @access public
*/
function createTag($name, $attributes) {
static $map = array(
'a' => 'SimpleAnchorTag',
'title' => 'SimpleTitleTag',
'button' => 'SimpleButtonTag',
'textarea' => 'SimpleTextAreaTag',
'option' => 'SimpleOptionTag',
'label' => 'SimpleLabelTag',
'form' => 'SimpleFormTag',
'frame' => 'SimpleFrameTag');
$attributes = $this->_keysToLowerCase($attributes);
if (array_key_exists($name, $map)) {
$tag_class = $map[$name];
return new $tag_class($attributes);
} elseif ($name == 'select') {
return $this->_createSelectionTag($attributes);
} elseif ($name == 'input') {
return $this->_createInputTag($attributes);
}
return new SimpleTag($name, $attributes);
}
/**
* Factory for selection fields.
* @param hash $attributes Element attributes.
* @return SimpleTag Tag object.
* @access protected
*/
function _createSelectionTag($attributes) {
if (isset($attributes['multiple'])) {
return new MultipleSelectionTag($attributes);
}
return new SimpleSelectionTag($attributes);
}
/**
* Factory for input tags.
* @param hash $attributes Element attributes.
* @return SimpleTag Tag object.
* @access protected
*/
function _createInputTag($attributes) {
if (! isset($attributes['type'])) {
return new SimpleTextTag($attributes);
}
$type = strtolower(trim($attributes['type']));
$map = array(
'submit' => 'SimpleSubmitTag',
'image' => 'SimpleImageSubmitTag',
'checkbox' => 'SimpleCheckboxTag',
'radio' => 'SimpleRadioButtonTag',
'text' => 'SimpleTextTag',
'hidden' => 'SimpleTextTag',
'password' => 'SimpleTextTag',
'file' => 'SimpleUploadTag');
if (array_key_exists($type, $map)) {
$tag_class = $map[$type];
return new $tag_class($attributes);
}
return false;
}
/**
* Make the keys lower case for case insensitive look-ups.
* @param hash $map Hash to convert.
* @return hash Unchanged values, but keys lower case.
* @access private
*/
function _keysToLowerCase($map) {
$lower = array();
foreach ($map as $key => $value) {
$lower[strtolower($key)] = $value;
}
return $lower;
}
}
/**
* SAX event handler. Maintains a list of
* open tags and dispatches them as they close.
* @package SimpleTest
* @subpackage WebTester
*/
class SimplePageBuilder extends SimpleSaxListener {
var $_tags;
var $_page;
var $_private_content_tag;
/**
* Sets the builder up empty.
* @access public
*/
function SimplePageBuilder() {
$this->SimpleSaxListener();
}
/**
* Frees up any references so as to allow the PHP garbage
* collection from unset() to work.
* @access public
*/
function free() {
unset($this->_tags);
unset($this->_page);
unset($this->_private_content_tags);
}
/**
* Reads the raw content and send events
* into the page to be built.
* @param $response SimpleHttpResponse Fetched response.
* @return SimplePage Newly parsed page.
* @access public
*/
function &parse($response) {
$this->_tags = array();
$this->_page = &$this->_createPage($response);
$parser = &$this->_createParser($this);
$parser->parse($response->getContent());
$this->_page->acceptPageEnd();
return $this->_page;
}
/**
* Creates an empty page.
* @return SimplePage New unparsed page.
* @access protected
*/
function &_createPage($response) {
$page = &new SimplePage($response);
return $page;
}
/**
* Creates the parser used with the builder.
* @param $listener SimpleSaxListener Target of parser.
* @return SimpleSaxParser Parser to generate
* events for the builder.
* @access protected
*/
function &_createParser(&$listener) {
$parser = &new SimpleHtmlSaxParser($listener);
return $parser;
}
/**
* Start of element event. Opens a new tag.
* @param string $name Element name.
* @param hash $attributes Attributes without content
* are marked as true.
* @return boolean False on parse error.
* @access public
*/
function startElement($name, $attributes) {
$factory = &new SimpleTagBuilder();
$tag = $factory->createTag($name, $attributes);
if (! $tag) {
return true;
}
if ($tag->getTagName() == 'label') {
$this->_page->acceptLabelStart($tag);
$this->_openTag($tag);
return true;
}
if ($tag->getTagName() == 'form') {
$this->_page->acceptFormStart($tag);
return true;
}
if ($tag->getTagName() == 'frameset') {
$this->_page->acceptFramesetStart($tag);
return true;
}
if ($tag->getTagName() == 'frame') {
$this->_page->acceptFrame($tag);
return true;
}
if ($tag->isPrivateContent() && ! isset($this->_private_content_tag)) {
$this->_private_content_tag = &$tag;
}
if ($tag->expectEndTag()) {
$this->_openTag($tag);
return true;
}
$this->_page->acceptTag($tag);
return true;
}
/**
* End of element event.
* @param string $name Element name.
* @return boolean False on parse error.
* @access public
*/
function endElement($name) {
if ($name == 'label') {
$this->_page->acceptLabelEnd();
return true;
}
if ($name == 'form') {
$this->_page->acceptFormEnd();
return true;
}
if ($name == 'frameset') {
$this->_page->acceptFramesetEnd();
return true;
}
if ($this->_hasNamedTagOnOpenTagStack($name)) {
$tag = array_pop($this->_tags[$name]);
if ($tag->isPrivateContent() && $this->_private_content_tag->getTagName() == $name) {
unset($this->_private_content_tag);
}
$this->_addContentTagToOpenTags($tag);
$this->_page->acceptTag($tag);
return true;
}
return true;
}
/**
* Test to see if there are any open tags awaiting
* closure that match the tag name.
* @param string $name Element name.
* @return boolean True if any are still open.
* @access private
*/
function _hasNamedTagOnOpenTagStack($name) {
return isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0);
}
/**
* Unparsed, but relevant data. The data is added
* to every open tag.
* @param string $text May include unparsed tags.
* @return boolean False on parse error.
* @access public
*/
function addContent($text) {
if (isset($this->_private_content_tag)) {
$this->_private_content_tag->addContent($text);
} else {
$this->_addContentToAllOpenTags($text);
}
return true;
}
/**
* Any content fills all currently open tags unless it
* is part of an option tag.
* @param string $text May include unparsed tags.
* @access private
*/
function _addContentToAllOpenTags($text) {
foreach (array_keys($this->_tags) as $name) {
for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
$this->_tags[$name][$i]->addContent($text);
}
}
}
/**
* Parsed data in tag form. The parsed tag is added
* to every open tag. Used for adding options to select
* fields only.
* @param SimpleTag $tag Option tags only.
* @access private
*/
function _addContentTagToOpenTags(&$tag) {
if ($tag->getTagName() != 'option') {
return;
}
foreach (array_keys($this->_tags) as $name) {
for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
$this->_tags[$name][$i]->addTag($tag);
}
}
}
/**
* Opens a tag for receiving content. Multiple tags
* will be receiving input at the same time.
* @param SimpleTag $tag New content tag.
* @access private
*/
function _openTag(&$tag) {
$name = $tag->getTagName();
if (! in_array($name, array_keys($this->_tags))) {
$this->_tags[$name] = array();
}
$this->_tags[$name][] = &$tag;
}
}
/**
* A wrapper for a web page.
* @package SimpleTest
* @subpackage WebTester
*/
class SimplePage {
var $_links;
var $_title;
var $_last_widget;
var $_label;
var $_left_over_labels;
var $_open_forms;
var $_complete_forms;
var $_frameset;
var $_frames;
var $_frameset_nesting_level;
var $_transport_error;
var $_raw;
var $_text;
var $_sent;
var $_headers;
var $_method;
var $_url;
var $_request_data;
/**
* Parses a page ready to access it's contents.
* @param SimpleHttpResponse $response Result of HTTP fetch.
* @access public
*/
function SimplePage($response = false) {
$this->_links = array();
$this->_title = false;
$this->_left_over_labels = array();
$this->_open_forms = array();
$this->_complete_forms = array();
$this->_frameset = false;
$this->_frames = array();
$this->_frameset_nesting_level = 0;
$this->_text = false;
if ($response) {
$this->_extractResponse($response);
} else {
$this->_noResponse();
}
}
/**
* Extracts all of the response information.
* @param SimpleHttpResponse $response Response being parsed.
* @access private
*/
function _extractResponse($response) {
$this->_transport_error = $response->getError();
$this->_raw = $response->getContent();
$this->_sent = $response->getSent();
$this->_headers = $response->getHeaders();
$this->_method = $response->getMethod();
$this->_url = $response->getUrl();
$this->_request_data = $response->getRequestData();
}
/**
* Sets up a missing response.
* @access private
*/
function _noResponse() {
$this->_transport_error = 'No page fetched yet';
$this->_raw = false;
$this->_sent = false;
$this->_headers = false;
$this->_method = 'GET';
$this->_url = false;
$this->_request_data = false;
}
/**
* Original request as bytes sent down the wire.
* @return mixed Sent content.
* @access public
*/
function getRequest() {
return $this->_sent;
}
/**
* Accessor for raw text of page.
* @return string Raw unparsed content.
* @access public
*/
function getRaw() {
return $this->_raw;
}
/**
* Accessor for plain text of page as a text browser
* would see it.
* @return string Plain text of page.
* @access public
*/
function getText() {
if (! $this->_text) {
$this->_text = SimpleHtmlSaxParser::normalise($this->_raw);
}
return $this->_text;
}
/**
* Accessor for raw headers of page.
* @return string Header block as text.
* @access public
*/
function getHeaders() {
if ($this->_headers) {
return $this->_headers->getRaw();
}
return false;
}
/**
* Original request method.
* @return string GET, POST or HEAD.
* @access public
*/
function getMethod() {
return $this->_method;
}
/**
* Original resource name.
* @return SimpleUrl Current url.
* @access public
*/
function getUrl() {
return $this->_url;
}
/**
* Original request data.
* @return mixed Sent content.
* @access public
*/
function getRequestData() {
return $this->_request_data;
}
/**
* Accessor for last error.
* @return string Error from last response.
* @access public
*/
function getTransportError() {
return $this->_transport_error;
}
/**
* Accessor for current MIME type.
* @return string MIME type as string; e.g. 'text/html'
* @access public
*/
function getMimeType() {
if ($this->_headers) {
return $this->_headers->getMimeType();
}
return false;
}
/**
* Accessor for HTTP response code.
* @return integer HTTP response code received.
* @access public
*/
function getResponseCode() {
if ($this->_headers) {
return $this->_headers->getResponseCode();
}
return false;
}
/**
* Accessor for last Authentication type. Only valid
* straight after a challenge (401).
* @return string Description of challenge type.
* @access public
*/
function getAuthentication() {
if ($this->_headers) {
return $this->_headers->getAuthentication();
}
return false;
}
/**
* Accessor for last Authentication realm. Only valid
* straight after a challenge (401).
* @return string Name of security realm.
* @access public
*/
function getRealm() {
if ($this->_headers) {
return $this->_headers->getRealm();
}
return false;
}
/**
* Accessor for current frame focus. Will be
* false as no frames.
* @return array Always empty.
* @access public
*/
function getFrameFocus() {
return array();
}
/**
* Sets the focus by index. The integer index starts from 1.
* @param integer $choice Chosen frame.
* @return boolean Always false.
* @access public
*/
function setFrameFocusByIndex($choice) {
return false;
}
/**
* Sets the focus by name. Always fails for a leaf page.
* @param string $name Chosen frame.
* @return boolean False as no frames.
* @access public
*/
function setFrameFocus($name) {
return false;
}
/**
* Clears the frame focus. Does nothing for a leaf page.
* @access public
*/
function clearFrameFocus() {
}
/**
* Adds a tag to the page.
* @param SimpleTag $tag Tag to accept.
* @access public
*/
function acceptTag(&$tag) {
if ($tag->getTagName() == "a") {
$this->_addLink($tag);
} elseif ($tag->getTagName() == "title") {
$this->_setTitle($tag);
} elseif ($this->_isFormElement($tag->getTagName())) {
for ($i = 0; $i < count($this->_open_forms); $i++) {
$this->_open_forms[$i]->addWidget($tag);
}
$this->_last_widget = &$tag;
}
}
/**
* Opens a label for a described widget.
* @param SimpleFormTag $tag Tag to accept.
* @access public
*/
function acceptLabelStart(&$tag) {
$this->_label = &$tag;
unset($this->_last_widget);
}
/**
* Closes the most recently opened label.
* @access public
*/
function acceptLabelEnd() {
if (isset($this->_label)) {
if (isset($this->_last_widget)) {
$this->_last_widget->setLabel($this->_label->getText());
unset($this->_last_widget);
} else {
$this->_left_over_labels[] = SimpleTestCompatibility::copy($this->_label);
}
unset($this->_label);
}
}
/**
* Tests to see if a tag is a possible form
* element.
* @param string $name HTML element name.
* @return boolean True if form element.
* @access private
*/
function _isFormElement($name) {
return in_array($name, array('input', 'button', 'textarea', 'select'));
}
/**
* Opens a form. New widgets go here.
* @param SimpleFormTag $tag Tag to accept.
* @access public
*/
function acceptFormStart(&$tag) {
$this->_open_forms[] = &new SimpleForm($tag, $this->getUrl());
}
/**
* Closes the most recently opened form.
* @access public
*/
function acceptFormEnd() {
if (count($this->_open_forms)) {
$this->_complete_forms[] = array_pop($this->_open_forms);
}
}
/**
* Opens a frameset. A frameset may contain nested
* frameset tags.
* @param SimpleFramesetTag $tag Tag to accept.
* @access public
*/
function acceptFramesetStart(&$tag) {
if (! $this->_isLoadingFrames()) {
$this->_frameset = &$tag;
}
$this->_frameset_nesting_level++;
}
/**
* Closes the most recently opened frameset.
* @access public
*/
function acceptFramesetEnd() {
if ($this->_isLoadingFrames()) {
$this->_frameset_nesting_level--;
}
}
/**
* Takes a single frame tag and stashes it in
* the current frame set.
* @param SimpleFrameTag $tag Tag to accept.
* @access public
*/
function acceptFrame(&$tag) {
if ($this->_isLoadingFrames()) {
if ($tag->getAttribute('src')) {
$this->_frames[] = &$tag;
}
}
}
/**
* Test to see if in the middle of reading
* a frameset.
* @return boolean True if inframeset.
* @access private
*/
function _isLoadingFrames() {
if (! $this->_frameset) {
return false;
}
return ($this->_frameset_nesting_level > 0);
}
/**
* Test to see if link is an absolute one.
* @param string $url Url to test.
* @return boolean True if absolute.
* @access protected
*/
function _linkIsAbsolute($url) {
$parsed = new SimpleUrl($url);
return (boolean)($parsed->getScheme() && $parsed->getHost());
}
/**
* Adds a link to the page.
* @param SimpleAnchorTag $tag Link to accept.
* @access protected
*/
function _addLink($tag) {
$this->_links[] = $tag;
}
/**
* Marker for end of complete page. Any work in
* progress can now be closed.
* @access public
*/
function acceptPageEnd() {
while (count($this->_open_forms)) {
$this->_complete_forms[] = array_pop($this->_open_forms);
}
foreach ($this->_left_over_labels as $label) {
for ($i = 0, $count = count($this->_complete_forms); $i < $count; $i++) {
$this->_complete_forms[$i]->attachLabelBySelector(
new SimpleById($label->getFor()),
$label->getText());
}
}
}
/**
* Test for the presence of a frameset.
* @return boolean True if frameset.
* @access public
*/
function hasFrames() {
return (boolean)$this->_frameset;
}
/**
* Accessor for frame name and source URL for every frame that
* will need to be loaded. Immediate children only.
* @return boolean/array False if no frameset or
* otherwise a hash of frame URLs.
* The key is either a numerical
* base one index or the name attribute.
* @access public
*/
function getFrameset() {
if (! $this->_frameset) {
return false;
}
$urls = array();
for ($i = 0; $i < count($this->_frames); $i++) {
$name = $this->_frames[$i]->getAttribute('name');
$url = new SimpleUrl($this->_frames[$i]->getAttribute('src'));
$urls[$name ? $name : $i + 1] = $url->makeAbsolute($this->getUrl());
}
return $urls;
}
/**
* Fetches a list of loaded frames.
* @return array/string Just the URL for a single page.
* @access public
*/
function getFrames() {
$url = $this->getUrl();
return $url->asString();
}
/**
* Accessor for a list of all fixed links.
* @return array List of urls with scheme of
* http or https and hostname.
* @access public
*/
function getAbsoluteUrls() {
$all = array();
foreach ($this->_links as $link) {
if ($this->_linkIsAbsolute($link->getHref())) {
$all[] = $link->getHref();
}
}
return $all;
}
/**
* Accessor for a list of all relative links.
* @return array List of urls without hostname.
* @access public
*/
function getRelativeUrls() {
$all = array();
foreach ($this->_links as $link) {
if (! $this->_linkIsAbsolute($link->getHref())) {
$all[] = $link->getHref();
}
}
return $all;
}
/**
* Accessor for URLs by the link label. Label will match
* regardess of whitespace issues and case.
* @param string $label Text of link.
* @return array List of links with that label.
* @access public
*/
function getUrlsByLabel($label) {
$matches = array();
foreach ($this->_links as $link) {
if ($link->getText() == $label) {
$matches[] = $this->_getUrlFromLink($link);
}
}
return $matches;
}
/**
* Accessor for a URL by the id attribute.
* @param string $id Id attribute of link.
* @return SimpleUrl URL with that id of false if none.
* @access public
*/
function getUrlById($id) {
foreach ($this->_links as $link) {
if ($link->getAttribute('id') === (string)$id) {
return $this->_getUrlFromLink($link);
}
}
return false;
}
/**
* Converts a link into a target URL.
* @param SimpleAnchor $link Parsed link.
* @return SimpleUrl URL with frame target if any.
* @access private
*/
function _getUrlFromLink($link) {
$url = $this->_makeAbsolute($link->getHref());
if ($link->getAttribute('target')) {
$url->setTarget($link->getAttribute('target'));
}
return $url;
}
/**
* Expands expandomatic URLs into fully qualified
* URLs.
* @param SimpleUrl $url Relative URL.
* @return SimpleUrl Absolute URL.
* @access protected
*/
function _makeAbsolute($url) {
if (! is_object($url)) {
$url = new SimpleUrl($url);
}
return $url->makeAbsolute($this->getUrl());
}
/**
* Sets the title tag contents.
* @param SimpleTitleTag $tag Title of page.
* @access protected
*/
function _setTitle(&$tag) {
$this->_title = &$tag;
}
/**
* Accessor for parsed title.
* @return string Title or false if no title is present.
* @access public
*/
function getTitle() {
if ($this->_title) {
return $this->_title->getText();
}
return false;
}
/**
* Finds a held form by button label. Will only
* search correctly built forms.
* @param SimpleSelector $selector Button finder.
* @return SimpleForm Form object containing
* the button.
* @access public
*/
function &getFormBySubmit($selector) {
for ($i = 0; $i < count($this->_complete_forms); $i++) {
if ($this->_complete_forms[$i]->hasSubmit($selector)) {
return $this->_complete_forms[$i];
}
}
$null = null;
return $null;
}
/**
* Finds a held form by image using a selector.
* Will only search correctly built forms.
* @param SimpleSelector $selector Image finder.
* @return SimpleForm Form object containing
* the image.
* @access public
*/
function &getFormByImage($selector) {
for ($i = 0; $i < count($this->_complete_forms); $i++) {
if ($this->_complete_forms[$i]->hasImage($selector)) {
return $this->_complete_forms[$i];
}
}
$null = null;
return $null;
}
/**
* Finds a held form by the form ID. A way of
* identifying a specific form when we have control
* of the HTML code.
* @param string $id Form label.
* @return SimpleForm Form object containing the matching ID.
* @access public
*/
function &getFormById($id) {
for ($i = 0; $i < count($this->_complete_forms); $i++) {
if ($this->_complete_forms[$i]->getId() == $id) {
return $this->_complete_forms[$i];
}
}
$null = null;
return $null;
}
/**
* Sets a field on each form in which the field is
* available.
* @param SimpleSelector $selector Field finder.
* @param string $value Value to set field to.
* @return boolean True if value is valid.
* @access public
*/
function setField($selector, $value) {
$is_set = false;
for ($i = 0; $i < count($this->_complete_forms); $i++) {
if ($this->_complete_forms[$i]->setField($selector, $value)) {
$is_set = true;
}
}
return $is_set;
}
/**
* Accessor for a form element value within a page.
* @param SimpleSelector $selector Field finder.
* @return string/boolean A string if the field is
* present, false if unchecked
* and null if missing.
* @access public
*/
function getField($selector) {
for ($i = 0; $i < count($this->_complete_forms); $i++) {
$value = $this->_complete_forms[$i]->getValue($selector);
if (isset($value)) {
return $value;
}
}
return null;
}
}
?>

View File

@ -1,773 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage MockObjects
* @version $Id$
*/
/**#@+
* Lexer mode stack constants
*/
if (! defined('LEXER_ENTER')) {
define('LEXER_ENTER', 1);
}
if (! defined('LEXER_MATCHED')) {
define('LEXER_MATCHED', 2);
}
if (! defined('LEXER_UNMATCHED')) {
define('LEXER_UNMATCHED', 3);
}
if (! defined('LEXER_EXIT')) {
define('LEXER_EXIT', 4);
}
if (! defined('LEXER_SPECIAL')) {
define('LEXER_SPECIAL', 5);
}
/**#@-*/
/**
* Compounded regular expression. Any of
* the contained patterns could match and
* when one does, it's label is returned.
* @package SimpleTest
* @subpackage WebTester
*/
class ParallelRegex {
var $_patterns;
var $_labels;
var $_regex;
var $_case;
/**
* Constructor. Starts with no patterns.
* @param boolean $case True for case sensitive, false
* for insensitive.
* @access public
*/
function ParallelRegex($case) {
$this->_case = $case;
$this->_patterns = array();
$this->_labels = array();
$this->_regex = null;
}
/**
* Adds a pattern with an optional label.
* @param string $pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string $label Label of regex to be returned
* on a match.
* @access public
*/
function addPattern($pattern, $label = true) {
$count = count($this->_patterns);
$this->_patterns[$count] = $pattern;
$this->_labels[$count] = $label;
$this->_regex = null;
}
/**
* Attempts to match all patterns at once against
* a string.
* @param string $subject String to match against.
* @param string $match First matched portion of
* subject.
* @return boolean True on success.
* @access public
*/
function match($subject, &$match) {
if (count($this->_patterns) == 0) {
return false;
}
if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
$match = '';
return false;
}
$match = $matches[0];
for ($i = 1; $i < count($matches); $i++) {
if ($matches[$i]) {
return $this->_labels[$i - 1];
}
}
return true;
}
/**
* Compounds the patterns into a single
* regular expression separated with the
* "or" operator. Caches the regex.
* Will automatically escape (, ) and / tokens.
* @param array $patterns List of patterns in order.
* @access private
*/
function _getCompoundedRegex() {
if ($this->_regex == null) {
for ($i = 0, $count = count($this->_patterns); $i < $count; $i++) {
$this->_patterns[$i] = '(' . str_replace(
array('/', '(', ')'),
array('\/', '\(', '\)'),
$this->_patterns[$i]) . ')';
}
$this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags();
}
return $this->_regex;
}
/**
* Accessor for perl regex mode flags to use.
* @return string Perl regex flags.
* @access private
*/
function _getPerlMatchingFlags() {
return ($this->_case ? "msS" : "msSi");
}
}
/**
* States for a stack machine.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleStateStack {
var $_stack;
/**
* Constructor. Starts in named state.
* @param string $start Starting state name.
* @access public
*/
function SimpleStateStack($start) {
$this->_stack = array($start);
}
/**
* Accessor for current state.
* @return string State.
* @access public
*/
function getCurrent() {
return $this->_stack[count($this->_stack) - 1];
}
/**
* Adds a state to the stack and sets it
* to be the current state.
* @param string $state New state.
* @access public
*/
function enter($state) {
array_push($this->_stack, $state);
}
/**
* Leaves the current state and reverts
* to the previous one.
* @return boolean False if we drop off
* the bottom of the list.
* @access public
*/
function leave() {
if (count($this->_stack) == 1) {
return false;
}
array_pop($this->_stack);
return true;
}
}
/**
* Accepts text and breaks it into tokens.
* Some optimisation to make the sure the
* content is only scanned by the PHP regex
* parser once. Lexer modes must not start
* with leading underscores.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleLexer {
var $_regexes;
var $_parser;
var $_mode;
var $_mode_handlers;
var $_case;
/**
* Sets up the lexer in case insensitive matching
* by default.
* @param SimpleSaxParser $parser Handling strategy by
* reference.
* @param string $start Starting handler.
* @param boolean $case True for case sensitive.
* @access public
*/
function SimpleLexer(&$parser, $start = "accept", $case = false) {
$this->_case = $case;
$this->_regexes = array();
$this->_parser = &$parser;
$this->_mode = &new SimpleStateStack($start);
$this->_mode_handlers = array($start => $start);
}
/**
* Adds a token search pattern for a particular
* parsing mode. The pattern does not change the
* current mode.
* @param string $pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string $mode Should only apply this
* pattern when dealing with
* this type of input.
* @access public
*/
function addPattern($pattern, $mode = "accept") {
if (! isset($this->_regexes[$mode])) {
$this->_regexes[$mode] = new ParallelRegex($this->_case);
}
$this->_regexes[$mode]->addPattern($pattern);
if (! isset($this->_mode_handlers[$mode])) {
$this->_mode_handlers[$mode] = $mode;
}
}
/**
* Adds a pattern that will enter a new parsing
* mode. Useful for entering parenthesis, strings,
* tags, etc.
* @param string $pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string $mode Should only apply this
* pattern when dealing with
* this type of input.
* @param string $new_mode Change parsing to this new
* nested mode.
* @access public
*/
function addEntryPattern($pattern, $mode, $new_mode) {
if (! isset($this->_regexes[$mode])) {
$this->_regexes[$mode] = new ParallelRegex($this->_case);
}
$this->_regexes[$mode]->addPattern($pattern, $new_mode);
if (! isset($this->_mode_handlers[$new_mode])) {
$this->_mode_handlers[$new_mode] = $new_mode;
}
}
/**
* Adds a pattern that will exit the current mode
* and re-enter the previous one.
* @param string $pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string $mode Mode to leave.
* @access public
*/
function addExitPattern($pattern, $mode) {
if (! isset($this->_regexes[$mode])) {
$this->_regexes[$mode] = new ParallelRegex($this->_case);
}
$this->_regexes[$mode]->addPattern($pattern, "__exit");
if (! isset($this->_mode_handlers[$mode])) {
$this->_mode_handlers[$mode] = $mode;
}
}
/**
* Adds a pattern that has a special mode. Acts as an entry
* and exit pattern in one go, effectively calling a special
* parser handler for this token only.
* @param string $pattern Perl style regex, but ( and )
* lose the usual meaning.
* @param string $mode Should only apply this
* pattern when dealing with
* this type of input.
* @param string $special Use this mode for this one token.
* @access public
*/
function addSpecialPattern($pattern, $mode, $special) {
if (! isset($this->_regexes[$mode])) {
$this->_regexes[$mode] = new ParallelRegex($this->_case);
}
$this->_regexes[$mode]->addPattern($pattern, "_$special");
if (! isset($this->_mode_handlers[$special])) {
$this->_mode_handlers[$special] = $special;
}
}
/**
* Adds a mapping from a mode to another handler.
* @param string $mode Mode to be remapped.
* @param string $handler New target handler.
* @access public
*/
function mapHandler($mode, $handler) {
$this->_mode_handlers[$mode] = $handler;
}
/**
* Splits the page text into tokens. Will fail
* if the handlers report an error or if no
* content is consumed. If successful then each
* unparsed and parsed token invokes a call to the
* held listener.
* @param string $raw Raw HTML text.
* @return boolean True on success, else false.
* @access public
*/
function parse($raw) {
if (! isset($this->_parser)) {
return false;
}
$length = strlen($raw);
while (is_array($parsed = $this->_reduce($raw))) {
list($raw, $unmatched, $matched, $mode) = $parsed;
if (! $this->_dispatchTokens($unmatched, $matched, $mode)) {
return false;
}
if ($raw === '') {
return true;
}
if (strlen($raw) == $length) {
return false;
}
$length = strlen($raw);
}
if (! $parsed) {
return false;
}
return $this->_invokeParser($raw, LEXER_UNMATCHED);
}
/**
* Sends the matched token and any leading unmatched
* text to the parser changing the lexer to a new
* mode if one is listed.
* @param string $unmatched Unmatched leading portion.
* @param string $matched Actual token match.
* @param string $mode Mode after match. A boolean
* false mode causes no change.
* @return boolean False if there was any error
* from the parser.
* @access private
*/
function _dispatchTokens($unmatched, $matched, $mode = false) {
if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) {
return false;
}
if (is_bool($mode)) {
return $this->_invokeParser($matched, LEXER_MATCHED);
}
if ($this->_isModeEnd($mode)) {
if (! $this->_invokeParser($matched, LEXER_EXIT)) {
return false;
}
return $this->_mode->leave();
}
if ($this->_isSpecialMode($mode)) {
$this->_mode->enter($this->_decodeSpecial($mode));
if (! $this->_invokeParser($matched, LEXER_SPECIAL)) {
return false;
}
return $this->_mode->leave();
}
$this->_mode->enter($mode);
return $this->_invokeParser($matched, LEXER_ENTER);
}
/**
* Tests to see if the new mode is actually to leave
* the current mode and pop an item from the matching
* mode stack.
* @param string $mode Mode to test.
* @return boolean True if this is the exit mode.
* @access private
*/
function _isModeEnd($mode) {
return ($mode === "__exit");
}
/**
* Test to see if the mode is one where this mode
* is entered for this token only and automatically
* leaves immediately afterwoods.
* @param string $mode Mode to test.
* @return boolean True if this is the exit mode.
* @access private
*/
function _isSpecialMode($mode) {
return (strncmp($mode, "_", 1) == 0);
}
/**
* Strips the magic underscore marking single token
* modes.
* @param string $mode Mode to decode.
* @return string Underlying mode name.
* @access private
*/
function _decodeSpecial($mode) {
return substr($mode, 1);
}
/**
* Calls the parser method named after the current
* mode. Empty content will be ignored. The lexer
* has a parser handler for each mode in the lexer.
* @param string $content Text parsed.
* @param boolean $is_match Token is recognised rather
* than unparsed data.
* @access private
*/
function _invokeParser($content, $is_match) {
if (($content === '') || ($content === false)) {
return true;
}
$handler = $this->_mode_handlers[$this->_mode->getCurrent()];
return $this->_parser->$handler($content, $is_match);
}
/**
* Tries to match a chunk of text and if successful
* removes the recognised chunk and any leading
* unparsed data. Empty strings will not be matched.
* @param string $raw The subject to parse. This is the
* content that will be eaten.
* @return array/boolean Three item list of unparsed
* content followed by the
* recognised token and finally the
* action the parser is to take.
* True if no match, false if there
* is a parsing error.
* @access private
*/
function _reduce($raw) {
if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) {
$unparsed_character_count = strpos($raw, $match);
$unparsed = substr($raw, 0, $unparsed_character_count);
$raw = substr($raw, $unparsed_character_count + strlen($match));
return array($raw, $unparsed, $match, $action);
}
return true;
}
}
/**
* Breas HTML into SAX events.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHtmlLexer extends SimpleLexer {
/**
* Sets up the lexer with case insensitive matching
* and adds the HTML handlers.
* @param SimpleSaxParser $parser Handling strategy by
* reference.
* @access public
*/
function SimpleHtmlLexer(&$parser) {
$this->SimpleLexer($parser, 'text');
$this->mapHandler('text', 'acceptTextToken');
$this->_addSkipping();
foreach ($this->_getParsedTags() as $tag) {
$this->_addTag($tag);
}
$this->_addInTagTokens();
}
/**
* List of parsed tags. Others are ignored.
* @return array List of searched for tags.
* @access private
*/
function _getParsedTags() {
return array('a', 'title', 'form', 'input', 'button', 'textarea', 'select',
'option', 'frameset', 'frame', 'label');
}
/**
* The lexer has to skip certain sections such
* as server code, client code and styles.
* @access private
*/
function _addSkipping() {
$this->mapHandler('css', 'ignore');
$this->addEntryPattern('<style', 'text', 'css');
$this->addExitPattern('</style>', 'css');
$this->mapHandler('js', 'ignore');
$this->addEntryPattern('<script', 'text', 'js');
$this->addExitPattern('</script>', 'js');
$this->mapHandler('comment', 'ignore');
$this->addEntryPattern('<!--', 'text', 'comment');
$this->addExitPattern('-->', 'comment');
}
/**
* Pattern matches to start and end a tag.
* @param string $tag Name of tag to scan for.
* @access private
*/
function _addTag($tag) {
$this->addSpecialPattern("</$tag>", 'text', 'acceptEndToken');
$this->addEntryPattern("<$tag", 'text', 'tag');
}
/**
* Pattern matches to parse the inside of a tag
* including the attributes and their quoting.
* @access private
*/
function _addInTagTokens() {
$this->mapHandler('tag', 'acceptStartToken');
$this->addSpecialPattern('\s+', 'tag', 'ignore');
$this->_addAttributeTokens();
$this->addExitPattern('/>', 'tag');
$this->addExitPattern('>', 'tag');
}
/**
* Matches attributes that are either single quoted,
* double quoted or unquoted.
* @access private
*/
function _addAttributeTokens() {
$this->mapHandler('dq_attribute', 'acceptAttributeToken');
$this->addEntryPattern('=\s*"', 'tag', 'dq_attribute');
$this->addPattern("\\\\\"", 'dq_attribute');
$this->addExitPattern('"', 'dq_attribute');
$this->mapHandler('sq_attribute', 'acceptAttributeToken');
$this->addEntryPattern("=\s*'", 'tag', 'sq_attribute');
$this->addPattern("\\\\'", 'sq_attribute');
$this->addExitPattern("'", 'sq_attribute');
$this->mapHandler('uq_attribute', 'acceptAttributeToken');
$this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute');
}
}
/**
* Converts HTML tokens into selected SAX events.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleHtmlSaxParser {
var $_lexer;
var $_listener;
var $_tag;
var $_attributes;
var $_current_attribute;
/**
* Sets the listener.
* @param SimpleSaxListener $listener SAX event handler.
* @access public
*/
function SimpleHtmlSaxParser(&$listener) {
$this->_listener = &$listener;
$this->_lexer = &$this->createLexer($this);
$this->_tag = '';
$this->_attributes = array();
$this->_current_attribute = '';
}
/**
* Runs the content through the lexer which
* should call back to the acceptors.
* @param string $raw Page text to parse.
* @return boolean False if parse error.
* @access public
*/
function parse($raw) {
return $this->_lexer->parse($raw);
}
/**
* Sets up the matching lexer. Starts in 'text' mode.
* @param SimpleSaxParser $parser Event generator, usually $self.
* @return SimpleLexer Lexer suitable for this parser.
* @access public
* @static
*/
function &createLexer(&$parser) {
$lexer = &new SimpleHtmlLexer($parser);
return $lexer;
}
/**
* Accepts a token from the tag mode. If the
* starting element completes then the element
* is dispatched and the current attributes
* set back to empty. The element or attribute
* name is converted to lower case.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function acceptStartToken($token, $event) {
if ($event == LEXER_ENTER) {
$this->_tag = strtolower(substr($token, 1));
return true;
}
if ($event == LEXER_EXIT) {
$success = $this->_listener->startElement(
$this->_tag,
$this->_attributes);
$this->_tag = '';
$this->_attributes = array();
return $success;
}
if ($token != '=') {
$this->_current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token));
$this->_attributes[$this->_current_attribute] = '';
}
return true;
}
/**
* Accepts a token from the end tag mode.
* The element name is converted to lower case.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function acceptEndToken($token, $event) {
if (! preg_match('/<\/(.*)>/', $token, $matches)) {
return false;
}
return $this->_listener->endElement(strtolower($matches[1]));
}
/**
* Part of the tag data.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function acceptAttributeToken($token, $event) {
if ($event == LEXER_UNMATCHED) {
$this->_attributes[$this->_current_attribute] .=
SimpleHtmlSaxParser::decodeHtml($token);
}
if ($event == LEXER_SPECIAL) {
$this->_attributes[$this->_current_attribute] .=
preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token));
}
return true;
}
/**
* A character entity.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function acceptEntityToken($token, $event) {
}
/**
* Character data between tags regarded as
* important.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function acceptTextToken($token, $event) {
return $this->_listener->addContent($token);
}
/**
* Incoming data to be ignored.
* @param string $token Incoming characters.
* @param integer $event Lexer event type.
* @return boolean False if parse error.
* @access public
*/
function ignore($token, $event) {
return true;
}
/**
* Decodes any HTML entities.
* @param string $html Incoming HTML.
* @return string Outgoing plain text.
* @access public
* @static
*/
function decodeHtml($html) {
static $translations;
if (! isset($translations)) {
$translations = array_flip(get_html_translation_table(HTML_ENTITIES));
}
return strtr($html, $translations);
}
/**
* Turns HTML into text browser visible text. Images
* are converted to their alt text and tags are supressed.
* Entities are converted to their visible representation.
* @param string $html HTML to convert.
* @return string Plain text.
* @access public
* @static
*/
function normalise($html) {
$text = preg_replace('|<!--.*?-->|', '', $html);
$text = preg_replace('|<img.*?alt\s*=\s*"(.*?)".*?>|', ' \1 ', $text);
$text = preg_replace('|<img.*?alt\s*=\s*\'(.*?)\'.*?>|', ' \1 ', $text);
$text = preg_replace('|<img.*?alt\s*=\s*([a-zA-Z_]+).*?>|', ' \1 ', $text);
$text = preg_replace('|<.*?>|', '', $text);
$text = SimpleHtmlSaxParser::decodeHtml($text);
$text = preg_replace('|\s+|', ' ', $text);
return trim($text);
}
}
/**
* SAX event handler.
* @package SimpleTest
* @subpackage WebTester
* @abstract
*/
class SimpleSaxListener {
/**
* Sets the document to write to.
* @access public
*/
function SimpleSaxListener() {
}
/**
* Start of element event.
* @param string $name Element name.
* @param hash $attributes Name value pairs.
* Attributes without content
* are marked as true.
* @return boolean False on parse error.
* @access public
*/
function startElement($name, $attributes) {
}
/**
* End of element event.
* @param string $name Element name.
* @return boolean False on parse error.
* @access public
*/
function endElement($name) {
}
/**
* Unparsed, but relevant data.
* @param string $text May include unparsed tags.
* @return boolean False on parse error.
* @access public
*/
function addContent($text) {
}
}
?>

View File

@ -1,275 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**
* Version specific reflection API.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleReflection {
var $_interface;
/**
* Stashes the class/interface.
* @param string $interface Class or interface
* to inspect.
*/
function SimpleReflection($interface) {
$this->_interface = $interface;
}
/**
* Checks that a class has been declared. Versions
* before PHP5.0.2 need a check that it's not really
* an interface.
* @return boolean True if defined.
* @access public
*/
function classExists() {
if (! class_exists($this->_interface)) {
return false;
}
$reflection = new ReflectionClass($this->_interface);
return ! $reflection->isInterface();
}
/**
* Needed to kill the autoload feature in PHP5
* for classes created dynamically.
* @return boolean True if defined.
* @access public
*/
function classExistsSansAutoload() {
return class_exists($this->_interface, false);
}
/**
* Checks that a class or interface has been
* declared.
* @return boolean True if defined.
* @access public
*/
function classOrInterfaceExists() {
return $this->_classOrInterfaceExistsWithAutoload($this->_interface, true);
}
/**
* Needed to kill the autoload feature in PHP5
* for classes created dynamically.
* @return boolean True if defined.
* @access public
*/
function classOrInterfaceExistsSansAutoload() {
return $this->_classOrInterfaceExistsWithAutoload($this->_interface, false);
}
/**
* Needed to select the autoload feature in PHP5
* for classes created dynamically.
* @param string $interface Class or interface name.
* @param boolean $autoload True totriggerautoload.
* @return boolean True if interface defined.
* @access private
*/
function _classOrInterfaceExistsWithAutoload($interface, $autoload) {
if (function_exists('interface_exists')) {
if (interface_exists($this->_interface, $autoload)) {
return true;
}
}
return class_exists($this->_interface, $autoload);
}
/**
* Gets the list of methods on a class or
* interface. Needs to recursively look at all of
* the interfaces included.
* @returns array List of method names.
* @access public
*/
function getMethods() {
return array_unique(get_class_methods($this->_interface));
}
/**
* Gets the list of interfaces from a class. If the
* class name is actually an interface then just that
* interface is returned.
* @returns array List of interfaces.
* @access public
*/
function getInterfaces() {
$reflection = new ReflectionClass($this->_interface);
if ($reflection->isInterface()) {
return array($this->_interface);
}
return $this->_onlyParents($reflection->getInterfaces());
}
/**
* Gets the list of methods for the implemented
* interfaces only.
* @returns array List of enforced method signatures.
* @access public
*/
function getInterfaceMethods() {
$methods = array();
foreach ($this->getInterfaces() as $interface) {
$methods = array_merge($methods, get_class_methods($interface));
}
return array_unique($methods);
}
/**
* Checks to see if the method signature has to be tightly
* specified.
* @param string $method Method name.
* @returns boolean True if enforced.
* @access private
*/
function _isInterfaceMethod($method) {
return in_array($method, $this->getInterfaceMethods());
}
/**
* Finds the parent class name.
* @returns string Parent class name.
* @access public
*/
function getParent() {
$reflection = new ReflectionClass($this->_interface);
$parent = $reflection->getParentClass();
if ($parent) {
return $parent->getName();
}
return false;
}
/**
* Determines if the class is abstract.
* @returns boolean True if abstract.
* @access public
*/
function isAbstract() {
$reflection = new ReflectionClass($this->_interface);
return $reflection->isAbstract();
}
/**
* Wittles a list of interfaces down to only the top
* level parents.
* @param array $interfaces Reflection API interfaces
* to reduce.
* @returns array List of parent interface names.
* @access private
*/
function _onlyParents($interfaces) {
$parents = array();
foreach ($interfaces as $interface) {
foreach($interfaces as $possible_parent) {
if ($interface->getName() == $possible_parent->getName()) {
continue;
}
if ($interface->isSubClassOf($possible_parent)) {
break;
}
}
$parents[] = $interface->getName();
}
return $parents;
}
/**
* Gets the source code matching the declaration
* of a method.
* @param string $name Method name.
* @return string Method signature up to last
* bracket.
* @access public
*/
function getSignature($name) {
if ($name == '__get') {
return 'function __get($key)';
}
if ($name == '__set') {
return 'function __set($key, $value)';
}
if (! is_callable(array($this->_interface, $name))) {
return "function $name()";
}
if ($this->_isInterfaceMethod($name)) {
return $this->_getFullSignature($name);
}
return "function $name()";
}
/**
* For a signature specified in an interface, full
* details must be replicated to be a valid implementation.
* @param string $name Method name.
* @return string Method signature up to last
* bracket.
* @access private
*/
function _getFullSignature($name) {
$interface = new ReflectionClass($this->_interface);
$method = $interface->getMethod($name);
$reference = $method->returnsReference() ? '&' : '';
return "function $reference$name(" .
implode(', ', $this->_getParameterSignatures($method)) .
")";
}
/**
* Gets the source code for each parameter.
* @param ReflectionMethod $method Method object from
* reflection API
* @return array List of strings, each
* a snippet of code.
* @access private
*/
function _getParameterSignatures($method) {
$signatures = array();
foreach ($method->getParameters() as $parameter) {
$type = $parameter->getClass();
$signatures[] =
(! is_null($type) ? $type->getName() . ' ' : '') .
($parameter->isPassedByReference() ? '&' : '') .
'$' . $this->_suppressSpurious($parameter->getName()) .
($this->_isOptional($parameter) ? ' = null' : '');
}
return $signatures;
}
/**
* The SPL library has problems with the
* Reflection library. In particular, you can
* get extra characters in parameter names :(.
* @param string $name Parameter name.
* @return string Cleaner name.
* @access private
*/
function _suppressSpurious($name) {
return str_replace(array('[', ']', ' '), '', $name);
}
/**
* Test of a reflection parameter being optional
* that works with early versions of PHP5.
* @param reflectionParameter $parameter Is this optional.
* @return boolean True if optional.
* @access private
*/
function _isOptional($parameter) {
if (method_exists($parameter, 'isOptional')) {
return $parameter->isOptional();
}
return false;
}
}
?>

View File

@ -1,115 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/browser.php');
require_once(dirname(__FILE__) . '/xml.php');
require_once(dirname(__FILE__) . '/test_case.php');
/**#@-*/
/**
* Runs an XML formated test on a remote server.
* @package SimpleTest
* @subpackage UnitTester
*/
class RemoteTestCase {
var $_url;
var $_dry_url;
var $_size;
/**
* Sets the location of the remote test.
* @param string $url Test location.
* @param string $dry_url Location for dry run.
* @access public
*/
function RemoteTestCase($url, $dry_url = false) {
$this->_url = $url;
$this->_dry_url = $dry_url ? $dry_url : $url;
$this->_size = false;
}
/**
* Accessor for the test name for subclasses.
* @return string Name of the test.
* @access public
*/
function getLabel() {
return $this->_url;
}
/**
* Runs the top level test for this class. Currently
* reads the data as a single chunk. I'll fix this
* once I have added iteration to the browser.
* @param SimpleReporter $reporter Target of test results.
* @returns boolean True if no failures.
* @access public
*/
function run(&$reporter) {
$browser = &$this->_createBrowser();
$xml = $browser->get($this->_url);
if (! $xml) {
trigger_error('Cannot read remote test URL [' . $this->_url . ']');
return false;
}
$parser = &$this->_createParser($reporter);
if (! $parser->parse($xml)) {
trigger_error('Cannot parse incoming XML from [' . $this->_url . ']');
return false;
}
return true;
}
/**
* Creates a new web browser object for fetching
* the XML report.
* @return SimpleBrowser New browser.
* @access protected
*/
function &_createBrowser() {
return new SimpleBrowser();
}
/**
* Creates the XML parser.
* @param SimpleReporter $reporter Target of test results.
* @return SimpleTestXmlListener XML reader.
* @access protected
*/
function &_createParser(&$reporter) {
return new SimpleTestXmlParser($reporter);
}
/**
* Accessor for the number of subtests.
* @return integer Number of test cases.
* @access public
*/
function getSize() {
if ($this->_size === false) {
$browser = &$this->_createBrowser();
$xml = $browser->get($this->_dry_url);
if (! $xml) {
trigger_error('Cannot read remote test URL [' . $this->_dry_url . ']');
return false;
}
$reporter = &new SimpleReporter();
$parser = &$this->_createParser($reporter);
if (! $parser->parse($xml)) {
trigger_error('Cannot parse incoming XML from [' . $this->_dry_url . ']');
return false;
}
$this->_size = $reporter->getTestCaseCount();
}
return $this->_size;
}
}
?>

View File

@ -1,367 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/scorer.php');
/**#@-*/
/**
* Sample minimal test displayer. Generates only
* failure messages and a pass count.
* @package SimpleTest
* @subpackage UnitTester
*/
class HtmlReporter extends SimpleReporter {
var $_character_set;
/**
* Does nothing yet. The first output will
* be sent on the first test start. For use
* by a web browser.
* @access public
*/
function HtmlReporter($character_set = 'ISO-8859-1') {
$this->SimpleReporter();
$this->_character_set = $character_set;
}
/**
* Paints the top of the web page setting the
* title to the name of the starting test.
* @param string $test_name Name class of test.
* @access public
*/
function paintHeader($test_name) {
$this->sendNoCacheHeaders();
print "<html>\n<head>\n<title>$test_name</title>\n";
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" .
$this->_character_set . "\">\n";
print "<style type=\"text/css\">\n";
print $this->_getCss() . "\n";
print "</style>\n";
print "</head>\n<body>\n";
print "<h1>$test_name</h1>\n";
flush();
}
/**
* Send the headers necessary to ensure the page is
* reloaded on every request. Otherwise you could be
* scratching your head over out of date test data.
* @access public
* @static
*/
function sendNoCacheHeaders() {
if (! headers_sent()) {
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
}
}
/**
* Paints the CSS. Add additional styles here.
* @return string CSS code as text.
* @access protected
*/
function _getCss() {
return ".fail { color: red; } pre { background-color: lightgray; }";
}
/**
* Paints the end of the test with a summary of
* the passes and failures.
* @param string $test_name Name class of test.
* @access public
*/
function paintFooter($test_name) {
$colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
print "<div style=\"";
print "padding: 8px; margin-top: 1em; background-color: $colour; color: white;";
print "\">";
print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount();
print " test cases complete:\n";
print "<strong>" . $this->getPassCount() . "</strong> passes, ";
print "<strong>" . $this->getFailCount() . "</strong> fails and ";
print "<strong>" . $this->getExceptionCount() . "</strong> exceptions.";
print "</div>\n";
print "</body>\n</html>\n";
}
/**
* Paints the test failure with a breadcrumbs
* trail of the nesting test suites below the
* top level test.
* @param string $message Failure message displayed in
* the context of the other tests.
* @access public
*/
function paintFail($message) {
parent::paintFail($message);
print "<span class=\"fail\">Fail</span>: ";
$breadcrumb = $this->getTestList();
array_shift($breadcrumb);
print implode(" -&gt; ", $breadcrumb);
print " -&gt; " . $this->_htmlEntities($message) . "<br />\n";
}
/**
* Paints a PHP error or exception.
* @param string $message Message is ignored.
* @access public
* @abstract
*/
function paintError($message) {
parent::paintError($message);
print "<span class=\"fail\">Exception</span>: ";
$breadcrumb = $this->getTestList();
array_shift($breadcrumb);
print implode(" -&gt; ", $breadcrumb);
print " -&gt; <strong>" . $this->_htmlEntities($message) . "</strong><br />\n";
}
/**
* Paints formatted text such as dumped variables.
* @param string $message Text to show.
* @access public
*/
function paintFormattedMessage($message) {
print '<pre>' . $this->_htmlEntities($message) . '</pre>';
}
/**
* Character set adjusted entity conversion.
* @param string $message Plain text or Unicode message.
* @return string Browser readable message.
* @access protected
*/
function _htmlEntities($message) {
return htmlentities($message, ENT_COMPAT, $this->_character_set);
}
}
/**
* Sample minimal test displayer. Generates only
* failure messages and a pass count. For command
* line use. I've tried to make it look like JUnit,
* but I wanted to output the errors as they arrived
* which meant dropping the dots.
* @package SimpleTest
* @subpackage UnitTester
*/
class TextReporter extends SimpleReporter {
/**
* Does nothing yet. The first output will
* be sent on the first test start.
* @access public
*/
function TextReporter() {
$this->SimpleReporter();
}
/**
* Paints the title only.
* @param string $test_name Name class of test.
* @access public
*/
function paintHeader($test_name) {
if (! SimpleReporter::inCli()) {
header('Content-type: text/plain');
}
print "$test_name\n";
flush();
}
/**
* Paints the end of the test with a summary of
* the passes and failures.
* @param string $test_name Name class of test.
* @access public
*/
function paintFooter($test_name) {
if ($this->getFailCount() + $this->getExceptionCount() == 0) {
print "OK\n";
} else {
print "FAILURES!!!\n";
}
print "Test cases run: " . $this->getTestCaseProgress() .
"/" . $this->getTestCaseCount() .
", Passes: " . $this->getPassCount() .
", Failures: " . $this->getFailCount() .
", Exceptions: " . $this->getExceptionCount() . "\n";
}
/**
* Paints the test failure as a stack trace.
* @param string $message Failure message displayed in
* the context of the other tests.
* @access public
*/
function paintFail($message) {
parent::paintFail($message);
print $this->getFailCount() . ") $message\n";
$breadcrumb = $this->getTestList();
array_shift($breadcrumb);
print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
print "\n";
}
/**
* Paints a PHP error or exception.
* @param string $message Message is ignored.
* @access public
* @abstract
*/
function paintError($message) {
parent::paintError($message);
print "Exception " . $this->getExceptionCount() . "!\n$message\n";
}
/**
* Paints formatted text such as dumped variables.
* @param string $message Text to show.
* @access public
*/
function paintFormattedMessage($message) {
print "$message\n";
flush();
}
}
/**
* Runs just a single test group, a single case or
* even a single test within that case.
* @package SimpleTest
* @subpackage UnitTester
*/
class SelectiveReporter extends SimpleReporterDecorator {
var $_just_this_case =false;
var $_just_this_test = false;
var $_within_test_case = true;
/**
* Selects the test case or group to be run,
* and optionally a specific test.
* @param SimpleScorer $reporter Reporter to receive events.
* @param string $just_this_case Only this case or group will run.
* @param string $just_this_test Only this test method will run.
*/
function SelectiveReporter(&$reporter, $just_this_case = false, $just_this_test = false) {
if (isset($just_this_case) && $just_this_case) {
$this->_just_this_case = strtolower($just_this_case);
$this->_within_test_case = false;
}
if (isset($just_this_test) && $just_this_test) {
$this->_just_this_test = strtolower($just_this_test);
}
$this->SimpleReporterDecorator($reporter);
}
/**
* Compares criteria to actual the case/group name.
* @param string $test_case The incoming test.
* @return boolean True if matched.
* @access protected
*/
function _isCaseMatch($test_case) {
if ($this->_just_this_case) {
return $this->_just_this_case == strtolower($test_case);
}
return false;
}
/**
* Compares criteria to actual the test name.
* @param string $method The incoming test method.
* @return boolean True if matched.
* @access protected
*/
function _isTestMatch($method) {
if ($this->_just_this_test) {
return $this->_just_this_test == strtolower($method);
}
return true;
}
/**
* Veto everything that doesn't match the method wanted.
* @param string $test_case Name of test case.
* @param string $method Name of test method.
* @return boolean True if test should be run.
* @access public
*/
function shouldInvoke($test_case, $method) {
if ($this->_within_test_case && $this->_isTestMatch($method)) {
return $this->_reporter->shouldInvoke($test_case, $method);
}
return false;
}
/**
* Paints the start of a group test.
* @param string $test_case Name of test or other label.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_case, $size) {
if ($this->_isCaseMatch($test_case)) {
$this->_within_test_case = true;
}
if ($this->_within_test_case) {
$this->_reporter->paintGroupStart($test_case, $size);
}
}
/**
* Paints the end of a group test.
* @param string $test_case Name of test or other label.
* @access public
*/
function paintGroupEnd($test_case) {
if ($this->_within_test_case) {
$this->_reporter->paintGroupEnd($test_case);
}
if ($this->_isCaseMatch($test_case)) {
$this->_within_test_case = false;
}
}
/**
* Paints the start of a test case.
* @param string $test_case Name of test or other label.
* @access public
*/
function paintCaseStart($test_case) {
if ($this->_isCaseMatch($test_case)) {
$this->_within_test_case = true;
}
if ($this->_within_test_case) {
$this->_reporter->paintCaseStart($test_case);
}
}
/**
* Paints the end of a test case.
* @param string $test_case Name of test or other label.
* @access public
*/
function paintCaseEnd($test_case) {
if ($this->_within_test_case) {
$this->_reporter->paintCaseEnd($test_case);
}
if ($this->_isCaseMatch($test_case)) {
$this->_within_test_case = false;
}
}
}
?>

View File

@ -1,777 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+*/
require_once(dirname(__FILE__) . '/invoker.php');
/**#@-*/
/**
* Can recieve test events and display them. Display
* is achieved by making display methods available
* and visiting the incoming event.
* @package SimpleTest
* @subpackage UnitTester
* @abstract
*/
class SimpleScorer {
var $_passes;
var $_fails;
var $_exceptions;
var $_is_dry_run;
/**
* Starts the test run with no results.
* @access public
*/
function SimpleScorer() {
$this->_passes = 0;
$this->_fails = 0;
$this->_exceptions = 0;
$this->_is_dry_run = false;
}
/**
* Signals that the next evaluation will be a dry
* run. That is, the structure events will be
* recorded, but no tests will be run.
* @param boolean $is_dry Dry run if true.
* @access public
*/
function makeDry($is_dry = true) {
$this->_is_dry_run = $is_dry;
}
/**
* The reporter has a veto on what should be run.
* @param string $test_case_name name of test case.
* @param string $method Name of test method.
* @access public
*/
function shouldInvoke($test_case_name, $method) {
return ! $this->_is_dry_run;
}
/**
* Can wrap the invoker in preperation for running
* a test.
* @param SimpleInvoker $invoker Individual test runner.
* @return SimpleInvoker Wrapped test runner.
* @access public
*/
function &createInvoker(&$invoker) {
return $invoker;
}
/**
* Accessor for current status. Will be false
* if there have been any failures or exceptions.
* Used for command line tools.
* @return boolean True if no failures.
* @access public
*/
function getStatus() {
if ($this->_exceptions + $this->_fails > 0) {
return false;
}
return true;
}
/**
* Paints the start of a group test.
* @param string $test_name Name of test or other label.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_name, $size) {
}
/**
* Paints the end of a group test.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintGroupEnd($test_name) {
}
/**
* Paints the start of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseStart($test_name) {
}
/**
* Paints the end of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseEnd($test_name) {
}
/**
* Paints the start of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodStart($test_name) {
}
/**
* Paints the end of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodEnd($test_name) {
}
/**
* Increments the pass count.
* @param string $message Message is ignored.
* @access public
*/
function paintPass($message) {
$this->_passes++;
}
/**
* Increments the fail count.
* @param string $message Message is ignored.
* @access public
*/
function paintFail($message) {
$this->_fails++;
}
/**
* Deals with PHP 4 throwing an error or PHP 5
* throwing an exception.
* @param string $message Text of error formatted by
* the test case.
* @access public
*/
function paintError($message) {
$this->_exceptions++;
}
/**
* Accessor for the number of passes so far.
* @return integer Number of passes.
* @access public
*/
function getPassCount() {
return $this->_passes;
}
/**
* Accessor for the number of fails so far.
* @return integer Number of fails.
* @access public
*/
function getFailCount() {
return $this->_fails;
}
/**
* Accessor for the number of untrapped errors
* so far.
* @return integer Number of exceptions.
* @access public
*/
function getExceptionCount() {
return $this->_exceptions;
}
/**
* Paints a simple supplementary message.
* @param string $message Text to display.
* @access public
*/
function paintMessage($message) {
}
/**
* Paints a formatted ASCII message such as a
* variable dump.
* @param string $message Text to display.
* @access public
*/
function paintFormattedMessage($message) {
}
/**
* By default just ignores user generated events.
* @param string $type Event type as text.
* @param mixed $payload Message or object.
* @access public
*/
function paintSignal($type, $payload) {
}
}
/**
* Recipient of generated test messages that can display
* page footers and headers. Also keeps track of the
* test nesting. This is the main base class on which
* to build the finished test (page based) displays.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleReporter extends SimpleScorer {
var $_test_stack;
var $_size;
var $_progress;
/**
* Starts the display with no results in.
* @access public
*/
function SimpleReporter() {
$this->SimpleScorer();
$this->_test_stack = array();
$this->_size = null;
$this->_progress = 0;
}
/**
* Paints the start of a group test. Will also paint
* the page header and footer if this is the
* first test. Will stash the size if the first
* start.
* @param string $test_name Name of test that is starting.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_name, $size) {
if (! isset($this->_size)) {
$this->_size = $size;
}
if (count($this->_test_stack) == 0) {
$this->paintHeader($test_name);
}
$this->_test_stack[] = $test_name;
}
/**
* Paints the end of a group test. Will paint the page
* footer if the stack of tests has unwound.
* @param string $test_name Name of test that is ending.
* @param integer $progress Number of test cases ending.
* @access public
*/
function paintGroupEnd($test_name) {
array_pop($this->_test_stack);
if (count($this->_test_stack) == 0) {
$this->paintFooter($test_name);
}
}
/**
* Paints the start of a test case. Will also paint
* the page header and footer if this is the
* first test. Will stash the size if the first
* start.
* @param string $test_name Name of test that is starting.
* @access public
*/
function paintCaseStart($test_name) {
if (! isset($this->_size)) {
$this->_size = 1;
}
if (count($this->_test_stack) == 0) {
$this->paintHeader($test_name);
}
$this->_test_stack[] = $test_name;
}
/**
* Paints the end of a test case. Will paint the page
* footer if the stack of tests has unwound.
* @param string $test_name Name of test that is ending.
* @access public
*/
function paintCaseEnd($test_name) {
$this->_progress++;
array_pop($this->_test_stack);
if (count($this->_test_stack) == 0) {
$this->paintFooter($test_name);
}
}
/**
* Paints the start of a test method.
* @param string $test_name Name of test that is starting.
* @access public
*/
function paintMethodStart($test_name) {
$this->_test_stack[] = $test_name;
}
/**
* Paints the end of a test method. Will paint the page
* footer if the stack of tests has unwound.
* @param string $test_name Name of test that is ending.
* @access public
*/
function paintMethodEnd($test_name) {
array_pop($this->_test_stack);
}
/**
* Paints the test document header.
* @param string $test_name First test top level
* to start.
* @access public
* @abstract
*/
function paintHeader($test_name) {
}
/**
* Paints the test document footer.
* @param string $test_name The top level test.
* @access public
* @abstract
*/
function paintFooter($test_name) {
}
/**
* Accessor for internal test stack. For
* subclasses that need to see the whole test
* history for display purposes.
* @return array List of methods in nesting order.
* @access public
*/
function getTestList() {
return $this->_test_stack;
}
/**
* Accessor for total test size in number
* of test cases. Null until the first
* test is started.
* @return integer Total number of cases at start.
* @access public
*/
function getTestCaseCount() {
return $this->_size;
}
/**
* Accessor for the number of test cases
* completed so far.
* @return integer Number of ended cases.
* @access public
*/
function getTestCaseProgress() {
return $this->_progress;
}
/**
* Static check for running in the comand line.
* @return boolean True if CLI.
* @access public
* @static
*/
function inCli() {
return php_sapi_name() == 'cli';
}
}
/**
* For modifying the behaviour of the visual reporters.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleReporterDecorator {
var $_reporter;
/**
* Mediates between teh reporter and the test case.
* @param SimpleScorer $reporter Reporter to receive events.
*/
function SimpleReporterDecorator(&$reporter) {
$this->_reporter = &$reporter;
}
/**
* Signals that the next evaluation will be a dry
* run. That is, the structure events will be
* recorded, but no tests will be run.
* @param boolean $is_dry Dry run if true.
* @access public
*/
function makeDry($is_dry = true) {
$this->_reporter->makeDry($is_dry);
}
/**
* Accessor for current status. Will be false
* if there have been any failures or exceptions.
* Used for command line tools.
* @return boolean True if no failures.
* @access public
*/
function getStatus() {
return $this->_reporter->getStatus();
}
/**
* The reporter has a veto on what should be run.
* @param string $test_case_name name of test case.
* @param string $method Name of test method.
* @return boolean True if test should be run.
* @access public
*/
function shouldInvoke($test_case_name, $method) {
return $this->_reporter->shouldInvoke($test_case_name, $method);
}
/**
* Can wrap the invoker in preperation for running
* a test.
* @param SimpleInvoker $invoker Individual test runner.
* @return SimpleInvoker Wrapped test runner.
* @access public
*/
function &createInvoker(&$invoker) {
return $this->_reporter->createInvoker($invoker);
}
/**
* Paints the start of a group test.
* @param string $test_name Name of test or other label.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_name, $size) {
$this->_reporter->paintGroupStart($test_name, $size);
}
/**
* Paints the end of a group test.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintGroupEnd($test_name) {
$this->_reporter->paintGroupEnd($test_name);
}
/**
* Paints the start of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseStart($test_name) {
$this->_reporter->paintCaseStart($test_name);
}
/**
* Paints the end of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseEnd($test_name) {
$this->_reporter->paintCaseEnd($test_name);
}
/**
* Paints the start of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodStart($test_name) {
$this->_reporter->paintMethodStart($test_name);
}
/**
* Paints the end of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodEnd($test_name) {
$this->_reporter->paintMethodEnd($test_name);
}
/**
* Chains to the wrapped reporter.
* @param string $message Message is ignored.
* @access public
*/
function paintPass($message) {
$this->_reporter->paintPass($message);
}
/**
* Chains to the wrapped reporter.
* @param string $message Message is ignored.
* @access public
*/
function paintFail($message) {
$this->_reporter->paintFail($message);
}
/**
* Chains to the wrapped reporter.
* @param string $message Text of error formatted by
* the test case.
* @access public
*/
function paintError($message) {
$this->_reporter->paintError($message);
}
/**
* Chains to the wrapped reporter.
* @param string $message Text to display.
* @access public
*/
function paintMessage($message) {
$this->_reporter->paintMessage($message);
}
/**
* Chains to the wrapped reporter.
* @param string $message Text to display.
* @access public
*/
function paintFormattedMessage($message) {
$this->_reporter->paintFormattedMessage($message);
}
/**
* Chains to the wrapped reporter.
* @param string $type Event type as text.
* @param mixed $payload Message or object.
* @return boolean Should return false if this
* type of signal should fail the
* test suite.
* @access public
*/
function paintSignal($type, &$payload) {
$this->_reporter->paintSignal($type, $payload);
}
}
/**
* For sending messages to multiple reporters at
* the same time.
* @package SimpleTest
* @subpackage UnitTester
*/
class MultipleReporter {
var $_reporters = array();
/**
* Adds a reporter to the subscriber list.
* @param SimpleScorer $reporter Reporter to receive events.
* @access public
*/
function attachReporter(&$reporter) {
$this->_reporters[] = &$reporter;
}
/**
* Signals that the next evaluation will be a dry
* run. That is, the structure events will be
* recorded, but no tests will be run.
* @param boolean $is_dry Dry run if true.
* @access public
*/
function makeDry($is_dry = true) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->makeDry($is_dry);
}
}
/**
* Accessor for current status. Will be false
* if there have been any failures or exceptions.
* If any reporter reports a failure, the whole
* suite fails.
* @return boolean True if no failures.
* @access public
*/
function getStatus() {
for ($i = 0; $i < count($this->_reporters); $i++) {
if (! $this->_reporters[$i]->getStatus()) {
return false;
}
}
return true;
}
/**
* The reporter has a veto on what should be run.
* It requires all reporters to want to run the method.
* @param string $test_case_name name of test case.
* @param string $method Name of test method.
* @access public
*/
function shouldInvoke($test_case_name, $method) {
for ($i = 0; $i < count($this->_reporters); $i++) {
if (! $this->_reporters[$i]->shouldInvoke($test_case_name, $method)) {
return false;
}
}
return true;
}
/**
* Every reporter gets a chance to wrap the invoker.
* @param SimpleInvoker $invoker Individual test runner.
* @return SimpleInvoker Wrapped test runner.
* @access public
*/
function &createInvoker(&$invoker) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$invoker = &$this->_reporters[$i]->createInvoker($invoker);
}
return $invoker;
}
/**
* Paints the start of a group test.
* @param string $test_name Name of test or other label.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_name, $size) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintGroupStart($test_name, $size);
}
}
/**
* Paints the end of a group test.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintGroupEnd($test_name) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintGroupEnd($test_name);
}
}
/**
* Paints the start of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseStart($test_name) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintCaseStart($test_name);
}
}
/**
* Paints the end of a test case.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintCaseEnd($test_name) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintCaseEnd($test_name);
}
}
/**
* Paints the start of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodStart($test_name) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintMethodStart($test_name);
}
}
/**
* Paints the end of a test method.
* @param string $test_name Name of test or other label.
* @access public
*/
function paintMethodEnd($test_name) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintMethodEnd($test_name);
}
}
/**
* Chains to the wrapped reporter.
* @param string $message Message is ignored.
* @access public
*/
function paintPass($message) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintPass($message);
}
}
/**
* Chains to the wrapped reporter.
* @param string $message Message is ignored.
* @access public
*/
function paintFail($message) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintFail($message);
}
}
/**
* Chains to the wrapped reporter.
* @param string $message Text of error formatted by
* the test case.
* @access public
*/
function paintError($message) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintError($message);
}
}
/**
* Chains to the wrapped reporter.
* @param string $message Text to display.
* @access public
*/
function paintMessage($message) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintMessage($message);
}
}
/**
* Chains to the wrapped reporter.
* @param string $message Text to display.
* @access public
*/
function paintFormattedMessage($message) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintFormattedMessage($message);
}
}
/**
* Chains to the wrapped reporter.
* @param string $type Event type as text.
* @param mixed $payload Message or object.
* @return boolean Should return false if this
* type of signal should fail the
* test suite.
* @access public
*/
function paintSignal($type, &$payload) {
for ($i = 0; $i < count($this->_reporters); $i++) {
$this->_reporters[$i]->paintSignal($type, $payload);
}
}
}
?>

View File

@ -1,133 +0,0 @@
<?php
/**
* Base include file for SimpleTest.
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
require_once(dirname(__FILE__) . '/tag.php');
require_once(dirname(__FILE__) . '/encoding.php');
/**#@-*/
/**
* Used to extract form elements for testing against.
* Searches by name attribute.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleByName {
var $_name;
/**
* Stashes the name for later comparison.
* @param string $name Name attribute to match.
*/
function SimpleByName($name) {
$this->_name = $name;
}
/**
* Compares with name attribute of widget.
* @param SimpleWidget $widget Control to compare.
* @access public
*/
function isMatch($widget) {
return ($widget->getName() == $this->_name);
}
}
/**
* Used to extract form elements for testing against.
* Searches by visible label or alt text.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleByLabel {
var $_label;
/**
* Stashes the name for later comparison.
* @param string $label Visible text to match.
*/
function SimpleByLabel($label) {
$this->_label = $label;
}
/**
* Comparison. Compares visible text of widget or
* related label.
* @param SimpleWidget $widget Control to compare.
* @access public
*/
function isMatch($widget) {
if (! method_exists($widget, 'isLabel')) {
return false;
}
return $widget->isLabel($this->_label);
}
}
/**
* Used to extract form elements for testing against.
* Searches dy id attribute.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleById {
var $_id;
/**
* Stashes the name for later comparison.
* @param string $id ID atribute to match.
*/
function SimpleById($id) {
$this->_id = $id;
}
/**
* Comparison. Compares id attribute of widget.
* @param SimpleWidget $widget Control to compare.
* @access public
*/
function isMatch($widget) {
return $widget->isId($this->_id);
}
}
/**
* Used to extract form elements for testing against.
* Searches by visible label, name or alt text.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleByLabelOrName {
var $_label;
/**
* Stashes the name/label for later comparison.
* @param string $label Visible text to match.
*/
function SimpleByLabelOrName($label) {
$this->_label = $label;
}
/**
* Comparison. Compares visible text of widget or
* related label or name.
* @param SimpleWidget $widget Control to compare.
* @access public
*/
function isMatch($widget) {
if (method_exists($widget, 'isLabel')) {
if ($widget->isLabel($this->_label)) {
return true;
}
}
return ($widget->getName() == $this->_label);
}
}
?>

View File

@ -1,306 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/test_case.php');
/**#@-*/
/**
* Wrapper for exec() functionality.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleShell {
var $_output;
/**
* Executes the shell comand and stashes the output.
* @access public
*/
function SimpleShell() {
$this->_output = false;
}
/**
* Actually runs the command. Does not trap the
* error stream output as this need PHP 4.3+.
* @param string $command The actual command line
* to run.
* @return integer Exit code.
* @access public
*/
function execute($command) {
$this->_output = false;
exec($command, $this->_output, $ret);
return $ret;
}
/**
* Accessor for the last output.
* @return string Output as text.
* @access public
*/
function getOutput() {
return implode("\n", $this->_output);
}
/**
* Accessor for the last output.
* @return array Output as array of lines.
* @access public
*/
function getOutputAsList() {
return $this->_output;
}
}
/**
* Test case for testing of command line scripts and
* utilities. Usually scripts taht are external to the
* PHP code, but support it in some way.
* @package SimpleTest
* @subpackage UnitTester
*/
class ShellTestCase extends SimpleTestCase {
var $_current_shell;
var $_last_status;
var $_last_command;
/**
* Creates an empty test case. Should be subclassed
* with test methods for a functional test case.
* @param string $label Name of test case. Will use
* the class name if none specified.
* @access public
*/
function ShellTestCase($label = false) {
$this->SimpleTestCase($label);
$this->_current_shell = &$this->_createShell();
$this->_last_status = false;
$this->_last_command = '';
}
/**
* Executes a command and buffers the results.
* @param string $command Command to run.
* @return boolean True if zero exit code.
* @access public
*/
function execute($command) {
$shell = &$this->_getShell();
$this->_last_status = $shell->execute($command);
$this->_last_command = $command;
return ($this->_last_status === 0);
}
/**
* Dumps the output of the last command.
* @access public
*/
function dumpOutput() {
$this->dump($this->getOutput());
}
/**
* Accessor for the last output.
* @return string Output as text.
* @access public
*/
function getOutput() {
$shell = &$this->_getShell();
return $shell->getOutput();
}
/**
* Accessor for the last output.
* @return array Output as array of lines.
* @access public
*/
function getOutputAsList() {
$shell = &$this->_getShell();
return $shell->getOutputAsList();
}
/**
* Will trigger a pass if the two parameters have
* the same value only. Otherwise a fail. This
* is for testing hand extracted text, etc.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertEqual($first, $second, $message = "%s") {
return $this->assert(
new EqualExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters have
* a different value. Otherwise a fail. This
* is for testing hand extracted text, etc.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNotEqual($first, $second, $message = "%s") {
return $this->assert(
new NotEqualExpectation($first),
$second,
$message);
}
/**
* Tests the last status code from the shell.
* @param integer $status Expected status of last
* command.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertExitCode($status, $message = "%s") {
$message = sprintf($message, "Expected status code of [$status] from [" .
$this->_last_command . "], but got [" .
$this->_last_status . "]");
return $this->assertTrue($status === $this->_last_status, $message);
}
/**
* Attempt to exactly match the combined STDERR and
* STDOUT output.
* @param string $expected Expected output.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertOutput($expected, $message = "%s") {
$shell = &$this->_getShell();
return $this->assert(
new EqualExpectation($expected),
$shell->getOutput(),
$message);
}
/**
* Scans the output for a Perl regex. If found
* anywhere it passes, else it fails.
* @param string $pattern Regex to search for.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertOutputPattern($pattern, $message = "%s") {
$shell = &$this->_getShell();
return $this->assert(
new PatternExpectation($pattern),
$shell->getOutput(),
$message);
}
/**
* If a Perl regex is found anywhere in the current
* output then a failure is generated, else a pass.
* @param string $pattern Regex to search for.
* @param $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoOutputPattern($pattern, $message = "%s") {
$shell = &$this->_getShell();
return $this->assert(
new NoPatternExpectation($pattern),
$shell->getOutput(),
$message);
}
/**
* File existence check.
* @param string $path Full filename and path.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertFileExists($path, $message = "%s") {
$message = sprintf($message, "File [$path] should exist");
return $this->assertTrue(file_exists($path), $message);
}
/**
* File non-existence check.
* @param string $path Full filename and path.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertFileNotExists($path, $message = "%s") {
$message = sprintf($message, "File [$path] should not exist");
return $this->assertFalse(file_exists($path), $message);
}
/**
* Scans a file for a Perl regex. If found
* anywhere it passes, else it fails.
* @param string $pattern Regex to search for.
* @param string $path Full filename and path.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertFilePattern($pattern, $path, $message = "%s") {
$shell = &$this->_getShell();
return $this->assert(
new PatternExpectation($pattern),
implode('', file($path)),
$message);
}
/**
* If a Perl regex is found anywhere in the named
* file then a failure is generated, else a pass.
* @param string $pattern Regex to search for.
* @param string $path Full filename and path.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoFilePattern($pattern, $path, $message = "%s") {
$shell = &$this->_getShell();
return $this->assert(
new NoPatternExpectation($pattern),
implode('', file($path)),
$message);
}
/**
* Accessor for current shell. Used for testing the
* the tester itself.
* @return Shell Current shell.
* @access protected
*/
function &_getShell() {
return $this->_current_shell;
}
/**
* Factory for the shell to run the command on.
* @return Shell New shell object.
* @access protected
*/
function &_createShell() {
$shell = &new SimpleShell();
return $shell;
}
}
?>

View File

@ -1,282 +0,0 @@
<?php
/**
* Global state for SimpleTest and kicker script in future versions.
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
if (version_compare(phpversion(), '5') >= 0) {
require_once(dirname(__FILE__) . '/reflection_php5.php');
} else {
require_once(dirname(__FILE__) . '/reflection_php4.php');
}
/**#@-*/
/**
* Static global directives and options. I hate this
* class. It's a mixture of reference hacks, configuration
* and previous design screw-ups that I have to maintain
* to keep backward compatibility.
* @package SimpleTest
*/
class SimpleTest {
/**
* Reads the SimpleTest version from the release file.
* @return string Version string.
* @static
* @access public
*/
function getVersion() {
$content = file(dirname(__FILE__) . '/VERSION');
return trim($content[0]);
}
/**
* Sets the name of a test case to ignore, usually
* because the class is an abstract case that should
* not be run. Once PHP4 is dropped this will disappear
* as a public method and "abstract" will rule.
* @param string $class Add a class to ignore.
* @static
* @access public
*/
function ignore($class) {
$registry = &SimpleTest::_getRegistry();
$registry['IgnoreList'][strtolower($class)] = true;
}
/**
* Scans the now complete ignore list, and adds
* all parent classes to the list. If a class
* is not a runnable test case, then it's parents
* wouldn't be either. This is syntactic sugar
* to cut down on ommissions of ignore()'s or
* missing abstract declarations. This cannot
* be done whilst loading classes wiithout forcing
* a particular order on the class declarations and
* the ignore() calls. It's nice to havethe ignore()
* calls at the top of teh file.
* @param array $classes Class names of interest.
* @static
* @access public
*/
function ignoreParentsIfIgnored($classes) {
$registry = &SimpleTest::_getRegistry();
foreach ($classes as $class) {
if (SimpleTest::isIgnored($class)) {
$reflection = new SimpleReflection($class);
if ($parent = $reflection->getParent()) {
SimpleTest::ignore($parent);
}
}
}
}
/**
* Test to see if a test case is in the ignore
* list. Quite obviously the ignore list should
* be a separate object and will be one day.
* This method is internal to SimpleTest. Don't
* use it.
* @param string $class Class name to test.
* @return boolean True if should not be run.
* @access public
* @static
*/
function isIgnored($class) {
$registry = &SimpleTest::_getRegistry();
return isset($registry['IgnoreList'][strtolower($class)]);
}
/**
* @deprecated
*/
function setMockBaseClass($mock_base) {
$registry = &SimpleTest::_getRegistry();
$registry['MockBaseClass'] = $mock_base;
}
/**
* @deprecated
*/
function getMockBaseClass() {
$registry = &SimpleTest::_getRegistry();
return $registry['MockBaseClass'];
}
/**
* Sets proxy to use on all requests for when
* testing from behind a firewall. Set host
* to false to disable. This will take effect
* if there are no other proxy settings.
* @param string $proxy Proxy host as URL.
* @param string $username Proxy username for authentication.
* @param string $password Proxy password for authentication.
* @access public
*/
function useProxy($proxy, $username = false, $password = false) {
$registry = &SimpleTest::_getRegistry();
$registry['DefaultProxy'] = $proxy;
$registry['DefaultProxyUsername'] = $username;
$registry['DefaultProxyPassword'] = $password;
}
/**
* Accessor for default proxy host.
* @return string Proxy URL.
* @access public
*/
function getDefaultProxy() {
$registry = &SimpleTest::_getRegistry();
return $registry['DefaultProxy'];
}
/**
* Accessor for default proxy username.
* @return string Proxy username for authentication.
* @access public
*/
function getDefaultProxyUsername() {
$registry = &SimpleTest::_getRegistry();
return $registry['DefaultProxyUsername'];
}
/**
* Accessor for default proxy password.
* @return string Proxy password for authentication.
* @access public
*/
function getDefaultProxyPassword() {
$registry = &SimpleTest::_getRegistry();
return $registry['DefaultProxyPassword'];
}
/**
* Sets the current test case instance. This
* global instance can be used by the mock objects
* to send message to the test cases.
* @param SimpleTestCase $test Test case to register.
* @access public
* @static
*/
function setCurrent(&$test) {
$registry = &SimpleTest::_getRegistry();
$registry['CurrentTestCase'] = &$test;
}
/**
* Accessor for current test instance.
* @return SimpleTEstCase Currently running test.
* @access public
* @static
*/
function &getCurrent() {
$registry = &SimpleTest::_getRegistry();
return $registry['CurrentTestCase'];
}
/**
* Accessor for global registry of options.
* @return hash All stored values.
* @access private
* @static
*/
function &_getRegistry() {
static $registry = false;
if (! $registry) {
$registry = SimpleTest::_getDefaults();
}
return $registry;
}
/**
* Constant default values.
* @return hash All registry defaults.
* @access private
* @static
*/
function _getDefaults() {
return array(
'StubBaseClass' => 'SimpleStub',
'MockBaseClass' => 'SimpleMock',
'IgnoreList' => array(),
'DefaultProxy' => false,
'DefaultProxyUsername' => false,
'DefaultProxyPassword' => false);
}
}
/**
* @deprecated
*/
class SimpleTestOptions extends SimpleTest {
/**
* @deprecated
*/
function getVersion() {
return Simpletest::getVersion();
}
/**
* @deprecated
*/
function ignore($class) {
return Simpletest::ignore($class);
}
/**
* @deprecated
*/
function isIgnored($class) {
return Simpletest::isIgnored($class);
}
/**
* @deprecated
*/
function setMockBaseClass($mock_base) {
return Simpletest::setMockBaseClass($mock_base);
}
/**
* @deprecated
*/
function getMockBaseClass() {
return Simpletest::getMockBaseClass();
}
/**
* @deprecated
*/
function useProxy($proxy, $username = false, $password = false) {
return Simpletest::useProxy($proxy, $username, $password);
}
/**
* @deprecated
*/
function getDefaultProxy() {
return Simpletest::getDefaultProxy();
}
/**
* @deprecated
*/
function getDefaultProxyUsername() {
return Simpletest::getDefaultProxyUsername();
}
/**
* @deprecated
*/
function getDefaultProxyPassword() {
return Simpletest::getDefaultProxyPassword();
}
}
?>

View File

@ -1,216 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage MockObjects
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
require_once(dirname(__FILE__) . '/compatibility.php');
/**#@-*/
/**
* Stashes an error for later. Useful for constructors
* until PHP gets exceptions.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleStickyError {
var $_error = 'Constructor not chained';
/**
* Sets the error to empty.
* @access public
*/
function SimpleStickyError() {
$this->_clearError();
}
/**
* Test for an outstanding error.
* @return boolean True if there is an error.
* @access public
*/
function isError() {
return ($this->_error != '');
}
/**
* Accessor for an outstanding error.
* @return string Empty string if no error otherwise
* the error message.
* @access public
*/
function getError() {
return $this->_error;
}
/**
* Sets the internal error.
* @param string Error message to stash.
* @access protected
*/
function _setError($error) {
$this->_error = $error;
}
/**
* Resets the error state to no error.
* @access protected
*/
function _clearError() {
$this->_setError('');
}
}
/**
* Wrapper for TCP/IP socket.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleSocket extends SimpleStickyError {
var $_handle;
var $_is_open = false;
var $_sent = '';
var $lock_size;
/**
* Opens a socket for reading and writing.
* @param string $host Hostname to send request to.
* @param integer $port Port on remote machine to open.
* @param integer $timeout Connection timeout in seconds.
* @param integer $block_size Size of chunk to read.
* @access public
*/
function SimpleSocket($host, $port, $timeout, $block_size = 255) {
$this->SimpleStickyError();
if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) {
$this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds");
return;
}
$this->_is_open = true;
$this->_block_size = $block_size;
SimpleTestCompatibility::setTimeout($this->_handle, $timeout);
}
/**
* Writes some data to the socket and saves alocal copy.
* @param string $message String to send to socket.
* @return boolean True if successful.
* @access public
*/
function write($message) {
if ($this->isError() || ! $this->isOpen()) {
return false;
}
$count = fwrite($this->_handle, $message);
if (! $count) {
if ($count === false) {
$this->_setError('Cannot write to socket');
$this->close();
}
return false;
}
fflush($this->_handle);
$this->_sent .= $message;
return true;
}
/**
* Reads data from the socket. The error suppresion
* is a workaround for PHP4 always throwing a warning
* with a secure socket.
* @return integer/boolean Incoming bytes. False
* on error.
* @access public
*/
function read() {
if ($this->isError() || ! $this->isOpen()) {
return false;
}
$raw = @fread($this->_handle, $this->_block_size);
if ($raw === false) {
$this->_setError('Cannot read from socket');
$this->close();
}
return $raw;
}
/**
* Accessor for socket open state.
* @return boolean True if open.
* @access public
*/
function isOpen() {
return $this->_is_open;
}
/**
* Closes the socket preventing further reads.
* Cannot be reopened once closed.
* @return boolean True if successful.
* @access public
*/
function close() {
$this->_is_open = false;
return fclose($this->_handle);
}
/**
* Accessor for content so far.
* @return string Bytes sent only.
* @access public
*/
function getSent() {
return $this->_sent;
}
/**
* Actually opens the low level socket.
* @param string $host Host to connect to.
* @param integer $port Port on host.
* @param integer $error_number Recipient of error code.
* @param string $error Recipoent of error message.
* @param integer $timeout Maximum time to wait for connection.
* @access protected
*/
function _openSocket($host, $port, &$error_number, &$error, $timeout) {
return @fsockopen($host, $port, $error_number, $error, $timeout);
}
}
/**
* Wrapper for TCP/IP socket over TLS.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleSecureSocket extends SimpleSocket {
/**
* Opens a secure socket for reading and writing.
* @param string $host Hostname to send request to.
* @param integer $port Port on remote machine to open.
* @param integer $timeout Connection timeout in seconds.
* @access public
*/
function SimpleSecureSocket($host, $port, $timeout) {
$this->SimpleSocket($host, $port, $timeout);
}
/**
* Actually opens the low level socket.
* @param string $host Host to connect to.
* @param integer $port Port on host.
* @param integer $error_number Recipient of error code.
* @param string $error Recipient of error message.
* @param integer $timeout Maximum time to wait for connection.
* @access protected
*/
function _openSocket($host, $port, &$error_number, &$error, $timeout) {
return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout);
}
}
?>

View File

@ -1,1392 +0,0 @@
<?php
/**
* Base include file for SimpleTest.
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include SimpleTest files
*/
require_once(dirname(__FILE__) . '/parser.php');
require_once(dirname(__FILE__) . '/encoding.php');
/**#@-*/
/**
* HTML or XML tag.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTag {
var $_name;
var $_attributes;
var $_content;
/**
* Starts with a named tag with attributes only.
* @param string $name Tag name.
* @param hash $attributes Attribute names and
* string values. Note that
* the keys must have been
* converted to lower case.
*/
function SimpleTag($name, $attributes) {
$this->_name = strtolower(trim($name));
$this->_attributes = $attributes;
$this->_content = '';
}
/**
* Check to see if the tag can have both start and
* end tags with content in between.
* @return boolean True if content allowed.
* @access public
*/
function expectEndTag() {
return true;
}
/**
* The current tag should not swallow all content for
* itself as it's searchable page content. Private
* content tags are usually widgets that contain default
* values.
* @return boolean False as content is available
* to other tags by default.
* @access public
*/
function isPrivateContent() {
return false;
}
/**
* Appends string content to the current content.
* @param string $content Additional text.
* @access public
*/
function addContent($content) {
$this->_content .= (string)$content;
}
/**
* Adds an enclosed tag to the content.
* @param SimpleTag $tag New tag.
* @access public
*/
function addTag(&$tag) {
}
/**
* Accessor for tag name.
* @return string Name of tag.
* @access public
*/
function getTagName() {
return $this->_name;
}
/**
* List of legal child elements.
* @return array List of element names.
* @access public
*/
function getChildElements() {
return array();
}
/**
* Accessor for an attribute.
* @param string $label Attribute name.
* @return string Attribute value.
* @access public
*/
function getAttribute($label) {
$label = strtolower($label);
if (! isset($this->_attributes[$label])) {
return false;
}
return (string)$this->_attributes[$label];
}
/**
* Sets an attribute.
* @param string $label Attribute name.
* @return string $value New attribute value.
* @access protected
*/
function _setAttribute($label, $value) {
$this->_attributes[strtolower($label)] = $value;
}
/**
* Accessor for the whole content so far.
* @return string Content as big raw string.
* @access public
*/
function getContent() {
return $this->_content;
}
/**
* Accessor for content reduced to visible text. Acts
* like a text mode browser, normalising space and
* reducing images to their alt text.
* @return string Content as plain text.
* @access public
*/
function getText() {
return SimpleHtmlSaxParser::normalise($this->_content);
}
/**
* Test to see if id attribute matches.
* @param string $id ID to test against.
* @return boolean True on match.
* @access public
*/
function isId($id) {
return ($this->getAttribute('id') == $id);
}
}
/**
* Page title.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTitleTag extends SimpleTag {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleTitleTag($attributes) {
$this->SimpleTag('title', $attributes);
}
}
/**
* Link.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleAnchorTag extends SimpleTag {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleAnchorTag($attributes) {
$this->SimpleTag('a', $attributes);
}
/**
* Accessor for URL as string.
* @return string Coerced as string.
* @access public
*/
function getHref() {
$url = $this->getAttribute('href');
if (is_bool($url)) {
$url = '';
}
return $url;
}
}
/**
* Form element.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleWidget extends SimpleTag {
var $_value;
var $_label;
var $_is_set;
/**
* Starts with a named tag with attributes only.
* @param string $name Tag name.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleWidget($name, $attributes) {
$this->SimpleTag($name, $attributes);
$this->_value = false;
$this->_label = false;
$this->_is_set = false;
}
/**
* Accessor for name submitted as the key in
* GET/POST variables hash.
* @return string Parsed value.
* @access public
*/
function getName() {
return $this->getAttribute('name');
}
/**
* Accessor for default value parsed with the tag.
* @return string Parsed value.
* @access public
*/
function getDefault() {
return $this->getAttribute('value');
}
/**
* Accessor for currently set value or default if
* none.
* @return string Value set by form or default
* if none.
* @access public
*/
function getValue() {
if (! $this->_is_set) {
return $this->getDefault();
}
return $this->_value;
}
/**
* Sets the current form element value.
* @param string $value New value.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
$this->_value = $value;
$this->_is_set = true;
return true;
}
/**
* Resets the form element value back to the
* default.
* @access public
*/
function resetValue() {
$this->_is_set = false;
}
/**
* Allows setting of a label externally, say by a
* label tag.
* @param string $label Label to attach.
* @access public
*/
function setLabel($label) {
$this->_label = trim($label);
}
/**
* Reads external or internal label.
* @param string $label Label to test.
* @return boolean True is match.
* @access public
*/
function isLabel($label) {
return $this->_label == trim($label);
}
/**
* Dispatches the value into the form encoded packet.
* @param SimpleEncoding $encoding Form packet.
* @access public
*/
function write(&$encoding) {
if ($this->getName()) {
$encoding->add($this->getName(), $this->getValue());
}
}
}
/**
* Text, password and hidden field.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTextTag extends SimpleWidget {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleTextTag($attributes) {
$this->SimpleWidget('input', $attributes);
if ($this->getAttribute('value') === false) {
$this->_setAttribute('value', '');
}
}
/**
* Tag contains no content.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* Sets the current form element value. Cannot
* change the value of a hidden field.
* @param string $value New value.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
if ($this->getAttribute('type') == 'hidden') {
return false;
}
return parent::setValue($value);
}
}
/**
* Submit button as input tag.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleSubmitTag extends SimpleWidget {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleSubmitTag($attributes) {
$this->SimpleWidget('input', $attributes);
if ($this->getAttribute('value') === false) {
$this->_setAttribute('value', 'Submit');
}
}
/**
* Tag contains no end element.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* Disables the setting of the button value.
* @param string $value Ignored.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
return false;
}
/**
* Value of browser visible text.
* @return string Visible label.
* @access public
*/
function getLabel() {
return $this->getValue();
}
/**
* Test for a label match when searching.
* @param string $label Label to test.
* @return boolean True on match.
* @access public
*/
function isLabel($label) {
return trim($label) == trim($this->getLabel());
}
}
/**
* Image button as input tag.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleImageSubmitTag extends SimpleWidget {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleImageSubmitTag($attributes) {
$this->SimpleWidget('input', $attributes);
}
/**
* Tag contains no end element.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* Disables the setting of the button value.
* @param string $value Ignored.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
return false;
}
/**
* Value of browser visible text.
* @return string Visible label.
* @access public
*/
function getLabel() {
if ($this->getAttribute('title')) {
return $this->getAttribute('title');
}
return $this->getAttribute('alt');
}
/**
* Test for a label match when searching.
* @param string $label Label to test.
* @return boolean True on match.
* @access public
*/
function isLabel($label) {
return trim($label) == trim($this->getLabel());
}
/**
* Dispatches the value into the form encoded packet.
* @param SimpleEncoding $encoding Form packet.
* @param integer $x X coordinate of click.
* @param integer $y Y coordinate of click.
* @access public
*/
function write(&$encoding, $x, $y) {
if ($this->getName()) {
$encoding->add($this->getName() . '.x', $x);
$encoding->add($this->getName() . '.y', $y);
} else {
$encoding->add('x', $x);
$encoding->add('y', $y);
}
}
}
/**
* Submit button as button tag.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleButtonTag extends SimpleWidget {
/**
* Starts with a named tag with attributes only.
* Defaults are very browser dependent.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleButtonTag($attributes) {
$this->SimpleWidget('button', $attributes);
}
/**
* Check to see if the tag can have both start and
* end tags with content in between.
* @return boolean True if content allowed.
* @access public
*/
function expectEndTag() {
return true;
}
/**
* Disables the setting of the button value.
* @param string $value Ignored.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
return false;
}
/**
* Value of browser visible text.
* @return string Visible label.
* @access public
*/
function getLabel() {
return $this->getContent();
}
/**
* Test for a label match when searching.
* @param string $label Label to test.
* @return boolean True on match.
* @access public
*/
function isLabel($label) {
return trim($label) == trim($this->getLabel());
}
}
/**
* Content tag for text area.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTextAreaTag extends SimpleWidget {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleTextAreaTag($attributes) {
$this->SimpleWidget('textarea', $attributes);
}
/**
* Accessor for starting value.
* @return string Parsed value.
* @access public
*/
function getDefault() {
return $this->_wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent()));
}
/**
* Applies word wrapping if needed.
* @param string $value New value.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
return parent::setValue($this->_wrap($value));
}
/**
* Test to see if text should be wrapped.
* @return boolean True if wrapping on.
* @access private
*/
function _wrapIsEnabled() {
if ($this->getAttribute('cols')) {
$wrap = $this->getAttribute('wrap');
if (($wrap == 'physical') || ($wrap == 'hard')) {
return true;
}
}
return false;
}
/**
* Performs the formatting that is peculiar to
* this tag. There is strange behaviour in this
* one, including stripping a leading new line.
* Go figure. I am using Firefox as a guide.
* @param string $text Text to wrap.
* @return string Text wrapped with carriage
* returns and line feeds
* @access private
*/
function _wrap($text) {
$text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text));
$text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text));
if (strncmp($text, "\r\n", strlen("\r\n")) == 0) {
$text = substr($text, strlen("\r\n"));
}
if ($this->_wrapIsEnabled()) {
return wordwrap(
$text,
(integer)$this->getAttribute('cols'),
"\r\n");
}
return $text;
}
/**
* The content of textarea is not part of the page.
* @return boolean True.
* @access public
*/
function isPrivateContent() {
return true;
}
}
/**
* File upload widget.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleUploadTag extends SimpleWidget {
/**
* Starts with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleUploadTag($attributes) {
$this->SimpleWidget('input', $attributes);
}
/**
* Tag contains no content.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* Dispatches the value into the form encoded packet.
* @param SimpleEncoding $encoding Form packet.
* @access public
*/
function write(&$encoding) {
if (! file_exists($this->getValue())) {
return;
}
$encoding->attach(
$this->getName(),
implode('', file($this->getValue())),
basename($this->getValue()));
}
}
/**
* Drop down widget.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleSelectionTag extends SimpleWidget {
var $_options;
var $_choice;
/**
* Starts with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleSelectionTag($attributes) {
$this->SimpleWidget('select', $attributes);
$this->_options = array();
$this->_choice = false;
}
/**
* Adds an option tag to a selection field.
* @param SimpleOptionTag $tag New option.
* @access public
*/
function addTag(&$tag) {
if ($tag->getTagName() == 'option') {
$this->_options[] = &$tag;
}
}
/**
* Text within the selection element is ignored.
* @param string $content Ignored.
* @access public
*/
function addContent($content) {
}
/**
* Scans options for defaults. If none, then
* the first option is selected.
* @return string Selected field.
* @access public
*/
function getDefault() {
for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
if ($this->_options[$i]->getAttribute('selected') !== false) {
return $this->_options[$i]->getDefault();
}
}
if ($count > 0) {
return $this->_options[0]->getDefault();
}
return '';
}
/**
* Can only set allowed values.
* @param string $value New choice.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
if ($this->_options[$i]->isValue($value)) {
$this->_choice = $i;
return true;
}
}
return false;
}
/**
* Accessor for current selection value.
* @return string Value attribute or
* content of opton.
* @access public
*/
function getValue() {
if ($this->_choice === false) {
return $this->getDefault();
}
return $this->_options[$this->_choice]->getValue();
}
}
/**
* Drop down widget.
* @package SimpleTest
* @subpackage WebTester
*/
class MultipleSelectionTag extends SimpleWidget {
var $_options;
var $_values;
/**
* Starts with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function MultipleSelectionTag($attributes) {
$this->SimpleWidget('select', $attributes);
$this->_options = array();
$this->_values = false;
}
/**
* Adds an option tag to a selection field.
* @param SimpleOptionTag $tag New option.
* @access public
*/
function addTag(&$tag) {
if ($tag->getTagName() == 'option') {
$this->_options[] = &$tag;
}
}
/**
* Text within the selection element is ignored.
* @param string $content Ignored.
* @access public
*/
function addContent($content) {
}
/**
* Scans options for defaults to populate the
* value array().
* @return array Selected fields.
* @access public
*/
function getDefault() {
$default = array();
for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
if ($this->_options[$i]->getAttribute('selected') !== false) {
$default[] = $this->_options[$i]->getDefault();
}
}
return $default;
}
/**
* Can only set allowed values. Any illegal value
* will result in a failure, but all correct values
* will be set.
* @param array $desired New choices.
* @return boolean True if all allowed.
* @access public
*/
function setValue($desired) {
$achieved = array();
foreach ($desired as $value) {
$success = false;
for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
if ($this->_options[$i]->isValue($value)) {
$achieved[] = $this->_options[$i]->getValue();
$success = true;
break;
}
}
if (! $success) {
return false;
}
}
$this->_values = $achieved;
return true;
}
/**
* Accessor for current selection value.
* @return array List of currently set options.
* @access public
*/
function getValue() {
if ($this->_values === false) {
return $this->getDefault();
}
return $this->_values;
}
}
/**
* Option for selection field.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleOptionTag extends SimpleWidget {
/**
* Stashes the attributes.
*/
function SimpleOptionTag($attributes) {
$this->SimpleWidget('option', $attributes);
}
/**
* Does nothing.
* @param string $value Ignored.
* @return boolean Not allowed.
* @access public
*/
function setValue($value) {
return false;
}
/**
* Test to see if a value matches the option.
* @param string $compare Value to compare with.
* @return boolean True if possible match.
* @access public
*/
function isValue($compare) {
$compare = trim($compare);
if (trim($this->getValue()) == $compare) {
return true;
}
return trim($this->getContent()) == $compare;
}
/**
* Accessor for starting value. Will be set to
* the option label if no value exists.
* @return string Parsed value.
* @access public
*/
function getDefault() {
if ($this->getAttribute('value') === false) {
return $this->getContent();
}
return $this->getAttribute('value');
}
/**
* The content of options is not part of the page.
* @return boolean True.
* @access public
*/
function isPrivateContent() {
return true;
}
}
/**
* Radio button.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleRadioButtonTag extends SimpleWidget {
/**
* Stashes the attributes.
* @param array $attributes Hash of attributes.
*/
function SimpleRadioButtonTag($attributes) {
$this->SimpleWidget('input', $attributes);
if ($this->getAttribute('value') === false) {
$this->_setAttribute('value', 'on');
}
}
/**
* Tag contains no content.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* The only allowed value sn the one in the
* "value" attribute.
* @param string $value New value.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
if ($value === false) {
return parent::setValue($value);
}
if ($value !== $this->getAttribute('value')) {
return false;
}
return parent::setValue($value);
}
/**
* Accessor for starting value.
* @return string Parsed value.
* @access public
*/
function getDefault() {
if ($this->getAttribute('checked') !== false) {
return $this->getAttribute('value');
}
return false;
}
}
/**
* Checkbox widget.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleCheckboxTag extends SimpleWidget {
/**
* Starts with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleCheckboxTag($attributes) {
$this->SimpleWidget('input', $attributes);
if ($this->getAttribute('value') === false) {
$this->_setAttribute('value', 'on');
}
}
/**
* Tag contains no content.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
/**
* The only allowed value in the one in the
* "value" attribute. The default for this
* attribute is "on". If this widget is set to
* true, then the usual value will be taken.
* @param string $value New value.
* @return boolean True if allowed.
* @access public
*/
function setValue($value) {
if ($value === false) {
return parent::setValue($value);
}
if ($value === true) {
return parent::setValue($this->getAttribute('value'));
}
if ($value != $this->getAttribute('value')) {
return false;
}
return parent::setValue($value);
}
/**
* Accessor for starting value. The default
* value is "on".
* @return string Parsed value.
* @access public
*/
function getDefault() {
if ($this->getAttribute('checked') !== false) {
return $this->getAttribute('value');
}
return false;
}
}
/**
* A group of multiple widgets with some shared behaviour.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleTagGroup {
var $_widgets = array();
/**
* Adds a tag to the group.
* @param SimpleWidget $widget
* @access public
*/
function addWidget(&$widget) {
$this->_widgets[] = &$widget;
}
/**
* Accessor to widget set.
* @return array All widgets.
* @access protected
*/
function &_getWidgets() {
return $this->_widgets;
}
/**
* Accessor for an attribute.
* @param string $label Attribute name.
* @return boolean Always false.
* @access public
*/
function getAttribute($label) {
return false;
}
/**
* Fetches the name for the widget from the first
* member.
* @return string Name of widget.
* @access public
*/
function getName() {
if (count($this->_widgets) > 0) {
return $this->_widgets[0]->getName();
}
}
/**
* Scans the widgets for one with the appropriate
* ID field.
* @param string $id ID value to try.
* @return boolean True if matched.
* @access public
*/
function isId($id) {
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
if ($this->_widgets[$i]->isId($id)) {
return true;
}
}
return false;
}
/**
* Scans the widgets for one with the appropriate
* attached label.
* @param string $label Attached label to try.
* @return boolean True if matched.
* @access public
*/
function isLabel($label) {
for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
if ($this->_widgets[$i]->isLabel($label)) {
return true;
}
}
return false;
}
/**
* Dispatches the value into the form encoded packet.
* @param SimpleEncoding $encoding Form packet.
* @access public
*/
function write(&$encoding) {
$encoding->add($this->getName(), $this->getValue());
}
}
/**
* A group of tags with the same name within a form.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleCheckboxGroup extends SimpleTagGroup {
/**
* Accessor for current selected widget or false
* if none.
* @return string/array Widget values or false if none.
* @access public
*/
function getValue() {
$values = array();
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if ($widgets[$i]->getValue() !== false) {
$values[] = $widgets[$i]->getValue();
}
}
return $this->_coerceValues($values);
}
/**
* Accessor for starting value that is active.
* @return string/array Widget values or false if none.
* @access public
*/
function getDefault() {
$values = array();
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if ($widgets[$i]->getDefault() !== false) {
$values[] = $widgets[$i]->getDefault();
}
}
return $this->_coerceValues($values);
}
/**
* Accessor for current set values.
* @param string/array/boolean $values Either a single string, a
* hash or false for nothing set.
* @return boolean True if all values can be set.
* @access public
*/
function setValue($values) {
$values = $this->_makeArray($values);
if (! $this->_valuesArePossible($values)) {
return false;
}
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
$possible = $widgets[$i]->getAttribute('value');
if (in_array($widgets[$i]->getAttribute('value'), $values)) {
$widgets[$i]->setValue($possible);
} else {
$widgets[$i]->setValue(false);
}
}
return true;
}
/**
* Tests to see if a possible value set is legal.
* @param string/array/boolean $values Either a single string, a
* hash or false for nothing set.
* @return boolean False if trying to set a
* missing value.
* @access private
*/
function _valuesArePossible($values) {
$matches = array();
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
$possible = $widgets[$i]->getAttribute('value');
if (in_array($possible, $values)) {
$matches[] = $possible;
}
}
return ($values == $matches);
}
/**
* Converts the output to an appropriate format. This means
* that no values is false, a single value is just that
* value and only two or more are contained in an array.
* @param array $values List of values of widgets.
* @return string/array/boolean Expected format for a tag.
* @access private
*/
function _coerceValues($values) {
if (count($values) == 0) {
return false;
} elseif (count($values) == 1) {
return $values[0];
} else {
return $values;
}
}
/**
* Converts false or string into array. The opposite of
* the coercian method.
* @param string/array/boolean $value A single item is converted
* to a one item list. False
* gives an empty list.
* @return array List of values, possibly empty.
* @access private
*/
function _makeArray($value) {
if ($value === false) {
return array();
}
if (is_string($value)) {
return array($value);
}
return $value;
}
}
/**
* A group of tags with the same name within a form.
* Used for radio buttons.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleRadioGroup extends SimpleTagGroup {
/**
* Each tag is tried in turn until one is
* successfully set. The others will be
* unchecked if successful.
* @param string $value New value.
* @return boolean True if any allowed.
* @access public
*/
function setValue($value) {
if (! $this->_valueIsPossible($value)) {
return false;
}
$index = false;
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if (! $widgets[$i]->setValue($value)) {
$widgets[$i]->setValue(false);
}
}
return true;
}
/**
* Tests to see if a value is allowed.
* @param string Attempted value.
* @return boolean True if a valid value.
* @access private
*/
function _valueIsPossible($value) {
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if ($widgets[$i]->getAttribute('value') == $value) {
return true;
}
}
return false;
}
/**
* Accessor for current selected widget or false
* if none.
* @return string/boolean Value attribute or
* content of opton.
* @access public
*/
function getValue() {
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if ($widgets[$i]->getValue() !== false) {
return $widgets[$i]->getValue();
}
}
return false;
}
/**
* Accessor for starting value that is active.
* @return string/boolean Value of first checked
* widget or false if none.
* @access public
*/
function getDefault() {
$widgets = &$this->_getWidgets();
for ($i = 0, $count = count($widgets); $i < $count; $i++) {
if ($widgets[$i]->getDefault() !== false) {
return $widgets[$i]->getDefault();
}
}
return false;
}
}
/**
* Tag to keep track of labels.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleLabelTag extends SimpleTag {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleLabelTag($attributes) {
$this->SimpleTag('label', $attributes);
}
/**
* Access for the ID to attach the label to.
* @return string For attribute.
* @access public
*/
function getFor() {
return $this->getAttribute('for');
}
}
/**
* Tag to aid parsing the form.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleFormTag extends SimpleTag {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleFormTag($attributes) {
$this->SimpleTag('form', $attributes);
}
}
/**
* Tag to aid parsing the frames in a page.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleFrameTag extends SimpleTag {
/**
* Starts with a named tag with attributes only.
* @param hash $attributes Attribute names and
* string values.
*/
function SimpleFrameTag($attributes) {
$this->SimpleTag('frame', $attributes);
}
/**
* Tag contains no content.
* @return boolean False.
* @access public
*/
function expectEndTag() {
return false;
}
}
?>

View File

@ -1,684 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* Includes SimpleTest files and defined the root constant
* for dependent libraries.
*/
require_once(dirname(__FILE__) . '/invoker.php');
require_once(dirname(__FILE__) . '/errors.php');
require_once(dirname(__FILE__) . '/compatibility.php');
require_once(dirname(__FILE__) . '/scorer.php');
require_once(dirname(__FILE__) . '/expectation.php');
require_once(dirname(__FILE__) . '/dumper.php');
require_once(dirname(__FILE__) . '/simpletest.php');
if (version_compare(phpversion(), '5') >= 0) {
require_once(dirname(__FILE__) . '/exceptions.php');
require_once(dirname(__FILE__) . '/reflection_php5.php');
} else {
require_once(dirname(__FILE__) . '/reflection_php4.php');
}
if (! defined('SIMPLE_TEST')) {
/**
* @ignore
*/
define('SIMPLE_TEST', dirname(__FILE__) . '/');
}
/**#@-*/
/**
* Basic test case. This is the smallest unit of a test
* suite. It searches for
* all methods that start with the the string "test" and
* runs them. Working test cases extend this class.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleTestCase {
var $_label = false;
var $_reporter;
var $_observers;
/**
* Sets up the test with no display.
* @param string $label If no test name is given then
* the class name is used.
* @access public
*/
function SimpleTestCase($label = false) {
if ($label) {
$this->_label = $label;
}
}
/**
* Accessor for the test name for subclasses.
* @return string Name of the test.
* @access public
*/
function getLabel() {
return $this->_label ? $this->_label : get_class($this);
}
/**
* Used to invoke the single tests.
* @return SimpleInvoker Individual test runner.
* @access public
*/
function &createInvoker() {
$invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this));
if (version_compare(phpversion(), '5') >= 0) {
$invoker = &new SimpleExceptionTrappingInvoker($invoker);
}
return $invoker;
}
/**
* Uses reflection to run every method within itself
* starting with the string "test" unless a method
* is specified.
* @param SimpleReporter $reporter Current test reporter.
* @access public
*/
function run(&$reporter) {
SimpleTest::setCurrent($this);
$this->_reporter = &$reporter;
$this->_reporter->paintCaseStart($this->getLabel());
foreach ($this->getTests() as $method) {
if ($this->_reporter->shouldInvoke($this->getLabel(), $method)) {
$invoker = &$this->_reporter->createInvoker($this->createInvoker());
$invoker->before($method);
$invoker->invoke($method);
$invoker->after($method);
}
}
$this->_reporter->paintCaseEnd($this->getLabel());
unset($this->_reporter);
return $reporter->getStatus();
}
/**
* Gets a list of test names. Normally that will
* be all internal methods that start with the
* name "test". This method should be overridden
* if you want a different rule.
* @return array List of test names.
* @access public
*/
function getTests() {
$methods = array();
foreach (get_class_methods(get_class($this)) as $method) {
if ($this->_isTest($method)) {
$methods[] = $method;
}
}
return $methods;
}
/**
* Tests to see if the method is a test that should
* be run. Currently any method that starts with 'test'
* is a candidate unless it is the constructor.
* @param string $method Method name to try.
* @return boolean True if test method.
* @access protected
*/
function _isTest($method) {
if (strtolower(substr($method, 0, 4)) == 'test') {
return ! SimpleTestCompatibility::isA($this, strtolower($method));
}
return false;
}
/**
* Announces the start of the test.
* @param string $method Test method just started.
* @access public
*/
function before($method) {
$this->_reporter->paintMethodStart($method);
$this->_observers = array();
}
/**
* Sets up unit test wide variables at the start
* of each test method. To be overridden in
* actual user test cases.
* @access public
*/
function setUp() {
}
/**
* Clears the data set in the setUp() method call.
* To be overridden by the user in actual user test cases.
* @access public
*/
function tearDown() {
}
/**
* Announces the end of the test. Includes private clean up.
* @param string $method Test method just finished.
* @access public
*/
function after($method) {
for ($i = 0; $i < count($this->_observers); $i++) {
$this->_observers[$i]->atTestEnd($method);
}
$this->_reporter->paintMethodEnd($method);
}
/**
* Sets up an observer for the test end.
* @param object $observer Must have atTestEnd()
* method.
* @access public
*/
function tell(&$observer) {
$this->_observers[] = &$observer;
}
/**
* Sends a pass event with a message.
* @param string $message Message to send.
* @access public
*/
function pass($message = "Pass") {
if (! isset($this->_reporter)) {
trigger_error('Can only make assertions within test methods');
}
$this->_reporter->paintPass(
$message . $this->getAssertionLine());
return true;
}
/**
* Sends a fail event with a message.
* @param string $message Message to send.
* @access public
*/
function fail($message = "Fail") {
if (! isset($this->_reporter)) {
trigger_error('Can only make assertions within test methods');
}
$this->_reporter->paintFail(
$message . $this->getAssertionLine());
return false;
}
/**
* Formats a PHP error and dispatches it to the
* reporter.
* @param integer $severity PHP error code.
* @param string $message Text of error.
* @param string $file File error occoured in.
* @param integer $line Line number of error.
* @access public
*/
function error($severity, $message, $file, $line) {
if (! isset($this->_reporter)) {
trigger_error('Can only make assertions within test methods');
}
$this->_reporter->paintError(
"Unexpected PHP error [$message] severity [$severity] in [$file] line [$line]");
}
/**
* Formats an exception and dispatches it to the
* reporter.
* @param Exception $exception Object thrown.
* @access public
*/
function exception($exception) {
$this->_reporter->paintError(
'Unexpected exception of type [' . get_class($exception) .
'] with message ['. $exception->getMessage() .
'] in ['. $exception->getFile() .
'] line [' . $exception->getLine() . ']');
}
/**
* Sends a user defined event to the test reporter.
* This is for small scale extension where
* both the test case and either the reporter or
* display are subclassed.
* @param string $type Type of event.
* @param mixed $payload Object or message to deliver.
* @access public
*/
function signal($type, &$payload) {
if (! isset($this->_reporter)) {
trigger_error('Can only make assertions within test methods');
}
$this->_reporter->paintSignal($type, $payload);
}
/**
* Cancels any outstanding errors.
* @access public
*/
function swallowErrors() {
$queue = &SimpleErrorQueue::instance();
$queue->clear();
}
/**
* Runs an expectation directly, for extending the
* tests with new expectation classes.
* @param SimpleExpectation $expectation Expectation subclass.
* @param mixed $compare Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assert(&$expectation, $compare, $message = '%s') {
return $this->assertTrue(
$expectation->test($compare),
sprintf($message, $expectation->overlayMessage($compare)));
}
/**
* @deprecated
*/
function assertExpectation(&$expectation, $compare, $message = '%s') {
return $this->assert($expectation, $compare, $message);
}
/**
* Called from within the test methods to register
* passes and failures.
* @param boolean $result Pass on true.
* @param string $message Message to display describing
* the test state.
* @return boolean True on pass
* @access public
*/
function assertTrue($result, $message = false) {
if (! $message) {
$message = 'True assertion got ' . ($result ? 'True' : 'False');
}
if ($result) {
return $this->pass($message);
} else {
return $this->fail($message);
}
}
/**
* Will be true on false and vice versa. False
* is the PHP definition of false, so that null,
* empty strings, zero and an empty array all count
* as false.
* @param boolean $result Pass on false.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertFalse($result, $message = false) {
if (! $message) {
$message = 'False assertion got ' . ($result ? 'True' : 'False');
}
return $this->assertTrue(! $result, $message);
}
/**
* Uses a stack trace to find the line of an assertion.
* @param string $format String formatting.
* @param array $stack Stack frames top most first. Only
* needed if not using the PHP
* backtrace function.
* @return string Line number of first assert*
* method embedded in format string.
* @access public
*/
function getAssertionLine($stack = false) {
if ($stack === false) {
$stack = SimpleTestCompatibility::getStackTrace();
}
return SimpleDumper::getFormattedAssertionLine($stack);
}
/**
* Sends a formatted dump of a variable to the
* test suite for those emergency debugging
* situations.
* @param mixed $variable Variable to display.
* @param string $message Message to display.
* @return mixed The original variable.
* @access public
*/
function dump($variable, $message = false) {
$formatted = SimpleDumper::dump($variable);
if ($message) {
$formatted = $message . "\n" . $formatted;
}
$this->_reporter->paintFormattedMessage($formatted);
return $variable;
}
/**
* Dispatches a text message straight to the
* test suite. Useful for status bar displays.
* @param string $message Message to show.
* @access public
*/
function sendMessage($message) {
$this->_reporter->PaintMessage($message);
}
/**
* Accessor for the number of subtests.
* @return integer Number of test cases.
* @access public
* @static
*/
function getSize() {
return 1;
}
}
/**
* This is a composite test class for combining
* test cases and other RunnableTest classes into
* a group test.
* @package SimpleTest
* @subpackage UnitTester
*/
class GroupTest {
var $_label;
var $_test_cases;
var $_old_track_errors;
var $_xdebug_is_enabled;
/**
* Sets the name of the test suite.
* @param string $label Name sent at the start and end
* of the test.
* @access public
*/
function GroupTest($label = false) {
$this->_label = $label ? $label : get_class($this);
$this->_test_cases = array();
$this->_old_track_errors = ini_get('track_errors');
$this->_xdebug_is_enabled = function_exists('xdebug_is_enabled') ?
xdebug_is_enabled() : false;
}
/**
* Accessor for the test name for subclasses.
* @return string Name of the test.
* @access public
*/
function getLabel() {
return $this->_label;
}
/**
* Adds a test into the suite. Can be either a group
* test or some other unit test.
* @param SimpleTestCase $test_case Suite or individual test
* case implementing the
* runnable test interface.
* @access public
*/
function addTestCase(&$test_case) {
$this->_test_cases[] = &$test_case;
}
/**
* Adds a test into the suite by class name. The class will
* be instantiated as needed.
* @param SimpleTestCase $test_case Suite or individual test
* case implementing the
* runnable test interface.
* @access public
*/
function addTestClass($class) {
if ($this->_getBaseTestCase($class) == 'grouptest') {
$this->_test_cases[] = &new $class();
} else {
$this->_test_cases[] = $class;
}
}
/**
* Builds a group test from a library of test cases.
* The new group is composed into this one.
* @param string $test_file File name of library with
* test case classes.
* @access public
*/
function addTestFile($test_file) {
$existing_classes = get_declared_classes();
if ($error = $this->_requireWithError($test_file)) {
$this->addTestCase(new BadGroupTest($test_file, $error));
return;
}
$classes = $this->_selectRunnableTests($existing_classes, get_declared_classes());
if (count($classes) == 0) {
$this->addTestCase(new BadGroupTest($test_file, "No runnable test cases in [$test_file]"));
return;
}
$group = &$this->_createGroupFromClasses($test_file, $classes);
$this->addTestCase($group);
}
/**
* Requires a source file recording any syntax errors.
* @param string $file File name to require in.
* @return string/boolean An error message on failure or false
* if no errors.
* @access private
*/
function _requireWithError($file) {
$this->_enableErrorReporting();
include_once($file);
$error = isset($php_errormsg) ? $php_errormsg : false;
$this->_disableErrorReporting();
$self_inflicted_errors = array(
'Assigning the return value of new by reference is deprecated',
'var: Deprecated. Please use the public/private/protected modifiers');
if (in_array($error, $self_inflicted_errors)) {
return false;
}
return $error;
}
/**
* Sets up detection of parse errors. Note that XDebug
* interferes with this and has to be disabled. This is
* to make sure the correct error code is returned
* from unattended scripts.
* @access private
*/
function _enableErrorReporting() {
if ($this->_xdebug_is_enabled) {
xdebug_disable();
}
ini_set('track_errors', true);
}
/**
* Resets detection of parse errors to their old values.
* This is to make sure the correct error code is returned
* from unattended scripts.
* @access private
*/
function _disableErrorReporting() {
ini_set('track_errors', $this->_old_track_errors);
if ($this->_xdebug_is_enabled) {
xdebug_enable();
}
}
/**
* Calculates the incoming test cases from a before
* and after list of loaded classes. Skips abstract
* classes.
* @param array $existing_classes Classes before require().
* @param array $new_classes Classes after require().
* @return array New classes which are test
* cases that shouldn't be ignored.
* @access private
*/
function _selectRunnableTests($existing_classes, $new_classes) {
$classes = array();
foreach ($new_classes as $class) {
if (in_array($class, $existing_classes)) {
continue;
}
if ($this->_getBaseTestCase($class)) {
$reflection = new SimpleReflection($class);
if ($reflection->isAbstract()) {
SimpleTest::ignore($class);
}
$classes[] = $class;
}
}
return $classes;
}
/**
* Builds a group test from a class list.
* @param string $title Title of new group.
* @param array $classes Test classes.
* @return GroupTest Group loaded with the new
* test cases.
* @access private
*/
function &_createGroupFromClasses($title, $classes) {
SimpleTest::ignoreParentsIfIgnored($classes);
$group = &new GroupTest($title);
foreach ($classes as $class) {
if (! SimpleTest::isIgnored($class)) {
$group->addTestClass($class);
}
}
return $group;
}
/**
* Test to see if a class is derived from the
* SimpleTestCase class.
* @param string $class Class name.
* @access private
*/
function _getBaseTestCase($class) {
while ($class = get_parent_class($class)) {
$class = strtolower($class);
if ($class == "simpletestcase" || $class == "grouptest") {
return $class;
}
}
return false;
}
/**
* Delegates to a visiting collector to add test
* files.
* @param string $path Path to scan from.
* @param SimpleCollector $collector Directory scanner.
* @access public
*/
function collect($path, &$collector) {
$collector->collect($this, $path);
}
/**
* Invokes run() on all of the held test cases, instantiating
* them if necessary.
* @param SimpleReporter $reporter Current test reporter.
* @access public
*/
function run(&$reporter) {
$reporter->paintGroupStart($this->getLabel(), $this->getSize());
for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
if (is_string($this->_test_cases[$i])) {
$class = $this->_test_cases[$i];
$test = &new $class();
$test->run($reporter);
} else {
$this->_test_cases[$i]->run($reporter);
}
}
$reporter->paintGroupEnd($this->getLabel());
return $reporter->getStatus();
}
/**
* Number of contained test cases.
* @return integer Total count of cases in the group.
* @access public
*/
function getSize() {
$count = 0;
foreach ($this->_test_cases as $case) {
if (is_string($case)) {
$count++;
} else {
$count += $case->getSize();
}
}
return $count;
}
}
/**
* This is a failing group test for when a test suite hasn't
* loaded properly.
* @package SimpleTest
* @subpackage UnitTester
*/
class BadGroupTest {
var $_label;
var $_error;
/**
* Sets the name of the test suite and error message.
* @param string $label Name sent at the start and end
* of the test.
* @access public
*/
function BadGroupTest($label, $error) {
$this->_label = $label;
$this->_error = $error;
}
/**
* Accessor for the test name for subclasses.
* @return string Name of the test.
* @access public
*/
function getLabel() {
return $this->_label;
}
/**
* Sends a single error to the reporter.
* @param SimpleReporter $reporter Current test reporter.
* @access public
*/
function run(&$reporter) {
$reporter->paintGroupStart($this->getLabel(), $this->getSize());
$reporter->paintFail('Bad GroupTest [' . $this->getLabel() .
'] with error [' . $this->_error . ']');
$reporter->paintGroupEnd($this->getLabel());
return $reporter->getStatus();
}
/**
* Number of contained test cases. Always zero.
* @return integer Total count of cases in the group.
* @access public
*/
function getSize() {
return 0;
}
}
?>

View File

@ -1,373 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/test_case.php');
require_once(dirname(__FILE__) . '/dumper.php');
/**#@-*/
/**
* Standard unit test class for day to day testing
* of PHP code XP style. Adds some useful standard
* assertions.
* @package SimpleTest
* @subpackage UnitTester
*/
class UnitTestCase extends SimpleTestCase {
/**
* Creates an empty test case. Should be subclassed
* with test methods for a functional test case.
* @param string $label Name of test case. Will use
* the class name if none specified.
* @access public
*/
function UnitTestCase($label = false) {
if (! $label) {
$label = get_class($this);
}
$this->SimpleTestCase($label);
}
/**
* Will be true if the value is null.
* @param null $value Supposedly null value.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNull($value, $message = "%s") {
$dumper = &new SimpleDumper();
$message = sprintf(
$message,
"[" . $dumper->describeValue($value) . "] should be null");
return $this->assertTrue(! isset($value), $message);
}
/**
* Will be true if the value is set.
* @param mixed $value Supposedly set value.
* @param string $message Message to display.
* @return boolean True on pass.
* @access public
*/
function assertNotNull($value, $message = "%s") {
$dumper = &new SimpleDumper();
$message = sprintf(
$message,
"[" . $dumper->describeValue($value) . "] should not be null");
return $this->assertTrue(isset($value), $message);
}
/**
* Type and class test. Will pass if class
* matches the type name or is a subclass or
* if not an object, but the type is correct.
* @param mixed $object Object to test.
* @param string $type Type name as string.
* @param string $message Message to display.
* @return boolean True on pass.
* @access public
*/
function assertIsA($object, $type, $message = "%s") {
return $this->assert(
new IsAExpectation($type),
$object,
$message);
}
/**
* Type and class mismatch test. Will pass if class
* name or underling type does not match the one
* specified.
* @param mixed $object Object to test.
* @param string $type Type name as string.
* @param string $message Message to display.
* @return boolean True on pass.
* @access public
*/
function assertNotA($object, $type, $message = "%s") {
return $this->assert(
new NotAExpectation($type),
$object,
$message);
}
/**
* Will trigger a pass if the two parameters have
* the same value only. Otherwise a fail.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertEqual($first, $second, $message = "%s") {
return $this->assert(
new EqualExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters have
* a different value. Otherwise a fail.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNotEqual($first, $second, $message = "%s") {
return $this->assert(
new NotEqualExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if the if the first parameter
* is near enough to the second by the margin.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param mixed $margin Fuzziness of match.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertWithinMargin($first, $second, $margin, $message = "%s") {
return $this->assert(
new WithinMarginExpectation($first, $margin),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters differ
* by more than the margin.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param mixed $margin Fuzziness of match.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertOutsideMargin($first, $second, $margin, $message = "%s") {
return $this->assert(
new OutsideMarginExpectation($first, $margin),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters have
* the same value and same type. Otherwise a fail.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertIdentical($first, $second, $message = "%s") {
return $this->assert(
new IdenticalExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters have
* the different value or different type.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNotIdentical($first, $second, $message = "%s") {
return $this->assert(
new NotIdenticalExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if both parameters refer
* to the same object. Fail otherwise.
* @param mixed $first Object reference to check.
* @param mixed $second Hopefully the same object.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertReference(&$first, &$second, $message = "%s") {
$dumper = &new SimpleDumper();
$message = sprintf(
$message,
"[" . $dumper->describeValue($first) .
"] and [" . $dumper->describeValue($second) .
"] should reference the same object");
return $this->assertTrue(
SimpleTestCompatibility::isReference($first, $second),
$message);
}
/**
* Will trigger a pass if both parameters refer
* to different objects. Fail otherwise. The objects
* have to be identical though.
* @param mixed $first Object reference to check.
* @param mixed $second Hopefully not the same object.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertClone(&$first, &$second, $message = "%s") {
$dumper = &new SimpleDumper();
$message = sprintf(
$message,
"[" . $dumper->describeValue($first) .
"] and [" . $dumper->describeValue($second) .
"] should not be the same object");
$identical = &new IdenticalExpectation($first);
return $this->assertTrue(
$identical->test($second) &&
! SimpleTestCompatibility::isReference($first, $second),
$message);
}
/**
* @deprecated
*/
function assertCopy(&$first, &$second, $message = "%s") {
$dumper = &new SimpleDumper();
$message = sprintf(
$message,
"[" . $dumper->describeValue($first) .
"] and [" . $dumper->describeValue($second) .
"] should not be the same object");
return $this->assertFalse(
SimpleTestCompatibility::isReference($first, $second),
$message);
}
/**
* Will trigger a pass if the Perl regex pattern
* is found in the subject. Fail otherwise.
* @param string $pattern Perl regex to look for including
* the regex delimiters.
* @param string $subject String to search in.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertPattern($pattern, $subject, $message = "%s") {
return $this->assert(
new PatternExpectation($pattern),
$subject,
$message);
}
/**
* @deprecated
*/
function assertWantedPattern($pattern, $subject, $message = "%s") {
return $this->assertPattern($pattern, $subject, $message);
}
/**
* Will trigger a pass if the perl regex pattern
* is not present in subject. Fail if found.
* @param string $pattern Perl regex to look for including
* the regex delimiters.
* @param string $subject String to search in.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNoPattern($pattern, $subject, $message = "%s") {
return $this->assert(
new NoPatternExpectation($pattern),
$subject,
$message);
}
/**
* @deprecated
*/
function assertNoUnwantedPattern($pattern, $subject, $message = "%s") {
return $this->assertNoPattern($pattern, $subject, $message);
}
/**
* Confirms that no errors have occoured so
* far in the test method.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNoErrors($message = "%s") {
$queue = &SimpleErrorQueue::instance();
return $this->assertTrue(
$queue->isEmpty(),
sprintf($message, "Should be no errors"));
}
/**
* Confirms that an error has occoured and
* optionally that the error text matches exactly.
* @param string $expected Expected error text or
* false for no check.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertError($expected = false, $message = "%s") {
$queue = &SimpleErrorQueue::instance();
if ($queue->isEmpty()) {
$this->fail(sprintf($message, "Expected error not found"));
return;
}
list($severity, $content, $file, $line, $globals) = $queue->extract();
$severity = SimpleErrorQueue::getSeverityAsString($severity);
if (! $expected) {
return $this->pass(
"Captured a PHP error of [$content] severity [$severity] in [$file] line [$line] -> %s");
}
$expected = $this->_coerceToExpectation($expected);
return $this->assert(
$expected,
$content,
"Expected PHP error [$content] severity [$severity] in [$file] line [$line] -> %s");
}
/**
* Creates an equality expectation if the
* object/value is not already some type
* of expectation.
* @param mixed $expected Expected value.
* @return SimpleExpectation Expectation object.
* @access private
*/
function _coerceToExpectation($expected) {
if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) {
return $expected;
}
return new EqualExpectation($expected);
}
/**
* @deprecated
*/
function assertErrorPattern($pattern, $message = "%s") {
return $this->assertError(new PatternExpectation($pattern), $message);
}
}
?>

View File

@ -1,525 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/encoding.php');
/**#@-*/
/**
* URL parser to replace parse_url() PHP function which
* got broken in PHP 4.3.0. Adds some browser specific
* functionality such as expandomatics.
* Guesses a bit trying to separate the host from
* the path and tries to keep a raw, possibly unparsable,
* request string as long as possible.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleUrl {
var $_scheme;
var $_username;
var $_password;
var $_host;
var $_port;
var $_path;
var $_request;
var $_fragment;
var $_x;
var $_y;
var $_target;
var $_raw = false;
/**
* Constructor. Parses URL into sections.
* @param string $url Incoming URL.
* @access public
*/
function SimpleUrl($url) {
list($x, $y) = $this->_chompCoordinates($url);
$this->setCoordinates($x, $y);
$this->_scheme = $this->_chompScheme($url);
list($this->_username, $this->_password) = $this->_chompLogin($url);
$this->_host = $this->_chompHost($url);
$this->_port = false;
if (preg_match('/(.*?):(.*)/', $this->_host, $host_parts)) {
$this->_host = $host_parts[1];
$this->_port = (integer)$host_parts[2];
}
$this->_path = $this->_chompPath($url);
$this->_request = $this->_parseRequest($this->_chompRequest($url));
$this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false);
$this->_target = false;
}
/**
* Extracts the X, Y coordinate pair from an image map.
* @param string $url URL so far. The coordinates will be
* removed.
* @return array X, Y as a pair of integers.
* @access private
*/
function _chompCoordinates(&$url) {
if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) {
$url = $matches[1];
return array((integer)$matches[2], (integer)$matches[3]);
}
return array(false, false);
}
/**
* Extracts the scheme part of an incoming URL.
* @param string $url URL so far. The scheme will be
* removed.
* @return string Scheme part or false.
* @access private
*/
function _chompScheme(&$url) {
if (preg_match('/(.*?):(\/\/)(.*)/', $url, $matches)) {
$url = $matches[2] . $matches[3];
return $matches[1];
}
return false;
}
/**
* Extracts the username and password from the
* incoming URL. The // prefix will be reattached
* to the URL after the doublet is extracted.
* @param string $url URL so far. The username and
* password are removed.
* @return array Two item list of username and
* password. Will urldecode() them.
* @access private
*/
function _chompLogin(&$url) {
$prefix = '';
if (preg_match('/^(\/\/)(.*)/', $url, $matches)) {
$prefix = $matches[1];
$url = $matches[2];
}
if (preg_match('/(.*?)@(.*)/', $url, $matches)) {
$url = $prefix . $matches[2];
$parts = split(":", $matches[1]);
return array(
urldecode($parts[0]),
isset($parts[1]) ? urldecode($parts[1]) : false);
}
$url = $prefix . $url;
return array(false, false);
}
/**
* Extracts the host part of an incoming URL.
* Includes the port number part. Will extract
* the host if it starts with // or it has
* a top level domain or it has at least two
* dots.
* @param string $url URL so far. The host will be
* removed.
* @return string Host part guess or false.
* @access private
*/
function _chompHost(&$url) {
if (preg_match('/^(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) {
$url = $matches[3];
return $matches[2];
}
if (preg_match('/(.*?)(\.\.\/|\.\/|\/|\?|#|$)(.*)/', $url, $matches)) {
$tlds = SimpleUrl::getAllTopLevelDomains();
if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) {
$url = $matches[2] . $matches[3];
return $matches[1];
} elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) {
$url = $matches[2] . $matches[3];
return $matches[1];
}
}
return false;
}
/**
* Extracts the path information from the incoming
* URL. Strips this path from the URL.
* @param string $url URL so far. The host will be
* removed.
* @return string Path part or '/'.
* @access private
*/
function _chompPath(&$url) {
if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) {
$url = $matches[2] . $matches[3];
return ($matches[1] ? $matches[1] : '');
}
return '';
}
/**
* Strips off the request data.
* @param string $url URL so far. The request will be
* removed.
* @return string Raw request part.
* @access private
*/
function _chompRequest(&$url) {
if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) {
$url = $matches[2] . $matches[3];
return $matches[1];
}
return '';
}
/**
* Breaks the request down into an object.
* @param string $raw Raw request.
* @return SimpleFormEncoding Parsed data.
* @access private
*/
function _parseRequest($raw) {
$this->_raw = $raw;
$request = new SimpleGetEncoding();
foreach (split("&", $raw) as $pair) {
if (preg_match('/(.*?)=(.*)/', $pair, $matches)) {
$request->add($matches[1], urldecode($matches[2]));
} elseif ($pair) {
$request->add($pair, '');
}
}
return $request;
}
/**
* Accessor for protocol part.
* @param string $default Value to use if not present.
* @return string Scheme name, e.g "http".
* @access public
*/
function getScheme($default = false) {
return $this->_scheme ? $this->_scheme : $default;
}
/**
* Accessor for user name.
* @return string Username preceding host.
* @access public
*/
function getUsername() {
return $this->_username;
}
/**
* Accessor for password.
* @return string Password preceding host.
* @access public
*/
function getPassword() {
return $this->_password;
}
/**
* Accessor for hostname and port.
* @param string $default Value to use if not present.
* @return string Hostname only.
* @access public
*/
function getHost($default = false) {
return $this->_host ? $this->_host : $default;
}
/**
* Accessor for top level domain.
* @return string Last part of host.
* @access public
*/
function getTld() {
$path_parts = pathinfo($this->getHost());
return (isset($path_parts['extension']) ? $path_parts['extension'] : false);
}
/**
* Accessor for port number.
* @return integer TCP/IP port number.
* @access public
*/
function getPort() {
return $this->_port;
}
/**
* Accessor for path.
* @return string Full path including leading slash if implied.
* @access public
*/
function getPath() {
if (! $this->_path && $this->_host) {
return '/';
}
return $this->_path;
}
/**
* Accessor for page if any. This may be a
* directory name if ambiguious.
* @return Page name.
* @access public
*/
function getPage() {
if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) {
return false;
}
return $matches[1];
}
/**
* Gets the path to the page.
* @return string Path less the page.
* @access public
*/
function getBasePath() {
if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) {
return false;
}
return $matches[1];
}
/**
* Accessor for fragment at end of URL after the "#".
* @return string Part after "#".
* @access public
*/
function getFragment() {
return $this->_fragment;
}
/**
* Sets image coordinates. Set to false to clear
* them.
* @param integer $x Horizontal position.
* @param integer $y Vertical position.
* @access public
*/
function setCoordinates($x = false, $y = false) {
if (($x === false) || ($y === false)) {
$this->_x = $this->_y = false;
return;
}
$this->_x = (integer)$x;
$this->_y = (integer)$y;
}
/**
* Accessor for horizontal image coordinate.
* @return integer X value.
* @access public
*/
function getX() {
return $this->_x;
}
/**
* Accessor for vertical image coordinate.
* @return integer Y value.
* @access public
*/
function getY() {
return $this->_y;
}
/**
* Accessor for current request parameters
* in URL string form. Will return teh original request
* if at all possible even if it doesn't make much
* sense.
* @return string Form is string "?a=1&b=2", etc.
* @access public
*/
function getEncodedRequest() {
if ($this->_raw) {
$encoded = $this->_raw;
} else {
$encoded = $this->_request->asUrlRequest();
}
if ($encoded) {
return '?' . preg_replace('/^\?/', '', $encoded);
}
return '';
}
/**
* Adds an additional parameter to the request.
* @param string $key Name of parameter.
* @param string $value Value as string.
* @access public
*/
function addRequestParameter($key, $value) {
$this->_raw = false;
$this->_request->add($key, $value);
}
/**
* Adds additional parameters to the request.
* @param hash/SimpleFormEncoding $parameters Additional
* parameters.
* @access public
*/
function addRequestParameters($parameters) {
$this->_raw = false;
$this->_request->merge($parameters);
}
/**
* Clears down all parameters.
* @access public
*/
function clearRequest() {
$this->_raw = false;
$this->_request = &new SimpleGetEncoding();
}
/**
* Gets the frame target if present. Although
* not strictly part of the URL specification it
* acts as similarily to the browser.
* @return boolean/string Frame name or false if none.
* @access public
*/
function getTarget() {
return $this->_target;
}
/**
* Attaches a frame target.
* @param string $frame Name of frame.
* @access public
*/
function setTarget($frame) {
$this->_raw = false;
$this->_target = $frame;
}
/**
* Renders the URL back into a string.
* @return string URL in canonical form.
* @access public
*/
function asString() {
$scheme = $identity = $host = $path = $encoded = $fragment = '';
if ($this->_username && $this->_password) {
$identity = $this->_username . ':' . $this->_password . '@';
}
if ($this->getHost()) {
$scheme = $this->getScheme() ? $this->getScheme() : 'http';
$host = $this->getHost();
}
if (substr($this->_path, 0, 1) == '/') {
$path = $this->normalisePath($this->_path);
}
$encoded = $this->getEncodedRequest();
$fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
$coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
return "$scheme://$identity$host$path$encoded$fragment$coords";
}
/**
* Replaces unknown sections to turn a relative
* URL into an absolute one. The base URL can
* be either a string or a SimpleUrl object.
* @param string/SimpleUrl $base Base URL.
* @access public
*/
function makeAbsolute($base) {
if (! is_object($base)) {
$base = new SimpleUrl($base);
}
$scheme = $this->getScheme() ? $this->getScheme() : $base->getScheme();
if ($this->getHost()) {
$host = $this->getHost();
$port = $this->getPort() ? ':' . $this->getPort() : '';
$identity = $this->getIdentity() ? $this->getIdentity() . '@' : '';
if (! $identity) {
$identity = $base->getIdentity() ? $base->getIdentity() . '@' : '';
}
} else {
$host = $base->getHost();
$port = $base->getPort() ? ':' . $base->getPort() : '';
$identity = $base->getIdentity() ? $base->getIdentity() . '@' : '';
}
$path = $this->normalisePath($this->_extractAbsolutePath($base));
$encoded = $this->getEncodedRequest();
$fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
$coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords");
}
/**
* Replaces unknown sections of the path with base parts
* to return a complete absolute one.
* @param string/SimpleUrl $base Base URL.
* @param string Absolute path.
* @access private
*/
function _extractAbsolutePath($base) {
if ($this->getHost()) {
return $this->_path;
}
if (! $this->_isRelativePath($this->_path)) {
return $this->_path;
}
if ($this->_path) {
return $base->getBasePath() . $this->_path;
}
return $base->getPath();
}
/**
* Simple test to see if a path part is relative.
* @param string $path Path to test.
* @return boolean True if starts with a "/".
* @access private
*/
function _isRelativePath($path) {
return (substr($path, 0, 1) != '/');
}
/**
* Extracts the username and password for use in rendering
* a URL.
* @return string/boolean Form of username:password or false.
* @access public
*/
function getIdentity() {
if ($this->_username && $this->_password) {
return $this->_username . ':' . $this->_password;
}
return false;
}
/**
* Replaces . and .. sections of the path.
* @param string $path Unoptimised path.
* @return string Path with dots removed if possible.
* @access public
*/
function normalisePath($path) {
$path = preg_replace('|/[^/]+/\.\./|', '/', $path);
return preg_replace('|/\./|', '/', $path);
}
/**
* A pipe seperated list of all TLDs that result in two part
* domain names.
* @return string Pipe separated list.
* @access public
* @static
*/
function getAllTopLevelDomains() {
return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum';
}
}
?>

View File

@ -1,333 +0,0 @@
<?php
/**
* Base include file for SimpleTest
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/cookies.php');
require_once(dirname(__FILE__) . '/http.php');
require_once(dirname(__FILE__) . '/encoding.php');
require_once(dirname(__FILE__) . '/authentication.php');
/**#@-*/
if (! defined('DEFAULT_MAX_REDIRECTS')) {
define('DEFAULT_MAX_REDIRECTS', 3);
}
if (! defined('DEFAULT_CONNECTION_TIMEOUT')) {
define('DEFAULT_CONNECTION_TIMEOUT', 15);
}
/**
* Fetches web pages whilst keeping track of
* cookies and authentication.
* @package SimpleTest
* @subpackage WebTester
*/
class SimpleUserAgent {
var $_cookie_jar;
var $_cookies_enabled = true;
var $_authenticator;
var $_max_redirects = DEFAULT_MAX_REDIRECTS;
var $_proxy = false;
var $_proxy_username = false;
var $_proxy_password = false;
var $_connection_timeout = DEFAULT_CONNECTION_TIMEOUT;
var $_additional_headers = array();
/**
* Starts with no cookies, realms or proxies.
* @access public
*/
function SimpleUserAgent() {
$this->_cookie_jar = &new SimpleCookieJar();
$this->_authenticator = &new SimpleAuthenticator();
}
/**
* Removes expired and temporary cookies as if
* the browser was closed and re-opened. Authorisation
* has to be obtained again as well.
* @param string/integer $date Time when session restarted.
* If omitted then all persistent
* cookies are kept.
* @access public
*/
function restart($date = false) {
$this->_cookie_jar->restartSession($date);
$this->_authenticator->restartSession();
}
/**
* Adds a header to every fetch.
* @param string $header Header line to add to every
* request until cleared.
* @access public
*/
function addHeader($header) {
$this->_additional_headers[] = $header;
}
/**
* Ages the cookies by the specified time.
* @param integer $interval Amount in seconds.
* @access public
*/
function ageCookies($interval) {
$this->_cookie_jar->agePrematurely($interval);
}
/**
* Sets an additional cookie. If a cookie has
* the same name and path it is replaced.
* @param string $name Cookie key.
* @param string $value Value of cookie.
* @param string $host Host upon which the cookie is valid.
* @param string $path Cookie path if not host wide.
* @param string $expiry Expiry date.
* @access public
*/
function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
$this->_cookie_jar->setCookie($name, $value, $host, $path, $expiry);
}
/**
* Reads the most specific cookie value from the
* browser cookies.
* @param string $host Host to search.
* @param string $path Applicable path.
* @param string $name Name of cookie to read.
* @return string False if not present, else the
* value as a string.
* @access public
*/
function getCookieValue($host, $path, $name) {
return $this->_cookie_jar->getCookieValue($host, $path, $name);
}
/**
* Reads the current cookies within the base URL.
* @param string $name Key of cookie to find.
* @param SimpleUrl $base Base URL to search from.
* @return string/boolean Null if there is no base URL, false
* if the cookie is not set.
* @access public
*/
function getBaseCookieValue($name, $base) {
if (! $base) {
return null;
}
return $this->getCookieValue($base->getHost(), $base->getPath(), $name);
}
/**
* Switches off cookie sending and recieving.
* @access public
*/
function ignoreCookies() {
$this->_cookies_enabled = false;
}
/**
* Switches back on the cookie sending and recieving.
* @access public
*/
function useCookies() {
$this->_cookies_enabled = true;
}
/**
* Sets the socket timeout for opening a connection.
* @param integer $timeout Maximum time in seconds.
* @access public
*/
function setConnectionTimeout($timeout) {
$this->_connection_timeout = $timeout;
}
/**
* Sets the maximum number of redirects before
* a page will be loaded anyway.
* @param integer $max Most hops allowed.
* @access public
*/
function setMaximumRedirects($max) {
$this->_max_redirects = $max;
}
/**
* Sets proxy to use on all requests for when
* testing from behind a firewall. Set URL
* to false to disable.
* @param string $proxy Proxy URL.
* @param string $username Proxy username for authentication.
* @param string $password Proxy password for authentication.
* @access public
*/
function useProxy($proxy, $username, $password) {
if (! $proxy) {
$this->_proxy = false;
return;
}
if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) {
$proxy = 'http://'. $proxy;
}
$this->_proxy = &new SimpleUrl($proxy);
$this->_proxy_username = $username;
$this->_proxy_password = $password;
}
/**
* Test to see if the redirect limit is passed.
* @param integer $redirects Count so far.
* @return boolean True if over.
* @access private
*/
function _isTooManyRedirects($redirects) {
return ($redirects > $this->_max_redirects);
}
/**
* Sets the identity for the current realm.
* @param string $host Host to which realm applies.
* @param string $realm Full name of realm.
* @param string $username Username for realm.
* @param string $password Password for realm.
* @access public
*/
function setIdentity($host, $realm, $username, $password) {
$this->_authenticator->setIdentityForRealm($host, $realm, $username, $password);
}
/**
* Fetches a URL as a response object. Will keep trying if redirected.
* It will also collect authentication realm information.
* @param string/SimpleUrl $url Target to fetch.
* @param SimpleEncoding $encoding Additional parameters for request.
* @return SimpleHttpResponse Hopefully the target page.
* @access public
*/
function &fetchResponse($url, $encoding) {
if ($encoding->getMethod() != 'POST') {
$url->addRequestParameters($encoding);
$encoding->clear();
}
$response = &$this->_fetchWhileRedirected($url, $encoding);
if ($headers = $response->getHeaders()) {
if ($headers->isChallenge()) {
$this->_authenticator->addRealm(
$url,
$headers->getAuthentication(),
$headers->getRealm());
}
}
return $response;
}
/**
* Fetches the page until no longer redirected or
* until the redirect limit runs out.
* @param SimpleUrl $url Target to fetch.
* @param SimpelFormEncoding $encoding Additional parameters for request.
* @return SimpleHttpResponse Hopefully the target page.
* @access private
*/
function &_fetchWhileRedirected($url, $encoding) {
$redirects = 0;
do {
$response = &$this->_fetch($url, $encoding);
if ($response->isError()) {
return $response;
}
$headers = $response->getHeaders();
$location = new SimpleUrl($headers->getLocation());
$url = $location->makeAbsolute($url);
if ($this->_cookies_enabled) {
$headers->writeCookiesToJar($this->_cookie_jar, $url);
}
if (! $headers->isRedirect()) {
break;
}
$encoding = new SimpleGetEncoding();
} while (! $this->_isTooManyRedirects(++$redirects));
return $response;
}
/**
* Actually make the web request.
* @param SimpleUrl $url Target to fetch.
* @param SimpleFormEncoding $encoding Additional parameters for request.
* @return SimpleHttpResponse Headers and hopefully content.
* @access protected
*/
function &_fetch($url, $encoding) {
$request = &$this->_createRequest($url, $encoding);
$response = &$request->fetch($this->_connection_timeout);
return $response;
}
/**
* Creates a full page request.
* @param SimpleUrl $url Target to fetch as url object.
* @param SimpleFormEncoding $encoding POST/GET parameters.
* @return SimpleHttpRequest New request.
* @access private
*/
function &_createRequest($url, $encoding) {
$request = &$this->_createHttpRequest($url, $encoding);
$this->_addAdditionalHeaders($request);
if ($this->_cookies_enabled) {
$request->readCookiesFromJar($this->_cookie_jar, $url);
}
$this->_authenticator->addHeaders($request, $url);
return $request;
}
/**
* Builds the appropriate HTTP request object.
* @param SimpleUrl $url Target to fetch as url object.
* @param SimpleFormEncoding $parameters POST/GET parameters.
* @return SimpleHttpRequest New request object.
* @access protected
*/
function &_createHttpRequest($url, $encoding) {
$request = &new SimpleHttpRequest($this->_createRoute($url), $encoding);
return $request;
}
/**
* Sets up either a direct route or via a proxy.
* @param SimpleUrl $url Target to fetch as url object.
* @return SimpleRoute Route to take to fetch URL.
* @access protected
*/
function &_createRoute($url) {
if ($this->_proxy) {
$route = &new SimpleProxyRoute(
$url,
$this->_proxy,
$this->_proxy_username,
$this->_proxy_password);
} else {
$route = &new SimpleRoute($url);
}
return $route;
}
/**
* Adds additional manual headers.
* @param SimpleHttpRequest $request Outgoing request.
* @access private
*/
function _addAdditionalHeaders(&$request) {
foreach ($this->_additional_headers as $header) {
$request->addHeaderLine($header);
}
}
}
?>

View File

@ -1,1455 +0,0 @@
<?php
/**
* Base include file for SimpleTest.
* @package SimpleTest
* @subpackage WebTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/test_case.php');
require_once(dirname(__FILE__) . '/browser.php');
require_once(dirname(__FILE__) . '/page.php');
require_once(dirname(__FILE__) . '/expectation.php');
/**#@-*/
/**
* Test for an HTML widget value match.
* @package SimpleTest
* @subpackage WebTester
*/
class FieldExpectation extends SimpleExpectation {
var $_value;
/**
* Sets the field value to compare against.
* @param mixed $value Test value to match. Can be an
* expectation for say pattern matching.
* @param string $message Optiona message override. Can use %s as
* a placeholder for the original message.
* @access public
*/
function FieldExpectation($value, $message = '%s') {
$this->SimpleExpectation($message);
if (is_array($value)) {
sort($value);
}
$this->_value = $value;
}
/**
* Tests the expectation. True if it matches
* a string value or an array value in any order.
* @param mixed $compare Comparison value. False for
* an unset field.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
if ($this->_value === false) {
return ($compare === false);
}
if ($this->_isSingle($this->_value)) {
return $this->_testSingle($compare);
}
if (is_array($this->_value)) {
return $this->_testMultiple($compare);
}
return false;
}
/**
* Tests for valid field comparisons with a single option.
* @param mixed $value Value to type check.
* @return boolean True if integer, string or float.
* @access private
*/
function _isSingle($value) {
return is_string($value) || is_integer($value) || is_float($value);
}
/**
* String comparison for simple field with a single option.
* @param mixed $compare String to test against.
* @returns boolean True if matching.
* @access private
*/
function _testSingle($compare) {
if (is_array($compare) && count($compare) == 1) {
$compare = $compare[0];
}
if (! $this->_isSingle($compare)) {
return false;
}
return ($this->_value == $compare);
}
/**
* List comparison for multivalue field.
* @param mixed $compare List in any order to test against.
* @returns boolean True if matching.
* @access private
*/
function _testMultiple($compare) {
if (is_string($compare)) {
$compare = array($compare);
}
if (! is_array($compare)) {
return false;
}
sort($compare);
return ($this->_value === $compare);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$dumper = &$this->_getDumper();
if (is_array($compare)) {
sort($compare);
}
if ($this->test($compare)) {
return "Field expectation [" . $dumper->describeValue($this->_value) . "]";
} else {
return "Field expectation [" . $dumper->describeValue($this->_value) .
"] fails with [" .
$this->_dumper->describeValue($compare) . "] " .
$this->_dumper->describeDifference($this->_value, $compare);
}
}
}
/**
* Test for a specific HTTP header within a header block.
* @package SimpleTest
* @subpackage WebTester
*/
class HttpHeaderExpectation extends SimpleExpectation {
var $_expected_header;
var $_expected_value;
/**
* Sets the field and value to compare against.
* @param string $header Case insenstive trimmed header name.
* @param mixed $value Optional value to compare. If not
* given then any value will match. If
* an expectation object then that will
* be used instead.
* @param string $message Optiona message override. Can use %s as
* a placeholder for the original message.
*/
function HttpHeaderExpectation($header, $value = false, $message = '%s') {
$this->SimpleExpectation($message);
$this->_expected_header = $this->_normaliseHeader($header);
$this->_expected_value = $value;
}
/**
* Accessor for aggregated object.
* @return mixed Expectation set in constructor.
* @access protected
*/
function _getExpectation() {
return $this->_expected_value;
}
/**
* Removes whitespace at ends and case variations.
* @param string $header Name of header.
* @param string Trimmed and lowecased header
* name.
* @access private
*/
function _normaliseHeader($header) {
return strtolower(trim($header));
}
/**
* Tests the expectation. True if it matches
* a string value or an array value in any order.
* @param mixed $compare Raw header block to search.
* @return boolean True if header present.
* @access public
*/
function test($compare) {
return is_string($this->_findHeader($compare));
}
/**
* Searches the incoming result. Will extract the matching
* line as text.
* @param mixed $compare Raw header block to search.
* @return string Matching header line.
* @access protected
*/
function _findHeader($compare) {
$lines = split("\r\n", $compare);
foreach ($lines as $line) {
if ($this->_testHeaderLine($line)) {
return $line;
}
}
return false;
}
/**
* Compares a single header line against the expectation.
* @param string $line A single line to compare.
* @return boolean True if matched.
* @access private
*/
function _testHeaderLine($line) {
if (count($parsed = split(':', $line, 2)) < 2) {
return false;
}
list($header, $value) = $parsed;
if ($this->_normaliseHeader($header) != $this->_expected_header) {
return false;
}
return $this->_testHeaderValue($value, $this->_expected_value);
}
/**
* Tests the value part of the header.
* @param string $value Value to test.
* @param mixed $expected Value to test against.
* @return boolean True if matched.
* @access protected
*/
function _testHeaderValue($value, $expected) {
if ($expected === false) {
return true;
}
if (SimpleExpectation::isExpectation($expected)) {
return $expected->test(trim($value));
}
return (trim($value) == trim($expected));
}
/**
* Returns a human readable test message.
* @param mixed $compare Raw header block to search.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if (SimpleExpectation::isExpectation($this->_expected_value)) {
$message = $this->_expected_value->testMessage($compare);
} else {
$message = $this->_expected_header .
($this->_expected_value ? ': ' . $this->_expected_value : '');
}
if (is_string($line = $this->_findHeader($compare))) {
return "Searching for header [$message] found [$line]";
} else {
return "Failed to find header [$message]";
}
}
}
/**
* Test for a specific HTTP header within a header block that
* should not be found.
* @package SimpleTest
* @subpackage WebTester
*/
class NoHttpHeaderExpectation extends HttpHeaderExpectation {
var $_expected_header;
var $_expected_value;
/**
* Sets the field and value to compare against.
* @param string $unwanted Case insenstive trimmed header name.
* @param string $message Optiona message override. Can use %s as
* a placeholder for the original message.
*/
function NoHttpHeaderExpectation($unwanted, $message = '%s') {
$this->HttpHeaderExpectation($unwanted, false, $message);
}
/**
* Tests that the unwanted header is not found.
* @param mixed $compare Raw header block to search.
* @return boolean True if header present.
* @access public
*/
function test($compare) {
return ($this->_findHeader($compare) === false);
}
/**
* Returns a human readable test message.
* @param mixed $compare Raw header block to search.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
$expectation = $this->_getExpectation();
if (is_string($line = $this->_findHeader($compare))) {
return "Found unwanted header [$expectation] with [$line]";
} else {
return "Did not find unwanted header [$expectation]";
}
}
}
/**
* Test for a text substring.
* @package SimpleTest
* @subpackage UnitTester
*/
class TextExpectation extends SimpleExpectation {
var $_substring;
/**
* Sets the value to compare against.
* @param string $substring Text to search for.
* @param string $message Customised message on failure.
* @access public
*/
function TextExpectation($substring, $message = '%s') {
$this->SimpleExpectation($message);
$this->_substring = $substring;
}
/**
* Accessor for the substring.
* @return string Text to match.
* @access protected
*/
function _getSubstring() {
return $this->_substring;
}
/**
* Tests the expectation. True if the text contains the
* substring.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return (strpos($compare, $this->_substring) !== false);
}
/**
* Returns a human readable test message.
* @param mixed $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_describeTextMatch($this->_getSubstring(), $compare);
} else {
$dumper = &$this->_getDumper();
return "Text [" . $this->_getSubstring() .
"] not detected in [" .
$dumper->describeValue($compare) . "]";
}
}
/**
* Describes a pattern match including the string
* found and it's position.
* @param string $substring Text to search for.
* @param string $subject Subject to search.
* @access protected
*/
function _describeTextMatch($substring, $subject) {
$position = strpos($subject, $substring);
$dumper = &$this->_getDumper();
return "Text [$substring] detected at character [$position] in [" .
$dumper->describeValue($subject) . "] in region [" .
$dumper->clipString($subject, 100, $position) . "]";
}
}
/**
* Fail if a substring is detected within the
* comparison text.
* @package SimpleTest
* @subpackage UnitTester
*/
class NoTextExpectation extends TextExpectation {
/**
* Sets the reject pattern
* @param string $substring Text to search for.
* @param string $message Customised message on failure.
* @access public
*/
function NoTextExpectation($substring, $message = '%s') {
$this->TextExpectation($substring, $message);
}
/**
* Tests the expectation. False if the substring appears
* in the text.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}
/**
* Returns a human readable test message.
* @param string $compare Comparison value.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
$dumper = &$this->_getDumper();
return "Text [" . $this->_getSubstring() .
"] not detected in [" .
$dumper->describeValue($compare) . "]";
} else {
return $this->_describeTextMatch($this->_getSubstring(), $compare);
}
}
}
/**
* Test case for testing of web pages. Allows
* fetching of pages, parsing of HTML and
* submitting forms.
* @package SimpleTest
* @subpackage WebTester
*/
class WebTestCase extends SimpleTestCase {
var $_browser;
var $_ignore_errors = false;
/**
* Creates an empty test case. Should be subclassed
* with test methods for a functional test case.
* @param string $label Name of test case. Will use
* the class name if none specified.
* @access public
*/
function WebTestCase($label = false) {
$this->SimpleTestCase($label);
}
/**
* Announces the start of the test.
* @param string $method Test method just started.
* @access public
*/
function before($method) {
parent::before($method);
$this->setBrowser($this->createBrowser());
}
/**
* Announces the end of the test. Includes private clean up.
* @param string $method Test method just finished.
* @access public
*/
function after($method) {
$this->unsetBrowser();
parent::after($method);
}
/**
* Gets a current browser reference for setting
* special expectations or for detailed
* examination of page fetches.
* @return SimpleBrowser Current test browser object.
* @access public
*/
function &getBrowser() {
return $this->_browser;
}
/**
* Gets a current browser reference for setting
* special expectations or for detailed
* examination of page fetches.
* @param SimpleBrowser $browser New test browser object.
* @access public
*/
function setBrowser(&$browser) {
return $this->_browser = &$browser;
}
/**
* Clears the current browser reference to help the
* PHP garbage collector.
* @access public
*/
function unsetBrowser() {
unset($this->_browser);
}
/**
* Creates a new default web browser object.
* Will be cleared at the end of the test method.
* @return TestBrowser New browser.
* @access public
*/
function &createBrowser() {
$browser = &new SimpleBrowser();
return $browser;
}
/**
* Gets the last response error.
* @return string Last low level HTTP error.
* @access public
*/
function getTransportError() {
return $this->_browser->getTransportError();
}
/**
* Accessor for the currently selected URL.
* @return string Current location or false if
* no page yet fetched.
* @access public
*/
function getUrl() {
return $this->_browser->getUrl();
}
/**
* Dumps the current request for debugging.
* @access public
*/
function showRequest() {
$this->dump($this->_browser->getRequest());
}
/**
* Dumps the current HTTP headers for debugging.
* @access public
*/
function showHeaders() {
$this->dump($this->_browser->getHeaders());
}
/**
* Dumps the current HTML source for debugging.
* @access public
*/
function showSource() {
$this->dump($this->_browser->getContent());
}
/**
* Dumps the visible text only for debugging.
* @access public
*/
function showText() {
$this->dump(wordwrap($this->_browser->getContentAsText(), 80));
}
/**
* Simulates the closing and reopening of the browser.
* Temporary cookies will be discarded and timed
* cookies will be expired if later than the
* specified time.
* @param string/integer $date Time when session restarted.
* If ommitted then all persistent
* cookies are kept. Time is either
* Cookie format string or timestamp.
* @access public
*/
function restart($date = false) {
if ($date === false) {
$date = time();
}
$this->_browser->restart($date);
}
/**
* Moves cookie expiry times back into the past.
* Useful for testing timeouts and expiries.
* @param integer $interval Amount to age in seconds.
* @access public
*/
function ageCookies($interval) {
$this->_browser->ageCookies($interval);
}
/**
* Disables frames support. Frames will not be fetched
* and the frameset page will be used instead.
* @access public
*/
function ignoreFrames() {
$this->_browser->ignoreFrames();
}
/**
* Switches off cookie sending and recieving.
* @access public
*/
function ignoreCookies() {
$this->_browser->ignoreCookies();
}
/**
* Skips errors for the next request only. You might
* want to confirm that a page is unreachable for
* example.
* @access public
*/
function ignoreErrors() {
$this->_ignore_errors = true;
}
/**
* Issues a fail if there is a transport error anywhere
* in the current frameset. Only one such error is
* reported.
* @param string/boolean $result HTML or failure.
* @return string/boolean $result Passes through result.
* @access private
*/
function _failOnError($result) {
if (! $this->_ignore_errors) {
if ($error = $this->_browser->getTransportError()) {
$this->fail($error);
}
}
$this->_ignore_errors = false;
return $result;
}
/**
* Adds a header to every fetch.
* @param string $header Header line to add to every
* request until cleared.
* @access public
*/
function addHeader($header) {
$this->_browser->addHeader($header);
}
/**
* Sets the maximum number of redirects before
* the web page is loaded regardless.
* @param integer $max Maximum hops.
* @access public
*/
function setMaximumRedirects($max) {
if (! $this->_browser) {
trigger_error(
'Can only set maximum redirects in a test method, setUp() or tearDown()');
}
$this->_browser->setMaximumRedirects($max);
}
/**
* Sets the socket timeout for opening a connection and
* receiving at least one byte of information.
* @param integer $timeout Maximum time in seconds.
* @access public
*/
function setConnectionTimeout($timeout) {
$this->_browser->setConnectionTimeout($timeout);
}
/**
* Sets proxy to use on all requests for when
* testing from behind a firewall. Set URL
* to false to disable.
* @param string $proxy Proxy URL.
* @param string $username Proxy username for authentication.
* @param string $password Proxy password for authentication.
* @access public
*/
function useProxy($proxy, $username = false, $password = false) {
$this->_browser->useProxy($proxy, $username, $password);
}
/**
* Fetches a page into the page buffer. If
* there is no base for the URL then the
* current base URL is used. After the fetch
* the base URL reflects the new location.
* @param string $url URL to fetch.
* @param hash $parameters Optional additional GET data.
* @return boolean/string Raw page on success.
* @access public
*/
function get($url, $parameters = false) {
return $this->_failOnError($this->_browser->get($url, $parameters));
}
/**
* Fetches a page by POST into the page buffer.
* If there is no base for the URL then the
* current base URL is used. After the fetch
* the base URL reflects the new location.
* @param string $url URL to fetch.
* @param hash $parameters Optional additional GET data.
* @return boolean/string Raw page on success.
* @access public
*/
function post($url, $parameters = false) {
return $this->_failOnError($this->_browser->post($url, $parameters));
}
/**
* Does a HTTP HEAD fetch, fetching only the page
* headers. The current base URL is unchanged by this.
* @param string $url URL to fetch.
* @param hash $parameters Optional additional GET data.
* @return boolean True on success.
* @access public
*/
function head($url, $parameters = false) {
return $this->_failOnError($this->_browser->head($url, $parameters));
}
/**
* Equivalent to hitting the retry button on the
* browser. Will attempt to repeat the page fetch.
* @return boolean True if fetch succeeded.
* @access public
*/
function retry() {
return $this->_failOnError($this->_browser->retry());
}
/**
* Equivalent to hitting the back button on the
* browser.
* @return boolean True if history entry and
* fetch succeeded.
* @access public
*/
function back() {
return $this->_failOnError($this->_browser->back());
}
/**
* Equivalent to hitting the forward button on the
* browser.
* @return boolean True if history entry and
* fetch succeeded.
* @access public
*/
function forward() {
return $this->_failOnError($this->_browser->forward());
}
/**
* Retries a request after setting the authentication
* for the current realm.
* @param string $username Username for realm.
* @param string $password Password for realm.
* @return boolean/string HTML on successful fetch. Note
* that authentication may still have
* failed.
* @access public
*/
function authenticate($username, $password) {
return $this->_failOnError(
$this->_browser->authenticate($username, $password));
}
/**
* Gets the cookie value for the current browser context.
* @param string $name Name of cookie.
* @return string Value of cookie or false if unset.
* @access public
*/
function getCookie($name) {
return $this->_browser->getCurrentCookieValue($name);
}
/**
* Sets a cookie in the current browser.
* @param string $name Name of cookie.
* @param string $value Cookie value.
* @param string $host Host upon which the cookie is valid.
* @param string $path Cookie path if not host wide.
* @param string $expiry Expiry date.
* @access public
*/
function setCookie($name, $value, $host = false, $path = "/", $expiry = false) {
$this->_browser->setCookie($name, $value, $host, $path, $expiry);
}
/**
* Accessor for current frame focus. Will be
* false if no frame has focus.
* @return integer/string/boolean Label if any, otherwise
* the position in the frameset
* or false if none.
* @access public
*/
function getFrameFocus() {
return $this->_browser->getFrameFocus();
}
/**
* Sets the focus by index. The integer index starts from 1.
* @param integer $choice Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocusByIndex($choice) {
return $this->_browser->setFrameFocusByIndex($choice);
}
/**
* Sets the focus by name.
* @param string $name Chosen frame.
* @return boolean True if frame exists.
* @access public
*/
function setFrameFocus($name) {
return $this->_browser->setFrameFocus($name);
}
/**
* Clears the frame focus. All frames will be searched
* for content.
* @access public
*/
function clearFrameFocus() {
return $this->_browser->clearFrameFocus();
}
/**
* Clicks a visible text item. Will first try buttons,
* then links and then images.
* @param string $label Visible text or alt text.
* @return string/boolean Raw page or false.
* @access public
*/
function click($label) {
return $this->_failOnError($this->_browser->click($label));
}
/**
* Clicks the submit button by label. The owning
* form will be submitted by this.
* @param string $label Button label. An unlabeled
* button can be triggered by 'Submit'.
* @param hash $additional Additional form values.
* @return boolean/string Page on success, else false.
* @access public
*/
function clickSubmit($label = 'Submit', $additional = false) {
return $this->_failOnError(
$this->_browser->clickSubmit($label, $additional));
}
/**
* Clicks the submit button by name attribute. The owning
* form will be submitted by this.
* @param string $name Name attribute of button.
* @param hash $additional Additional form values.
* @return boolean/string Page on success.
* @access public
*/
function clickSubmitByName($name, $additional = false) {
return $this->_failOnError(
$this->_browser->clickSubmitByName($name, $additional));
}
/**
* Clicks the submit button by ID attribute. The owning
* form will be submitted by this.
* @param string $id ID attribute of button.
* @param hash $additional Additional form values.
* @return boolean/string Page on success.
* @access public
*/
function clickSubmitById($id, $additional = false) {
return $this->_failOnError(
$this->_browser->clickSubmitById($id, $additional));
}
/**
* Clicks the submit image by some kind of label. Usually
* the alt tag or the nearest equivalent. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param string $label Alt attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form values.
* @return boolean/string Page on success.
* @access public
*/
function clickImage($label, $x = 1, $y = 1, $additional = false) {
return $this->_failOnError(
$this->_browser->clickImage($label, $x, $y, $additional));
}
/**
* Clicks the submit image by the name. Usually
* the alt tag or the nearest equivalent. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param string $name Name attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form values.
* @return boolean/string Page on success.
* @access public
*/
function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
return $this->_failOnError(
$this->_browser->clickImageByName($name, $x, $y, $additional));
}
/**
* Clicks the submit image by ID attribute. The owning
* form will be submitted by this. Clicking outside of
* the boundary of the coordinates will result in
* a failure.
* @param integer/string $id ID attribute of button.
* @param integer $x X-coordinate of imaginary click.
* @param integer $y Y-coordinate of imaginary click.
* @param hash $additional Additional form values.
* @return boolean/string Page on success.
* @access public
*/
function clickImageById($id, $x = 1, $y = 1, $additional = false) {
return $this->_failOnError(
$this->_browser->clickImageById($id, $x, $y, $additional));
}
/**
* Submits a form by the ID.
* @param string $id Form ID. No button information
* is submitted this way.
* @return boolean/string Page on success.
* @access public
*/
function submitFormById($id) {
return $this->_failOnError($this->_browser->submitFormById($id));
}
/**
* Follows a link by name. Will click the first link
* found with this link text by default, or a later
* one if an index is given. Match is case insensitive
* with normalised space.
* @param string $label Text between the anchor tags.
* @param integer $index Link position counting from zero.
* @return boolean/string Page on success.
* @access public
*/
function clickLink($label, $index = 0) {
return $this->_failOnError($this->_browser->clickLink($label, $index));
}
/**
* Follows a link by id attribute.
* @param string $id ID attribute value.
* @return boolean/string Page on success.
* @access public
*/
function clickLinkById($id) {
return $this->_failOnError($this->_browser->clickLinkById($id));
}
/**
* Will trigger a pass if the two parameters have
* the same value only. Otherwise a fail. This
* is for testing hand extracted text, etc.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertEqual($first, $second, $message = "%s") {
return $this->assert(
new EqualExpectation($first),
$second,
$message);
}
/**
* Will trigger a pass if the two parameters have
* a different value. Otherwise a fail. This
* is for testing hand extracted text, etc.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertNotEqual($first, $second, $message = "%s") {
return $this->assert(
new NotEqualExpectation($first),
$second,
$message);
}
/**
* Tests for the presence of a link label. Match is
* case insensitive with normalised space.
* @param string $label Text between the anchor tags.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if link present.
* @access public
*/
function assertLink($label, $message = "%s") {
return $this->assertTrue(
$this->_browser->isLink($label),
sprintf($message, "Link [$label] should exist"));
}
/**
* Tests for the non-presence of a link label. Match is
* case insensitive with normalised space.
* @param string/integer $label Text between the anchor tags
* or ID attribute.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if link missing.
* @access public
*/
function assertNoLink($label, $message = "%s") {
return $this->assertFalse(
$this->_browser->isLink($label),
sprintf($message, "Link [$label] should not exist"));
}
/**
* Tests for the presence of a link id attribute.
* @param string $id Id attribute value.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if link present.
* @access public
*/
function assertLinkById($id, $message = "%s") {
return $this->assertTrue(
$this->_browser->isLinkById($id),
sprintf($message, "Link ID [$id] should exist"));
}
/**
* Tests for the non-presence of a link label. Match is
* case insensitive with normalised space.
* @param string $id Id attribute value.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if link missing.
* @access public
*/
function assertNoLinkById($id, $message = "%s") {
return $this->assertFalse(
$this->_browser->isLinkById($id),
sprintf($message, "Link ID [$id] should not exist"));
}
/**
* Sets all form fields with that label, or name if there
* is no label attached.
* @param string $name Name of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setField($label, $value) {
return $this->_browser->setField($label, $value);
}
/**
* Sets all form fields with that name.
* @param string $name Name of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setFieldByName($name, $value) {
return $this->_browser->setFieldByName($name, $value);
}
/**
* Sets all form fields with that name.
* @param string/integer $id Id of field in forms.
* @param string $value New value of field.
* @return boolean True if field exists, otherwise false.
* @access public
*/
function setFieldById($id, $value) {
return $this->_browser->setFieldById($id, $value);
}
/**
* Confirms that the form element is currently set
* to the expected value. A missing form will always
* fail. If no value is given then only the existence
* of the field is checked.
* @param string $name Name of field in forms.
* @param mixed $expected Expected string/array value or
* false for unset fields.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if pass.
* @access public
*/
function assertField($label, $expected = true, $message = '%s') {
$value = $this->_browser->getField($label);
return $this->_assertFieldValue($label, $value, $expected, $message);
}
/**
* Confirms that the form element is currently set
* to the expected value. A missing form element will always
* fail. If no value is given then only the existence
* of the field is checked.
* @param string $name Name of field in forms.
* @param mixed $expected Expected string/array value or
* false for unset fields.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if pass.
* @access public
*/
function assertFieldByName($name, $expected = true, $message = '%s') {
$value = $this->_browser->getFieldByName($name);
return $this->_assertFieldValue($name, $value, $expected, $message);
}
/**
* Confirms that the form element is currently set
* to the expected value. A missing form will always
* fail. If no ID is given then only the existence
* of the field is checked.
* @param string/integer $id Name of field in forms.
* @param mixed $expected Expected string/array value or
* false for unset fields.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if pass.
* @access public
*/
function assertFieldById($id, $expected = true, $message = '%s') {
$value = $this->_browser->getFieldById($id);
return $this->_assertFieldValue($id, $value, $expected, $message);
}
/**
* Tests the field value against the expectation.
* @param string $identifier Name, ID or label.
* @param mixed $value Current field value.
* @param mixed $expected Expected value to match.
* @param string $message Failure message.
* @return boolean True if pass
* @access protected
*/
function _assertFieldValue($identifier, $value, $expected, $message) {
if ($expected === true) {
return $this->assertTrue(
isset($value),
sprintf($message, "Field [$identifier] should exist"));
}
if (! SimpleExpectation::isExpectation($expected)) {
$identifier = str_replace('%', '%%', $identifier);
$expected = new FieldExpectation(
$expected,
"Field [$identifier] should match with [%s]");
}
return $this->assert($expected, $value, $message);
}
/**
* Checks the response code against a list
* of possible values.
* @param array $responses Possible responses for a pass.
* @param string $message Message to display. Default
* can be embedded with %s.
* @return boolean True if pass.
* @access public
*/
function assertResponse($responses, $message = '%s') {
$responses = (is_array($responses) ? $responses : array($responses));
$code = $this->_browser->getResponseCode();
$message = sprintf($message, "Expecting response in [" .
implode(", ", $responses) . "] got [$code]");
return $this->assertTrue(in_array($code, $responses), $message);
}
/**
* Checks the mime type against a list
* of possible values.
* @param array $types Possible mime types for a pass.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertMime($types, $message = '%s') {
$types = (is_array($types) ? $types : array($types));
$type = $this->_browser->getMimeType();
$message = sprintf($message, "Expecting mime type in [" .
implode(", ", $types) . "] got [$type]");
return $this->assertTrue(in_array($type, $types), $message);
}
/**
* Attempt to match the authentication type within
* the security realm we are currently matching.
* @param string $authentication Usually basic.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertAuthentication($authentication = false, $message = '%s') {
if (! $authentication) {
$message = sprintf($message, "Expected any authentication type, got [" .
$this->_browser->getAuthentication() . "]");
return $this->assertTrue(
$this->_browser->getAuthentication(),
$message);
} else {
$message = sprintf($message, "Expected authentication [$authentication] got [" .
$this->_browser->getAuthentication() . "]");
return $this->assertTrue(
strtolower($this->_browser->getAuthentication()) == strtolower($authentication),
$message);
}
}
/**
* Checks that no authentication is necessary to view
* the desired page.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoAuthentication($message = '%s') {
$message = sprintf($message, "Expected no authentication type, got [" .
$this->_browser->getAuthentication() . "]");
return $this->assertFalse($this->_browser->getAuthentication(), $message);
}
/**
* Attempts to match the current security realm.
* @param string $realm Name of security realm.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertRealm($realm, $message = '%s') {
if (! SimpleExpectation::isExpectation($realm)) {
$realm = new EqualExpectation($realm);
}
return $this->assert(
$realm,
$this->_browser->getRealm(),
"Expected realm -> $message");
}
/**
* Checks each header line for the required value. If no
* value is given then only an existence check is made.
* @param string $header Case insensitive header name.
* @param mixed $value Case sensitive trimmed string to
* match against. An expectation object
* can be used for pattern matching.
* @return boolean True if pass.
* @access public
*/
function assertHeader($header, $value = false, $message = '%s') {
return $this->assert(
new HttpHeaderExpectation($header, $value),
$this->_browser->getHeaders(),
$message);
}
/**
* @deprecated
*/
function assertHeaderPattern($header, $pattern, $message = '%s') {
return $this->assert(
new HttpHeaderExpectation($header, new PatternExpectation($pattern)),
$this->_browser->getHeaders(),
$message);
}
/**
* Confirms that the header type has not been received.
* Only the landing page is checked. If you want to check
* redirect pages, then you should limit redirects so
* as to capture the page you want.
* @param string $header Case insensitive header name.
* @return boolean True if pass.
* @access public
*/
function assertNoHeader($header, $message = '%s') {
return $this->assert(
new NoHttpHeaderExpectation($header),
$this->_browser->getHeaders(),
$message);
}
/**
* @deprecated
*/
function assertNoUnwantedHeader($header, $message = '%s') {
return $this->assertNoHeader($header, $message);
}
/**
* Tests the text between the title tags.
* @param string $title Expected title.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertTitle($title = false, $message = '%s') {
if (! SimpleExpectation::isExpectation($title)) {
$title = new EqualExpectation($title);
}
return $this->assert($title, $this->_browser->getTitle(), $message);
}
/**
* Will trigger a pass if the text is found in the plain
* text form of the page.
* @param string $text Text to look for.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertText($text, $message = '%s') {
return $this->assert(
new TextExpectation($text),
$this->_browser->getContentAsText(),
$message);
}
/**
* @deprecated
*/
function assertWantedText($text, $message = '%s') {
return $this->assertText($text, $message);
}
/**
* Will trigger a pass if the text is not found in the plain
* text form of the page.
* @param string $text Text to look for.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoText($text, $message = '%s') {
return $this->assert(
new NoTextExpectation($text),
$this->_browser->getContentAsText(),
$message);
}
/**
* @deprecated
*/
function assertNoUnwantedText($text, $message = '%s') {
return $this->assertNoText($text, $message);
}
/**
* Will trigger a pass if the Perl regex pattern
* is found in the raw content.
* @param string $pattern Perl regex to look for including
* the regex delimiters.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertPattern($pattern, $message = '%s') {
return $this->assert(
new PatternExpectation($pattern),
$this->_browser->getContent(),
$message);
}
/**
* @deprecated
*/
function assertWantedPattern($pattern, $message = '%s') {
return $this->assertPattern($pattern, $message);
}
/**
* Will trigger a pass if the perl regex pattern
* is not present in raw content.
* @param string $pattern Perl regex to look for including
* the regex delimiters.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoPattern($pattern, $message = '%s') {
return $this->assert(
new NoPatternExpectation($pattern),
$this->_browser->getContent(),
$message);
}
/**
* @deprecated
*/
function assertNoUnwantedPattern($pattern, $message = '%s') {
return $this->assertNoPattern($pattern, $message);
}
/**
* Checks that a cookie is set for the current page
* and optionally checks the value.
* @param string $name Name of cookie to test.
* @param string $expected Expected value as a string or
* false if any value will do.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertCookie($name, $expected = false, $message = '%s') {
$value = $this->getCookie($name);
if (! $expected) {
return $this->assertTrue(
$value,
sprintf($message, "Expecting cookie [$name]"));
}
if (! SimpleExpectation::isExpectation($expected)) {
$expected = new EqualExpectation($expected);
}
return $this->assert($expected, $value, "Expecting cookie [$name] -> $message");
}
/**
* Checks that no cookie is present or that it has
* been successfully cleared.
* @param string $name Name of cookie to test.
* @param string $message Message to display.
* @return boolean True if pass.
* @access public
*/
function assertNoCookie($name, $message = '%s') {
return $this->assertTrue(
$this->getCookie($name) === false,
sprintf($message, "Not expecting cookie [$name]"));
}
}
?>

View File

@ -1,614 +0,0 @@
<?php
/**
* base include file for SimpleTest
* @package SimpleTest
* @subpackage UnitTester
* @version $Id$
*/
/**#@+
* include other SimpleTest class files
*/
require_once(dirname(__FILE__) . '/scorer.php');
/**#@-*/
/**
* Creates the XML needed for remote communication
* by SimpleTest.
* @package SimpleTest
* @subpackage UnitTester
*/
class XmlReporter extends SimpleReporter {
var $_indent;
var $_namespace;
/**
* Does nothing yet.
* @access public
*/
function XmlReporter($namespace = false, $indent = ' ') {
$this->SimpleReporter();
$this->_namespace = ($namespace ? $namespace . ':' : '');
$this->_indent = $indent;
}
/**
* Calculates the pretty printing indent level
* from the current level of nesting.
* @param integer $offset Extra indenting level.
* @return string Leading space.
* @access protected
*/
function _getIndent($offset = 0) {
return str_repeat(
$this->_indent,
count($this->getTestList()) + $offset);
}
/**
* Converts character string to parsed XML
* entities string.
* @param string text Unparsed character data.
* @return string Parsed character data.
* @access public
*/
function toParsedXml($text) {
return str_replace(
array('&', '<', '>', '"', '\''),
array('&amp;', '&lt;', '&gt;', '&quot;', '&apos;'),
$text);
}
/**
* Paints the start of a group test.
* @param string $test_name Name of test that is starting.
* @param integer $size Number of test cases starting.
* @access public
*/
function paintGroupStart($test_name, $size) {
parent::paintGroupStart($test_name, $size);
print $this->_getIndent();
print "<" . $this->_namespace . "group size=\"$size\">\n";
print $this->_getIndent(1);
print "<" . $this->_namespace . "name>" .
$this->toParsedXml($test_name) .
"</" . $this->_namespace . "name>\n";
}
/**
* Paints the end of a group test.
* @param string $test_name Name of test that is ending.
* @access public
*/
function paintGroupEnd($test_name) {
print $this->_getIndent();
print "</" . $this->_namespace . "group>\n";
parent::paintGroupEnd($test_name);
}
/**
* Paints the start of a test case.
* @param string $test_name Name of test that is starting.
* @access public
*/
function paintCaseStart($test_name) {
parent::paintCaseStart($test_name);
print $this->_getIndent();
print "<" . $this->_namespace . "case>\n";
print $this->_getIndent(1);
print "<" . $this->_namespace . "name>" .
$this->toParsedXml($test_name) .
"</" . $this->_namespace . "name>\n";
}
/**
* Paints the end of a test case.
* @param string $test_name Name of test that is ending.
* @access public
*/
function paintCaseEnd($test_name) {
print $this->_getIndent();
print "</" . $this->_namespace . "case>\n";
parent::paintCaseEnd($test_name);
}
/**
* Paints the start of a test method.
* @param string $test_name Name of test that is starting.
* @access public
*/
function paintMethodStart($test_name) {
parent::paintMethodStart($test_name);
print $this->_getIndent();
print "<" . $this->_namespace . "test>\n";
print $this->_getIndent(1);
print "<" . $this->_namespace . "name>" .
$this->toParsedXml($test_name) .
"</" . $this->_namespace . "name>\n";
}
/**
* Paints the end of a test method.
* @param string $test_name Name of test that is ending.
* @param integer $progress Number of test cases ending.
* @access public
*/
function paintMethodEnd($test_name) {
print $this->_getIndent();
print "</" . $this->_namespace . "test>\n";
parent::paintMethodEnd($test_name);
}
/**
* Increments the pass count.
* @param string $message Message is ignored.
* @access public
*/
function paintPass($message) {
parent::paintPass($message);
print $this->_getIndent(1);
print "<" . $this->_namespace . "pass>";
print $this->toParsedXml($message);
print "</" . $this->_namespace . "pass>\n";
}
/**
* Increments the fail count.
* @param string $message Message is ignored.
* @access public
*/
function paintFail($message) {
parent::paintFail($message);
print $this->_getIndent(1);
print "<" . $this->_namespace . "fail>";
print $this->toParsedXml($message);
print "</" . $this->_namespace . "fail>\n";
}
/**
* Paints a PHP error or exception.
* @param string $message Message is ignored.
* @access public
* @abstract
*/
function paintError($message) {
parent::paintError($message);
print $this->_getIndent(1);
print "<" . $this->_namespace . "exception>";
print $this->toParsedXml($message);
print "</" . $this->_namespace . "exception>\n";
}
/**
* Paints a simple supplementary message.
* @param string $message Text to display.
* @access public
*/
function paintMessage($message) {
parent::paintMessage($message);
print $this->_getIndent(1);
print "<" . $this->_namespace . "message>";
print $this->toParsedXml($message);
print "</" . $this->_namespace . "message>\n";
}
/**
* Paints a formatted ASCII message such as a
* variable dump.
* @param string $message Text to display.
* @access public
*/
function paintFormattedMessage($message) {
parent::paintFormattedMessage($message);
print $this->_getIndent(1);
print "<" . $this->_namespace . "formatted>";
print "<![CDATA[$message]]>";
print "</" . $this->_namespace . "formatted>\n";
}
/**
* Serialises the event object.
* @param string $type Event type as text.
* @param mixed $payload Message or object.
* @access public
*/
function paintSignal($type, &$payload) {
parent::paintSignal($type, $payload);
print $this->_getIndent(1);
print "<" . $this->_namespace . "signal type=\"$type\">";
print "<![CDATA[" . serialize($payload) . "]]>";
print "</" . $this->_namespace . "signal>\n";
}
/**
* Paints the test document header.
* @param string $test_name First test top level
* to start.
* @access public
* @abstract
*/
function paintHeader($test_name) {
if (! SimpleReporter::inCli()) {
header('Content-type: text/xml');
}
print "<?xml version=\"1.0\"";
if ($this->_namespace) {
print " xmlns:" . $this->_namespace .
"=\"www.lastcraft.com/SimpleTest/Beta3/Report\"";
}
print "?>\n";
print "<" . $this->_namespace . "run>\n";
}
/**
* Paints the test document footer.
* @param string $test_name The top level test.
* @access public
* @abstract
*/
function paintFooter($test_name) {
print "</" . $this->_namespace . "run>\n";
}
}
/**
* Accumulator for incoming tag. Holds the
* incoming test structure information for
* later dispatch to the reporter.
* @package SimpleTest
* @subpackage UnitTester
*/
class NestingXmlTag {
var $_name;
var $_attributes;
/**
* Sets the basic test information except
* the name.
* @param hash $attributes Name value pairs.
* @access public
*/
function NestingXmlTag($attributes) {
$this->_name = false;
$this->_attributes = $attributes;
}
/**
* Sets the test case/method name.
* @param string $name Name of test.
* @access public
*/
function setName($name) {
$this->_name = $name;
}
/**
* Accessor for name.
* @return string Name of test.
* @access public
*/
function getName() {
return $this->_name;
}
/**
* Accessor for attributes.
* @return hash All attributes.
* @access protected
*/
function _getAttributes() {
return $this->_attributes;
}
}
/**
* Accumulator for incoming method tag. Holds the
* incoming test structure information for
* later dispatch to the reporter.
* @package SimpleTest
* @subpackage UnitTester
*/
class NestingMethodTag extends NestingXmlTag {
/**
* Sets the basic test information except
* the name.
* @param hash $attributes Name value pairs.
* @access public
*/
function NestingMethodTag($attributes) {
$this->NestingXmlTag($attributes);
}
/**
* Signals the appropriate start event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintStart(&$listener) {
$listener->paintMethodStart($this->getName());
}
/**
* Signals the appropriate end event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintEnd(&$listener) {
$listener->paintMethodEnd($this->getName());
}
}
/**
* Accumulator for incoming case tag. Holds the
* incoming test structure information for
* later dispatch to the reporter.
* @package SimpleTest
* @subpackage UnitTester
*/
class NestingCaseTag extends NestingXmlTag {
/**
* Sets the basic test information except
* the name.
* @param hash $attributes Name value pairs.
* @access public
*/
function NestingCaseTag($attributes) {
$this->NestingXmlTag($attributes);
}
/**
* Signals the appropriate start event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintStart(&$listener) {
$listener->paintCaseStart($this->getName());
}
/**
* Signals the appropriate end event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintEnd(&$listener) {
$listener->paintCaseEnd($this->getName());
}
}
/**
* Accumulator for incoming group tag. Holds the
* incoming test structure information for
* later dispatch to the reporter.
* @package SimpleTest
* @subpackage UnitTester
*/
class NestingGroupTag extends NestingXmlTag {
/**
* Sets the basic test information except
* the name.
* @param hash $attributes Name value pairs.
* @access public
*/
function NestingGroupTag($attributes) {
$this->NestingXmlTag($attributes);
}
/**
* Signals the appropriate start event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintStart(&$listener) {
$listener->paintGroupStart($this->getName(), $this->getSize());
}
/**
* Signals the appropriate end event on the
* listener.
* @param SimpleReporter $listener Target for events.
* @access public
*/
function paintEnd(&$listener) {
$listener->paintGroupEnd($this->getName());
}
/**
* The size in the attributes.
* @return integer Value of size attribute or zero.
* @access public
*/
function getSize() {
$attributes = $this->_getAttributes();
if (isset($attributes['SIZE'])) {
return (integer)$attributes['SIZE'];
}
return 0;
}
}
/**
* Parser for importing the output of the XmlReporter.
* Dispatches that output to another reporter.
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleTestXmlParser {
var $_listener;
var $_expat;
var $_tag_stack;
var $_in_content_tag;
var $_content;
var $_attributes;
/**
* Loads a listener with the SimpleReporter
* interface.
* @param SimpleReporter $listener Listener of tag events.
* @access public
*/
function SimpleTestXmlParser(&$listener) {
$this->_listener = &$listener;
$this->_expat = &$this->_createParser();
$this->_tag_stack = array();
$this->_in_content_tag = false;
$this->_content = '';
$this->_attributes = array();
}
/**
* Parses a block of XML sending the results to
* the listener.
* @param string $chunk Block of text to read.
* @return boolean True if valid XML.
* @access public
*/
function parse($chunk) {
if (! xml_parse($this->_expat, $chunk)) {
trigger_error('XML parse error with ' .
xml_error_string(xml_get_error_code($this->_expat)));
return false;
}
return true;
}
/**
* Sets up expat as the XML parser.
* @return resource Expat handle.
* @access protected
*/
function &_createParser() {
$expat = xml_parser_create();
xml_set_object($expat, $this);
xml_set_element_handler($expat, '_startElement', '_endElement');
xml_set_character_data_handler($expat, '_addContent');
xml_set_default_handler($expat, '_default');
return $expat;
}
/**
* Opens a new test nesting level.
* @return NestedXmlTag The group, case or method tag
* to start.
* @access private
*/
function _pushNestingTag($nested) {
array_unshift($this->_tag_stack, $nested);
}
/**
* Accessor for current test structure tag.
* @return NestedXmlTag The group, case or method tag
* being parsed.
* @access private
*/
function &_getCurrentNestingTag() {
return $this->_tag_stack[0];
}
/**
* Ends a nesting tag.
* @return NestedXmlTag The group, case or method tag
* just finished.
* @access private
*/
function _popNestingTag() {
return array_shift($this->_tag_stack);
}
/**
* Test if tag is a leaf node with only text content.
* @param string $tag XML tag name.
* @return @boolean True if leaf, false if nesting.
* @private
*/
function _isLeaf($tag) {
return in_array($tag, array(
'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL'));
}
/**
* Handler for start of event element.
* @param resource $expat Parser handle.
* @param string $tag Element name.
* @param hash $attributes Name value pairs.
* Attributes without content
* are marked as true.
* @access protected
*/
function _startElement($expat, $tag, $attributes) {
$this->_attributes = $attributes;
if ($tag == 'GROUP') {
$this->_pushNestingTag(new NestingGroupTag($attributes));
} elseif ($tag == 'CASE') {
$this->_pushNestingTag(new NestingCaseTag($attributes));
} elseif ($tag == 'TEST') {
$this->_pushNestingTag(new NestingMethodTag($attributes));
} elseif ($this->_isLeaf($tag)) {
$this->_in_content_tag = true;
$this->_content = '';
}
}
/**
* End of element event.
* @param resource $expat Parser handle.
* @param string $tag Element name.
* @access protected
*/
function _endElement($expat, $tag) {
$this->_in_content_tag = false;
if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) {
$nesting_tag = $this->_popNestingTag();
$nesting_tag->paintEnd($this->_listener);
} elseif ($tag == 'NAME') {
$nesting_tag = &$this->_getCurrentNestingTag();
$nesting_tag->setName($this->_content);
$nesting_tag->paintStart($this->_listener);
} elseif ($tag == 'PASS') {
$this->_listener->paintPass($this->_content);
} elseif ($tag == 'FAIL') {
$this->_listener->paintFail($this->_content);
} elseif ($tag == 'EXCEPTION') {
$this->_listener->paintError($this->_content);
} elseif ($tag == 'SIGNAL') {
$this->_listener->paintSignal(
$this->_attributes['TYPE'],
unserialize($this->_content));
} elseif ($tag == 'MESSAGE') {
$this->_listener->paintMessage($this->_content);
} elseif ($tag == 'FORMATTED') {
$this->_listener->paintFormattedMessage($this->_content);
}
}
/**
* Content between start and end elements.
* @param resource $expat Parser handle.
* @param string $text Usually output messages.
* @access protected
*/
function _addContent($expat, $text) {
if ($this->_in_content_tag) {
$this->_content .= $text;
}
return true;
}
/**
* XML and Doctype handler. Discards all such content.
* @param resource $expat Parser handle.
* @param string $default Text of default content.
* @access protected
*/
function _default($expat, $default) {
}
}
?>

View File

@ -1,570 +0,0 @@
<?php
/**
* Rigorous testing of all of DataObject's abilities
* The testing is carried out by generating a number of test object types.
*/
class DataObjectTest extends UnitTestCase {
static $tables = array('DataObjectTest_Class','DataObjectTest_NoDefaults','DataObjectTest_OtherSubclass',
'DataObjectTest_Subclass','DataObjectTest_FinalOne','DataObjectTest_BrokenBeforeWrite');
public $whatsBeingTested = "Everything except for permissions and inter-table relationships are being tested";
public $testComplete = "orange";
function __construct() {
global $_ALL_CLASSES;
// Build the data-objects
Database::$supressOutput = true;
foreach(self::$tables as $table) {
DB::query("DROP TABLE IF EXISTS $table");
singleton($table)->requireTable();
$_ALL_CLASSES['hastable'][$table] = true;
}
// Create some Class and Subclass objects
$obj = new DataObjectTest_Class();
$obj->update(array("Field1" => 1, "Field2" => "red"));
$obj->write();
$obj = new DataObjectTest_Class();
$obj->update(array("Field1" => 6, "Field2" => "red"));
$obj->write();
$obj = new DataObjectTest_Class();
$obj->update(array("Field1" => 6, "Field2" => "green"));
$obj->write();
$obj = new DataObjectTest_Class();
$obj->update(array("Field1" => 2, "Field2" => "green"));
$obj->write();
$obj = new DataObjectTest_Subclass();
$obj->update(array("Field1" => 2, "Field2" => "black", "Field3" => "2005-12-03"));
$obj->write();
$obj = new DataObjectTest_Subclass();
$obj->update(array("Field1" => 6, "Field2" => "red", "Field3" => "2006-01-10"));
$obj->write();
$obj = new DataObjectTest_Subclass();
$obj->update(array("Field1" => 4, "Field2" => "blue"));
$obj->write();
$obj = new DataObjectTest_OtherSubclass();
$obj->update(array("Field1" => 4, "OtherField" => 6, "DuplicateField" => 1));
$obj->write();
$obj = new DataObjectTest_OtherSubclass();
$obj->update(array("Field1" => 4, "OtherField" => 4, "DuplicateField" => 2));
$obj->write();
}
function __destruct() {
// Remove all our tester tables
foreach(self::$tables as $table) DB::query("DROP TABLE $table");
}
/**
* Test customDatabaseFields(), databaseFields(), requireTable()
*/
function testDatabaseSetup() {
// Check that the database has been created correctly.
foreach(self::$tables as $table) {
foreach(DB::query("SHOW FIELDS FROM $table") as $item) $tableSpec[$table][] = $item;
}
$desiredSpec = array ( 'DataObjectTest_Class' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'ClassName', 'Type' => 'enum(\'DataObjectTest_Class\',\'DataObjectTest_Subclass\',\'DataObjectTest_OtherSubclass\')', 'Null' => 'YES', 'Key' => '', 'Default' => 'DataObjectTest_Class', 'Extra' => '', ), 2 => array ( 'Field' => 'Created', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 3 => array ( 'Field' => 'LastEdited', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 4 => array ( 'Field' => 'Field1', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 5 => array ( 'Field' => 'Field2', 'Type' => 'varchar(50)', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 6 => array ( 'Field' => 'Link1ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), 'DataObjectTest_NoDefaults' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'ClassName', 'Type' => 'enum(\'DataObjectTest_NoDefaults\')', 'Null' => 'YES', 'Key' => '', 'Default' => 'DataObjectTest_NoDefaults', 'Extra' => '', ), 2 => array ( 'Field' => 'Created', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 3 => array ( 'Field' => 'LastEdited', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 4 => array ( 'Field' => 'Field1', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 5 => array ( 'Field' => 'Field2', 'Type' => 'tinyint(1) unsigned', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), 'DataObjectTest_OtherSubclass' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'DuplicateField', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 2 => array ( 'Field' => 'OtherField', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), 'DataObjectTest_Subclass' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'Field3', 'Type' => 'date', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 2 => array ( 'Field' => 'AnotherField', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 3 => array ( 'Field' => 'VarcharField', 'Type' => 'varchar(50)', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 4 => array ( 'Field' => 'DuplicateField', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 5 => array ( 'Field' => 'Link2ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), 6 => array ( 'Field' => 'Link3ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), 'DataObjectTest_FinalOne' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'ClassName', 'Type' => 'enum(\'DataObjectTest_FinalOne\')', 'Null' => 'YES', 'Key' => '', 'Default' => 'DataObjectTest_FinalOne', 'Extra' => '', ), 2 => array ( 'Field' => 'Created', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 3 => array ( 'Field' => 'LastEdited', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 4 => array ( 'Field' => 'Field1', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), 'DataObjectTest_BrokenBeforeWrite' => array ( 0 => array ( 'Field' => 'ID', 'Type' => 'int(11)', 'Null' => '', 'Key' => 'PRI', 'Default' => NULL, 'Extra' => 'auto_increment', ), 1 => array ( 'Field' => 'ClassName', 'Type' => 'enum(\'DataObjectTest_BrokenBeforeWrite\')', 'Null' => 'YES', 'Key' => '', 'Default' => 'DataObjectTest_BrokenBeforeWrite', 'Extra' => '', ), 2 => array ( 'Field' => 'Created', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 3 => array ( 'Field' => 'LastEdited', 'Type' => 'datetime', 'Null' => 'YES', 'Key' => '', 'Default' => NULL, 'Extra' => '', ), 4 => array ( 'Field' => 'Field1', 'Type' => 'int(11)', 'Null' => '', 'Key' => '', 'Default' => '0', 'Extra' => '', ), ), );
// Use this line to generate an updated spec.
// echo "<p>" . var_export($tableSpec, true) . "</p>";
$this->assertEqual($tableSpec, $desiredSpec, "Database schema not properly generated: %s");
// Call databaseFields() on an object with just fields
$obj = new DataObjectTest_NoDefaults();
$this->assertEqual($obj->databaseFields(), array(
'ClassName' => "Enum('DataObjectTest_NoDefaults')",
'Created' => "Datetime",
'LastEdited' => "Datetime",
'Field1' => "Int",
'Field2' => "Boolean",
));
// Call customDatabaseFields() on an object with fields and has_ones
$obj2 = new DataObjectTest_Class();
$this->assertEqual($obj2->customDatabaseFields(), array(
'Field1' => "Int",
'Field2' => "Varchar",
'Link1ID' => "Int",
));
}
/**
* Test __construct(), populateDefaults()
*/
function testCreation() {
// Create a new object
$obj = new DataObjectTest_Subclass();
// Are its defaults set, from self and parent?
$this->assertEqual($obj->Field2,'happy');
$this->assertEqual($obj->AnotherField,7);
$this->assertEqual($obj->getAllFields(), array(
'ID' => 0,
'Field2' => 'happy',
'AnotherField' => 7,
));
// Can you write it? Does it create a new record? Are all the defaults saved?
$obj->write();
$this->assertNotEqual($obj->ID, 0);
$this->assertEqual(DB::query("SELECT Field2 FROM DataObjectTest_Class WHERE ID = $obj->ID")->value(), 'happy');
$this->assertEqual(DB::query("SELECT AnotherField FROM DataObjectTest_Subclass WHERE ID = $obj->ID")->value(), 7);
// Can you write an object that has no defaults?
$obj2 = new DataObjectTest_NoDefaults();
$obj2->write();
$this->assertNotEqual($obj->ID, 0);
// Create an object, passing the data - force a change
$obj3 = new DataObjectTest_Subclass(array(
"ID" => $obj->ID,
"Field2" => 'sad',
));
$obj3->forceChange();
$this->assertNull($obj3->AnotherField);
// Does saving update the database, rather than insert?
$obj3->write();
$this->assertEqual(DB::$lastQuery, array ( 'DataObjectTest_Class' => array ( 'fields' => array ( 'Field2' => '\'sad\'', 'LastEdited' => 'now()', ), 'command' => 'update', 'id' => '10', ), ));
$this->assertEqual($obj->ID, $obj3->ID);
$this->assertEqual(DB::query("SELECT Field2 FROM DataObjectTest_Class WHERE ID = $obj3->ID")->value(), 'sad');
$this->assertEqual(DB::query("SELECT AnotherField FROM DataObjectTest_Subclass WHERE ID = $obj3->ID")->value(), 7);
// Create a singleton - are the defaults not set?
$obj4 = singleton("DataObjectTest_Class");
$this->assertNull($obj4->Field2);
// Clean-up: delete any records.
$obj->delete();
$obj2->delete();
$obj3->delete();
}
/**
* Test buildDataObjectSet(), buildSQL()
*/
function testQueryGeneration() {
// Test query of Class - returns Class, Subclass and OtherSubclass objects
$records = DataObject::get("DataObjectTest_Class");
$this->assertEqual($records->consolidate('ClassName','Field1','Field2','Link1ID','Field3','AnotherField'),
array (
array( 'DataObjectTest_Class', '1', 'red', '0', '', '',),
array( 'DataObjectTest_Class', '6', 'red', '0', '', '',),
array( 'DataObjectTest_Class', '6', 'green', '0', '', '',),
array( 'DataObjectTest_Class', '2', 'green', '0', '', '',),
array( 'DataObjectTest_Subclass', '2', 'black', '0', '2005-12-03', '7',),
array( 'DataObjectTest_Subclass', '6', 'red', '0', '2006-01-10', '7',),
array( 'DataObjectTest_Subclass', '4', 'blue', '0', '', '7',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '',),
)
);
// Test query of Subclass - returns just Subclass objects
$records = DataObject::get("DataObjectTest_Subclass");
$this->assertEqual($records->consolidate('ClassName','Field1','Field2','Link1ID','Field3','AnotherField'),
array (
array( 'DataObjectTest_Subclass', '2', 'black', '0', '2005-12-03', '7',),
array( 'DataObjectTest_Subclass', '6', 'red', '0', '2006-01-10', '7',),
array( 'DataObjectTest_Subclass', '4', 'blue', '0', '', '7',),
)
);
// Test filtering on one table's fields, sorting on another
$records = DataObject::get("DataObjectTest_Class", "Field1 = 4", "OtherField");
$this->assertEqual($records->consolidate('ClassName','Field1','Field2','Link1ID','Field3','AnotherField'),
array (
array( 'DataObjectTest_Subclass', '4', 'blue', '0', '', '7',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '',),
)
);
// Test filtering on DuplicateField
$records = DataObject::get("DataObjectTest_Class", "`DataObjectTest_OtherSubclass`.DuplicateField = 2");
$this->assertEqual($records->consolidate('ClassName','Field1','Field2','Link1ID','Field3','AnotherField'),
array (
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '',),
)
);
// Test sorting by DuplicateField
$records = DataObject::get("DataObjectTest_Class", "Field1 = 4", "`DataObjectTest_OtherSubclass`.DuplicateField");
$this->assertEqual($records->consolidate('ClassName','Field1','Field2','Link1ID','Field3','AnotherField','DuplicateField'),
array (
array( 'DataObjectTest_Subclass', '4', 'blue', '0', '', '7', '',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '', '1',),
array( 'DataObjectTest_OtherSubclass', '4', 'happy', '0', '', '', '2',),
)
);
// Test an empty query
$records = DataObject::get("DataObjectTest_Class", "Field1 = 4234234", "`DataObjectTest_OtherSubclass`.DuplicateField");
$this->assertNull($records);
}
/**
* Test buildDataObjectSet(), buildSQL()
*/
function testLimits() {
// Test query of Class with a limit 5
$records = DataObject::get("DataObjectTest_Class", "", "`DataObjectTest_Class`.ID", "", "5");
$this->assertPattern('/start=5$/', $records->NextLink());
$this->assertNull($records->PrevLink());
$this->assertEqual(5, $records->Count());
$this->assertEqual(9, $records->TotalItems());
// Test query of Class with a limit 5,5
$records = DataObject::get("DataObjectTest_Class", "", "`DataObjectTest_Class`.ID", "", "5,5");
$this->assertNull($records->NextLink());
$this->assertPattern('/start=0$/',$records->PrevLink());
$this->assertEqual(4, $records->Count());
$this->assertEqual(9, $records->TotalItems());
// Test query of Class with a limit 0,5
$records = DataObject::get("DataObjectTest_Class", "", "`DataObjectTest_Class`.ID", "", "0,5");
$this->assertPattern('/start=5$/', $records->NextLink());
$this->assertNull($records->PrevLink());
$this->assertEqual(5, $records->Count());
$this->assertEqual(9, $records->TotalItems());
}
/**
* Test delete(), onBeforeDelete()
*/
function testDelete() {
// Test simple delete - has the record disappeared
$obj = new DataObjectTest_Subclass();
$obj->write();
$id = $obj->ID;
$this->assertEqual($id, DB::query("SELECT ID FROM DataObjectTest_Class WHERE ID = $id")->value());
$this->assertEqual($id, DB::query("SELECT ID FROM DataObjectTest_Subclass WHERE ID = $id")->value());
$obj->delete();
$this->assertNull(DB::query("SELECT ID FROM DataObjectTest_Class WHERE ID = $id")->value(), "Couldn't delete record #$id");
$this->assertNull(DB::query("SELECT ID FROM DataObjectTest_Subclass WHERE ID = $id")->value(), "Couldn't delete record #$id");
$this->assertEqual(0, $obj->ID, 'ID not set to null when data-object deleted - %s');
// Test delete with onBeforeDelete()
$obj = new DataObjectTest_NoDefaults();
$obj->Field2 = 10;
$obj->write();
$obj->delete();
$this->assertEqual(0, $obj->ID, 'ID not set to null when data-object deleted - %s');
$this->assertEqual(30, $obj->Field1, 'onBeforeDelete not getting executed -%s');
// Test delete with a broken onBeforeDelete()
$obj = new DataObjectTest_FinalOne();
$obj->write();
$obj->delete();
$this->assertError(null, 'Broken onBeforeDelete not detected - %s');
}
/**
* Test update(), forceChange(), onBeforeWrite(), setField()
*/
function testUpdate() {
// Test adding some values and writing
$obj = new DataObjectTest_Class();
$obj->Field2 = "grumpy";
$obj->write();
$this->assertEqual("grumpy", DB::query("SELECT Field2 FROM DataObjectTest_Class WHERE ID = $obj->ID")->value());
// Test adding some values are the same and writing, ensure that no DB query is made.
DB::$lastQuery = null;
$obj->Field2 = "grumpy";
$obj->Field1 = null;
$obj->write();
$this->assertNull(DB::$lastQuery, "Redundnant update called - %s");
// Similar test using update()
$obj2 = new DataObjectTest_Class();
$obj2->update(array('Field2' => "grumpy"));
$obj2->write();
$this->assertEqual("grumpy", DB::query("SELECT Field2 FROM DataObjectTest_Class WHERE ID = $obj2->ID")->value());
// Test adding some values are the same and writing, ensure that no DB query is made.
DB::$lastQuery = null;
$obj2->update(array('Field2' => "grumpy", 'Field1' => null));
$obj2->write();
$this->assertNull(DB::$lastQuery, "Redundnant update called - %s");
// Test forceChange() and update, ensure complete DB query is made.
$obj2->forceChange();
$obj2->write();
$this->assertNotNull(DB::$lastQuery, "Forced update not called - %s");
// Test writing with an onBeforeWrite()
$obj3 = new DataObjectTest_NoDefaults();
$obj3->Field2 = 10;
$obj3->write();
$this->assertEqual(50, $obj3->Field1);
$this->assertEqual(50, DB::query("SELECT Field1 FROM DataObjectTest_NoDefaults WHERE ID = $obj3->ID")->value());
// Test writing with a broken onBeforeWrite()
$obj4 = new DataObjectTest_BrokenBeforeWrite();
$obj4->Field2 = 7;
$obj4->write();
$this->assertError(null, "Broken onBeforeWrite not detected - %s");
// Cleanup
$obj->delete();
$obj2->delete();
$obj3->delete();
$obj4->delete();
}
/**
* Test can()
*/
function testPermissions() {
// STILL TO COME!
}
/**
* Test createComponent(), getComponent(), has_one()
*/
function testHasOne() {
// Get a has-one item using getComponent(), where one exists
// Get a has-one item using the FieldName() generated method.
// Get a has-one item that's defined on the parent class, using the FieldName() generated method
// Get a has-one item using getComponent(), where one doesn't exists
// Call has_one() for a specific field
// Call has_one() for all fields - base class
// Call has_one() for all fields - subclass
// Call has_one() for a non-existant field
}
/**
* Test createComponent(), getComponents(), has_many()
*/
function testHasMany() {
}
/**
* Test getManyManyComponents(), many_many()
*/
function testManyMany() {
}
/**
* Test defineMethods()
*/
function testDefineMethods() {
// Create an object with has-one, has-many, many-many joins
// Create another one of those objects, ensure that the same methods are set up
}
/**
* Test fieldExists()
*/
function testFieldExists() {
$obj1 = singleton("DataObjectTest_Class");
$obj2 = singleton("DataObjectTest_Subclass");
// Test that fieldExists() returns field from this class
$this->assertEqual('Int', $obj1->fieldExists('Field1'));
// Test that fieldExists() returns has-one from this class
$this->assertEqual('Int', $obj1->fieldExists('Link1ID'));
// Test that fieldExists() doesn't return field from parent class
$this->assertEqual('Date', $obj2->fieldExists('Field3'));
$this->assertFalse($obj2->fieldExists('Field1'));
// Test that fieldExists() doesn't return a totally unrelated field
$this->assertFalse($obj2->fieldExists('afsdafasdfdsafs'));
// Test that fieldExists() doesnt' return has-one from parent class
$this->assertFalse($obj2->fieldExists('Link1ID'));
// Test ID, ClassName, LastEdited and Created on base class
$this->assertEqual('Enum', $obj1->fieldExists('ClassName'));
$this->assertEqual('Datetime', $obj1->fieldExists('LastEdited'));
$this->assertEqual('Datetime', $obj1->fieldExists('Created'));
$this->assertEqual('Int', $obj1->fieldExists('ID'));
// Test ID on sub-class
$this->assertEqual('Int', $obj1->fieldExists('ID'));
}
/**
* Test getAllFields()
*/
function testGetAllFields() {
// Call getAllFields() on a base class
$obj1 = DataObject::get_by_id("DataObjectTest_Class",1);
$getAllFields1 = $obj1->getAllFields();
$this->assertEqual($getAllFields1['ID'],1);
$this->assertEqual($getAllFields1['ClassName'],"DataObjectTest_Class");
$this->assertEqual($getAllFields1['Field1'],1);
$this->assertEqual($getAllFields1['Field2'],'red');
// Call getAllFields() on a sub-class
$obj2 = DataObject::get_by_id("DataObjectTest_Class",6);
$getAllFields2 = $obj2->getAllFields();
$this->assertEqual($getAllFields2['ID'],6);
$this->assertEqual($getAllFields2['ClassName'],"DataObjectTest_Subclass");
$this->assertEqual($getAllFields2['Field1'],6);
$this->assertEqual($getAllFields2['Field2'],'red');
$this->assertEqual($getAllFields2['Field3'],'2006-01-10');
}
/**
* Test setCastedField
*/
function testSetCastedField() {
// Test setting of a date field - different date formats
$obj = new DataObjectTest_Subclass();
$dates = array(
"10 Jan 2006" => "2006-01-10",
"10/3/2006" => "2006-03-10",
"2006-01-01" => "2006-01-01",
);
foreach($dates as $input => $internal) {
$obj->setCastedField("Field3", $input);
$this->assertEqual($internal, $obj->Field3, "Couldn't convert $input to $internal - %s");
}
}
/**
* Test get_one(), flush_cache()
*/
function testGetOneCache() {
// Get an object
$obj1 = DataObject::get_by_id("DataObjectTest_Class",6);
$this->assertEqual(6,$obj1->ID);
// Get the same object again, verify that DB query wasn't made
DB::$lastQuery = null;
$obj2 = DataObject::get_by_id("DataObjectTest_Class",6);
$this->assertNull(DB::$lastQuery);
$this->assertEqual(6,$obj2->ID);
// Get an object with the same filter on a different class
$obj3 = DataObject::get_by_id("DataObjectTest_Subclass",6);
$this->assertNotNull(DB::$lastQuery);
$this->assertEqual(6,$obj3->ID);
// Flush the cache
$obj1->flushCache();
// Get the same object again, verify that DB query was made
DB::$lastQuery = null;
$obj4 = DataObject::get_by_id("DataObjectTest_Class",6);
$this->assertNotNull(DB::$lastQuery);
$this->assertEqual(6,$obj4->ID);
}
}
/// These DataObjects are a "fire-test" for the DataObject system in general.
class DataObjectTest_Class extends DataObject {
static $db = array(
"Field1" => "Int",
"Field2" => "Varchar",
);
static $has_one = array(
"Link1" => "DataObjectTest_NoDefaults",
);
static $defaults = array(
"Field2" => "happy",
);
}
class DataObjectTest_Subclass extends DataObjectTest_Class {
static $db = array(
"Field3" => "Date",
"AnotherField" => "Int",
"VarcharField" => "Varchar",
"DuplicateField" => "Int",
);
static $has_one = array(
"Link2" => "DataObjectTest_FinalOne",
"Link3" => "DataObjectTest_NoDefaults",
);
static $defaults = array(
"AnotherField" => 7,
);
}
class DataObjectTest_OtherSubclass extends DataObjectTest_Class {
static $db = array(
"DuplicateField" => "Int",
"OtherField" => "Int",
);
}
class DataObjectTest_NoDefaults extends DataObject {
static $db = array(
"Field1" => "Int",
"Field2" => "Boolean",
);
function onBeforeDelete() {
parent::onBeforeDelete();
$this->Field1 = $this->Field2 * 3;
}
function onBeforeWrite() {
parent::onBeforeWrite();
$this->Field1 = $this->Field2 * 5;
}
}
class DataObjectTest_FinalOne extends DataObject {
static $db = array(
"Field1" => "Int",
);
// Broken onBeforeDelete
function onBeforeDelete() {
if(!$this->Field1) $this->Field1 = 490;
}
}
class DataObjectTest_BrokenBeforeWrite extends DataObject {
static $db = array(
"Field1" => "Int",
);
// Broken onBeforeWrite
function onBeforeWrite() {
if(!$this->Field1) $this->Field1 = 490;
}
}
?>

View File

@ -1,119 +0,0 @@
<?php
class DirectorTest extends UnitTestCase {
public $whatsBeingTested = "URL direction and URL generation are being tested";
public $testComplete = "green";
function testURLGeneration() {
// Test baseURL
$this->assertEqual(Director::baseURL() . 'sapphire/main.php', $_SERVER['SCRIPT_NAME']);
$this->assertEqual(Director::baseFolder() . '/sapphire/main.php', $_SERVER['SCRIPT_FILENAME']);
// Tets fileExists
$this->assertTrue(Director::fileExists('sapphire/main.php'));
// Test make relative
$testURL = Director::absoluteBaseURL() . 'someRandomUrl/yes';
$this->assertEqual(Director::makeRelative($testURL), 'someRandomUrl/yes', "Director::makeRelative($testURL) broken");
// Throw an extra slash in there and see what happens
$testURL = Director::absoluteBaseURL() . '/someRandomUrl/yes';
$this->assertEqual(Director::makeRelative($testURL), 'someRandomUrl/yes', "Director::makeRelative($testURL) broken");
// Test mucking with HTTP host stuff
$oldServer = $_SERVER;
$_SERVER['HTTP_HOST'] = "www.testsomething.com";
$_SERVER['PORT'] = "www.testsomething.com";
$_SERVER['SCRIPT_NAME'] = "/auntBetty/sapphire/main.php5";
$_SERVER['SCRIPT_FILENAME'] = "/home/shares/other/auntBetty/sapphire/main.php5";
$_SERVER['SSL'] = true;
$this->assertEqual(Director::absoluteBaseURL(), 'https://www.testsomething.com/auntBetty/');
$this->assertEqual(Director::baseURL(), '/auntBetty/');
$this->assertEqual(Director::baseFolder(), '/home/shares/other/auntBetty');
$_SERVER = $oldServer;
}
/**
* Test that isLive(), isTest() and isDev() work properly.
*/
function testLiveChecking() {
$urls = array(
'test' => 'isTest',
'test.totallydigital.co.nz' => 'isTest',
'manu.test.totallydigital.co.nz' => 'isTest',
'manu.test.silverstripe.com' => 'isTest',
'test.transport.govt.nz' => 'isTest',
'dev/internaltest' => 'isTest',
'dev/internaltest/mainsite' => 'isTest',
'dev' => 'isDev',
'dev.totallydigital.co.nz' => 'isDev',
'dev.silverstripe.com' => 'isDev',
'manu.dev.silverstripe.com' => 'isDev',
'dev.transport.govt.nz' => 'isDev',
'www.transport.govt.nz' => 'isLive',
'testsevices.co.nz' => 'isLive',
'dev-experts.com' => 'isLive',
'www.testing.co.nz' => 'isLive',
'nationaltesting.co.nz' => 'isLive',
'www.perweek.co.nz' => 'isLive',
);
$oldServer = $_SERVER;
foreach($urls as $url => $siteType) {
list($_SERVER['HTTP_HOST'],$uri) = explode('/',$url,2);
$_SERVER['REQUEST_URI'] = '/' . $uri;
$this->assertEqual(Director::isTest(), $siteType == 'isTest', "Director::isTest() $url not $siteType");
$this->assertEqual(Director::isDev(), $siteType == 'isDev', "Director::isDev() $url not $siteType");
$this->assertEqual(Director::isLive(), $siteType == 'isLive', "Director::isLive() $url not $siteType");
}
$_SERVER = $oldServer;
}
function testGetControllerForURL() {
$d = new Director();
$base = Director::baseURL();
// Test a bunnch of different URLs
$this->assertEqual($d->getControllerForURL(""), "redirect:{$base}home/");
$this->assertEqual($d->getControllerForURL("home")->class, "ModelAsController");
$this->assertEqual($d->getControllerForURL("Security")->class, "Security");
$this->assertEqual($d->getControllerForURL("Security/")->class, "Security");
$controller = $d->getControllerForURL("Security/testAction/asdfa/dsfasdf");
$urlParams = $controller->getURLParams();
$this->assertEqual($controller->class, "Security");
$this->assertEqual($urlParams['Action'], "testAction");
$this->assertEqual($d->getControllerForURL("images")->class, "Image_Uploader");
$this->assertEqual($d->getControllerForURL("Security/")->class, "Security");
$this->assertEqual($d->getControllerForURL("Security/asdf/asdfa/dsfasdf")->class, "Security");
}
function testRules() {
$d = new Director();
// Test adding rules of different priorities
Director::addRules(100, array(
'directorTest/$Action' => 'Security'
));
$this->assertEqual($d->getControllerForURL("directorTest/rar")->class, 'Security');
Director::addRules(200, array(
'directorTest/$Action' => 'Image_Uploader'
));
$this->assertEqual($d->getControllerForURL("directorTest/rar")->class, 'Image_Uploader');
}
}
?>

View File

@ -1,8 +0,0 @@
<?php
class VersionedTest extends UnitTestCase {
public $whatsBeingTested = "Nothing at all!";
public $testComplete = "red";
}
?>